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)========================= FOOTNOTES =================================
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)This section adds footnote handling to markdown.  It can be used as
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)an example for extending python-markdown with relatively complex
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)functionality.  While in this case the extension is included inside
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)the module itself, it could just as easily be added from outside the
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)module.  Not that all markdown classes above are ignorant about
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)footnotes.  All footnote functionality is provided separately and
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)then added to the markdown instance at the run time.
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)Footnote functionality is attached by calling extendMarkdown()
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)method of FootnoteExtension.  The method also registers the
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)extension to allow it's state to be reset by a call to reset()
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)method.
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)Example:
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    Footnotes[^1] have a label[^label] and a definition[^!DEF].
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    [^1]: This is a footnote
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    [^label]: A footnote on "label"
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    [^!DEF]: The footnote for definition
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)"""
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from __future__ import absolute_import
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from __future__ import unicode_literals
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from . import Extension
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from ..preprocessors import Preprocessor
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from ..inlinepatterns import Pattern
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from ..treeprocessors import Treeprocessor
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from ..postprocessors import Postprocessor
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from ..util import etree, text_type
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)from ..odict import OrderedDict
675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)import re
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)FN_BACKLINK_TEXT = "zz1337820767766393qq"
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)NBSP_PLACEHOLDER =  "qq3936677670287331zz"
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)DEF_RE = re.compile(r'[ ]{0,3}\[\^([^\]]*)\]:\s*(.*)')
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TABBED_RE = re.compile(r'((\t)|(    ))(.*)')
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class FootnoteExtension(Extension):
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """ Footnote Extension. """
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def __init__ (self, configs):
785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """ Setup configs. """
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.config = {'PLACE_MARKER':
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                       ["///Footnotes Go Here///",
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        "The text string that marks where the footnotes go"],
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                       'UNIQUE_IDS':
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                       [False,
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        "Avoid name collisions across "
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        "multiple calls to reset()."],
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                       "BACKLINK_TEXT":
875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                       ["&#8617;",
885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        "The text string that links from the footnote to the reader's place."]
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                       }
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        for key, value in configs:
925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            self.config[key][0] = value
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # In multiple invocations, emit links that don't get tangled.
955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.unique_prefix = 0
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.reset()
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def extendMarkdown(self, md, md_globals):
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """ Add pieces to Markdown. """
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        md.registerExtension(self)
1025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.parser = md.parser
1035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.md = md
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.sep = ':'
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if self.md.output_format in ['html5', 'xhtml5']:
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            self.sep = '-'
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # Insert a preprocessor before ReferencePreprocessor
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        md.preprocessors.add("footnote", FootnotePreprocessor(self),
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                             "<reference")
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # Insert an inline pattern before ImageReferencePattern
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        md.inlinePatterns.add("footnote", FootnotePattern(FOOTNOTE_RE, self),
1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                              "<reference")
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # Insert a tree-processor that would actually add the footnote div
1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # This must be before all other treeprocessors (i.e., inline and
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # codehilite) so they can run on the the contents of the div.
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        md.treeprocessors.add("footnote", FootnoteTreeprocessor(self),
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                 "_begin")
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        # Insert a postprocessor after amp_substitute oricessor
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        md.postprocessors.add("footnote", FootnotePostprocessor(self),
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                  ">amp_substitute")
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def reset(self):
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """ Clear the footnotes on reset, and prepare for a distinct document. """
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.footnotes = OrderedDict()
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.unique_prefix += 1
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def findFootnotesPlaceholder(self, root):
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """ Return ElementTree Element that contains Footnote placeholder. """
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        def finder(element):
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            for child in element:
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                if child.text:
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    if child.text.find(self.getConfig("PLACE_MARKER")) > -1:
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        return child, element, True
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                if child.tail:
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    if child.tail.find(self.getConfig("PLACE_MARKER")) > -1:
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        return child, element, False
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                finder(child)
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return None
1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        res = finder(root)
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return res
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def setFootnote(self, id, text):
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """ Store a footnote for later retrieval. """
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.footnotes[id] = text
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def makeFootnoteId(self, id):
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """ Return footnote link id. """
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if self.getConfig("UNIQUE_IDS"):
1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return 'fn%s%d-%s' % (self.sep, self.unique_prefix, id)
1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        else:
1535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return 'fn%s%s' % (self.sep, id)
1545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def makeFootnoteRefId(self, id):
1565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """ Return footnote back-link id. """
1575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if self.getConfig("UNIQUE_IDS"):
1585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return 'fnref%s%d-%s' % (self.sep, self.unique_prefix, id)
1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        else:
1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return 'fnref%s%s' % (self.sep, id)
1615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def makeFootnotesDiv(self, root):
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """ Return div of footnotes as et Element. """
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if not list(self.footnotes.keys()):
1665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return None
1675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        div = etree.Element("div")
1695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        div.set('class', 'footnote')
1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        etree.SubElement(div, "hr")
1715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        ol = etree.SubElement(div, "ol")
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        for id in self.footnotes.keys():
1745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            li = etree.SubElement(ol, "li")
1755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            li.set("id", self.makeFootnoteId(id))
1765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            self.parser.parseChunk(li, self.footnotes[id])
1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            backlink = etree.Element("a")
1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            backlink.set("href", "#" + self.makeFootnoteRefId(id))
1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if self.md.output_format not in ['html5', 'xhtml5']:
1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                backlink.set("rev", "footnote") # Invalid in HTML5
1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            backlink.set("class", "footnote-backref")
1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            backlink.set("title", "Jump back to footnote %d in the text" % \
1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            (self.footnotes.index(id)+1))
1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            backlink.text = FN_BACKLINK_TEXT
1855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if li.getchildren():
1875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                node = li[-1]
1885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                if node.tag == "p":
1895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    node.text = node.text + NBSP_PLACEHOLDER
1905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    node.append(backlink)
1915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                else:
1925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    p = etree.SubElement(li, "p")
1935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    p.append(backlink)
1945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return div
1955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class FootnotePreprocessor(Preprocessor):
1985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """ Find all footnote references and store for later use. """
1995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def __init__ (self, footnotes):
2015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.footnotes = footnotes
2025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def run(self, lines):
2045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """
2055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        Loop through lines and find, set, and remove footnote definitions.
2065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        Keywords:
2085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        * lines: A list of lines of text
2105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        Return: A list of lines of text with footnote definitions removed.
2125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """
2145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        newlines = []
2155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        i = 0
2165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        while True:
2175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            m = DEF_RE.match(lines[i])
2185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if m:
2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                fn, _i = self.detectTabbed(lines[i+1:])
2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                fn.insert(0, m.group(2))
2215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                i += _i-1 # skip past footnote
2225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                self.footnotes.setFootnote(m.group(1), "\n".join(fn))
2235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            else:
2245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                newlines.append(lines[i])
2255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if len(lines) > i+1:
2265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                i += 1
2275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            else:
2285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                break
2295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return newlines
2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def detectTabbed(self, lines):
2325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """ Find indented text and remove indent before further proccesing.
2335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        Keyword arguments:
2355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        * lines: an array of strings
2375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        Returns: a list of post processed items and the index of last line.
2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        """
2415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        items = []
2425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        blank_line = False # have we encountered a blank line yet?
2435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        i = 0 # to keep track of where we are
2445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        def detab(line):
2465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            match = TABBED_RE.match(line)
2475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if match:
2485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)               return match.group(4)
2495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        for line in lines:
2515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if line.strip(): # Non-blank line
2525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                detabbed_line = detab(line)
2535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                if detabbed_line:
2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    items.append(detabbed_line)
2555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    i += 1
2565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    continue
2575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                elif not blank_line and not DEF_RE.match(line):
2585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    # not tabbed but still part of first par.
2595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    items.append(line)
2605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    i += 1
2615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    continue
2625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                else:
2635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    return items, i+1
2645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            else: # Blank line: _maybe_ we are done.
2665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                blank_line = True
2675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                i += 1 # advance
2685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                # Find the next non-blank line
2705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                for j in range(i, len(lines)):
2715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    if lines[j].strip():
2725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                        next_line = lines[j]; break
2735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                else:
2745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    break # There is no more text; we are done.
2755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                # Check if the next non-blank line is tabbed
2775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                if detab(next_line): # Yes, more work to do.
2785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    items.append("")
2795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    continue
2805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                else:
2815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    break # No, we are done.
2825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        else:
2835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            i += 1
2845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return items, i
2865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2885d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class FootnotePattern(Pattern):
2895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """ InlinePattern for footnote markers in a document's body text. """
2905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def __init__(self, pattern, footnotes):
2925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        super(FootnotePattern, self).__init__(pattern)
2935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.footnotes = footnotes
2945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
2955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def handleMatch(self, m):
2965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        id = m.group(2)
2975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if id in self.footnotes.footnotes.keys():
2985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            sup = etree.Element("sup")
2995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            a = etree.SubElement(sup, "a")
3005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            sup.set('id', self.footnotes.makeFootnoteRefId(id))
3015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            a.set('href', '#' + self.footnotes.makeFootnoteId(id))
3025d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if self.footnotes.md.output_format not in ['html5', 'xhtml5']:
3035d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                a.set('rel', 'footnote') # invalid in HTML5
3045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            a.set('class', 'footnote-ref')
3055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            a.text = text_type(self.footnotes.footnotes.index(id) + 1)
3065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return sup
3075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        else:
3085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            return None
3095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class FootnoteTreeprocessor(Treeprocessor):
3125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """ Build and append footnote div to end of document. """
3135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def __init__ (self, footnotes):
3155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.footnotes = footnotes
3165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def run(self, root):
3185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        footnotesDiv = self.footnotes.makeFootnotesDiv(root)
3195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        if footnotesDiv:
3205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            result = self.footnotes.findFootnotesPlaceholder(root)
3215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            if result:
3225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                child, parent, isText = result
3235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                ind = parent.getchildren().index(child)
3245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                if isText:
3255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    parent.remove(child)
3265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    parent.insert(ind, footnotesDiv)
3275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                else:
3285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    parent.insert(ind + 1, footnotesDiv)
3295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                    child.tail = None
3305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            else:
3315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                root.append(footnotesDiv)
3325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)class FootnotePostprocessor(Postprocessor):
3345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """ Replace placeholders with html entities. """
3355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def __init__(self, footnotes):
3365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        self.footnotes = footnotes
3375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    def run(self, text):
3395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        text = text.replace(FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT"))
3405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return text.replace(NBSP_PLACEHOLDER, "&#160;")
3415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
3425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)def makeExtension(configs=[]):
3435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    """ Return an instance of the FootnoteExtension """
3445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return FootnoteExtension(configs=configs)
3455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
346