9

I want a command that does the following:

\variation{1}{aaa;bbb;ccc} ----> aaa
\variation{2}{aaa;bbb;ccc} ----> bbb
\variation{3}{aaa;bbb;ccc} ----> ccc

The string in the second input can be a list with as many ; as needed (perhaps none at all), not just three as shown. I have almost got what I needed using xstring but it contains superfluous inputs and notation and is basically not elegant!

\newcommand{\variation}[3]{\StrBetween[#1,#2]{;#3;}{;}{;}}

Hence this is what I get

\variation{1}{2}{aaa;bbb;ccc} ----> aaa

It's the second input is the superfluous one.

1

4 Answers 4

6

The solution with xstring and eTeX's \numexpr:

\documentclass{article}

\usepackage{xstring}

\newcommand*{\variation}[2]{%
  \StrBetween[{#1},{\numexpr(#1)+1\relax}]{;#2;}{;}{;}%
}

\newcommand*{\test}[1]{%
  \detokenize{#1} $\rightarrow$ #1%
}

\begin{document}
\noindent
\ttfamily
\test{\variation{1}{aaa;bbb;ccc}}\\
\test{\variation{2}{aaa;bbb;ccc}}\\
\test{\variation{3}{aaa;bbb;ccc}}
\end{document}

Result

Update

  • \StrBetween knows a final optional argument, that allows to store the result in a macro. Because I did not know, how \variation is used, the macro inherited the final optional argument. The usual looking for optional arguments in LaTeX (based on \@ifnextchar) removes following white spaces to find the opening square bracket. \relax stops the looking for [ and the space after the argument braces of \variation is kept.

  • Second problem from the comments: Package xstring adds spaces (uncommented line ends) after numerical comparisons. In case of explicit numbers, the space is ignored and the parsing of the number finished. But if the number is given by an integer register or in this case by a complete \numexpr, the space is not needed and kept.

    There are several ways to cure this:

    • Bug report.
    • \the\numexpr instead of \numexpr
    • Expanding the arguments and convert to explicit numbers before. Then \StrBetween only sees the expanded numbers.

New test file:

\documentclass{article}

\usepackage{xstring}

\newcommand*{\variation}[2]{%
  \edef\variationNext{%
    \noexpand\StrBetween[%
      \the\numexpr(#1)\relax,%
      \the\numexpr(#1)+1\relax
    ]%
  }%
  \variationNext{;#2;}{;}{;}\relax
}

\newcommand*{\test}[1]{%
  \detokenize\expandafter{\string#1} $\rightarrow$ [#1]%
}

\begin{document}
\noindent
\ttfamily
\test{\variation{1}{aaa;bbb;ccc}}\\
\test{\variation{2}{aaa;bbb;ccc}}\\
\test{\variation{3}{aaa;bbb;ccc}}
\end{document}

Result

9
  • This works a treat! But what's eTex?
    – Geoff
    Oct 19, 2012 at 20:03
  • @Geoff See The eTeX manual. You have it already, because package xstring requires it. Oct 19, 2012 at 21:08
  • I have seen that your macro (albeit one now modified by me) gives an unwanted space. \newcommand*{\variation}[2][1]{\StrBetween[{#1},{\numexpr(#1)+1\relax}]{;#2;}{;}{;}\xspace}. Now hello \variation[1]{aaa;bbb;ccc} world doesn't typeset as it should hello aaa world.
    – Geoff
    Oct 20, 2012 at 13:13
  • @Geoff Remove \xspace, it does not make any sense here, because the space after the argument braces is already kept. Oct 20, 2012 at 14:05
  • Without the \xspace I get as output hello aaaworld. It is the space between hello and aaa that is too large.
    – Geoff
    Oct 20, 2012 at 19:21
4

Here's an option using xparse/expl3 rather than xstring (if you're only interested in an xstring solution, then just take this as comparison). The variation macro takes an optional first argument that determines the character to split the list at, the default is ;.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\msg_new:nnn {variation}{index~out~of~bounds}{You~have~requested~an~item~that~doesn't~exist}

\NewDocumentCommand{\variation}{ O{;} m m }
 {
    % split arg #3 at every occurrence of #1 (default #1=;) and store in sequence
    \seq_set_split:Nnn \l_tmpa_seq {#1}{#3}

    % compare arg #2 to the length of the sequence
    \int_compare:nTF {#2>\seq_count:N \l_tmpa_seq}

        % if arg #2 > sequence length, then the item doesn't exist
        {\msg_warning:nnn {variation}{index~out~of~bounds}{}}

        % if arg #2 <= sequence length, then return requested item.
        {\seq_item:Nn \l_tmpa_seq {#2}}
 }

\ExplSyntaxOff

\begin{document}

\variation{3}{aaa;bbb;ccc;ddd;eee}
\variation{1}{aaa}
\variation{2}{aaa}

\end{document}
10
  • +1; it would be easy to add a test for a too large first argument.
    – egreg
    Oct 19, 2012 at 19:53
  • I cannot get your example to work. It gives me an empty document.
    – Geoff
    Oct 19, 2012 at 19:54
  • @egreg warning added :)
    – Scott H.
    Oct 19, 2012 at 19:56
  • I'm not sure why it wouldn't work for you Geoff, are you getting any errors? You may need to manually add the xparse and expl3 packages if your setup doesn't add missing packages on the fly.
    – Scott H.
    Oct 19, 2012 at 19:58
  • @Geoff It may be that your TeX distribution is too old. What are you using?
    – egreg
    Oct 19, 2012 at 20:00
4

Just a variation on Scott H.'s solution, for showing a perhaps better way to deal with the problem.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\NewDocumentCommand{\variation}{ s O{;} m m }
 {
  \IfBooleanTF{#1}
   {
    \geoff_variation_check:nnn { #2 } { #3 } { #4 }
   }
   {
    \geoff_variation_nocheck:nnn { #2 } { #3 } { #4 }
   }
 }

\msg_new:nnn { variation }
 { index~out~of~bounds }
 { You~have~requested~an~item~that~doesn't~exist }

\seq_new:N \l__geoff_item_list_seq

\cs_new_protected:Npn \geoff_variation_nocheck:nnn #1 #2 #3
 {
  \seq_set_split:Nnn \l__geoff_item_list_seq { #1 } { #3 }
  \seq_item:Nn \l__geoff_item_list_seq { #2 }
 }

\cs_new_protected:Npn \geoff_variation_check:nnn #1 #2 #3
 {
  \seq_set_split:Nnn \l__geoff_item_list_seq { #1 } { #3 }
  \int_compare:nTF { #2 > \seq_count:N \l__geoff_item_list_seq }
   { \msg_warning:nn { variation }{ index~out~of~bounds } }
   { \seq_item:Nn \l__geoff_item_list_seq {#2} }
 }

\ExplSyntaxOff

\begin{document}

\variation{3}{aaa;bbb;ccc;ddd;eee}

\variation[,]{3}{aaa,bbb,ccc,ddd,eee}

\variation{1}{aaa}

\variation{2}{aaa}

\variation*{2}{aaa}

\end{document}

The \NewDocumentCommand instruction should be used to pass the arguments to an "internal" function. In order to better show how, I introduce a *-variant, so the control is passed either to \geoff_variation_check:nnn or to \geoff_variation_nocheck:nnn. The internal functions should be protected, since they do unexpandable actions (\set...).

The bulk of the action is splitting the argument into a sequence and returning the needed sequence item:

\seq_set_split:Nnn <sequence> { <tokens> } { <token list> }

\seq_item:Nn <sequence> { <integer expression> }
0

In the general case, the machine may generate a list like

{aaa;bbb; ; ccc}

which will yield a result not intended

\test{\variation{3}{aaa;bbb; ; ccc}} -> [ ]

Also, the parser (; in this case) may be active. These call for the prior normalization of the list.

\documentclass{article}
\usepackage{catoptions}
\makeatletter
% \getstrofnumber{<parser>}{<nr>}{<list>}
\robust@def*\getstrofnumber#1#2#3{%
  \begingroup
  \@tempcnta\z@
  \def\@getstrofnumber##1#1##2\get@nnil{%
    \ifstrcmpTF{##1}\get@nil{%
      \endgroup
      \@latexerr{Item of serial number '#2' not found}\@ehd
    }{%
      \advance\@tempcnta\@ne
      \ifnum#2=\@tempcnta
        \endgroup##1%
        \expandafter\@gobble
      \else
        \expandafter\@iden
      \fi
      {\@getstrofnumber##2\get@nnil}%
    }%
  }%
  \edef\alist{\unexpanded{#3}}%
  \cpt@sttrue\cpt@csvnormalize[#1]\alist
  \expandafter\@getstrofnumber\alist#1\get@nil#1\get@nnil
}
\makeatother

\begin{document}
\def\pput#1{\char`{#1\char`} \space$\rightarrow$\space}
\noindent
\ttfamily
\pput{aaa;bbb; ; ccc}\getstrofnumber{;}{1}{aaa;bbb; ; ccc}\\
\pput{aaa;;bbb;ccc}\getstrofnumber{;}{2}{aaa;;bbb;ccc}\\
\pput{aaa;bbb;ccc}\getstrofnumber{;}{3}{aaa;bbb;ccc}\\
% \pput{aaa;bbb;ccc}\getstrofnumber{;}{4}{aaa;bbb;ccc}% error

\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .