我想实现一个简单的 foreach 循环,它接受两个参数 - 一个字符串和每个字符的预期格式(最后一个字符除外),如下所示:
\spell{example}{\x-}
,其产生输出:e-x-a-m-p-l-e
。
我发现自己是 TeX 的新手,对 TeX 的具体细节并不熟悉,因此搜索 MWE 并没有成功。以下示例让我陷入了错误(已编辑):
更新:我已经更新了代码,现在可以正常工作了。但是,下面的各种答案也提供了更好的方法。
\documentclass{article}
\makeatletter
\newcommand{\spell}[2]{
\@tfor\x:=#1\do{\x#2}
}
\makeatletter
\begin{document}
\spell{example}{-}
\end{document}
答案1
我在这里只看到 Expl3 示例,但此任务非常适合仅使用 TeX 基元。我们定义宏,\spell
该宏获取单个单词并按照 给出的格式打印第一个字母\spellFirst
,按照 给出的格式打印下一个字母\spellNext
。我们只需要五行非常清晰的代码。另一方面,在我看来,Expl3 示例并不清晰。
\def\spell #1{\spellA#1{}}
\def\spellA #1{\spellFirst{#1}\spellB}
\def\spellB #1{\ifx^#1^\else\spellNext{#1}\expandafter\spellB\fi}
% format is given by:
\def\spellFirst #1{#1} % only copy to the output
\def\spellNext #1{-#1} % add `-` before the character
\spell{example}.
\bye
答案2
以下实现了两者。包含最后一个字符的变体直接使用函数即可expl3
,\text_map_inline:nn
忽略最后一个字符的变体需要循环文本两次(首先计算长度,然后应用用户函数)。
\documentclass{article}
\ExplSyntaxOn
\int_new:N \l__chzzh_grapheme_count_int
\int_new:N \l__chzzh_current_int
\NewDocumentCommand \chzzhLoop { s m m }
{
\IfBooleanTF {#1}
{ \__chzzh_loop_without_last:en { \text_expand:n {#2} } {#3} }
{ \text_map_inline:nn {#2} {#3} }
}
\cs_new:Npn \__chzzh_loop_without_last:nn #1#2
{
\int_zero:N \l__chzzh_grapheme_count_int
\int_zero:N \l__chzzh_current_int
\text_map_inline:nn {#1} { \int_incr:N \l__chzzh_grapheme_count_int }
\text_map_inline:nn {#1}
{
\int_incr:N \l__chzzh_current_int
\int_compare:nNnTF \l__chzzh_current_int < \l__chzzh_grapheme_count_int
{#2}
{##1}
}
}
\cs_generate_variant:Nn \__chzzh_loop_without_last:nn { e }
\ExplSyntaxOff
\begin{document}
\chzzhLoop{example}{#1-}
\chzzhLoop*{example}{#1-}
\end{document}
答案3
总结
如果您只想在字素之间插入连字符,这是可行的。
\documentclass{article}
\usepackage{iftex}
\iftutex
\usepackage{fontsetup}
\fi
\ExplSyntaxOn
\NewDocumentCommand{\unicodespell}{m}
{
\seq_clear:N \l_tmpa_seq
\text_map_inline:nn { #1 } { \seq_put_right:Nn \l_tmpa_seq { ##1 } }
\seq_pop_left:NN \l_tmpa_seq \l_tmpa_tl
\tl_use:N \l_tmpa_tl
\seq_map_inline:Nn \l_tmpa_seq { -##1 }
}
\ExplSyntaxOff
\begin{document}
\iftutex
\unicodespell{éxãm̃ple}% can't work correctly in pdflatex
\else
\unicodespell{éxãmple}% but this works in pdflatex
\fi
\end{document}
输出pdflatex
笔记。éxãmple
这里用的是单词(pdflatex
无法处理组合字符)。
输出与xelatex
或lualatex
笔记。在这里您可以看到m̃
,即使是 U+006D LATIN SMALL LETTER M 与 U+0303 COMBINING TILDE 的组合,也能被正确处理。
与 wipet 的解决方案进行比较,后者声称可以使用 Unicode。使用 进行编译optex
。
\fontfam[Termes]
\def\spell #1{\spellA#1{}}
\def\spellA #1{\spellFirst{#1}\spellB}
\def\spellB #1{\ifx^#1^\else\spellNext{#1}\expandafter\spellB\fi}
% format is given by:
\def\spellFirst #1{#1} % only copy to the output
\def\spellNext #1{-#1} % add `-` before the character
\spell{éxãm̃ple}% this doesn't work
\bye
原始答案包含更多详细信息
在每个项目之间插入一些标记很容易:分离给定字符串中的第一个项目,然后通过在每个项目前面添加第二个参数来处理其余项目。
最后,可选择添加第二个参数。如果要交换*
-variant 的行为,只需更改\IfBooleanT
为\IfBooleanF
。
下面的例子表明,这也适用于单字母的单词,甚至第一个参数为空。
\documentclass{article}
\ExplSyntaxOn
\NewDocumentCommand{\spell}{smm}
{
\tl_head:n { #2 }
\tl_map_inline:en { \tl_tail:n { #2 } } { #3 ##1 }
\IfBooleanF{#1}{#3}
}
\cs_generate_variant:Nn \tl_map_inline:nn { e }
\ExplSyntaxOff
\begin{document}
\spell*{example}{-}
\spell{example}{-}
\spell*{example}{=}
\spell*{e}{-} \spell{e}{-}
X\spell{}{-}X
\end{document}
然而,如果您希望对项目进行更通用的格式化,比如\fbox
对每个项目进行格式化,您需要做更多的事情。
\documentclass{article}
\ExplSyntaxOn
\NewDocumentCommand{\spell}{smm}
{
\chzzh_spell:nnn { #1 } { #2 } { #3 }
}
\cs_generate_variant:Nn \tl_map_function:nN { e }
\cs_new_protected:Nn \chzzh_spell:nnn
{
\cs_set_protected:Nn \__chzzh_spell_item:n { #3 }
\tl_map_function:eN { \tl_range:nnn { #2 } { 1 } { -2 } } \__chzzh_spell_item:n
\bool_if:nTF { #1 }
{
\tl_item:nn { #2 } { -1 }
}
{
\__chzzh_spell_item:e { \tl_item:nn { #2 } { -1 } }
}
}
\cs_new_protected:Nn \__chzzh_spell_item:n {} % initialize
\cs_generate_variant:Nn \__chzzh_spell_item:n { e }
\ExplSyntaxOff
\begin{document}
\spell{example}{#1-}
\spell*{example}{#1-}
\spell{example}{\fbox{#1}}
\end{document}
此处分离的项目是最后一个。
但是,如果您的文本预计包含非 ASCII 字符,则需要更多内容;在帮助下,\text_map_inline:nn
我们可以填充序列并对其采取行动。
\documentclass{article}
\ExplSyntaxOn
\NewDocumentCommand{\spell}{smm}
{
\chzzh_spell:nnn { #1 } { #2 } { #3 }
}
\cs_generate_variant:Nn \tl_map_function:nN { e }
\seq_new:N \l__chzzh_spell_seq
\tl_new:N \l__chzzh_spell_last_tl
\cs_new_protected:Nn \chzzh_spell:nnn
{
% populate the sequence with the items in #2
\seq_clear:N \l__chzzh_spell_seq
\text_map_inline:nn { #2 }
{
\seq_put_right:Nn \l__chzzh_spell_seq { ##1 }
}
% detach the last item
\seq_pop_right:NN \l__chzzh_spell_seq \l__chzzh_spell_last_tl
% define the aux function like specified by the last argument
\cs_set_protected:Nn \__chzzh_spell_item:n { #3 }
% map the sequence
\seq_map_function:NN \l__chzzh_spell_seq \__chzzh_spell_item:n
% deal with the last item
\bool_if:nTF { #1 }
{
\tl_use:N \l__chzzh_spell_last_tl
}
{
\__chzzh_spell_item:V \l__chzzh_spell_last_tl
}
}
\cs_new_protected:Nn \__chzzh_spell_item:n {} % initialize
\cs_generate_variant:Nn \__chzzh_spell_item:n { V }
\ExplSyntaxOff
\begin{document}
\spell{example}{#1-}
\spell*{example}{#1-}
\spell{éxãmple}{\fbox{#1}}
\end{document}