[Mac] Enable MacViews bookmark editor behind --enable-mac-views-dialogs.
[chromium-blink-merge.git] / third_party / markdown / extensions / footnotes.py
blob38b43b4d30580574bec2f2d1fad84e657542637b
1 # markdown is released under the BSD license
2 # Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)
3 # Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
4 # Copyright 2004 Manfred Stienstra (the original version)
5 #
6 # All rights reserved.
7 #
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions are met:
11 # * Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
16 # * Neither the name of the <organization> nor the
17 # names of its contributors may be used to endorse or promote products
18 # derived from this software without specific prior written permission.
20 # THIS SOFTWARE IS PROVIDED BY THE PYTHON MARKDOWN PROJECT ''AS IS'' AND ANY
21 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 # DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO THE PYTHON MARKDOWN PROJECT
24 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 # POSSIBILITY OF SUCH DAMAGE.
33 """
34 ========================= FOOTNOTES =================================
36 This section adds footnote handling to markdown. It can be used as
37 an example for extending python-markdown with relatively complex
38 functionality. While in this case the extension is included inside
39 the module itself, it could just as easily be added from outside the
40 module. Not that all markdown classes above are ignorant about
41 footnotes. All footnote functionality is provided separately and
42 then added to the markdown instance at the run time.
44 Footnote functionality is attached by calling extendMarkdown()
45 method of FootnoteExtension. The method also registers the
46 extension to allow it's state to be reset by a call to reset()
47 method.
49 Example:
50 Footnotes[^1] have a label[^label] and a definition[^!DEF].
52 [^1]: This is a footnote
53 [^label]: A footnote on "label"
54 [^!DEF]: The footnote for definition
56 """
58 from __future__ import absolute_import
59 from __future__ import unicode_literals
60 from . import Extension
61 from ..preprocessors import Preprocessor
62 from ..inlinepatterns import Pattern
63 from ..treeprocessors import Treeprocessor
64 from ..postprocessors import Postprocessor
65 from ..util import etree, text_type
66 from ..odict import OrderedDict
67 import re
69 FN_BACKLINK_TEXT = "zz1337820767766393qq"
70 NBSP_PLACEHOLDER = "qq3936677670287331zz"
71 DEF_RE = re.compile(r'[ ]{0,3}\[\^([^\]]*)\]:\s*(.*)')
72 TABBED_RE = re.compile(r'((\t)|( ))(.*)')
74 class FootnoteExtension(Extension):
75 """ Footnote Extension. """
77 def __init__ (self, configs):
78 """ Setup configs. """
79 self.config = {'PLACE_MARKER':
80 ["///Footnotes Go Here///",
81 "The text string that marks where the footnotes go"],
82 'UNIQUE_IDS':
83 [False,
84 "Avoid name collisions across "
85 "multiple calls to reset()."],
86 "BACKLINK_TEXT":
87 ["&#8617;",
88 "The text string that links from the footnote to the reader's place."]
91 for key, value in configs:
92 self.config[key][0] = value
94 # In multiple invocations, emit links that don't get tangled.
95 self.unique_prefix = 0
97 self.reset()
99 def extendMarkdown(self, md, md_globals):
100 """ Add pieces to Markdown. """
101 md.registerExtension(self)
102 self.parser = md.parser
103 self.md = md
104 self.sep = ':'
105 if self.md.output_format in ['html5', 'xhtml5']:
106 self.sep = '-'
107 # Insert a preprocessor before ReferencePreprocessor
108 md.preprocessors.add("footnote", FootnotePreprocessor(self),
109 "<reference")
110 # Insert an inline pattern before ImageReferencePattern
111 FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
112 md.inlinePatterns.add("footnote", FootnotePattern(FOOTNOTE_RE, self),
113 "<reference")
114 # Insert a tree-processor that would actually add the footnote div
115 # This must be before all other treeprocessors (i.e., inline and
116 # codehilite) so they can run on the the contents of the div.
117 md.treeprocessors.add("footnote", FootnoteTreeprocessor(self),
118 "_begin")
119 # Insert a postprocessor after amp_substitute oricessor
120 md.postprocessors.add("footnote", FootnotePostprocessor(self),
121 ">amp_substitute")
123 def reset(self):
124 """ Clear the footnotes on reset, and prepare for a distinct document. """
125 self.footnotes = OrderedDict()
126 self.unique_prefix += 1
128 def findFootnotesPlaceholder(self, root):
129 """ Return ElementTree Element that contains Footnote placeholder. """
130 def finder(element):
131 for child in element:
132 if child.text:
133 if child.text.find(self.getConfig("PLACE_MARKER")) > -1:
134 return child, element, True
135 if child.tail:
136 if child.tail.find(self.getConfig("PLACE_MARKER")) > -1:
137 return child, element, False
138 finder(child)
139 return None
141 res = finder(root)
142 return res
144 def setFootnote(self, id, text):
145 """ Store a footnote for later retrieval. """
146 self.footnotes[id] = text
148 def makeFootnoteId(self, id):
149 """ Return footnote link id. """
150 if self.getConfig("UNIQUE_IDS"):
151 return 'fn%s%d-%s' % (self.sep, self.unique_prefix, id)
152 else:
153 return 'fn%s%s' % (self.sep, id)
155 def makeFootnoteRefId(self, id):
156 """ Return footnote back-link id. """
157 if self.getConfig("UNIQUE_IDS"):
158 return 'fnref%s%d-%s' % (self.sep, self.unique_prefix, id)
159 else:
160 return 'fnref%s%s' % (self.sep, id)
162 def makeFootnotesDiv(self, root):
163 """ Return div of footnotes as et Element. """
165 if not list(self.footnotes.keys()):
166 return None
168 div = etree.Element("div")
169 div.set('class', 'footnote')
170 etree.SubElement(div, "hr")
171 ol = etree.SubElement(div, "ol")
173 for id in self.footnotes.keys():
174 li = etree.SubElement(ol, "li")
175 li.set("id", self.makeFootnoteId(id))
176 self.parser.parseChunk(li, self.footnotes[id])
177 backlink = etree.Element("a")
178 backlink.set("href", "#" + self.makeFootnoteRefId(id))
179 if self.md.output_format not in ['html5', 'xhtml5']:
180 backlink.set("rev", "footnote") # Invalid in HTML5
181 backlink.set("class", "footnote-backref")
182 backlink.set("title", "Jump back to footnote %d in the text" % \
183 (self.footnotes.index(id)+1))
184 backlink.text = FN_BACKLINK_TEXT
186 if li.getchildren():
187 node = li[-1]
188 if node.tag == "p":
189 node.text = node.text + NBSP_PLACEHOLDER
190 node.append(backlink)
191 else:
192 p = etree.SubElement(li, "p")
193 p.append(backlink)
194 return div
197 class FootnotePreprocessor(Preprocessor):
198 """ Find all footnote references and store for later use. """
200 def __init__ (self, footnotes):
201 self.footnotes = footnotes
203 def run(self, lines):
205 Loop through lines and find, set, and remove footnote definitions.
207 Keywords:
209 * lines: A list of lines of text
211 Return: A list of lines of text with footnote definitions removed.
214 newlines = []
215 i = 0
216 while True:
217 m = DEF_RE.match(lines[i])
218 if m:
219 fn, _i = self.detectTabbed(lines[i+1:])
220 fn.insert(0, m.group(2))
221 i += _i-1 # skip past footnote
222 self.footnotes.setFootnote(m.group(1), "\n".join(fn))
223 else:
224 newlines.append(lines[i])
225 if len(lines) > i+1:
226 i += 1
227 else:
228 break
229 return newlines
231 def detectTabbed(self, lines):
232 """ Find indented text and remove indent before further proccesing.
234 Keyword arguments:
236 * lines: an array of strings
238 Returns: a list of post processed items and the index of last line.
241 items = []
242 blank_line = False # have we encountered a blank line yet?
243 i = 0 # to keep track of where we are
245 def detab(line):
246 match = TABBED_RE.match(line)
247 if match:
248 return match.group(4)
250 for line in lines:
251 if line.strip(): # Non-blank line
252 detabbed_line = detab(line)
253 if detabbed_line:
254 items.append(detabbed_line)
255 i += 1
256 continue
257 elif not blank_line and not DEF_RE.match(line):
258 # not tabbed but still part of first par.
259 items.append(line)
260 i += 1
261 continue
262 else:
263 return items, i+1
265 else: # Blank line: _maybe_ we are done.
266 blank_line = True
267 i += 1 # advance
269 # Find the next non-blank line
270 for j in range(i, len(lines)):
271 if lines[j].strip():
272 next_line = lines[j]; break
273 else:
274 break # There is no more text; we are done.
276 # Check if the next non-blank line is tabbed
277 if detab(next_line): # Yes, more work to do.
278 items.append("")
279 continue
280 else:
281 break # No, we are done.
282 else:
283 i += 1
285 return items, i
288 class FootnotePattern(Pattern):
289 """ InlinePattern for footnote markers in a document's body text. """
291 def __init__(self, pattern, footnotes):
292 super(FootnotePattern, self).__init__(pattern)
293 self.footnotes = footnotes
295 def handleMatch(self, m):
296 id = m.group(2)
297 if id in self.footnotes.footnotes.keys():
298 sup = etree.Element("sup")
299 a = etree.SubElement(sup, "a")
300 sup.set('id', self.footnotes.makeFootnoteRefId(id))
301 a.set('href', '#' + self.footnotes.makeFootnoteId(id))
302 if self.footnotes.md.output_format not in ['html5', 'xhtml5']:
303 a.set('rel', 'footnote') # invalid in HTML5
304 a.set('class', 'footnote-ref')
305 a.text = text_type(self.footnotes.footnotes.index(id) + 1)
306 return sup
307 else:
308 return None
311 class FootnoteTreeprocessor(Treeprocessor):
312 """ Build and append footnote div to end of document. """
314 def __init__ (self, footnotes):
315 self.footnotes = footnotes
317 def run(self, root):
318 footnotesDiv = self.footnotes.makeFootnotesDiv(root)
319 if footnotesDiv:
320 result = self.footnotes.findFootnotesPlaceholder(root)
321 if result:
322 child, parent, isText = result
323 ind = parent.getchildren().index(child)
324 if isText:
325 parent.remove(child)
326 parent.insert(ind, footnotesDiv)
327 else:
328 parent.insert(ind + 1, footnotesDiv)
329 child.tail = None
330 else:
331 root.append(footnotesDiv)
333 class FootnotePostprocessor(Postprocessor):
334 """ Replace placeholders with html entities. """
335 def __init__(self, footnotes):
336 self.footnotes = footnotes
338 def run(self, text):
339 text = text.replace(FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT"))
340 return text.replace(NBSP_PLACEHOLDER, "&#160;")
342 def makeExtension(configs=[]):
343 """ Return an instance of the FootnoteExtension """
344 return FootnoteExtension(configs=configs)