/markdown/extensions/toc.py
https://github.com/Wilfred/Python-Markdown · Python · 157 lines · 107 code · 23 blank · 27 comment · 30 complexity · e6cfd855369aebc98de504ea0afa1a2f MD5 · raw file
- """
- Table of Contents Extension for Python-Markdown
- * * *
- (c) 2008 [Jack Miller](http://codezen.org)
- Dependencies:
- * [Markdown 2.1+](http://www.freewisdom.org/projects/python-markdown/)
- """
- import markdown
- from markdown.util import etree
- from markdown.extensions.headerid import slugify, unique, itertext
- import re
- class TocTreeprocessor(markdown.treeprocessors.Treeprocessor):
- # Iterator wrapper to get parent and child all at once
- def iterparent(self, root):
- for parent in root.getiterator():
- for child in parent:
- yield parent, child
- def run(self, doc):
- marker_found = False
- div = etree.Element("div")
- div.attrib["class"] = "toc"
- last_li = None
- # Add title to the div
- if self.config["title"]:
- header = etree.SubElement(div, "span")
- header.attrib["class"] = "toctitle"
- header.text = self.config["title"]
- level = 0
- list_stack=[div]
- header_rgx = re.compile("[Hh][123456]")
- # Get a list of id attributes
- used_ids = []
- for c in doc.getiterator():
- if "id" in c.attrib:
- used_ids.append(c.attrib["id"])
- for (p, c) in self.iterparent(doc):
- text = ''.join(itertext(c)).strip()
- if not text:
- continue
- # To keep the output from screwing up the
- # validation by putting a <div> inside of a <p>
- # we actually replace the <p> in its entirety.
- # We do not allow the marker inside a header as that
- # would causes an enless loop of placing a new TOC
- # inside previously generated TOC.
- if c.text and c.text.strip() == self.config["marker"] and \
- not header_rgx.match(c.tag) and c.tag not in ['pre', 'code']:
- for i in range(len(p)):
- if p[i] == c:
- p[i] = div
- break
- marker_found = True
-
- if header_rgx.match(c.tag):
- tag_level = int(c.tag[-1])
-
- while tag_level < level:
- list_stack.pop()
- level -= 1
- if tag_level > level:
- if self.config['ordered']:
- newlist = etree.Element("ol")
- else:
- newlist = etree.Element("ul")
- if last_li:
- last_li.append(newlist)
- else:
- list_stack[-1].append(newlist)
- list_stack.append(newlist)
- if level == 0:
- level = tag_level
- else:
- level += 1
- # Do not override pre-existing ids
- if not "id" in c.attrib:
- id = unique(self.config["slugify"](text, '-'), used_ids)
- c.attrib["id"] = id
- else:
- id = c.attrib["id"]
- # List item link, to be inserted into the toc div
- last_li = etree.Element("li")
- link = etree.SubElement(last_li, "a")
- link.text = text
- link.attrib["href"] = '#' + id
- if int(self.config["anchorlink"]):
- anchor = etree.Element("a")
- anchor.text = c.text
- anchor.attrib["href"] = "#" + id
- anchor.attrib["class"] = "toclink"
- c.text = ""
- for elem in c.getchildren():
- anchor.append(elem)
- c.remove(elem)
- c.append(anchor)
- list_stack[-1].append(last_li)
- if not marker_found:
- # searialize and attach to markdown instance.
- prettify = self.markdown.treeprocessors.get('prettify')
- if prettify: prettify.run(div)
- toc = self.markdown.serializer(div)
- for pp in self.markdown.postprocessors.values():
- toc = pp.run(toc)
- self.markdown.toc = toc
- class TocExtension(markdown.Extension):
- def __init__(self, configs):
- self.config = { "marker" : ["[TOC]",
- "Text to find and replace with Table of Contents -"
- "Defaults to \"[TOC]\""],
- "slugify" : [slugify,
- "Function to generate anchors based on header text-"
- "Defaults to the headerid ext's slugify function."],
- "title" : [None,
- "Title to insert into TOC <div> - "
- "Defaults to None"],
- "anchorlink" : [0,
- "1 if header should be a self link"
- "Defaults to 0"],
- "ordered" : [False,
- "If true, <ol> tags are used instead of <ul> tags"
- "Defaults to False"]}
- for key, value in configs:
- self.setConfig(key, value)
- def extendMarkdown(self, md, md_globals):
- tocext = TocTreeprocessor(md)
- tocext.config = self.getConfigs()
- # Headerid ext is set to '>inline'. With this set to '<prettify',
- # it should always come after headerid ext (and honor ids assinged
- # by the header id extension) if both are used. Same goes for
- # attr_list extension. This must come last because we don't want
- # to redefine ids after toc is created. But we do want toc prettified.
- md.treeprocessors.add("toc", tocext, "<prettify")
-
- def makeExtension(configs={}):
- return TocExtension(configs=configs)