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

Side by Side Diff: trunk/src/third_party/markdown/extensions/footnotes.py

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

Powered by Google App Engine
This is Rietveld 408576698