Skip to content

footnotes

Footnotes Extension for Python-Markdown.

Adds footnote handling to Python-Markdown.

See https://pythonhosted.org/Markdown/extensions/footnotes.html for documentation.

Copyright The Python Markdown Project

License: BSD

Attributes

NBSP_PLACEHOLDER = util.STX + 'qq3936677670287331zz' + util.ETX module-attribute

DEF_RE = re.compile('[ ]{0,3}\\[\\^([^\\]]*)\\]:\\s*(.*)') module-attribute

TABBED_RE = re.compile('((\\t)|( ))(.*)') module-attribute

RE_REF_ID = re.compile('(fnref)(\\d+)') module-attribute

Classes

FootnoteExtension(*args, **kwargs)

Bases: Extension

Footnote Extension.

Setup configs.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def __init__(self, *args, **kwargs):
    """Setup configs."""
    self.config = {
        'PLACE_MARKER':
            ["///Footnotes Go Here///",
             "The text string that marks where the footnotes go"],
        'UNIQUE_IDS':
            [False,
             "Avoid name collisions across "
             "multiple calls to reset()."],
        "BACKLINK_TEXT":
            ["↩",
             "The text string that links from the footnote "
             "to the reader's place."]
    }
    super(FootnoteExtension, self).__init__(*args, **kwargs)

    # In multiple invocations, emit links that don't get tangled.
    self.unique_prefix = 0
    self.found_refs = {}
    self.used_refs = set()

    self.reset()

Attributes

config = {'PLACE_MARKER': ['///Footnotes Go Here///', 'The text string that marks where the footnotes go'], 'UNIQUE_IDS': [False, 'Avoid name collisions across multiple calls to reset().'], 'BACKLINK_TEXT': ['↩', "The text string that links from the footnote to the reader's place."]} instance-attribute
unique_prefix = 0 instance-attribute
found_refs = {} instance-attribute
used_refs = set() instance-attribute

Functions

getConfig(key, default='')

Return a setting for the given key or an empty string.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/__init__.py
def getConfig(self, key, default=''):
    """Return a setting for the given key or an empty string."""
    if key in self.config:
        return self.config[key][0]
    else:
        return default
getConfigs()

Return all configs settings as a dict.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/__init__.py
def getConfigs(self):
    """Return all configs settings as a dict."""
    return dict([(key, self.getConfig(key)) for key in self.config.keys()])
getConfigInfo()

Return all config descriptions as a list of tuples.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/__init__.py
def getConfigInfo(self):
    """Return all config descriptions as a list of tuples."""
    return [(key, self.config[key][1]) for key in self.config.keys()]
setConfig(key, value)

Set a config setting for key with the given value.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/__init__.py
def setConfig(self, key, value):
    """Set a config setting for `key` with the given `value`."""
    if isinstance(self.config[key][0], bool):
        value = parseBoolValue(value)
    if self.config[key][0] is None:
        value = parseBoolValue(value, preserve_none=True)
    self.config[key][0] = value
setConfigs(items)

Set multiple config settings given a dict or list of tuples.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/__init__.py
def setConfigs(self, items):
    """Set multiple config settings given a dict or list of tuples."""
    if hasattr(items, 'items'):
        # it's a dict
        items = items.items()
    for key, value in items:
        self.setConfig(key, value)
extendMarkdown(md, md_globals)

Add pieces to Markdown.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def extendMarkdown(self, md, md_globals):
    """Add pieces to Markdown."""
    md.registerExtension(self)
    self.parser = md.parser
    self.md = md
    # Insert a preprocessor before ReferencePreprocessor
    md.preprocessors.add(
        "footnote", FootnotePreprocessor(self), "<reference"
    )
    # Insert an inline pattern before ImageReferencePattern
    FOOTNOTE_RE = r'\[\^([^\]]*)\]'  # blah blah [^1] blah
    md.inlinePatterns.add(
        "footnote", FootnotePattern(FOOTNOTE_RE, self), "<reference"
    )
    # Insert a tree-processor that would actually add the footnote div
    # This must be before all other treeprocessors (i.e., inline and
    # codehilite) so they can run on the the contents of the div.
    md.treeprocessors.add(
        "footnote", FootnoteTreeprocessor(self), "_begin"
    )

    # Insert a tree-processor that will run after inline is done.
    # In this tree-processor we want to check our duplicate footnote tracker
    # And add additional backrefs to the footnote pointing back to the
    # duplicated references.
    md.treeprocessors.add(
        "footnote-duplicate", FootnotePostTreeprocessor(self), '>inline'
    )

    # Insert a postprocessor after amp_substitute oricessor
    md.postprocessors.add(
        "footnote", FootnotePostprocessor(self), ">amp_substitute"
    )
reset()

Clear footnotes on reset, and prepare for distinct document.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def reset(self):
    """Clear footnotes on reset, and prepare for distinct document."""
    self.footnotes = OrderedDict()
    self.unique_prefix += 1
    self.found_refs = {}
    self.used_refs = set()
unique_ref(reference, found=False)

Get a unique reference if there are duplicates.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def unique_ref(self, reference, found=False):
    """Get a unique reference if there are duplicates."""
    if not found:
        return reference

    original_ref = reference
    while reference in self.used_refs:
        ref, rest = reference.split(self.get_separator(), 1)
        m = RE_REF_ID.match(ref)
        if m:
            reference = '%s%d%s%s' % (m.group(1), int(m.group(2))+1, self.get_separator(), rest)
        else:
            reference = '%s%d%s%s' % (ref, 2, self.get_separator(), rest)

    self.used_refs.add(reference)
    if original_ref in self.found_refs:
        self.found_refs[original_ref] += 1
    else:
        self.found_refs[original_ref] = 1
    return reference
findFootnotesPlaceholder(root)

Return ElementTree Element that contains Footnote placeholder.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def findFootnotesPlaceholder(self, root):
    """Return ElementTree Element that contains Footnote placeholder."""
    def finder(element):
        for child in element:
            if child.text:
                if child.text.find(self.getConfig("PLACE_MARKER")) > -1:
                    return child, element, True
            if child.tail:
                if child.tail.find(self.getConfig("PLACE_MARKER")) > -1:
                    return child, element, False
            child_res = finder(child)
            if child_res is not None:
                return child_res
        return None

    res = finder(root)
    return res
setFootnote(id, text)

Store a footnote for later retrieval.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def setFootnote(self, id, text):
    """Store a footnote for later retrieval."""
    self.footnotes[id] = text
get_separator()
Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def get_separator(self):
    if self.md.output_format in ['html5', 'xhtml5']:
        return '-'
    return ':'
makeFootnoteId(id)

Return footnote link id.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def makeFootnoteId(self, id):
    """Return footnote link id."""
    if self.getConfig("UNIQUE_IDS"):
        return 'fn%s%d-%s' % (self.get_separator(), self.unique_prefix, id)
    else:
        return 'fn%s%s' % (self.get_separator(), id)
makeFootnoteRefId(id, found=False)

Return footnote back-link id.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def makeFootnoteRefId(self, id, found=False):
    """Return footnote back-link id."""
    if self.getConfig("UNIQUE_IDS"):
        return self.unique_ref('fnref%s%d-%s' % (self.get_separator(), self.unique_prefix, id), found)
    else:
        return self.unique_ref('fnref%s%s' % (self.get_separator(), id), found)
makeFootnotesDiv(root)

Return div of footnotes as et Element.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def makeFootnotesDiv(self, root):
    """Return div of footnotes as et Element."""
    if not list(self.footnotes.keys()):
        return None

    div = util.etree.Element("div")
    div.set('class', 'footnote')
    util.etree.SubElement(div, "hr")
    ol = util.etree.SubElement(div, "ol")
    surrogate_parent = util.etree.Element("div")

    for id in self.footnotes.keys():
        li = util.etree.SubElement(ol, "li")
        li.set("id", self.makeFootnoteId(id))
        # Parse footnote with surrogate parent as li cannot be used.
        # List block handlers have special logic to deal with li.
        # When we are done parsing, we will copy everything over to li.
        self.parser.parseChunk(surrogate_parent, self.footnotes[id])
        for el in list(surrogate_parent):
            li.append(el)
            surrogate_parent.remove(el)
        backlink = util.etree.Element("a")
        backlink.set("href", "#" + self.makeFootnoteRefId(id))
        if self.md.output_format not in ['html5', 'xhtml5']:
            backlink.set("rev", "footnote")  # Invalid in HTML5
        backlink.set("class", "footnote-backref")
        backlink.set(
            "title",
            "Jump back to footnote %d in the text" %
            (self.footnotes.index(id)+1)
        )
        backlink.text = FN_BACKLINK_TEXT

        if li.getchildren():
            node = li[-1]
            if node.tag == "p":
                node.text = node.text + NBSP_PLACEHOLDER
                node.append(backlink)
            else:
                p = util.etree.SubElement(li, "p")
                p.append(backlink)
    return div

FootnotePreprocessor(footnotes)

Bases: Preprocessor

Find all footnote references and store for later use.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def __init__(self, footnotes):
    self.footnotes = footnotes

Attributes

markdown = markdown_instance instance-attribute
footnotes = footnotes instance-attribute

Functions

run(lines)

Loop through lines and find, set, and remove footnote definitions.

Keywords:

  • lines: A list of lines of text

Return: A list of lines of text with footnote definitions removed.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def run(self, lines):
    """Loop through lines and find, set, and remove footnote definitions.

    Keywords:

    * lines: A list of lines of text

    Return: A list of lines of text with footnote definitions removed.

    """
    newlines = []
    i = 0
    while True:
        m = DEF_RE.match(lines[i])
        if m:
            fn, _i = self.detectTabbed(lines[i+1:])
            fn.insert(0, m.group(2))
            i += _i-1  # skip past footnote
            self.footnotes.setFootnote(m.group(1), "\n".join(fn))
        else:
            newlines.append(lines[i])
        if len(lines) > i+1:
            i += 1
        else:
            break
    return newlines
detectTabbed(lines)

Find indented text and remove indent before further proccesing.

Parameters:

Name Type Description Default
lines list[str]

text lines

required

Returns:

Type Description
tuple[list, int]

post processed items and the index of last line.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def detectTabbed(self, lines):
    """Find indented text and remove indent before further proccesing.

    Args:
        lines (list[str]): text lines

    Returns:
        (tuple[list, int]): post processed items and the index of last line.

    """
    items = []
    blank_line = False  # have we encountered a blank line yet?
    i = 0  # to keep track of where we are

    def detab(line):
        match = TABBED_RE.match(line)
        if match:
            return match.group(4)

    for line in lines:
        if line.strip():  # Non-blank line
            detabbed_line = detab(line)
            if detabbed_line:
                items.append(detabbed_line)
                i += 1
                continue
            elif not blank_line and not DEF_RE.match(line):
                # not tabbed but still part of first par.
                items.append(line)
                i += 1
                continue
            else:
                return items, i+1

        else:  # Blank line: _maybe_ we are done.
            blank_line = True
            i += 1  # advance

            # Find the next non-blank line
            for j in range(i, len(lines)):
                if lines[j].strip():
                    next_line = lines[j]
                    break
            else:
                break  # There is no more text; we are done.

            # Check if the next non-blank line is tabbed
            if detab(next_line):  # Yes, more work to do.
                items.append("")
                continue
            else:
                break  # No, we are done.
    else:
        i += 1

    return items, i

FootnotePattern(pattern, footnotes)

Bases: Pattern

InlinePattern for footnote markers in a document's body text.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def __init__(self, pattern, footnotes):
    super(FootnotePattern, self).__init__(pattern)
    self.footnotes = footnotes

Attributes

pattern = pattern instance-attribute
compiled_re = re.compile('^(.*?)%s(.*)$' % pattern, re.DOTALL | re.UNICODE) instance-attribute
safe_mode = False instance-attribute
markdown = markdown_instance instance-attribute
footnotes = footnotes instance-attribute

Functions

getCompiledRegExp()

Return a compiled regular expression.

Source code in pyrevitlib/pyrevit/coreutils/markdown/inlinepatterns.py
def getCompiledRegExp(self):
    """Return a compiled regular expression."""
    return self.compiled_re
type()

Return class name, to define pattern type.

Source code in pyrevitlib/pyrevit/coreutils/markdown/inlinepatterns.py
def type(self):
    """Return class name, to define pattern type."""
    return self.__class__.__name__
unescape(text)

Return unescaped text given text with an inline placeholder.

Source code in pyrevitlib/pyrevit/coreutils/markdown/inlinepatterns.py
def unescape(self, text):
    """Return unescaped text given text with an inline placeholder."""
    try:
        stash = self.markdown.treeprocessors['inline'].stashed_nodes
    except KeyError:  # pragma: no cover
        return text

    def itertext(el):  # pragma: no cover
        """Reimplement Element.itertext for older python versions."""
        tag = el.tag
        if not isinstance(tag, util.string_type) and tag is not None:
            return
        if el.text:
            yield el.text
        for e in el:
            for s in itertext(e):
                yield s
            if e.tail:
                yield e.tail

    def get_stash(m):
        id = m.group(1)
        if id in stash:
            value = stash.get(id)
            if isinstance(value, util.string_type):
                return value
            else:
                # An etree Element - return text content only
                return ''.join(itertext(value))
    return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text)
handleMatch(m)
Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def handleMatch(self, m):
    id = m.group(2)
    if id in self.footnotes.footnotes.keys():
        sup = util.etree.Element("sup")
        a = util.etree.SubElement(sup, "a")
        sup.set('id', self.footnotes.makeFootnoteRefId(id, found=True))
        a.set('href', '#' + self.footnotes.makeFootnoteId(id))
        if self.footnotes.md.output_format not in ['html5', 'xhtml5']:
            a.set('rel', 'footnote')  # invalid in HTML5
        a.set('class', 'footnote-ref')
        a.text = unicode(self.footnotes.footnotes.index(id) + 1)
        return sup
    else:
        return None

FootnotePostTreeprocessor(footnotes)

Bases: Treeprocessor

Ammend footnote div with duplicates.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def __init__(self, footnotes):
    self.footnotes = footnotes

Attributes

markdown = markdown_instance instance-attribute
footnotes = footnotes instance-attribute

Functions

add_duplicates(li, duplicates)

Adjust current li and add the duplicates: fnref2, fnref3, etc.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def add_duplicates(self, li, duplicates):
    """Adjust current li and add the duplicates: fnref2, fnref3, etc."""
    for link in li.iter('a'):
        # Find the link that needs to be duplicated.
        if link.attrib.get('class', '') == 'footnote-backref':
            ref, rest = link.attrib['href'].split(self.footnotes.get_separator(), 1)
            # Duplicate link the number of times we need to
            # and point the to the appropriate references.
            links = []
            for index in range(2, duplicates + 1):
                sib_link = copy.deepcopy(link)
                sib_link.attrib['href'] = '%s%d%s%s' % (ref, index, self.footnotes.get_separator(), rest)
                links.append(sib_link)
                self.offset += 1
            # Add all the new duplicate links.
            el = list(li)[-1]
            for l in links:
                el.append(l)
            break
get_num_duplicates(li)

Get the number of duplicate refs of the footnote.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def get_num_duplicates(self, li):
    """Get the number of duplicate refs of the footnote."""
    fn, rest = li.attrib.get('id', '').split(self.footnotes.get_separator(), 1)
    link_id = '%sref%s%s' % (fn, self.footnotes.get_separator(), rest)
    return self.footnotes.found_refs.get(link_id, 0)
handle_duplicates(parent)

Find duplicate footnotes and format and add the duplicates.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def handle_duplicates(self, parent):
    """Find duplicate footnotes and format and add the duplicates."""
    for li in list(parent):
        # Check number of duplicates footnotes and insert
        # additional links if needed.
        count = self.get_num_duplicates(li)
        if count > 1:
            self.add_duplicates(li, count)
run(root)

Crawl the footnote div and add missing duplicate footnotes.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def run(self, root):
    """Crawl the footnote div and add missing duplicate footnotes."""
    self.offset = 0
    for div in root.iter('div'):
        if div.attrib.get('class', '') == 'footnote':
            # Footnotes shoul be under the first orderd list under
            # the footnote div.  So once we find it, quit.
            for ol in div.iter('ol'):
                self.handle_duplicates(ol)
                break

FootnoteTreeprocessor(footnotes)

Bases: Treeprocessor

Build and append footnote div to end of document.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def __init__(self, footnotes):
    self.footnotes = footnotes

Attributes

markdown = markdown_instance instance-attribute
footnotes = footnotes instance-attribute

Functions

run(root)
Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def run(self, root):
    footnotesDiv = self.footnotes.makeFootnotesDiv(root)
    if footnotesDiv is not None:
        result = self.footnotes.findFootnotesPlaceholder(root)
        if result:
            child, parent, isText = result
            ind = parent.getchildren().index(child)
            if isText:
                parent.remove(child)
                parent.insert(ind, footnotesDiv)
            else:
                parent.insert(ind + 1, footnotesDiv)
                child.tail = None
        else:
            root.append(footnotesDiv)

FootnotePostprocessor(footnotes)

Bases: Postprocessor

Replace placeholders with html entities.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def __init__(self, footnotes):
    self.footnotes = footnotes

Attributes

markdown = markdown_instance instance-attribute
footnotes = footnotes instance-attribute

Functions

run(text)
Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def run(self, text):
    text = text.replace(
        FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT")
    )
    return text.replace(NBSP_PLACEHOLDER, "&#160;")

Functions

makeExtension(*args, **kwargs)

Return an instance of the FootnoteExtension.

Source code in pyrevitlib/pyrevit/coreutils/markdown/extensions/footnotes.py
def makeExtension(*args, **kwargs):
    """Return an instance of the FootnoteExtension."""
    return FootnoteExtension(*args, **kwargs)