import codecs
import locale
import pathlib
import os
import re
class Cite:
"""Citation package emurating contents and commands.
Parameters
----------
citeleft : str, default '['
Left delimiter of list.
citeright : str, default ']'
Right delimiter of list.
use_cite_package : bool, default False
If False, emulate LaTeX's use_cite_package citation handling.
If True, emulate cite package's behavior.
"""
def __init__(
self,
citeleft='[',
citeright=']',
targetbasename='wdbib',
use_cite_package=False,
workdir='.tmp',
):
"""Costructor of Cite.
"""
# Store settings in internal attributes.
if os.path.isabs(workdir):
self.workdir = pathlib.Path(workdir)
else:
self.workdir = (
pathlib.Path(os.getcwd()) / workdir
).resolve()
self._targetbasename = targetbasename
self._replacer = None
self._citation = []
self._bibstyle = None
self._bibdata = None
self._bibcite = {}
self._conversion_dict = {}
self._citation_labels = dict()
self._citeleft = citeleft
self._citeright = citeright
self._use_cite_package = use_cite_package
self._citation_keys_in_context = []
@property
def citeleft(self):
r"""Left delimiter of list. Default '['.
Returns
-------
str
Left delimiter of list.
Examples
--------
>>> import wdbibtex
>>> tx = wdbibtex.LaTeX()
>>> tx.citation_labels = {'key1': 1, 'key2': 2, 'key3': 3}
>>> tx.citeleft
'['
>>> tx.cite('\\cite{key1}')
'[1]'
>>> tx.cite('\\cite{key2,key3}')
'[2,3]'
>>> tx.cite('\\cite{key3,key2,key1}')
'[3,2,1]'
>>> tx.citeleft = '('
>>> tx.citeleft
'('
>>> tx.cite('\\cite{key1}')
'(1]'
>>> tx.cite('\\cite{key2,key3}')
'(2,3]'
>>> tx.cite('\\cite{key3,key2,key1}')
'(3,2,1]'
"""
return self._citeleft
@citeleft.setter
def citeleft(self, s):
if not isinstance(s, str):
TypeError(
'expected string object but '
'%s object given.' % type(s))
self._citeleft = s
@property
def citeright(self):
r"""Right delimiter of list. Default ']'.
Returns
-------
str
Right delimiter of list.
Examples
--------
>>> import wdbibtex
>>> tx = wdbibtex.LaTeX()
>>> tx.citation_labels = {'key1': 1, 'key2': 2, 'key3': 3}
>>> tx.citeright
']'
>>> tx.cite('\\cite{key1}')
'[1]'
>>> tx.cite('\\cite{key2,key3}')
'[2,3]'
>>> tx.cite('\\cite{key3,key2,key1}')
'[3,2,1]'
>>> tx.citeright = ')'
>>> tx.citeright
')'
>>> tx.cite('\\cite{key1}')
'[1)'
>>> tx.cite('\\cite{key2,key3}')
'[2,3)'
>>> tx.cite('\\cite{key3,key2,key1}')
'[3,2,1)'
"""
return self._citeright
@citeright.setter
def citeright(self, s):
if not isinstance(s, str):
TypeError(
'expected string object but '
'%s object given.' % type(s))
self._citeright = s
@property
def citation_labels(self):
"""Key to number map of citations.
Returns
-------
dict
Citation key to citation number map.
"""
return self._citation_labels
@citation_labels.setter
def citation_labels(self, d):
if not isinstance(d, str):
TypeError(
'expected dictionary object but '
'%s object given.' % type(d))
self._citation_labels = d
def _parse_context(self, c):
r"""Find all citation keys from context written to .tex file.
Find all citation keys from context written to .tex file.
Found keys are stores to citation_keys_in_context attribute.
Parameters
----------
c : str
Parsed texts.
Examples
--------
>>> import wdbibtex
>>> tx = wdbibtex.LaTeX()
>>> tx._parse_context(
... 'Some citation \\cite{key}. Some example \\cite{key1,key2}'
... )
>>> tx._citation_keys_in_context
['key', 'key1,key2']
"""
found_keys = re.findall(r'\\+cite\{(.*?)\}', c)
for k in found_keys:
self._citation_keys_in_context.append(k)
def read_aux(self):
r"""Read .aux file.
Aux file will be read line-by-line.
Following four types of the line will be
interpreted and stored to the LaTeX attributes.
- \\citation{keys}
Appended to the citation attribute
(list object) key as string.
- \\bibstyle{s}
Stored as bibstyle string attribute.
- \\bibdata{d}
Stored as bibdata string attribute.
- \\bibcite{k}{n}
Added to bibcite attribute
(dictionary) as {k: n}.
"""
fn = self.workdir / (self._targetbasename + '.aux')
with codecs.open(fn, 'r', 'utf-8') as f:
self._auxdata = f.readlines()
for line in self._auxdata:
self._parse_line(line)
self._build_conversion_dict()
self._citation_labels.update(self._bibcite)
self._get_replacer()
def _parse_line(self, line):
r"""Parse one line of .aux
Parameters
----------
line : str
One line of .aux file to parse.
"""
if line.startswith('\\citation'):
self._citation.append(line[len('\\citation{'): -len('}\n')])
elif line.startswith('\\bibstyle'):
self._bibstyle = line[len('\\bibstyle{'): -len('}\n')]
elif line.startswith('\\bibdata'):
self._bibdata = line[len('\\bibdata{'): -len('}\n')]
elif line.startswith('\\bibcite'):
key, value = line[len('\\bibcite{'): -len('}\n')].split('}{')
value = int(value)
self._bibcite.update({key: value})
def _get_replacer(self):
"""Get key and value for replace word document.
"""
replacer = dict()
for k, v in self._conversion_dict.items():
replacer.update({'\\\\cite\\{%s\\}' % k: '[%s]' % v})
self._replacer = replacer
def _build_conversion_dict(self):
r"""Prepare replaing citation keys with dashed range strings.
Generate dictionary of such as {'refa,refb,refc,refe,refg': '1-3,5,7'}.
"""
for cite in self._citation:
cite_nums = [self._bibcite[c] for c in cite.split(',')]
self._conversion_dict.update(
{cite: self._compress(cite_nums)}
)
for cite in self._citation_keys_in_context:
cite_nums = [self._bibcite[c] for c in cite.split(',')]
if self._use_cite_package:
self._conversion_dict.update(
{cite: self._compress(sorted(cite_nums))}
)
else:
self._conversion_dict.update(
{cite: ','.join(str(c) for c in cite_nums)}
)
def cite(self, s):
r"""Do \cite command formatting.
Returns formated text from citation commands such as
\cite{key1} and \cite{key1,key2,key3}, etc.
By default, if there are three or more consecutive numbers,
they are compressed into a range using an en-dash.
Citation numbers are also sorted in the default condition.
Parameters
----------
s : str
Raw string to be formatted.
For example, \\cite{key1} or \\cite{key2,key3}.
Examples
--------
>>> import wdbibtex
>>> tx = wdbibtex.LaTeX()
>>> tx.citation_labels = {'key1': 1, 'key2': 2, 'key3': 3}
>>> tx.cite('\\cite{key1}')
'[1]'
>>> tx.cite('\\cite{key2,key3}')
'[2,3]'
>>> tx.cite('\\cite{key3,key2,key1}')
'[3,2,1]'
>>> import wdbibtex
>>> tx = wdbibtex.LaTeX()
>>> tx.add_package('cite')
>>> tx.citation_labels = {'key1': 1, 'key2': 2, 'key3': 3}
>>> tx.cite('\\cite{key1}')
'[1]'
>>> tx.cite('\\cite{key2,key3}')
'[2,3]'
>>> tx.cite('\\cite{key3,key2,key1}')
'[1\u20133]'
Note \\u2013 is en-dash.
"""
p = re.compile(r'\\+cite\{(.*)\}')
if p.match(s):
keys = p.match(s).group(1).split(',')
if len(keys) == 1:
key = keys[0]
return (
self._citeleft
+ str(self._citation_labels[key])
+ self._citeright
)
if len(keys) > 1:
if self._use_cite_package:
nums = sorted(
[self._citation_labels[key] for key in keys]
)
return (
self._citeleft
+ self._compress(nums)
+ self._citeright
)
else:
nums = [str(self._citation_labels[key]) for key in keys]
return (
self._citeleft
+ ','.join(nums)
+ self._citeright
)
else:
ValueError(
'no citation pattern matched.'
)
def _compress(self, nums, sep=u'\u2013'):
r"""Compress groups of three or more consecutive numbers into a range.
Compress poor list of positive integers with three or more
consecutive numbers into a range using a separating character.
For example, a list ``[1,2,3,6]`` will be converted into ``[1-3,6]``.
Parameters
----------
nums : list of positive integers
Multiple integers to convert dashed range string.
A list of single element integer is also allowd.
sep : str, default en-dash(U+2013)
A character inserted betwen start and end of range.
"""
seq = []
final = []
last = 0
for index, val in enumerate(nums):
if last + 1 == val or index == 0:
seq.append(val)
last = val
else:
if len(seq) > 2:
final.append(str(seq[0]) + sep + str(seq[len(seq)-1]))
elif len(seq) == 2:
final.append(str(seq[0]) + ',' + str(seq[len(seq)-1]))
else:
final.append(str(seq[0]))
seq = []
seq.append(val)
last = val
if index == len(nums) - 1:
if len(seq) > 2:
final.append(str(seq[0]) + sep + str(seq[len(seq)-1]))
elif len(seq) == 2:
final.append(str(seq[0]) + ',' + str(seq[len(seq)-1]))
else:
final.append(str(seq[0]))
final_str = ','.join(map(str, final))
return final_str
class Bibliography:
"""LaTeX bbl file related contents and commands.
Parameters
----------
targetbasename : str, default 'wdbib'
Base name of LaTeX related files.
workdir : str or path object, default '.tmp'
Temporal working directory to store LaTeX contents.
Examples
--------
>>> import wdbibtex
>>> bb = wdbibtex.Bibliography()
>>> bb.read_bbl() # doctest: +SKIP
"""
def __init__(
self,
targetbasename='wdbib',
workdir='.tmp',
):
"""Cunstructor of Bibliography
"""
# Store settings in internal attributes.
if os.path.isabs(workdir):
self.workdir = pathlib.Path(workdir)
else:
self.workdir = (
pathlib.Path(os.getcwd()) / workdir
).resolve()
self._targetbasename = targetbasename
@property
def thebibliography(self):
r"""Plain text to replace \\thebibliography in word file.
A plain text of LaTeX-processed bibliography list.
An tab string is inserted between each citenum and citation string.
Example in IEEE format follows:
.. code-block:: text
[1]\\tF. Author, S. Author, "Paper Title," Journal Name, vol. 1, no. 1, p. 1, march 2022.
[2]\\tG. Name, F. Name, "Title," Journal, vol. 2, no. 2, pp. 1-10, 2020.
Returns
-------
str
Plain text of the thebibliography.
Raises
------
ValueError
If thebibliography text is not set.
""" # noqa E501
if self._thebibtext is None:
raise ValueError(
'Thebibliography text is not set yet.'
)
return self._thebibtext
def read_bbl(self):
"""Read .bbl file.
Read .bbl file to extract formatted thebibliography text.
Examples
--------
>>> import wdbibtex
>>> bb = wdbibtex.Bibliography()
>>> bb.read_bbl() # doctest: +SKIP
"""
fn = self.workdir / (self._targetbasename + '.bbl')
with codecs.open(fn, 'r', 'utf-8') as f:
self._bbldata = f.readlines()
self._make_thebibliography_text()
def _make_thebibliography_text(self):
"""Generate thebibliography plain text to incert word file.
"""
replacer = {}
replacer.update({
r'\n ': ' ',
r'\{\\em (.*?)\}': r'\1',
r'\\emph\{((?>[^\{\}]+|(?R))*)\}': r'\1',
r'\\BIBforeignlanguage\{(.*?)\}\{(.*?)\}': r'\2',
r'\\BIBforeignlanguage\{(.*?)\{(.*?)\}\}': r'\2',
r'~': ' ',
r'--': u'\u2013',
r'``': '“',
r"''": '”',
r'\n\n': '\n',
r'\\BIBentryALTinterwordspacing\n': '',
r'\\BIBentrySTDinterwordspacing\n': '',
r'\\url\{(.*?)\}': r'\1',
r'\{\\"\{A\}\}': 'Ä',
r'\{\\"\{a\}\}': 'ä',
r'\{\\"\{E\}\}': 'Ë',
r'\{\\"\{e\}\}': 'ë',
r'\{\\"\{I\}\}': 'Ï',
r'\{\\"\{i\}\}': 'ï',
r'\{\\"\{O\}\}': 'Ö',
r'\{\\"\{o\}\}': 'ö',
r'\{\\"\{U\}\}': 'Ü',
r'\{\\"\{u\}\}': 'ü',
r'\{\\"\{Y\}\}': 'Ÿ',
r'\{\\"\{y\}\}': 'ÿ',
r"\{\\'\{E\}\}": 'É',
r"\{\\'\{e\}\}": 'é',
r"\{\\'\{O\}\}": 'Ó',
r"\{\\'\{o\}\}": 'ó',
r'{\\AA}': 'Å',
r'{\\aa}': 'å',
r' +': ' ',
r'\\hskip [+-]?(?:\d*\.)?\d+(?:(?<!(\.\d+))\.\d*)?em ' +
r'plus [+-]?(?:\d*\.)?\d+(?:(?<!(\.\d+))\.\d*)?em ' +
r'minus [+-]?(?:\d*\.)?\d+(?:(?<!(\.\d+))\.\d*)?em\\relax': ' ',
})
thebib_begin = None
for i, line in enumerate(self._bbldata):
if line.startswith('\\bibitem') and thebib_begin is None:
thebib_begin = i
if line.startswith('\\end{thebibliography}'):
thebib_end = i
thebibtext = ''.join(self._bbldata[thebib_begin: thebib_end])
# Replace thebibliography text
found = True
while found:
found = False
for k, v in replacer.items():
thebibold = thebibtext
import regex
thebibtext = regex.sub(k, v, thebibtext)
if thebibold != thebibtext:
found = True
# Bracket removal
found = True
while found:
found = False
thebibold = thebibtext
import regex
thebibtext = regex.sub(
r'(?<!bibitem)\{((?>[^\{\}]+|(?R))*)\}',
r'\1',
thebibtext
)
if thebibold != thebibtext:
found = True
for c, m in enumerate(re.findall('\\\\bibitem{(.*)}\n', thebibtext)):
thebibtext = re.sub(
'\\\\bibitem{%s}\n' % m, '[%s]\t' % (c+1), thebibtext
)
self._thebibtext = thebibtext
[docs]class LaTeX(Cite, Bibliography):
"""LaTeX related contents and commands.
Run LaTeX and BibTeX commands. Write .tex files.
Read and parse .aux and .bbl files.
Prepare conversion LaTeX keys in Word file into BibTeX processed texts.
Parameters
----------
bibtexcmd : str or None, default None
BibTeX command.
If None, automatically selected accorting to system locale.
bibtexopts : str or None, default None
BibTeX command options.
If None, automatically selected according to system locale.
preamble : str or None, default None
Preamble of .tex file.
If None, automatically selected.
targetbasename : str, default 'wdbib'
Base name of LaTeX related files.
texcmd : str or None, default None
LaTeX command.
If None, automatically selected according to system locale.
texopts : str or None, default None
LaTeX command options.
If None, automatically selected accorgin to system locale.
workdir : str or path object, default '.tmp'
Temporal working directory to store LaTeX contents.
"""
def __init__(
self,
bibtexcmd=None,
bibtexopts=None,
preamble=None,
targetbasename='wdbib',
texcmd=None,
texopts=None,
workdir='.tmp',
):
super(LaTeX, self).__init__()
self.__locale = self.__default_locale()
# Set automatically selected values
if texcmd is None:
if self.__locale == 'en':
texcmd = 'latex'
elif self.__locale == 'ja':
texcmd = 'uplatex'
if texopts is None:
texopts = '-interaction=nonstopmode -file-line-error'
if bibtexcmd is None:
if self.__locale == 'en':
bibtexcmd = 'bibtex'
elif self.__locale == 'ja':
bibtexcmd = 'upbibtex'
if bibtexopts is None:
bibtexopts = ''
# Store settings in internal attributes.
if os.path.isabs(workdir):
self.workdir = pathlib.Path(workdir)
else:
self.workdir = (
pathlib.Path(os.getcwd()) / workdir
).resolve()
self.__targetbasename = targetbasename
self.__texcmd = texcmd
self.__texopts = texopts
self.__bibtexcmd = bibtexcmd
self.__bibtexopts = bibtexopts
self.__packages = None
self.__bibliographystyle = None
self.__formatted_bibliographystyle = None
self.__documentclass = None
self.__package_list = []
self.preamble = preamble
# Makedir working directory if not exist.
self.workdir.mkdir(exist_ok=True)
@property
def documentclass(self):
"""LaTeX documentclass string."""
return self.__documentclass
@documentclass.setter
def documentclass(self, documentclass):
if not documentclass.startswith('\\'):
raise ValueError(
'Invalid documentclass.'
)
self.__documentclass = documentclass
# Update preamble
self.__update_preamble()
[docs] def set_documentclass(self, documentclass, *options):
"""Documentclass setter.
Parameters
----------
documentclass
Documentclass
*options
Documentclass options.
"""
if documentclass.startswith('\\'):
self.__documentclass = documentclass
else:
if bool(options):
opts = '[%s]' % ','.join(options)
self.__documentclass = \
'\\documentclass%s{%s}' % (opts, documentclass)
# Update preamble
self.__update_preamble()
@property
def formatted_bibliographystyle(self):
r"""[Read only] Formatted bibliographystyle, e.g. \bibliographystyle{IEEEtran}
Formatted bibliography string to be written in preamble.
In the case ``bibliographystyle`` is ``SomeBST``,
``formatted_bibliographystyle`` is ``\bibliographystyle{SomeBST}``.
See Also
--------
bibliographystyle : bare bibliographystyle to be used
"""
return self.__formatted_bibliographystyle
@property
def bibliographystyle(self):
r"""Bibliographystyle string.
Bibliography string. If None is set, a .bst is automatically selected.
The ``bibliography`` string is, for example,
``SomeBST`` of ``\bibliographystyle{SomeBST}``.
While the ``formatted_bibliographystyle``
is ``\bibliographystyle{SomeBST}``.
See Also
--------
formatted_bibliographystyle : formatted line to be written in preamble
Examples
--------
>>> import wdbibtex
>>> tx = wdbibtex.LaTeX()
>>> tx.bibliographystyle = 'IEEEtran'
>>> tx.bibliographystyle
'IEEEtran'
>>> tx.formatted_bibliographystyle
'\\bibliographystyle{IEEEtran}'
In the case of None and no .bst file is found, raise ValueError.
>>> import wdbibtex
>>> tx = wdbibtex.LaTeX()
>>> tx.bibliographystyle = None
Traceback (most recent call last):
...
ValueError: No .bst files found in working directory.
In the case of None and some .bst file is in the working directory,
the .bst file is automatically selected.
>>> import wdbibtex
>>> import pathlib
>>> import shutil
>>> shutil.rmtree('.tmp', ignore_errors=True)
>>> tx = wdbibtex.LaTeX(workdir='.tmp')
>>> pathlib.Path('.tmp/testbst.bst').touch()
>>> tx.bibliographystyle = None
>>> tx.bibliographystyle
'testbst'
>>> tx.formatted_bibliographystyle
'\\bibliographystyle{testbst}'
Raises
------
ValueError
If bst is None and there is no or multiple .bst files in cwd.
"""
return self.__bibliographystyle
@bibliographystyle.setter
def bibliographystyle(self, bibliographystyle):
import glob
if bibliographystyle:
self.set_bibliographystyle(bibliographystyle)
else:
bibliographystyle = glob.glob(str(self.workdir) + '/*.bst')
if len(bibliographystyle) > 1:
raise ValueError(
'More than two .bst files found in working directory.'
)
elif len(bibliographystyle) == 0:
raise ValueError(
'No .bst files found in working directory.'
)
else:
bstfile = os.path.basename(bibliographystyle[0])
bibliographystyle = os.path.splitext(bstfile)[0]
self.set_bibliographystyle(bibliographystyle)
[docs] def set_bibliographystyle(self, bst):
"""Bibliographystyle setter.
Parameters
----------
bst : str
Bibliography style such as IEEEtran or ieeetr.
"""
if re.search(r'[^a-zA-Z]', bst):
raise ValueError(
'Invalid bibliographystyle. Only plain alphabets are allowed.'
)
else:
self.__bibliographystyle = bst
self.__formatted_bibliographystyle = \
'\\bibliographystyle{%s}' % bst
# Update preamble
self.__update_preamble()
@property
def packages(self):
r"""Returns used LaTeX packages.
Returns
-------
str
Multi-line LaTeX \\usepackage[options]{package} string.
Examples
--------
>>> import wdbibtex
>>> tx = wdbibtex.LaTeX()
>>> tx.add_package('cite')
>>> print(tx.packages)
\usepackage{cite}
>>> tx.add_package('graphicx', 'dvipdfmx')
>>> print(tx.packages)
\usepackage{cite}
\usepackage[dvipdfmx]{graphicx}
"""
return self.__packages
def __update_packages(self):
pkgs = []
is_cite_package_found = False
for pkg, *opts in self.__package_list:
if bool(opts):
pkgs.append('\\usepackage[%s]{%s}' % (','.join(opts), pkg))
else:
pkgs.append('\\usepackage{%s}' % pkg)
if pkg == 'cite':
is_cite_package_found = True
self.__packages = '\n'.join(pkgs)
self._use_cite_package = is_cite_package_found
[docs] def add_package(self, package, *options):
"""Add a package to the package list
Add a package to the package list of package_list.
The package can have option.
The package will used in the preamble attribute.
Parameters
----------
package : str
Package name.
*options
Options of the package.
"""
# Overwrite duplicated package
for i, (p, *o) in enumerate(self.__package_list):
if p == package:
self.__package_list.pop(i)
break
self.__package_list.append(
[package, *options]
)
# Update package string.
self.__update_packages()
# Update preamble
self.__update_preamble()
[docs] def is_package_used(self, p):
r"""Returns if the package is used.
Returns False if the package is not used
while True if the package is used without option.
If the package is used with option(s), returns List of option(s).
Parameters
----------
p : str
Package name to find.
Returns
-------
bool or list
False if the package is not used.
True if the package is used without option.
List of option(s) if the package is used with option(s).
Examples
--------
>>> import wdbibtex
>>> tx = wdbibtex.LaTeX()
>>> tx.add_package('cite')
>>> tx.is_package_used('cite')
True
>>> tx.add_package('graphicx', 'dvipdfmx')
>>> tx.is_package_used('graphicx')
['dvipdfmx']
>>> tx.is_package_used('xcolor')
False
>>> print(tx.packages)
\usepackage{cite}
\usepackage[dvipdfmx]{graphicx}
"""
for package in self.__package_list:
if package[0] == p:
if len(package) == 1:
return True
else:
return package[1:]
else:
return False
[docs] def write(self, c, bib=None):
r"""Write .tex file.
Write minimal .tex file into workdir.
TeX file contains only citation contents,
pre-defined (at constructor of LaTeX object) preamble,
\\bibliography, and \\bibliographystyle.
Parameters
----------
c : str
String data to be written in .tex file.
bib : str or None, default None
Bibliography library file(s). If None, use all .bib files in cwd.
"""
import glob
if bib is None:
# Use only root name (file name without extension).
bib = ','.join(
[os.path.splitext(b)[0] for b in glob.glob('*.bib')]
)
fn = self.workdir / (self.__targetbasename + '.tex')
with codecs.open(fn, 'w', 'utf-8') as f:
f.writelines(
'\n'.join([
self.preamble,
'\\begin{document}',
c,
'\\bibliography{%s}' % bib,
'\\end{document}',
'',
])
)
self._parse_context(c)
[docs] def build(self):
"""Build LaTeX related files.
Build LaTeX files in old-style four steps (without PDF generation).
1. latex: to generate .aux from .tex
2. bibtex: to generate .bbl and update .aux from .aux and .bst.
3. latex: to update .aux.
4. latex: to complete .aux.
Firstly the current directory is switched to the working directory.
Secondly the above four steps are invoked.
Thirdly read .bbl and .aux files are parsed.
Finally, the current directory is switched
to the original working directory.
"""
import subprocess
cwd = os.getcwd() # Save original working directory.
os.chdir(self.workdir)
latexcmd = ' '.join(filter(None, [
self.__texcmd,
self.__texopts,
self.__targetbasename + '.tex'
]))
bibtexcmd = ' '.join(filter(None, [
self.__bibtexcmd,
self.__bibtexopts,
self.__targetbasename,
]))
# Four steps to complete build LaTeX project.
subprocess.call(latexcmd, shell=True)
subprocess.call(bibtexcmd, shell=True)
subprocess.call(latexcmd, shell=True)
subprocess.call(latexcmd, shell=True)
os.chdir(cwd) # Back to original working directory.
@property
def preamble(self):
r"""Returns latex preamble text.
A text to be used as LaTeX preamble. Note that not all latex-compatible
preamble is used in WdBibTeX package. LaTeX class accepts None
for preamble attribute. In this case, the following default preamble
text is used according to system locale.
Note BST is replaced a bibliography style file
placed in the project directory.
.. code-block:: text
\documentclass[latex]{article}
\bibliographystyle{BST}
.. code-block:: text
\documentclass[uplatex]{jsarticle}
\bibliographystyle{BST}
Returns
-------
str
Preamble text.
"""
return self.__preamble
@preamble.setter
def preamble(self, s):
if s is None:
if self.__locale == 'en':
self.set_documentclass('article')
elif self.__locale == 'ja':
self.set_documentclass('jsarticle', 'uplatex')
elif isinstance(s, str):
self.__parse_preamble(s)
else:
raise ValueError(
'Invalid preamble. '
'Only None or str is allowed.'
)
def __update_preamble(self):
contents = [
self.documentclass,
self.packages,
self.formatted_bibliographystyle,
]
self.__preamble = '\n'.join(
[c for c in contents if c is not None]
)
def __parse_preamble(self, preamble):
detect_documentclass = False
for ln in preamble.split('\n'):
if ln.startswith('%') and not detect_documentclass:
pass
elif re.match(r'.*documentclass.*', ln):
detect_documentclass = True
m = re.match(r'.*documentclass(\[(.*)\])*\{(.*)\}', ln)
documentclass_opt = []
if m.group(1) is not None:
documentclass_opt = m.group(2).replace(' ', '').split(',')
documentclsass = m.group(3)
self.set_documentclass(documentclsass, *documentclass_opt)
elif re.match(r'.*usepackage.*', ln):
m = re.match(r'.*usepackage(\[(.*)\])*\{(.*)\}', ln)
package_opt = []
if m.group(1) is not None:
package_opt = m.group(2).replace(' ', '').split(',')
package = m.group(3)
self.add_package(package, *package_opt)
elif re.match(r'.*bibliographystyle.*', ln):
m = re.match(r'.*bibliographystyle\{(.*)\}', ln)
bibliographystyle = m.group(1)
self.set_bibliographystyle(bibliographystyle)
elif re.match(r'.*renewcommand\\citeleft.*', ln):
m = re.match(r'.*renewcommand\\citeleft\{(.*)\}', ln)
self.citeleft = m.group(1)
elif re.match(r'.*renewcommand\\citeright.*', ln):
m = re.match(r'.*renewcommand\\citeright\{(.*)\}', ln)
self.citeright = m.group(1)
else:
pass
@property
def locale(self):
"""Returns system locale
Locale string to decide which latex commands used.
Currently english(en) and japanese(ja) are supported.
If locale is manually set, returns the local as is.
Else, determined using locale.getlocale().
Returns
-------
str
Locale text in two characters for example 'en' or 'ja'.
"""
return self.__locale
@locale.setter
def locale(self, s):
if isinstance(s, str) and len(s) == 2:
self.__locale = s
else:
raise ValueError(
'Invalid locale string. '
'Only 2-characters string is allowed.'
)
def __default_locale(self):
loca, locb = locale.getlocale()
if 'en' in loca or 'en' in locb:
return 'en'
elif 'English' in loca or 'English' in locb:
return 'en'
elif 'ja' in loca or 'ja' in locb:
return 'ja'
elif 'Japanese' in loca or 'Japanese' in locb:
return 'ja'
else:
raise ValueError('Unhandled locale %s' % locale.getlocale())