3

In my document I have many tables on the format

\begin{tabular}{l|l||l}
    a & b & W\\ \hline
    c & d & g\\ 
    e & f & g\\ 
\end{tabular}

The number of columns might vary, as can the number of rows but I always want to have a double line separating the rightmost column from the others and a line separating the first and second rows.

Is there some way that I can write a custom command allowing me to write something along the lines of

\begin{tabular2}{3}
    a & b & W\\
    c & d & g\\ 
    e & f & g\\ 
\end{tabular2}

For the same result?

This is so that I can ensure that I haven't made minor formatting errors changes in any of my many tables

Bomaz
  • 133

4 Answers4

7

With xparse (see edit history for versions prior to 2019-03-05):

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentEnvironment{bomaztab}{ O{c} m +b }
 {
  \bomaz_maketable:nnn { #1 } { #2 } { #3 }
 }
 {}

\tl_new:N \l_bomaz_preamble_tl
\tl_new:N \l_bomaz_firstrow_tl
\seq_new:N \l_bomaz_body_seq

\cs_new_protected:Nn \bomaz_maketable:nnn
 {
  % #1 = alignment argument, #2 = number of cols, #3 = body
  % add #2-1 'l|' cols
  \tl_set:Nx \l_bomaz_preamble_tl { \prg_replicate:nn { #2 - 1 }{ l| } }
  % add a trailing '|l'
  \tl_put_right:Nn \l_bomaz_preamble_tl { |l }
  % split the body at \\
  \seq_set_split:Nnn \l_bomaz_body_seq { \\ } { #3 }
  % detach the first row
  \seq_pop_left:NN \l_bomaz_body_seq \l_bomaz_firstrow_tl
  % start the tabular
  \bomaz_tabular:nV { #1 } \l_bomaz_preamble_tl
  % first row is followed by \hline
  \l_bomaz_firstrow_tl \\ \hline
  % deliver the other rows
  \seq_use:Nn \l_bomaz_body_seq { \\ }
  % end the tabular
  \endtabular
 }

% helper function to be varied (start of tabular)
\cs_new_protected:Nn \bomaz_tabular:nn { \tabular[#1]{#2} }
\cs_generate_variant:Nn \bomaz_tabular:nn { nV }

\ExplSyntaxOff

\begin{document}

\begin{bomaztab}{3}
    a & b & W\\
    c & d & g\\ 
    e & f & g\\ 
\end{bomaztab}
\qquad
\begin{bomaztab}{2}
    a & W \\
    c & g \\ 
    e & g \\ 
\end{bomaztab}

\medskip

XYZ
\begin{bomaztab}[t]{2}
    a & W \\
    c & g \\ 
    e & g \\ 
\end{bomaztab}

\end{document}

enter image description here

A slightly different implementation where the number of columns is guessed from the first row; however, columns with different alignments can be specified explicitly, see the last example; also the vertical alignment can be set independently.

\documentclass{article}
\usepackage{xparse,environ}

\ExplSyntaxOn

\NewDocumentEnvironment{bomaztab}{ O{} +b }
 {
  \bool_set_true:N \l_bomaz_guess_bool
  \keys_set:nn { bomaz/tables }
   {
    valign=c,
    #1
   }
  \bomaz_maketable:VVn \l_bomaz_align_tl \l_bomaz_preamble_tl { #2 }
 }
 {}

\bool_new:N \l_bomaz_guess_bool
\tl_new:N \l_bomaz_align_tl
\tl_new:N \l_bomaz_preamble_tl
\tl_new:N \l_bomaz_firstrow_tl
\seq_new:N \l_bomaz_body_seq
\seq_new:N \l__bomaz_preamble_seq
\seq_new:N \l__bomaz_temp_seq

\keys_define:nn { bomaz/tables }
 {
  valign .tl_set:N = \l_bomaz_align_tl,
  preamble .code:n =
   {
    \bool_set_false:N \l_bomaz_guess_bool
    \tl_set:Nn \l_bomaz_preamble_tl { #1 }
   },
 }

\cs_new_protected:Nn \bomaz_maketable:nnn
 {
  % #1 = alignment argument, #2 = preamble, #3 = body
  % split the body at \\
  \seq_set_split:Nnn \l_bomaz_body_seq { \\ } { #3 }
  % detach the first row
  \seq_pop_left:NN \l_bomaz_body_seq \l_bomaz_firstrow_tl
  \bool_if:NT \l_bomaz_guess_bool
   {
    \__bomaz_preamble_guess:
   }
  \__bomaz_preamble_compute:
  % start the tabular
  \bomaz_tabular:nV { #1 } \l_bomaz_preamble_tl
  % first row is followed by \hline
  \l_bomaz_firstrow_tl \\ \hline
  % deliver the other rows
  \seq_use:Nn \l_bomaz_body_seq { \\ }
  % end the tabular
  \endtabular
 }

% helper function to be varied (start of tabular)
\cs_new_protected:Nn \bomaz_tabular:nn { \tabular[#1]{#2} }
\cs_generate_variant:Nn \bomaz_tabular:nn { nV }
% variant
\cs_generate_variant:Nn \bomaz_maketable:nnn { VVn }

\cs_new_protected:Nn \__bomaz_preamble_guess:
 {
  \seq_set_split:NnV \l__bomaz_temp_seq { & } \l_bomaz_firstrow_tl
  \tl_set:Nx \l_bomaz_preamble_tl
   {
    \prg_replicate:nn { \seq_count:N \l__bomaz_temp_seq } { l }
   }
 }
\cs_new_protected:Nn \__bomaz_preamble_compute:
 {
  \seq_set_split:NnV \l__bomaz_preamble_seq { } \l_bomaz_preamble_tl
  \tl_set:Nx \l_bomaz_preamble_tl
   {
    \seq_use:Nnnn \l__bomaz_preamble_seq { || } { | } { || }
   }
 }
\ExplSyntaxOff

\begin{document}

\begin{bomaztab}
    a & b & W\\
    c & d & g\\ 
    e & f & g\\ 
\end{bomaztab}
\qquad
\begin{bomaztab}
    a & W \\
    c & g \\ 
    e & g \\ 
\end{bomaztab}

\medskip

XYZ
\begin{bomaztab}[valign=t,preamble=rl]
    aaa & WWW \\
    cc & gg \\ 
    e & ggg \\ 
\end{bomaztab}

\end{document}

enter image description here

egreg
  • 1,121,712
  • 1
    If you use expl3 and environ you could as well parse the number of columns... Just saying :) – Skillmon Oct 23 '17 at 11:51
  • @Skillmon Do you mean calling \begin{bomaztab}{ccrllc} and get the equivalent of \begin{tabular}{c|c|r|l|l||c}? – egreg Oct 23 '17 at 11:57
  • No I meant you should just have to call \begin{bomaztab} and it knows the correct amount of columns (with the option to give it the correct amount of columns for edge cases where the alignment tab character might be hidden inside unexpandable macros). – Skillmon Oct 23 '17 at 14:56
  • @Skillmon That's a nice idea, let me work it out (train is 30 minutes late and the lounge has WiFi). – egreg Oct 23 '17 at 17:05
  • @Skillmon New implementation on-line. – egreg Oct 23 '17 at 17:45
4

You could use something like the following. Notice that the method to put a \hline under the first row is not pretty, imho.

\documentclass[]{article}

\newcount\tabtwocnt

\makeatletter
\newenvironment*{tabular2}[2][]
{%
  \def\tabtwotmpa{\begin{tabular}[#1]}%
  \ifnum#2=1\relax%
    \def\tabtwotmpb{||l}
  \else%
    \def\tabtwotmpb{}
    \tabtwocnt=1\relax%
    \loop%
      \edef\tabtwotmpb{\tabtwotmpb l|}%
      \advance\tabtwocnt by 1\relax%
      \ifnum\tabtwocnt<#2\relax%
    \repeat%
    \edef\tabtwotmpb{\tabtwotmpb|l}
  \fi%
  \global\let\tabtwotmpc\\
  \expandafter\tabtwotmpa\expandafter{\tabtwotmpb}%
  \global\let\tabtwotmpd\\%
  \gdef\\{\global\let\\\tabtwotmpd\\\hline}\@firstofone%
}{%
  \end{tabular}
  \global\let\\\tabtwotmpc
}
\makeatother

\begin{document}
\begin{tabular2}[]{1}
  a
\end{tabular2}
\begin{tabular2}[]{2}
  a & b
\end{tabular2}
\begin{tabular2}[]{3}
  a & b & c\\
  d & e & f
\end{tabular2}
\begin{tabular2}[]{4}
  a & b & c & z\\
  d & e & f & z
\end{tabular2}
\begin{tabular2}[]{5}
  a & b & c & y &z\\
  d & e & f & y &z
\end{tabular2}
\begin{tabular2}[]{12}
  a & b & c & y & z & 1 & 2 & 3 & 4 & 5 & 6 & 7\\
  a & b & c & y & z & 1 & 2 & 3 & 4 & 5 & 6 & 7\\
  a & b & c & y & z & 1 & 2 & 3 & 4 & 5 & 6 & 7
\end{tabular2}
Foo\\Bar
\end{document}

enter image description here

Skillmon
  • 60,462
2

With listofitems and token lists. No number-of-columns argument is required.

\documentclass{article}
\usepackage{listofitems,environ}
\newtoks\tabAtoks
\newtoks\tabAhead
\newcommand\apptotoks[2]{#1\expandafter{\the#1#2}}
\NewEnviron{tabularA}[1][c]{%
  \setsepchar{\\/&}%
  \readlist\tabA{\BODY}%
  \tabAtoks{}%
  \foreachitem\i\in\tabA[]{%
    \ifnum\listlen\tabA[\icnt]>1\relax%
      \tabAhead{}%
      \foreachitem\j\in\tabA[\icnt]{%
        \ifnum\jcnt=1\relax\else\apptotoks\tabAhead{|}\fi%
        \ifnum\jcnt=\listlen\tabA[\icnt]\relax\apptotoks\tabAhead{|}\fi%
        \apptotoks\tabAhead{l}%
        \ifnum\jcnt=1\relax\else\apptotoks\tabAtoks{&}\fi%
        \expandafter\apptotoks\expandafter\tabAtoks\expandafter{\j}%
      }%
      \ifnum\icnt<\listlen\tabA[]\relax\apptotoks\tabAtoks{\\}\fi%
      \ifnum\icnt=1\relax\apptotoks\tabAtoks{\hline}\fi%
    \fi%
  }%
  \def\tmp{\begin{tabular}[#1]}
  \expandafter\tmp\expandafter{\the\tabAhead}%
  \the\tabAtoks
  \end{tabular}
}
\begin{document}
\begin{tabularA}
    a & b & W\\
    c & d & g\\ 
    e & f & g\\
\end{tabularA}
\qquad
\begin{tabularA}
    a & W \\
    c & g \\ 
    e & g \\
\end{tabularA}

\medskip

XYZ
\begin{tabularA}[t]
    a & W \\
    c & g \\ 
    e & g \\
\end{tabularA}
\end{document}

enter image description here

0

I do precisely this using TeXstudio's macros, and I imagine most editors have a similar macro or snippet functionality.

In TeXstudio, it's as simple as pasting your code into a box and assigning it a name:

  • Go to Macros > Edit Macros.
  • Click the Add button.
  • Put your table template text in the LaTeX Content field, selecting Type = Normal.
  • Put the name you want to appear on the TeXstudio menu in the Name field.
  • Optionally, you can define trigger text (Trigger field) which, when typed, will insert your table template.

To use it, go to the Macros menu in TeXstudio and select the name you added to the Name field above.

In the case of frequently used items, I also assign a shortcut key combination, so inserting, say, a table or figure or section header is as easy as CTRL+SHIFT+T or whatever:

  • Go to Options > Configure TeXstudio....
  • Select the Shortcuts tab.
  • Go to the entry for the Macro menu.
  • Expand it and select the macro you just created.
  • Double click in the Current Shortcut field, press your desired key combination, and hit ENTER.

Note that in TeXstudio, shortcuts seem to be positional: if you assign CTRL+Q to your second macro, "Insert Cool Stuff", and then move this macro elsewhere in the editable list, CTRL+Q will activate whatever ends up being in the second position in your macro list, and not "Insert Cool Stuff". This is an odd design choice, so you should be aware of it.

I realize you were looking at using a Latex macro (i.e. \newcommand\whatever) for this purpose, but as is so often the case, Latex makes the impossible easy and the easy excruciating, only solvable through a profound understanding of ancient programming, or both.

Your editor has no such problems ;-)

(EDIT: Considering the plethora of packages --thousands of lines of code, at least-- which have been written just to get basic table functionality to work right in Latex (by-row formatting, multi-columns, expanding cells, vertical alignment settings, and even that most basic of all functions, text wrapping, which worked just fine on my Atari 800 without any hacks), I stand by my statement about having to have a deep knowledge of ancient programming to do what is easy in other software (and any language that treats _ as a reserved character is ancient, if not pre-historic). That doesn't mean Latex is bad, but it is perhaps the single most arcane thing anyone not working on legacy bank mainframes will ever come across).

  • 2
    (a) There's no need for "ancient programming" (if you do not like TeX, try the very recent "expl3". (b) Answers should provide some details. How did you achieve a TeXstudio macro for arbitrary column numbers etc.? That would be a point to start. – TeXnician Oct 23 '17 at 08:45
  • @TeXnician Actually, and in spite of the other answers that have been given here, I'd say there's no need for programming of any type in this case. We're talking, essentially, about pasting a few code snippets into the editor -- most editors excel at this, and there's absolutely no need for scores of lines of code like \def\tabtwotmpa{\begin{tabular}[#1]}%! – Gil Williams Oct 23 '17 at 09:41
  • 3
    Will this work for an arbitrary number of columns, like the other answers do? – samcarter_is_at_topanswers.xyz Oct 23 '17 at 09:52
  • @GilWilliams \begin{tabular2}{12} works just fine on my machine. My code works for an arbitrary number of columns (as long as TeX's capacities are not superseded). – Skillmon Oct 23 '17 at 11:14
  • @GilWilliams Both \begin{tabular2}[]{8} or \begin{bomaztab}{12} work perfectly fine. – samcarter_is_at_topanswers.xyz Oct 23 '17 at 11:17
  • @Skillmon Okay, I see what you're doing now -- this applies the formatting the OP requested to a table of n columns, assuming you manually create a table with the same number of columns first. I was assuming it generated the table, too, or at least a few rows with n columns each. Could that even be done? Can Latex do constructs like the following pseudocode: for row = 1 to n; while row {print $ycolumnrow}? – Gil Williams Oct 23 '17 at 11:26
  • @GilWilliams how should I generate a table, if I don't know the contents. The a & b & c are just placeholders and not the final contents. – Skillmon Oct 23 '17 at 11:29
  • @Skillmon Those placeholders are all you need to make it easy to work with the table (well, easy as defined in Latex!). They're sort of the equivalent of empty cells in a spreadsheet. That would be extreeeeeemely convenient and powerful! – Gil Williams Oct 23 '17 at 11:31
  • @GilWilliams and how should TeX source code generate source code in the same file without a TeX-run (with a TeX-run would be hard enough)? Of course you can write code that iterates an integer. It's used in my answer. In TeX-pseudo-code: \loop\doStuff\advance\count by 1\relax\ifnum\count<\endcount\relax\repeat – Skillmon Oct 23 '17 at 11:35
  • @Skillmon Ah, that's the gotcha, right! This could, however, be done with TeXstudio's macros, which allow you to use Javascript (or a subset thereof). It would be a sort of customizable table-generating wizard. Pity I don't know Javascript! – Gil Williams Oct 23 '17 at 11:39
  • @GilWilliams and this is superior because of? That's not a gotcha, that is like saying C is the worst programming language because it has to be compiled. Inserting a & or \\ is (or might be) faster than mouse-navigating through a wizard if you know what should be in the table. – Skillmon Oct 23 '17 at 11:49
  • @Skillmon The "gotcha" isn't a failure of Latex, it was me being caught by a pretty obvious problem that I needed you to point out to me. As to making a wizard that would generate arbitrary-sized tables with custom formatting, hitting a key combo and typing 3 (cols) 12 (rows) would be far faster than typing. But more importantly, it would eliminate a source of human error, insuring consistency. To this day, the best way to get complex tables into Latex is to create them in Excel and use a package whose name eludes me to export them to Latex format. And I really wish that wasn't the case. – Gil Williams Oct 23 '17 at 11:56