With the newer engines that support \scantokens
, you can directly use that one so you don't need to use external files. (*)
(*): it's "equivalent" to using external files.
Method 1: custom macro \cprotDef
to make defining such macros easy
In the code below, \cprotDef
is defined, which is similar to \cprotect
, but for macro definitions.
It seems to be "do only argument substitution", while making the user-side macro definition as simple as possible (although in this case the catcode information of the inputs are discarded)
Note that the commands defined using this command will use the catcodes at the point of execution, not the point of definition (so, for example \ExplSyntaxOff
before calling the macro itself).
%! TEX program = lualatex
\documentclass{article}
\usepackage{listings}
\begin{document}
% ======== Define the helper command \cprotDef to make the main macro definition (see below) easy. ========
\errorcontextlines=10000
\ExplSyntaxOn
% format: \cprotDef \cs_set_protected:Npn \control_sequence (parameter text) {(replacement text)}
% or \cprotDef {\cs_set_protected:Npn \control_sequence (parameter text)} {(replacement text)}
\cs_set_protected:Npn \cprotDef #1 # {
\tl_if_empty:nTF {#1} {
\cprotDef_auxi:n
} {
\cprotDef_auxi:n {#1}
}
}
\cs_set_protected:Npn \cprotDef_auxi:n #1 {
\begingroup
% after reading the control sequence and the parameter text, set catcode
% (list of all special characters can be found in \dospecials macro)
% special characters that keeps their meaning
%\{
%\}
%\#
% extra not listed in dospecials
\char_set_catcode_other:N \^^I
\char_set_catcode_other:N \^^J
% the rest are set to (other).
\char_set_catcode_other:N \ %space
\char_set_catcode_other:N \\
\char_set_catcode_other:N \$
\char_set_catcode_other:N \&
\char_set_catcode_other:N \^
\char_set_catcode_other:N \_
\char_set_catcode_other:N \%
\char_set_catcode_other:N \~
\newlinechar=`\^^J % which character is considered a newline when printed / by \scantokens
\endlinechar=`\^^J % which character is put after each line
\relax
% then read in the {(replacement text)}
\cprotDef_aux:nn {#1}
}
\cs_set_protected:Npn \cprotDef_aux:nn #1 #2 {
% #1 = (stored) approximately `\def \control_sequence (parameter_text)'
% #2 = (newly read) (replacement_text), with catcode of most special characters set to (other)
\endgroup % restore the catcode values
#1 {\scantokens{#2}}
}
\ExplSyntaxOff
% ======== Define the main command. ========
\cprotDef \protected \def \mycode #1 {
\begin{lstlisting}[escapechar=!]
class person {
private int !{\bf #1}!;
int getAge(){
return #1;
}
}
\end{lstlisting}
}
% ======== Use that command. ========
\mycode{age}
\end{document}
Method 2. Use scontents
.
In this particular case you don't really need to substitute the value of the argument into the code itself (can assign a macro outside and use it inside).
%! TEX program = xelatex
\documentclass{article}
\usepackage{listings}
\usepackage{scontents}
\begin{document}
\begin{scontents}[store-env=myStorageListing]
\begin{lstlisting}[escapechar=!]
class person {
private int !{\bf \myAge}!;
int getAge(){
return age;
}
}
\end{lstlisting}
\end{scontents}
\ExplSyntaxOn
% ======== Define the main command. ========
\NewDocumentCommand \myCommand {m} {
\tl_set:Nn \myAge {#1}
\getstored{myStorageListing}
}
\ExplSyntaxOff
% ======== Use that command. ========
\myCommand {age}
\end{document}
Method 3: raw \scantokens
Build the code one part at a time and finally \scantokens
it. This is the strongest method, but may be somewhat hard to read.
More explanation: https://tex.stackexchange.com/a/622505/250119
%! TEX program = xelatex
\documentclass{article}
\usepackage{listings}
\begin{document}
\ExplSyntaxOn
% ======== Define \myTLSetVerbatim : similar to \tl_set:Nn, but second argument is of xparse's +v type. ========
\NewDocumentCommand \myTLSetVerbatim {m +v} {
\tl_set:Nn #1 {#2}
}
\ExplSyntaxOff
% ======== Define some auxiliary helper TLs. ========
\myTLSetVerbatim \myTextBefore @\begin{lstlisting}[escapechar=!]
class person {
private int !{\bf @
\myTLSetVerbatim \myTextAfter @}!;
int getAge(){
return age;
}
}
\end{lstlisting}@
% ======== Define the main command. ========
\ExplSyntaxOn
\NewDocumentCommand \myCommand {m} {
\exp_args:Nx \scantokens {
\myTextBefore #1 \myTextAfter
}
}
\ExplSyntaxOff
% ======== Use that command. ========
\myCommand {age}
\end{document}
Because you're "only replace text", you can't put verbatim content into it even with +v
argument unless you put it in verbatim yourself (!{\bf <verbatim content>}!
also doesn't work in the environment.)
Method 4. \cprotect
\cprotect
can be used like this: (internally it uses external file)
This solution is less neat than scontents
and have the same restrictions.
%! TEX program = xelatex
\documentclass{article}
\usepackage{listings}
\usepackage{cprotect}
\begin{document}
% ======== Define the helper commands.
\cprotect {\newcommand \myCommandAux} {
\begin{lstlisting}[escapechar=!]
class person {
private int !{\bf \myAge}!;
int getAge(){
return age;
}
}
\end{lstlisting}
}
\ExplSyntaxOn
% ======== Define the main command. ========
\NewDocumentCommand \myCommand {m} {
\tl_set:Nn \myAge {#1}
\myCommandAux
}
\ExplSyntaxOff
% ======== Use that command. ========
\myCommand {age}
\end{document}
lstlistings
is likeverbatim
, so it cannot go in the argument to another command.