Source code for ipypublish.filters_pandoc.format_cite_elements
""" a panflute filter to format Cite elements
The :py:mod:`ipypublish.filters_pandoc.prepare_cites` filter should be run
first to access the functionality below:
"""
from panflute import Element, Doc, Span, Cite # noqa: F401
import panflute as pf
from ipypublish.filters_pandoc.definitions import (
ATTRIBUTE_CITE_CLASS,
CONVERTED_CITE_CLASS,
IPUB_META_ROUTE,
CITE_HTML_NAMES,
)
from ipypublish.filters_pandoc.html_bib import read_bibliography, process_bib_entry
[docs]def format_cites(cite, doc):
# type: (Cite, Doc) -> Element
"""
originally adapted from:
`pandoc-xnos <https://github.com/tomduck/pandoc-xnos/>`_
"""
if not isinstance(cite, pf.Cite):
return None
# default tags for latex and rst
cite_tag = doc.get_metadata(IPUB_META_ROUTE + ".reftag", "cite")
cite_role = "cite"
html_capitalize = False
# check is the Cite has a surrounding Span to supply attributed
span = None
if isinstance(cite.parent, pf.Span) and ATTRIBUTE_CITE_CLASS in cite.parent.classes:
span = cite.parent
cite_tag = span.attributes.get("latex", cite_tag)
cite_role = span.attributes.get("rst", cite_role)
html_capitalize = "capital" in span.classes
if cite_role == "numref" and (
not doc.get_metadata(IPUB_META_ROUTE + ".use_numref", False)
):
cite_role = "ref"
if doc.format in ("latex", "tex"):
if cite_tag in ["cite", "cref", "Cref"] or len(cite.citations) == 1:
# multiple labels are allowed
return pf.RawInline(
"\\{0}{{{1}}}".format(
cite_tag, ",".join([c.id for c in cite.citations])
),
format="tex",
)
else:
tex = ", ".join(
["\\{0}{{{1}}}".format(cite_tag, c.id) for c in cite.citations[:-1]]
) + " and \\{0}{{{1}}}".format(cite_tag, cite.citations[-1].id)
return pf.RawInline(tex, format="tex")
if doc.format == "rst":
if len(cite.citations) == 1:
raw = pf.RawInline(
":{0}:`{1}`".format(cite_role, cite.citations[0].id), format="rst"
)
elif cite_role == "cite":
raw = pf.RawInline(
":{0}:`{1}`".format(
cite_role, ",".join([c.id for c in cite.citations])
),
format="rst",
)
else:
raw = pf.RawInline(
", ".join(
[":{0}:`{1}`".format(cite_role, c.id) for c in cite.citations[:-1]]
)
+ " and :{0}:`{1}`".format(cite_role, cite.citations[-1].id),
format="rst",
)
# in testing, rst cite roles required space either side
# to render correctly
# TODO check if spacing is required for :cite: roles (and others)
if cite_role == "cite":
elem = span if span else cite # type: pf.Inline
raw = [raw]
if elem.prev and not isinstance(elem.prev, pf.Space):
raw.insert(0, pf.Space())
if elem.next and not isinstance(elem.next, pf.Space):
raw.append(pf.Space())
return raw
if doc.format in ("html", "html5"):
elements = []
cites = set()
names = dict()
unknown = set()
for citation in cite.citations:
ref = doc.get_metadata("$$references.{}".format(citation.id), False)
if ref:
# ref -> e.g. {"type": "Math", "number": 1}
prefix = dict(CITE_HTML_NAMES).get(ref["type"], ref["type"])
prefix = prefix.capitalize() if html_capitalize else prefix
# label = "{} {}".format(prefix, ref["number"])
# elements.append(pf.RawInline(
# '<a href="#{0}">{1}</a>'.format(citation.id, label),
# format=doc.format))
# found_ref = True
names.setdefault(prefix, set()).add(
'<a href="#{0}">{1}</a>'.format(citation.id, ref["number"])
)
elif citation.id in doc.bibdatabase:
cites.add(process_bib_entry(citation.id, doc.bibdatabase, doc.bibnums))
# elements.append(pf.RawInline(
# process_bib_entry(
# citation.id, doc.bibdatabase, doc.bibnums),
# format=doc.format))
# found_ref = True
else:
unknown.add(citation.id)
# elements.append(pf.Cite(citations=[citation]))
# if found_ref:
# return elements
# else:
# return pf.RawInline(
# '<span style="background-color:rgba(225, 0, 0, .5)">'
# # 'No reference found for: {}</span>'.format(
# '{}</span>'.format(
# ", ".join([c.id for c in cite.citations])))
elements = []
if cites:
# TODO sort
elements.append(
pf.RawInline(
"<span>[{}]</span>".format(",".join(c for c in cites)),
format=doc.format,
)
)
if names:
# TODO sort
for prefix, labels in names.items():
elements.append(
pf.RawInline(
"<span>{} {}</span>".format(
prefix, ",".join(l for l in labels)
),
format=doc.format,
)
)
if unknown:
elements.append(
pf.RawInline(
'<span style="background-color:rgba(225, 0, 0, .5)">'
# 'No reference found for: {}</span>'.format(
"{}</span>".format(", ".join([l for l in unknown]))
)
)
return elements
[docs]def format_span_cites(span, doc):
# type: (Cite, Doc) -> Element
if not isinstance(span, pf.Span):
return None
if CONVERTED_CITE_CLASS not in span.classes:
return None
if doc.format in ("latex", "tex"):
cite_tag = "cite"
if span.attributes["format"] == "latex":
cite_tag = span.attributes["tag"]
# TODO use cref for rst ref/numref
return pf.RawInline(
"\\{0}{{{1}}}".format(cite_tag, span.identifier, format="tex")
)
if doc.format == "rst":
cite_role = "cite"
if span.attributes["format"] == "rst":
cite_role = span.attributes["role"]
# TODO use ref for latex ref/cref/Cref
return [
pf.RawInline(":{0}:`{1}`".format(cite_role, span.identifier), format="rst")
]
if doc.format in ("html", "html5"):
# <cite data-cite="cite_key">text</cite>
return (
[
pf.RawInline(
'<cite data-cite="{}">'.format(span.identifier), format="html"
)
]
+ list(span.content)
+ [pf.RawInline("</cite>", format="html")]
)
[docs]def prepare(doc):
# type: (Doc) -> None
doc.bibnums = {}
doc.bibdatabase = {}
if doc.format in ("html", "html5"):
bib_path = doc.get_metadata("ipub.bibliography", None)
if bib_path:
doc.bibdatabase = read_bibliography(bib_path)
[docs]def strip_cite_spans(span, doc):
# type: (Span, Doc) -> Element
if isinstance(span, pf.Span) and ATTRIBUTE_CITE_CLASS in span.classes:
return list(span.content)
[docs]def main(doc=None, strip_spans=True):
# type: (Doc) -> None
to_run = [format_cites]
if strip_spans:
to_run.append(strip_cite_spans)
return pf.run_filters(to_run, prepare, finalize, doc=doc)
if __name__ == "__main__":
main()