6

I'm using a custom macros as suggested by Christian Hupfer here to create characters for typesetting a drama script:

\makeatletter
    \NewDocumentCommand{\NewPerson}{m}{%
      \expandafter\NewDocumentCommand\csname #1x\endcsname{+m}{%
        #1: \textbf{##1}\par%
      }
      \expandafter\NewDocumentCommand\csname #1h\endcsname{}{%
        \textsc{#1}%
      }
    }
\makeatother

When called like this:

\NewPerson{thomas}

everything works fine, the macros \thomash and \thomasx are available in the entire document. But when called from within a group, like this:

{
\NewPerson{thomas}
}

both macros are only accessible from within the group. How can I define those macros in such a manner that they may be used outside the group? I've read about prepending \global to the command definitions inside \NewPerson, but that didn't fix the issue. A MWE could be:

\documentclass{minimal}
\usepackage{xparse}

\makeatletter
    \NewDocumentCommand{\NewPerson}{m}{%
      \expandafter\NewDocumentCommand\csname #1x\endcsname{+m}{%
        #1: \textbf{##1}\par%
      }
      \expandafter\NewDocumentCommand\csname #1h\endcsname{}{%
        \textsc{#1}%
      }
    }
\makeatother

\begin{document}
\NewPerson{thomas}
\thomasx{asdf}
\thomash

{
\NewPerson{elvis}
\elvisx{asdf}
\elvish
}

\elvisx{asdf}
\elvish

\end{document}

Here, using \elvisx and \elvish outside the group doesn't work. Any ideas on how to fix that are very appreciated!

  • 1
    Well, you don't need \makeatletter...\makeatother here... – user31729 Mar 10 '17 at 19:46
  • 2
    See documenation of xparse: The xparse package provides a high-level interface for producing document-level commands -- So just use \gdef or somthing similar inside your definition. – Marco Daniel Mar 10 '17 at 19:47
  • As others noted: Your choice of syntax is odd. Did you over-simplify to make the MWE? I assume that is why the \makeat commands. But there is no reason to need xparse or \NewDocumentCommand for what you are trying to do. – user103221 Mar 10 '17 at 19:50
  • The \NewPerson macro was never intended to be used in a group. – user31729 Mar 10 '17 at 19:55
  • 2
    \NewDocumentCommand is intended for commands with document scope (there is a clue in the name) – David Carlisle Mar 10 '17 at 20:05
6

Either use \expandafter\gdef\csname #1x\endcsname##1{...} or remain in the expl3 domain which is the ground of xparse, saying \cs_gset:cpn.

In order to prevent the gobbling of spaces etc. in the \ExplSyntaxOn...\ExplSyntaxOff domain, I've defined format wrappers (or 'hooks'), which is a good way if the style of the speech etc. formatting should be changed later on.

\documentclass{minimal}
\usepackage{xparse}

\NewDocumentCommand{\personspeechformater}{m+m}{%
  #1: \textbf{#2}\par%
}

\NewDocumentCommand{\personhighlighter}{m}{%
  \textsc{#1}%
}

\ExplSyntaxOn
\NewDocumentCommand{\NewPerson}{m}{%
  \cs_gset:cpn  {#1x} ##1{\personspeechformater{#1}{##1}}%
  \cs_gset:cpn  {#1h} {\personhighlighter{#1}}%
}
\ExplSyntaxOff

% With \gdef



\NewDocumentCommand{\NewOtherPerson}{m}{%
  \expandafter\gdef\csname #1x\endcsname##1{\personspeechformater{#1}{##1}}%
  \expandafter\gdef\csname #1h\endcsname{\personhighlighter{#1}}%
}

\begin{document}
\NewPerson{thomas}
\thomasx{asdf}
\thomash

{
  \NewPerson{elvis}
  \elvisx{asdf}
  \elvish
}


\elvisx{asdf}
\elvish


{
  \NewOtherPerson{Gandalf}
}


\Gandalfx{Foo}
\Gandalfh
\end{document}

enter image description here

5

Extending and improving Christian's fine answer, I suggest adding something. A character's name might have diacritics that make it impossible directly using the name in command names.

I also added a rudimentary way for keeping track of the names.

Note that \cs_new:Npn acts globally and also checks whether the command is not already defined.

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{xparse}

\ExplSyntaxOn

\seq_new:N \g_lukelr_persons_seq
\prop_new:N \g_lukelr_persons_prop

\NewDocumentCommand{\NewPerson}{O{#2}m}
 {
  \cs_new:cpn {#1x} ##1 { \speech { #2 } { ##1 } }
  \cs_new:cpn {#1h} { \mention { #2 } }
  % list of keys for persons
  \seq_gput_right:Nn \g_lukelr_persons_seq { #1 }
  % correspondence between keys and persons
  \prop_gput:Nnn \g_lukelr_persons_prop { #1 } { #2 }
 }

\NewDocumentCommand{\ListPersons}{}
 {
  \seq_map_inline:Nn \g_lukelr_persons_seq
   {
    \prop_item:Nn \g_lukelr_persons_prop { ##1 } \par
   }
 }
\ExplSyntaxOff

\NewDocumentCommand{\speech}{ m +m }{%
  #1: {\bfseries #2}\par
}
\NewDocumentCommand{\mention}{ m }{%
  \textsc{#1}%
}

\begin{document}

\NewPerson{Treemunch}
{\NewPerson[Ooc]{Ööç}} % in a group just for testing

Scene 1: \Treemunchh\ and \Ooch

\Treemunchx{Here I am.}
\Oocx{Why are you so late?}

\bigskip

\ListPersons

\end{document}

enter image description here

  • Well, initially, for the other solution I had some 'complex' solution in mind, but I did not want to fire with the loaded weapon of expl3 to achieve something what you did here. – user31729 Mar 11 '17 at 7:01
1

I have written a function \GNewDocumentCommand which has the same signature as \NewDocumentCommand and almost the same semantics, except the new command is defined globally. I've also written analogous versions for the Renew, Provide, and Declare variants.

One way to use these functions is to paste the following code into a new file xparse-global.tex, place this file in the TeX engine's search path, and then import the code into your working TeX document thus: \input xparse-global. You must also import the xparse package with \usepackage. It doesn't matter if xparse is imported before or after xparse-global. Alternatively, you can create a LaTeX2e package from this code and import it with \usepackage.

\input expl3-generic
\ExplSyntaxOn
\group_begin:

\cs_set_protected:Npn \__xparse_global_new:NN #1#2
{
    \cs_new_protected:Npn #2 ##1##2##3
    {
        #1 ##1 {##2} {##3}
        \cs_gset_eq:NN ##1 ##1

        % The following couple of lines make use of xparse's internals,
        % namely the implementation of \...DocumentCommand's "return value".
        \cs_gset_eq:cc { \cs_to_str:N ##1 ~ } { \cs_to_str:N ##1 ~ }
        \cs_gset_eq:cc { \cs_to_str:N ##1 ~code } { \cs_to_str:N ##1 ~code }
    }
}

\clist_set:Nn \l__variants_clist { New, Renew, Provide, Declare }
\clist_map_variable:NNn \l__variants_clist \l__variant_str
{
    \str_set:Nx \l__local_func_name_str { \l__variant_str DocumentCommand }
    \str_set:Nx \l__global_func_name_str { G \l__local_func_name_str }

    \exp_args:Ncc \__xparse_global_new:NN
        { \l__local_func_name_str }
        { \l__global_func_name_str }
}

\group_end:
\ExplSyntaxOff

My code depends on the implementation of \NewDocumentCommand and its siblings by the xparse source code, so it may break if this implementation is changed in the future. It'll be nice if the LaTeX3 folks can incorporate my code into the xparse package and keep it in sync with future modifications of this package.


Now your minimal example can be rewritten as follows. One line was added and two more were slightly modified. See comments below.

\documentclass{minimal}
\usepackage{xparse}
% This line was added
\input xparse-global

\makeatletter
    \NewDocumentCommand{\NewPerson}{m}{%
      % This line was modified: \NewDocumentCommand -> \GNewDocumentCommand
      \expandafter\GNewDocumentCommand\csname #1x\endcsname{+m}{%
        #1: \textbf{##1}\par%
      }
      % This line was modified: \NewDocumentCommand -> \GNewDocumentCommand
      \expandafter\GNewDocumentCommand\csname #1h\endcsname{}{%
        \textsc{#1}%
      }
    }
\makeatother

\begin{document}
\NewPerson{thomas}
\thomasx{asdf}
\thomash

{
\NewPerson{elvis}
\elvisx{asdf}
\elvish
}

\elvisx{asdf}
\elvish

\end{document}

This compiles successfully and typesets as follows.

OP's minimal example rewritten with my global versions of exparse's functions


P.S. Your minimal example contains an un-manifested bug. To fix it, either replace +m by m, or replace \textbf{##1} by \begin{bfseries}##1\end{bfseries}.

  • Evan acknowledges this in their answer but just to emphasise for anyone unfamiliar with expl3: the internals of xparse are very much subject to change over time and so this code may very well no longer work if it is not updated accordingly... in expl3 code a \__ prefix to a command indicates that it is internal to the package and stability should not be expected. – Will Robertson Oct 18 '17 at 11:25
  • @WillRobertson: Actually all the \__ variables and functions I used are my own. The part that depends on the xparse implementation consists of the three consecutive lines that start with \cs_gset_eq:NN. What they do is they make global three functions used internally by the "return value" of \NewDocumentCommand. – Evan Aad Oct 18 '17 at 12:17
  • @EvanAad The standard with local private variables (in case you intend to follow, just to leave it here in case someone reads the answer) is \l__.. rather than \__l_.., IIRC. – Manuel Oct 18 '17 at 16:22
  • @Manuel: Thanks. What about private functions? – Evan Aad Oct 18 '17 at 16:25
  • @EvanAad Yes, private functions since have no scope \l__, \g__, \c__, just start with \__. – Manuel Oct 18 '17 at 16:34

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.