编辑2:在底部我结合了这里的答案并更新了问题:使用 {} 时,索引的新命令会插入不需要的空格
我创建了一个函数来以粗体显示命令,并可选择在括号中提供手册页部分。
\usepackage{xparse}
\newcommand*{\man}[2][]{%
\textbf{#2}\IfNoValueF{#1}{(#1)}%
}
现在我还想为这些命令创建一个索引条目。因此我创建了一个包装器命令:
\newcommand*{\mani}[2][]{%
\man[#1]{#2}%
\index{#2@\man[#1]{#2}}%
}
但是,有时我想使用相同的格式创建索引条目,但不在运行的文本中打印命令。这是一个 MWE:
\documentclass{memoir}
\usepackage{xparse}
\newcommand*{\man}[2][]{%
\textbf{#2}\IfNoValueF{#1}{(#1)}%
}
\newcommand*{\mani}[2][]{%
\man[#1]{#2}%
\index{#2@\man[#1]{#2}}%
}
\makeindex
\begin{document}
Only an index entry: \index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world
Command and index entry: \mani[1]{ssh-keygen}
\printindex
\end{document}
该文件output.idx
包含两行,第二行包含一个额外的空格。因此问题是,为什么会有额外的空格,我该如何修复它以使其没有额外的空格?
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen@\man [1]{ssh-keygen}}{1}
编辑1:我添加了这个额外空间的结果的屏幕截图:打印了两个相同的索引条目。
更新
我已根据提供的解决方案更新了我的代码,但现在我收到一条我无法理解的错误消息。
\documentclass{memoir}
\usepackage{xparse}
\makeindex
\NewDocumentCommand\man{om}{%
\textbf{#2}\IfNoValueF{#1}{(#1)}%
}
\makeatletter
\newcommand*{\mani}{%
\@bsphack
\begingroup
\@sanitize
\@mani
}
\newcommand*{\@mani}[2][]{%
\man[#1]{#2}%
\@wrindex{#2@\string\man[#1]{#2}}%
}
\makeatletter
\begin{document}
Two separate commands: \man[1]{ssh-keygen}\index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world
One command: \mani[1]{ssh-keygen}
\printindex
\end{document}
OverLeaf 指出:
编译器无法理解您使用的命令。请检查命令拼写是否正确。如果命令是软件包的一部分,请确保您已使用 \usepackage{...} 将该软件包包含在您的前言中。
日志文件报告:
! Missing number, treated as zero.
<to be read again>
{
l.25 One command: \mani[1]{ssh-keygen}
A number should have been here; I inserted `0'.
(If you can't figure out why I needed to see a number,
look up `weird error' in the index to The TeXbook.)
答案1
该命令\index
应通过从\@sanitize
-category-code-régime 下的 .tex-input 文件中读取和标记内容来获取其参数。(-category-code-régime 表示:空格字符,,,,,,,,并且类别\@sanitize
代码为\
12 (其他)。)这有几个原因-例如,$
&
#
^
_
%
~
- 避免可扩展令牌的不必要的扩展。
- 避免在将控制字标记未扩展地写入外部文件时附加空格字符。
但是使用你的命令,\mani
命令\index
确实会从 传递它的参数\mani
。当\mani
gathers/composes\index
的参数时,形成该参数的标记不会在\@sanitize
-category-code-régime 下进行标记,而是在正常 category-code-régime 下进行标记。
在其他事情之下,在正常类别代码机制下进行标记化意味着短语像\man
被标记化为控制字标记,而不是字符序列\
, m
, a
, n
。当控制字标记未展开地写入文本文件时,例如,.idx
属于创建索引过程的某些文件,将附加一个空格字符。即,将写入字符序列\
, m
, a
, n
, 。⟨space character⟩
在定义中,\mani
您可以应用\string
命令\man
将其转换为字符标记序列。(因此,它仅依赖于一个具有类别代码 0(转义)的输入字符,并且整数参数的值\escapechar
等于 TeX 引擎内部字符编码方案中该字符的代码点数。通常,反斜杠字符\
是类别代码 0(转义)的唯一字符,并且通常\escapechar
具有值 92,这是 TeX 引擎内部字符编码方案中反斜杠字符的代码点数。)
\documentclass{memoir}
\usepackage{xparse}
\NewDocumentCommand{\man}{om}{%
\textbf{#2}\IfNoValueF{#1}{(#1)}%
}
\NewDocumentCommand{\mani}{om}{%
\IfNoValueTF{#1}{%
\man{#2}%
\index{#2@\string\man{#2}}%
}{%
\man[#1]{#2}%
\index{#2@\string\man[#1]{#2}}%
}%
}%
\makeindex
\begin{document}
Only an index entry: \index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world
Command and index entry: \mani[1]{ssh-keygen}
Only an index entry: \index{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}
Hello world
Command and index entry: \mani{ssh-keygen-no-optional-argument}
\printindex
\end{document}
根据上面的例子,生成的.idx 文件如下所示:
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}{1}
\indexentry{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}{1}
\index
采用这种方法,通过宏提供的参数中唯一“字符串化”的\mani
是短语\man
。
来自 的\mani
第一个或第二个参数的内容不会被字符串化。如果通过这些参数提供的标记集还包含控制字标记,您也可能会在这里得到不需要的空格。
我可以提供一个\StringifyNAct
适用\string
于其参数中的每个标记的例程:
\StringifyNAct{⟨action⟩}{⟨token 1⟩⟨token 2⟩...⟨token n⟩}
产量:
⟨action⟩{⟨stringification of token 1⟩}%
⟨action⟩{⟨stringification of token 2⟩}%
...
⟨action⟩{⟨stringification of token n⟩}%
\string
其中“token 的字符串化”是指应用于所讨论的 token 的结果。
由于\romannumeral
-expansion,结果是通过触发两个扩展步骤(例如,通过两个\expandafter
-chains)来传递的。
我建议该命令\mani
在正常的类别代码制度下读取和标记其参数,但使用空格字符(也可能是水平制表符,可^^I
使用 TeX 的^^
-notation 寻址)作为类别代码 12(其他),然后应用于\StringifyNAct
参数,然后将其结果传递给\index
-command 并嵌套在-command\scantokens
中\man
:
\documentclass{memoir}
\makeatletter
%%========================Code for \StringifyNAct==============================
%%
%% Copyright (C) 2019, 2020 by Ulrich Diez ([email protected])
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public Licence (LPPL), either
%% version 1.3 of this license or (at your option) any later
%% version. (The latest version of this license is in:
%% http://www.latex-project.org/lppl.txt
%% and version 1.3 or later is part of all distributions of LaTeX
%% version 1999/12/01 or later.)
%% The author of this work is Ulrich Diez.
%% This work has the LPPL maintenance status 'not maintained'.
%% Usage of any/every component of this work is at your own risk.
%% There is no warranty - neither for probably included
%% documentation nor for any other part/component of this work.
%% If something breaks, you usually may keep the pieces.
%%
%%=============================================================================
%% 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}
%%.............................................................................
\newcommand\UD@RemoveTillUD@SelDOm{}%
\long\def\UD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
\newcommand\UD@ExtractFirstArg[1]{%
\romannumeral0%
\UD@ExtractFirstArgLoop{#1UD@SelDOm}%
}%
\newcommand\UD@ExtractFirstArgLoop[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{ #1}%
{\expandafter\UD@ExtractFirstArgLoop\expandafter{\UD@RemoveTillUD@SelDOm#1}}%
}%
%%-----------------------------------------------------------------------------
%% In case an argument's first token is an opening brace, stringify that and
%% add another opening brace before that and remove everything behind the
%% matching closing brace:
%% \UD@StringifyOpeningBrace{{Foo}bar} yields {{Foo} whereby the second
%% opening brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyOpeningBrace[1]{%
\romannumeral0%
\expandafter\UD@ExtractFirstArgLoop\expandafter{%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\expandafter
\expandafter {%
\expandafter\UD@firstoftwo
\expandafter{%
\expandafter}%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\string
\expandafter}%
\string#1%
UD@SelDOm}%
}%
%%-----------------------------------------------------------------------------
%% In case an argument's first token is an opening brace, remove everything till
%% finding the corresponding closing brace. Then stringify that closing brace:
%% \UD@StringifyClosingBrace{{Foo}bar} yields: {}bar} whereby the first closing
%% brace is stringified:
%%.............................................................................
\newcommand\UD@StringifyClosingBrace[1]{%
\romannumeral0\expandafter\expandafter\expandafter
\UD@StringifyClosingBraceloop
\UD@ExtractFirstArg{#1}{#1}%
}%
\newcommand\UD@CheckWhetherStringifiedOpenBraceIsSpace[1]{%
%% This can happen when character 32 (space) has catcode 1...
\expandafter\UD@CheckWhetherLeadingSpace\expandafter{%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo
\expandafter{%
\expandafter}%
\expandafter{%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\UD@firstoftwo
\expandafter{%
\expandafter}%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\string
\expandafter}%
\string#1%
}%
}%
\newcommand\UD@TerminateStringifyClosingBraceloop[2]{%
\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter\expandafter
\expandafter{%
\expandafter\string
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter#1%
\string#2%
}%
}%
\newcommand\UD@StringifyClosingBraceloopRemoveElement[4]{%
\expandafter\UD@PassFirstToSecond\expandafter{\expandafter
{\romannumeral0\expandafter\UD@secondoftwo\string}{}%
\UD@CheckWhetherStringifiedOpenBraceIsSpace{#4}{%
\UD@Exchange{\UD@removespace}%
}{%
\UD@Exchange{\UD@firstoftwo\expandafter{\expandafter}}%
}{%
\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter#1%
\romannumeral0\UD@Exchange{ }{\expandafter\expandafter\expandafter}%
\expandafter
}%
\string#4%
}{\expandafter\UD@StringifyClosingBraceloop\expandafter{#2#3}}%
}%
\newcommand\UD@StringifyClosingBraceloop[2]{%
\UD@CheckWhetherNull{#1}{%
\UD@CheckWhetherStringifiedOpenBraceIsSpace{#2}{%
\UD@TerminateStringifyClosingBraceloop{\UD@removespace}%
}{%
\UD@TerminateStringifyClosingBraceloop{\UD@firstoftwo\expandafter{\expandafter}}%
}%
{#2}%
}{%
\UD@CheckWhetherLeadingSpace{#1}{%
\UD@StringifyClosingBraceloopRemoveElement
{\UD@removespace}{\UD@removespace}%
}{%
\UD@StringifyClosingBraceloopRemoveElement
{\UD@firstoftwo\expandafter{\expandafter}}{\UD@firstoftwo{}}%
}%
{#1}{#2}%
}%
}%
%%-----------------------------------------------------------------------------
%% Apply <action> to the stringification of each token of the argument:
%%
%% \StringifyNAct{<action>}{<token 1><token 2>...<token n>}
%%
%% yields: <action>{<stringification of token 1>}%
%% <action>{<stringification of token 2>}%
%% ...
%% <action>{<stringification of token n>}%
%%
%% whereby "stringification of token" means the result of applying \string
%% to the token in question.
%% Due to \romannumeral-expansion the result is delivered after two
%% \expandafter-chains.
%% If you leave <action> empty, you can apply a loop on the list formed by
%% {<stringification of token 1>}%
%% {<stringification of token 2>}%
%% ...
%% {<stringification of token n>}%
%%
%% Below a macro \ConcatenateStringifiedtokens is implemented which loops
%% on that list for concatenating.
%%.............................................................................
\newcommand\StringifyNAct{%
\romannumeral0\StringifyNActLoop{}%
}%
%%.............................................................................
%% \StringifyNActLoop{{<stringification of token 1>}...{<stringification of token k-1>}}%
%% {<action>}%
%% {<token k>...<token n>}
%%.............................................................................
\newcommand\StringifyNActLoop[3]{%
\UD@CheckWhetherNull{#3}{%
\UD@firstoftwo{ }{}#1%
}{%
\UD@CheckWhetherBrace{#3}{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{%
\UD@StringifyClosingBrace{#3}%
}{%
\expandafter\StringifyNActLoop\expandafter{%
\romannumeral0%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{\UD@StringifyOpeningBrace{#3}}{\StringifyNActLoop{#1}{#2}}%
}{#2}%
}%
}{%
\UD@CheckWhetherLeadingSpace{#3}{%
\expandafter\UD@PassFirstToSecond\expandafter{\UD@removespace#3}{%
\StringifyNActLoop{#1#2{ }}{#2}%
}%
}{%
\expandafter\UD@PassFirstToSecond\expandafter{\UD@firstoftwo{}#3}{%
\expandafter\StringifyNActLoop\expandafter{%
\romannumeral0%
\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\UD@PassFirstToSecond
\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter{%
\expandafter\expandafter\expandafter\string
\expandafter\UD@Exchange
\romannumeral0\UD@ExtractFirstArgLoop{#3UD@SelDOm}{}%
}{ #1#2}%
}%
{#2}%
}%
}%
}%
}%
}%
%% The promised loop for concatenating stringified tokens - apply as:
%%
%% \romannumeral0%
%% \expandafter\expandafter\expandafter
%% \ConcatenateStringifiedtokens
%% \StringifyNAct{}{<tokens to stringify>}\relax
%%
\newcommand*\ConcatenateStringifiedtokens{%
\ConcatenateStringifiedtokensloop{ }%
}%
\newcommand\ConcatenateStringifiedtokensloop[2]{%
\ifx\relax#2\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{#1}{%
\ConcatenateStringifiedtokensloop{#1#2}%
}%
}%
%%=================== End of code for \StringifyNAct ==========================
\makeatother
\usepackage{xparse}
\makeatletter
\NewDocumentCommand{\man}{om}{%
\UD@CheckWhetherNull{#2}{}{%
\toks@{#2}%
\textbf{\the\toks@}%
}%
\IfNoValueF{#1}{(#1)}%
}%
\NewDocumentCommand{\mani}{}{%
\begingroup
\catcode`\ =12\relax
\catcode`\^^I=12\relax
\maniinner
}%
\NewDocumentCommand{\maniinner}{om}{%
\endgroup
\IfNoValueTF{#1}{%
\expandafter\maniinnerinner\expandafter{%
\romannumeral0%
\expandafter\expandafter\expandafter
\ConcatenateStringifiedtokens
\StringifyNAct{}{#2}\relax
}%
}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral0%
\expandafter\expandafter\expandafter
\ConcatenateStringifiedtokens
\StringifyNAct{}{#2}\relax
}{%
\expandafter\maniinnerinner\expandafter[\expandafter{%
\romannumeral0%
\expandafter\expandafter\expandafter
\ConcatenateStringifiedtokens
\StringifyNAct{}{#1}\relax
}]%
}%
}%
}%
\makeatother
\begingroup
\newcommand\maniinnerinner[1]{%
\endgroup
\NewDocumentCommand{\maniinnerinner}{om}{%
\IfNoValueTF{##1}{%
\scantokens{\man{##2}#1}%
\index{##2@\string\man{##2}}%
}{%
\scantokens{\man[##1]{##2}#1}%
\index{##2@\string\man[##1]{##2}}%
}%
}%
}%
\catcode`\%=12\relax
\maniinnerinner{%}%
\makeindex
\begin{document}
Only an index entry: \index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world
Command and index entry: \mani[1]{ssh-keygen}
Only an index entry: \index{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}
Hello world
Command and index entry: \mani{ssh-keygen-no-optional-argument}
\newcommand\ke{ke}%
\newcommand\one{1}%
Only an index entry: \index{ssh-\ke y\string#gen@\man[\one]{ssh-\ke y\string#gen}}
Hello world
Command and index entry: \mani[\one]{ssh-\ke y\string#gen}
\printindex
\end{document}
根据上面的例子,生成的.idx 文件如下所示:
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}{1}
\indexentry{ssh-keygen-no-optional-argument@\man{ssh-keygen-no-optional-argument}}{1}
\indexentry{ssh-\ke y\string#gen@\man[\one]{ssh-\ke y\string#gen}}{1}
\indexentry{ssh-\ke y\string#gen@\man[\one]{ssh-\ke y\string#gen}}{1}
答案2
带有回忆录的 .idx 文件使用的 -handle名称与LaTeX 2ε-macro 使用的 -handle\write
名称不同。\write
\@wrindex
\write
因此,您需要将索引的“kernel- -handle”名称映射到\write
索引的“memoir- -handle”:
\documentclass{memoir}
\usepackage{xparse}
\makeindex
\NewDocumentCommand\man{om}{%
\textbf{#2}\IfNoValueF{#1}{(#1)}%
}
\makeatletter
\newcommand*{\mani}{%
\@bsphack
\begingroup
\@sanitize
\@mani
}
\NewDocumentCommand{\@mani}{om}{%
\@ifundefined{@indexfile}{%
\expandafter\let\expandafter\@indexfile\csname\jobname @idxfile\endcsname
}{}%
\IfNoValueTF{#1}{%
\man{#2}%
\@wrindex{#2@\string\man{#2}}%
}{%
\man[#1]{#2}%
\@wrindex{#2@\string\man[#1]{#2}}%
}%
}
\makeatletter
\begin{document}
Two separate commands: \man[1]{ssh-keygen}\index{ssh-keygen@\man[1]{ssh-keygen}}
Hello world
One command: \mani[1]{ssh-keygen}
\printindex
\end{document}
.idx 文件如下所示:
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
\indexentry{ssh-keygen@\man[1]{ssh-keygen}}{1}
请注意 - 与我的另一个答案中提出的标记化后字符串化的方法不同 -\@sanitize
如果的参数\mani
包含控制符号标记\{
和/或控制符号标记,则不会处理花括号的平衡\}
。