如何使 xkeyval 接受包含 \par 和逗号的值

如何使 xkeyval 接受包含 \par 和逗号的值

我正在编写一个包,理想情况下,我希望使用 的键值机制来保存大段代码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在补丁之前是必需的。我会再测试一下,但如果有人发现我的补丁有问题或更好的解决方案,请告诉我!

相关内容