回答原始问题并编辑 I

回答原始问题并编辑 I

为了简化程序包的编程,我想用单个命令生成多个类似的命令( \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一组内部密钥和一层用户级密钥,这些密钥将它们包装起来,以便所有\NEWCOMMANDs 共享相同的密钥。这里提供密钥parbasebasefront

请注意,所示方法对于许多键可能不切实际(在此示例中,在必须将输出拆分为两个宏之前,还有 3 个内部键的空间)。但基本上可以通过将值存储在宏中(例如通过expkv-def上述代码片段)或使用 来expkv-cs实现相同的效果\ekvcHashAndForward(请参阅 中的文档expkv-bundle.pdf)。

另外,截至撰写本文时,的当前版本(expkv-csv1.3 2023-01-23)的类型有限,choice这就是为什么我们使用enum和数值来表示和true(即将发布的版本将为键提供一种机制,允许转发宏而不仅仅是用户输入)。falsebasefrontparchoice

\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,我不会使用pgfkeysl3keys提供了各种现成的项目,您可以使用它们来区分此类情况。特别\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}

在此处输入图片描述

相关内容