mirror of
https://github.com/kennethreitz/tablib.git
synced 2026-06-05 23:10:17 +00:00
135 lines
3.6 KiB
Python
135 lines
3.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""Tablib - LaTeX table export support.
|
|
|
|
Generates a LaTeX booktabs-style table from the dataset.
|
|
"""
|
|
import re
|
|
|
|
from tablib.compat import unicode
|
|
|
|
title = 'latex'
|
|
extensions = ('tex',)
|
|
|
|
TABLE_TEMPLATE = """\
|
|
%% Note: add \\usepackage{booktabs} to your preamble
|
|
%%
|
|
\\begin{table}[!htbp]
|
|
\\centering
|
|
%(CAPTION)s
|
|
\\begin{tabular}{%(COLSPEC)s}
|
|
\\toprule
|
|
%(HEADER)s
|
|
%(MIDRULE)s
|
|
%(BODY)s
|
|
\\bottomrule
|
|
\\end{tabular}
|
|
\\end{table}
|
|
"""
|
|
|
|
TEX_RESERVED_SYMBOLS_MAP = dict([
|
|
('\\', '\\textbackslash{}'),
|
|
('{', '\\{'),
|
|
('}', '\\}'),
|
|
('$', '\\$'),
|
|
('&', '\\&'),
|
|
('#', '\\#'),
|
|
('^', '\\textasciicircum{}'),
|
|
('_', '\\_'),
|
|
('~', '\\textasciitilde{}'),
|
|
('%', '\\%'),
|
|
])
|
|
|
|
TEX_RESERVED_SYMBOLS_RE = re.compile(
|
|
'(%s)' % '|'.join(map(re.escape, TEX_RESERVED_SYMBOLS_MAP.keys())))
|
|
|
|
|
|
def export_set(dataset):
|
|
"""Returns LaTeX representation of dataset
|
|
|
|
:param dataset: dataset to serialize
|
|
:type dataset: tablib.core.Dataset
|
|
"""
|
|
|
|
caption = '\\caption{%s}' % dataset.title if dataset.title else '%'
|
|
colspec = _colspec(dataset.width)
|
|
header = _serialize_row(dataset.headers) if dataset.headers else ''
|
|
midrule = _midrule(dataset.width)
|
|
body = '\n'.join([_serialize_row(row) for row in dataset])
|
|
return TABLE_TEMPLATE % dict(CAPTION=caption, COLSPEC=colspec,
|
|
HEADER=header, MIDRULE=midrule, BODY=body)
|
|
|
|
|
|
def _colspec(dataset_width):
|
|
"""Generates the column specification for the LaTeX `tabular` environment
|
|
based on the dataset width.
|
|
|
|
The first column is justified to the left, all further columns are aligned
|
|
to the right.
|
|
|
|
.. note:: This is only a heuristic and most probably has to be fine-tuned
|
|
post export. Column alignment should depend on the data type, e.g., textual
|
|
content should usually be aligned to the left while numeric content almost
|
|
always should be aligned to the right.
|
|
|
|
:param dataset_width: width of the dataset
|
|
"""
|
|
|
|
spec = 'l'
|
|
for _ in range(1, dataset_width):
|
|
spec += 'r'
|
|
return spec
|
|
|
|
|
|
def _midrule(dataset_width):
|
|
"""Generates the table `midrule`, which may be composed of several
|
|
`cmidrules`.
|
|
|
|
:param dataset_width: width of the dataset to serialize
|
|
"""
|
|
|
|
if not dataset_width or dataset_width == 1:
|
|
return '\\midrule'
|
|
return ' '.join([_cmidrule(colindex, dataset_width) for colindex in
|
|
range(1, dataset_width + 1)])
|
|
|
|
|
|
def _cmidrule(colindex, dataset_width):
|
|
"""Generates the `cmidrule` for a single column with appropriate trimming
|
|
based on the column position.
|
|
|
|
:param colindex: Column index
|
|
:param dataset_width: width of the dataset
|
|
"""
|
|
|
|
rule = '\\cmidrule(%s){%d-%d}'
|
|
if colindex == 1:
|
|
# Rule of first column is trimmed on the right
|
|
return rule % ('r', colindex, colindex)
|
|
if colindex == dataset_width:
|
|
# Rule of last column is trimmed on the left
|
|
return rule % ('l', colindex, colindex)
|
|
# Inner columns are trimmed on the left and right
|
|
return rule % ('lr', colindex, colindex)
|
|
|
|
|
|
def _serialize_row(row):
|
|
"""Returns string representation of a single row.
|
|
|
|
:param row: single dataset row
|
|
"""
|
|
|
|
new_row = [_escape_tex_reserved_symbols(unicode(item)) if item else '' for
|
|
item in row]
|
|
return 6 * ' ' + ' & '.join(new_row) + ' \\\\'
|
|
|
|
|
|
def _escape_tex_reserved_symbols(input):
|
|
"""Escapes all TeX reserved symbols ('_', '~', etc.) in a string.
|
|
|
|
:param input: String to escape
|
|
"""
|
|
def replace(match):
|
|
return TEX_RESERVED_SYMBOLS_MAP[match.group()]
|
|
return TEX_RESERVED_SYMBOLS_RE.sub(replace, input)
|