我对 expandafter 和 newcommand 的理解存在问题,并带有参数和 sout

我对 expandafter 和 newcommand 的理解存在问题,并带有参数和 sout

我花了好几天时间才搞清楚代码的扩展。遗憾的是,我还是搞不懂。

我尝试将带有参数的命令作为参数传递给 sout。

\documentclass{article}
\usepackage{ulem,lipsum}
\textwidth=3cm % just to force hyphenation/linebreaks to happen
\usepackage[T1]{fontenc}

\newcommand*{\rom}[1]{\romannumeral #1}

\newcommand\INTiintKATAkatNUMinum{% A bunch of commands get’s constructed by a lyx extension
Test Text for demonstration. In the original this command is constructed. %
}

\newcommand{\Kat}[4]{% Command to piece together the constructed commands.
 \expandafter\csname INT\rom{#1}intKAT#2katNUM\rom{#3}num#4\endcsname%
}

\newcommand\soutt[1]{% sout with expansion
\expandafter\sout\expandafter{#1}%
}

\begin{document}
 \sout{The sout makro of ulem is able to handle hyphenation and linebreaks in text entered directly but not as macro see next line!}
 \soutt{\INTiintKATAkatNUMinum} %works
 \soutt{\Kat{1}{A}{1}{}} %doesn't
\end{document}

很感谢任何形式的帮助。

答案1

您应该意识到,\Kat为了传递存储的文本,需要多个扩展步骤:在第一个扩展步骤之后,您将获得

\expandafter\csname INT\rom{1}intKATAkatNUM\rom{1}num\endcsname

\expandafter什么都不做,因为它试图扩展I

您可以使用 `\romannumeral 技巧强制“几乎完全”扩展,这正是您在这里所需要的:

\documentclass{article}
\usepackage{ulem,lipsum}
\usepackage[T1]{fontenc}

\newcommand*{\rom}[1]{\romannumeral #1}

\newcommand\INTiintKATAkatNUMinum{%
  Test Text for demonstration. In the original this command is constructed. %
}

\newcommand{\Kat}[4]{% Command to piece together the constructed commands.
  \csname INT\rom{#1}intKAT#2katNUM\rom{#3}num#4\endcsname
}

\newcommand\soutt[1]{% sout with expansion
  \expandafter\sout\expandafter{\romannumeral-`Q#1}%
}

\begin{document}

\parbox{2cm}{
 \sout{The sout makro of ulem is able to handle hyphenation and linebreaks in text entered directly but not as macro see next line!}
 \soutt{\INTiintKATAkatNUMinum} %works
 \soutt{\Kat{1}{A}{1}{}} %doesn't
}

\end{document}

在此处输入图片描述

不,\sout不使用连字符。

这个\romannumeral技巧已解释过多次,但这里仅作一个简要说明。

原语\romannumeral需要<number>在其后跟一个,这里我们利用了 TeX 的一个特殊特性:在显式的之后<number>,TeX 会在其后寻找一个<optional space>,在此查找过程中扩展标记;一旦找到显式空格标记或不可扩展标记,它就会停止扩展。

“明确<number>”可以用几种方式来表达:

  • 一系列数字;
  • 一个十六进制数,即 其中的数字或字符序列ABCDEF,如果第一个标记是"
  • 一个八进制数,即其中的数字序列,01234567如果第一个标记是'
  • `如果第一个标记是(反引号),则为字母常量。

"'`在所有情况下,如果我们想要指定一个负数,那么应该在基数表示法前面加上减号。

在前三种情况下,TeX 会进行扩展,直到找到不符合可接受数字的字符。

我们感兴趣的是最后一种情况。字母常量是字符标记或控制序列,其名称由单个字符组成(无需定义)。因此

`Q  `\Q

是请求数字 81(Q 的 ASCII 码)的等效方法。转义符号对于具有“奇怪”类别代码的字符至关重要,例如

`\% `\^^@

(后者为 0)。

TeX 在字母常量之后扩展标记这一事实对于此应用程序具有决定性意义:<number>已经完全已知,在我们的例子中是-81,但 TeX 仍然会扩展标记。在结束对可选空间的搜索后,TeX 将继续完成 的扩展\romannumeral-`Q,由于数字为负数,因此为空。

我们不能\romannumeral-81直接使用,因为如果扩展后的下一个标记恰好是数字,那么它将被视为 的一部分<number>

这种方法的局限性是最终扩展中的初始空间将被吞噬。

在 TeX Live 的下一个版本中,所有引擎都将拥有\expanded(目前仅适用于 LuaTeX);基于 MiKTeX 的引擎应该已经拥有它。有了\expanded,事情会更简单:

\newcommand\soutt[1]{% sout with expansion
  \expandafter\sout\expandafter{\expanded{#1}}
}

因为\expanded只需一步就可以全面扩展其论点。


如果要使用连字符,可以使用soul。相应的宏是\st(或\textst)。为了进行更改,我将在 中实现它expl3,其中\romannumeral可用作“ f-expansion”。

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{soul}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\Kat}{mmmm}
 {
  \use:c
   {
    INT \int_to_roman:n {#1} intKAT #2 katNUM \int_to_roman:n {#3} num #4
   }
 }
\NewExpandableDocumentCommand{\soutt}{m}
 {
  \oberreiter_sout:f { #1 }
 }
\cs_new_protected:Nn \oberreiter_sout:n { \textst{#1} }
\cs_generate_variant:Nn \oberreiter_sout:n { f }

\ExplSyntaxOff

\newcommand\INTiintKATAkatNUMinum{%
  Test Text for demonstration. In the original this command is constructed. %
}

\begin{document}

\parbox{2cm}{
 \st{The st macro of soul is able to handle hyphenation and linebreaks
   in text entered directly but not as macro see next line!}
 \soutt{\INTiintKATAkatNUMinum} %works
 \soutt{\Kat{1}{A}{1}{}} %doesn't
}

\end{document}

在此处输入图片描述

答案2

灵感来自egreg 的回答—他展示了一个技巧,即保持扩展与前导标记的匹配,直到获得不可扩展的前导标记,其中扩展可能会“吃掉”参数中的前导空格标记—我刚刚编写了一个例程,它确实“命中”了其未定界/花括号嵌套参数的前导标记,直到获得不可扩展的前导标记。 即使参数是单个标记,而不是嵌套在花括号中,结果也将嵌套在花括号中。\romannumeral-`Q⟨argument⟩⟨argument⟩\romannumeral\HitArgsFirstTokenWithExpandafters\expandafter

该例程的要点是:

启动循环:

该例程检查参数是否为空,或者参数的前导标记是否为空格或左括号。
如果是这种情况,则例程将执行,因为空或前导括号或前导空格意味着没有可扩展的前导标记。

如果不是这种情况,则可以轻松地从参数中提取前导标记,并通过检查该标记的可扩展性,如果可扩展,则在启动下一次迭代之前通过生成命中。\expandafter\ifx\noexpand⟨token⟩⟨token⟩⟨not expandable⟩\else ⟨expandable⟩\fi\expandafter

请注意,此例程并没有完全扩展论点。就像\romannumeral-`Q⟨argument⟩一样只针对论点的第一个标记(但与-trick 不同,它不会“吃掉”前导空格标记)。 因此,如果参数包含宏标记但不是前导标记而是在(完全展开的)前导标记后面的某个位置,则换行仍然会被中断。\romannumeral-`Q⟨argument⟩

请注意,此类例程/技巧可能会导致问题,例如,如果存在像这样的前导可扩展标记序列,则括号不平衡
\expandafter\@gobble\string{!!!Attention!!! the closing brace will be unbalanced}

请注意,此类例程/技巧可能会导致问题(容量超出错误、永不终止循环等),例如,使用扩展到自身的宏,例如,\foo在定义之后。\newcommand\foo{\foo⟨something or nothing⟩}

其他旨在完全扩展论点的方法,已在egreg 的回答正在使用\expanded基于 LuaTeX 的引擎,并应用一些变体\edef——我建议\protected@edef——与没有 -primitive 的引擎一起使用\expanded

\documentclass{article}
\usepackage{ulem,lipsum}
%\usepackage[T1]{fontenc}
\usepackage{ifluatex}

\newcommand*{\rom}[1]{\romannumeral #1}

\newcommand\INTiintKATAkatNUMinum{ A bunch of commands gets constructed by a lyx extension
  Test Text for demonstration. In the original this command is constructed. %
}

\newcommand{\Kat}[4]{% Command to piece together the constructed commands.
  \expandafter\csname INT\rom{#1}intKAT#2katNUM\rom{#3}num#4\endcsname
}%

% a redefinition of \sout which lets you see what tokens \sout gets as argument:
%\def\sout#1{\def\test{#1}\texttt{\meaning\test}}%

\newcommand\soutt[1]{% expandable sout with total expansion of first token of argument
  \expandafter\expandafter\expandafter\sout\HitArgsFirstTokenWithExpandafters{#1}%
}%

% You can use \protected@edef for defining a temporary macro to expand
% to the total expansion of the argument before passing the expansion of
% that temporary macro to \sout:

\newcommand\souttb[1]{% non-expandable sout with total expansion of entire argument
  \csname protected@edef\endcsname\mytempa{#1}%
  \expandafter\sout\expandafter{\mytempa}%
}%

% In case of using LuaLaTeX you can use the \expanded-primitive:

\ifluatex
\newcommand\souttc[1]{% expandable sout with total expansion of entire argument
  \expandafter\sout\expandafter{\expanded{#1}}%
}%
\fi


\makeatletter
%%-----------------------------------------------------------------------------
%% Paraphernalia ;-) :
%%.............................................................................
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@exchange[2]{#2#1}%
%%-----------------------------------------------------------------------------
%% 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}
%%.............................................................................
\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}}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's first token is expandable
%%.............................................................................
%% \UD@CheckWhetherFirstTokenExpandable{<Argument which is to be checked>}%
%%                      {<Tokens to be delivered in case that 
%%                         <argument which is to be checked> has a first
%%                         token which is expandable>}%
%%                      {<Tokens to be delivered in case that
%%                         <argument which is to be checked> does not have
%%                         a first token which is expandable>}%
%%
\newcommand\UD@CheckWhetherFirstTokenExpandable[1]{%
  \romannumeral0%
  \UD@CheckWhetherNull{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
    \UD@CheckWhetherBrace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
      \UD@CheckWhetherLeadingSpace{#1}{\UD@exchange{ }{\expandafter}\UD@secondoftwo}{%
        \expandafter\expandafter\expandafter\UD@@CheckWhetherFirstTokenExpandable
        \UD@ExtractFirstArg{#1}%
      }%
    }%
  }%
}%
\newcommand\UD@@CheckWhetherFirstTokenExpandable[1]{%
  \expandafter\ifx\noexpand#1#1%
  \expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
  {\UD@exchange{ }{\expandafter}\UD@secondoftwo}%
  {\UD@exchange{ }{\expandafter}\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Hit argument's first token with \expandafter until argument's first token is
%% not an expandable token. Then deliver that nested in braces.
%%.............................................................................
\newcommand\HitArgsFirstTokenWithExpandafters[1]{%
  \romannumeral
  \UD@CheckWhetherFirstTokenExpandable{#1}{%
     \expandafter\UD@firstoftwo\expandafter{\expandafter}%
     \romannumeral0\UD@exchange{ }{\expandafter\expandafter\expandafter}%
     \expandafter\HitArgsFirstTokenWithExpandafters\expandafter{#1}%
  }{0 {#1}}%
}%
%%
%% With all the macros above, the result is delivered after two expansion-steps 
%% / after two \expandafter-chains due to  \romannumeral-expansion.
\makeatother

\parindent=0ex
\parskip=\smallskipamount

\begin{document}
\vspace*{-4.5cm}%
\enlargethispage{8cm}%
\pagestyle{empty}%
The sout makro of ulem does suppress hyphenation in any case.

\hrulefill\null

\begin{verbatim}
\parbox{4.5cm}{%
  \sout{The sout makro of ulem is able to handle linebreaks in text
        entered directly but not as macro!}
}%
\end{verbatim}%
\parbox{5cm}{%
  \sout{The sout makro of ulem is able to handle linebreaks in text
        entered directly but not as macro!}
}%

\hrulefill\null

Linebreaking is not disturbed with the following:

\verb|\parbox{5cm}{\soutt{\INTiintKATAkatNUMinum}}|

\parbox{5cm}{\soutt{\INTiintKATAkatNUMinum}}%

\dotfill\null

\verb|\parbox{5cm}{\soutt{\Kat{1}{A}{1}{}}}|

\parbox{5cm}{\soutt{\Kat{1}{A}{1}{}}}

\hrulefill\null

Linebreaking is disturbed with the following:

\verb|\parbox{5cm}{\soutt{Breaking of lines is broken: \INTiintKATAkatNUMinum}}|

\parbox{5cm}{\soutt{Breaking of lines is broken: \INTiintKATAkatNUMinum}}

\dotfill\null

\verb|\parbox{5cm}{\soutt{Breaking of lines is broken: \Kat{1}{A}{1}{}}}|

\parbox{5cm}{\soutt{Breaking of lines is broken: \Kat{1}{A}{1}{}}}

\hrulefill\null

Linebreaking is not disturbed with the following:

\verb|\parbox{5cm}{\souttb{Breaking of lines is not broken: \INTiintKATAkatNUMinum}}|

\parbox{5cm}{\souttb{Breaking of lines is not broken: \INTiintKATAkatNUMinum}}

\dotfill\null

\verb|\parbox{5cm}{\souttb{Breaking of lines is not broken: \Kat{1}{A}{1}{}}}|

\parbox{5cm}{\souttb{Breaking of lines is not broken: \Kat{1}{A}{1}{}}}

\ifluatex\dotfill\null

\verb|\parbox{5cm}{\souttc{Breaking of lines is not broken: \INTiintKATAkatNUMinum}}|

\parbox{5cm}{\souttc{Breaking of lines is not broken: \INTiintKATAkatNUMinum}}

\dotfill\null

\verb|\parbox{5cm}{\souttc{Breaking of lines is not broken: \Kat{1}{A}{1}{}}}|

\parbox{5cm}{\souttc{Breaking of lines is not broken: \Kat{1}{A}{1}{}}}
\fi

\end{document}

在此处输入图片描述

相关内容