7

Assuming I have a macro

\def\x{This is a string 2015/12.}

I'd like to have a macro \extract{string}{number} that returns the character at position number in the string only with primitives. I've looked at the xstring package but am interested in a pure primitive solution. This should be simple but I'm having a really hard time figuring it out. I assume it should work with expand.

  • TeX doesn't really have strings, at least not in the sense other languages do. Do you want to first detokenize (turn into tokens of catcode 12 only) and return those or do you want to extract on a token basis? (I'm thinking of input like foo\bar which has four tokens but seven detokenized characters.) – Joseph Wright Dec 28 '16 at 15:10
  • The string (sequence of words) I get as input is untokenized. I don't know how to tokenize it first (i.e., this would only make it more complicated I guess). – Mathias Payer Dec 28 '16 at 16:31
  • The moment you grab an argument in TeX, it is tokenized using the current category codes. – Joseph Wright Dec 28 '16 at 16:40
2

Assuming your string only contains printable ASCII characters, here's a quite clumsy implementation. The last example shows that braced groups are treated as single items.

\catcode`@=11

\def\extract{\futurelet\next\extract@save}
\def\extract@save{%
  \ifx\next[%
    \expandafter\extract@save@opt
  \else
    \let\extract@return\@firstofone
    \expandafter\extract@
  \fi
}
\def\extract@save@opt[#1]{%
  \def\extract@return##1{\def#1{##1}}%
  \extract@
}
\def\extract@#1#2{%
  \edef\extract@string{#1}%
  \extract@loop=\z@
  \extract@max=#2\relax
  \expandafter\extract@i\extract@string\extract
}
\def\extract@i{%
  \advance\extract@loop\@ne
  \futurelet\next\extract@ii
}
\def\extract@ii{%
  \ifx\next\extract
    \expandafter\@gobble
  \else
    \expandafter\extract@iii
  \fi}
\def\extract@iii{%
  \ifx\next\space@token
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\extract@check@space}%
  {\extract@check}%
}
\def\extract@check@space{%
  \ifnum\extract@loop=\extract@max
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\extract@return{ }\extract@finish}%
  {\expandafter\extract@i\@firstofone}%
}
\def\extract@check#1{%
  \ifnum\extract@loop=\extract@max
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {\extract@return{#1}\extract@finish}%
  {\extract@i}%
}
\def\extract@finish#1\extract{}
\newcount\extract@loop
\newcount\extract@max
\long\def\@firstoftwo#1#2{#1}
\long\def\@secondoftwo#1#2{#2}
\long\def\@firstofone#1{#1}
\long\def\@gobble#1{}
\begingroup\def\\ \\{\endgroup\let\space@token= }\\ \\
\catcode`@=12

X\extract{abc def}{3}X (should be c)

X\extract{abc def}{4}X (should be space)

X\extract{abc def}{5}X (should be d)

X\extract{abc def}{8}X (should be empty)

X\extract{abc }{4}X (should be space)

\def\mystring{abc def}

X\extract\mystring{7}X (should be f)

\extract[\foo]\mystring{6}

{\tt\meaning\foo}

\extract[\foo]{A{BC}D}{2}

{\tt\meaning\foo}

\bye

Without the optional argument, the item is just returned in the input stream; with the optional argument it is saved in the control sequence given between brackets.

enter image description here

Of course I'd do it quite differently. With this code, \extract is fully expandable as shown with the final \edef example. Note that you can also count backwards. The code requires an e-TeX engine, so not Knuth TeX.

\input expl3-generic

\ExplSyntaxOn
\cs_new:Npn \extract #1 #2
 {
  \str_item:fn { #1 } { #2 }
 }
\cs_generate_variant:Nn \str_item:nn {f}
\ExplSyntaxOff

X\extract{abc def}{3}X (should be c)

X\extract{abc def}{4}X (should be space)

X\extract{abc def}{5}X (should be d)

X\extract{abc def}{8}X (should be empty)

X\extract{abc }{4}X (should be space)

\def\mystring{abc def}

X\extract\mystring{7}X (should be f)

X\extract\mystring{-2}X (should be e)

\edef\foo{\extract\mystring{6}}

{\tt\meaning\foo}

\bye

enter image description here

2

Something like this? Shown both with ignoring and with counting spaces. Set up for pdftex, but with commented lines for pdflatex.

%\documentclass{article}
\def\extract{\catcode`\ =\active\extractx}
\def\extractx#1#2{\def\extractcount{#2}\expandafter\extracthelp#1\relax}
\def\extracthelp#1#2\relax{%
  \edef\extractcount{\the\numexpr\extractcount-1\relax}%
  \ifnum\extractcount=0\relax``#1''\else%
    \ifx\relax#2\relax[EOF]\else\extracthelp#2\relax\fi\fi%
  }
%\begin{document}
Spaces ignored: \def\x{This is a string 2015/12.}

\extract{\x}{1}

\extract{\x}{5}

\extract{\x}{6}

\extract{\x}{22}

Spaces counted: {\catcode`\ =\active \gdef\x{This is a string 2015/12.}}

\extract{\x}{1}

\extract{\x}{5}

\extract{\x}{6}

\extract{\x}{22}

%\end{document}
\bye

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.