我是一名长期的 Latex 用户,过去几周我参与了一个需要执行以下操作的项目:我想逐字逐句地输入一些可能包含用户定义宏的数学 latex 代码。让我们举一个例子:
\documentclass[a4paper,11pt]{article}
\usepackage{amsmath}
\def\userMacro{\sqrt{4}}
\begin{document}
\texttt{My Verbatim equation: \expandafter\detokenize\expandafter{\userMacro}}
\end{document}
其渲染效果为:
My Verbatim equation: \sqrt {4}
现在我的问题出现在我不只有一个宏而是有多个宏的情况下,如下例所示:
\documentclass[a4paper,11pt]{article}
\usepackage{amsmath}
\def\userMacro{\sqrt{4}}
\begin{document}
\texttt{My Verbatim equation: \expandafter\detokenize\expandafter{\userMacro\userMacro}}
\end{document}
输出为:
My Verbatim equation: \sqrt {4}\userMacro
知道如何在应用之前扩展任意数量的令牌吗
\detokenize
谢谢!
编辑 06/01/2018:我应该强调这一事实
可能包含用户定义宏的数学 latex 代码
因此,最好的例子是:
\documentclass[a4paper,11pt]{article}
\usepackage{amsmath}
\def\userMacro{\sqrt{4}}
\def\R{\mathbb{R}}
\begin{document}
\texttt{My Verbatim equation: \expandafter\detokenize\expandafter{\int_{\R}\userMacro}}
\end{document}
应呈现为
My Verbatim equation: \int_{\mathbb{R}}\sqrt{4}
希望这能解释清楚。如果您认为这是一个完全不同的问题,我会接受最佳答案并最终再次提出问题。
道歉。
编辑2:
\newcommand\xdetokenize[1]{\detokaux#1\edetok\edetok}
\def\detokaux#1#2\edetok{\ifx\edetok#1\edetok\else
\expandafter\IfSubStr*{\detokenize\expandafter{#1}}{protect}{\detokenize{#1}}{\detokenize\expandafter{#1}}\def\next{\detokaux#2\edetok\edetok}\expandafter\next\fi
}
通过这个调整(使用 xstring),我可以避免“受保护的宏”扩展。我认为这对于这个问题的目的来说已经足够了。谢谢大家!
答案1
在一般情况下(不知道的完整论证的确切组成\detokenize
),您必须扩展论证的方式。
下面分析这个例子,第 1、3 和 4 个\expandafter
宏展开了第 2 个宏一次,\expandafter
最后第 2 个宏展开了第 1 个宏一次。
请注意,\expandafter
不需要前这\detokenize
。
\documentclass[a4paper,11pt]{article}
\usepackage{amsmath}
\def\userMacro{\sqrt{4}}
\begin{document}
\texttt{My Verbatim equation:
\detokenize\expandafter\expandafter\expandafter{\expandafter\userMacro\userMacro}}
\end{document}
现在...
如果你可以保证该参数将只包含您想要去标记一次的宏,然后这将使其自动化:
\documentclass[a4paper,11pt]{article}
\usepackage{amsmath}
\let\edetok\relax
\def\userMacroA{\sqrt{4}}
\def\userMacroB{\frac{A}{B}}
\def\userMacroC{\qed}
\newcommand\xdetokenize[1]{\detokaux#1\edetok\edetok}
\def\detokaux#1#2\edetok{\ifx\edetok#1\edetok\else
\detokenize\expandafter{#1}\def\next{\detokaux#2\edetok\edetok}\expandafter\next\fi
}
\begin{document}
\ttfamily My Verbatim equation:
1: \xdetokenize{}
2: \xdetokenize{\userMacroA}
3: \xdetokenize{\userMacroA\userMacroB}
4: \xdetokenize{\userMacroA\userMacroB\userMacroC}
\end{document}
如果你将纯文本放入 的参数中\xdetokenize
,空格将被占用(我警告过你)。可以对其进行调整以避免这种情况,但这样做会变得越来越复杂。
答案2
因此,您有一个文本片段(或任何一组标记),其中散布着一些需要扩展到任何级别的标记。
处理这个问题的一种朴素而老式的方法可能是将这段文本拆分成几部分,每部分都以要扩展的标记开头。
然后以相反的顺序嵌套这些部分。
然后,对每个部分,根据需要多次扩展第一个标记,然后将该部分与嵌套在其后面的部分进行交换。这意味着在扩展后反转反向顺序。这意味着在扩展事物时获取初始顺序。
在详细阐述这一点并提供一个最小的工作示例之前,让我们先解释一些与 (La)TeX 相关的事情。
第一件事:
在 (La)TeX 中有所谓的原语。其中一些处理所谓的⟨一般文本⟩。例如,\detokenize
doses so。语法\detokenize
是:
\detokenize<general text>
尽管
<general text>=<filler><catcode-1-token><balanced text><explicit character token of catcode 2>
。因此的语法\detokenize
也可以描述为:
\detokenize<filler><catcode-1-token><balanced text><explicit character token of catcode 2>
⟨填料⟩是一组任意的空间标记和其含义等于-primitive含义的标记\relax
。⟨填料⟩将被删除。
⟨平衡文本⟩是一组任意的标记,其中包含数量相等的显式 catcode-1(开始组)字符标记和显式 catcode-2(结束组)字符标记。
\detokenize
将提取⟨平衡文本⟩从⟨一般文本⟩并采取行动⟨平衡文本⟩就好像它被写入未扩展的外部文件,然后在 catcode-régime 下从该外部文件读回并标记,其中空间标记具有类别代码 10(空间),而所有其他字符具有类别代码 12(其他)。
有趣的是:
当 (La)TeX 搜索⟨catcode-1-token⟩之前⟨平衡文本⟩,它将继续删除形成⟨填料⟩(即空间标记和含义等于基元含义的标记\relax
)并不断扩展可扩展标记,直到找到所需的⟨catcode-1-token⟩。
事实上,(La)TeX 在不断扩展的同时,⟨填料⟩并寻找所需的⟨catcode-1-token⟩可用于(例如通过\expandafter
和\romannumeral
扩展)在让(La)TeX 找到所需的⟨catcode-1-token⟩。
第二件事:
你可以使用 expandable\romannumeral
原语来触发大量的扩展工作:\romannumeral
通常会导致 (La)TeX 收集⟨数字⟩并以小写罗马数字的形式提供其表示。在收集标记时考虑(数字)⟨数字⟩,(La)TeX 会不断扩展可扩展标记。如果⟨数字⟩收集的不是积极的,形成那个的标记⟨数字⟩将被 (La)TeX “吃掉”,而在这种情况下 (La)TeX 根本不会提供任何标记。因此,\romannumeral
只要确保最终 (La)TeX 会找到一个非正的⟨数字⟩。
第三件事:
通常你使用\expandafter
它来影响可扩展标记的扩展时间顺序。
例如,如果您希望按时间顺序排列的输入流形成的标记的第二个标记在输入流的第一个标记之前展开,则只需确保在输入流中第一个标记前面有\expandafter
:
\def\first{1}
\def\second{2}
\expandafter\first\second
-> \first 2
\expandafter
本身是可扩展的。参考 TeXbook 中的类比,TeX 对输入和标记的处理被比作消化道,其扩展发生在 TeX 的嘴里,可以说\expandafter
在 TeX 的嘴里进行处理。当由 触发的工作\expandafter
完成后,\expandafter
所讨论的 -token 将不再存在。\expandafter
当尝试扩展下一个标记的过程完成时,(La)TeX 认为由 触发的工作已完成。因此,您可以将第一个标记散布在钾一组标记的元素,\expandafter
以便让 (La)TeX 扩展 (k+1)第个标记,然后再进一步处理该集合的第一个标记。这种穿插称为创建\expandafter
:
\def\first{1}
\def\second{2}
\def\third{3}
\def\fourth{4}
\expandafter\first\expandafter\second\expandafter\third\fourth
-> \first\second\third 4.
你可以有多个\expandafter
链。
对于每个以特定 token 为起点的额外扩展级别,您需要添加另一个\expandafter
“指向”该 token 的 -chain。所需的 -token 数量\expandafter
会随着所需扩展级别的数量呈指数增长:
\def\first{1}
\def\second{2}
\def\third{3}
\def\fourth{4}
\def\leveluponfourth{\fourth}
%|1st \expand|2nd \expand|%
%|after-chain|after-chain|%
%| | | | |%
\expandafter\expandafter
\expandafter \first
\expandafter\expandafter
\expandafter \second
\expandafter\expandafter
\expandafter \third
\leveluponfourth
->
\expandafter\first
\expandafter\second
\expandafter\third
\fourth
->
\first\second\third 4
您可以\expandafter
使用以下方法避免插入许多链\romannumeral
-expansion 来实现一种语法为 的机制,从而
\romannumeral\Expandtimes{<number whose value is k>}<token-sequence>
-chains, 并且该机制能够钾“点击”\expandafter
第一个标记⟨标记序列⟩作为触发扩展 -primitive 的结果\romannumeral
。扩展\romannumeral
-primitive 反过来可以由单个\expandafter
-chain “引导” 到该\romannumeral
-primitive 来触发。
在扩展上下文中,只有一个\expandafter
-chain 引导到\romannumeral
-primitive 的 -chain 即可获得与通过以下方式获得的相同结果钾 \expandafter
-chains 指向第一个 token⟨标记序列⟩:
\def\first{1}
\def\second{2}
\def\third{3}
\def\fourth{4}
\def\leveluponfourth{\fourth}
\expandafter\first
\expandafter\second
\expandafter\third
\romannumeral\Expandtimes{2}\leveluponfourth
->
\first\second\third 4
有几种实现方法\Expandtimes
。其中一种方法在本答案末尾的最小工作示例中显示。
在解释完这些事情之后(或者至少尝试过解释之后),让我们回到我对这个问题的朴素而老式的处理方法:
假设你有以下序列
Whatsoever before tokenA.\tokenA Whatsoever between
tokenA and tokenB.\tokenB Whatsoever between tokenB
and tokenC.\tokenC Whatsoever between tokenC and
tokenD.\tokenD Whatsoever after tokenD.
,并且希望
\tokenA
扩大3倍,
\tokenB
扩大1倍,
\tokenC
扩大2倍,
\tokenD
扩大4倍。
首先将该文本片段分割成几部分,每部分都以要扩展的标记开头:
{Whatsoever before tokenA.}%
{\tokenA Whatsoever between tokenA and tokenB.}%
{\tokenB Whatsoever between tokenB and tokenC.}%
{\tokenC Whatsoever between tokenC and tokenD.}%
{\tokenD Whatsoever after tokenD.}%
然后按相反的顺序嵌套这些部分:
{\tokenD Whatsoever after tokenD.}{%
{\tokenC Whatsoever between tokenC and tokenD.}{%
{\tokenB Whatsoever between tokenB and tokenC.}{%
{\tokenA Whatsoever between tokenA and tokenB.}{%
Whatsoever before tokenA.%
}%
}%
}%
}%
然后,对每个部分,根据需要多次扩展第一个标记,并将该部分与嵌套在其后面的部分进行交换。在下面的最小工作示例中,所有这些都是通过宏完成的,该宏\Expandtimesexchange
的第一个参数表示在交换第二个和第三个参数之前,第二个参数的第一个标记所需的扩展级别:
\Expandtimesexchange{4}{\tokenD Whatsoever after tokenD.}{%
\Expandtimesexchange{2}{\tokenC Whatsoever between tokenC and tokenD.}{%
\Expandtimesexchange{1}{\tokenB Whatsoever between tokenB and tokenC.}{%
\Expandtimesexchange{3}{\tokenA Whatsoever between tokenA and tokenB.}{%
Whatsoever before tokenA.%
}%
}%
}%
}%
如果您希望在应用之前完成所有这些操作\detokenize
,您可以通过\expandafter
在后面插入来\detokenize
触发一些 -\romannumeral
扩展,当参数扩展并交换时,该扩展将终止,扩展的结果\romannumeral
将找到非正数 0 - 零后面的空格标记“让 (La)TeX 知道”要收集的数字序列已完成。因此,该空格标记将被“吃掉”,作为完成由 触发的“收集数字的数字”过程的一部分\romannumeral
:
\detokenize\expandafter{%
\romannumeral\Expandtimesexchange{4}{\tokenD Whatsoever after tokenD.}{%
\Expandtimesexchange{2}{\tokenC Whatsoever between tokenC and tokenD.}{%
\Expandtimesexchange{1}{\tokenB Whatsoever between tokenB and tokenC.}{%
\Expandtimesexchange{3}{\tokenA Whatsoever between tokenA and tokenB.}{%
0 Whatsoever before tokenA.% "0<space>" ends \romannmeral expansion.
}%
}%
}%
}%
}%
以下是承诺的完整最小工作示例:
\documentclass[a4paper,11pt]{article}
\begingroup
\makeatletter
\@firstofone{%
\endgroup
\newcommand*\dfork[1]{\innerdfork#1{\@firstoftwo}d{\@secondoftwo}dd}%
}%
\newcommand\innerdfork{}\def\innerdfork#1d#2#3dd{#2}%
\newcommand*\Expandtimes[1]{%
0\expandafter\innerExp
\expandafter{%
\expandafter}%
\romannumeral\number\number#1 000d%
}%
\newcommand*\innerExp[2]{\dfork{#2}{#1 }{\innerExp{#1#1\expandafter}}}%
\newcommand*\Expandtimesexchange[2]{%
\expandafter\bracestripexchange\expandafter{\romannumeral\Expandtimes{#1}#2}%
}%
\newcommand\bracestripexchange[2]{#2#1}%
\newcommand\tokenA{\onelevelA}
\newcommand\tokenB{ [B.] }
\newcommand\tokenC{\onelevelC}
\newcommand\tokenD{\onelevelD}
\newcommand\onelevelA{\twolevelA}
\newcommand\onelevelC{ [C.] }
\newcommand\onelevelD{\twolevelD}
\newcommand\twolevelA{ [A.] }
\newcommand\twolevelD{\treelevelD}
\newcommand\treelevelD{ [D.] }
\begin{document}
\scriptsize
\parindent=0ex
\parskip=\baselineskip
\begin{verbatim}
\newcommand\tokenA{\onelevelA}
\newcommand\tokenB{ [B.] }
\newcommand\tokenC{\onelevelC}
\newcommand\tokenD{\onelevelD}
\newcommand\onelevelA{\twolevelA}
\newcommand\onelevelC{ [C.] }
\newcommand\onelevelD{\twolevelD}
\newcommand\twolevelA{ [A.] }
\newcommand\twolevelD{\treelevelD}
\newcommand\treelevelD{ [D.] }
\expandafter\newcommand
\expandafter\test
\expandafter{%<-these \expandafter trigger \detokenize
\detokenize\expandafter{%<- this \expandafter triggered by \detokenize triggers \romannumeral-expansion
\romannumeral\Expandtimesexchange{4}{\tokenD Whatsoever after \tokenD.}{%
\Expandtimesexchange{2}{\tokenC Whatsoever between \tokenC and \tokenD.}{%
\Expandtimesexchange{1}{\tokenB Whatsoever between \tokenB and \tokenC.}{%
\Expandtimesexchange{3}{\tokenA Whatsoever between \tokenA and \tokenB.}{%
0 Whatsoever before \tokenA.% "0<space>" ends \romannmeral expansion.
}%
}%
}%
}%
}%
}%
\end{verbatim}
\expandafter\newcommand
\expandafter\test
\expandafter{%<-these \expandafter trigger \detokenize
\detokenize\expandafter{%<- this \expandafter triggered by \detokenize triggers \romannumeral-expansion
\romannumeral\Expandtimesexchange{4}{\tokenD Whatsoever after \tokenD.}{%
\Expandtimesexchange{2}{\tokenC Whatsoever between \tokenC and \tokenD.}{%
\Expandtimesexchange{1}{\tokenB Whatsoever between \tokenB and \tokenC.}{%
\Expandtimesexchange{3}{\tokenA Whatsoever between \tokenA and \tokenB.}{%
0 Whatsoever before \tokenA.% "0<space>" ends \romannmeral expansion.
}%
}%
}%
}%
}%
}%
\texttt{\string\meaning\string\test \(\to\) \meaning\test}
\bigskip
Now see that \texttt{\string\detokenize}-ing took place before definig:
\bigskip
\texttt{\string\test \(\to\) \test}
\end{document}
答案3
不\expandafter
,但是你必须小心使用里面的代码\stringify
。
用 定义的宏\defineusermacro
是安全的;而诸如 的命令\frac
则不安全。
\documentclass[a4paper,11pt]{article}
\usepackage{amsmath}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\stringify}{m}
{
\group_begin:
\bool_set_true:N \l_stringify_expand_bool
\tl_to_str:x { #1 }
\group_end:
}
\cs_generate_variant:Nn \tl_to_str:n { x }
\bool_new:N \l_stringify_expand_bool
\NewDocumentCommand{\defineusermacro}{mm}
{
\newcommand{#1}
{
\bool_if:NTF \l_stringify_expand_bool
{ \exp_not:n { #2 } }
{ #2 }
}
}
\ExplSyntaxOff
\defineusermacro\userMacro{\sqrt{4}}
\begin{document}
\texttt{My Verbatim equation: \stringify{\userMacro+\userMacro}}
\end{document}
答案4
您可以使用xinttools
诸如此类的设施。
\documentclass[a4paper,11pt]{article}
\usepackage{amsmath}
\newcommand\userMacro{\sqrt{4}}
\newcommand\otherMacro{\frac{3}{7}}
\newcommand\ContainerMacro {%
\userMacro
\otherMacro
\userMacro
\otherMacro
\userMacro
\otherMacro
\userMacro
\otherMacro
\userMacro
\otherMacro
\userMacro
\otherMacro
}%
\usepackage{xinttools}
\begin{document}
\texttt{My Verbatim equation:
\xintApplyUnbracedNoExpand{\detokenize\expandafter}
{%
\userMacro
\otherMacro
\userMacro
\otherMacro
\userMacro
\otherMacro
\userMacro
\otherMacro
\userMacro
\otherMacro
\userMacro
\otherMacro
}%
}
\texttt{My Verbatim equation:
\xintApplyUnbraced{\detokenize\expandafter}
{\expandafter\space\ContainerMacro}%
}%
\end{document}
扩展\expandafter\space\ContainerMacro
一次\ContainerMacro
,然后就会停止对其参数\space
进行的所谓的“f”扩展。\xintApplyUnbraced
另一个宏调用xinttools
也\xintApplyInline
逐个标记地获取,但是它对每个项目进行“f”扩展,因此不能在这里使用。