How to make \NewDocumentCommand global?
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}
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}
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.
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}
.