Simple Footnotes plugin. Add footnotes to your text by enclosing them in [ref] and [/ref].

This commit is contained in:
sil
2014-02-15 01:21:06 +00:00
parent f7178ae0e9
commit c85ea081c0
4 changed files with 151 additions and 0 deletions

View File

@@ -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.

View File

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

View File

@@ -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)

View File

@@ -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()