TransWikia.com

Extra space in index entry

TeX - LaTeX Asked by Tommiie on May 13, 2021

Edit 2: At the bottom I have updated the question after incorporating the answer from here: newcommand for index inserts unwanted spaces when using {}

I created a function to display a command in bold with optionally giving the man page section in parenthesis.

usepackage{xparse}
newcommand*{man}[2][]{%
   textbf{#2}IfNoValueF{#1}{(#1)}%
}

Now I also want to be able to create an index entry for these commands. So I created a wrapper command:

newcommand*{mani}[2][]{%
   man[#1]{#2}%
   index{#2@man[#1]{#2}}%
}

However, sometimes I want to create an index entry using the same formatting, but not print the command in the running text. Here is a 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}

The file output.idx contains two lines, the second containing an additional space. The question thus is, why is there an additional space and how can I fix it so there is no additional space?

indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}
indexentry{ssh-keygen@man [1]{ssh-keygen}}{1}

Edit 1: I’ve added a screenshot of the result of this extra space: two identical index entries are printed.

Double index entry

Update

I have updated my code as per the provided solution but I now get an error message which I do not understand.

documentclass{memoir}
usepackage{xparse}
makeindex
NewDocumentCommandman{om}{%
   textbf{#2}IfNoValueF{#1}{(#1)}%
}
makeatletter
newcommand*{mani}{%
   @bsphack
   begingroup
   @sanitize
   @mani
}
newcommand*{@mani}[2][]{%
   man[#1]{#2}%
   @wrindex{#2@stringman[#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 states:

The compiler is having trouble understanding a command you have used. Check that the command is spelled correctly. If the command is part of a package, make sure you have included the package in your preamble using usepackage{…}.

The log file reports:

! 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.)

2 Answers

The command index is expected to get its argument by reading and tokenizing things from the .tex-input file under @sanitize-category-code-régime. (@sanitize-category-code-régime means: Space-character, , $, &, #, ^, _, % and ~ have category code 12(other).) This is for several reasons—e.g.,

  • avoiding unwanted expansion of expandable tokens.
  • avoiding the appending of a space-character when unexpanded-writing a control-word-token to external file.

But with your command mani the command index does get its argument passed from mani. When mani gathers/composes index's argument, the tokens forming that argument are not tokenized under @sanitize-category-code-régime but are tokenized under normal category-code-régime.

Beneath other things tokenizing under normal category-code-régime implies that phrases like man get tokenized as control-word-tokens, not as character-sequences , m, a, n. When control-word-tokens get written unexpanded to text-file, e.g., some .idx-file belonging to the process of creating the index, a space-character will be appended. I.e., the character-sequence , m, a, n, ⟨space character⟩ will be written.

Within the definition of mani you can apply string to the command man in order to turn it into a sequence of character-tokens. (Hereby it is relied on only one input-character having category code 0 (escape) and the value of the integer-parameter escapechar being equal to the number of the code-point of that character in the TeX-engine's internal character-encoding-scheme. Usually the backslash-character is the only character of category code 0(escape) and usually escapechar has the value 92 which is the number of the backslash-character's code-point in the TeX-engine's internal character-encoding-scheme.)

documentclass{memoir}
usepackage{xparse}
NewDocumentCommand{man}{om}{%
   textbf{#2}IfNoValueF{#1}{(#1)}%
}
NewDocumentCommand{mani}{om}{%
   IfNoValueTF{#1}{%
      man{#2}%
      index{#2@stringman{#2}}%
    }{%
      man[#1]{#2}%
      index{#2@stringman[#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}

With the example above the resulting .idx-file looks like this:

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}

With this approach, the only thing "stringified" within the argument of index, which is supplied via the macro mani, is the phrase man.

Things that come from mani's first or second argument are not stringified. In case the sets of tokens supplied via these arguments also contain control-word-tokens, you might get unwanted spaces here, too.

I can offer a routine StringifyNAct which applies string to each token within its 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 by triggering two expansion-steps (e.g., via two expandafter-chains).

I suggest the command mani to read and tokenize its arguments under normal category-code-régime, but with the space-character (and probably also the horizontal-tab-character, addressable as ^^I with TeX's ^^-notation) being of category-code 12(other), then to apply StringifyNAct to the arguments, then passing the result thereof to the index-command and—nested in scantokens—to the man-command:

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
%%=============================================================================
newcommandUD@firstoftwo[2]{#1}%
newcommandUD@secondoftwo[2]{#2}%
newcommandUD@PassFirstToSecond[2]{#2{#1}}%
newcommandUD@Exchange[2]{#2#1}%
newcommandUD@removespace{}UD@firstoftwo{defUD@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>
newcommandUD@CheckWhetherNull[1]{%
  romannumeral0expandafterUD@secondoftwostring{expandafter
  UD@secondoftwoexpandafter{expandafter{string#1}expandafter
  UD@secondoftwostring}expandafterUD@firstoftwoexpandafter{expandafter
  UD@secondoftwostring}expandafterexpandafterUD@firstoftwo{ }{}%
  UD@secondoftwo}{expandafterexpandafterUD@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>}%
newcommandUD@CheckWhetherBrace[1]{%
  romannumeral0expandafterUD@secondoftwoexpandafter{expandafter{%
  string#1.}expandafterUD@firstoftwoexpandafter{expandafter
  UD@secondoftwostring}expandafterexpandafterUD@firstoftwo{ }{}%
  UD@firstoftwo}{expandafterexpandafterUD@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>}%
newcommandUD@CheckWhetherLeadingSpace[1]{%
  romannumeral0UD@CheckWhetherNull{#1}%
  {expandafterexpandafterUD@firstoftwo{ }{}UD@secondoftwo}%
  {expandafterUD@secondoftwostring{UD@CheckWhetherLeadingSpaceB.#1 }{}}%
}%
newcommandUD@CheckWhetherLeadingSpaceB{}%
longdefUD@CheckWhetherLeadingSpaceB#1 {%
  expandafterUD@CheckWhetherNullexpandafter{UD@secondoftwo#1{}}%
  {UD@Exchange{UD@firstoftwo}}{UD@Exchange{UD@secondoftwo}}%
  {UD@Exchange{ }{expandafterexpandafterexpandafterexpandafter
   expandafterexpandafterexpandafter}expandafterexpandafter
   expandafter}expandafterUD@secondoftwoexpandafter{string}%
}%
%%-----------------------------------------------------------------------------
%% Extract first inner undelimited argument:
%%
%%   UD@ExtractFirstArg{ABCDE} yields  {A}
%%
%%   UD@ExtractFirstArg{{AB}CDE} yields  {AB}
%%.............................................................................
newcommandUD@RemoveTillUD@SelDOm{}%
longdefUD@RemoveTillUD@SelDOm#1#2UD@SelDOm{{#1}}%
newcommandUD@ExtractFirstArg[1]{%
  romannumeral0%
  UD@ExtractFirstArgLoop{#1UD@SelDOm}%
}%
newcommandUD@ExtractFirstArgLoop[1]{%
  expandafterUD@CheckWhetherNullexpandafter{UD@firstoftwo{}#1}%
  { #1}%
  {expandafterUD@ExtractFirstArgLoopexpandafter{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:
%%.............................................................................
newcommandUD@StringifyOpeningBrace[1]{%
  romannumeral0%
  expandafterUD@ExtractFirstArgLoopexpandafter{%
    romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
    expandafterexpandafter
    expandafter            {%
    expandafterUD@firstoftwo
    expandafter{%
    expandafter}%
    romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
    expandafterstring
    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:
%%.............................................................................
newcommandUD@StringifyClosingBrace[1]{%
   romannumeral0expandafterexpandafterexpandafter
                 UD@StringifyClosingBraceloop
                 UD@ExtractFirstArg{#1}{#1}%
}%
newcommandUD@CheckWhetherStringifiedOpenBraceIsSpace[1]{%
%% This can happen when character 32 (space) has catcode 1...
  expandafterUD@CheckWhetherLeadingSpaceexpandafter{%
    romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
    expandafterUD@secondoftwo
    expandafter{%
    expandafter}%
    expandafter{%
    romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
    expandafterUD@firstoftwo
    expandafter{%
    expandafter}%
    romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
    expandafterstring
    expandafter}%
    string#1%
  }%
}%
newcommandUD@TerminateStringifyClosingBraceloop[2]{%
  UD@Exchange{ }{expandafterexpandafterexpandafter}%
  expandafterexpandafter
  expandafter{%
  expandafterstring      
  romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
  expandafter#1%
  string#2%
  }%
}%
newcommandUD@StringifyClosingBraceloopRemoveElement[4]{%
  expandafterUD@PassFirstToSecondexpandafter{expandafter
  {romannumeral0expandafterUD@secondoftwostring}{}%
    UD@CheckWhetherStringifiedOpenBraceIsSpace{#4}{%
      UD@Exchange{UD@removespace}%
    }{%
      UD@Exchange{UD@firstoftwoexpandafter{expandafter}}%
    }{%
      UD@Exchange{ }{expandafterexpandafterexpandafter}%
      expandafter#1%
      romannumeral0UD@Exchange{ }{expandafterexpandafterexpandafter}%
      expandafter
    }%
    string#4%
  }{expandafterUD@StringifyClosingBraceloopexpandafter{#2#3}}%
}%
newcommandUD@StringifyClosingBraceloop[2]{%
  UD@CheckWhetherNull{#1}{%
    UD@CheckWhetherStringifiedOpenBraceIsSpace{#2}{%
      UD@TerminateStringifyClosingBraceloop{UD@removespace}%
    }{%
      UD@TerminateStringifyClosingBraceloop{UD@firstoftwoexpandafter{expandafter}}%
    }%
    {#2}%
  }{%
    UD@CheckWhetherLeadingSpace{#1}{%
      UD@StringifyClosingBraceloopRemoveElement
      {UD@removespace}{UD@removespace}%
    }{%
      UD@StringifyClosingBraceloopRemoveElement
      {UD@firstoftwoexpandafter{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.
%%.............................................................................
newcommandStringifyNAct{%
  romannumeral0StringifyNActLoop{}%
}%
%%.............................................................................
%% StringifyNActLoop{{<stringification of token 1>}...{<stringification of token k-1>}}%
%%                   {<action>}%
%%                   {<token k>...<token n>}
%%.............................................................................
newcommandStringifyNActLoop[3]{%
  UD@CheckWhetherNull{#3}{%
    UD@firstoftwo{ }{}#1%
  }{%
    UD@CheckWhetherBrace{#3}{%
      expandafterexpandafterexpandafterUD@Exchange
      expandafterexpandafterexpandafter{%
        UD@StringifyClosingBrace{#3}%
      }{%
        expandafterStringifyNActLoopexpandafter{%
          romannumeral0%
          expandafterexpandafterexpandafterUD@Exchange
          expandafterexpandafterexpandafter{UD@StringifyOpeningBrace{#3}}{StringifyNActLoop{#1}{#2}}%
        }{#2}%
      }%
    }{%
      UD@CheckWhetherLeadingSpace{#3}{%
        expandafterUD@PassFirstToSecondexpandafter{UD@removespace#3}{%
          StringifyNActLoop{#1#2{ }}{#2}%
        }%
      }{%
        expandafterUD@PassFirstToSecondexpandafter{UD@firstoftwo{}#3}{%
          expandafterStringifyNActLoopexpandafter{%
             romannumeral0%
             expandafterexpandafterexpandafterexpandafterexpandafterexpandafterexpandafterUD@PassFirstToSecond
             expandafterexpandafterexpandafterexpandafterexpandafterexpandafterexpandafter{%
               expandafterexpandafterexpandafterstring
               expandafterUD@Exchange
               romannumeral0UD@ExtractFirstArgLoop{#3UD@SelDOm}{}%
             }{ #1#2}%
          }%
          {#2}%
        }%
      }%
    }%
  }%
}%
%% The promised loop for concatenating stringified tokens - apply as:
%%
%%      romannumeral0%
%%      expandafterexpandafterexpandafter
%%      ConcatenateStringifiedtokens
%%      StringifyNAct{}{<tokens to stringify>}relax
%%
newcommand*ConcatenateStringifiedtokens{%
  ConcatenateStringifiedtokensloop{ }%
}%
newcommandConcatenateStringifiedtokensloop[2]{%
  ifxrelax#2expandafter@firstoftwoelseexpandafter@secondoftwofi
  {#1}{%
    ConcatenateStringifiedtokensloop{#1#2}%
  }%
}%
%%=================== End of code for StringifyNAct ==========================
makeatother

usepackage{xparse}

makeatletter
NewDocumentCommand{man}{om}{%
   UD@CheckWhetherNull{#2}{}{%
     toks@{#2}%
     textbf{thetoks@}%
   }%
   IfNoValueF{#1}{(#1)}%
}%
NewDocumentCommand{mani}{}{%
  begingroup
  catcode` =12relax
  catcode`^^I=12relax
  maniinner
}%
NewDocumentCommand{maniinner}{om}{%
   endgroup
   IfNoValueTF{#1}{%
      expandaftermaniinnerinnerexpandafter{%
        romannumeral0%
        expandafterexpandafterexpandafter
        ConcatenateStringifiedtokens
        StringifyNAct{}{#2}relax
      }%
    }{%
      expandafterUD@PassFirstToSecondexpandafter{%
        romannumeral0%
        expandafterexpandafterexpandafter
        ConcatenateStringifiedtokens
        StringifyNAct{}{#2}relax
      }{%
        expandaftermaniinnerinnerexpandafter[expandafter{%
          romannumeral0%
          expandafterexpandafterexpandafter
          ConcatenateStringifiedtokens
          StringifyNAct{}{#1}relax
        }]%
      }%
    }%
}%
makeatother
begingroup
newcommandmaniinnerinner[1]{%
  endgroup
  NewDocumentCommand{maniinnerinner}{om}{%
     IfNoValueTF{##1}{%
        scantokens{man{##2}#1}%
        index{##2@stringman{##2}}%
      }{%
        scantokens{man[##1]{##2}#1}%
        index{##2@stringman[##1]{##2}}%
      }%
  }%
}%
catcode`%=12relax
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}

newcommandke{ke}%
newcommandone{1}%

Only an index entry: index{ssh-ke ystring#gen@man[one]{ssh-ke ystring#gen}}
Hello world

Command and index entry: mani[one]{ssh-ke ystring#gen}


printindex
end{document}

With the example above the resulting .idx-file looks like this:

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 ystring#gen@man[one]{ssh-ke ystring#gen}}{1}
indexentry{ssh-ke ystring#gen@man[one]{ssh-ke ystring#gen}}{1}

Answered by Ulrich Diez on May 13, 2021

The name of the write-handle used for the .idx-file with memoir differs from the name of the write-handle used with the LaTeX 2ε-macro @wrindex.

Thus you need to map the name of the "kernel-write-handle" for the index to the "memoir-write-handle" for the index:

documentclass{memoir}
usepackage{xparse}
makeindex
NewDocumentCommandman{om}{%
   textbf{#2}IfNoValueF{#1}{(#1)}%
}
makeatletter
newcommand*{mani}{%
   @bsphack
   begingroup
   @sanitize
   @mani
}
NewDocumentCommand{@mani}{om}{%
   @ifundefined{@indexfile}{%
     expandafterletexpandafter@indexfilecsnamejobname @idxfileendcsname
   }{}%
   IfNoValueTF{#1}{%
     man{#2}%
     @wrindex{#2@stringman{#2}}%
   }{%
     man[#1]{#2}%
     @wrindex{#2@stringman[#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}

The .idx-file looks like this:

indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}
indexentry{ssh-keygen@man[1]{ssh-keygen}}{1}

Be aware that—unlike the approach of stringification after tokenization presented in my other answer—@sanitize does not handle the balancing of curly braces in case the arguments of mani are to contain the control-symbol-token { and/or the control-symbol-token }.

Answered by Ulrich Diez on May 13, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP