From 8eb7d089ddada9da348b5f0816f2b0ff9eb0dd50 Mon Sep 17 00:00:00 2001 From: jdutant <34026710+jdutant@users.noreply.github.com> Date: Sun, 17 Jan 2021 00:35:10 +0000 Subject: [PATCH 1/7] initial commit --- longtable-to-xtab/LICENSE | 21 ++++ longtable-to-xtab/Makefile | 10 ++ longtable-to-xtab/README.md | 138 ++++++++++++++++++++++++ longtable-to-xtab/expected.tex | 77 +++++++++++++ longtable-to-xtab/longtable-to-xtab.lua | 108 +++++++++++++++++++ longtable-to-xtab/sample.md | 45 ++++++++ 6 files changed, 399 insertions(+) create mode 100644 longtable-to-xtab/LICENSE create mode 100644 longtable-to-xtab/Makefile create mode 100644 longtable-to-xtab/README.md create mode 100644 longtable-to-xtab/expected.tex create mode 100644 longtable-to-xtab/longtable-to-xtab.lua create mode 100644 longtable-to-xtab/sample.md 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..4a36a6a2 --- /dev/null +++ b/longtable-to-xtab/Makefile @@ -0,0 +1,10 @@ +DIFF ?= diff --strip-trailing-cr -u + +.PHONY: 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..d5630296 --- /dev/null +++ b/longtable-to-xtab/README.md @@ -0,0 +1,138 @@ +--- +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. + +Note +---- + +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`. + +Solving the duplicate headers issue +----- + +If a table has both headers and a caption, Pandoc generates a first header +(caption and headers) and main header (headers only). If we simply turn the table into an `xtabular` and erase the `\endfirsthead` and `\endhead` +commands we get a duplicate header row. + +Illustration of the code generated by Pandoc. Note the duplicate headers. + +```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: + + ```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..4e9d83ee --- /dev/null +++ b/longtable-to-xtab/expected.tex @@ -0,0 +1,77 @@ +\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..e35efb56 --- /dev/null +++ b/longtable-to-xtab/longtable-to-xtab.lua @@ -0,0 +1,108 @@ +--- # 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 + +-- For debug +local pretty = require 'pl.pretty' + +--- 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..a2ed4237 --- /dev/null +++ b/longtable-to-xtab/sample.md @@ -0,0 +1,45 @@ +# 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. +------------------------------------------------------------- From 1459aa4e0a5616540412e9025b0650e62edc3c76 Mon Sep 17 00:00:00 2001 From: jdutant <34026710+jdutant@users.noreply.github.com> Date: Sun, 17 Jan 2021 00:38:30 +0000 Subject: [PATCH 2/7] demonstrates twocolumn layout --- longtable-to-xtab/sample.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/longtable-to-xtab/sample.md b/longtable-to-xtab/sample.md index a2ed4237..7a147846 100644 --- a/longtable-to-xtab/sample.md +++ b/longtable-to-xtab/sample.md @@ -1,3 +1,9 @@ +--- +classoption: + - twocolumn + - landscape +--- + # Table with headers and caption ------------------------------------------------------------- From 2d1b35236b6c23ee36236ff6c1563e70eaf2bcd2 Mon Sep 17 00:00:00 2001 From: jdutant <34026710+jdutant@users.noreply.github.com> Date: Sun, 17 Jan 2021 00:40:40 +0000 Subject: [PATCH 3/7] demonstration two column layout --- longtable-to-xtab/expected.tex | 5 +++++ longtable-to-xtab/sample.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/longtable-to-xtab/expected.tex b/longtable-to-xtab/expected.tex index 4e9d83ee..6ea45c78 100644 --- a/longtable-to-xtab/expected.tex +++ b/longtable-to-xtab/expected.tex @@ -1,3 +1,8 @@ +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}} diff --git a/longtable-to-xtab/sample.md b/longtable-to-xtab/sample.md index 7a147846..99c628a6 100644 --- a/longtable-to-xtab/sample.md +++ b/longtable-to-xtab/sample.md @@ -4,6 +4,11 @@ classoption: - 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 ------------------------------------------------------------- From ea9e371bb9b225d793644c0d435bd5c843b0a52e Mon Sep 17 00:00:00 2001 From: jdutant <34026710+jdutant@users.noreply.github.com> Date: Sun, 17 Jan 2021 00:52:20 +0000 Subject: [PATCH 4/7] fix Makefile --- longtable-to-xtab/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/longtable-to-xtab/Makefile b/longtable-to-xtab/Makefile index 4a36a6a2..10a2d22b 100644 --- a/longtable-to-xtab/Makefile +++ b/longtable-to-xtab/Makefile @@ -1,6 +1,8 @@ DIFF ?= diff --strip-trailing-cr -u -.PHONY: test_latex +.PHONY: test + +test: test_latex test_latex: sample.md expected.tex longtable-to-xtab.lua @pandoc --lua-filter longtable-to-xtab.lua --to=latex $< \ From fe2f2b0f522ce4be9e3040256925b576c4638661 Mon Sep 17 00:00:00 2001 From: jdutant <34026710+jdutant@users.noreply.github.com> Date: Sun, 17 Jan 2021 00:59:55 +0000 Subject: [PATCH 5/7] removes debug code --- longtable-to-xtab/longtable-to-xtab.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/longtable-to-xtab/longtable-to-xtab.lua b/longtable-to-xtab/longtable-to-xtab.lua index e35efb56..c70a34a5 100644 --- a/longtable-to-xtab/longtable-to-xtab.lua +++ b/longtable-to-xtab/longtable-to-xtab.lua @@ -8,9 +8,6 @@ -- @license MIT - see LICENSE file for details. -- @release 1.0 --- For debug -local pretty = require 'pl.pretty' - --- 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 From 0ec7e089f4aca3f6ea8bc2f7f28782b49573875e Mon Sep 17 00:00:00 2001 From: jdutant <34026710+jdutant@users.noreply.github.com> Date: Sun, 17 Jan 2021 01:12:30 +0000 Subject: [PATCH 6/7] fix README.md --- longtable-to-xtab/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/longtable-to-xtab/README.md b/longtable-to-xtab/README.md index d5630296..2077137c 100644 --- a/longtable-to-xtab/README.md +++ b/longtable-to-xtab/README.md @@ -16,8 +16,8 @@ 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. +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 From 99402227f3cc9d131ddf54e27c83657a14d164b6 Mon Sep 17 00:00:00 2001 From: jdutant <34026710+jdutant@users.noreply.github.com> Date: Sun, 17 Jan 2021 01:16:06 +0000 Subject: [PATCH 7/7] Improved README.md --- longtable-to-xtab/README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/longtable-to-xtab/README.md b/longtable-to-xtab/README.md index 2077137c..e340f26a 100644 --- a/longtable-to-xtab/README.md +++ b/longtable-to-xtab/README.md @@ -37,21 +37,19 @@ dir path. Add `-L longtable-to-xtab.lua` to your Pandoc command line, or add `filter: longtable-to-xtab.lua` to a document's metadata block. -Note +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`. -Solving the duplicate headers issue ------ +### 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). If we simply turn the table into an `xtabular` and erase the `\endfirsthead` and `\endhead` -commands we get a duplicate header row. - -Illustration of the code generated by Pandoc. Note the duplicate headers. +(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}[]{@{} @@ -107,7 +105,7 @@ Possible solutions: \bottomrule ``` - and see below the ideal `xtab` code: + 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