Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(250)

Side by Side Diff: third_party/Python-Markdown/markdown/extensions/footnotes.py

Issue 1356203004: Check in a simple pure-python based Markdown previewer. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@add
Patch Set: fix license file Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # markdown is released under the BSD license 1 """
2 # Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later) 2 Footnotes Extension for Python-Markdown
3 # Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) 3 =======================================
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:
10 #
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.
19 #
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.
31 4
5 Adds footnote handling to Python-Markdown.
32 6
33 """ 7 See <https://pythonhosted.org/Markdown/extensions/footnotes.html>
34 ========================= FOOTNOTES ================================= 8 for documentation.
35 9
36 This section adds footnote handling to markdown. It can be used as 10 Copyright The Python Markdown Project
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.
43 11
44 Footnote functionality is attached by calling extendMarkdown() 12 License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
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.
48
49 Example:
50 Footnotes[^1] have a label[^label] and a definition[^!DEF].
51
52 [^1]: This is a footnote
53 [^label]: A footnote on "label"
54 [^!DEF]: The footnote for definition
55 13
56 """ 14 """
57 15
58 from __future__ import absolute_import 16 from __future__ import absolute_import
59 from __future__ import unicode_literals 17 from __future__ import unicode_literals
60 from . import Extension 18 from . import Extension
61 from ..preprocessors import Preprocessor 19 from ..preprocessors import Preprocessor
62 from ..inlinepatterns import Pattern 20 from ..inlinepatterns import Pattern
63 from ..treeprocessors import Treeprocessor 21 from ..treeprocessors import Treeprocessor
64 from ..postprocessors import Postprocessor 22 from ..postprocessors import Postprocessor
65 from ..util import etree, text_type 23 from ..util import etree, text_type
66 from ..odict import OrderedDict 24 from ..odict import OrderedDict
67 import re 25 import re
68 26
69 FN_BACKLINK_TEXT = "zz1337820767766393qq" 27 FN_BACKLINK_TEXT = "zz1337820767766393qq"
70 NBSP_PLACEHOLDER = "qq3936677670287331zz" 28 NBSP_PLACEHOLDER = "qq3936677670287331zz"
71 DEF_RE = re.compile(r'[ ]{0,3}\[\^([^\]]*)\]:\s*(.*)') 29 DEF_RE = re.compile(r'[ ]{0,3}\[\^([^\]]*)\]:\s*(.*)')
72 TABBED_RE = re.compile(r'((\t)|( ))(.*)') 30 TABBED_RE = re.compile(r'((\t)|( ))(.*)')
73 31
32
74 class FootnoteExtension(Extension): 33 class FootnoteExtension(Extension):
75 """ Footnote Extension. """ 34 """ Footnote Extension. """
76 35
77 def __init__ (self, configs): 36 def __init__(self, *args, **kwargs):
78 """ Setup configs. """ 37 """ 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 rea der's place."]
89 }
90 38
91 for key, value in configs: 39 self.config = {
92 self.config[key][0] = value 40 'PLACE_MARKER':
41 ["///Footnotes Go Here///",
42 "The text string that marks where the footnotes go"],
43 'UNIQUE_IDS':
44 [False,
45 "Avoid name collisions across "
46 "multiple calls to reset()."],
47 "BACKLINK_TEXT":
48 ["&#8617;",
49 "The text string that links from the footnote "
50 "to the reader's place."]
51 }
52 super(FootnoteExtension, self).__init__(*args, **kwargs)
93 53
94 # In multiple invocations, emit links that don't get tangled. 54 # In multiple invocations, emit links that don't get tangled.
95 self.unique_prefix = 0 55 self.unique_prefix = 0
96 56
97 self.reset() 57 self.reset()
98 58
99 def extendMarkdown(self, md, md_globals): 59 def extendMarkdown(self, md, md_globals):
100 """ Add pieces to Markdown. """ 60 """ Add pieces to Markdown. """
101 md.registerExtension(self) 61 md.registerExtension(self)
102 self.parser = md.parser 62 self.parser = md.parser
103 self.md = md 63 self.md = md
104 self.sep = ':'
105 if self.md.output_format in ['html5', 'xhtml5']:
106 self.sep = '-'
107 # Insert a preprocessor before ReferencePreprocessor 64 # Insert a preprocessor before ReferencePreprocessor
108 md.preprocessors.add("footnote", FootnotePreprocessor(self), 65 md.preprocessors.add(
109 "<reference") 66 "footnote", FootnotePreprocessor(self), "<reference"
67 )
110 # Insert an inline pattern before ImageReferencePattern 68 # Insert an inline pattern before ImageReferencePattern
111 FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah 69 FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
112 md.inlinePatterns.add("footnote", FootnotePattern(FOOTNOTE_RE, self), 70 md.inlinePatterns.add(
113 "<reference") 71 "footnote", FootnotePattern(FOOTNOTE_RE, self), "<reference"
72 )
114 # Insert a tree-processor that would actually add the footnote div 73 # Insert a tree-processor that would actually add the footnote div
115 # This must be before all other treeprocessors (i.e., inline and 74 # This must be before all other treeprocessors (i.e., inline and
116 # codehilite) so they can run on the the contents of the div. 75 # codehilite) so they can run on the the contents of the div.
117 md.treeprocessors.add("footnote", FootnoteTreeprocessor(self), 76 md.treeprocessors.add(
118 "_begin") 77 "footnote", FootnoteTreeprocessor(self), "_begin"
78 )
119 # Insert a postprocessor after amp_substitute oricessor 79 # Insert a postprocessor after amp_substitute oricessor
120 md.postprocessors.add("footnote", FootnotePostprocessor(self), 80 md.postprocessors.add(
121 ">amp_substitute") 81 "footnote", FootnotePostprocessor(self), ">amp_substitute"
82 )
122 83
123 def reset(self): 84 def reset(self):
124 """ Clear the footnotes on reset, and prepare for a distinct document. " "" 85 """ Clear footnotes on reset, and prepare for distinct document. """
125 self.footnotes = OrderedDict() 86 self.footnotes = OrderedDict()
126 self.unique_prefix += 1 87 self.unique_prefix += 1
127 88
128 def findFootnotesPlaceholder(self, root): 89 def findFootnotesPlaceholder(self, root):
129 """ Return ElementTree Element that contains Footnote placeholder. """ 90 """ Return ElementTree Element that contains Footnote placeholder. """
130 def finder(element): 91 def finder(element):
131 for child in element: 92 for child in element:
132 if child.text: 93 if child.text:
133 if child.text.find(self.getConfig("PLACE_MARKER")) > -1: 94 if child.text.find(self.getConfig("PLACE_MARKER")) > -1:
134 return child, element, True 95 return child, element, True
135 if child.tail: 96 if child.tail:
136 if child.tail.find(self.getConfig("PLACE_MARKER")) > -1: 97 if child.tail.find(self.getConfig("PLACE_MARKER")) > -1:
137 return child, element, False 98 return child, element, False
138 finder(child) 99 finder(child)
139 return None 100 return None
140 101
141 res = finder(root) 102 res = finder(root)
142 return res 103 return res
143 104
144 def setFootnote(self, id, text): 105 def setFootnote(self, id, text):
145 """ Store a footnote for later retrieval. """ 106 """ Store a footnote for later retrieval. """
146 self.footnotes[id] = text 107 self.footnotes[id] = text
147 108
109 def get_separator(self):
110 if self.md.output_format in ['html5', 'xhtml5']:
111 return '-'
112 return ':'
113
148 def makeFootnoteId(self, id): 114 def makeFootnoteId(self, id):
149 """ Return footnote link id. """ 115 """ Return footnote link id. """
150 if self.getConfig("UNIQUE_IDS"): 116 if self.getConfig("UNIQUE_IDS"):
151 return 'fn%s%d-%s' % (self.sep, self.unique_prefix, id) 117 return 'fn%s%d-%s' % (self.get_separator(), self.unique_prefix, id)
152 else: 118 else:
153 return 'fn%s%s' % (self.sep, id) 119 return 'fn%s%s' % (self.get_separator(), id)
154 120
155 def makeFootnoteRefId(self, id): 121 def makeFootnoteRefId(self, id):
156 """ Return footnote back-link id. """ 122 """ Return footnote back-link id. """
157 if self.getConfig("UNIQUE_IDS"): 123 if self.getConfig("UNIQUE_IDS"):
158 return 'fnref%s%d-%s' % (self.sep, self.unique_prefix, id) 124 return 'fnref%s%d-%s' % (self.get_separator(),
125 self.unique_prefix, id)
159 else: 126 else:
160 return 'fnref%s%s' % (self.sep, id) 127 return 'fnref%s%s' % (self.get_separator(), id)
161 128
162 def makeFootnotesDiv(self, root): 129 def makeFootnotesDiv(self, root):
163 """ Return div of footnotes as et Element. """ 130 """ Return div of footnotes as et Element. """
164 131
165 if not list(self.footnotes.keys()): 132 if not list(self.footnotes.keys()):
166 return None 133 return None
167 134
168 div = etree.Element("div") 135 div = etree.Element("div")
169 div.set('class', 'footnote') 136 div.set('class', 'footnote')
170 etree.SubElement(div, "hr") 137 etree.SubElement(div, "hr")
171 ol = etree.SubElement(div, "ol") 138 ol = etree.SubElement(div, "ol")
172 139
173 for id in self.footnotes.keys(): 140 for id in self.footnotes.keys():
174 li = etree.SubElement(ol, "li") 141 li = etree.SubElement(ol, "li")
175 li.set("id", self.makeFootnoteId(id)) 142 li.set("id", self.makeFootnoteId(id))
176 self.parser.parseChunk(li, self.footnotes[id]) 143 self.parser.parseChunk(li, self.footnotes[id])
177 backlink = etree.Element("a") 144 backlink = etree.Element("a")
178 backlink.set("href", "#" + self.makeFootnoteRefId(id)) 145 backlink.set("href", "#" + self.makeFootnoteRefId(id))
179 if self.md.output_format not in ['html5', 'xhtml5']: 146 if self.md.output_format not in ['html5', 'xhtml5']:
180 backlink.set("rev", "footnote") # Invalid in HTML5 147 backlink.set("rev", "footnote") # Invalid in HTML5
181 backlink.set("class", "footnote-backref") 148 backlink.set("class", "footnote-backref")
182 backlink.set("title", "Jump back to footnote %d in the text" % \ 149 backlink.set(
183 (self.footnotes.index(id)+1)) 150 "title",
151 "Jump back to footnote %d in the text" %
152 (self.footnotes.index(id)+1)
153 )
184 backlink.text = FN_BACKLINK_TEXT 154 backlink.text = FN_BACKLINK_TEXT
185 155
186 if li.getchildren(): 156 if li.getchildren():
187 node = li[-1] 157 node = li[-1]
188 if node.tag == "p": 158 if node.tag == "p":
189 node.text = node.text + NBSP_PLACEHOLDER 159 node.text = node.text + NBSP_PLACEHOLDER
190 node.append(backlink) 160 node.append(backlink)
191 else: 161 else:
192 p = etree.SubElement(li, "p") 162 p = etree.SubElement(li, "p")
193 p.append(backlink) 163 p.append(backlink)
194 return div 164 return div
195 165
196 166
197 class FootnotePreprocessor(Preprocessor): 167 class FootnotePreprocessor(Preprocessor):
198 """ Find all footnote references and store for later use. """ 168 """ Find all footnote references and store for later use. """
199 169
200 def __init__ (self, footnotes): 170 def __init__(self, footnotes):
201 self.footnotes = footnotes 171 self.footnotes = footnotes
202 172
203 def run(self, lines): 173 def run(self, lines):
204 """ 174 """
205 Loop through lines and find, set, and remove footnote definitions. 175 Loop through lines and find, set, and remove footnote definitions.
206 176
207 Keywords: 177 Keywords:
208 178
209 * lines: A list of lines of text 179 * lines: A list of lines of text
210 180
211 Return: A list of lines of text with footnote definitions removed. 181 Return: A list of lines of text with footnote definitions removed.
212 182
213 """ 183 """
214 newlines = [] 184 newlines = []
215 i = 0 185 i = 0
216 while True: 186 while True:
217 m = DEF_RE.match(lines[i]) 187 m = DEF_RE.match(lines[i])
218 if m: 188 if m:
219 fn, _i = self.detectTabbed(lines[i+1:]) 189 fn, _i = self.detectTabbed(lines[i+1:])
220 fn.insert(0, m.group(2)) 190 fn.insert(0, m.group(2))
221 i += _i-1 # skip past footnote 191 i += _i-1 # skip past footnote
222 self.footnotes.setFootnote(m.group(1), "\n".join(fn)) 192 self.footnotes.setFootnote(m.group(1), "\n".join(fn))
223 else: 193 else:
224 newlines.append(lines[i]) 194 newlines.append(lines[i])
225 if len(lines) > i+1: 195 if len(lines) > i+1:
226 i += 1 196 i += 1
227 else: 197 else:
228 break 198 break
229 return newlines 199 return newlines
230 200
231 def detectTabbed(self, lines): 201 def detectTabbed(self, lines):
232 """ Find indented text and remove indent before further proccesing. 202 """ Find indented text and remove indent before further proccesing.
233 203
234 Keyword arguments: 204 Keyword arguments:
235 205
236 * lines: an array of strings 206 * lines: an array of strings
237 207
238 Returns: a list of post processed items and the index of last line. 208 Returns: a list of post processed items and the index of last line.
239 209
240 """ 210 """
241 items = [] 211 items = []
242 blank_line = False # have we encountered a blank line yet? 212 blank_line = False # have we encountered a blank line yet?
243 i = 0 # to keep track of where we are 213 i = 0 # to keep track of where we are
244 214
245 def detab(line): 215 def detab(line):
246 match = TABBED_RE.match(line) 216 match = TABBED_RE.match(line)
247 if match: 217 if match:
248 return match.group(4) 218 return match.group(4)
249 219
250 for line in lines: 220 for line in lines:
251 if line.strip(): # Non-blank line 221 if line.strip(): # Non-blank line
252 detabbed_line = detab(line) 222 detabbed_line = detab(line)
253 if detabbed_line: 223 if detabbed_line:
254 items.append(detabbed_line) 224 items.append(detabbed_line)
255 i += 1 225 i += 1
256 continue 226 continue
257 elif not blank_line and not DEF_RE.match(line): 227 elif not blank_line and not DEF_RE.match(line):
258 # not tabbed but still part of first par. 228 # not tabbed but still part of first par.
259 items.append(line) 229 items.append(line)
260 i += 1 230 i += 1
261 continue 231 continue
262 else: 232 else:
263 return items, i+1 233 return items, i+1
264 234
265 else: # Blank line: _maybe_ we are done. 235 else: # Blank line: _maybe_ we are done.
266 blank_line = True 236 blank_line = True
267 i += 1 # advance 237 i += 1 # advance
268 238
269 # Find the next non-blank line 239 # Find the next non-blank line
270 for j in range(i, len(lines)): 240 for j in range(i, len(lines)):
271 if lines[j].strip(): 241 if lines[j].strip():
272 next_line = lines[j]; break 242 next_line = lines[j]
243 break
273 else: 244 else:
274 break # There is no more text; we are done. 245 break # There is no more text; we are done.
275 246
276 # Check if the next non-blank line is tabbed 247 # Check if the next non-blank line is tabbed
277 if detab(next_line): # Yes, more work to do. 248 if detab(next_line): # Yes, more work to do.
278 items.append("") 249 items.append("")
279 continue 250 continue
280 else: 251 else:
281 break # No, we are done. 252 break # No, we are done.
282 else: 253 else:
283 i += 1 254 i += 1
284 255
285 return items, i 256 return items, i
286 257
287 258
288 class FootnotePattern(Pattern): 259 class FootnotePattern(Pattern):
289 """ InlinePattern for footnote markers in a document's body text. """ 260 """ InlinePattern for footnote markers in a document's body text. """
290 261
291 def __init__(self, pattern, footnotes): 262 def __init__(self, pattern, footnotes):
292 super(FootnotePattern, self).__init__(pattern) 263 super(FootnotePattern, self).__init__(pattern)
293 self.footnotes = footnotes 264 self.footnotes = footnotes
294 265
295 def handleMatch(self, m): 266 def handleMatch(self, m):
296 id = m.group(2) 267 id = m.group(2)
297 if id in self.footnotes.footnotes.keys(): 268 if id in self.footnotes.footnotes.keys():
298 sup = etree.Element("sup") 269 sup = etree.Element("sup")
299 a = etree.SubElement(sup, "a") 270 a = etree.SubElement(sup, "a")
300 sup.set('id', self.footnotes.makeFootnoteRefId(id)) 271 sup.set('id', self.footnotes.makeFootnoteRefId(id))
301 a.set('href', '#' + self.footnotes.makeFootnoteId(id)) 272 a.set('href', '#' + self.footnotes.makeFootnoteId(id))
302 if self.footnotes.md.output_format not in ['html5', 'xhtml5']: 273 if self.footnotes.md.output_format not in ['html5', 'xhtml5']:
303 a.set('rel', 'footnote') # invalid in HTML5 274 a.set('rel', 'footnote') # invalid in HTML5
304 a.set('class', 'footnote-ref') 275 a.set('class', 'footnote-ref')
305 a.text = text_type(self.footnotes.footnotes.index(id) + 1) 276 a.text = text_type(self.footnotes.footnotes.index(id) + 1)
306 return sup 277 return sup
307 else: 278 else:
308 return None 279 return None
309 280
310 281
311 class FootnoteTreeprocessor(Treeprocessor): 282 class FootnoteTreeprocessor(Treeprocessor):
312 """ Build and append footnote div to end of document. """ 283 """ Build and append footnote div to end of document. """
313 284
314 def __init__ (self, footnotes): 285 def __init__(self, footnotes):
315 self.footnotes = footnotes 286 self.footnotes = footnotes
316 287
317 def run(self, root): 288 def run(self, root):
318 footnotesDiv = self.footnotes.makeFootnotesDiv(root) 289 footnotesDiv = self.footnotes.makeFootnotesDiv(root)
319 if footnotesDiv: 290 if footnotesDiv is not None:
320 result = self.footnotes.findFootnotesPlaceholder(root) 291 result = self.footnotes.findFootnotesPlaceholder(root)
321 if result: 292 if result:
322 child, parent, isText = result 293 child, parent, isText = result
323 ind = parent.getchildren().index(child) 294 ind = parent.getchildren().index(child)
324 if isText: 295 if isText:
325 parent.remove(child) 296 parent.remove(child)
326 parent.insert(ind, footnotesDiv) 297 parent.insert(ind, footnotesDiv)
327 else: 298 else:
328 parent.insert(ind + 1, footnotesDiv) 299 parent.insert(ind + 1, footnotesDiv)
329 child.tail = None 300 child.tail = None
330 else: 301 else:
331 root.append(footnotesDiv) 302 root.append(footnotesDiv)
332 303
304
333 class FootnotePostprocessor(Postprocessor): 305 class FootnotePostprocessor(Postprocessor):
334 """ Replace placeholders with html entities. """ 306 """ Replace placeholders with html entities. """
335 def __init__(self, footnotes): 307 def __init__(self, footnotes):
336 self.footnotes = footnotes 308 self.footnotes = footnotes
337 309
338 def run(self, text): 310 def run(self, text):
339 text = text.replace(FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK _TEXT")) 311 text = text.replace(
312 FN_BACKLINK_TEXT, self.footnotes.getConfig("BACKLINK_TEXT")
313 )
340 return text.replace(NBSP_PLACEHOLDER, "&#160;") 314 return text.replace(NBSP_PLACEHOLDER, "&#160;")
341 315
342 def makeExtension(configs=[]): 316
317 def makeExtension(*args, **kwargs):
343 """ Return an instance of the FootnoteExtension """ 318 """ Return an instance of the FootnoteExtension """
344 return FootnoteExtension(configs=configs) 319 return FootnoteExtension(*args, **kwargs)
345
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698