5

I need to treat input differently, depending on whether it contains a space or a hyphen. That is, the following

\foo{foobar}
\foo{foo bar baz}
\foo{foo bar-baz}
\foo{foo-bar baz}
\foo{foo-bar-baz}

should become

\fooA{foobar}
\fooB{foo}\fooC{bar baz}
\fooB{foo}\fooC{bar-baz}
\fooD{foo}\fooE{bar baz}
\fooD{foo}\fooE{bar-baz}

How can I achieve that, i.e., how to define \foo?

2
  • 1
    algorithm is: fooA = no space or hyphen, fooB = space first, fooD = hypen first, fooC = remainder after B, fooE = remainder after D ? – David Carlisle Sep 7 '16 at 17:14
  • I've taken the liberty of modifying the ordering of the lines in your examples, to (hopefully) make it easier to discern the conditions that lead to outcomes \fooA, \fooB\fooC, and \fooD\fooE. Feel free to revert if you prefer the original layout. – Mico Sep 8 '16 at 5:04
2

Here is a good place to introduce the listofitems package for parsing input. I had recently introduced a package getargs, and almost immediately received an email from Christian Tellechea (http://ctan.org/author/tellechea) pointing out a number of flaws with the package. But more importantly, my deficient package inspired him to write a better one: listofitems, which was even more recently released (http://ctan.org/pkg/listofitems)

It is an extremely powerful and well written package that can perform nested parsing of input lists. In this example, we don't use nesting, but a neat feature of parsing with two different characters simultaneously: hyphen (-) or space ().

\documentclass{article}
\usepackage{listofitems}
\newcommand\foo[1]{%
  \setsepchar{-|| }%
  \readlist\foobar{#1}%
  \ifnum\foobarlen=1\fooA{#1}\else%
    \ifnum\foobarlen=3%
      \if\foobarsep[1]-%
        \fooD{\foobar[1]}\fooE{\foobar[2]\foobarsep[2]\foobar[3]}%
      \else
        \fooB{\foobar[1]}\fooC{\foobar[2]\foobarsep[2]\foobar[3]}%
      \fi
    \else%
      Invalid argument!
    \fi%
  \fi%
}
\def\fooA#1{\textbackslash fooA\{#1\}\par}
\def\fooB#1{\textbackslash fooB\{#1\}}
\def\fooC#1{\textbackslash fooC\{#1\}\par}
\def\fooD#1{\textbackslash fooD\{#1\}}
\def\fooE#1{\textbackslash fooE\{#1\}\par}
\begin{document}
\foo{foobar}
\foo{foo bar baz}
\foo{foo bar-baz}
\foo{foo-bar baz}
\foo{foo-bar-baz}
\end{document}

enter image description here

The listofitems package is even set to work in plain tex:

\input listofitems.tex
\def\foo#1{%
  \setsepchar{-|| }%
  \readlist\foobar{#1}%
  \ifnum\foobarlen=1\relax\fooA{#1}\else%
    \ifnum\foobarlen=3%
      \if\foobarsep[1]-%
        \fooD{\foobar[1]}\fooE{\foobar[2]\foobarsep[2]\foobar[3]}%
      \else
        \fooB{\foobar[1]}\fooC{\foobar[2]\foobarsep[2]\foobar[3]}%
      \fi
    \else%
      Invalid argument!
    \fi%
  \fi%
}

\def\fooA#1{FooA[#1]\par}
\def\fooB#1{FooB[#1]}
\def\fooC#1{FooC[#1]\par}
\def\fooD#1{FooD[#1]}
\def\fooE#1{FooE[#1]\par}

\foo{foobar}
\foo{foo bar baz}
\foo{foo bar-baz}
\foo{foo-bar baz}
\foo{foo-bar-baz}
\bye

Here's a fun example (in LaTeX) that shows nested parsing. The * form of the \readlist macro strips leading/trailing spaces from each list item. While not the case here, one can invoke \ignoreemptyitems or \reademptyitems (the default) to decide how empty items in the list should be treated.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{listofitems}
\usepackage{amssymb}
\begin{document}
% LEVEL 1 PARSE CHAR: \\
% LEVEL 2 PARSE CHAR: &
% LEVEL 3 PARSE CHARS: + OR -
\setsepchar{\\/&/+||-}
\readlist*\foo{
y &= mx + b\\
E &= mc^2\\
y &= Ax^2 + \mathbb{B}x - \mathbb{C }
}

\showitems*\foo\par
\showitems*\foo[1]\par
\showitems*\foo[2]\par
\showitems*\foo[3]... $\foo[3,2]$\par
1st term: $\foo[3,2,1]$\par
Following separator: $\foosep[3,2,1]$\par
2nd term: $\foo[3,2,2]$\par
Following separator: $\foosep[3,2,2]$\par
3rd term: $\foo[3,2,3]$
\end{document}

enter image description here

2
  • I used foo-bar-baz etc. with three parts only to illustrate that I want to split at the fist hyphen/space. It ought to work as well with foo-bar and also with foo-bar-baz-oompa-loompa ... – Hagen von Eitzen Sep 9 '16 at 6:25
  • @HagenvonEitzen I now understand that you need something more general. However, it is tough for us to infer the rules you want applied to these other cases. I think it would help if you edited your question to either give us the exhaustive list of rule cases you need or else to be able to clearly specify a general rule that can be adapted for all cases. – Steven B. Segletes Sep 9 '16 at 10:39
3

An implementation in expl3; if there is a hyphen, we check whether the text before the hyphen contains a space, otherwise we follow the BC route; if there's no hyphens the text is split at spaces and the DE route is followed. No space and no hyphen will follow the A route.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\foo}{m}
 {% call an internal function
  \hagen_foo:n { #1 }
 }

\cs_new_protected:Nn \hagen_foo:n
 {% check whether there is a hyphen first
  \seq_set_split:Nnn \l_hagen_foo_seq { - } { #1 }
  \int_compare:nTF { \seq_count:N \l_hagen_foo_seq = 1 }
   {% no hyphen
    \hagen_foo_spaces:n { #1 }
   }
   {% at least a hyphen
    \seq_pop_left:NN \l_hagen_foo_seq \l_hagen_foo_tl
    \tl_if_in:NnTF \l_hagen_foo_tl { ~ }
     {% the text before the hyphen has a space
      \hagen_foo_spaces:n { #1 }
     }
     {% no space
      \hagen_fooD:V \l_hagen_foo_tl
      \hagen_fooE:f { \seq_use:Nn \l_hagen_foo_seq { - } }
     }
   }
 }
\cs_new_protected:Nn \hagen_foo_spaces:n
 {% check whether there is at least a space
  \seq_set_split:Nnn \l_hagen_foo_seq { ~ } { #1 }
  \seq_pop_left:NN \l_hagen_foo_seq \l_hagen_foo_tl
  \seq_if_empty:NTF \l_hagen_foo_seq
   {% no space
    \hagen_fooA:V \l_hagen_foo_tl
   }
   {% at least a space
    \hagen_fooB:V \l_hagen_foo_tl
    \hagen_fooC:f { \seq_use:Nn \l_hagen_foo_seq { ~ } }
   }
 }

% here the auxiliary macros should be defined
\cs_new_protected:Nn \hagen_fooA:n {\texttt{|fooA: #1|}}
\cs_new_protected:Nn \hagen_fooB:n {\texttt{|fooB: #1|}}
\cs_new_protected:Nn \hagen_fooC:n {\texttt{|fooC: #1|}}
\cs_new_protected:Nn \hagen_fooD:n {\texttt{|fooD: #1|}}
\cs_new_protected:Nn \hagen_fooE:n {\texttt{|fooE: #1|}}
% and here the required variants
\cs_generate_variant:Nn \hagen_fooA:n { V }
\cs_generate_variant:Nn \hagen_fooB:n { V }
\cs_generate_variant:Nn \hagen_fooC:n { f }
\cs_generate_variant:Nn \hagen_fooD:n { V }
\cs_generate_variant:Nn \hagen_fooE:n { f }
\ExplSyntaxOff

\begin{document}

\foo{foobar}\par
\foo{foo bar baz}\par
\foo{foo bar-baz}\par
\foo{foo-bar baz}\par
\foo{foo-bar-baz}\par

\end{document}

enter image description here

2

Here's a LuaLaTeX-based solution. It sets up a LaTeX macro named \foo which, in turn, calls a Lua function named HvE that examines the argument of \foo and performs the splitting of the string and the assigning of the substrings to \fooA, \fooB, etc.

enter image description here

\documentclass{article}

\usepackage{luacode}
\begin{luacode*}
function HvE ( s )
   if     string.find ( s , "^%a-%s" ) then
      s = string.gsub ( s , "^(%a-)%s(.*)" , "\\fooB{%1}\\fooC{%2}" ) 
   elseif string.find ( s , "^%a-%-" ) then
      s = string.gsub ( s , "^(%a-)%-(.*)" , "\\fooD{%1}\\fooE{%2}" ) 
   else
      s = "\\fooA{"..s.."}" 
   end
   tex.sprint ( s )
end
\end{luacode*}
\newcommand\foo[1]{\directlua{HvE(\luastring{#1})}}

% set up test versions of \fooA, \fooB, etc
\usepackage{xcolor}
\newcommand\fooA[1]{\textit{\textbf{#1}}}
\newcommand\fooB[1]{\textit{#1}}
\newcommand\fooC[1]{\textcolor{red}{\textbf{#1}}}
\newcommand\fooD[1]{\textcolor{purple}{\textsc{#1}}}
\newcommand\fooE[1]{\textsf{#1}}

\begin{document}
\obeylines % just for this example    
\foo{foobar}
\foo{foo bar baz}
\foo{foo bar-baz}
\foo{foo-bar baz}
\foo{foo-bar-baz}
\end{document}

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.