使用字符串使用 listofitems 处理宏名称似乎失败

使用字符串使用 listofitems 处理宏名称似乎失败

我正在尝试使用 处理命令的名称listofitems,但是当我使用它\string来转换该命令时,它的行为并不像我想象的那样。

下面是一段代码来说明:

\documentclass[varwidth]{standalone}
\usepackage{xstring}
\usepackage{listofitems}
\makeatletter\newcommand{\currentname}{}

% Process ABC@DEF
\newcommand{\makelistA}[1]{
    \setsepchar{@}%
    \renewcommand{\currentname}{#1}%
    \readlist\currentlist{\currentname}%
    \currentname = [\currentlist[1],\currentlist[2]]\newline%
}
\newcommand{\runA}{\makelistA{ABC@DEF}}

% Process the command \ABC@DEF
\newcommand{\ABC@DEF}{}
\newcommand{\makelistB}[1]{
    \setsepchar{@}%
    \renewcommand{\currentname}{\StrGobbleLeft{\string#1}{1}}%
    \readlist\currentlist{\currentname}%
    \currentname = [\currentlist[1],\currentlist[2]]\newline%
}
\newcommand{\runB}{\makelistB{\ABC@DEF}}

\makeatother
\begin{document}
\runA % Writes ABC@DEF = [ABC,DEF] as expected
\runB % Writes ABC@DEF = [ABC@DEF,ABC@DEF] and generates errors
\end{document}

处理命令的版本失败了,我不明白为什么。

有什么解释吗?如何让它工作?

答案1

有几个问题。首先,

\renewcommand{\currentname}{\StrGobbleLeft{\string#1}{1}}

永远不会起作用,因为\StrGobbleLeft不可扩展。你想要

\StrGobbleLeft{\string#1}{1}[\currentname]

它将正确存储ABC@DEF在 中\currentname。但是,listofitems找不到,因为您在具有类别代码 11 的@上下文中定义命令,但在生成的字符串中它具有类别代码 12。@

你可能想要

\newcommand{\makelistB}[1]{%
    \expandafter\setsepchar\expandafter{\string @}%
    \StrGobbleLeft{\string#1}{1}[\currentname]%
    \readlist\currentlist{\currentname}%
    \currentname = [\currentlist[1],\currentlist[2]]\newline
}

因此分隔符@肯定具有类别代码 12。

这是expl3没有被类别代码混淆的代码。

\documentclass{article}


\ExplSyntaxOn

\NewDocumentCommand{\makelist}{m}
 {
  \tl_set:Nx \l_tmpa_tl { \cs_to_str:N #1 }
  \regex_split:nVN { \@ } \l_tmpa_tl \l_tmpa_seq
  [\seq_use:Nn \l_tmpa_seq {,}]
 }
\cs_generate_variant:Nn \regex_split:nnN { nV }

\ExplSyntaxOff

\begin{document}

\makeatletter % pretend we're in a package file

\newcommand{\ABC@DEF}

\makelist{\ABC@DEF}

\makeatother

\end{document}

您可以检查输出为

[ABC,DEF]

答案2

代码有两个问题。首先,xstring不应该使用 来存储宏的输出\renewcommand。这会将 的整个代码存储\StrGobbleLeft在命令中,而您只需要结果。使用xstring可选的最终参数可以完成此操作:

\StrGobbleLeft{\string#1}{1}[\currentname]

第二个问题是类别代码问题。您使用\makeatletter,将的类别代码设置为@11(即字母,请参阅类别代码是什么?)。但是,\string(并且\detokenize)将的类别代码设置为@12(即其他)。现在listofitems无法拆分列表,因为它正在寻找带有 catcode 11 的分隔符,而您提供的分隔符带有 catcode 12,并且标记比较是 catcode 感知的。

解决办法很简单:不要制造单一字母,而是制造其他字母。

\documentclass[varwidth]{standalone}
\usepackage{xstring}
\usepackage{listofitems}

\makeatletter
\newcommand{\currentname}{}

% Process ABC@DEF
\newcommand{\makelistA}[1]{
    \setsepchar{@}%
    \renewcommand{\currentname}{#1}%
    \readlist\currentlist{\currentname}%
    \currentname = [\currentlist[1],\currentlist[2]]\newline%
}
\newcommand{\runA}{\makelistA{ABC@DEF}}

% Process the command \ABC@DEF
\newcommand{\ABC@DEF}{}
\makeatother
\newcommand{\makelistB}[1]{
    \setsepchar{@}%
    \StrGobbleLeft{\string#1}{1}[\currentname]
    \readlist\currentlist{\currentname}%
    \currentname = [\currentlist[1],\currentlist[2]]\newline%
}
\makeatletter
\newcommand{\runB}{\makelistB{\ABC@DEF}}

\makeatother
\begin{document}
\runA % Writes ABC@DEF = [ABC,DEF] as expected
\runB % Same output as above
\end{document}

结果:

在此处输入图片描述

注意必须进行 catcode 更改外部使\newcommand{\makelistB}更改在命令定义期间生效,而不是在执行期间生效,这已经太晚了,因为符号@在此时已经被标记化了。


一个可能的改进是listofitems使分隔符比较与 catcode 无关,类似于xstring提供带星号的变体,\IfStrEq*首先规范化 catcode,然后执行比较。

相关内容