当我定义一个内容包含环境的宏时,我遇到了一些问题lstlisting
。例如,(假设!
是环境中的转义字符lstlisting
)
\def\mycode#1{
\begin{lstlisting}
class Person {
private int !{\bf #1}!;
int getAge(){
return !#1!;
}
}
\end{lstlisting}
}
当我使用时\mycode{age}
出现错误。有什么方法可以解决这个问题吗(请注意,我尝试过lrbox
环境但没有成功)?
我真正想要的是一些宏定义的东西,它只是替换文本中的参数并将结果内容复制到调用宏的位置。也就是说,下面这行出现的位置
\mycode{age}
LaTeX 将取代它
\begin{lstlisting}
class Person {
private int !{\bf age}!;
int getAge(){
return age;
}
}
\end{lstlisting}
并开始处理\begin{lstlisting} ... \end{lstlisting}
。
这应该很简单,但不幸的是它似乎太复杂了(至少对我来说)。
答案1
环境lstlisting
不能是另一个宏的参数。
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{listings}
\lstnewenvironment{mycode}[1]
{\lstset{escapechar=!}\gdef\myPara{#1}}
{}
\begin{document}
\begin{mycode}{age}
class Person {
private int !\textbf{\myPara}!;
int getAge(){
return !\myPara!;
}
}
\end{mycode}
\end{document}
答案2
使用支持的较新引擎\scantokens
,您可以直接使用该引擎,而不需要使用外部文件。(*)
(*):它“相当于”使用外部文件。
方法 1:自定义宏\cprotDef
,使定义此类宏变得简单
在下面的代码中,\cprotDef
定义了,它与类似\cprotect
,但是用于宏定义。
它似乎是“只进行参数替换”,同时使用户端宏定义尽可能简单(尽管在这种情况下输入的 catcode 信息被丢弃)
请注意,使用此命令定义的命令将使用执行点的 catcode,而不是定义点(例如\ExplSyntaxOff
在调用宏本身之前)。
%! 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}
方法2.使用scontents
。
在此特别的您实际上并不需要将参数的值代入代码本身(可以在外部分配一个宏并在内部使用它)。
%! 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}
方法 3:生吃\scantokens
一次构建一部分代码,最后\scantokens
构建完成。这是最强大的方法,但可能有点难以阅读。
更多解释: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}
因为您“仅替换文本”,所以即使有参数,您也不能将逐字内容放入其中,+v
除非您自己逐字放入(!{\bf <verbatim content>}!
在环境中也不起作用)。
方法4.\cprotect
\cprotect
可以像这样使用:(内部使用外部文件)
该解决方案不太简洁,scontents
并且具有相同的限制。
%! 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}
答案3
该环境的问题lstlisting
与以下环境的问题相同verbatim
:
它期望其“主体”和短语\end{lstlisting}
在某些非标准 catcode 制度下进行标记化,从而产生一组与在标准 catcode 制度下进行标记化所获得的标记集不同的标记。
如果来自宏的替换文本,而\mycode
该宏的定义文本在标准 catcode 制度下被标记,那么该短语\end{lstlisting}
将不会被识别为环境的结束lstlisting
。
作为一种解决方法,我可以提供例程
\DefineVerbatimToScantokens{⟨control-word-token⟩}{⟨xparse-argument-specifiers⟩}{%
⟨verbatim-material to be passed to \scantokens⟩
}%
⟨逐字逐句地传递给 \scantokens⟩在 verbatim-catcode-régime 下读取并标记。
然后在⟨逐字逐句地传递给 \scantokens⟩每一个都#
被 catcode 6(参数)的字符标记替换。
然后⟨控制字标记⟩定义为根据以下方式处理参数⟨xparse-参数说明符⟩并通过⟨逐字逐句地传递给 \scantokens⟩到\scantokens
。
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{listings}
%=== Code of \DefineVerbatimToScantokens ========================
% With older LaTeX-releases uncomment the following line:
%\usepackage{xparse}
\NewDocumentCommand\DefineVerbatimToScantokens{mm}{%
\begingroup
\catcode`\^^I=12\relax
\InnerDefineVerbatimToScantokens{#1}{#2}%
}%
\begingroup
\makeatletter
\def\InnerDefineVerbatimToScantokens#1#2{%
\endgroup
\NewDocumentCommand\InnerDefineVerbatimToScantokens{mm+v}{%
\endgroup\ReplaceHashloop{##3}{##1}{##2}%
}%
\newcommand\ReplaceHashloop[3]{%
\ifcat$\detokenize\expandafter{\Hashcheck##1#1}$%
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{%
\NewDocumentCommand{##2}{##3}{%
\begingroup\newlinechar=\endlinechar
\scantokens{\endgroup##1#2}%
}%
}{%
\expandafter\ReplaceHashloop\expandafter{\Hashreplace##1}{##2}{##3}%
}%
}%
\@ifdefinable\Hashcheck{\long\def\Hashcheck##1#1{}}%
\@ifdefinable\Hashreplace{\long\def\Hashreplace##1#1{##1####}}%
}%
\catcode`\%=12\relax
\catcode`\#=12\relax
\InnerDefineVerbatimToScantokens{#}{%}%
%=== End of code of \DefineVerbatimToScantokens =================
% Be aware that indenting does matter within \DefineVerbatimToScantokens's
% <verbatim-material to be passed to \scantokens>-argument:
\DefineVerbatimToScantokens\mycode{m}{%
\begin{lstlisting}[escapechar=!]
class Person {
private int !\textbf{#1}!;
int getAge(){
return !#1!;
}
}
\end{lstlisting}
}%
\begin{document}
\noindent aaa
\noindent \mycode{age}
\end{document}