rra
7 years ago
4 changed files with 168 additions and 0 deletions
@ -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. |
@ -0,0 +1 @@ |
|||
from .simple_footnotes import * |
@ -0,0 +1,108 @@ |
|||
#!/usr/bin/env python |
|||
# -*- coding: utf-8 -*- # |
|||
|
|||
from pelican import signals |
|||
import html5lib |
|||
import six |
|||
|
|||
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 = [u''] |
|||
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 u''.join(L) |
|||
|
|||
|
|||
def sequence_gen(genlist): |
|||
for gen in genlist: |
|||
for elem in gen: |
|||
yield elem |
|||
|
|||
|
|||
def parse_for_footnotes(article_or_page_generator): |
|||
all_content = [ |
|||
getattr(article_or_page_generator, attr, None) \ |
|||
for attr in [u'articles', u'drafts', u'pages']] |
|||
all_content = [x for x in all_content if x is not None] |
|||
for article in sequence_gen(all_content): |
|||
if u"[ref]" in article._content and u"[/ref]" in article._content: |
|||
content = article._content.replace(u"[ref]", u"<x-simple-footnote>").replace(u"[/ref]", |
|||
u"</x-simple-footnote>") |
|||
parser = html5lib.HTMLParser(tree=html5lib.getTreeBuilder(u"dom")) |
|||
dom = parser.parse(content) |
|||
endnotes = [] |
|||
count = 0 |
|||
for footnote in dom.getElementsByTagName(u"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 = u"sf-%s-%s" % (article.slug, count) |
|||
fnbackid = u"%s-back" % (fnid,) |
|||
endnotes.append((footnote, fnid, fnbackid)) |
|||
number = dom.createElement(u"sup") |
|||
number.setAttribute(u"id", fnbackid) |
|||
|
|||
numbera = dom.createElement(u"a") |
|||
numbera.setAttribute(u"href", u"#%s" % fnid) |
|||
numbera.setAttribute(u"class", u"simple-footnote") |
|||
numbera.appendChild(dom.createTextNode(six.text_type(count))) |
|||
txt = getText(footnote, recursive=True).replace(u"\n", u" ") |
|||
|
|||
footnote_container = dom.createElement(u"span") |
|||
footnote_container.setAttribute(u"class", u"simple-footnote-container") |
|||
footnote_content = dom.createElement(u"span") |
|||
footnote_content.appendChild(footnote.firstChild) |
|||
footnote_content.setAttribute(u"class", u"simple-footnote-content") |
|||
footnote_container.appendChild(footnote_content) |
|||
print(footnote.firstChild) |
|||
|
|||
numbera.setAttribute(u"title", txt) |
|||
number.appendChild(footnote_container) |
|||
number.appendChild(numbera) |
|||
|
|||
footnote.parentNode.insertBefore(number, footnote) |
|||
footnote.parentNode.insertBefore(footnote_container, footnote) |
|||
if endnotes: |
|||
ol = dom.createElement(u"ol") |
|||
ol.setAttribute(u"class", u"simple-footnotes") |
|||
for e, fnid, fnbackid in endnotes: |
|||
# li = dom.createElement(u"li") |
|||
# li.setAttribute(u"id", fnid) |
|||
# while e.firstChild: |
|||
# li.appendChild(e.firstChild) |
|||
# backlink = dom.createElement(u"a") |
|||
# backlink.setAttribute(u"href", u"#%s" % fnbackid) |
|||
# backlink.setAttribute(u"class", u"simple-footnote-back") |
|||
# backlink.appendChild(dom.createTextNode(u'\u21a9')) |
|||
# li.appendChild(dom.createTextNode(u" ")) |
|||
# li.appendChild(backlink) |
|||
# ol.appendChild(li) |
|||
e.parentNode.removeChild(e) |
|||
dom.getElementsByTagName(u"body")[0].appendChild(ol) |
|||
s = html5lib.serializer.HTMLSerializer(omit_optional_tags=False, quote_attr_values='legacy') |
|||
output_generator = s.serialize( |
|||
html5lib.treewalkers.getTreeWalker(u"dom")(dom.getElementsByTagName(u"body")[0])) |
|||
article._content = u"".join(list(output_generator)).replace( |
|||
u"<x-simple-footnote>", u"[ref]").replace(u"</x-simple-footnote>", u"[/ref]").replace( |
|||
u"<body>", u"").replace(u"</body>", u"") |
|||
|
|||
|
|||
def register(): |
|||
signals.article_generator_finalized.connect(parse_for_footnotes) |
|||
signals.page_generator_finalized.connect(parse_for_footnotes) |
@ -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() |
Loading…
Reference in new issue