![如何吞噬一个角色(如果存在)](https://linux22.com/image/450090/%E5%A6%82%E4%BD%95%E5%90%9E%E5%99%AC%E4%B8%80%E4%B8%AA%E8%A7%92%E8%89%B2%EF%BC%88%E5%A6%82%E6%9E%9C%E5%AD%98%E5%9C%A8%EF%BC%89.png)
我想做以下事情:
给定一个(复杂的)字符串/宏\String
,如果它以 * 开头,我就将其删除,否则我什么也不做。
到目前为止我尝试过
\def\gobbleast *{}%
如果以 开头,则\gobbleast \String
工作正常,否则会出现错误。\String
*
答案1
如果还需要涵盖边缘情况,例如需要考虑花括号,或者字符串本身可能包含不平衡的\if...
/ \or
/ \else
/\fi
我有一个令人恐惧的答案,其中\if..
- \or
- \else
-\fi
构造不会出现在任何地方,但使用分隔参数和参数空性测试来实现分支。;->
由于整个过程不是很快,因此它更像是一种教学手段,用来了解 TeX 如何收集属于宏参数的标记,以及如何通过扩展等方式操纵标记扩展的时间\expandafter
顺序\romannumeral
。
\documentclass[a4paper, landscape]{article}
% ---- Layout ----------------------------------------------------------------------
% The code for changing the layout is not of importance to you.
% It is messy and only suits the need of having the result of compiling this
% example fit on one page
\pagestyle{plain}
\csname @ifundefined\endcsname{pagewidth}{}{\pagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pdfpagewidth}{}{\pdfpagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pageheight}{}{\pageheight=\paperheight}%
\csname @ifundefined\endcsname{pdfpageheight}{}{\pdfpageheight=\paperheight}%
\textwidth=\paperwidth
\advance\textwidth-3cm
\evensidemargin=\dimexpr-1in+1.5cm\relax
\oddsidemargin=\dimexpr-1in+1.5cm\relax
\marginparsep=2mm
\marginparwidth=\dimexpr1.5cm-2\marginparsep\relax
\textheight=\paperheight
\advance\textheight-3cm
\topmargin=\dimexpr-1in+1.5cm\relax
\headheight=0pt
\headsep=0pt
{\normalfont
\setbox\csname @tempboxa\endcsname\hbox{0123456789}%
\global\footskip=\dimexpr .75cm -.5\dp\csname @tempboxa\endcsname
+.5\ht\csname @tempboxa\endcsname\relax
}%
\parindent=0pt
\flushbottom
% ---- END OF LAYOUT ---------------------------------------------------------------
\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>-sequence and spit that out
%% nested in braces.
%%
%% Defines <token-sequence-gobble-macro> to remove <token sequence> which in the
%% token-stream must definitely follow the token <token-sequence-gobble-macro>.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[3]{%
\@ifdefinable#1{\long\def#1##1#3{{##1}}}%
\@ifdefinable#2{\def#2#3{}}%
}%
%%=============================================================================
%% Checking for a leading *:
%%=============================================================================
\UD@internaltokencheckdefiner{\UD@SnapToStar}{\UD@GobbleStar}{*}%
%
% Now you can check for a leading * via
%
% \UD@CheckWhetherLeadingTokens{<argument to check>}{*}{\UD@SnapToStar}%
% {<tokens in case <argument to check> has a leading *>}%
% {<tokens in case <argument to check> does not have a leading *>}%
%
%%=============================================================================
%% Remove a leading * if present - due to \romannumeral-expansion the
%% result is delivered by triggering two expansion-steps:
%%=============================================================================
\newcommand\RemoveLeadingStar[1]{%
\romannumeral
\UD@CheckWhetherLeadingTokens{#1}{*}{\UD@SnapToStar}%
{\expandafter\UD@stopromannumeral\UD@GobbleStar}{\UD@stopromannumeral}#1%
}%
%%=============================================================================
%% Remove a leading * from the result of "hitting" the 1st token of the
%% argument with \expandafter once - due to \romannumeral-expansion the
%% result is delivered by triggering two expansion-steps::
%%=============================================================================
\newcommand\ExpandAndRemoveLeadingStar[1]{%
\romannumeral\expandafter\expandafter\expandafter\UD@secondoftwo
\expandafter\RemoveLeadingStar\expandafter{#1}%
}%
%------------------------------------------------------------------------------
\makeatother
\begin{document}
% Testsuite:
\noindent Defining \verb|\test| from the result of \verb|\RemoveLeadingStar{No leading star}|:
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\RemoveLeadingStar{No leading star}%
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent Defining \verb|\test| from the result of \verb|\RemoveLeadingStar{*Some leading star}|:
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\RemoveLeadingStar{*Some leading star}%
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent Defining \verb|\test| from the result of \verb|\RemoveLeadingStar{{*}Some leading star in braces}|:
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\RemoveLeadingStar{{*}Some leading star in braces}%
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent Defining \verb|\test| from the result of \verb|\RemoveLeadingStar{{*Some} leading star in braces}|:
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\RemoveLeadingStar{{*Some} leading star in braces}%
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent Defining \verb|\test| from the result of \verb|\RemoveLeadingStar{*Some leading star and imbalanced \fi}|:
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\RemoveLeadingStar{*Some leading star and imbalanced \fi}%
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent Defining \verb|\test| from the result of \verb|\RemoveLeadingStar{No leading star and imbalanced \fi}|:
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\RemoveLeadingStar{No leading star and imbalanced \fi}%
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\medskip\hrule\medskip
\noindent {\bfseries\selectfont Defining \verb|\test| from the result of \verb|\ExpandAndRemoveLeadingStar\String|}
\medskip
\noindent... while \verb|\String| is defined to expand to \verb|No leading star|:%
\def\String{No leading star}
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\ExpandAndRemoveLeadingStar\String
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent... while \verb|\String| is defined to expand to \verb|*Some leading star|:%
\def\String{*Some leading star}
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\ExpandAndRemoveLeadingStar\String
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent... while \verb|\String| is defined to expand to \verb|{*}Some leading star in braces|:%
\def\String{{*}Some leading star in braces}
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\ExpandAndRemoveLeadingStar\String
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent... while \verb|\String| is defined to expand to \verb|{*Some} leading star in braces|:%
\def\String{{*Some} leading star in braces}
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\ExpandAndRemoveLeadingStar\String
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent... while \verb|\String| is defined to expand to \verb|*Some leading star and imbalanced \fi|:%
\def\String{*Some leading star and imbalanced \fi}
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\ExpandAndRemoveLeadingStar\String
}%
\noindent
\texttt{\string\test=\meaning\test}
\medskip\hrule\medskip
\noindent... while \verb|\String| is defined to expand to \verb|No leading star and imbalanced \fi|:%
\def\String{No leading star and imbalanced \fi}
\medskip
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{%
\ExpandAndRemoveLeadingStar\String
}%
\noindent
\texttt{\string\test=\meaning\test}
\end{document}