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

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

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

Powered by Google App Engine
This is Rietveld 408576698