20

I want to apply a macro to all the cells of a column with the cell content as the input to the macro. Is this possible?

For example if the cell contents have the word TEST, the macro should take TEST as its argument.

lockstep
  • 250,273
Chuang
  • 579

6 Answers6

21

You can use the relatively new collcell package to collect the cell content and feed it to a macro of your choice. It uses the array package to place code before and after each cell.

\documentclass{article}
\usepackage{collcell}
\usepackage{array}% actually already loaded by `collcell`
\newcommand*{\mymacro}[1]{\fbox{#1}}% Do anything you like with `#1`
\newcolumntype{C}{>{\collectcell\mymacro}c<{\endcollectcell}}

\begin{document}

\begin{tabular}{CC}
  TestA  & A longer test cell \\
  \empty & The new version supports 'verb'! \\
\end{tabular}

\end{document}

Make sure you use the version 2011/02/27 (or later) which contains a lot of improvements.

There is also the possibility to do it the following way, which compiles faster than collcell but does not work in the last cell of each row. It reads everything between the internally used macros \ignorespaces and \unskip. I got this from Ulrike Fischer on de.c.t.t a while ago.

\documentclass{article}
\usepackage{array}
\newcommand*{\mymacro}[1]{\fbox{#1}}

\def\simplecollect#1#2\ignorespaces#3\unskip{#1{#3}\unskip}
\newcolumntype{S}{>{\simplecollect\mymacro}c}

\begin{document}

\begin{tabular}{Sc}
  TestA  & Doesn't work in the last cell \\
  \empty & Sorry!  \\
\end{tabular}

\end{document}
David Carlisle
  • 757,742
Martin Scharrer
  • 262,582
  • Nice solution! – yannisl Mar 04 '11 at 10:33
  • @Yiannis: Thanks. I needed this once for my tikz-timing package and then started to code collcell. Ironically, tikz-timing now simply removes \ignorespaces and uses \unskip or \\ as end-marker, so it doesn't need collcell :-) – Martin Scharrer Mar 04 '11 at 10:37
  • Thank you very much for collcell. Compared with \ignorespaces…\unskip, it has several advantages, including stripping off trailing whitespace (I wanted to use the content of the cell as a label) and not causing errors when encountering \noalign in a tabularx environment (that may not be the exact issue, I stopped debugging because collcell just works). – Gilles 'SO- stop being evil' Dec 27 '13 at 18:47
10

It should be as simple as:

\def\mymacro#1{\lowercase{#1}}
\halign{&\mymacro{#}\cr
HELLO&WORLD\cr
TEST&123\cr}
\bye

For Yiannis's comment to apply the macro only to the first column, change the preamble (&\mymacro{#}\cr) to \mymacro{#}&&#\hfil\cr, for example. An ampersand at the very beginning tells TeX to repeat the definition(s) for every column, whereas && defines that the following column definitions should be repeated.

Ofcourse, you don't need to repeat anything if you don't want to.

morbusg
  • 25,490
  • 4
  • 81
  • 162
  • @morsburg I am not very familiar with TeX's tables, how would you apply the macro only on the first column? – yannisl Mar 04 '11 at 12:17
  • @Yiannis: I've updated the answer to include a solution for your question. BTW, my handle is morbusg, not morsburg. – morbusg Mar 04 '11 at 12:41
  • @morbusg: Very nice plainTeX solution! Thank you very much for explaining the things about repeating column definitions in the preamble. I didn't know about them yet. – Martin Scharrer Mar 04 '11 at 12:46
  • @morbusg thanks for posting the additional comments. Sorry for the "burg", I spent too many years in South Africa! – yannisl Mar 04 '11 at 18:15
  • @Martin: In my opinion, morbusg's answer shows that plain TeX tables are great! No need for any package. (No offense meant :-)) Can't you just do this in collcell somehow? @morbusg: +1, nicely written up answer! – Hendrik Vogt Mar 06 '11 at 08:16
  • @Hendrik: In theory it would be possible to extend array to not only have > and < to add material before and after the # in the internal \halign preamble, but actually insert full column description as one expression like \mymacro{#} above. However, it's not that simple to do. – Martin Scharrer Mar 06 '11 at 10:46
  • @Martin: Perhaps one should just write a different version of the array package that doesn't have < and >? – Hendrik Vogt Mar 06 '11 at 11:41
  • @Hendrik: > and < are fine, it just needs another character which allows to insert both sides of the code at the same time. – Martin Scharrer Mar 06 '11 at 12:08
  • @Martin: Sorry, I didn't express myself clearly enough. What I meant: If you have #, then > and < are no longer needed. In fact, you're back to the plain TeX syntax, which I like best! – Hendrik Vogt Mar 06 '11 at 13:23
  • @Hendrik: I see, however there is no much reason to disallow the widely used >/< characters even if you have access to #. – Martin Scharrer Mar 06 '11 at 13:27
  • @Martin: That's true. Unless it would simplify the implementation a lot. – Hendrik Vogt Mar 06 '11 at 13:28
  • @Hendrik: it is possible to do that using >{...} and gobbling the hash which represents the cell body in the preamble (see below). But in general @morbusg's approach (and my adaptation to LaTeX) forces the user to explictly write something (e.g. \cr, \crcr, \\) at the end of the last line of the tabular if the last column is of the type that takes arguments. – Bruno Le Floch Mar 07 '11 at 20:27
  • @Bruno: Thanks for the comment! Somehow I think it was a bad idea of the LaTeX authors to not have \\ in the end of the last row. It's a hassle if you keep swapping rows ... – Hendrik Vogt Mar 07 '11 at 21:27
  • @morbusg: Will this work also for something like \def\mymacro#1{\lowercase{#1}} \tabskip=0pt \halign to .5\hsize{ \vrule\tabskip=4pt#&\mymacro{#}\tabskip=0pt plus 1fil\strut&#\tabskip=4pt&\vrule#\cr &HELLO & WORLD&\cr &TEST & My number 123&\cr }? – Ahmed Musa May 05 '11 at 11:21
  • @Ahmed: do you mean repeating? Or applying the \mymacro? The #-symbol inside the preamble represents the stuff that gets inserted into the alignment, so if you mean applying the macro, then by changing the plain # by \mymacro{#} will change that column. If you meant repeating, then, well, I find it more productive to first just construct the preamble without repeats, and only if it's instantly apparent one could use repeats, to use them. I cannot instantly see how to repeat your example because of the fil and vrules. – morbusg May 06 '11 at 06:41
  • @morbusg: +1. What an elegant solution! – Mafra Jun 22 '13 at 22:15
4

You can do this by using the array package and defining a \newcolumntype.

For example we can define a macro \test that can take a parameter as its argument and capitalize it.

\def\test#1 {\uppercase{#1}}

We can then define a new column type as:

\newcolumntype{D}{>{\test}l<{.}}

and our minimal example would be:

\documentclass{article}
\usepackage{array}
\begin{document}
  \def\test#1 {\uppercase{#1}}
  \newcolumntype{D}{>{\test}l<{.}}
  \begin{tabular}{DD}
    test &test \\
    other &test \\
  \end{tabular}
\end{document}

Please note that this approach involves delimited macros, in this case the space after the word in the table acts to tell the command that it must only read up to there and obviously will fail if there are more than one word as Martin pointed out. One could use other types of delimiters perhaps a . or a !.

David Carlisle
  • 757,742
yannisl
  • 117,160
  • 3
    This doesn't work in the general case. Here \test reads everything to the first space as argument. So cells with more than a single word or such not ending in a space will fail. – Martin Scharrer Mar 04 '11 at 06:07
3

EDIT: added support for \multicolumn.

This solution is more or less a LaTeX version of @morbusg's plain TeX answer. \span forces the expansion of the next token when TeX reads the preamble. \@gobbletwo removes the tokens forming the normal preamble which is inserted by {tabular}: this way, we move the # (i.e. body of the cell) to the middle of what >{...} inserts, rather than after it. More explanations after the code.

\documentclass{article}
\usepackage{array}

\makeatletter
\def\KWP@safe@newline{%
  \iffalse{\fi
  \let\KWP@old@newline\\%
  \let\\\cr
  \iffalse}\fi
}
\def\KWP@restore@newline{\iffalse{\fi\let\\\KWP@old@newline\iffalse}\fi}

\newcolumntype{\arg}[1]{%
  >{\KWP@safe@newline
    #1{\ignorespaces \@sharp\unskip}%
    \KWP@restore@newline
    \span\@gobbletwo}%
  c}

\newcolumntype{\argmulti}[1]{%
  >{\KWP@safe@newline
    #1{\ignorespaces \@sharp\unskip}%
    \KWP@restore@newline
    \@gobbletwo}%
  c}

\makeatother

\begin{document}

First test.
\begin{tabular}{c\arg{\textbf}}
    abc & bcd \\
    cde & def \\
\end{tabular}

\bigskip

\newcommand{\mymacro}[1]{#1: $(#1)^{#1}$}%
Second test.
\begin{tabular}{|\arg{\mymacro}|c|}
    abc & bcd \\
    cdefgh & A\\
    \multicolumn{2}{|c|}{ABC}\\
\end{tabular}

\bigskip

\begin{tabular}{|c|c|}
    abc & bcd \\
    cdefgh & A\\
    \multicolumn{2}{|\argmulti{\mymacro}|}{ABC}\\
\end{tabular}

\end{document}

To give an idea of what's going on, let's look at the preamble constructed in the case of the column specification \arg{\textbf}. The "new" part is between \KWP@safe@newline and \@gobbletwo three lines below.

\KWP@safe@newline saves \\, and lets it to \cr. Otherwise, the macro, looking for an argument, would grab everything until the end of the cell, without expanding, and would not see the \cr hidden in the definition of \\. We'll restore the former definition with \KWP@restore@newline.

\textbf{\ignorespaces \@sharp \unskip} is just our macro. \ignorespaces and \unskip remove spaces from the argument (a better way would trim spaces expandably and pass the argument to \textbf), and \@sharp is let to #, which represents the body of the cell.

Finally, \span\@gobbletwo expands when the preamble is read (i.e., earlier than everything else here), and removes the \@sharp (and \ignorespaces, which I don't care about).

\@preamble ->\ialign \bgroup \unhcopy \@arstrutbox 
\hskip \col@sep \hfil 
\d@llarbegin 
\KWP@safe@newline 
\textbf {\ignorespaces \@sharp \unskip }%
\KWP@restore@newline 
\span \@gobbletwo \ignorespaces \@sharp \unskip \relax 
\d@llarend 
\hfil \hskip \col@sep 
\tabskip \z@ \cr 

Caveat: if the last column specifier is that \arg, then the last line of the tabular must be terminated with \\ (or \cr or \crcr).

David Carlisle
  • 757,742
2

Perhaps analogous to @Martin's answer, you can box the cell contents using an lrbox environment, and then supply the boxed contents to your macro:

enter image description here

\documentclass{article}
\usepackage{array}% http://ctan.org/pkg/array
\begin{document}
\newsavebox{\mybox}
\begin{tabular}{c>{\begin{lrbox}{\mybox}}c<{\end{lrbox}\fbox{\usebox\mybox}}c}
  One & Two & Three \\ \hline
  1 & 2 & 3 \\
  4 & 5 & 6 \\
  7 & 8 & 9 \\ \hline
\end{tabular}
\end{document}

The array package provides the means to insert content before >{...} and after <{...} a specific column. In this case, the lrbox environment start and end, as well as the resulting macro application for typesetting the cell content after boxing.

The restriction is mainly reliant on what can be boxed by the lrbox environment.

Moriambar
  • 11,466
Werner
  • 603,163
0

An alternative solution with tblr environment of tabularray package:

\documentclass{article}

\usepackage{tabularray} \newcommand{\mycmda}[1]{\fbox{#1}} \newcommand{\mycmdb}[1]{\underline{#1}}

\begin{document}

\begin{tblr}{Q[c,cmd=\mycmda]Q[c,cmd=\mycmdb]} Alpha & Gamma \ Beta & Delta \ \end{tblr}

\end{document}

enter image description here

L.J.R.
  • 10,932