我花了好几天时间才搞清楚代码的扩展。遗憾的是,我还是搞不懂。
我尝试将带有参数的命令作为参数传递给 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}