在 tex 中是否有办法测试参数是否以某个字符串开头?然后提取该开头后面的文本?
举个例子,我想定义一个命令,如果文本以 开头,则将其变为粗体my
,否则变为斜体。
\transform{my big world}
% Returns "\textbf{big world}".
\transform{our small world}
% Returns "\textit{small world}".
\transform{your world}
% Returns \textit{world}
% It would be great if the test is case insensitive
\transform{My world}
% Returns \textbf{world}
如果可能的话,我更喜欢不使用 expl3 的方法(我对它了解不够,无法对其进行修改)。
答案这里吞噬单个字符,我可以将其用于字符串的每个字符。但对于长字符串来说,这很快就会变得难以处理。
答案1
如果您的第一个单词总是用空格与其他单词分隔,则可以使用以下代码:
\documentclass{article}
\makeatletter
\newcommand{\transform}[1]{\transform@#1\@nil}
\def\transform@@prefix{my}
\def\transform@#1 #2\@nil{%
\lowercase{\def\transform@@start{#1}}%
\ifx\transform@@start\transform@@prefix
\textbf{#2}%
\else
\textit{#2}%
\fi
}
\makeatother
\begin{document}
\transform{my big world} should return \textbf{big world}.
\transform{our small world} should return \textit{small world}.
\transform{your world} should return \textit{world}
\transform{My world} should return \textbf{world}
\end{document}
第一个单词被隔离,并且其小写版本与 匹配my
。
由于您需要的是冒号而不是空格,因此可以管理前缀的缺失。
\documentclass{article}
\makeatletter
\newcommand{\transform}[1]{\transform@#1::\@nil}
\def\transform@@prefix{my}
\def\transform@#1:#2:#3\@nil{%
\if\relax\detokenize{#2}\relax
% no colon in #1
\textit{#1}%
\else
\lowercase{\def\transform@@start{#1}}%
\ifx\transform@@start\transform@@prefix
\textbf{#2}%
\else
\textit{#2}%
\fi
\fi
}
\makeatother
\begin{document}
\transform{my:big world} should return \textbf{big world}.
\transform{our:small world} should return \textit{small world}.
\transform{your:world} should return \textit{world}
\transform{My:world} should return \textbf{world}
\transform{world} should return \textit{world}
\end{document}
一个expl3
版本,它也是(几乎)完全可扩展的(它实际上并不是因为\textit
和\textbf
,但我们可以用来\text_expand:n
解决这个问题。
\documentclass{article}
\ExplSyntaxOn
% just for the final test
\cs_new_eq:NN \textexpand \text_expand:n
%%%
\NewExpandableDocumentCommand{\transform}{sm}
{
\IfBooleanTF{#1}
{
\tohiko_transform:V #2
}
{
\tohiko_transform:n { #2 }
}
}
\cs_new:Npx \tohiko_transform:n #1
{% colons are special in expl3
\exp_not:N \__tohiko_transform:w #1 \c_colon_str \c_colon_str \exp_not:N \q_stop
}
\cs_generate_variant:Nn \tohiko_transform:n { V }
\use:x
{% colons are special in expl3
\cs_new:Npn \exp_not:N \__tohiko_transform:w
##1 \c_colon_str ##2 \c_colon_str ##3 \exp_not:N \q_stop
}
{
\tl_if_empty:nTF { #2 }
{
\__tohiko_transformed:Nn \textit { #1 }
}
{
\__tohiko_transform:nn { #1 } { #2 }
}
}
\cs_new:Npn \__tohiko_transform:nn #1 #2
{
\str_if_eq:eeTF { \str_lowercase:n { #1 } } { my }
{
\__tohiko_transformed:Nn \textbf { #2 }
}
{
\__tohiko_transformed:Nn \textit { #2 }
}
}
\cs_new:Npn \__tohiko_transformed:Nn #1 #2
{
#1{\tl_trim_spaces:n{#2}}
}
\ExplSyntaxOff
\newcommand{\testA}{my:hello world}
\newcommand{\testB}{our:hello world}
\begin{document}
\transform{my: big world} should return \textbf{big world}.
\transform{our:small world} should return \textit{small world}.
\transform{your:world } should return \textit{world}.
\transform{My:world} should return \textbf{world}.
\transform{world} should return \textit{world}.
\transform*{\testA} should return \textbf{hello world}.
\transform*{\testB} should return \textit{hello world}.
\edef\test{\textexpand{\transform{my: big world}}}
\texttt{\meaning\test}
\edef\test{\textexpand{\transform*{\testB}}}
\texttt{\meaning\test}
\end{document}
答案2
这是一个基于 LuaLaTeX 的解决方案。它将:
和 空格都识别为可能的分隔符。
\documentclass{article}
\usepackage{luacode}
\begin{luacode}
function transform ( s )
if s:find ( "^[Mm]y" ) then
s = s:gsub ( "^.-[%s:]+(.*)" , "\\textbf{%1}")
elseif s:find ( "^.-[%s:]+" ) then
s = s:gsub ( "^.-[%s:]+(.*)" , "\\textit{%1}")
else
s = "\\textit{"..s.."}"
end
tex.sprint ( s )
end
\end{luacode}
\newcommand\transform[1]{\directlua{transform(\luastring{#1})}}
\begin{document}
\obeylines
\transform{my:big world} should return \textbf{big world}.
\transform{my big world} should return \textbf{big world}.
\transform{my: big world} should return \textbf{big world}.
\smallskip
\transform{My:big world} should return \textbf{big world}.
\transform{My big world} should return \textbf{big world}.
\transform{My: big world} should return \textbf{big world}.
\smallskip
\transform{our:big world} should return \textit{big world}.
\transform{our big world} should return \textit{big world}.
\transform{our: big world} should return \textit{big world}.
\smallskip
\transform{world} should return \textit{world}
\end{document}
答案3
不使用 expl3 我可以提供一种通用机制\UD@CheckWhetherLeadingTokens
来检测宏参数的前导标记是否形成特定的标记序列。
\UD@CheckWhetherLeadingTokens
需要一个⟨内部标记检查宏⟩处理由特定标记序列分隔的宏参数。
特定的标记序列用作参数分隔符这一事实意味着特定的标记序列不能包含类别 1(开始组)或 2(结束组)或 6(参数)的明确字符标记。
语法是:
\UD@CheckWhetherLeadingTokens{⟨argument which is to be checked⟩}%
{⟨a ⟨token sequence⟩ without explicit
character tokens of category 1 or 2
or 6⟩}%
{⟨internal token-check-macro⟩}%
{⟨tokens to be delivered in case
⟨argument which is to be checked⟩ has
⟨token sequence⟩ as leading tokens⟩}%
{⟨tokens to be delivered in case
⟨argument which is to be checked⟩
does not have ⟨token sequence⟩ as
leading tokens⟩}%
为了定义⟨内部标记检查宏⟩您可以使用分隔参数
\UD@internaltokencheckdefiner{⟨internal token-check-macro⟩}%
{⟨token-sequence-gobble-macro⟩}%
{⟨token sequence⟩}%
⟨token-sequence-gobble-macro⟩虽然不被使用,\UD@CheckWhetherLeadingTokens
但是它可以用于吞噬前导标记序列。
以下是 MWE:
\documentclass{article}
\makeatletter
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@stopromannumeral,
%% \UD@CheckWhetherNull, \UD@CheckWhetherLeadingTokens
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's leading tokens form a specific
%% token-sequence that does not contain explicit character tokens of
%% category 1 or 2 or 6:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%% {<a <token sequence> without explicit
%% character tokens of category 1 or 2
%% or 6>}%
%% {<internal token-check-macro>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked> has
%% <token sequence> as leading tokens>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked>
%% does not have <token sequence> as
%% leading tokens>}%
\newcommand\UD@CheckWhetherLeadingTokens[3]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral
\expandafter\UD@secondoftwo\string{\expandafter\UD@@CheckWhetherLeadingTokens#3{\relax}#1#2}{}}{}%
}%
}%
\newcommand\UD@@CheckWhetherLeadingTokens[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%% {<token-sequence-gobble-macro>}%
%% {<token sequence>}%
%% - Defines <internal token-check-macro> to snap everything
%% until reaching <token sequence> and spit that out,
%% nested in braces. <token sequence> is discarded.
%% - Defines <token-sequence-gobble-macro> to gobble/discard
%% everything until reaching <token sequence>.
%% <token sequence> is discarded, too.
%% <token sequence> must not contain explicit character tokens
%% of category 1 or 2 or 6.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[3]{%
\@ifdefinable#1{\long\def#1##1#3{{##1}}}%
\@ifdefinable#2{\def#2#3{}}%
}%
%------------------------------------------------------------------------------
\UD@internaltokencheckdefiner{\UD@Checkmyspace}{\UD@Gobblemyspace}{my }%
\UD@internaltokencheckdefiner{\UD@CheckMyspace}{\UD@GobbleMyspace}{My }%
\UD@internaltokencheckdefiner{\UD@CheckmYspace}{\UD@GobblemYspace}{mY }%
\UD@internaltokencheckdefiner{\UD@CheckMYspace}{\UD@GobbleMYspace}{MY }%
%\UD@internaltokencheckdefiner{\UD@Checkmy}{\UD@Gobblemy}{my}%
%\UD@internaltokencheckdefiner{\UD@CheckMy}{\UD@GobbleMy}{My}%
%\UD@internaltokencheckdefiner{\UD@CheckmY}{\UD@GobblemY}{mY}%
%\UD@internaltokencheckdefiner{\UD@CheckMY}{\UD@GobbleMY}{MY}%
%------------------------------------------------------------------------------
\@ifdefinable\UD@gobbletospace{\long\def\UD@gobbletospace#1 {}}%
\newcommand\UD@CheckWhetherNoSpace[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletospace#1 }%
}%
%------------------------------------------------------------------------------
\newcommand\transform[1]{%
\UD@CheckWhetherLeadingTokens{#1}{my }{\UD@Checkmyspace}{\textbf{\UD@Gobblemyspace#1}}{%
\UD@CheckWhetherLeadingTokens{#1}{My }{\UD@CheckMyspace}{\textbf{\UD@GobbleMyspace#1}}{%
\UD@CheckWhetherLeadingTokens{#1}{mY }{\UD@CheckmYspace}{\textbf{\UD@GobblemYspace#1}}{%
\UD@CheckWhetherLeadingTokens{#1}{MY }{\UD@CheckMYspace}{\textbf{\UD@GobbleMYspace#1}}{%
% \UD@CheckWhetherLeadingTokens{#1}{my }{\UD@Checkmy}{\textbf{\UD@Gobblemy#1}}{%
% \UD@CheckWhetherLeadingTokens{#1}{My }{\UD@CheckMy}{\textbf{\UD@GobbleMy#1}}{%
% \UD@CheckWhetherLeadingTokens{#1}{mY }{\UD@CheckmY}{\textbf{\UD@GobblemY#1}}{%
% \UD@CheckWhetherLeadingTokens{#1}{MY }{\UD@CheckMY}{\textbf{\UD@GobbleMY#1}}{%
\textit{\UD@CheckWhetherNoSpace{#1}{}{\UD@gobbletospace}#1}%
% }%
% }%
% }%
% }%
}%
}%
}%
}%
}%
%------------------------------------------------------------------------------
\makeatother
\begin{document}
\verb|\transform{my big world}|: \transform{my big world}
\verb|\transform{our small world}|: \transform{our small world}
\verb|\transform{your world}|: \transform{your world}
\verb|\transform{My world}|: \transform{My world}
\verb|\transform{My }|: \transform{My }
\verb|\transform{My}|: \transform{My}
\verb|\transform{Bla}|: \transform{Bla}
\end{document}
如果参数不包含空格,则您没有指定所需的行为。在这种情况下,参数被传递,\textit
而不会删除任何内容。
如果您需要不同的行为,请通过评论告诉我,我会相应地编辑我的答案。
\UD@CheckWhetherLeadingTokens
不会改变其第一个参数的任何组件的字母大小写,因此也\transform
不会改变其参数的任何组件的字母大小写。
\@nil
在 的参数中\transform
/ 在 的第一个参数中出现标记标记/哨兵标记\UD@CheckWhetherLeadingTokens
是被允许的。
不匹配的\if..
或\else
或\or
或\fi
或\csname
可能\endcsname
出现在 的第一个参数中\UD@CheckWhetherLeadingTokens
。但是 的\transform
东西被传递给 LaTeX 2ε-kernel-macros \textit
/ \textbf
,它不处理这种不匹配的东西。
\UD@CheckWhetherLeadingTokens
因此\transform
其本身仅通过扩展来发挥作用。这意味着执行属于用于找出参数是否具有前导标记序列的机制的任何宏/ / /不会触发执行任何临时分配。也可以与诸如、、、、、、等一起使用。my⟨space⟩
My⟨space⟩
mY⟨space⟩
MY⟨space⟩
\UD@CheckWhetherLeadingTokens
\edef
\expanded
\write
\message
\csname..\endcsname
\number
\romannumeral
\ifcsname..\endcsname
\numexpr
但是,LaTeX 2ε-kernel-macros\textbf
和\textit
本身并非完全可扩展,无法可靠地与此类事物一起使用。使用\textbf
和\textit
LaTeX 2ε-kernel 的\protect
-机制和 TeX-engine 的-机制,以及旧的 LaTeX 2ε-kernel 不对和protected
应用任何保护机制,至少会干扰、、和等事物。\textbf
\textit
\csname..\endcsname
\number
\romannumeral
\ifcsname..\endcsname
\numexpr
您可以轻松地将其调整为使用冒号(:
)代替空格,但请注意,有些包会将冒号变成活动字符,而下面的机制是在类别代码为:
12(其他)时定义的。
\documentclass{article}
\makeatletter
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@stopromannumeral,
%% \UD@CheckWhetherNull, \UD@CheckWhetherLeadingTokens,
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's leading tokens form a specific
%% token-sequence that does not contain explicit character tokens of
%% category 1 or 2 or 6:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%% {<a <token sequence> without explicit
%% character tokens of category 1 or 2
%% or 6>}%
%% {<internal token-check-macro>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked> has
%% <token sequence> as leading tokens>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked>
%% does not have <token sequence> as
%% leading tokens>}%
\newcommand\UD@CheckWhetherLeadingTokens[3]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral
\expandafter\UD@secondoftwo\string{\expandafter\UD@@CheckWhetherLeadingTokens#3{\relax}#1#2}{}}{}%
}%
}%
\newcommand\UD@@CheckWhetherLeadingTokens[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%% {<token-sequence-gobble-macro>}%
%% {<token sequence>}%
%% - Defines <internal token-check-macro> to snap everything
%% until reaching <token sequence> and spit that out,
%% nested in braces. <token sequence> is discarded.
%% - Defines <token-sequence-gobble-macro> to gobble/discard
%% everything until reaching <token sequence>.
%% <token sequence> is discarded, too.
%% <token sequence> must not contain explicit character tokens
%% of category 1 or 2 or 6.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[3]{%
\@ifdefinable#1{\long\def#1##1#3{{##1}}}%
\@ifdefinable#2{\def#2#3{}}%
}%
%------------------------------------------------------------------------------
\UD@internaltokencheckdefiner{\UD@Checkmycolon}{\UD@Gobblemycolon}{my:}%
\UD@internaltokencheckdefiner{\UD@CheckMycolon}{\UD@GobbleMycolon}{My:}%
\UD@internaltokencheckdefiner{\UD@CheckmYcolon}{\UD@GobblemYcolon}{mY:}%
\UD@internaltokencheckdefiner{\UD@CheckMYcolon}{\UD@GobbleMYcolon}{MY:}%
%------------------------------------------------------------------------------
\@ifdefinable\UD@gobbletocolon{\long\def\UD@gobbletocolon#1:{}}%
\newcommand\UD@CheckWhetherNoColon[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@gobbletocolon#1:}%
}%
%------------------------------------------------------------------------------
\newcommand\transform[1]{%
\UD@CheckWhetherLeadingTokens{#1}{my:}{\UD@Checkmycolon}{\textbf{\UD@Gobblemycolon#1}}{%
\UD@CheckWhetherLeadingTokens{#1}{My:}{\UD@CheckMycolon}{\textbf{\UD@GobbleMycolon#1}}{%
\UD@CheckWhetherLeadingTokens{#1}{mY:}{\UD@CheckmYcolon}{\textbf{\UD@GobblemYcolon#1}}{%
\UD@CheckWhetherLeadingTokens{#1}{MY:}{\UD@CheckMYcolon}{\textbf{\UD@GobbleMYcolon#1}}{%
\textit{\UD@CheckWhetherNoColon{#1}{}{\UD@gobbletocolon}#1}%
}%
}%
}%
}%
}%
%------------------------------------------------------------------------------
\makeatother
\begin{document}
\verb|\transform{my:big world}| yields \transform{my:big world}
\verb|\transform{our:small world}| yields \transform{our:small world}
\verb|\transform{your:world}| yields \transform{your:world}
\verb|\transform{My:world}| yields \transform{My:world}
\verb|\transform{My:}| yields \transform{My:}
\verb|\transform{My}| yields \transform{My}
\verb|\transform{Bla}| yields \transform{Bla}
\end{document}
答案4
似乎\getword
来自的宏这里也能很好地工作。
\documentclass{article}
% https://tex.stackexchange.com/a/616582/
\protected\def\getword#1{%
\begingroup \toks0{}\toks1{#1}%
\futurelet\next\getwordA
}
\def\getwordA{\ifcat A\noexpand\next \expandafter\getwordB \else \expandafter\getwordC \fi}
\def\getwordB#1{\toks0\expandafter{\the\toks0 #1}\futurelet\next\getwordA}
\def\getwordC{\edef\tmp{\endgroup \the\toks1{\the\toks0}}\tmp}
\def\transformA#1{%
\lowercase{\ifnum\pdfstrcmp{#1}{my}=0 \bfseries
\else \ifnum\pdfstrcmp{#1}{our}=0 \itshape \fi\fi}%
\ignorespaces
}
\newcommand\transform[1]{\begingroup \getword\transformA#1\endgroup}
\begin{document}
\transform{my big world}
\transform{our small world}
\transform{your world}
\transform{My world}
\transform{} \transform{\relax} % nothing
\end{document}