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}

enter image description here


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


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}.