我正在编写一个包,理想情况下,我希望使用 的键值机制来保存大段代码xkeyval
。也就是说,我希望能够说类似
\setkeys{foo}{mytext={This is some long text,
including multiple paragraphs.
Here is the second paragraph.}}
并让mytext
密钥进行一些处理或操作。当值包含\par
标记时,我没有遇到任何问题,当它包含逗号时,我也没有遇到任何问题,但当它同时包含两者时,我会收到一条错误消息。这是一个最小(非)工作示例:
\documentclass{minimal}
\usepackage{xkeyval}
\makeatletter
\define@key{ABC}{mykey}{\long\def\mymacro{#1}}
\begin{document}
%\setkeys{ABC}{mykey={DE,F}} % This line works.
%\setkeys{ABC}{mykey={D\par EF}} % This line works.
\setkeys{ABC}{mykey={D\par E,F}} % This line doesn't work.
\mymacro
\end{document}
它会产生错误消息
Runaway argument?
{mykey={D
! Paragraph ended before \@s@l@ctive@sanitize was complete.
<to be read again>
\par
l.11 \setkeys{ABC}{mykey={D\par E,F}}
?
我猜测,当xkeyval
在键的值中看到逗号时,它会调用未定义为的清理宏long
;快速浏览一下xkvutils.tex
似乎证实了我的怀疑。
有没有办法让xkeyval
键值对同时包含\par
和逗号?
答案1
由于xkeyval
不扩展键值,因此可以将“违规”部分存储D\par E,F
到宏中,例如\myarg
。这样就可以了。
\documentclass{minimal}
\usepackage{xkeyval}
\makeatletter
\define@key{ABC}{mykey}{\long\def\mymacro{#1}}
\newcommand{\myarg}{D\par E,F}
\begin{document}
%\setkeys{ABC}{mykey={DE,F}} % This line works.
%\setkeys{ABC}{mykey={D\par EF}} % This line works.
%\setkeys{ABC}{mykey={{D\protect\par E},F}} % This line doesn't work.
\setkeys{ABC}{mykey={\myarg}}
\mymacro
\end{document}
答案2
Christian Hupfer 的上述评论帮助我明确了自己的想法,我真正想要的是xkeyval
按照自己想要的方式工作,接受 a\par
和逗号,而无需最终用户或包作者进行任何额外干预。在花了一周时间试图在空闲时间理解代码后xkvutils.tex
,我想出了以下代码来重新定义一些宏xkeyval
:
\documentclass{minimal}
\usepackage{xkeyval}
\makeatletter
\long\def\@s@lective@sanitize[#1]#2#3{%
\begingroup
\count@#1\relax\advance\count@\@ne
\XKV@toks\expandafter{#3}%
\def#3{#2}\@onelevel@sanitize#3%
\edef#3{{#3}{\the\XKV@toks}}%
\expandafter\@s@l@ctive@sanitize\expandafter#3#3%
\expandafter\XKV@tempa@toks\expandafter{#3}%
\expandafter\endgroup\expandafter\toks@\expandafter{\the\XKV@tempa@toks}%
\edef#3{\the\toks@}%
}
\long\def\@s@l@ctive@sanitize#1#2#3{%
\def\@i{\futurelet\@@tok\@ii}%
\def\@ii{%
\ifx\@@tok\@s@l@ctive@sanitize
\let\@@cmd\@gobble
\else
\expandafter\@iii\meaning\@@tok\relax
\ifx\@@tok\@sptoken
\XKV@toks\expandafter{#1}\edef#1{\the\XKV@toks\space}%
\def\@@cmd{\afterassignment\@i\let\@@tok= }%
\else
\let\@@cmd\@iv
\fi
\fi
\@@cmd
}%
\def\@iii##1##2\relax{\if##1\@backslashchar\let\@@tok\relax\fi}%
\long\def\@iv##1{%
\toks@\expandafter{#1}\XKV@toks{##1}%
\ifx\@@tok\bgroup
\advance\count@\m@ne
\ifnum\count@>\z@
\begingroup
\def#1{\expandafter\@s@l@ctive@sanitize
\csname\string#1\endcsname{#2}}%
\expandafter#1\expandafter{\the\XKV@toks}%
\XKV@toks\expandafter\expandafter\expandafter
{\csname\string#1\endcsname}%
\edef#1{\noexpand\XKV@toks{\the\XKV@toks}}%
\expandafter\endgroup#1%
\fi
\edef#1{\the\toks@{\the\XKV@toks}}%
\advance\count@\@ne
\let\@@cmd\@i
\else
\edef#1{\expandafter\string\the\XKV@toks}%
\expandafter\in@\expandafter{#1}{#2}%
\edef#1{\the\toks@\ifin@#1\else
\ifx\@@tok\@sptoken\space\else\the\XKV@toks\fi\fi}%
\edef\@@cmd{\noexpand\@i\ifx\@@tok\@sptoken\the\XKV@toks\fi}%
\fi
\@@cmd
}%
\let#1\@empty\@i#3\@s@l@ctive@sanitize
}
\define@key{ABC}{mykey}{\long\def\mymacro{#1}}
\begin{document}
%\setkeys{ABC}{mykey={DE,F}} % This line works.
%\setkeys{ABC}{mykey={D\par EF}} % This line works.
\setkeys{ABC}{mykey={D\par E,F}} % This line now works.
\mymacro
%It can still handle conditionals.
\setkeys{ABC}{mykey={\ifnum1<2\relax True \else False\fi}}
{\tt\meaning\mymacro}
\end{document}
唯一的变化是\@s@lective@sanitize
将其变成\long
宏。内部宏\@s@l@ctive@sanitize
需要做更多工作。如果有人对细节感兴趣,这里是原始代码以供比较:
\def\@s@l@ctive@sanitize#1#2#3{%
\def\@i{\futurelet\@@tok\@ii}%
\def\@ii{%
\expandafter\@iii\meaning\@@tok\relax
\ifx\@@tok\@s@l@ctive@sanitize
\let\@@cmd\@gobble
\else
\ifx\@@tok\@sptoken
\XKV@toks\expandafter{#1}\edef#1{\the\XKV@toks\space}%
\def\@@cmd{\afterassignment\@i\let\@@tok= }%
\else
\let\@@cmd\@iv
\fi
\fi
\@@cmd
}%
\def\@iii##1##2\relax{\if##1\@backslashchar\let\@@tok\relax\fi}%
\def\@iv##1{%
\toks@\expandafter{#1}\XKV@toks{##1}%
\ifx\@@tok\bgroup
\tracingmacros=1\relax
\advance\count@\m@ne
\ifnum\count@>\z@
\begingroup
\def#1{\expandafter\@s@l@ctive@sanitize
\csname\string#1\endcsname{#2}}%
\expandafter#1\expandafter{\the\XKV@toks}%
\XKV@toks\expandafter\expandafter\expandafter
{\csname\string#1\endcsname}%
\edef#1{\noexpand\XKV@toks{\the\XKV@toks}}%
\expandafter\endgroup#1%
\fi
\edef#1{\the\toks@{\the\XKV@toks}}%
\advance\count@\@ne
\let\@@cmd\@i
\else
\edef#1{\expandafter\string\the\XKV@toks}%
\expandafter\in@\expandafter{#1}{#2}%
\edef#1{\the\toks@\ifin@#1\else
\ifx\@@tok\@sptoken\space\else\the\XKV@toks\fi\fi}%
\edef\@@cmd{\noexpand\@i\ifx\@@tok\@sptoken\the\XKV@toks\fi}%
\fi
\@@cmd
}%
\let#1\@empty\@i#3\@s@l@ctive@sanitize
}
当然\@s@l@ctive@sanitize
需要自己制作\long
,但是它定义了另一个宏\@iv
,该宏也读取要清理的标记,因此\@iv
也需要制作\long
。
这些改变很有意义,但现在出现了一个新问题:递归从未停止。宏使用自身\@s@l@ctive@sanitize
作为分隔符来让它知道何时停止。首先\@i
使用\futurelet
来保存中的下一个标记\@@tok
,然后\@ii
接管。然后\@ii
检查下一个标记是否为 TeX 基元,使用询问其是否以反斜杠开头\@iii
的机制;如果是,则将其设置为等于。(这里的目标似乎是防止条件语句(如、和)因在内部扩展而引起麻烦。)完成后,再次接管,检查(a)是否是位于要清理的标记末尾的,在这种情况下我们通过简单地对该标记执行 -ing 操作来停止递归;(b)是否是空格标记,这需要特殊处理;或者(c)是其他任何东西,在这种情况下它会被添加到已清理列表中。\meaning
\@@tok
\relax
\if
\else
\fi
\@iv
\@iii
\@ii
\@@tok
\@s@l@ctive@sanitize
\@gobble
\@s@l@ctive@sanitize
\@@tok
\@@tok
\@iv
因此,新的问题是,当我们制作\@s@l@ctive@sanitize
长时,它\meaning
已经从
macro: #1#2#3->\def \@i {\futurelet ...
到
\long macro: #1#2#3->\def \@i {\futurelet ...
现在,当我们到达要清理的标记序列的末尾时,当\@@tok
等于\let
时\@s@l@ctive@sanitize
,\@iii
测试 的含义\@@tok
并看到 的反斜杠\long
,认为它已经找到了需要防范的 TeX 基元,并将 设置\@@tok
为 相等\relax
。因此,当\@ii
运行时\ifx\@@tok\@s@l@ctive@sanitize
,条件评估为假,并且递归永不结束。
我在上面的补丁中修复了这个问题\@ii
首先测试了递归结束标记,前检查\@iii
反斜杠。
我有点担心这是否会破坏任何\@iii
应该防范的东西,但到目前为止,只要密钥中的条件是平衡的,我的新代码似乎就可以工作,而这xkeyval
在补丁之前是必需的。我会再测试一下,但如果有人发现我的补丁有问题或更好的解决方案,请告诉我!