toc.py revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
15d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# markdown is released under the BSD license
25d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)
35d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
45d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Copyright 2004 Manfred Stienstra (the original version)
55d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
65d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# All rights reserved.
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
85d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# Redistribution and use in source and binary forms, with or without
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# modification, are permitted provided that the following conditions are met:
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# *   Redistributions of source code must retain the above copyright
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#     notice, this list of conditions and the following disclaimer.
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# *   Redistributions in binary form must reproduce the above copyright
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#     notice, this list of conditions and the following disclaimer in the
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#     documentation and/or other materials provided with the distribution.
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# *   Neither the name of the <organization> nor the
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#     names of its contributors may be used to endorse or promote products
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#     derived from this software without specific prior written permission.
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# THIS SOFTWARE IS PROVIDED BY THE PYTHON MARKDOWN PROJECT ''AS IS'' AND ANY
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO THE PYTHON MARKDOWN PROJECT
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)# POSSIBILITY OF SUCH DAMAGE.
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)"""
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)Table of Contents Extension for Python-Markdown
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)* * *
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)(c) 2008 [Jack Miller](http://codezen.org)
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)Dependencies:
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)* [Markdown 2.1+](http://packages.python.org/Markdown/)
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)"""
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from __future__ import absolute_import
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from __future__ import unicode_literals
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from . import Extension
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from ..treeprocessors import Treeprocessor
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from ..util import etree
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from .headerid import slugify, unique, itertext
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import re
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def order_toc_list(toc_list):
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """Given an unsorted list with errors and skips, return a nested one.
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    [{'level': 1}, {'level': 2}]
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    =>
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    [{'level': 1, 'children': [{'level': 2, 'children': []}]}]
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    A wrong list is also converted:
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    [{'level': 2}, {'level': 1}]
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    =>
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    [{'level': 2, 'children': []}, {'level': 1, 'children': []}]
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def build_correct(remaining_list, prev_elements=[{'level': 1000}]):
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if not remaining_list:
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return [], []
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        current = remaining_list.pop(0)
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if not 'children' in current.keys():
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            current['children'] = []
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if not prev_elements:
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # This happens for instance with [8, 1, 1], ie. when some
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # header level is outside a scope. We treat it as a
775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # top-level
785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            next_elements, children = build_correct(remaining_list, [current])
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            current['children'].append(children)
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return [current] + next_elements, []
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        prev_element = prev_elements.pop()
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        children = []
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        next_elements = []
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # Is current part of the child list or next list?
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if current['level'] > prev_element['level']:
875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            #print "%d is a child of %d" % (current['level'], prev_element['level'])
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            prev_elements.append(prev_element)
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            prev_elements.append(current)
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            prev_element['children'].append(current)
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            next_elements2, children2 = build_correct(remaining_list, prev_elements)
925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            children += children2
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            next_elements += next_elements2
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        else:
955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            #print "%d is ancestor of %d" % (current['level'], prev_element['level'])
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if not prev_elements:
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                #print "No previous elements, so appending to the next set"
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                next_elements.append(current)
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                prev_elements = [current]
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                next_elements2, children2 = build_correct(remaining_list, prev_elements)
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                current['children'].extend(children2)
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            else:
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                #print "Previous elements, comparing to those first"
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                remaining_list.insert(0, current)
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                next_elements2, children2 = build_correct(remaining_list, prev_elements)
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                children.extend(children2)
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            next_elements += next_elements2
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return next_elements, children
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    ordered_list, __ = build_correct(toc_list)
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return ordered_list
1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class TocTreeprocessor(Treeprocessor):
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    # Iterator wrapper to get parent and child all at once
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def iterparent(self, root):
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        for parent in root.getiterator():
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            for child in parent:
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                yield parent, child
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def add_anchor(self, c, elem_id): #@ReservedAssignment
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if self.use_anchors:
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            anchor = etree.Element("a")
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            anchor.text = c.text
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            anchor.attrib["href"] = "#" + elem_id
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            anchor.attrib["class"] = "toclink"
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            c.text = ""
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            for elem in c.getchildren():
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                anchor.append(elem)
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                c.remove(elem)
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            c.append(anchor)
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def build_toc_etree(self, div, toc_list):
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # Add title to the div
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if self.config["title"]:
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            header = etree.SubElement(div, "span")
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            header.attrib["class"] = "toctitle"
1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            header.text = self.config["title"]
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        def build_etree_ul(toc_list, parent):
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            ul = etree.SubElement(parent, "ul")
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            for item in toc_list:
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                # List item link, to be inserted into the toc div
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                li = etree.SubElement(ul, "li")
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                link = etree.SubElement(li, "a")
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                link.text = item.get('name', '')
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                link.attrib["href"] = '#' + item.get('id', '')
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                if item['children']:
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    build_etree_ul(item['children'], li)
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return ul
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return build_etree_ul(toc_list, div)
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def run(self, doc):
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        div = etree.Element("div")
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        div.attrib["class"] = "toc"
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        header_rgx = re.compile("[Hh][123456]")
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.use_anchors = self.config["anchorlink"] in [1, '1', True, 'True', 'true']
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # Get a list of id attributes
1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        used_ids = set()
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        for c in doc.getiterator():
1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if "id" in c.attrib:
1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                used_ids.add(c.attrib["id"])
1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        toc_list = []
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        marker_found = False
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        for (p, c) in self.iterparent(doc):
1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            text = ''.join(itertext(c)).strip()
1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if not text:
1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                continue
1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # To keep the output from screwing up the
1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # validation by putting a <div> inside of a <p>
1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # we actually replace the <p> in its entirety.
1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # We do not allow the marker inside a header as that
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # would causes an enless loop of placing a new TOC
1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # inside previously generated TOC.
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if c.text and c.text.strip() == self.config["marker"] and \
1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               not header_rgx.match(c.tag) and c.tag not in ['pre', 'code']:
1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                for i in range(len(p)):
1865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    if p[i] == c:
1875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        p[i] = div
1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        break
1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                marker_found = True
1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if header_rgx.match(c.tag):
1925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                # Do not override pre-existing ids
1945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                if not "id" in c.attrib:
1955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    elem_id = unique(self.config["slugify"](text, '-'), used_ids)
1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    c.attrib["id"] = elem_id
1975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                else:
1985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    elem_id = c.attrib["id"]
1995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                tag_level = int(c.tag[-1])
2015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                toc_list.append({'level': tag_level,
2035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    'id': elem_id,
2045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    'name': text})
2055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                self.add_anchor(c, elem_id)
2075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        toc_list_nested = order_toc_list(toc_list)
2095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.build_toc_etree(div, toc_list_nested)
2105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        prettify = self.markdown.treeprocessors.get('prettify')
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if prettify: prettify.run(div)
2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if not marker_found:
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            # serialize and attach to markdown instance.
2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            toc = self.markdown.serializer(div)
2155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            for pp in self.markdown.postprocessors.values():
2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                toc = pp.run(toc)
2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            self.markdown.toc = toc
2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class TocExtension(Extension):
2215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    TreeProcessorClass = TocTreeprocessor
2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def __init__(self, configs=[]):
2255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.config = { "marker" : ["[TOC]",
2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            "Text to find and replace with Table of Contents -"
2275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            "Defaults to \"[TOC]\""],
2285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        "slugify" : [slugify,
2295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            "Function to generate anchors based on header text-"
2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            "Defaults to the headerid ext's slugify function."],
2315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        "title" : [None,
2325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            "Title to insert into TOC <div> - "
2335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            "Defaults to None"],
2345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        "anchorlink" : [0,
2355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            "1 if header should be a self link"
2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            "Defaults to 0"]}
2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        for key, value in configs:
2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            self.setConfig(key, value)
2405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def extendMarkdown(self, md, md_globals):
2425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        tocext = self.TreeProcessorClass(md)
2435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        tocext.config = self.getConfigs()
2445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # Headerid ext is set to '>prettify'. With this set to '_end',
2455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # it should always come after headerid ext (and honor ids assinged
2465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # by the header id extension) if both are used. Same goes for
2475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # attr_list extension. This must come last because we don't want
2485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # to redefine ids after toc is created. But we do want toc prettified.
2495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        md.treeprocessors.add("toc", tocext, "_end")
2505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def makeExtension(configs={}):
2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return TocExtension(configs=configs)
254