Forráskód Böngészése

Merge pull request #156 from stuartlangridge/simple-footnotes

Add Simple Footnotes plugin
Justin Mayer 10 éve
szülő
commit
5e7bf54a95

+ 26 - 0
simple_footnotes/README.md

@@ -0,0 +1,26 @@
+Simple Footnotes
+================
+
+A Pelican plugin to add footnotes to blog posts.
+
+When writing a post or page, add a footnote like this:
+
+    Here's my written text[ref]and here is a footnote[/ref].
+
+This will appear as, roughly:
+
+Here's my written text<sup>1</sup>
+
+ 1. and here is a footnote ↩
+
+Inspired by Andrew Nacin's [Simple Footnotes WordPress plugin](http://wordpress.org/plugins/simple-footnotes/).
+
+Requirements
+============
+
+Needs html5lib, so you'll want to `pip install html5lib` before running.
+
+Should work with any content format (ReST, Markdown, whatever), because
+it looks for the `[ref]` and `[/ref]` once the conversion to HTML has happened.
+
+Stuart Langridge, http://www.kryogenix.org/, February 2014.

+ 1 - 0
simple_footnotes/__init__.py

@@ -0,0 +1 @@
+from .simple_footnotes import *

+ 91 - 0
simple_footnotes/simple_footnotes.py

@@ -0,0 +1,91 @@
+from pelican import signals
+import re
+import html5lib
+
+RAW_FOOTNOTE_CONTAINERS = ["code"]
+
+def getText(node, recursive = False):
+    """Get all the text associated with this node.
+       With recursive == True, all text from child nodes is retrieved."""
+    L = ['']
+    for n in node.childNodes:
+        if n.nodeType in (node.TEXT_NODE, node.CDATA_SECTION_NODE):
+            L.append(n.data)
+        else:
+            if not recursive:
+                return None
+        L.append(getText(n) )
+    return ''.join(L)
+
+def parse_for_footnotes(article_generator):
+    for article in article_generator.articles:
+        if "[ref]" in article._content and "[/ref]" in article._content:
+            content = article._content.replace("[ref]", "<x-simple-footnote>").replace("[/ref]", "</x-simple-footnote>")
+            parser = html5lib.HTMLParser(tree=html5lib.getTreeBuilder("dom"))
+            dom = parser.parse(content)
+            endnotes = []
+            count = 0
+            for footnote in dom.getElementsByTagName("x-simple-footnote"):
+                pn = footnote
+                leavealone = False
+                while pn:
+                    if pn.nodeName in RAW_FOOTNOTE_CONTAINERS:
+                        leavealone = True
+                        break
+                    pn = pn.parentNode
+                if leavealone:
+                    continue
+                count += 1
+                fnid = "sf-%s-%s" % (article.slug, count)
+                fnbackid = "%s-back" % (fnid,)
+                endnotes.append((footnote, fnid, fnbackid))
+                number = dom.createElement("sup")
+                number.setAttribute("id", fnbackid)
+                numbera = dom.createElement("a")
+                numbera.setAttribute("href", "#%s" % fnid)
+                numbera.setAttribute("class", "simple-footnote")
+                numbera.appendChild(dom.createTextNode(str(count)))
+                txt = getText(footnote, recursive=True).replace("\n", " ")
+                numbera.setAttribute("title", txt)
+                number.appendChild(numbera)
+                footnote.parentNode.insertBefore(number, footnote)
+            if endnotes:
+                ol = dom.createElement("ol")
+                ol.setAttribute("class", "simple-footnotes")
+                for e, fnid, fnbackid in endnotes:
+                    li = dom.createElement("li")
+                    li.setAttribute("id", fnid)
+                    while e.firstChild:
+                        li.appendChild(e.firstChild)
+                    backlink = dom.createElement("a")
+                    backlink.setAttribute("href", "#%s" % fnbackid)
+                    backlink.setAttribute("class", "simple-footnote-back")
+                    backlink.appendChild(dom.createTextNode(u'\u21a9'))
+                    li.appendChild(dom.createTextNode(" "))
+                    li.appendChild(backlink)
+                    ol.appendChild(li)
+                    e.parentNode.removeChild(e)
+                dom.getElementsByTagName("body")[0].appendChild(ol)
+                s = html5lib.serializer.htmlserializer.HTMLSerializer(omit_optional_tags=False, quote_attr_values=True)
+                output_generator = s.serialize(html5lib.treewalkers.getTreeWalker("dom")(dom.getElementsByTagName("body")[0]))
+                article._content =  "".join(list(output_generator)).replace(
+                    "<x-simple-footnote>", "[ref]").replace("</x-simple-footnote>", "[/ref]").replace(
+                    "<body>", "").replace("</body>", "")
+        if False:
+            count = 0
+            endnotes = []
+            for f in footnotes:
+                count += 1
+                fnstr = '<a class="simple-footnote" name="%s-%s-back" href="#%s-%s"><sup>%s</a>' % (
+                    article.slug, count, article.slug, count, count)
+                endstr = '<li id="%s-%s">%s <a href="#%s-%s-back">&uarr;</a></li>' % (
+                    article.slug, count, f[len("[ref]"):-len("[/ref]")], article.slug, count)
+                content = content.replace(f, fnstr)
+                endnotes.append(endstr)
+            content += '<h4>Footnotes</h4><ol class="simple-footnotes">%s</ul>' % ("\n".join(endnotes),)
+            article._content = content
+
+
+def register():
+    signals.article_generator_finalized.connect(parse_for_footnotes)
+

+ 33 - 0
simple_footnotes/test_simple_footnotes.py

@@ -0,0 +1,33 @@
+import unittest
+from simple_footnotes import parse_for_footnotes
+
+class PseudoArticleGenerator(object):
+    articles = []
+class PseudoArticle(object):
+    _content = ""
+    slug = "article"
+
+class TestFootnotes(unittest.TestCase):
+
+    def _expect(self, input, expected_output):
+        ag = PseudoArticleGenerator()
+        art = PseudoArticle()
+        art._content = input
+        ag.articles = [art]
+        parse_for_footnotes(ag)
+        self.assertEqual(art._content, expected_output)
+
+    def test_simple(self):
+        self._expect("words[ref]footnote[/ref]end",
+        ('words<sup id="sf-article-1-back"><a title="footnote" '
+         'href="#sf-article-1" class="simple-footnote">1</a></sup>end'
+         '<ol class="simple-footnotes">'
+         u'<li id="sf-article-1">footnote <a href="#sf-article-1-back" class="simple-footnote-back">\u21a9</a></li>'
+         '</ol>'))
+
+    def test_no_footnote_inside_code(self):
+        self._expect("words<code>this is code[ref]footnote[/ref] end code </code> end",
+            "words<code>this is code[ref]footnote[/ref] end code </code> end")
+
+if __name__ == '__main__':
+    unittest.main()