假设我有一个包含 的标记列表变量 abc{ab{abc}c}
。我想用 替换每个出现的b
。d
如您所见,有一些子组包含b
,我也想替换它,因此一个简单的
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\tl_set:Nn \l_tmpa_tl { abc{ab{abc}c} }
\tl_replace_all:Nnn \l_tmpa_tl { b } { d }
\tl_show:N \l_tmpa_tl
\ExplSyntaxOff
\begin{document}
\end{document}
不行,因为结果是adc{ab{abc}c}
(只有b
最外层分组级别的被替换了)。
可以尝试通过映射标记列表来获取第一级分组
\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\tl_new:N \l_tmpc_tl
\tl_set:Nn \l_tmpa_tl { abc{ab{abc}c} }
% iterate all tokens
\tl_map_inline:Nn \l_tmpa_tl
{
% obtain sub token list
\tl_set:Nn \l_tmpb_tl { #1 }
% replace
\tl_replace_all:Nnn \l_tmpb_tl { b } { d }
% append to result
\tl_put_right:NV \l_tmpc_tl \l_tmpb_tl
}
\tl_show:N \l_tmpc_tl
\ExplSyntaxOff
\begin{document}
\end{document}
不幸的是,这只作用于第一层,并且会产生完全失去该层的不良副作用,因为其结果是adcad{abc}c
。
我怎样才能递归搜索和替换没有失去分组?(完全可扩展性的奖励!)
我希望这个简单的例子没有失去任何普遍性。
答案1
如果您需要完全可扩展的解决方案来替换文本(包括组中的文本),那么这里有一个想法:
\def\repl#1{\replA #1{\end}}
\def\replA#1#{\replB#1\end}
\def\replB#1{\ifx\end#1\expandafter\replC \else\replX{#1}\expandafter\replB\fi}
\def\replC#1{\ifx\end#1\empty\else{\repl{#1}}\expandafter\replA\fi}
\def\replX#1{\ifx#1bd\else#1\fi}
\message{....\repl{abc{aabc{bb}}cb}}
\bye
该\message
命令扩展其参数并打印:....adc{aadc{dd}}cd
。
此代码忽略了标记之间的空格。您的任务中未指定空格处理。如果您需要保持空格不变,则代码需要稍微复杂一些(大约多五行)。
编辑我的估计不准确。接受空格的代码只需要三行:
\def\repl#1{\replA #1{\end}}
\def\replA#1#{\replD#1 {\end} }
\def\replD#1 #2 {\replB#1\end
\ifx\end#2\expandafter\replC\else\space\fihere\replD#2 \fi}
\def\replB#1{\ifx\end#1\else\replX{#1}\expandafter\replB\fi}
\def\replC#1{\ifx\end#1\empty\else{\repl{#1}}\expandafter\replA\fi}
\def\replX#1{\ifx#1bd\else#1\fi}
\def\fihere#1\fi{\fi#1}
\message{....\repl{ab c{ aa bc {bb}}cb}}
\bye
答案2
当 Manuel 发表评论时,我想起了\regex_replace_all:nnN
,其中第一个参数包含要由作为第三个参数给出的标记变量中的第二个参数替换的标记。
\documentclass{article}
\usepackage{l3regex}
\begin{document}
\ExplSyntaxOn
\tl_set:Nn \l_tmpa_tl { abc{ab{abc}c} }
Before:\space \l_tmpa_tl \par
\regex_replace_all:nnN {b} {d} \l_tmpa_tl
After:\space \l_tmpa_tl
\ExplSyntaxOff
\end{document}
答案3
我不知道这是否就是你的意思,现在我已经在评论中看到了你的讨论,但它确实适用于原始问题中的情况。
该代码不依赖于 L3 开发人员明确指定为实验性的任何包或代码。
但请注意,我不知道自己在做什么。
买者自负 ....
计数包括表明标记列表内的分组得到保留,例如,a{bcde}f
在重新组装标记列表时,将其计为 3 个标记,而不是 6 个或 8 个。在处理过程中,细绳显然被视为具有更多标记,因为这对于在组内搜索和替换是必要的。
替换操作的结果存储在全局设置变量中\g_henri_mod_tl
。
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\str_new:N \l_henri_mod_str
\int_new:N \l_henri_tmpa_int
\int_new:N \l_henri_tmpb_int
\int_new:N \l_henri_tmpc_int
\tl_new:N \g_henri_mod_tl
\cs_new_protected:Npn \henri_replace_all:nnn #1 #2 #3
{
\group_begin:
\str_clear:N \l_henri_mod_str
\int_zero:N \l_henri_tmpa_int
\str_set:Nn \l_tmpa_str { #1 }
\str_set:Nn \l_tmpb_str { #2 }
\int_set:Nn \l_henri_tmpb_int { \str_count:N \l_tmpa_str }
\int_set:Nn \l_henri_tmpc_int { \str_count:N \l_tmpb_str }
\int_compare:nTF { \l_henri_tmpc_int = 1 }
{
\int_do_until:nn { \l_henri_tmpb_int = \l_henri_tmpa_int }
{
\str_if_eq_x:nnTF { #2 } { \str_head:N \l_tmpa_str }
{
\str_put_right:Nx \l_henri_mod_str { #3 }
}
{
\str_put_right:Nx \l_henri_mod_str { \str_head:N \l_tmpa_str }
}
\str_set:Nx \l_tmpa_str { \str_tail:N \l_tmpa_str }
\int_incr:N \l_henri_tmpa_int
}
}
{
\int_do_until:nn { \l_henri_tmpb_int = \l_henri_tmpa_int }
{
\str_if_eq_x:nnTF { #2 } { \str_range:Nnn \l_tmpa_str { 1 } { \l_henri_tmpc_int } }
{
\str_put_right:Nx \l_henri_mod_str { #3 }
\int_set:Nn \l_tmpa_int { \str_count:N \l_tmpa_str }
\str_set:Nx \l_tmpa_str { \str_range:Nnn \l_tmpa_str { 1 + \l_henri_tmpc_int } { \l_tmpa_int } }
\int_add:Nn \l_henri_tmpa_int { \l_henri_tmpc_int }
}
{
\str_put_right:Nx \l_henri_mod_str { \str_head:N \l_tmpa_str }
\str_set:Nx \l_tmpa_str { \str_tail:N \l_tmpa_str }
\int_incr:N \l_henri_tmpa_int
}
}
}
\tl_gset_rescan:Nno \g_henri_mod_tl {} { \l_henri_mod_str }
\group_end:
}
\cs_generate_variant:Nn \henri_replace_all:nnn { Vnn }
\henri_replace_all:nnn { abc{ab{abc}c} } { b } { d }
\g_henri_mod_tl {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\tl_set:Nn \l_tmpa_tl { abc{ab{abc}c} }
\henri_replace_all:Vnn \l_tmpa_tl { b } { d }
\g_henri_mod_tl {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\henri_replace_all:nnn { {a=b}\,{[]} } { [ } { \sqsubseteq }
$\g_henri_mod_tl$ {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\henri_replace_all:nnn { gydihŵs } { y } { w }
\g_henri_mod_tl {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\henri_replace_all:nnn { abc{ab{abc}c} } { bc } { doodle }
\g_henri_mod_tl {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\henri_replace_all:nnn { {a=[b}\,{[]} } { =[ } { \sqsubseteq }
$\g_henri_mod_tl$ {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\ExplSyntaxOff
\end{document}
已编辑处理对多于一个字符的字符串的搜索。这可以正确地替换=[
为\sqsubseteq
如上所述在评论中。
编辑
可以定义遵循目标语法的进一步命令序列。但是,应该注意的是,这并非在所有情况下都有效。特别是,它无法与 gwdihŵs 正确配合使用。
这个想法只是进行替换,然后吐出全局变量。我不确定调用宏是否正确,\tl_replace_allrecursive:nnn
因为这缺少任何适当的前缀,但如果宏仅供个人使用,并且您不担心将来会损坏,那就由您决定。就我个人而言,我会将其命名为类似这样的名字,这样\henri_replace_allrecursive:nnn
比较安全,因为我认为违反命名规则不会有任何好处。
\cs_new_protected:Npn \tl_replace_allrecursive:nnn #1 #2 #3
{
\henri_replace_all:nnn { #1 } { #2 } { #3 }
\g_henri_mod_tl
}
然后我们可以说
\tl_set:Nx \l_tmpa_tl { \tl_replace_allrecursive:nnn { abc{ab{abc}c} } { b } { d } }
\l_tmpa_tl \par
\tl_set:Nx \l_tmpa_tl { \tl_replace_allrecursive:nnn { {a=b}\,{[]} } { [ } { \sqsubseteq } }
$\l_tmpa_tl$ \par
\tl_set:Nx \l_tmpa_tl { \tl_replace_allrecursive:nnn { abc{ab{abc}c} } { bc } { doodle } }
\l_tmpa_tl \par
%
\tl_set:Nx \l_tmpa_tl { \tl_replace_allrecursive:nnn { {a=[b}\,{[]} } { =[ } { \sqsubseteq } }
$\l_tmpa_tl$ \par
并且,与原始结果相比,我们可以看到替换符合预期(当然,gwdihŵs 较少)。
我认为这里的令牌数量无关紧要,因为一切都在扩展。
完整代码:
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\str_new:N \l_henri_mod_str
\int_new:N \l_henri_tmpa_int
\int_new:N \l_henri_tmpb_int
\int_new:N \l_henri_tmpc_int
\tl_new:N \g_henri_mod_tl
\cs_new_protected:Npn \henri_replace_all:nnn #1 #2 #3
{
\group_begin:
\str_clear:N \l_henri_mod_str
\int_zero:N \l_henri_tmpa_int
\str_set:Nn \l_tmpa_str { #1 }
\str_set:Nn \l_tmpb_str { #2 }
\int_set:Nn \l_henri_tmpb_int { \str_count:N \l_tmpa_str }
\int_set:Nn \l_henri_tmpc_int { \str_count:N \l_tmpb_str }
\int_compare:nTF { \l_henri_tmpc_int = 1 }
{
\int_do_until:nn { \l_henri_tmpb_int = \l_henri_tmpa_int }
{
\str_if_eq_x:nnTF { #2 } { \str_head:N \l_tmpa_str }
{
\str_put_right:Nx \l_henri_mod_str { #3 }
}
{
\str_put_right:Nx \l_henri_mod_str { \str_head:N \l_tmpa_str }
}
\str_set:Nx \l_tmpa_str { \str_tail:N \l_tmpa_str }
\int_incr:N \l_henri_tmpa_int
}
}
{
\int_do_until:nn { \l_henri_tmpb_int = \l_henri_tmpa_int }
{
\str_if_eq_x:nnTF { #2 } { \str_range:Nnn \l_tmpa_str { 1 } { \l_henri_tmpc_int } }
{
\str_put_right:Nx \l_henri_mod_str { #3 }
\int_set:Nn \l_tmpa_int { \str_count:N \l_tmpa_str }
\str_set:Nx \l_tmpa_str { \str_range:Nnn \l_tmpa_str { 1 + \l_henri_tmpc_int } { \l_tmpa_int } }
\int_add:Nn \l_henri_tmpa_int { \l_henri_tmpc_int }
}
{
\str_put_right:Nx \l_henri_mod_str { \str_head:N \l_tmpa_str }
\str_set:Nx \l_tmpa_str { \str_tail:N \l_tmpa_str }
\int_incr:N \l_henri_tmpa_int
}
}
}
\tl_gset_rescan:Nno \g_henri_mod_tl {} { \l_henri_mod_str }
\group_end:
}
\cs_generate_variant:Nn \henri_replace_all:nnn { Vnn }
\cs_new_protected:Npn \tl_replace_allrecursive:nnn #1 #2 #3
{
\henri_replace_all:nnn { #1 } { #2 } { #3 }
\g_henri_mod_tl
}
\verb|\henri_replace_all:nnn { } { } { } \g_henri_mod_tl|
\smallskip\par
\henri_replace_all:nnn { abc{ab{abc}c} } { b } { d }
\g_henri_mod_tl {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\tl_set:Nn \l_tmpa_tl { abc{ab{abc}c} }
\henri_replace_all:Vnn \l_tmpa_tl { b } { d }
\g_henri_mod_tl {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\henri_replace_all:nnn { {a=b}\,{[]} } { [ } { \sqsubseteq }
$\g_henri_mod_tl$ {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\henri_replace_all:nnn { gydihŵs } { y } { w }
\g_henri_mod_tl {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\henri_replace_all:nnn { abc{ab{abc}c} } { bc } { doodle }
\g_henri_mod_tl {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\henri_replace_all:nnn { {a=[b}\,{[]} } { =[ } { \sqsubseteq }
$\g_henri_mod_tl$ {} ~ has ~ \tl_count:N \g_henri_mod_tl {} ~ tokens.\par
\bigskip\par
\verb|\tl_set:Nx \l_tmpa_tl { \tl_replace_allrecursive:nnn { ... } { ... } { ... } }|
\smallskip\par
\tl_set:Nx \l_tmpa_tl { \tl_replace_allrecursive:nnn { abc{ab{abc}c} } { b } { d } }
\l_tmpa_tl \par
\tl_set:Nx \l_tmpa_tl { \tl_replace_allrecursive:nnn { {a=b}\,{[]} } { [ } { \sqsubseteq } }
$\l_tmpa_tl$ \par
\tl_set:Nx \l_tmpa_tl { \tl_replace_allrecursive:nnn { abc{ab{abc}c} } { bc } { doodle } }
\l_tmpa_tl \par
%
\tl_set:Nx \l_tmpa_tl { \tl_replace_allrecursive:nnn { {a=[b}\,{[]} } { =[ } { \sqsubseteq } }
$\l_tmpa_tl$ \par
\ExplSyntaxOff
\end{document}
答案4
作为将 tl 操作为标记列表,analysis
可以使用 expl3 函数系列。
备择方案
l3regex
也可以使用,但peek_analysis_map_inline
无论如何它在内部使用。tokcycle
也不错,但是不支持{}
直接获取 charcode(据我所知)。
(此外,为了重建标记列表,为了处理中间值可能不平衡的事实,方法是将例如存储\iffalse { \else } \fi
到其中然后 x 扩展该事物。这也是的方法l3regex
)
然而,也存在一点缺点,那就是结果不可扩展。
实际代码:
\def \f #1 {
\tl_build_begin:N \a
\tl_analysis_map_inline:nn {#1} {
\int_compare:nNnTF {##2} = {`b} {
\tl_build_put_right:Nn \a {d}
} {
\tl_build_put_right:Nn \a {##1}
}
}
\tl_build_end:N \a
\tl_set:Nx \a {\a}
}
将输入作为#1
,将结果存储到\a
。
不用说,在实际代码中不要重新定义重要的 LaTeX 宏,例如\f
,\a
等等。