diff --git a/book/_config.yml b/book/_config.yml
index a81c825..769fcce 100644
--- a/book/_config.yml
+++ b/book/_config.yml
@@ -16,6 +16,7 @@ sphinx:
- sphinx.ext.imgconverter
- teachbooks_favourites
- teachbooks_sphinx_grasple
+ - sphinx_apa_references
config:
html_js_files:
- https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js
@@ -38,16 +39,11 @@ sphinx:
html_show_copyright: false
html_last_updated_fmt: '%B %-d, %Y'
html_static_path: ["images"]
- bibtex_default_style: myapastyle
- bibtex_reference_style: author_year_round
mathjax3_config:
tex:
macros:
"newcolor": ['\definecolor{newcolor}{RGB}{111, 29, 119}{\color{newcolor}#1}',1]
named_colors_custom_colors: {'onlylight':[165,21,160],'lightanddark':[45,180,117,204,158,110],'hyphen-color':[45,180,117,165,21,160],'attributiongrey':[150,150,150]}
- local_extensions:
- apastyle: _ext/
- bracket_citation_style: _ext/
teachbooks:
attribution_location: margin
diff --git a/book/_ext/apastyle.py b/book/_ext/apastyle.py
deleted file mode 100644
index 16828fe..0000000
--- a/book/_ext/apastyle.py
+++ /dev/null
@@ -1,17 +0,0 @@
-
-# from pybtex.style.formatting.unsrt import Style
-from pybtexapastyle.formatting.apa import APAStyle
-from pybtexapastyle.labels.apa import LabelStyle as APALabelStyle
-from pybtex.plugin import register_plugin
-# from pybtex.style.template import names, sentence
-
-class MyAPALabelStyle(APALabelStyle):
- def format_label(self, entry):
- return APALabelStyle.format_label(self, entry)
-
-class MyAPAStyle(APAStyle):
- default_label_style = 'myapa'
-
-def setup(app):
- register_plugin('pybtex.style.labels', 'myapa', MyAPALabelStyle)
- register_plugin('pybtex.style.formatting', 'myapastyle', MyAPAStyle)
\ No newline at end of file
diff --git a/book/_ext/bracket_citation_style.py b/book/_ext/bracket_citation_style.py
deleted file mode 100644
index cab10f3..0000000
--- a/book/_ext/bracket_citation_style.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from dataclasses import dataclass, field
-import sphinxcontrib.bibtex.plugin
-
-from sphinxcontrib.bibtex.style.referencing import BracketStyle
-from sphinxcontrib.bibtex.style.referencing.author_year import AuthorYearReferenceStyle
-
-
-def bracket_style() -> BracketStyle:
- return BracketStyle(
- left='(',
- right=')',
- )
-
-
-@dataclass
-class MyReferenceStyle(AuthorYearReferenceStyle):
- bracket_parenthetical: BracketStyle = field(default_factory=bracket_style)
- bracket_textual: BracketStyle = field(default_factory=bracket_style)
- bracket_author: BracketStyle = field(default_factory=bracket_style)
- bracket_label: BracketStyle = field(default_factory=bracket_style)
- bracket_year: BracketStyle = field(default_factory=bracket_style)
-
-
-def setup(app):
- sphinxcontrib.bibtex.plugin.register_plugin(
- 'sphinxcontrib.bibtex.style.referencing',
- 'author_year_round', MyReferenceStyle)
\ No newline at end of file
diff --git a/book/_ext/pybtexapastyle/LICENSE b/book/_ext/pybtexapastyle/LICENSE
deleted file mode 100644
index 579d367..0000000
--- a/book/_ext/pybtexapastyle/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2018 Naeka
-
-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.
\ No newline at end of file
diff --git a/book/_ext/pybtexapastyle/formatting/__init__.py b/book/_ext/pybtexapastyle/formatting/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/book/_ext/pybtexapastyle/formatting/apa.py b/book/_ext/pybtexapastyle/formatting/apa.py
deleted file mode 100644
index dfa5a58..0000000
--- a/book/_ext/pybtexapastyle/formatting/apa.py
+++ /dev/null
@@ -1,486 +0,0 @@
-# -*- coding:Utf-8 -*-
-from __future__ import unicode_literals
-
-import re
-import six
-
-from pybtex.plugin import find_plugin
-from pybtex.style.formatting import BaseStyle, toplevel
-from pybtex.style.template import (
- field, first_of, href, join, optional, optional_field, sentence, tag,
- together, words, node, FieldIsMissing
-)
-
-from pybtex.richtext import Text, Symbol
-
-
-firstlast = find_plugin('pybtex.style.names', 'lastfirst')()
-
-if six.PY2:
- def format_pages(text):
- dash_re = re.compile(r'-+')
- pages = Text(Symbol('ndash')).join(
- re.split(dash_re, six.text_type(text)))
- if re.search('[-‒–—―]', six.text_type(text)):
- return Text("pp.", Symbol('nbsp'), pages)
- return Text("p.", Symbol('nbsp'), pages)
-else:
- def format_pages(text):
- dash_re = re.compile(r'-+')
- pages = Text(Symbol('ndash')).join(text.split(dash_re))
- if re.search('[-‒–—―]', str(text)):
- return Text("pp.", Symbol('nbsp'), pages)
- return Text("p.", Symbol('nbsp'), pages)
-
-
-pages = field('pages', apply_func=format_pages)
-date = words[field('year'), optional[", ", field('month')]]
-
-
-@node
-def apa_names(children, context, role, **kwargs):
- """
- Returns formatted names as an APA compliant reference list citation.
- """
- assert not children
-
- try:
- persons = context['entry'].persons[role]
- except KeyError:
- raise FieldIsMissing(role, context['entry'])
-
- style = context['style']
-
- if len(persons) > 7:
- persons = persons[:6] + persons[-1:]
- formatted_names = [style.format_name(
- person, style.abbreviate_names) for person in persons]
- return join(sep=', ', last_sep=', … ')[
- formatted_names].format_data(context)
- else:
- formatted_names = [style.format_name(
- person, style.abbreviate_names) for person in persons]
- return join(sep=', ', sep2=', & ', last_sep=', & ')[
- formatted_names].format_data(context)
-
-
-@node
-def editor_names(children, context, with_suffix=True, **kwargs):
- """
- Returns formatted editor names for inbook.
- """
- assert not children
-
- try:
- editors = context['entry'].persons['editor']
- except KeyError:
- raise FieldIsMissing('editor', context['entry'])
-
- formatted_names = [
- firstlast.format(editor, True) for editor in editors]
-
- if with_suffix:
- return words[
- join(sep=', ', sep2=', & ', last_sep=', & ')[formatted_names],
- "(Eds.)" if len(editors) > 1 else "(Ed.)"
- ].format_data(context)
-
- return join(sep=', ', sep2=', & ', last_sep=', & ')[
- formatted_names
- ].format_data(context)
-
-
-class APAStyle(BaseStyle):
- name = 'apa'
- default_name_style = 'lastfirst'
- default_sorting_style = 'author_year_title'
- default_label_style = 'apa'
-
- def __init__(self, *args, **kwargs):
- super(APAStyle, self).__init__(*args, **kwargs)
- self.abbreviate_names = True
-
- def format_names(self, role, as_sentence=True):
- formatted_names = apa_names(role)
- if as_sentence:
- return sentence(capfirst=False)[formatted_names]
- else:
- return formatted_names
-
- def format_author_or_editor_and_date(self, e):
- if 'author' in e.persons and 'editor' in e.persons:
- return sentence(sep=' ')[
- self.format_names('author'), join["(", date, ")."],
- self.format_editor(e, as_sentence=False)]
- elif 'author' in e.persons:
- return sentence(sep=' ')[
- self.format_names('author'), join["(", date, ")"]]
- else:
- return sentence(sep=' ')[
- self.format_editor(e, as_sentence=False),
- join["(", date, ")"]]
-
- def format_editor(self, e, as_sentence=True):
- editors = self.format_names('editor', as_sentence=False)
- if 'editor' not in e.persons:
- # when parsing the template, a FieldIsMissing exception
- # will be thrown anyway; no need to do anything now,
- # just return the template that will throw the exception
- return editors
- if len(e.persons['editor']) > 1:
- word = '(Eds.)'
- else:
- word = '(Ed.)'
- result = join(sep=' ')[editors, word]
- if as_sentence:
- return sentence[result]
- else:
- return result
-
- def format_volume(self, e, for_article=False):
- prefix = "Vol."
- if for_article:
- return join[
- tag('em')[field('volume')],
- optional['(', field('number'), ')'],
- ]
- else:
- return optional[together[prefix, field('volume')]]
-
- def format_title(self, e, which_field, as_sentence=True):
- formatted_title = field(
- which_field, apply_func=lambda text: text.capitalize()
- )
- if as_sentence:
- return sentence[formatted_title]
- else:
- return formatted_title
-
- def format_btitle(self, e, which_field, as_sentence=True):
- formatted_title = tag('em')[field(which_field)]
- if as_sentence:
- return sentence[formatted_title]
- else:
- return formatted_title
-
- def format_web_refs(self, e):
- # Based on urlbst output.web.refs
- return sentence(add_period=False)[
- optional[self.format_url(e)],
- optional[self.format_eprint(e)],
- optional[self.format_pubmed(e)],
- optional[self.format_doi(e)],
- ]
-
- def format_url(self, e):
- # Based on urlbst format.url
- return words[
- 'URL:',
- href[
- field('url', raw=True),
- field('url', raw=True)
- ]
- ]
-
- def format_pubmed(self, e):
- # Based on urlbst format.pubmed
- return href[
- join[
- 'https://www.ncbi.nlm.nih.gov/pubmed/',
- field('pubmed', raw=True)
- ],
- join[
- 'PMID:',
- field('pubmed', raw=True)
- ]
- ]
-
- def format_doi(self, e):
- # Based on urlbst format.doi
- return href[
- join[
- 'https://doi.org/',
- field('doi', raw=True)
- ],
- join[
- 'doi:',
- field('doi', raw=True)
- ]
- ]
-
- def format_eprint(self, e):
- # Based on urlbst format.eprint
- return href[
- join[
- 'https://arxiv.org/abs/',
- field('eprint', raw=True)
- ],
- join[
- 'arXiv:',
- field('eprint', raw=True)
- ]
- ]
-
- def get_article_template(self, e):
- # Required fields: author, title, journal, year
- # Optional fields: volume, number, pages, month, note, key
- volume_and_pages = first_of[
- # Volume and pages, with optional issue number
- optional[
- join[
- self.format_volume(e, for_article=True),
- optional[', ', field('pages')]
- ],
- ],
- # Pages only
- pages,
- ]
- template = toplevel[
- self.format_names('author'),
- sentence[
- join["(", date, ")"]
- ],
- self.format_title(e, 'title'),
- sentence[
- tag('em')[field('journal')],
- optional[volume_and_pages],
- ],
- sentence[optional_field('note')],
- self.format_web_refs(e),
- ]
- return template
-
- def get_book_template(self, e):
- # Required fields: author/editor, title, publisher, year
- # Optional fields: volume, series, address, edition, month, note, key,
- # isbn
- return toplevel[
- self.format_author_or_editor_and_date(e),
- sentence(sep=' ')[
- self.format_btitle(e, 'title'),
- optional[
- sentence[
- optional[field('edition'), ' ed.'],
- self.format_volume(e),
- ]
- ]
- ],
- sentence(sep=': ')[
- optional_field('address'),
- field('publisher'),
- ],
- sentence[optional_field('note')],
- ]
-
- def get_booklet_template(self, e):
- # Required fields: title
- # Optional fields: author, howpublished, address, month, year, note,
- # key
- return toplevel[
- sentence(sep=' ')[
- first_of[
- optional[self.format_names(
- 'author', as_sentence=False)],
- "None to claim their bones"
- ],
- join["(", first_of[optional[date], "n.d."], ")"]
- ],
- self.format_title(e, 'title'),
- sentence[optional_field('address')],
- sentence[optional_field('note')],
- ]
-
- def get_inbook_template(self, e):
- # Required fields: author/editor, title, chapter/pages, publisher, year
- # Optional fields: volume, series, address, edition, month, note, key
- return toplevel[
- sentence(sep=' ')[
- self.format_names('author'),
- join["(", date, ")"]
- ],
- self.format_title(e, 'title'),
- sentence(sep=' ')[
- optional[
- "In ",
- editor_names(),
- ","
- ],
- self.format_btitle(e, 'booktitle', as_sentence=False),
- optional[
- join[
- "(",
- sentence(add_period=False)[
- optional[field('edition'), ' ed.'],
- self.format_volume(e),
- pages,
- ],
- ")"
- ]
- ]
- ],
- sentence(sep=': ')[
- optional_field('address'),
- field('publisher'),
- ],
- sentence[optional_field('note')],
- ]
-
- def get_incollection_template(self, e):
- # Required fields: author, title, booktitle, year
- # Optional fields: editor, pages, organization, publisher, address,
- # month, note, key
- return toplevel[
- self.format_author_or_editor_and_date(e),
- self.format_title(e, 'title'),
- sentence(sep=' ')[
- self.format_btitle(e, 'booktitle', as_sentence=False),
- optional["(", pages, ")"]
- ],
- sentence(sep=': ')[
- optional_field('address'),
- optional_field('publisher'),
- ],
- sentence[optional_field('note')],
- ]
-
- def get_inproceedings_template(self, e):
- # Required fields: author, title, booktitle, year
- # Optional fields: editor, pages, organization, publisher, address,
- # month, note, key
- return toplevel[
- self.format_author_or_editor_and_date(e),
- self.format_title(e, 'title'),
- sentence(sep=' ')[
- self.format_btitle(e, 'booktitle', as_sentence=False),
- optional["(", pages, ")"]
- ],
- sentence(sep=': ')[
- optional_field('address'),
- optional_field('publisher'),
- ],
- sentence[optional_field('note')],
- self.format_web_refs(e)
- ]
-
- def get_manual_template(self, e):
- # Required fields: title
- # Optional fields: author, organization, address, edition, month, year,
- # note, key
- return toplevel[
- sentence(sep=' ')[
- first_of[
- optional[self.format_names('author', as_sentence=False)],
- optional_field('organization'),
- "None to claim their bones"
- ],
- join["(", first_of[optional[date], "n.d."], ")"]
- ],
- self.format_btitle(e, 'title'),
- sentence[optional_field('address')],
- sentence[optional_field('note')],
- self.format_web_refs(e)
- ]
-
- def get_mastersthesis_template(self, e):
- # Required fields: author, title, school, year
- # Optional fields: address, month, note, key
- return toplevel[
- sentence(sep=' ')[
- self.format_names('author'),
- join["(", date, ")"]
- ],
- sentence(sep=' ')[
- self.format_btitle(e, 'title', as_sentence=False),
- "(Master's thesis)"
- ],
- sentence[
- field('school'),
- optional_field('address'),
- ],
- sentence[optional_field('note')],
- ]
-
- def get_misc_template(self, e):
- # Required fields: aucun
- # Optional fields: author, title, howpublished, month, year, note, key,
- # type
- template = toplevel[
- sentence(sep=' ')[
- first_of[
- optional[self.format_names('author', as_sentence=False)],
- optional_field('publisher'),
- "None to claim their bones"
- ],
- join["(", first_of[optional[date], "n.d."], ")"]
- ],
- optional[self.format_title(e, 'title')],
- sentence[optional_field('note')],
- self.format_web_refs(e)
- ]
- return template
-
- def get_phdthesis_template(self, e):
- # Required fields: author, title, school, year
- # Optional fields: address, month, note, key
- return toplevel[
- sentence(sep=' ')[
- self.format_names('author'),
- join["(", date, ")"]
- ],
- sentence(sep=' ')[
- self.format_btitle(e, 'title', as_sentence=False),
- "(Doctoral dissertation)"
- ],
- sentence[
- field('school'),
- optional_field('address'),
- ],
- sentence[optional_field('note')],
- ]
-
- def get_proceedings_template(self, e):
- # Required fields: title, year
- # Optional fields: editor, publisher, organization, address, month,
- # note, key
- return toplevel[
- sentence(sep=' ')[
- first_of[
- optional[self.format_editor(e, as_sentence=False)],
- optional_field('organization'),
- "None to claim their bones"
- ],
- join["(", first_of[optional[date], "n.d."], ")"]
- ],
- self.format_btitle(e, 'title'),
- sentence(sep=': ')[
- optional_field('address'),
- optional_field('publisher'),
- ],
- sentence[optional_field('note')],
- ]
-
- def get_techreport_template(self, e):
- # Required fields: author, title, institution, year
- # Optional fields: type, number, address, month, note, key
- return toplevel[
- sentence(sep=' ')[
- self.format_names('author', as_sentence=False),
- join["(", date, ")"]
- ],
- self.format_btitle(e, 'title'),
- sentence[field('institution')],
- sentence[optional_field('note')],
- ]
-
- def get_unpublished_template(self, e):
- # Required fields: author, title, note
- # Optional fields: month, year, key
- template = toplevel[
- sentence(sep=' ')[
- self.format_names('author', as_sentence=False),
- join["(", first_of[optional[date], "n.d."], ")"]
- ],
- self.format_btitle(e, 'title'),
- sentence[field('note')],
- ]
- return template
diff --git a/book/_ext/pybtexapastyle/formatting/pybtex/style/formatting/__init__.py b/book/_ext/pybtexapastyle/formatting/pybtex/style/formatting/__init__.py
deleted file mode 100644
index 5ac42b8..0000000
--- a/book/_ext/pybtexapastyle/formatting/pybtex/style/formatting/__init__.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from __future__ import unicode_literals
-
-from pybtex.style import FormattedEntry, FormattedBibliography
-from pybtex.style.template import node, join
-from pybtex.richtext import Symbol
-from pybtex.plugin import Plugin, find_plugin
-
-
-@node
-def toplevel(children, data):
- return join(sep=Symbol('newblock')) [children].format_data(data)
-
-
-class BaseStyle(Plugin):
- """
- The base class for pythonic formatting styles.
- """
-
- default_name_style = None
- default_label_style = None
- default_sorting_style = None
-
- def __init__(self, label_style=None, name_style=None, sorting_style=None, abbreviate_names=False, min_crossrefs=2, **kwargs):
- self.name_style = find_plugin('pybtex.style.names', name_style or self.default_name_style)()
- self.label_style = find_plugin('pybtex.style.labels', label_style or self.default_label_style)()
- self.sorting_style = find_plugin('pybtex.style.sorting', sorting_style or self.default_sorting_style)()
- self.format_name = self.name_style.format
- self.format_labels = self.label_style.format_labels
- self.sort = self.sorting_style.sort
- self.abbreviate_names = abbreviate_names
- self.min_crossrefs = min_crossrefs
-
- def format_entries(self, entries, bib_data=None):
- sorted_entries = self.sort(entries)
- labels = self.format_labels(sorted_entries)
- for label, entry in zip(labels, sorted_entries):
- yield self.format_entry(label, entry, bib_data=bib_data)
-
- def format_entry(self, label, entry, bib_data=None):
- context = {
- 'entry': entry,
- 'style': self,
- 'bib_data': bib_data,
- }
- try:
- get_template = getattr(self, 'get_{}_template'.format(entry.type))
- except AttributeError:
- format_method = getattr(self, "format_" + entry.type)
- text = format_method(context)
- else:
- text = get_template(entry).format_data(context)
- return FormattedEntry(entry.key, text, label)
-
- def format_bibliography(self, bib_data, citations=None):
- """
- Format bibliography entries with the given keys and return a
- ``FormattedBibliography`` object.
-
- :param bib_data: A :py:class:`pybtex.database.BibliographyData` object.
- :param citations: A list of citation keys.
- """
-
- if citations is None:
- citations = list(bib_data.entries.keys())
- citations = bib_data.add_extra_citations(citations, self.min_crossrefs)
- entries = [bib_data.entries[key] for key in citations]
- formatted_entries = self.format_entries(entries)
- formatted_bibliography = FormattedBibliography(formatted_entries, style=self, preamble=bib_data.preamble)
- return formatted_bibliography
diff --git a/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/__init__.py b/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/__init__.py
deleted file mode 100644
index 3430087..0000000
--- a/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from __future__ import unicode_literals
-
-
-from pybtex.plugin import Plugin
-
-
-class BaseSortingStyle(Plugin):
- def sorting_key(self, entry):
- raise NotImplementedError
-
- def sort(self, entries):
- return sorted(entries, key=self.sorting_key)
\ No newline at end of file
diff --git a/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/author_year_title.py b/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/author_year_title.py
deleted file mode 100644
index c315c0c..0000000
--- a/book/_ext/pybtexapastyle/formatting/pybtex/style/sorting/author_year_title.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from __future__ import unicode_literals
-
-from pybtex.style.sorting import BaseSortingStyle
-
-class SortingStyle(BaseSortingStyle):
-
- def sorting_key(self, entry):
- if entry.type in ('book', 'inbook'):
- author_key = self.author_editor_key(entry)
- elif 'author' in entry.persons:
- author_key = self.persons_key(entry.persons['author'])
- else:
- author_key = ''
- return (author_key, entry.fields.get('year', ''), entry.fields.get('title', ''))
-
- def persons_key(self, persons):
- return ' '.join(self.person_key(person) for person in persons)
-
- def person_key(self, person):
- if person.prelast_names[0] != '{' and person.last_names[0] != '{' and person.first_names[0] != '{':
- return ' '.join((
- ' '.join(person.prelast_names + person.last_names),
- ' '.join(person.first_names + person.middle_names),
- ' '.join(person.lineage_names),
- )).lower()
- else:
- return ' '.join((
- ' '.join(person.first_names + person.middle_names),
- ' '.join(person.prelast_names + person.last_names),
- ' '.join(person.lineage_names),
- )).lower()
-
- def author_editor_key(self, entry):
- if entry.persons.get('author'):
- return self.persons_key(entry.persons['author'])
- elif entry.persons.get('editor'):
- return self.persons_key(entry.persons['editor'])
- else:
- return ''
diff --git a/book/_ext/pybtexapastyle/labels/__init__.py b/book/_ext/pybtexapastyle/labels/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/book/_ext/pybtexapastyle/labels/apa.py b/book/_ext/pybtexapastyle/labels/apa.py
deleted file mode 100644
index 3632dc6..0000000
--- a/book/_ext/pybtexapastyle/labels/apa.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# -*- coding:Utf-8 -*-
-from __future__ import unicode_literals
-
-from collections import Counter
-import re
-import unicodedata
-
-from pybtex.style.labels import BaseLabelStyle
-
-
-_nonalnum_pattern = re.compile('[^A-Za-z0-9 \-]+', re.UNICODE)
-
-
-def _strip_accents(s):
- return "".join(
- (c for c in unicodedata.normalize('NFD', s)
- if not unicodedata.combining(c)))
-
-
-def _strip_nonalnum(parts):
- """Strip all non-alphanumerical characters from a list of strings.
-
- >>> print(_strip_nonalnum([u"ÅA. B. Testing 12+}[.@~_", u" 3%"]))
- AABTesting123
- """
- s = "".join(parts)
- return _nonalnum_pattern.sub("", _strip_accents(s))
-
-
-class LabelStyle(BaseLabelStyle):
- def format_labels(self, sorted_entries):
- labels = [self.format_label(entry) for entry in sorted_entries]
- count = Counter(labels)
- counted = Counter()
- for label in labels:
- if count[label] == 1:
- yield label
- else:
- yield label # + chr(ord('a') + counted[label])
- # counted.update([label])
-
- def format_label(self, entry):
- label = "Anonymous"
- if 'author' in entry.persons:
- label = self.format_author_or_editor_names(entry.persons['author'])
- elif 'editor' in entry.persons:
- label = self.format_author_or_editor_names(entry.persons['editor'])
- elif 'organization' in entry.fields:
- label = entry.fields['organization']
- if label.startswith("The "):
- label = label[4:]
- return ''
- # if 'year' in entry.fields:
- # return "{}, {}".format(label, entry.fields['year'])
- # else:
- # return "{}, n.d.".format(label)
-
- def format_author_or_editor_names(self, persons):
- if len(persons)==1:
- return _strip_nonalnum(persons[0].last_names)
- elif len(persons)==2:
- return "{} & {}".format(
- _strip_nonalnum(persons[0].last_names),
- _strip_nonalnum(persons[1].last_names))
- else:
- return "{} et al.".format(
- _strip_nonalnum(persons[0].last_names))
diff --git a/book/_ext/pybtexapastyle/names/__init__.py b/book/_ext/pybtexapastyle/names/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/book/_ext/pybtexapastyle/names/firstlast.py b/book/_ext/pybtexapastyle/names/firstlast.py
deleted file mode 100644
index 8f23263..0000000
--- a/book/_ext/pybtexapastyle/names/firstlast.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding:Utf-8 -*-
-from __future__ import unicode_literals
-
-from pybtex.style.names import BaseNameStyle, name_part
-from pybtex.style.template import join
-
-
-class NameStyle(BaseNameStyle):
-
- def format(self, person, abbr=False):
- r"""
- Format names similarly to {vv~}{ll}{, jj}{, f.} in BibTeX.
-
- >>> from pybtex.database import Person
- >>> name = Person(string=r"Charles Louis Xavier Joseph de la Vall{\'e}e Poussin")
- >>> firstlast = NameStyle().format
-
- >>> print(firstlast(name).format().render_as('latex'))
- Charles Louis Xavier~Joseph de~la Vall{é}e~Poussin
- >>> print(firstlast(name).format().render_as('html'))
- Charles Louis Xavier Joseph de la Vallée Poussin
-
- >>> print(firstlast(name, abbr=True).format().render_as('latex'))
- C.~L. X.~J. de~la Vall{é}e~Poussin
- >>> print(firstlast(name, abbr=True).format().render_as('html'))
- C. L. X. J. de la Vallée Poussin
-
- >>> name = Person(first='First', last='Last', middle='Middle')
- >>> print(firstlast(name).format().render_as('latex'))
- First~Middle Last
- >>> print(firstlast(name, abbr=True).format().render_as('latex'))
- F.~M. Last
-
- """
- return join[
- name_part(abbr=abbr)[person.rich_first_names + person.rich_middle_names],
- name_part(before=' ')[person.rich_prelast_names],
- name_part(before=' ')[person.rich_last_names],
- ]
diff --git a/book/_ext/pybtexapastyle/setup.py b/book/_ext/pybtexapastyle/setup.py
deleted file mode 100644
index a62d122..0000000
--- a/book/_ext/pybtexapastyle/setup.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import setuptools
-
-
-setuptools.setup(
- name='pybtex-apa-style',
- version='1.3',
- author='Naeka',
- author_email='contact@naeka.fr',
- description='Pybtex APA-like style',
- url='https://github.com/naeka/pybtex-apa-style',
- py_modules=['formatting.apa', 'labels.apa', 'names.firstlast'],
- entry_points={
- 'pybtex.style.formatting': 'apa = formatting.apa:APAStyle',
- 'pybtex.style.labels': 'apa = labels.apa:LabelStyle',
- 'pybtex.style.names': 'firstlast = names.firstlast:NameStyle',
- },
- classifiers=(
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'License :: OSI Approved :: MIT License',
- 'Operating System :: OS Independent',
- 'Topic :: Text Processing :: Markup',
- 'Topic :: Utilities',
- ),
-)
diff --git a/book/_static/apastyle.css b/book/_static/apastyle.css
deleted file mode 100644
index 01d759c..0000000
--- a/book/_static/apastyle.css
+++ /dev/null
@@ -1,3 +0,0 @@
-span.label:not(:has(:not(.fn-bracket))) {
- display: none;
-}
diff --git a/book/features/apa.md b/book/features/apa.md
index 897e160..6d2c670 100644
--- a/book/features/apa.md
+++ b/book/features/apa.md
@@ -4,25 +4,13 @@
This page is useful for user type 4 and 5.
```
-{bdg-primary}`(To be converted into) Sphinx Extension`
-{bdg-link-secondary}`Supported by TeachBooks package <./teachbooks_intro.html>`
-
+{bdg-primary}`Sphinx Extension`
````
(apa)=
# APA References
-```{note}
-This feature is stable but because it relies on a local extension that is part of your book files and _not_ managed as part of a computing environment (e.g., `pip` or `conda`), unknown issues may arise. We would like to convert this feature into an independent Sphinx extension and/or Pybtext plugin. Visit the README in the [project page on GitHub](https://github.com/orgs/TeachBooks/projects/17?pane=info) to learn more.
-
-The instructions provided here will work under conventional usage with TeachBooks and Jupyter Book v1 (e.g., `jupyter book build book`).
-
-A temporary fix has also been implemented in the TeachBooks package for use with the Deploy Book Workflow (release mode) (see [release notes from v0.1.0](https://github.com/TeachBooks/TeachBooks/releases/tag/v0.1.0)).
-
-_Update, October 21, 2025:_ a new fix was necessitated due to dependency issues with docutils making it no longer necessary to pin this package. See [PR 207 of the Manual for a description](https://github.com/TeachBooks/manual/pull/207).
-```
-
## Introduction
Do you include references in your book, but you're tired of the default options available in a TeachBook / Jupyter Book v1? For example, the square-bracket style of citation and reference, where the first three letters of the name year are combined in a way that seems designed to minimize transparency?
@@ -30,77 +18,95 @@ Do you include references in your book, but you're tired of the default options
There is a solution! This extension allows you to have APA formatting in your book.
## Installation
-To use APA-references, a set of Python files need to be manually loaded into your book.
-**Step 1: files to root directory of your book**
+To use APA-references, install the [Sphinx-APA-References extension](https://github.com/TeachBooks/Sphinx-APA-References) by following these two steps:
-Download this zip-file [`apa_config.zip`](apa_config.zip), which contains the necessary files, located in _two_ subdirectories: `_ext` and `_static`. Unzip and place the contents in the root of your book directory so that it looks like the schematic here:
+**Step 1: Install the Package**
+Install the module `sphinx-apa-references` package using `pip`:
```
-book
-├── _ext/
-│ ├── pybtexapastyle/
-│ ├── apastyle.py
-│ └── bracket_citation_style.py
-├── _static/
-| ├── ...
-│ └── apastyle.css
-├── _config.yml
-├── _toc.yml
-├── requirements.txt
-├──
+pip install sphinx-apa-references
+```
+
+**Step 2: Add to `requirements.txt`**
+
+Make sure that the package is included in your project's `requirements.txt` to track the dependency:
+```
+sphinx-apa-references
```
-Note that you may or may not already have a `_static` subdirectory, to which the file `apastyle.css` should be added.
-
-**Step 2: Enable in `_config.yml`**
-
-In your `_config.yml` file, add a `bibtex_default_style`, `bybtex_reference_style` and the local extensions `apastyle` and `bracket_citation_style.py`. This enables the (local) extension.
+**Step 3: Enable in `_config.yml`
+In your `_config.yml` file, add the extension to the list of Sphinx extra extensions (**important**: underscore, not dash this time):
```
-sphinx:
- config:
- ...
- bibtex_default_style: myapastyle
- bibtex_reference_style: author_year_round
- local_extensions:
- apastyle: _ext/
- bracket_citation_style: _ext/
- ...
+sphinx:
+ extra_extensions:
+ .
+ .
+ .
+ - sphinx_apa_references
+ .
+ .
+ .
```
## Usage
-All references are now made in APA-style. See for example this reference: {cite:t}`jason_moore` which shows up on the [references page](../references.md) too. The form of the citation looks like this:
+All references are now made in APA-style. Here are five examples how to create a citation in APA style:
-```
-{cite:t}`jason_moore`
-```
-
-Here are three examples for making citations:
-
-| Syntax | Result |
-| :---: | :---: |
-|`{cite:t}` | {cite:t}`jason_moore` |
-|`{cite:p}`| {cite:p}`jason_moore` |
-|`{cite}`| {cite}`jason_moore` |
+| Syntax | Result with 1 author | Result with many authors | Explanation |
+| :---: | :---: | :---: | :---: |
+|`{cite:t}` | {cite:t}`jason_moore` | {cite:t}`dummyreference` | Textual citation |
+|`{cite:p}`| {cite:p}`jason_moore` | {cite:p}`dummyreference` | Parenthetical citation |
+|`{cite}`| {cite}`jason_moore` | {cite}`dummyreference` | Same as `{cite:p}` |
+|`{cite:ts}` | {cite:ts}`jason_moore` | {cite:ts}`dummyreference` | Same as `{cite:t}`, but without abbreviation of authors |
+|`{cite:ps}`| {cite:ps}`jason_moore` | {cite:ps}`dummyreference` |Same as `{cite:p}`, but without abbreviation of authors |
For more options on the in-line citation style, see https://jupyterbook.org/v1/content/citations.html#change-the-in-line-citation-style.
-### Known Issues
+The references section at the end of your book will also be formatted in APA style:
-Two issues are known:
-- During the build, warning will be raised with `... WARNING: duplicate label for keys ...`. In most cases, these warnings can be ignored. As noted above, the book will not build references properly
-- In case you have both an `.ipynb` and `.md` version of a file, the `.md` version will always be used whenever this extension is used. This removes the possibility to show code outputs.
+::::{admonition} Example of APA References
+:class: example
+
+```{iframe} ../references.html
+:class: no-blend
+```
+::::
+
+## Duplicate label warning
+
+You might encounter a warning during the build process that has this structure:
+
+```
+:: WARNING: duplicate label ", " for keys "" and ""
+```
+
+This is might have different cause, but often it is because two different references have the same author(s) and the same year. A common solution is to add letters to the year in the `.bib` file, like this:
+
+```bibtex
+@misc{launch_buttons,
+ author = {{TeachBooks}},
+ title = {Custom launch buttons},
+ url = {https://github.com/TeachBooks/Sphinx-launch-buttons},
+ year = {2024a},
+ note = {Retrieved December, 2024. BSD-3-Clause.},
+}
+
+@misc{nested,
+ author = {{TeachBooks}},
+ title = {Git workflow: Share content between book},
+ url = {https://github.com/TeachBooks/Nested-Books},
+ year = {2024b},
+ note = {Retrieved December, 2024. CC BY 4.0.},
+}
+
+```
+
+This does negate the automatic ordering of references by the order `authors > year > title`, so our advice is to only use this solution when the bibliography contains duplicate author-year combinations ánd the bibliography is final. That way the you can use the sorted order with warnings to determine the correct letters to add to the years.
## Implementation
The extension is based on [`pybtex`](https://pybtex.org/), which is a BibTeX-compatible bibliography processor in Python that is extendible with plugins.
-Although some customization is possible with the standard Jupyter Book v1 features, [as described here](https://jupyterbook.org/v1/content/citations.html#change-the-in-line-citation-style), this extension implements the complete APA style, as well as enforcing round brackets (like this).
-
-The need to include a CSS file is an easy solution to the issue where empty brackets `[]` are left on the references page (to try it, simply delete `apastyle.css`).
-
-## Contribute
-
-This tool needs to be developed further to make it into a proper sphinx-extension (and/or an independent `pybtext` plugin). The process is described in the README of this [project page on GitHub](https://github.com/orgs/TeachBooks/projects/17?pane=info). If you've ideas or questions, please reach out to us at info@teachbooks.io!
+Although some customization is possible with the standard Jupyter Book v1 features, [as described here](https://jupyterbook.org/v1/content/citations.html#change-the-in-line-citation-style), this extension implements a complete APA style, as well as enforcing round brackets (like this).
diff --git a/book/features/apa_config.zip b/book/features/apa_config.zip
deleted file mode 100644
index eedf090..0000000
Binary files a/book/features/apa_config.zip and /dev/null differ
diff --git a/book/references.bib b/book/references.bib
index 292c741..0358ae3 100644
--- a/book/references.bib
+++ b/book/references.bib
@@ -197,4 +197,13 @@
url = {https://github.com/executablebooks/sphinx-exercise},
year = {2024},
note = {Retrieved September, 2025. MIT License.},
-}
\ No newline at end of file
+}
+
+@inproceedings{dummyreference,
+ author = {Author, Some and Writer, Another and Novelist, Also and Cowriter, Maybe},
+ title = {Efficient Estimation of Word Representations in Vector Space},
+ booktitle = {Proceedings of Workshop at ICLR},
+ year = {2013},
+ url = {https://arxiv.org/abs/1301.3781},
+}
+
diff --git a/requirements.txt b/requirements.txt
index 32f8e84..9378a18 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,4 +3,6 @@ sphinx-inline-tabs
git+https://github.com/TeachBooks/TeachBooks-Favourites
-teachbooks-sphinx-grasple
\ No newline at end of file
+teachbooks-sphinx-grasple
+
+sphinx-apa-references
\ No newline at end of file