为了简化程序包的编程,我想用单个命令生成多个类似的命令( \test
、 、...) 。每个键都需要与唯一的命令名称链接。该名称可以使用 自动生成。MWE 中的代码运行良好(灵感来自\testb
\NEWCOMMAND
\csname
这个问题),但对于没有值的键则不行:[key]
。由于\csname
在 之前使用了 ,\edef
值\ifdefstring
变为空:[key = {}]
。因此\pgfkeysnovalue
存储在 中\key@test
,但不存在于 中\edef
。对于\testb
命令应该存储在 中\key@testb
。
\pgfkeysnovalue
这种情况下怎样才能检测出来?
备注1:\ifdefstring{\key@test}{\pgfkeysnovalue}
仅用于显示\key@test
正确定义为\pgfkeysnovalue
内部\test
。最终代码中不需要它。
备注2\log
:最后添加了手动编程命令(例如)的期望行为示例。当使用单个命令自动编程命令时,此行为应该相同\NEWCOMMAND
。例如\sin
,\cos
和的括号分隔控件\tan
。只有\sin[par]
应激活此选项,其他符号会停用它:\sin[par = {}]
或\sin[par = false]
。此设置可能不会影响\cos
或\tan
。
平均能量损失
\documentclass[varwidth = 10cm, border = 5pt]{standalone}
\usepackage{pgfkeys}
\usepackage{etoolbox}
% https://tex.stackexchange.com/questions/500098/pgfkeys-store-in-constructed-macro
\pgfkeys{/handlers/.store in cs/.code=\pgfkeysalso{%
\pgfkeyscurrentpath/.code=\expandafter\def\csname#1\endcsname{##1}}%
}
\makeatletter
\NewDocumentCommand{\NEWCOMMAND}{m m}{
% Pgfkeys
\pgfkeys{/#2/.cd,
key/.store in cs = key@#2,
key = {}
}
\newcommand{#1}[1]{%
\begingroup%
\pgfkeys{/#2/.cd, ##1}%
\edef\tempi{\csname key@#2\endcsname}%
text: \ifdefstring{\tempi}{text}{YES}{NO} + %
no value: \ifdefstring{\tempi}{\pgfkeysnovalue}{YES}{NO} - %
\ifdefstring{\key@test}{\pgfkeysnovalue}{YES}{NO}% Only to check if \key@test is correctly defined as \pgfkeysnovalue inside \test
\endgroup%
}%
}
\makeatother
\NEWCOMMAND{\test}{test}
\NEWCOMMAND{\testb}{testb}
\begin{document}
\textbf{test}
\verb|key = text|
\test{key = text}% => YES, NO, NO: true
\verb|key|
\test{key}% => NO, YES, YES: false (once)
\bigskip
\textbf{testb}
\verb|key = text|
\testb{key = text}% => YES, NO, NO: true
\verb|key|
\testb{key}% => NO, YES, NO: false (once)
\end{document}
期望行为的示例
\documentclass[varwidth = 10cm, border = 5pt]{standalone}
\usepackage{pgfkeys}
\usepackage{tikz}
\usepackage{etoolbox}
\usepackage{mleftright}
\usepackage{amsmath}
%# Logarithm
\makeatletter
% Pgfkeys
\pgfkeys{/logM/.cd,
%
base/.store in = \base@logM,
base = {},
%
par/.store in = \par@logM,
par = {},
%
base front/.store in = \basefront@logM,
base front = {},
%
tikzset/.initial = {},
}
% Command
\RenewDocumentCommand{\log}{O{} m}{
\begingroup
\pgfkeys{/logM/.cd, tikzset, #1}
%
\ifdefstring{\par@logM}{\pgfkeysnovalue}{%
\def\temp@logM{\mleft ( #2 \mright)}
}{%
\def\temp@logM{#2}
}
%
\ifdefstring{\base@logM}{}{%
\operatorname{log}\temp@logM
}{%
\ifdefstring{\basefront@logM}{\pgfkeysnovalue}{%
\mathop{\displaystyle{}\textsuperscript{\base@logM}\mathrm{log}}\temp@logM
}{
\mathop{\displaystyle{}\mathrm{log}\textsubscript{\base@logM}}\temp@logM
}
}
\endgroup
}
\makeatother
\NewDocumentCommand{\mathset}{m O{} m}{
\ifnum\pdfstrcmp{#2}{} = 0
\tikzset{/#1M/.cd, tikzset = {\pgfkeys{/#1M/.cd, #3}}}
\else
\tikzset{/#1M/.cd, #2/.initial = {}, #2 = {\pgfkeys{/#1M/.cd, #3}}}
\fi
}
\begin{document}
\mathset{log}{base = a}
$\log{x}$
$\log[par]{\log[par = {}]{x}}$% or \log[par = false]{x} => no parentheses for second log
$\log[base front, base = b]{x}$
\end{document}
答案1
除了构建一些测试来检查是否有值(无论是通过pgfkeys
's\pgfkeysnovalue
还是 L3's \c_novalue_tl
),您还可以使用 key=value 解析器,它允许您为获得值的键和没有值的键定义不同的操作。基本上expkv
有两个单独的名称空间,一个用于获得值的键,一个用于没有值的键,您可以为这两个键定义不同的行为,即使在用户级别它们共享相同的名称。
以下定义了 NoVal-key,\@firstoftwo
当使用时将内部设置为(并且将存储 cs 的值为空),而 Val-key 将内部设置为\@secondoftwo
并存储该值。
否则,这几乎就是@cfr 的答案中发布的 MWE。
\documentclass[varwidth=10cm, border=3.14]{standalone}
\usepackage{expkv-def}
\makeatletter
\ExplSyntaxOn
\cs_new_eq:NN \sam@ifeq \tl_if_eq:NnTF
\ExplSyntaxOff
\protected\long\def\NEWCOMMAND@#1#2#3#4%
{%
% #1: \csname sam@key@value@@#3\endcsname
% #2: \csname sam@key@ifnoval@@#3\endcsname
% #3: name of NEWCOMMAND
% #4: set of NEWCOMMAND
\ekvdefinekeys{sam/#4}%
{
% how the key should behave with a value
store key = #1
,also code key = \let#2\@secondoftwo
% how the key should behave without a value
,protect noval key = \let#2\@firstoftwo\let#1\@empty
}%
\NewDocumentCommand #3 { m }
{%
\begingroup
\ekvset{sam/#4}{##1}%
Text: #2{N}{\sam@ifeq#1{text}{Y}{N}}\par
no value: #2{Y}{N}\par
\endgroup
}%
}
\NewDocumentCommand \NEWCOMMAND { m m }
{%
\expandafter\NEWCOMMAND@
\csname sam@key@value@@#2\expandafter\endcsname
\csname sam@key@ifnoval@@#2\endcsname
{#1}%
{#2}%
}
\NEWCOMMAND{\test}{test}
\NEWCOMMAND{\testb}{testb}
\begin{document}
\textbf{test}
\verb|key = text|
\test{key=text}% => Y N or N
\verb|key|
\test{key}% => N Y or Y
\bigskip
\textbf{testb}
\verb|key = text|
\testb{key = text}% => Y N or N
\verb|key|
\testb{key}% => N Y or Y
\end{document}
另一种方法是使用expkv-cs
一组内部密钥和一层用户级密钥,这些密钥将它们包装起来,以便所有\NEWCOMMAND
s 共享相同的密钥。这里提供密钥par
、base
和basefront
。
请注意,所示方法对于许多键可能不切实际(在此示例中,在必须将输出拆分为两个宏之前,还有 3 个内部键的空间)。但基本上可以通过将值存储在宏中(例如通过expkv-def
上述代码片段)或使用 来expkv-cs
实现相同的效果\ekvcHashAndForward
(请参阅 中的文档expkv-bundle.pdf
)。
另外,截至撰写本文时,的当前版本(expkv-cs
v1.3 2023-01-23)的类型有限,choice
这就是为什么我们使用enum
和数值来表示和true
(即将发布的版本将为键提供一种机制,允许转发宏而不仅仅是用户输入)。false
basefront
par
choice
\documentclass[varwidth=10cm, border=3.14]{standalone}
\usepackage{amsmath,mleftright}
\usepackage{expkv-cs}
\makeatletter
\providecommand\@thirdofthree[3]{#3}
\newcommand\sam@numboolTF[1]{\if0#1\expandafter\@thirdofthree\fi\@firstoftwo}
\ekvcSplitAndForward\sam@kv\sam@output
{
%
basefront|internal = 0 % #1
,par|internal = 0 % #2
,base|noval = \@secondoftwo % #3
,base|value = {} % #4
}
\ekvcSecondaryKeys\sam@kv
{
meta base = { base|noval = \@secondoftwo, base|value = {#1} }
,nmeta base = { base|noval = \@firstoftwo, base|value = {} }
,nmeta basefront = {basefront|internal = 1}
,enum basefront = {basefront|internal}{false, true}
,nmeta par = { par|internal = 1 }
,enum par = {par|internal}{false, true}
}
\NewDocumentCommand\mathset{m m}
{%
\@ifundefined{sam@defaults@@#1}%
{\PackageError{sam}{Unknown math set #1}{}}%
{\expandafter\edef\csname sam@defaults@@#1\endcsname{\unexpanded{#2}}}%
}
\NewDocumentCommand\NEWCOMMAND{m m}
{%
\expandafter\NEWCOMMAND@\csname sam@defaults@@#2\endcsname{#1}{#2}%
}
\protected\def\NEWCOMMAND@#1#2#3%
{%
\NewDocumentCommand #2 { O{} m }
{\expandafter\sam@kv\expandafter{#1,##1}{#3}{##2}}%
\expandafter\let\csname sam@defaults@@#3\endcsname\@empty
}
\newcommand\sam@output[6]
{%
\mathop
{%
\sam@numboolTF{#1}{#3{}{{}^{#4}}}{}%
{\operatorname{#5}}%
\sam@numboolTF{#1}{}{#3{}{_{#4}}}%
}%
\sam@numboolTF{#2}{\mleft(}{}%
#6%
\sam@numboolTF{#2}{\mright)}{}%
}
\makeatother
\NEWCOMMAND\Log{log}
\NEWCOMMAND\Tan{tan}
\NEWCOMMAND\Cos{cos}
\mathset{log}{base=a, basefront, par}
\mathset{cos}{base=b}
\begin{document}
$\Log{5} \cdot \Tan{\alpha} \cdot \Cos{\beta}$
$\Log[base]{5} \cdot \Tan[base=t]{\alpha} \cdot \Cos[basefront=true]{\beta}$
\end{document}
答案2
如果您确实需要区分key={}
和key
,我不会使用pgfkeys
。l3keys
提供了各种现成的项目,您可以使用它们来区分此类情况。特别\c_novalue_tl
是与和的构造不同,如果进行比较\c_empty_tl
,甚至与其排版结果也不同-NoValue-
。此外,\tl_if_novalue:nTF
专门设计用于启用需要检测缺少可选参数的用户界面。
\documentclass[varwidth = 10cm, border = 5pt]{standalone}
% ateb: https://tex.stackexchange.com/a/714697/
\ExplSyntaxOn
\cs_generate_variant:Nn \tl_if_novalue:nTF { v }
\NewDocumentCommand \NEWCOMMAND { m m }{
\keys_define:nn { sam / #2 }
{
key .tl_set:c = { l__sam_key_#2_tl },
key .default:V = \c_novalue_tl,
key .initial:V = \c_empty_tl,
}
\NewDocumentCommand #1 { m }
{
\group_begin:
\keys_set:nn { sam / #2 } { ##1 }
Text: ~ \tl_if_eq:cnTF { l__sam_key_#2_tl } { text } { Y } { N }
~ + ~ no ~ value: ~ \tl_if_eq:cNTF { l__sam_key_#2_tl } \c_novalue_tl { Y } { N }
\ ~ or ~ \tl_if_novalue:vTF { l__sam_key_#2_tl } { Y } { N }
\group_end:
}
}
\ExplSyntaxOff
\NEWCOMMAND{\test}{test}
\NEWCOMMAND{\testb}{testb}
\begin{document}
\textbf{test}
\verb|key = text|
\test{key=text}% => Y N or N
\verb|key|
\test{key}% => N Y or Y
\bigskip
\textbf{testb}
\verb|key = text|
\testb{key = text}% => Y N or N
\verb|key|
\testb{key}% => N Y or Y
\end{document}
也就是说,我会采用将责任推卸给 LaTeX 3 来解决扩展问题的方法。
回答原始问题并编辑 I
也许这有点帮助。如果我理解正确,并结合评论中的讨论,这将给出预期的结果。
我过去常常.store in/.expand once=\csname ... \endcsname
避免定义.store in cs
。但是,当然,如果您愿意,您可以使用自定义处理程序。我只是在这种情况下看不到好处,因为无论如何你都会把它包装在定义中。
除了测试\pgfkeysnovalue
,您还可以提供一个足够不可能的默认值并对其进行测试。如果您想测试\pgfkeysnovalue
,则需要\key@#2
在定义时避免完全展开\tempi
,或者测试 的完整展开\pgfkeysnovalue
或使用不同的条件测试。通过使用一些默认值,您实际上有一个要测试的字符串。
\documentclass[varwidth = 10cm, border = 5pt]{standalone}
% ateb: https://tex.stackexchange.com/a/714697/
\usepackage{pgfkeys}
\usepackage{etoolbox}
\makeatletter
\NewDocumentCommand{\NEWCOMMAND}{m m}{%
% Pgfkeys
\pgfkeys{/#2/.cd,
key/.store in/.expand once = \csname key@#2\endcsname,
key = {},
key/.default=XXX,
}%
\newcommand{#1}[1]{%
\begingroup
\pgfkeys{/#2/.cd, ##1}%
\edef\tempi{\csname key@#2\endcsname}%
text: \ifdefstring{\tempi}{text}{YES}{NO} + %
no value: \ifdefstring{\tempi}{XXX}{YES}{NO} - %
\ifdefstring{\key@test}{XXX}{YES}{NO}%
\endgroup
}%
}
\makeatother
\NEWCOMMAND{\test}{test}
\NEWCOMMAND{\testb}{testb}
\begin{document}
\textbf{test}
\verb|key = text|
\test{key = text}% => YES, NO, NO:
\verb|key|
\test{key}% => NO, YES, YES:
\bigskip
\textbf{testb}
\verb|key = text|
\testb{key = text}% => YES, NO, NO:
\verb|key|
\testb{key}% => NO, YES, NO:
\end{document}
图像现在对我来说是一个非常烦人的问题,但我得到了上面注释的输出(没有问号等)。
如果你确实需要测试\pgfkeysnovalue
,你需要一种不同的方法。例如,
etoolbox
提供替代条件测试。expl3
现在为的大多数功能提供了更优秀的替代方案etoolbox
,但学习曲线更陡峭。\edef\tempj{\pgfkeysnovalue}
例如,可以使用老式的路线进行测试\ifx\tempi\tempj ... \else ... \fi
。(如果您需要区分空值和根本没有值,那就不好了。)
编辑
如果您不需要对 进行最终测试(这很尴尬,因为它会扩展为与使用或时\key@test
相同),您可以通过简单地对空字符串进行测试来处理无值的测试。例如,\pgfkeysnovalue
\testb{key}
\testb{key=<value>}
\documentclass[varwidth = 10cm, border = 5pt]{standalone}
% ateb: https://tex.stackexchange.com/a/714697/
\usepackage{pgfkeys}
\usepackage{etoolbox}
\makeatletter
\NewDocumentCommand{\NEWCOMMAND}{m m}{%
% Pgfkeys
\pgfkeys{/#2/.cd,
key/.store in/.expand once = \csname key@#2\endcsname,
key = {},
}%
\newcommand{#1}[1]{%
\begingroup
\pgfkeys{/#2/.cd, ##1}%
\edef\tempi{\csname key@#2\endcsname}%
text: \ifdefstring{\tempi}{text}{YES}{NO} + %
no value: \ifdefstring{\tempi}{}{YES}{NO} - %
\endgroup
}%
}
\makeatother
\NEWCOMMAND{\test}{test}
\NEWCOMMAND{\testb}{testb}
\begin{document}
\textbf{test}
\verb|key = text|
\test{key = text}% => YES, NO,
\verb|key|
\test{key}% => NO, YES,
\bigskip
\textbf{testb}
\verb|key = text|
\testb{key = text}% => YES, NO,
\verb|key|
\testb{key}% => NO, YES,
\end{document}
请注意,每次调用时,您都会创建一个全局默认值\NEWCOMMAND
,或者用术语来说pgfkeys
,您正在设置初始值,无论您是否使用.initial
。因为
key = {},
与不设置值不同。就目前而言pgfkeys
,这非常接近,因为\pgfkeysnovalue
扩展为相同的东西。但是,在 TeX 术语中,这并不相同,因为立即扩展为零的宏与立即扩展为另一个最终扩展为零的宏的宏不同。我认为这不是您的情况的问题,所以只是需要注意的事情。
答案3
这里,我只是改变了测试:
\ifdefstring{\tempi}{\pgfkeysnovalue}{YES}{NO}
更直接一点,看看是否\tempi
为空。
\ifx\tempi\empty YES \else NO \fi
以下是 MWE:
\documentclass[varwidth = 10cm, border = 5pt]{standalone}
\usepackage{pgfkeys}
\usepackage{etoolbox}
% https://tex.stackexchange.com/questions/500098/pgfkeys-store-in-constructed-macro
\pgfkeys{/handlers/.store in cs/.code=\pgfkeysalso{%
\pgfkeyscurrentpath/.code=\expandafter\def\csname#1\endcsname{##1}}%
}
\makeatletter
\NewDocumentCommand{\NEWCOMMAND}{m m}{
% Pgfkeys
\pgfkeys{/#2/.cd,
key/.store in cs = key@#2,
key = {}
}
\newcommand{#1}[1]{%
\begingroup%
\pgfkeys{/#2/.cd, ##1}%
\edef\tempi{\csname key@#2\endcsname}%
text: \ifdefstring{\tempi}{text}{YES}{NO} + %
no value:
%REPLACE
%\ifdefstring{\tempi}{\pgfkeysnovalue}{YES}{NO} - %
% WITH:
\ifx\tempi\empty YES \else NO \fi - %
%
\ifdefstring{\key@test}{\pgfkeysnovalue}{YES}{NO}% Only to check if \key@test is correctly defined as \pgfkeysnovalue inside \test
\endgroup%
}%
}
\makeatother
\NEWCOMMAND{\test}{test}
\NEWCOMMAND{\testb}{testb}
\begin{document}
\textbf{test}
\verb|key = text|
\test{key = text}% => YES, NO, NO: true
\verb|key|
\test{key}% => NO, YES, YES: false (once)
\bigskip
\textbf{testb}
\verb|key = text|
\testb{key = text}% => YES, NO, NO: true
\verb|key|
\testb{key}% => NO, YES, NO: false (once)
\end{document}