~cbehrens/nova/lp844160-build-works-with-zones

« back to all changes in this revision

Viewing changes to vendor/tornado/website/markdown/extensions/toc.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

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