1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
================
Designing styles
================
.. testsetup:: *
from __future__ import unicode_literals, print_function
from pybtex.richtext import Text, String, Tag, HRef, Protected, Symbol, nbsp, textutils
.. currentmodule:: pybtex.richtext
.. contents::
:local:
.. _rich-text:
Rich text
=========
Pybtex has a set of classes for working with formatted text
and producing formatted output.
A piece of formatted text in Pybtex is represented by a :py:class:`Text`
object.
A :py:class:`Text` is basically a container that holds a list of
* plain text parts, represented by :py:class:`String` objects,
* formatted parts, represented by :py:class:`Tag` and :py:class:`HRef` objects.
The basic workflow is:
1. Construct a :py:class:`Text` object.
2. Render it as LaTeX, HTML or other markup.
.. doctest::
>>> from pybtex.richtext import Text, Tag
>>> text = Text('How to be ', Tag('em', 'a cat'), '.')
>>> print(text.render_as('html'))
How to be <em>a cat</em>.
>>> print(text.render_as('latex'))
How to be \emph{a cat}.
Rich text classes
-----------------
There are several rich text classes in Pybtex:
- :py:class:`Text`
- :py:class:`String`
- :py:class:`Tag`
- :py:class:`HRef`
- :py:class:`Protected`
:py:class:`Text` is the top level container that may contain
:py:class:`String`, :py:class:`Tag`, and :py:class:`HRef` objects.
When a :py:class:`Text` object is rendered into markup,
it renders all of its child objects, then concatenates the result.
:py:class:`String` is just a wrapper for a single Python string.
:py:class:`Tag` and :py:class:`HRef` are also containers that may contain
other :py:class:`String`, :py:class:`Tag`, and :py:class:`HRef` objects. This
makes nested formatting possible. For example, this stupidly formatted text:
|CTAN hyperlink|_ is *comprehensive*.
.. |CTAN hyperlink| replace:: *Comprehensive* TeX Archive Network
.. _CTAN hyperlink: https://ctan.org/
is represented by this object tree:
.. doctest::
>>> text = Text(
... HRef('https://ctan.org/', Tag('em', 'Comprehensive'), ' TeX Archive Network'),
... ' is ',
... Tag('em', 'comprehensive'),
... '.',
... )
>>> print(text.render_as('html'))
<a href="https://ctan.org/"><em>Comprehensive</em> TeX Archive Network</a> is <em>comprehensive</em>.
:py:class:`Protected` represents a "protected" piece of text, something like
{braced text} in BibTeX. It is not affected by case-changing operations, like
:py:meth:`Text.upper()` or :py:meth:`Text.lower()`, and is not splittable by
:py:meth:`Text.split()`.
All rich text classes share the same API which is more or less similar to plain
`Python strings`_.
.. _Python strings: https://docs.python.org/3/library/stdtypes.html#string-methods
Like Python strings, rich text objects are supposed to be immutable. Methods like
:py:meth:`Text.append` or :py:meth:`Text.upper` return a new :py:class:`Text`
object instead of modifying the data in place.
Attempting to modify the contents of an existing :py:class:`Text` object is
not supported and may lead to weird results.
Here we document the methods of the :py:class:`Text` class.
The other classes have the same methods.
.. autoclass:: pybtex.richtext.Text
:members:
:inherited-members:
.. automethod:: Text.__init__
.. automethod:: Text.__eq__
.. automethod:: Text.__len__
.. automethod:: Text.__contains__
.. automethod:: Text.__getitem__
.. automethod:: Text.__add__
.. autoclass:: pybtex.richtext.String
.. autoclass:: pybtex.richtext.Tag
.. autoclass:: pybtex.richtext.HRef
.. autoclass:: pybtex.richtext.Protected
.. autoclass:: pybtex.richtext.Symbol
Style API
=========
A formatting style in Pybtex is a class inherited from
:py:class:`pybtex.style.formatting.BaseStyle`.
.. autoclass:: pybtex.style.formatting.BaseStyle
:members:
.. currentmodule:: pybtex.richtext
Pybtex loads the style class as a :doc:`plugin <plugins>`,
instantiates it with proper parameters and
calls the :py:meth:`~.BaseStyle.format_bibliography` method that does
the actual formatting job.
The default implementation of :py:meth:`~.BaseStyle.format_bibliography`
calls a ``format_<type>()`` method for each bibliography entry, where ``<type>``
is the entry type, in lowercase. For example, to format
an entry of type ``book``, the ``format_book()`` method is called.
The method must return a :py:class:`.Text` object.
Style classes are supposed to implement ``format_<type>()`` methods
for all entry types they support. If a formatting method
is not found for some entry, Pybtex complains about unsupported entry type.
An example minimalistic style:
.. sourcecode:: python
from pybtex.style.formatting import BaseStyle
from pybtex.richtext import Text, Tag
class MyStyle(BaseStyle):
def format_article(self, entry):
return Text('Article ', Tag('em', entry.fields['title']))
Template language
=================
Manually creating :py:class:`.Text` objects may be tedious.
Pybtex has a small template language to simplify common formatting tasks,
like joining words with spaces, adding commas and periods, or handling missing fields.
The template language is is not very documented for now, so you should look at
the code in the :source:`pybtex.style.template <pybtex/style/template.py>` module
and the :source:`existing styles <pybtex/style/formatting>`.
An example formatting style using template language:
.. sourcecode:: python
from pybtex.style.formatting import BaseStyle, toplevel
from pybtex.style.template import field, join, optional
class MyStyle(BaseStyle):
def format_article(self, entry):
if entry.fields['volume']:
volume_and_pages = join [field('volume'), optional [':', pages]]
else:
volume_and_pages = words ['pages', optional [pages]]
template = toplevel [
self.format_names('author'),
sentence [field('title')],
sentence [
tag('emph') [field('journal')], volume_and_pages, date],
]
return template.format_data(entry)
|