
編輯2:在底部,我在合併此處的答案後更新了問題:使用 {} 時索引的 newcommand 會插入不需要的空格
我建立了一個函數來以粗體顯示命令,並可選擇在括號中給出手冊頁部分。
\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 檔案中的內容並對其進行標記來獲取其參數。 (\@sanitize
-category-code-régime 表示:空格字元、\
、$
、&
、 、#
、^
,_
且類別代碼為 12(其他)。)這有幾個原因 - 例如%
,~
- 避免可擴展令牌不必要的擴充。
- 避免未擴充將控製字令牌寫入外部檔案時附加空格字元。
但是使用您的命令,\mani
該命令\index
確實會從 傳遞其參數\mani
。當\mani
gets/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
其中「令牌的字串化」是指應用於相關令牌的結果。
由於\romannumeral
-擴展,結果是透過觸發兩個擴展步驟(例如,透過兩個\expandafter
-鏈)來傳遞的。
我建議命令\mani
在正常的類別代碼規則下讀取和標記其參數,但空格字元(也可能是水平製表符,可^^I
與 TeX 的^^
-notation 一樣尋址)屬於類別代碼 12(其他) ),然後應用於\StringifyNAct
參數,然後將其結果傳遞給\index
- 命令並嵌套在-\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
包含 control-symbol-token\{
和/或 control-symbol-token ,則不會處理大括號的平衡\}
。