4

I am trying to use a macro to reformat content in a clearer fashion. In particular, I am expecting to transform strings such as

foo; bar; baz;

into

foo;
bar;
baz;

Here is the macro I have so far, and its use in a document, working as expected:

\documentclass{article}
\usepackage{xstring}

\newcommand{\splitStatementsAndTextttize}[1]{%
  \StrCut{#1}{; }\leftPart\rightPart%
  \ifx\rightPart\empty
    \leftPart\\%
  \else
    \leftPart;\\ \splitStatementsAndTextttize{\rightPart}%
  \fi
}%

\begin{document}
\noindent \splitStatementsAndTextttize{foo; bar; baz;}
\end{document}

Gives me the following, with the expected typeset:

foo;
bar;
baz;

Now, this exact content should actually be in a single cell of a table. Following Table with multiple lines in some cells, this can be done by putting the content into a tabular.

Here is what the updated example looks like (same macro, different use):

\documentclass{article}
\usepackage{xstring}

\newcommand{\splitStatementsAndTextttize}[1]{%
  \StrCut{#1}{; }\leftPart\rightPart%
  \ifx\rightPart\empty
    \leftPart\\%
  \else
    \leftPart;\\ \splitStatementsAndTextttize{\rightPart}%
  \fi
}%

\begin{document}
\begin{tabular}[x]{@{}l@{}}
  \splitStatementsAndTextttize{foo; bar; baz;}
\end{tabular}
\end{document}

Which fails for some reason I do not understand...

! Undefined control sequence.
<argument> \rightPart 
                      
l.15 ...plitStatementsAndTextttize{foo; bar; baz;}
4

The problem is that table cells form a group, so any local assignment is lost between two cells. For example:

\def\cmd{?}
\begin{tabular}{cc}
  \def\cmd{foo} \cmd & \cmd \\
\end{tabular}

will result in

enter image description here

because when you switched to the second column the assignment was reverted.

The same happens with your example: In the first row \leftPart and \rightPart are assigned, and then you do \leftPart \\ something with \rightPart, but at the time you try to use \rightPart, the assignment was reverted (because you ended the table row) and \rightPart no longer exist.

You have to ways to get out of this: first is to use a few \expandafter to expand \rightPart before the table row ends:

\newcommand{\splitStatementsAndTextttize}[1]{%
  \StrCut{#1}{; }\leftPart\rightPart%
  \ifx\rightPart\empty
    \leftPart\\%
  \else
    \leftPart;%
    \expandafter\\%
      \expandafter\splitStatementsAndTextttize
        \expandafter{\rightPart}%
  \fi
}%

second is to not use assignments at all (the code below uses a delimited macro to grab things delimited by ; and typeset them one by one; this doesn't use xstring):

\newcommand{\splitStatementsAndTextttize}[1]{%
  \splitthingyaux#1;\splitthingyaux;}
\def\splitthingyaux#1;{%
  \if\relax\detokenize{#1}\relax\else % if non-empty
    \ifx\splitthingyaux#1\else % if not reached the end
      \texttt{\ignorespaces#1;}\\% use item
      \expandafter\expandafter
      \expandafter\splitthingyaux
    \fi
  \fi}

Also note that \begin{tabular}[x] is wrong (it behaves exactly like \begin{tabular}[c], but it's just by an implementation detail).

Full code:

\documentclass{article}
\usepackage{xstring}

\newcommand{\splitStatementsAndTextttize}[1]{%
  \splitthingyaux#1;\splitthingyaux;}
\def\splitthingyaux#1;{%
  \if\relax\detokenize{#1}\relax\else % if non-empty
    \ifx\splitthingyaux#1\else % if not reached the end
      \texttt{\ignorespaces#1;}\\% use item
      \expandafter\expandafter
      \expandafter\splitthingyaux
    \fi
  \fi}

% \newcommand{\splitStatementsAndTextttize}[1]{%
%   \StrCut{#1}{; }\leftPart\rightPart%
%   \ifx\rightPart\empty
%     \leftPart\\%
%   \else
%     \leftPart;%
%     \expandafter\\%
%       \expandafter\splitStatementsAndTextttize
%         \expandafter{\rightPart}%
%   \fi
% }%

\begin{document}
\begin{tabular}[c]{@{}l@{}}
  \splitStatementsAndTextttize{foo; bar; baz;}
\end{tabular}
\end{document}
4
  • Thanks for the explanation, this makes sense. I would gladly accept this answer, but there is an issue with the example that does not use xstring: I am looking to split the string by ; and not only ; (note the space character). Consequently, when I apply some more typesetting (for example \texttt{}) to #1, the problem becomes apparent: all but the first chunck contain leading spaces! – Pamplemousse Apr 23 at 16:04
  • @Pamplemousse If I change the line #1;\\% use item to \texttt{#1;}\\% use item everything looks (unless I'm not understanding what you want). Can you confirm that that doesn't work? It is possible to make it delimited by ; if you want, of course. Here's a test document with all three options: pastebin.com/raw/jWJWHn5n – Phelype Oleinik Apr 23 at 18:20
  • Simply changing #1; into \texttt{#1;} does not work, because all the lines but the first will display a spurious space at their beginning. – Pamplemousse Apr 23 at 20:00
  • @Pamplemousse Ah, indeed! Not sure how I didn't see that before. Adding an \ignorespaces there solves the problem – Phelype Oleinik Apr 23 at 20:27
2

I'd have to know more about the ways you want to use this, but a simple stack can transform the horizontal input into vertical output.

\documentclass{article}
\usepackage{stackengine}

\newcommand{\splitStatementsAndTextttize}[1]{\Longunderstack[l]{#1}}

\begin{document}
\begin{tabular}[x]{@{}l@{}}
  \splitStatementsAndTextttize{foo; bar; baz;}
\end{tabular}
\end{document}

enter image description here

1
  • The MWE has been simplified to focus on the essence of the question (my puzzlement seeing the macro not working in the tabular environment). More typesetting and formatting are actually happening inside the macro. – Pamplemousse Apr 23 at 15:29
1

This is a job for expl3. The \seq_map_function:NN facility will deliver the entire token list foo \\ bar \\ baz \\ in one step so the \\ will only be “seen” after the hole token list has been built.

The problem with your code is that the loop you set up is starting in one cell of the tabular and ending in another one, which is not possible.

\documentclass{article}
\usepackage{xstring}

\ExplSyntaxOn

\NewDocumentCommand{\splitStatementsAndTextttize}{m}
 {
  % split the input at semicolons
  \seq_set_split:Nnn \l_tmpa_seq { ; } { #1 }
  % remove the last item
  \seq_pop_right:NN \l_tmpa_seq \l_tmpa_tl
  % if the last item is not empty, reinsert it back
  \tl_if_empty:NF \l_tmpa_tl { \seq_put_right:NV \l_tmpa_seq \l_tmpa_tl }
  % map the sequence adding \\ after each item
  \seq_map_function:NN \l_tmpa_seq \pamplemousse_add_endline:n
 }
\cs_new_protected:Nn \pamplemousse_add_endline:n
 {
  #1 \\
 }

\ExplSyntaxOff

\begin{document}

\begin{tabular}{@{}l@{}}
\hline
  \splitStatementsAndTextttize{foo; bar; baz;}
\hline
\end{tabular}
\qquad
\begin{tabular}{@{}l@{}}
\hline
  \splitStatementsAndTextttize{foo; bar; baz}
\hline
\end{tabular}

\end{document}

I added \hline just to show that the output is the same whether or not you have a trailing semicolon.

enter image description here

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.