![尝试使用 \zap@space 和 \lowercase 进行字符串比较](https://linux22.com/image/382359/%E5%B0%9D%E8%AF%95%E4%BD%BF%E7%94%A8%20%5Czap%40space%20%E5%92%8C%20%5Clowercase%20%E8%BF%9B%E8%A1%8C%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%AF%94%E8%BE%83.png)
我正在尝试对两个可能有空格也可能没有空格的字符串进行不区分大小写的检查。我已经在 Google 上搜索过这个问题,有很多关于这两个问题的文献,但我似乎找不到太多关于同时完成这两个问题的文献,我理想情况下想要一个不需要加载外部包的 tex 或 latex 解决方案(但这不是必需的)。我有一个似乎应该使用包来工作的东西,但我无法确定为什么它不工作。这是 MWE。
\documentclass{article}
\usepackage{ifthenx}
%%% Support command %%%
\makeatletter
\newcommand\compareStrings[4]{%
\edef\tempA{\expandafter\zap@space \lowercase{#1} \@empty}
\edef\tempB{\expandafter\zap@space \lowercase{#2} \@empty}
\ifthenelse{\equal{\tempA}{\tempB}}{True}{False}
\tempA
\tempB
}
\makeatother
%%%%% End support commands %%%%
\begin{document}
\compareStrings{test}{tesT}{1}{2}
\end{document}
当您编译它时,\tempA
和\tempB
都显示完全相同的字符串,但比较本身返回False
(中的第 3 和第 4 个参数\compareStrings
应该是真和假条件,但为了测试目的,我将它们用单词替换了)。
任何关于为什么两个字符串看起来相同但没有产生“True”的建议或解释都将不胜感激。我的第一个想法是扩展应该受到指责,但我本以为 edef 可以解决这个问题,但显然它不能。
答案1
您需要在比较之前将字符串应用小写,因此它需要位于\edef
此处的任何内容之外\pdfstrcmp
,以避免必须定义额外的临时宏,此原语也可在 pdftex 以外的其他引擎中使用,如\strcmp
。
除最后一个之外,其余均视为平等
\documentclass{article}
%%% Support command %%%
\makeatletter
\newcommand\compareStrings[2]{%
\edef\tempA{\lowercase{\noexpand\ifnum0=\noexpand\pdfstrcmp
{\noexpand\zap@space#1 \noexpand\@empty}%
{\noexpand\zap@space#2 \noexpand\@empty}%
}\relax}%
\tempA
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi}
\makeatother
%%%%% End support commands %%%%
\begin{document}
\def\zz{T e sT}
0: \compareStrings{tesT}{tesT}{1}{2}
1: \compareStrings{test}{tesT}{1}{2}
2: \compareStrings{\zz}{tesT}{1}{2}
3: \compareStrings{testj}{tesT}{1}{2}
\end{document}
答案2
一个expl3
解决方案。在这里,我利用了会“吃掉”空格的事实\tl_map_function:nN
,并进行了正确的大小写折叠而不是小写(请参阅 Unicode 文档以了解这很重要的原因):
\documentclass{article}
\usepackage{expl3,xparse}
\ExplSyntaxOn
\NewExpandableDocumentCommand \compareStrings { m m +m +m }
{
\str_if_eq:eeTF % \str_if_eq_x:nnTF in older code
{ \tl_map_function:fN { \str_foldcase:n {#1} } \use:n }
{ \tl_map_function:fN { \str_foldcase:n {#2} } \use:n }
{#3} {#4}
}
\cs_generate_variant:Nn \tl_map_function:nN { f }
\ExplSyntaxOff
\begin{document}
\compareStrings{test}{tesT}{1}{2}
\end{document}
答案3
请注意,\lowercase
它既不可扩展,也不会触发其参数的扩展。
\lowercase
因此,您需要确保当或\uppercase
发挥作用时,那些扩展到要更改大小写的字符的标记已经完全扩展。 ( 也一样\uppercase
。)
请注意,这\zap@space
不会触发其论点的扩展。
因此,您需要确保那些扩展为需要删除空格的字符的标记在\zap@space
执行时已经完全扩展。
\documentclass{article}
\usepackage{ifthenx}
\makeatletter
\DeclareRobustCommand\compareStrings[2]{%
% Make sure each instance of \zap@space gets its argument
% expanded - do this by edef-fing while via \noexpand preventing
% expansion both of \zap@space and of \@empty. The latter is a
% sentinel-token for \zap@space and therefore must be let in place
% untouched also:
\protected@edef\tempA{%
{\noexpand\zap@space#1 \noexpand\@empty}%
{\noexpand\zap@space#2 \noexpand\@empty}%
}%
% Now that the arguments for the \zap@space-instances are expanded,
% via another \protected@edef have carried out the \zap@space-instances:
\protected@edef\tempA{%
% \lowercase is not expandable, thus does not get expanded
% /does not get carried out at "e-def-fing-time".
% Expansion of anything else but \tempA is prevented via
% \noexpand. Within \tempA anything but the \zap@space-
% instances is already expanded due to the previous \protected@edef.
% Thus the only effect of this \protected@edef is carrying
% out \zap@space-instances on arguments that were expanded by
% the previous \protected@edef.
\lowercase{\noexpand\ifthenelse{\noexpand\equal\tempA}}%
}%
% Now in \tempA the arguments are expanded and space tokens
% are removed. Thus \tempA expands to a call to \lowercase
% where the arguments are expanded and thus you don't have
% control-sequence-tokens any more (whereon \lowercase would have
% no effect) but character-tokens (whereon \lowwercase does have
% an effect).
\tempA{\@firstoftwo}{\@secondoftwo}%
}
\makeatother
\parindent=0ex
\parskip=\baselineskip
\begin{document}
\verb|\compareStrings{test}{tesT}{equal}{different}| yields:
\compareStrings{test}{tesT}{equal}{different}
\verb|\compareStrings{test}{t e sT}{equal}{different}| yields:
\compareStrings{test}{t e sT}{equal}{different}
\verb|\compareStrings{test}{t E sT}{equal}{different}| yields:
\compareStrings{test}{t e sT}{equal}{different}
\verb|\compareStrings{test}{tset}{equal}{different}| yields:
\compareStrings{test}{tset}{equal}{different}
\def\test{test}%
\def\tset{TsEt}%
\verb|\def\test{test}|\\
\verb|\def\tset{TsEt}|
\verb|\compareStrings{\test}{te s T}{equal}{different}| yields:
\compareStrings{\test}{te s T}{equal}{different}
\verb|\compareStrings{\test}{tSeT}{equal}{different}| yields:
\compareStrings{\test}{tSeT}{equal}{different}
\verb|\compareStrings{\tset}{te s T}{equal}{different}| yields:
\compareStrings{\tset}{te s T}{equal}{different}
\verb|\compareStrings{\tset}{tSeT}{equal}{different}| yields:
\compareStrings{\tset}{tSeT}{equal}{different}
\verb|\compareStrings{\tset}{\test}{equal}{different}| yields:
\compareStrings{\tset}{\test}{equal}{different}
\verb|\compareStrings{\test}{\tset}{equal}{different}| yields:
\compareStrings{\test}{\tset}{equal}{different}
Be aware that \verb|\uppercase| and \verb|\lowercase| are not expandable, thus:
\verb|\compareStrings{\uppercase{test}}{TEST}{equal}{different}| yields:
\compareStrings{\uppercase{test}}{TEST}{equal}{different}
\verb|\compareStrings{\uppercase{test}}{test}{equal}{different}| yields:
\compareStrings{\uppercase{test}}{test}{equal}{different}
\verb|\compareStrings{\lowercase{TEST}}{test}{equal}{different}| yields:
\compareStrings{\lowercase{TEST}}{test}{equal}{different}
\verb|\compareStrings{\lowercase{test}}{test}{equal}{different}| yields:
\compareStrings{\lowercase{test}}{test}{equal}{different}
\end{document}
如果在参数本身可能包含括号的情况下需要删除空格标记,我可以提供一个可扩展的例程,\UD@removeallspace
用于从标记序列中递归删除所有显式空格标记,即使参数包含括号,也不需要在参数中可能不会出现的标记标记。
作为副作用,该例程确实通过匹配括号来替换类别代码 1/2 的匹配显式字符标记对。
通常括号是唯一具有 catcode 1/2 的字符,因此通常这不应该是个问题。通常。
(如果有人知道一种方法,可以在没有 e-TeX 或 odfTeX 扩展的旧 8 位引擎的扩展上下文中使用,并且匹配类别 1/2 的显式字符标记将保持不变,我会很高兴了解它。;-))
\documentclass{article}
\usepackage{ifthenx}
\makeatletter
%% Code for expandable recursive space-remove-routine:
%%
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo,
%% \UD@PassFirstToSecond, \UD@Exchange, \UD@removespace
%% \UD@CheckWhetherNull, \UD@CheckWhetherBrace,
%% \UD@CheckWhetherLeadingSpace, \UD@ExtractFirstArg
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@removespace{}\UD@firstoftwo{\def\UD@removespace}{} {}%
%%-----------------------------------------------------------------------------
%% 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]{%
\romannumeral0\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
\UD@secondoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is a catcode-1-character
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has leading
%% catcode-1-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has no leading
%% catcode-1-token>}%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral0\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\expandafter\UD@firstoftwo{ }{}%
\UD@firstoftwo}{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether brace-balanced argument starts with a space-token
%%.............................................................................
%% \UD@CheckWhetherLeadingSpace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is a
%% space-token>}%
%% {<Tokens to be delivered in case <argument
%% which is to be checked>'s 1st token is not
%% a space-token>}%
\newcommand\UD@CheckWhetherLeadingSpace[1]{%
\romannumeral0\UD@CheckWhetherNull{#1}%
{\expandafter\expandafter\UD@firstoftwo{ }{}\UD@secondoftwo}%
{\expandafter\UD@secondoftwo\string{\UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
\newcommand\UD@CheckWhetherLeadingSpaceB{}%
\long\def\UD@CheckWhetherLeadingSpaceB#1 {%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@secondoftwo#1{}}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
\expandafter\expandafter\expandafter}\expandafter\expandafter
\expandafter}\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%% \UD@ExtractFirstArg{ABCDE} yields {A}
%%
%% \UD@ExtractFirstArg{{AB}CDE} yields {AB}
%%
%% Be aware that (La)TeX does discard preceding space tokens when
%% gathering an undelimited argument. Thus:
%%
%% \UD@ExtractFirstArg{ ABCDE} also yields {A}
%%
%% \UD@ExtractFirstArg{ {AB}CDE} also yields {AB}
%%
%% This routine only works when the argument of \UD@ExtractFirstArg
%% is not empty/when the argument of \UD@ExtractFirstArg does have a
%% first inner undelimited argument. Thus use this routine only in
%% situations where non-emptiness of \UD@ExtractFirstArg's argument is
%% ensured.
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2\UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
\romannumeral0%
\UD@ExtractFirstArgLoop{#1\UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{ #1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%=============================================================================
%% \UD@removeallspace{<argument probably with space tokens>}
%%
%% after two expansion-steps delivers <argument without space tokens>
%%
%% (!!! \UD@removeallspace does also replace all pairs of matching
%% explicit character tokens of catcode 1/2 by matching braces!!!)
%%-----------------------------------------------------------------------------
\newcommand\UD@removeallspace[1]{%
\romannumeral0\UD@RemoveAllSpaceLoop{#1}{}%
}%
\newcommand\UD@RemoveAllSpaceLoop[2]{%
\UD@CheckWhetherNull{#1}{ #2}{%
\UD@CheckWhetherLeadingSpace{#1}{%
\expandafter\UD@RemoveAllSpaceLoop
\expandafter{\UD@removespace#1}{#2}%
}{%
\UD@CheckWhetherBrace{#1}{%
\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral0\expandafter\UD@RemoveAllSpaceLoop
\romannumeral0\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{}%
}{#2}}%
{\expandafter\UD@RemoveAllSpaceLoop\expandafter{\UD@firstoftwo{}#1}}%
}{%
\expandafter\UD@RemoveAllSpaceLoopPushFirstArgument
\romannumeral0\UD@ExtractFirstArgLoop{#1\UD@SelDOm}{#1}{#2}%
}%
}%
}%
}%
\newcommand\UD@RemoveAllSpaceLoopPushFirstArgument[3]{%
\expandafter\UD@RemoveAllSpaceLoop
\expandafter{\UD@firstoftwo{}#2}{#3#1}%
}%
%% End of code for expandable recursive space-remove-routine.
\makeatother
\makeatletter
\DeclareRobustCommand\compareStrings[2]{%
% Make sure each instance of \UD@removeallspace gets its argument
% expanded - do this by edef-fing while via \noexpand preventing
% expansion of \UD@removeallspace:
\protected@edef\tempA{%
{\noexpand\UD@removeallspace{#1}}%
{\noexpand\UD@removeallspace{#2}}%
}%
% Now that the arguments for the \UD@removeallspace-instances are expanded,
% via another \protected@edef have carried out the \UD@removeallspace-instances:
\protected@edef\tempA{%
% \lowercase is not expandable, thus does not get expanded
% /does not get carried out at "e-def-fing-time".
% Expansion of anything else but \tempA is prevented via
% \noexpand. Within \tempA anything but the \UD@removeallspace-
% instances is already expanded due to the previous \protected@edef.
% Thus the only effect of this \protected@edef is carrying
% out \UD@removeallspace-instances on arguments that were expanded by
% the previous \protected@edef.
\lowercase{\noexpand\ifthenelse{\noexpand\equal\tempA}}%
}%
% Now in \tempA the arguments are expanded and space tokens
% are removed. Thus \tempA expands to a call to \lowercase
% where the arguments are expanded and thus you don't have
% control-sequence-tokens any more (whereon \lowercase would have
% no effect) but character-tokens (whereon \lowwercase does have
% an effect).
\tempA{\UD@firstoftwo}{\UD@secondoftwo}%
}
\makeatother
\parindent=0ex
\parskip=\baselineskip
\begin{document}
\verb|\compareStrings{te{s}t}{te { S } T}{equal}{different}| yields:
\compareStrings{te{s}t}{te { S } T}{equal}{different}
\verb|\compareStrings{test}{t E sT}{equal}{different}| yields:
\compareStrings{test}{t E sT}{equal}{different}
\verb|\compareStrings{t{es}t}{t{se}t}{equal}{different}| yields:
\compareStrings{t{es}t}{t{se}t}{equal}{different}
\def\test{te{ s} t}%
\def\tset{Ts{ E }t}%
\verb|\def\test{te{ s} t}|\\
\verb|\def\tset{Ts{ E }t}|
\verb|\compareStrings{\test}{te {s} T}{equal}{different}| yields:
\compareStrings{\test}{te {s} T}{equal}{different}
\verb|\compareStrings{\test}{{tS}eT}{equal}{different}| yields:
\compareStrings{\test}{{tS}eT}{equal}{different}
\verb|\compareStrings{\tset}{t{e} s T}{equal}{different}| yields:
\compareStrings{\tset}{t{e} s T}{equal}{different}
\verb|\compareStrings{\tset}{tS{e}T}{equal}{different}| yields:
\compareStrings{\tset}{tS{e}T}{equal}{different}
\verb|\compareStrings{\tset}{\test}{equal}{different}| yields:
\compareStrings{\tset}{\test}{equal}{different}
\verb|\compareStrings{\test}{\tset}{equal}{different}| yields:
\compareStrings{\test}{\tset}{equal}{different}
Be aware that braces are taken into account, thus:
\verb|\compareStrings{t{es}t}{test}{equal}{different}| yields:
\compareStrings{t{es}t}{test}{equal}{different}
Be aware that \verb|\uppercase| and \verb|\lowercase| are not expandable, thus:
\verb|\compareStrings{\uppercase{test}}{TEST}{equal}{different}| yields:
\compareStrings{\uppercase{test}}{TEST}{equal}{different}
\verb|\compareStrings{\uppercase{test}}{test}{equal}{different}| yields:
\compareStrings{\uppercase{test}}{test}{equal}{different}
\verb|\compareStrings{\lowercase{TEST}}{test}{equal}{different}| yields:
\compareStrings{\lowercase{TEST}}{test}{equal}{different}
\verb|\compareStrings{\lowercase{test}}{test}{equal}{different}| yields:
\compareStrings{\lowercase{test}}{test}{equal}{different}
\end{document}