Hello World!
我如何将存储到宏中的字符串拆分\def\mystring{Hello Word!}
为以逗号分隔的字符列表(包括空格),该列表可用作\foreach
循环的参数(\foreach \char in {\myCSlist}
)以循环遍历每个字符(如同\foreach \char in {H,e,l,l,o,\space,W,o,r,l,d,!})
?
编辑 1 (3 月 21 日):我为什么想要这个?
一开始,我没有解释为什么我希望解决方案生成一个逗号分隔的字符列表(例如\myCSlist
),该列表可以用作 的参数\foreach
。这是因为我想\node
使用 为每个字符创建一个 Tikz pic
。类似这样的内容(来自这里):
\newcommand{\hsp}{.5}
\tikzset{symbols/.pic={%
\foreach \s[count=\n from 0] in {\myCSlist}{%
\pgfmathsetmacro{\myangle}{360*rnd}%
\node[rotate=\myangle] at (\hsp*\n,0){\s};%
}}}%
第一次尝试
我尝试过\markletters
宏埃格尔(使用 xparse 和 expl3)但结果似乎不适合循环foreach
。
\markletters
我将宏的结果打印 - 从 更改(##1)
为##1,
,以获得以逗号分隔的列表,但这也导致以,
逗号分隔的列表末尾出现不需要的逗号。
这里有一个 M(Non)WE:
\documentclass{article}
\usepackage{tikz}% For the \foreach loop
\usepackage{xparse}
% egreg's \markletters macro : https://tex.stackexchange.com/a/359204/262081
\ExplSyntaxOn
\NewDocumentCommand{\markletters}{om}
{
\IfNoValueTF{#1}
{
\kessels_markletters:nn { #2 } { \tl_use:N \l_kessels_marked_letters_tl }
}
{
\kessels_markletters:nn { #2 } { \tl_set_eq:NN #1 \l_kessels_marked_letters_tl }
}
}
\tl_new:N \l_kessels_unmarked_letters_tl
\tl_new:N \l_kessels_marked_letters_tl
\cs_new_protected:Nn \kessels_markletters:nn
{
\tl_set:Nn \l_kessels_unmarked_letters_tl { #1 }
\tl_replace_all:Nnn \l_kessels_unmarked_letters_tl { ~ } { \textvisiblespace }
\tl_clear:N \l_kessels_marked_letters_tl
\tl_map_inline:Nn \l_kessels_unmarked_letters_tl
{
\tl_put_right:Nn \l_kessels_marked_letters_tl { ##1, }
}
#2
}
\ExplSyntaxOff
\def\mystring{Hello World!}
\begin{document}
\markletters[\foo]{\mystring}%
\foreach \char in {\foo}{%
<\char>%
}%
\end{document}
得出以下结果:
但我更喜欢这个结果(请注意“Hello”和“World”之间的空格):
答案1
这可以使用包来完成xstring
。代码还使用了\foreach
,它是的一部分,但如果您的文档不使用 tikz,则tikz
可以加载。pgffor
\documentclass{article}
\usepackage[T1]{fontenc} % to use accented characters and others
\usepackage{xstring}
\usepackage{pgffor} % in case you're not using tikz
\newcommand{\markletters}[1]{\StrLen{#1}[\strlen]\foreach \chr in {1,...,\strlen}{$<$\StrChar{#1}{\chr}$>$}}
\begin{document}
\markletters{Hello World!}
\markletters{äöü ÄÖÜ // ß}
\end{document}
答案2
这是一个基于 LuaLaTeX 的解决方案。它完全支持 utf8 编码,也就是说,输入字符串可能包含 utf8 编码的字符,而不仅仅是 ascii 编码的字符。
该代码由一个名为的 Lua 函数DoString
和一个名为的 LaTeX 实用程序宏组成\DoString
,它接受 1 个参数(一个字符串)并将其传递给 Lua 函数进行进一步处理。
% !TEX TS-program = lualatex
\documentclass{article}
\directlua{
function DoString ( s )
for i = 1,string.utflength(s) do
tex.sprint ( '<' .. unicode.utf8.sub ( s , i , i ) .. '>' )
end
end
}
%% Define a LaTeX utility macro:
\newcommand\DoString[1]{\directlua{DoString("#1")}}
\begin{document}
\ttfamily
\DoString{Hello World!}
\DoString{äöü ÄÖÜ // ß}
\end{document}
附录解决 OP 的后续问题:要按照评论中的建议创建 tikz 节点,我建议您(a)加载包luacode
(用于\luaexec
宏)和(b)将\directlua{...}
块更改为
\luaexec{
function DoString ( s )
for i = 1,string.utflength(s) do
tex.sprint ( '\\node{' .. unicode.utf8.sub ( s , i , i ) .. '};' )
end
end
}
答案3
这里我使用了一个标记循环。没有提到输入中出现的宏或组(只有字符和空格),因此这些应该从输入中排除,除非 OP 建议如何处理它们。
如果希望在 for 类循环中使用此列表,则可以将逗号分隔的内容读入列表后,\foreachitem
从包中使用。listofitems
\mystring
\documentclass{article}
\usepackage{tokcycle,listofitems}
\Characterdirective{\addcytoks{,#1}}
\Spacedirective{\addcytoks{,#1}}
\makeatletter
\newcommand\markletters[1]{%
\tokcyclexpress{#1}%
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\mystring
\expandafter\expandafter\expandafter
{\expandafter\@gobble\the\cytoks}%
}
\makeatother
\begin{document}
\markletters{Hello World!}
(\detokenize\expandafter{\mystring})
\readlist\mylist\mystring
\foreachitem\z\in\mylist{$<$\z$>$}
\end{document}
当然,整个过程可以在 token 循环中更简单地完成,这样listofitems
甚至不需要。而且,令我惊讶的是,如果我在 lualatex 下编译,它可以正确处理 UTF-8 输入
\documentclass{article}
\usepackage{tokcycle}
\newcommand\markletters[1]{%
\tokencycle{$<$##1$>$}{}{}{$<$##1$>$}#1\endtokencycle%
}
\begin{document}
\markletters{Hello World! äöü ÄÖÜ // ß}
\end{document}
补充
根据 OP 评论,这里有一个版本(基于第一种方法)可以处理宏和组。但是,需要参数的宏必须小心处理,因为每个标记/组后面都会插入逗号。
在此实现中,群组内容仅作为单个实体回显。如果想要按标记细分群组内容,tokcycle
可以这样做,但此实现中没有这样做。
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{tokcycle,listofitems}
\makeatletter
\stripgroupingtrue
\newcommand\markletters[1]{%
\tokcycle{\addcytoks{,##1}}{\addcytoks{,}\groupedcytoks{##1}}%
{\addcytoks{,##1}}{\addcytoks{,##1}}{#1}%
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\mystring
\expandafter\expandafter\expandafter
{\expandafter\@gobble\the\cytoks}%
}
\makeatother
\begin{document}
\markletters{Hello World! \today{} is a great day}
(\detokenize\expandafter{\mystring})
\readlist\mylist\mystring
\foreachitem\z\in\mylist{$<$\z$>$}
\mylist[7] is the 7th token
\detokenize\expandafter\expandafter\expandafter{\mylist[14]}
is the 14th token
\end{document}
答案4
由于我们无法通过未分隔的宏参数吸收单个空格,因此我们必须先通过宏预处理给定的宏\replspaces
。然后您可以使用\insertcommas
在标记之间插入逗号,但不在最后一个标记后插入逗号。或者用\insertangles
替换每个。token
<token>
\def\afterfi#1#2\fi{\fi#1}
\def\replspaces#1{\edef#1{\expandafter\replspacesA#1 \end}}
\def\replspacesA #1 #2{#1\ifx\end#2\else { }\afterfi{\replspacesA#2}\fi}
\def\insertcommas#1{\edef#1{\expandafter\insertcommasA#1\end}}
\def\insertcommasA#1#2{#1\ifx\end#2\else,\afterfi{\insertcommasA{#2}}\fi}
\def\insertangles#1{\edef#1{\expandafter\insertanglesA#1\end}}
\def\insertanglesA#1{\ifx\end#1\else<#1>\expandafter\insertanglesA\fi}
% test:
\def\mystring{Hello World!}
\replspaces\mystring
\insertcommas\mystring
\meaning\mystring % macro:-> H,e,l,l,o, ,W,o,r,l,d,!
\def\mystring{Hello World!}
\replspaces\mystring
\insertangles\mystring
\meaning\mystring % macro:-> <H><e><l><l><o>< ><W><o><r><l><d><!>
\bye
编辑您第一个问题是:如何H,e,l,l,o, ,w,o,r,d,!
从宏创建宏Hello World!
。我回答正确。
现在,您的第二个问题是:如何在\foreach
Tikz 中使用此类宏。我不知道如何有效地将扩展参数设置为\foreach
Tikz 中不太实用的参数(您可以使用\expanded{\unexpanded {\foreach ...}{\mystring}}
但看起来很糟糕)。最好不要\foreach
在 Tikz 中使用,而是定义您自己的\myforeach
:
\def\replspaces#1{\edef#1{\expandafter\replspacesA#1 \end}}
\def\replspacesA #1 #2{#1\ifx\end#2\else { }\afterfi{\replspacesA#2}\fi}
\def\myforeach#1{\expandafter\myforeachA#1\end}
\def\myforeachA#1{\ifx\end#1\else\body{#1}\expandafter\myforeachA\fi}
\def\body#1{<#1>}
\def\mystring{Hello World!}
\replspaces\mystring
\myforeach\mystring % prints: <H><e><l><l><o>< ><W><o><r><l><d><!>
\bye