我正在尝试使用 处理命令的名称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,然后执行比较。