summaryrefslogtreecommitdiff
path: root/tablib
diff options
context:
space:
mode:
authorMathias Loesch <m.loesch83@gmail.com>2014-08-27 21:08:48 +0200
committerMathias Loesch <m.loesch83@gmail.com>2015-06-04 09:26:35 +0200
commit79dc4524a05e7a89b0e65e9d5b78fa6a56652848 (patch)
tree668b89398b84172a6a3cea91ef547c136b3e810c /tablib
parenta785d77901f307ef499621753b078743e2fabb81 (diff)
downloadtablib-79dc4524a05e7a89b0e65e9d5b78fa6a56652848.tar.gz
Added LaTeX table export format
Diffstat (limited to 'tablib')
-rw-r--r--tablib/core.py10
-rw-r--r--tablib/formats/__init__.py3
-rw-r--r--tablib/formats/_latex.py134
3 files changed, 146 insertions, 1 deletions
diff --git a/tablib/core.py b/tablib/core.py
index 7afdd71..314ea1d 100644
--- a/tablib/core.py
+++ b/tablib/core.py
@@ -584,6 +584,16 @@ class Dataset(object):
pass
+ @property
+ def latex():
+ """A LaTeX booktabs representation of the :class:`Dataset` object. If a
+ title has been set, it will be exported as the table caption.
+
+ .. note:: This method can be used for export only.
+ """
+ pass
+
+
# ----
# Rows
# ----
diff --git a/tablib/formats/__init__.py b/tablib/formats/__init__.py
index 1eda107..5cca19f 100644
--- a/tablib/formats/__init__.py
+++ b/tablib/formats/__init__.py
@@ -12,5 +12,6 @@ from . import _html as html
from . import _xlsx as xlsx
from . import _ods as ods
from . import _dbf as dbf
+from . import _latex as latex
-available = (json, xls, yaml, csv, dbf, tsv, html, xlsx, ods)
+available = (json, xls, yaml, csv, dbf, tsv, html, latex, xlsx, ods)
diff --git a/tablib/formats/_latex.py b/tablib/formats/_latex.py
new file mode 100644
index 0000000..44ee101
--- /dev/null
+++ b/tablib/formats/_latex.py
@@ -0,0 +1,134 @@
+# -*- 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)