-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This package provides RMarkdown style bibliography and citations support to Python-Markdown. Currently, we can generating bibliography entries using either defined references or Bibtex entries in the same order that the citations appeared in the document. TODO: - Add tests and README - Implement option to alphabetize bibliography - Allow users to specify citation key formats - Allow users to define how the references are formatted (maybe using a CSL file?)
- Loading branch information
0 parents
commit b33135c
Showing
3 changed files
with
234 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2015 Darwin Darakananda | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
from markdown.extensions import Extension | ||
from markdown.preprocessors import Preprocessor | ||
from markdown.treeprocessors import Treeprocessor | ||
from markdown.postprocessors import Postprocessor | ||
from markdown.inlinepatterns import Pattern | ||
from markdown.util import etree | ||
|
||
from pybtex.database.input import bibtex | ||
|
||
from collections import OrderedDict | ||
import re | ||
|
||
BRACKET_RE = re.compile(r'\[([^\[]+)\]') | ||
CITE_RE = re.compile(r'@(\w+)') | ||
DEF_RE = re.compile(r'\A {0,3}\[@(\w+)\]:\s*(.*)') | ||
INDENT_RE = re.compile(r'\A\t| {4}(.*)') | ||
|
||
CITATION_RE = r'@(\w+)' | ||
|
||
class Bibliography(object): | ||
""" Keep track of document references and citations for exporting """ | ||
|
||
def __init__(self, extension, bibtex_file, order): | ||
self.extension = extension | ||
self.order = order | ||
|
||
self.citations = OrderedDict() | ||
self.references = dict() | ||
|
||
if bibtex_file: | ||
try: | ||
parser = bibtex.Parser() | ||
self.bibsource = parser.parse_file(bibtex_file).entries | ||
except: | ||
print("Error loading bibtex file") | ||
self.bibsource = dict() | ||
else: | ||
self.bibsource = dict() | ||
|
||
def addCitation(self, citekey): | ||
self.citations[citekey] = self.citations.get(citekey, 0) + 1 | ||
|
||
def setReference(self, citekey, reference): | ||
self.references[citekey] = reference | ||
|
||
def citationID(self, citekey): | ||
return "cite-" + citekey | ||
|
||
def referenceID(self, citekey): | ||
return "ref-" + citekey | ||
|
||
def formatAuthor(self, author): | ||
out = "%s %s."%(author.last()[0], author.first()[0][0]) | ||
if author.middle(): | ||
out += "%s."%(author.middle()[0][0]) | ||
return out | ||
|
||
def formatReference(self, ref): | ||
authors = ", ".join(map(self.formatAuthor, ref.persons["author"])) | ||
title = ref.fields["title"] | ||
journal = ref.fields.get("journal", "") | ||
volume = ref.fields.get("volume", "") | ||
year = ref.fields.get("year") | ||
|
||
reference = "<p>%s: <i>%s</i>."%(authors, title) | ||
if journal: | ||
reference += " %s."%journal | ||
if volume: | ||
reference += " <b>%s</b>,"%volume | ||
|
||
reference += " (%s)</p>"%year | ||
|
||
return reference | ||
|
||
def makeBibliography(self, root): | ||
if self.order == 'alphabetical': | ||
raise(NotImplementedError) | ||
|
||
div = etree.Element("div") | ||
div.set("class", "references") | ||
|
||
if not self.citations: | ||
return div | ||
|
||
table = etree.SubElement(div, "table") | ||
tbody = etree.SubElement(table, "tbody") | ||
for id in self.citations: | ||
tr = etree.SubElement(tbody, "tr") | ||
tr.set("id", self.referenceID(id)) | ||
ref_id = etree.SubElement(tr, "td") | ||
ref_id.text = id | ||
ref_txt = etree.SubElement(tr, "td") | ||
if id in self.references: | ||
self.extension.parser.parseChunk(ref_txt, self.references[id]) | ||
elif id in self.bibsource: | ||
ref_txt.text = self.formatReference(self.bibsource[id]) | ||
else: | ||
ref_txt.text = "Missing citation" | ||
|
||
return div | ||
|
||
class CitationsPreprocessor(Preprocessor): | ||
""" Gather reference definitions and citation keys """ | ||
|
||
def __init__(self, bibliography): | ||
self.bib = bibliography | ||
|
||
def subsequentIndents(self, lines, i): | ||
""" Concatenate consecutive indented lines """ | ||
linesOut = [] | ||
while i < len(lines): | ||
m = INDENT_RE.match(lines[i]) | ||
if m: | ||
linesOut.append(m.group(1)) | ||
i += 1 | ||
else: | ||
break | ||
return " ".join(linesOut), i | ||
|
||
def run(self, lines): | ||
linesOut = [] | ||
i = 0 | ||
|
||
while i < len(lines): | ||
# Check to see if the line starts a reference definition | ||
m = DEF_RE.match(lines[i]) | ||
if m: | ||
key = m.group(1) | ||
reference = m.group(2) | ||
indents, i = self.subsequentIndents(lines, i+1) | ||
reference += ' ' + indents | ||
|
||
self.bib.setReference(key, reference) | ||
continue | ||
|
||
# Look for all @citekey patterns inside hard brackets | ||
for bracket in BRACKET_RE.findall(lines[i]): | ||
for c in CITE_RE.findall(bracket): | ||
self.bib.addCitation(c) | ||
linesOut.append(lines[i]) | ||
i += 1 | ||
|
||
return linesOut | ||
|
||
class CitationsPattern(Pattern): | ||
""" Handles converting citations keys into links """ | ||
|
||
def __init__(self, pattern, bibliography): | ||
super(CitationsPattern, self).__init__(pattern) | ||
self.bib = bibliography | ||
|
||
def handleMatch(self, m): | ||
id = m.group(2) | ||
if id in self.bib.citations: | ||
a = etree.Element("a") | ||
a.set('id', self.bib.citationID(id)) | ||
a.set('href', '#' + self.bib.referenceID(id)) | ||
a.set('class', 'citation') | ||
a.text = id | ||
|
||
return a | ||
else: | ||
return None | ||
|
||
class CitationsTreeprocessor(Treeprocessor): | ||
""" Add a bibliography/reference section to the end of the document """ | ||
|
||
def __init__(self, bibliography): | ||
self.bib = bibliography | ||
|
||
def run(self, root): | ||
citations = self.bib.makeBibliography(root) | ||
root.append(citations) | ||
|
||
class CitationsExtension(Extension): | ||
|
||
def __init__(self, *args, **kwargs): | ||
|
||
self.config = { | ||
"bibtex_file": ["", | ||
"Bibtex file path"], | ||
'order': [ | ||
"unsorted", | ||
"Order of the references (unsorted, alphabetical)"] | ||
} | ||
super(CitationsExtension, self).__init__(*args, **kwargs) | ||
self.bib = Bibliography( | ||
self, | ||
self.getConfig('bibtex_file'), | ||
self.getConfig('order'), | ||
) | ||
|
||
def extendMarkdown(self, md, md_globals): | ||
md.registerExtension(self) | ||
self.parser = md.parser | ||
self.md = md | ||
|
||
md.preprocessors.add("mdx_bib", CitationsPreprocessor(self.bib), "<reference") | ||
md.inlinePatterns.add("mdx_bib", CitationsPattern(CITATION_RE, self.bib), "<reference") | ||
md.treeprocessors.add("mdx_bib", CitationsTreeprocessor(self.bib), "_begin") | ||
|
||
def makeExtension(*args, **kwargs): | ||
return CitationsExtension(*args, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from setuptools import setup | ||
|
||
setup(name='mdx_bib', | ||
description="A Python markdown extension for handling citations.", | ||
version='0.0.1', | ||
author = 'Darwin Darakananda', | ||
py_modules=['mdx_bib'], | ||
install_requires = ['markdown', 'pybtex'], | ||
license='BSD') |