9

Many on this site know (and perhaps find slightly annoying) that I will try to solve any LaTeX problem with the use of my stackengine package. My motto is like one of the overnight delivery services: "if it stacks, we ship it".

I found an odd problem in one of my parsing routines from that package, and I know it very much relates to the topic or robustness (which I kind of understand in theory, but certainly don't know enough to overcome). What I found surprising is that this particular problem only arises after an invocation of \centering which I found quite odd and unexpected.

I've boiled the problem down to this MWE. Here, \@readMANYrows will take an argument consisting of multi-row data, each row separated by a "end-of-line" (EOL) character, and parse it into individual row data. The stackengine package, for historical reasons, defaults this EOL character to a space, but has a package option to make the EOL a \\ to be more in line with what LaTeX users might expect (I realize \\ is a macro, not a character, and that perhaps is the root of this problem).

When the EOL is set to \\, everything works fine. If \centering is invoked, however, the routine will only work if the EOL is something other than \\. Finally, if I make \protect\\ the EOL, everything works again even when centering, but that last syntax is really a nuisance and for academic interest only.

So my question is two-fold: I am curious how it is that \centering undermines the parsing routine (it is not as if \centering is an argument to my routine... it is not)?; AND, is there an easy way to make the parsing routine robust without, for example, rewriting it in LaTeX3, as egreg did at Parsing rows (`\\`) and tab characters (`&`)?

I would only add, as a postscript, that \usepackage{fixltx2e} does not resolve this problem.

\documentclass{article}
\usepackage{ifthen}
\newcounter{ROWcellindex@}
\makeatletter
\newcommand\setstackEOL[1]{%
  \ifthenelse{\equal{#1}{}}{\def\SEP@char{ }}{\def\SEP@char{#1}}%
  \expandafter\define@processROW\expandafter{\SEP@char}%
}

\newcommand\define@processROW[1]{%
    \def\@processROW##1#1##2||{%
      \protected@edef\@preSEP{##1}%
      \protected@edef\@postSEP{##2}%
    }%
}

% FOR PROCESSING A GROUP OF STACK ROWS SEPARATED BY \SEP@char
\newcommand\@readMANYrows[1]{%
  \def\@doneROWreads{F}%
  \def\@postSEP{#1\SEP@char}%
  \setcounter{ROWcellindex@}{0}%
  \whiledo{\equal{\@doneROWreads}{F}}{%
    \stepcounter{ROWcellindex@}%
    \expandafter\@processROW\@postSEP||%
    \ifthenelse{\equal{\@postSEP}{}}{%
      \def\@doneROWreads{T}%
    }{}%
    \ifthenelse{\equal{\SEP@char}{ }}%
      {\def\@preSEPtemp{\@preSEP}}%
      {\def\@preSEPtemp{\ignorespaces\@preSEP\unskip}}%
    \expandafter\protected@edef\csname arg\roman{ROWcellindex@}\endcsname{%
      \@preSEPtemp}%
  }%
% \narg GIVES HOW MANY ROWS WERE PROCESSED
  \xdef\narg{\arabic{ROWcellindex@}}%
}
\begin{document}

% OK IF NO CENTERING
\setstackEOL{\\}
\@readMANYrows{a \\ b \\ c}
\argi, \argii, \argiii\\

% OK IF DIFFERENT EOL CHARACTER
\centering
\setstackEOL{$}
\@readMANYrows{d $ e $ f}
\argi, \argii, \argiii\\

% BREAKS! \\ NOT ROBUST
%\centering
%\setstackEOL{\\}
%\@readMANYrows{g \\ h \\ i}
%\argi, \argii, \argiii\\

% PROTECTING \\ WORKS, BUT IS A NUISANCE TO USE
\centering
\setstackEOL{\protect\\}
\@readMANYrows{j \protect\\ k \protect\\ l}
\argi, \argii, \argiii\\

\end{document}
  • 1
    The main difference is that in normal context \\ is a robust command, but it isn't in the scope of \centering. My impression is that you're passing it in a \protected@edef and this would explain the problem. Why you're passing the row delimiter to \protected@edef in the first place is not clear. And the use of \protected@edef when splitting is questionable too, I believe. – egreg Oct 10 '13 at 17:58
  • 1
    Note that \\ is redefined all over the place: center raggedright tabbing tabular anyway you are only using the sepchar as a separator so you may as well locally \let it to \relax so it is always robust. – David Carlisle Oct 10 '13 at 18:12
  • @egreg I had thought the \protected@edef was only adulterating the row data, and not the EOL, but I see now from the recursive nature of \@readMANYrows that downstream \SEP@chars become part of \@postSEP, and thus get expanded. And since you point out (I didn't know) that \\` is not robust with \centering, I think I understand now why it breaks. – Steven B. Segletes Oct 10 '13 at 18:14
  • @DavidCarlisle I understand what you are saying, and I am continuing my feeble attempts to figure out where locally to do that. – Steven B. Segletes Oct 10 '13 at 18:21
  • 1
    Actually \\ doesn't survive \ifthenelse{\equal{\\}{}} when it has the definition in \centering, which is the first problem. You really should use more robust tests, for instance those in etoolbox – egreg Oct 10 '13 at 19:20
7

With the redefinition performed by \centering, also \ifthenelse{\equal{\\}{}}{true}{false} fails, because \equal performs full expansion. This has no consequence with the default definition, because it's done with \DeclareRobustCommand. Surely you should also avoid \protected@edef where possible.

You are better off using more powerful and robust tests. I'd go with LaTeX3, but etoolbox also has many helpful features.

Here's an implementation with etoolbox.

\documentclass{article}
\usepackage{etoolbox}
\newcounter{ROWcellindex@}
\newtoggle{@doneROWreads}
\makeatletter
\newcommand\setstackEOL[1]{%
  \ifstrempty{#1}{\def\SEP@char{ }}{\def\SEP@char{#1}}%
  \expandafter\define@processROW\expandafter{\SEP@char}%
}

\newcommand\define@processROW[1]{%
    \def\@processROW##1#1##2||{%
      \def\@preSEP{##1}%
      \def\@postSEP{##2}%
    }%
}

% FOR PROCESSING A GROUP OF STACK ROWS SEPARATED BY \SEP@char
\newcommand\@readMANYrows[1]{%
  \togglefalse{@doneROWreads}%
  \edef\@postSEP{\unexpanded{#1}\expandonce{\SEP@char}}%
  \setcounter{ROWcellindex@}{0}%
  \whileboolexpr{test {\nottoggle{@doneROWreads}}}{%
    \stepcounter{ROWcellindex@}%
    \expandafter\@processROW\@postSEP||%
    \expandafter\ifstrempty\expandafter{\@postSEP}{%
      \toggletrue{@doneROWreads}%
    }{}%
    \expandafter\ifstrequal\expandafter{\SEP@char}{ }%
      {}%
      {\edef\@preSEP{\ignorespaces\expandonce{\@preSEP}\unskip}}%
    \csedef{arg\roman{ROWcellindex@}}{\expandonce{\@preSEP}}%
  }%
% \narg GIVES HOW MANY ROWS WERE PROCESSED
  \xdef\narg{\arabic{ROWcellindex@}}%
}
\begin{document}

% OK IF NO CENTERING
\setstackEOL{\\}
\@readMANYrows{a \\ b \\ c}
\argi, \argii, \argiii

% OK IF DIFFERENT EOL CHARACTER
\centering

\setstackEOL{$}
\@readMANYrows{d $ e $ f}
\argi, \argii, \argiii\\

% BREAKS! \\ NOT ROBUST
\centering
\setstackEOL{\\}
\@readMANYrows{g \\ h \\ i}
\argi, \argii, \argiii

% PROTECTING \\ WORKS, BUT IS A NUISANCE TO USE
\centering
\setstackEOL{\protect\\}
\@readMANYrows{j \protect\\ k \protect\\ l}
\argi, \argii, \argiii

\end{document}

enter image description here

| improve this answer | |
  • 2
    Your ability to digest bad code and fix it on the fly is much appreciated. And I can readily understand this syntax (unlike LaTeX3), and will use this example to enrich my learning. Not only did you fix the issue of fragile EOL separators, but as a by-product you fixed the issue of fragile arguments in \Shortstack, \Shortundertack, \Longstack and \Longunderstack. I am very grateful. This will without doubt go into the next package release. – Steven B. Segletes Oct 10 '13 at 20:11
  • @StevenB.Segletes You're welcome. Once you master etoolbox facilities, going to LaTeX3 shouldn't be difficult. ;-) – egreg Oct 10 '13 at 20:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.