具有可选参数的 Xparse 宏会破坏 PGF foreach 的值

具有可选参数的 Xparse 宏会破坏 PGF foreach 的值

我编写了一个文档类,并希望根据用户定义的数量执行某些操作。因此,我认为应该使用foreachPGF 中的循环以及访问器宏:

\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\usepackage{xparse,etoolbox,pgffor}

\makeatletter
\csdef{my@test}{3}

\DeclareDocumentCommand{\val}{m g}{%
  \csname #1@#2\endcsname%
}
\makeatother

\begin{document}
  Testing direct: \csname my@test\endcsname

  Testing macro: \val{my}{test}

  \foreach \i in {1,...,\csname my@test\endcsname}{a}

  \foreach \i in {1,...,\val{my}{test}}{a}
\end{document}

不幸的是,使用valbreak foreach

在此处输入图片描述

出现错误消息Illegal unit of measure (pt inserted)(多次)。

有趣的是:如果我使用参数规范{m m}而不是{m g},问题就会消失。

在类定义中不使用宏是可行的——但是我确实需要可选参数——但是这里发生了什么?

背景

X用户可以使用以下形式的宏来创建 s 列表

\addX{group}{name}{key1=val1,key2=val2,..} 

xparse此宏由和实现keyval。它将信息存储在 形式的宏中\X@group@name@key。为了向用户隐藏此信息,我提供了类似val上述的访问器宏。还有一些宏和环境直接利用创建的列表,其中一个将包含如上定义的循环,X根据用户提供的值重复执行(针对每个 )操作。

的最后一个参数val是可选的,这样如果用户不提供键(我认为这将是最常请求的值),则可以返回默认值。

答案1

这似乎是一个可扩展性问题。似乎xparse定义带有可选参数的函数的方式阻止了\foreach在其域中看到最终值。特别是,\foreach无法完全扩展宏的值,因为xparse创建g类型参数的方法阻止了完全扩展。

即使可选参数位于强制参数之前,您也会遇到类似的错误:

\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\usepackage{xparse,etoolbox,pgffor}

\makeatletter
\csdef{my@test}{3}

\DeclareDocumentCommand{\val}{o m}{%
  \csname #1@#2\endcsname%
}
\makeatother

\begin{document}

  Testing direct: \csname my@test\endcsname

  Testing macro: \val[my]{test}

  \foreach \i in {1,...,\csname my@test\endcsname}{a}

  \foreach \i in {1,...,\val[my]{test} }{a}

\end{document}

在文档中,xparse您可以创建具有可选参数的完全可扩展函数,但存在各种限制:

  • 可选参数可能不是以下类型g

  • 最后一个参数可能不是可选参数。

您可以编写以下内容

\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\usepackage{xparse,etoolbox,pgffor}

\makeatletter
\csdef{my@test}{3}

\DeclareExpandableDocumentCommand{\val}{o m}{%
  \csname #1@#2\endcsname%
}
\makeatother

\begin{document}

  Testing direct: \csname my@test\endcsname

  Testing macro: \val[my]{test}

  \foreach \i in {1,...,\csname my@test\endcsname}{a}

  \foreach \i in {1,...,\val[my]{test} }{a}

\end{document}

通过一些小技巧,你可以部分了解扩张方面正在发生的事情

\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}

\usepackage{xparse,etoolbox,pgffor}

\makeatletter
\csdef{my@test}{3}

%% an expandable version of your macro
\DeclareExpandableDocumentCommand{\eval}{o m}{%
  \csname #1@#2\endcsname%
}

%% a non-expandable version of your macro
\DeclareDocumentCommand{\val}{o m}{%
  \csname #1@#2\endcsname%
}
\makeatother
\setlength{\parskip}{2ex}
\begin{document}


 \makebox[1in][r]{Full expanded}: 
    \begin{minipage}[t]{3in}\ttfamily
      \detokenize\expandafter{\romannumeral-`G\eval[my]{test}}
    \end{minipage}

  \makebox[1in][r]{Expanded once}: 
    \begin{minipage}[t]{3in}\ttfamily
      \detokenize\expandafter{\eval[my]{test}}
    \end{minipage}

  \makebox[1in][r]{Not fully expandable}:
    \begin{minipage}[t]{3in}\ttfamily
      \detokenize\expandafter{\romannumeral-`G\val[my]{test}}
    \end{minipage}
\end{document}

在此处输入图片描述

也许如果您阐明这个宏如何运行,我们就能想出一个更能满足您需求的解决方案。

更新

您可以使两个参数都成为必需的,但要测试第二个参数是否传递了空参数。

代码如下:

\csdef{my@default}{5}

\DeclareDocumentCommand{\eval}{ mm }{%%
  \expandafter\ifx\expandafter\relax\detokenize{#2}\relax
    \csname #1@default\endcsname
  \else
    \csname #1@#2\endcsname
  \fi
}

然后你可以写类似

  \foreach \i in {1,...,\eval{my}{}}{D}

  \foreach \i in {1,...,\eval{my}{test}}{c}  

不过我应该指出,\eval{my}{ }对于一个空洞的论点来说,这是不正确的。

相关内容