/deps/build/Markdown/markdown/extensions/toc.py

https://github.com/smorstabilini/ilmioquartiere · Python · 140 lines · 93 code · 21 blank · 26 comment · 22 complexity · 758f05cc683d0e07daf137d8e4d43037 MD5 · raw file

  1. """
  2. Table of Contents Extension for Python-Markdown
  3. * * *
  4. (c) 2008 [Jack Miller](http://codezen.org)
  5. Dependencies:
  6. * [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
  7. """
  8. import markdown
  9. from markdown import etree
  10. import re
  11. class TocTreeprocessor(markdown.treeprocessors.Treeprocessor):
  12. # Iterator wrapper to get parent and child all at once
  13. def iterparent(self, root):
  14. for parent in root.getiterator():
  15. for child in parent:
  16. yield parent, child
  17. def run(self, doc):
  18. div = etree.Element("div")
  19. div.attrib["class"] = "toc"
  20. last_li = None
  21. # Add title to the div
  22. if self.config["title"][0]:
  23. header = etree.SubElement(div, "span")
  24. header.attrib["class"] = "toctitle"
  25. header.text = self.config["title"][0]
  26. level = 0
  27. list_stack=[div]
  28. header_rgx = re.compile("[Hh][123456]")
  29. # Get a list of id attributes
  30. used_ids = []
  31. for c in doc.getiterator():
  32. if "id" in c.attrib:
  33. used_ids.append(c.attrib["id"])
  34. for (p, c) in self.iterparent(doc):
  35. if not c.text:
  36. continue
  37. # To keep the output from screwing up the
  38. # validation by putting a <div> inside of a <p>
  39. # we actually replace the <p> in its entirety.
  40. # We do not allow the marker inside a header as that
  41. # would causes an enless loop of placing a new TOC
  42. # inside previously generated TOC.
  43. if c.text.find(self.config["marker"][0]) > -1 and not header_rgx.match(c.tag):
  44. for i in range(len(p)):
  45. if p[i] == c:
  46. p[i] = div
  47. break
  48. if header_rgx.match(c.tag):
  49. tag_level = int(c.tag[-1])
  50. # Regardless of how many levels we jumped
  51. # only one list should be created, since
  52. # empty lists containing lists are illegal.
  53. if tag_level < level:
  54. list_stack.pop()
  55. level = tag_level
  56. if tag_level > level:
  57. newlist = etree.Element("ul")
  58. if last_li:
  59. last_li.append(newlist)
  60. else:
  61. list_stack[-1].append(newlist)
  62. list_stack.append(newlist)
  63. level = tag_level
  64. # Do not override pre-existing ids
  65. if not "id" in c.attrib:
  66. id = self.config["slugify"][0](c.text)
  67. if id in used_ids:
  68. ctr = 1
  69. while "%s_%d" % (id, ctr) in used_ids:
  70. ctr += 1
  71. id = "%s_%d" % (id, ctr)
  72. used_ids.append(id)
  73. c.attrib["id"] = id
  74. else:
  75. id = c.attrib["id"]
  76. # List item link, to be inserted into the toc div
  77. last_li = etree.Element("li")
  78. link = etree.SubElement(last_li, "a")
  79. link.text = c.text
  80. link.attrib["href"] = '#' + id
  81. if int(self.config["anchorlink"][0]):
  82. anchor = etree.SubElement(c, "a")
  83. anchor.text = c.text
  84. anchor.attrib["href"] = "#" + id
  85. anchor.attrib["class"] = "toclink"
  86. c.text = ""
  87. list_stack[-1].append(last_li)
  88. class TocExtension(markdown.Extension):
  89. def __init__(self, configs):
  90. self.config = { "marker" : ["[TOC]",
  91. "Text to find and replace with Table of Contents -"
  92. "Defaults to \"[TOC]\""],
  93. "slugify" : [self.slugify,
  94. "Function to generate anchors based on header text-"
  95. "Defaults to a built in slugify function."],
  96. "title" : [None,
  97. "Title to insert into TOC <div> - "
  98. "Defaults to None"],
  99. "anchorlink" : [0,
  100. "1 if header should be a self link"
  101. "Defaults to 0"]}
  102. for key, value in configs:
  103. self.setConfig(key, value)
  104. # This is exactly the same as Django's slugify
  105. def slugify(self, value):
  106. """ Slugify a string, to make it URL friendly. """
  107. import unicodedata
  108. value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
  109. value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
  110. return re.sub('[-\s]+','-',value)
  111. def extendMarkdown(self, md, md_globals):
  112. tocext = TocTreeprocessor(md)
  113. tocext.config = self.config
  114. md.treeprocessors.add("toc", tocext, "_begin")
  115. def makeExtension(configs={}):
  116. return TocExtension(configs=configs)