diff --git a/longtable-to-xtab/LICENSE b/longtable-to-xtab/LICENSE new file mode 100644 index 00000000..b4e8923c --- /dev/null +++ b/longtable-to-xtab/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Julien Dutant + +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. diff --git a/longtable-to-xtab/Makefile b/longtable-to-xtab/Makefile new file mode 100644 index 00000000..10a2d22b --- /dev/null +++ b/longtable-to-xtab/Makefile @@ -0,0 +1,12 @@ +DIFF ?= diff --strip-trailing-cr -u + +.PHONY: test + +test: test_latex + +test_latex: sample.md expected.tex longtable-to-xtab.lua + @pandoc --lua-filter longtable-to-xtab.lua --to=latex $< \ + | $(DIFF) expected.tex - + +expected.tex: sample.md longtable-to-xtab.lua + pandoc --lua-filter longtable-to-xtab.lua --output $@ $< diff --git a/longtable-to-xtab/README.md b/longtable-to-xtab/README.md new file mode 100644 index 00000000..e340f26a --- /dev/null +++ b/longtable-to-xtab/README.md @@ -0,0 +1,136 @@ +--- +title: "Longtable-to-xtab - switch LaTeX table outputs from longtable to xtab" +author: "Julien Dutant" +--- + +Longtable-to-xtab +======= + +Convert Pandoc's LaTeX table output from `longtable` to `xtab`. + +v1.0. Copyright: © 2021 Julien Dutant +License: MIT - see LICENSE file for details. + +Introduction +------------ + +By default Pandoc outputs uses the LaTeX package `longtable` to format +tables in LaTeX. However `longtable` environments cannot be used in a two column document or in a multiple columns environements (`multicol`). +In those contexts one should use the `supertabular` or `xtab` packages - +preferably `xtab`, which is based on `supertabular` and improves it. + +This filter converts the LaTeX output of Pandoc for tables from +`longtable` to `xtab` codes. It does so by implementing a [suggestion of +Bustel](https://github.com/jgm/pandoc/issues/1023#issuecomment-656769330): +redefine the longtable environment in LaTeX itself. + +Usage +----- + +### Installation + +Copy `longtable-to-xtab.lua` in your document folder or in your pandoc data +dir path. + +### Usage + +Add `-L longtable-to-xtab.lua` to your Pandoc command line, or add +`filter: longtable-to-xtab.lua` to a document's metadata block. + +Details +---- + +### Dependencies + +Unlike `longtable`, the `xtab` and `supertabular` LaTeX packages are not +included in the core list of LaTeX packages. In Linux Debian distributions +they are included in the package `texlive-latex-extra`. + +### The duplicate headers issue and how the filter solves it + +If a table has both headers and a caption, Pandoc generates a first header +(caption and headers) and main header (headers only). See the code below for an illustration. If we simply turned the table into an `xtabular` and erase the `\endfirsthead` and `\endhead` commands we would get two header rows. + +```latex +\begin{longtable}[]{@{} + >{\centering\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.17}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.11}} + >{\raggedleft\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.22}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.36}}@{}} +\caption{Here's the \emph{caption}. It, too, may span multiple +lines.}\tabularnewline +\toprule +Centered Header & Default Aligned & Right Aligned & Left +Aligned\footnote{Footnote in a table.} \\ \addlinespace +\midrule +\endfirsthead +\toprule +Centered Header & Default Aligned & Right Aligned & Left +Aligned{} \\ \addlinespace +\midrule +\endhead +First & row & 12.0 & Example of a row that spans multiple +lines. \\ \addlinespace +Second & row & 5.0 & Here's another one. Note the blank line between +rows. \\ \addlinespace +\bottomrule +\end{longtable} + +``` + +Possible solutions: + +1. Use `\iffalse ...\endif` to turn off LaTeX on the main header. **This is + the solution adopted here.** It's a bit of a hack. + + Limitation: from `xtab`'s point of view the remaining first header row is a normal row. Not ideal if the table does span several pages. + +2. strip the markdown table of its headers, and add the headers as + `\tablefirstheader` (includes footnote) and `\tableheader` (*footnotes stripped and other unique identifiers need to be stripped from the + repeat header*). See below Pandoc code without headers (the + `\toprule` should be redefined to nothing). + + ```latex + \begin{longtable}[]{@{} + >{\centering\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.17}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.11}} + >{\raggedleft\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.22}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.36}}@{}} + \toprule + \endhead + First & row & 12.0 & Example of a row that spans multiple + lines. \\ \addlinespace + Second & row & 5.0 & Here's another one. Note the blank line between + rows. \\ \addlinespace + \bottomrule + ``` + + and see below the ideal `xtab` code for the first and main headers: + + ```latex + \tablecaption{Here's the \emph{caption}. It, too, may span multiple + lines.} + \tablefirsthead{\toprule + Centered Header & Default Aligned & Right Aligned & Left + Aligned\footnote{Footnote in a table.} \\ \midrule} + \tablehead{\toprule Centered Header & Default Aligned & Right Aligned & Left Aligned \\ \midrule} + \begin{center} + \begin{xtabular}[]{@{} + >{\centering\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.17}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.11}} + >{\raggedleft\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.22}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.36}}@{}} + First & row & 12.0 & Example of a row that spans multiple + lines. \\ \addlinespace + Second & row & 5.0 & Here's another one. Note the blank line between + rows. \\ \addlinespace + \bottomrule + \end{xtabular} + \end{center} + ``` + +Contributing +------------ + +PRs welcome. + diff --git a/longtable-to-xtab/expected.tex b/longtable-to-xtab/expected.tex new file mode 100644 index 00000000..6ea45c78 --- /dev/null +++ b/longtable-to-xtab/expected.tex @@ -0,0 +1,82 @@ +This is a two column layout with tables. Without the +\texttt{longtable-to-xtab} filter trying to convert this document to PDF +fails because Pandoc's favoured table package, \texttt{longtable}, isn't +compatible with multiple column layouts. + +\hypertarget{table-with-headers-and-caption}{% +\section{Table with headers and +caption}\label{table-with-headers-and-caption}} + +{ + {\renewcommand{\endfirsthead}{\iffalse} + \let\endhead\fi + + +\tablecaption{Here's the \emph{caption}. It, too, may span multiple +lines.} + +\begin{longtable}[]{@{} + >{\centering\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.17}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.11}} + >{\raggedleft\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.22}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.36}}@{}} +\caption{Here's the \emph{caption}. It, too, may span multiple +lines.}\tabularnewline +\toprule +Centered Header & Default Aligned & Right Aligned & Left +Aligned\footnote{Footnote in a table.} \\ \addlinespace +\midrule +\endfirsthead +\toprule +Centered Header & Default Aligned & Right Aligned & Left +Aligned{} \\ \addlinespace +\midrule +\endhead +First & row & 12.0 & Example of a row that spans multiple +lines. \\ \addlinespace +Second & row & 5.0 & Here's another one. Note the blank line between +rows. \\ \addlinespace +\bottomrule +\end{longtable} + +} + +\hypertarget{table-without-headers}{% +\section{Table without headers}\label{table-without-headers}} + +\tablecaption{this table doesn't have headers.} + +\begin{longtable}[]{@{} + >{\centering\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.17}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.11}} + >{\raggedleft\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.22}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.36}}@{}} +\caption{this table doesn't have headers.}\tabularnewline +\toprule +\endhead +First & row & 12.0 & Example of a row that spans multiple +lines. \\ \addlinespace +Second & row & 5.0 & Here's another one. Note the blank line between +rows. \\ \addlinespace +\bottomrule +\end{longtable} + +\hypertarget{table-without-caption}{% +\section{Table without caption}\label{table-without-caption}} + +\begin{longtable}[]{@{} + >{\centering\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.17}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.11}} + >{\raggedleft\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.22}} + >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.36}}@{}} +\toprule +Centered Header & Default Aligned & Right Aligned & Left +Aligned \\ \addlinespace +\midrule +\endhead +First & row & 12.0 & Example of a row that spans multiple +lines. \\ \addlinespace +Second & row & 5.0 & Here's another one. Note the blank line between +rows. \\ \addlinespace +\bottomrule +\end{longtable} diff --git a/longtable-to-xtab/longtable-to-xtab.lua b/longtable-to-xtab/longtable-to-xtab.lua new file mode 100644 index 00000000..c70a34a5 --- /dev/null +++ b/longtable-to-xtab/longtable-to-xtab.lua @@ -0,0 +1,105 @@ +--- # Longtable-to-xtab - switch LaTeX table outputs from longtable to xtab. +-- +-- This Lua filter for Pandoc converts Pandoc's default `longtable` output +-- of tables in LaTeX with `xtab` tables. +-- +-- @author Julien Dutant +-- @copyright (c) 2021 Julien Dutant +-- @license MIT - see LICENSE file for details. +-- @release 1.0 + +--- Add a block to the document's header-includes meta-data field. +-- @param meta the document's metadata block +-- @param block Pandoc block element (e.g. RawBlock or Para) to be added to header-includes +-- @return meta the modified metadata block +local function add_header_includes(meta, block) + + local header_includes + + -- make meta['header-includes'] a list if needed + if meta['header-includes'] and meta['header-includes'].t == 'MetaList' then + header_includes = meta['header-includes'] + else + header_includes = pandoc.MetaList{meta['header-includes']} + end + + -- insert `block` in header-includes and add it to `meta` + + header_includes[#header_includes + 1] = + pandoc.MetaBlocks{block} + + meta['header-includes'] = header_includes + + return meta +end + +--- Main filter + +local filter = { + + Meta = function (element) + + local latex_code = [[ + \usepackage{xtab} + \renewenvironment{longtable}{% + \begin{center}\begin{xtabular}% + }{% + \end{xtabular}\end{center}% + } + \renewcommand{\caption}[1]{} + \renewcommand{\endhead}{} + ]] + + add_header_includes(element, pandoc.RawBlock('latex', latex_code)) + + return element + + end, + + Table = function (element) + + if element.caption and #element.caption['long'] > 0 then + + local result = pandoc.List:new({}) + local inlines = pandoc.List:new({}) + + inlines:insert(pandoc.RawInline('latex', '\\tablecaption{')) + inlines:extend(pandoc.utils.blocks_to_inlines(element.caption['long'])) + inlines:insert(pandoc.RawInline('latex', '}')) + + result:insert(pandoc.Plain(inlines)) + + -- place the table + result:insert(element) + + -- if the table has both header and caption we need to hide the + -- duplicate header. We turn `\endfirsthead` into `\iffalse` + -- and `\endhead` into `\fi`. The latter must be present + -- when `\iffalse` is encountered, so we use `\let` for the + -- latter and `\renewcommand` for the former. + -- we wrap the whole in `{...}` to avoid affecting `\endhead` + -- down the line. + if #element.head[2] > 0 then + + latex_pre = [[{ + {\renewcommand{\endfirsthead}{\iffalse} + \let\endhead\fi + ]] + latex_post = '}' + + result:insert(1, pandoc.RawBlock('latex', latex_pre)) + result:insert(pandoc.RawBlock('latex', latex_post)) + + end + + return result + + end + + end +} + +-- return filter if targetting LaTeX +if FORMAT:match('latex') then + return {filter} +end diff --git a/longtable-to-xtab/sample.md b/longtable-to-xtab/sample.md new file mode 100644 index 00000000..99c628a6 --- /dev/null +++ b/longtable-to-xtab/sample.md @@ -0,0 +1,56 @@ +--- +classoption: + - twocolumn + - landscape +--- + +This is a two column layout with tables. Without the `longtable-to-xtab` +filter trying to convert this document to PDF fails because Pandoc's +favoured table package, `longtable`, isn't compatible with multiple +column layouts. + +# Table with headers and caption + +------------------------------------------------------------- + Centered Default Right Left + Header Aligned Aligned Aligned[^1] +----------- ------- --------------- ------------------------- + First row 12.0 Example of a row that + spans multiple lines. + + Second row 5.0 Here's another one. Note + the blank line between + rows. +------------------------------------------------------------- + +Table: Here's the *caption*. It, too, may span + multiple lines. + +[^1]: Footnote in a table. + +# Table without headers + +----------- ------- --------------- ------------------------- + First row 12.0 Example of a row that + spans multiple lines. + + Second row 5.0 Here's another one. Note + the blank line between + rows. +----------- ------ ---------------- ---------------------------- + +Table: this table doesn't have headers. + +# Table without caption + +------------------------------------------------------------- + Centered Default Right Left + Header Aligned Aligned Aligned +----------- ------- --------------- ------------------------- + First row 12.0 Example of a row that + spans multiple lines. + + Second row 5.0 Here's another one. Note + the blank line between + rows. +-------------------------------------------------------------