1

I have this set up:

%%% some keys
\pgfkeys{/mystuff/first color/.code=\gdef\myfirstcolor{#1}}
\pgfkeys{/mystuff/first color/.default=black}

\pgfkeys{/mystuff/last color/.code=\gdef\mylastcolor{#1}}
\pgfkeys{/mystuff/last color/.default=black}

%%% main cmd
\newcommand{\mylist}[2][]{% aaa [opts] : bbb,...
  \foreach \temp in {#2}{\expandafter\mysplit\temp\relax
  first is \textcolor{\myfirstcolor}{\texttt{\myfirst}},
  last  is \textcolor{\mylastcolor}{\texttt{\mylast}} \par}
}

%%% helper cmds
\def\mysplit#1:#2\relax{%
  \myfirstandopts#1[]\relax%
  \gdef\mylast{#2}
}
\def\myfirstandopts#1[#2]#3\relax{%
  \gdef\myfirst{#1}
  \pgfkeys{/mystuff/.cd,first color,last color,#2}
}

The idea is that I give a list and can apply some keys to each of the items:

\mylist{Hello:World,
        Hello[first color=blue]:World,
        Hello[last color=red]:World}

enter image description here

If I want to give more than one option, it does not work:

\mylist{Hello [first color=blue,last color=red]:World}
! Paragraph ended before \mysplit was complete.

The problem is in the foreach. It gets passed:

Hello [first color=blue,last color=red]:World

which are two items in the foreach list and the first one,

Hello [first color=blue

does not agree with the definition.

This is easily solved by bracing the key list:

\mylist{Hello[{first color=blue,last color=red}]:World}

enter image description here

However, this is not so intuitive, since normally it is not necessary to brace the whole list of keys. In this case, it is just because internally it is placed in a list separated with commas. So, I should like to add the braces automatically.

I have tried to loop over the key list and replace [ by [{ and ] by }].

For this I have used the following macros:

\newcount\mycount

\def\mybracedkeys#1{\def\mybrcdkeys{}\myloop#1\relax}%% looping macro by David Carlisle
\def\myloop#1{%
  \ifx\relax#1% check if \relax is reached, if so, do nothing
    \else
      \mycount`#1 % store the ascii code of the character in the counter
      \ifnum\mycount=91% ascii code of [
          \expandafter\def\expandafter\mybrcdkeys\expandafter{\mybrcdkeys [\bgroup}%
        \else
          \ifnum\mycount=93% ascii code of ]
              \expandafter\def\expandafter\mybrcdkeys\expandafter{\mybrcdkeys \egroup]}%
            \else
              \expandafter\def\expandafter\mybrcdkeys\expandafter{\mybrcdkeys #1}
          \fi
      \fi
  \expandafter\myloop\fi}

and redefined \mylist:

\renewcommand{\mylist}[2][]{% aaa [opts] : bbb,...
  \mybracedkeys{#2}
  \foreach \temp in \mybrcdkeys{\expandafter\mysplit\temp\relax
     first is \textcolor{\myfirstcolor}{\texttt{\myfirst}},
     last  is \textcolor{\mylastcolor}{\texttt{\mylast}} \par}
}

This still does not work yielding the same error, which is not because we have lost the spaces, since the same happens with:

\pgfkeys{/mystuff/.cd,\bgroup first color=blue,last color=red\egroup}

I found this explanation by @wipet in this answer:

The { and } (more exactly the tokens with catcode 1 and 2) have four different meanings in TeX:

  • they are the syntactic part of macro definitions: \def\foo...{...}
  • each sequence of tokens scanned as single token list in TeX (i.e. in the parameter of a macro, inside macro definitions...) have to be balanced text by these tokens: \macro{param{e}ter}.
  • they are a part of several TeX primitive constructions, for example \hbox...{...}, $e^{...}$, \write...{...}.
  • when they are used without any context mentioned above they open and close the group.

The \bgroup and \egroup are declared by \let\bgroup={ \let\egroup=} and you can replace { and } by \bgroup and \egroup only in the third and fourth meaning mentioned above.

I guess that in this case we are in the second meaning so it can not be done.

Is there some other way to get it working? Preferably without expl3. Although, after so many complications (and the spaces issue has not yet been addressed), having to brace the key list does not seem so bad.

The complete code:

\documentclass{article}

\usepackage{tikz}

\begin{document}

\section*{Bracing a list within a list}

%%% some keys
\pgfkeys{/mystuff/first color/.code=\gdef\myfirstcolor{#1}}
\pgfkeys{/mystuff/first color/.default=black}

\pgfkeys{/mystuff/last color/.code=\gdef\mylastcolor{#1}}
\pgfkeys{/mystuff/last color/.default=black}

%%% main cmd
\newcommand{\mylist}[2][]{% aaa [opts] : bbb,...
  \foreach \temp in {#2}{\expandafter\mysplit\temp\relax
  first is \textcolor{\myfirstcolor}{\texttt{\myfirst}},
  last  is \textcolor{\mylastcolor}{\texttt{\mylast}} \par}
}

%%% helper cmds
\def\mysplit#1:#2\relax{%
  \myfirstandopts#1[]\relax%
  \gdef\mylast{#2}
}

\def\myfirstandopts#1[#2]#3\relax{%
  \gdef\myfirst{#1}
  \pgfkeys{/mystuff/.cd,first color,last color,#2}
}

\begin{verbatim}
\mylist{Hello:World,
        Hello[first color=blue]:World,
        Hello[last color=red]:World}
\end{verbatim}

\mylist{Hello:World,
        Hello[first color=blue]:World,
        Hello[last color=red]:World}

 \begin{verbatim}
 \mylist{Hello[{first color=blue,last color=red}]:World}
 \end{verbatim}
 
 \mylist{Hello[{first color=blue,last color=red}]:World}
 
 \newcount\mycount
  
 \def\mybracedkeys#1{\def\mybrcdkeys{}\myloop#1\relax}%% looping macro by David Carlisle
 \def\myloop#1{%
    \ifx\relax#1% check if \relax is reached, if so, do nothing
       \else
         \mycount`#1 % store the ascii code of the character in the counter
          \ifnum\mycount=91% ascii code of [
              \expandafter\def\expandafter\mybrcdkeys\expandafter{\mybrcdkeys [\bgroup}%
            \else
              \ifnum\mycount=93% ascii code of ]
                   \expandafter\def\expandafter\mybrcdkeys\expandafter{\mybrcdkeys \egroup]}%
                \else
                   \expandafter\def\expandafter\mybrcdkeys\expandafter{\mybrcdkeys #1}
          \fi
    \fi
    \expandafter\myloop\fi}
 
 \renewcommand{\mylist}[2][]{% aaa [opts] : bbb,...
   \mybracedkeys{#2}
   \foreach \temp in \mybrcdkeys{\expandafter\mysplit\temp\relax
     first is \textcolor{\myfirstcolor}{\texttt{\myfirst}},
     last  is \textcolor{\mylastcolor}{\texttt{\mylast}} \par}
 }
 
  does not work
 
\verb|\mylist{Hello[first color=blue,last color=red]:World}|
 
does not work

\verb|\pgfkeys{/mystuff/.cd,\bgroup first color=blue,last color=red\egroup}|


%https://tex.stackexchange.com/q/1930/53956


\end{document}

1 Answer 1

2

I mean that you want to protect brackets [...] by [{...}] and then run your \foreach. Rename your \mylist macro to \mylistA and define

\def\mylist#1{%
  \edef\tmp{\protectbk#1[\end]}%
  \expandafter\mylistA\expandafter{\tmp}%
}
\def\protectbk#1[#2]{#1\ifx\end#2\else[{#2}]\expandafter\protectbk\fi}

Now, the usage of:

\mylist{Hello [first color=blue,last color=red]:World}

does

\mylistA{Hello [{first color=blue,last color=red}]:World}
2
  • That works nicely. Would it be possible to do it without \edef? There could be keys like text=\textbf{some text}. Commented Jan 31, 2023 at 22:56
  • Try this: \protected \expandafter\def \expandafter\textbf \expandafter{\csname textbf \endcsname}. We are using this: 1. \protected\def macros are not expanded during \edef. 2. LaTeX defines its internal \textbf as \textbf space.
    – wipet
    Commented Feb 2, 2023 at 12:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .