Explode arguments in argument list
You can use another separator different than @
that doesn't change catcodes, or use
\begingroup\lccode`;=`@ \lowercase{\endgroup
\def\@explode#1;#2;#3\@nil}{\edef\@pOne{#1}\edef\@pTwo{#2}}
instead of
\def\@explode#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}
but note that you won't be able to use it in a \makeatletter
part of the code.
And the full game
\begingroup\lccode`;=`@ \lowercase{\endgroup
\def\@explodeother#1;#2;#3\@nil}{\edef\@pOne{#1}\edef\@pTwo{#2}}
\def\@explodeletter#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}
\def\explode{\ifnum\catcode`@=11 \expandafter\explodeletter\else\expandafter\explodeother\fi}
\begingroup\lccode`;=`@ \lowercase{\endgroup
\def\explodeother#1{\expandafter\@explodeother#1;;\@nil}}
\def\explodeletter#1{\expandafter\@explodeletter#1@@\@nil}
\newcommand{\fun}[1][a@b,c@d,e@f,g,]{%
\@for\elem:=#1\do{\explode{\elem}1:\@pOne-2:\@pTwo, }%
}
This last one checks the catcode of @
and uses the letter-@ division or other-@ division accordingly.
It is a category code problem of @
. The definition used category code "letter", whereas the category code of @
is usually "other" in the main document, example:
\documentclass{article}
% \FunLetter using @ with category code "letter"
\makeatletter
\def\@explode@letter#1@#2@#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}
\newcommand*{\ExplodeLetter}[1]{\expandafter\@explode@letter#1@@\@nil}
\newcommand{\FunLetter}[1][a@b,c@d,e@f,g,]{%
\@for\elem:=#1\do{\ExplodeLetter{\elem}1:\@pOne-2:\@pTwo, }%
}
\makeatother
% \FunOther using @ with category code "other"
\makeatletter
\begingroup
\lccode`9=`@
\lowercase{%
\endgroup
\def\@explode@other#19#29#3\@nil{\edef\@pOne{#1}\edef\@pTwo{#2}}
\newcommand*{\ExplodeOther}[1]{\expandafter\@explode@other#199\@nil}
\newcommand{\FunOther}[1][a9b,c9d,e9f,g,]{%
\@for\elem:=#1\do{\ExplodeOther{\elem}1:\@pOne-2:\@pTwo, }%
}
}
\makeatother
\begin{document}
\setlength{\parindent}{0pt}
\verb|\makeatother|\makeatother\\
\begin{tabular}{@{}l@{ }l@{}}
\verb|\FunLetter|:& \FunLetter\\
\verb|\FunLetter[a@b,c@d,e@f,g,]|:& \FunLetter[a@b,c@d,e@f,g,]\\
\verb|\FunOther|:& \FunOther\\
\verb|\FunOther[a@b,c@d,e@f,g,]|:& \FunOther[a@b,c@d,e@f,g,]
\end{tabular}
\medskip
\verb|\makeatletter|\makeatletter\\
\begin{tabular}{@{}l@{ }l@{}}
\verb|\FunLetter|:& \FunLetter\\
\verb|\FunLetter[a@b,c@d,e@f,g,]|:& \FunLetter[a@b,c@d,e@f,g,]\\
\verb|\FunOther|:& \FunOther\\
\verb|\FunOther[a@b,c@d,e@f,g,]|:& \FunOther[a@b,c@d,e@f,g,]
\end{tabular}
\end{document}
Workarounds/solutions:
- Checking for both
@
tokens with category code "letter" and "other". - Using a different separator character. For example, there are lots of key value parser, if the equals sign
=
is uses as separating character, e.g. packagekvsetkeys
.
As already explained, the problem is that @
has category code 11 at definition time, but category code 12 at usage time.
Here's an implementation with xparse
; the \SplitArgument
processor pushes -NoValue-
when the argument hasn't the indicated number of tokens to split at, so it's necessary to use \IfValueT
in order to print the second part when existing.
\documentclass{article}
\usepackage{xparse}
\NewDocumentCommand{\fun}{>{\SplitList{,}}O{a@b,c@d,e@f,g,}}{%
\ProcessList{#1}{\explode}%
}
\NewDocumentCommand{\explode}{>{\SplitArgument{1}{@}}m}{%
\doexplosion#1, %
}
\NewDocumentCommand{\doexplosion}{mm}{%
1:#1-\IfValueT{#2}{#2}% No @ pushes -NoValue-
}
\begin{document}
\fun
\fun[a@b,c@d,e@f,g,]
\end{document}