12

Or as, Werner simplified it: "How to define a command without backslash in xparse".


Background:

I am trying to set up a macro to define a macro for me (which will also do some other things which are not essential in the MWE below).

The first version below set up the macro by passing in \NumericalToNumber as #1. Note the \ in the name.

\DefineMacro{\NumericalToNumber}{%
    {one}{1}%
    {two}{2}%
    {three}{3}% 
    {four}{4}% 
}%

This does exactly what I desire.

enter image description here

However to perform the "other things" I need done in \DefineMacro, I need to have the macro name available without the backslash. So, I though it would be easy to change the call to not have \ in the name, and use csuse{} from the etoolbox to generate the name (as opposed to the usual \expandafter\csname...\endcsname):

\DefineMacro{NumericalToNumber}{%
    {one}{1}%
    {two}{2}%
    {three}{3}% 
    {four}{4}% 
}%

But I can't seem to get this version to work.

I also tried to stick with the first version and adapt the solution from How to remove the trailing escape character in an argument? but that did not work for me. Basically the code needed for the "other things" is already functional so would prefer to get the second version to work.


Code: Works: Use \ in \DefineMacro

\documentclass{article}
\usepackage{xparse}
\usepackage{xstring}

\NewDocumentCommand{\DefineMacro}{m m}{%
    % #1 = command name to be defined (WITH backslash).
    % #2 = pairs of {<property name>}{<propery value} (same as what \ItStrEqCase accepts)
    \NewDocumentCommand{#1}{s O{UNKNOWN}}{%
        \IfStrEqCase{##2}{%
            #2% 
        }[\fbox{Unknown property name='##2' in \string#1.}]%
    }%
}%

\DefineMacro{\NumericalToNumber}{%
    {one}{1}%
    {two}{2}%
    {three}{3}% 
    {four}{4}% 
}%

\begin{document}
\NumericalToNumber[two]

\NumericalToNumber[four]

\NumericalToNumber[ten]
\end{document}

Code: Does NOT work without \ in \DefineMacro

\documentclass{article}
\usepackage{etoolbox}
\usepackage{xparse}
\usepackage{xstring}

\NewDocumentCommand{\DefineMacro}{m m}{%
    % #1 = command name to be defined (withOUT backslash).
    % #2 = pairs of {<property name>}{<propery value} (same as what \ItStrEqCase accepts)
    \NewDocumentCommand{\csuse{#1}}{s O{UNKNOWN}}{% <---- Use of \csuse here
        \IfStrEqCase{##2}{%
            #2% 
        }[\fbox{Unknown property name='##2' in \string#1.}]%
    }%
}%

\DefineMacro{NumericalToNumber}{% <--- No backslash here in #1.
    {one}{1}%
    {two}{2}%
    {three}{3}% 
    {four}{4}% 
}%

\begin{document}
\NumericalToNumber[two]

\NumericalToNumber[four]

\NumericalToNumber[ten]
\end{document}
  • I suspect your problem would be solved by using expl3 when using xparse, but to be perfectly honest, I don't fully understand the problem. Feel free to chalk that up to my lack of sleep, though :) The same functionality could easily be implemented using a prop structure. – Sean Allred Feb 7 '15 at 5:38
  • What you could do is use some sort of helper macro to turn that first argument into a cs for the 'main' macro (the one that's working right now). That's probably the most unobtrusive solution sans the actual fix. – Sean Allred Feb 7 '15 at 5:41
  • I imagine you could heavily simply this question: "How to define a command without backslash in xparse" (no xstring, no options). Maybe? – Werner Feb 7 '15 at 6:03
  • I assume that \SSErrorBox is not really important for the question? – user31729 Feb 7 '15 at 6:47
  • Looks like github.com/latex3/svn-mirror/issues/179 to me: the team are aware of the question but are not really sure what the best solution is at present. Conceptually, \NewDocumentCommand goes with commands that are 'fixed' rather than 'dynamic', i.e. they might be expected to all be listed individually, and one concept for LaTeX3 in general is 'self-documenting code'. There is an issue with some areas where long lists of similar commands are to be expected, and we still haven;t round the ideal solution. – Joseph Wright Feb 7 '15 at 7:45
7

I am not sure this is what is expected, but

\expandafter\NewDocumentCommand{\csname#1\endcsname} 

does not work, but

\expandafter\NewDocumentCommand\csname#1\endcsname 

indeed does. The first way tries to treat the name of the macro (NumericalToNumber) as the argument type list, leading to an error about an unknown 'N' argument type.

\documentclass{article}


\usepackage{etoolbox}
\usepackage{xparse}
\usepackage{xstring}

\DeclareDocumentCommand{\DefineMacro}{mm}{%
  % #1 = command name to be defined (withOUT backslash).
  % #2 = pairs of {<property name>}{<propery value} (same as what \ItStrEqCase accepts)
  \expandafter\DeclareDocumentCommand\csname#1\endcsname{s O{UNKNOWN}}{%
    \IfStrEqCase{##2}{%
      #2% 
        }[\fbox{Unknown property name='##2' in \string#1.}]%
      }%
}

\DeclareDocumentCommand{\OtherDefineMacro}{mm}{%
  % #1 = command name to be defined (withOUT backslash).
  % #2 = pairs of {<property name>}{<propery value} (same as what \ItStrEqCase accepts)
  \expandafter\NewDocumentCommand\csname#1\endcsname{s O{UNKNOWN}}{%
    \IfStrEqCase{##2}{%
      #2% 
        }[\fbox{Unknown property name='##2' in \string#1.}]%
      }%
}


\DefineMacro{NumericalToNumber}{% <--- No backslash here in #1.
    {one}{1}%
    {two}{2}%
    {three}{3}% 
    {four}{4}% 
}%

\OtherDefineMacro{OtherNumericalToNumber}{% <--- No backslash here in #1.
    {one}{1}%
    {two}{2}%
    {three}{3}% 
    {four}{4}% 
}%


\begin{document}
\NumericalToNumber[two]

\NumericalToNumber[four]

\OtherNumericalToNumber[eight]

\NumericalToNumber[ten]
\end{document}

enter image description here

I provided a version with \DeclareDocumentCommand too.

  • You need to »step over« the brace: \expandafter\NewDocumentCommand\expandafter{\csname#1\endcsname} or leave the braces away: \expandafter\NewDocumentCommand\csname#1\endcsname – clemens Feb 7 '15 at 9:10
  • @cgnieder: Thanks, but that's basically what I have done in my 'without braces' solution (if it's a solution at all) – user31729 Feb 7 '15 at 9:37
  • 1
    You say \expandafter\NewDocumentName{\csname#1\endcsname} doesn't work without explanation which is why I tried to offer one :). BTW: is \NewDocumentName a typo? (Other than that I haven't looked at the question and answer – I'm at work right now...) – clemens Feb 7 '15 at 9:48
  • Of course the first version doesn't work: you're trying to expand {. – egreg Feb 7 '15 at 9:51
  • @egreg: Yes. And I thought that was the way the OP was doing it. Perhaps I have misinterpreted the statements – user31729 Feb 7 '15 at 9:53
10

It's a very common problem: you have to provide a symbolic token (the macro name) before \NewDocumentCommand starts its job:

\expandafter\NewDocumentCommand\csname ...\endcsname

is the solution; the more complicated

\expandafter\NewDocumentCommand\expandafter{\csname ...\endcsname}

works as well.

There is a slicker way, that avoids doing \expandafter:

\documentclass{article}
\usepackage{xparse}

\newcommand{\SSErrorBox}[1]{\fbox{#1}}
\newcommand{\command}[1]{\texttt{#1}}


\ExplSyntaxOn
\NewDocumentCommand{\DefineMacro}{m m}
 {
  % #1 = command name to be defined (without backslash).
  % #2 = pairs of {<property name>}{<property value>}
  \grill_define_macro:cn { #1 } { #2 }
 }

\cs_new_protected:Npn \grill_define_macro:Nn #1 #2
 {
  \NewDocumentCommand{#1}{s O{UNKNOWN}}
   {
    \str_case:nnF { ##2 } { #2 }
     { \SSErrorBox{Unknown~property~name="##2"~in~"\command{\string#1}".} }
   }
 }
\cs_generate_variant:Nn \grill_define_macro:Nn { c }
\ExplSyntaxOff

\DefineMacro{NumericalToNumber}{% <--- No backslash here in #1.
  {one}{1}
  {two}{2}
  {three}{3} 
  {four}{4}
}

\begin{document}

X\NumericalToNumber[two]X

X\NumericalToNumber[four]X

X\NumericalToNumber[ten]X

\end{document}

The macro \DefineMacro simply passes control to \grill_define_macro:cn. We then define \grill_define_macro:Nn; according to the conventions in expl3, this function expects two arguments: an unbraced single token (N) and a normally braced argument (n).

Argument #1 is the macro to define with \NewDocumentCommand, #2 is the list of pairs {<key>}{<value>}. Instead of \IfStrEqCase we can use \str_case:nnF, that is called as

\str_case:nnF
 { ##2 } % the second argument to the macro
 { #2 }  % the list of properties
 { ... } % what to do when there's no match

Of course you're free to use \IfStrEqCase, if you prefer, just remember that in the programming environment \ExplSyntaxOn/\ExplSyntaxOff spaces are ignored and wanted spaces have to be supplied as ~.

Finally, the variant \grill_define_macro:cn is defined; the magic happens here: c means that the argument must be braced and a symbolic token will be built from it before \grill_define_macro:Nn is executed.

enter image description here

I used some X's in order to show that no spurious space crept in.


Here is the definition of \NewDocumentCommand in xparse.sty:

\cs_new_protected:Npn \NewDocumentCommand #1#2#3
  {
    \cs_if_exist:NTF #1
      {
        \__msg_kernel_error:nnx { xparse } { command-already-defined }
          { \token_to_str:N #1 }
      }
      { \__xparse_declare_cmd:Nnn #1 {#2} {#3} }
  }

If this had been

\cs_new_protected:Npn \xparse_new_document_command:Nnn #1#2#3
  {
    \cs_if_exist:NTF #1
      {
        \__msg_kernel_error:nnx { xparse } { command-already-defined }
          { \token_to_str:N #1 }
      }
      { \__xparse_declare_cmd:Nnn #1 {#2} {#3} }
  }
\cs_set_eq:NN \NewDocumentCommand \xparse_new_document_command:Nnn

you could simply say, in your document,

\ExplSyntaxOn
\cs_generate_variant:Nn \xparse_new_document_command:Nnn { c }
\cs_set_eq:NN \NewDocumentCommandName \xparse_new_document_command:cnn
\ExplSyntaxOff

and you could use \NewDocumentCommandName{<name>}{<args>}{<code>} where <name> is the macro name without the backslash.

There may be good reasons for not doing this in the package.

You can do it yourself, however:

\ExplSyntaxOn
\cs_set_eq:NN \grill_new_document_command:Nnn \NewDocumentCommand
\cs_generate_variant:Nn \grill_new_document_command:Nnn { c }
\cs_set_eq:NN \NewDocumentCommandName \grill_new_document_command:cnn
\ExplSyntaxOff

However, I'd prefer the strategy outlined above.

  • Something like \NewDocumentCommand \NewDocumentCommandName { } { \exp_args:Nc \NewDocumentCommand } is what I would do. Isn't that a good approach? – Manuel Feb 8 '15 at 10:40
  • @Manuel It's essentially the same as using \cs_generate_variant:Nn – egreg Feb 8 '15 at 10:48
1

I thought that the standard approach, when \cs_generate_variant:Nn is not available (like this case), was using \exp_args:??. In this case

\ExplSyntaxOn
\NewDocumentCommand \DefineMacro { }
  { \exp_args:Nc \NewDocumentCommand }
\ExplSyntaxOff

Or, for one it's not necessary \ExplSyntax..

\NewDocumentCommand\DefineMacro{}
  {\csname exp_args:Nc\endcsname\NewDocumentCommand}
 %{\csuse{exp_args:Nc}\NewDocumentCommand}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.