Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

I’d like to parse a list where the elements can be given as either [<pre>]<text>[<post>], where <pre> and <post> are optional, or (<special text>). I found a solution where everything works if <text> is a single token but not if it’s a word.

So an input like

\mylist{1,[a]2,3[b],[c]4[d],(5)}

I get and desire an output like

right result

but with

\mylist{111,[a]222,333[b],[c]444[d],(555)}

I get this

wrong result

while desiring this output

desired result

I also tried to use {<text>} instead of <text>, i.e. put the element in curly braces, but it doesn’t help in all cases. Furthermore I’d prefer to not use braces do delimit the element.

The separator printing doesn’t work well too.

Here’s what I’ve got so far

\documentclass[parskip=full]{scrartcl}

\makeatletter

\usepackage{etoolbox,xparse}
\def\cflist@printsep{%
   \ifcflist@sep\quad$\bullet$\quad\fi
}
\def\cflist@none#1{%
   \@ifnextchar[{\cflist@printsep[#1]\cflist@last}{%
      \cflist@printsep
      [#1]%
   }%
}
\def\cflist@first[#1]#2{%
   \@ifnextchar[{\cflist@printsep\cflist@both[#1]{#2}}{\cflist@printsep${}_{#1}$[#2]}%
}
\def\cflist@last#1[#2]{%
   #1${}_{#2}$%
}
\def\cflist@both[#1]#2[#3]{%
   ${}_{#1}$[#2]${}_{#3}$%
}
\def\cflist@extra(#1){\cflist@printsep[#1]${}_E$}
\newcounter{cflist@cnt}
\newif\ifcflist@sep
\def\cflist@do#1{%
   \@ifnextchar({%
      \cflist@extra%
   }{%
      \@ifnextchar[{\cflist@first}{%
         \cflist@none%
      }%
   }#1%
   \stepcounter{cflist@cnt}%
   \ifnum\value{cflist@cnt}=0\relax\else
      \cflist@septrue
   \fi
}

\DeclareListParser{\cflist@parser}{,}

\newcommand{\mylist}[1]{%
   \let\do\cflist@do
   \setcounter{cflist@cnt}{0}%
   \cflist@sepfalse
   \cflist@parser{#1}%
}

\makeatother


\begin{document}

Single element tokes work fine:\\
\mylist{1,[a]2,3[b],[c]4[d],(5)}

Multiple element tokes don't work:\\
\mylist{111,[a]222,333[b],[c]444[d],(555)}\\
\mylist{aaa,[a]bbb,ccc[b],[c]ddd[d],(eee)}

Multiple element tokens in curly braces don't work either:\\
\mylist{{111},[a]{222},{333}[b],[c]{444}[d],(555)}\\
\mylist{{aaa},[a]{bbb},{ccc}[b],[c]{ddd}[d],(eee)}

Desired result for multiple element tokes:\\
111\quad$\bullet$\quad${}_a$222\quad$\bullet$\quad
333${}_b$\quad$\bullet$\quad${}_c$444${}_d$
\quad$\bullet$\quad555${}_E$
\end{document}

The output format is a placeholder for other formatting macros, but I guess it doesn’t affect the problem and make the code a little shorter if I use the dummies here.

Update

I forgot \cflist@sepfalse which causes the superfluous sep at the begin of all lists after the first one

Updated 2

The real application is to send the list to \cite macros of biblatex, so the list

aaa,[a]bbb,ccc[b],[c]ddd[d],(eee)

should be processed as

\cite{aaa}, \cite[][a]{bbb}, \cite[b]{ccc}, \cite[c][d]{ddd}, eee

The (eee) part should bypass the \cite mechanism and printed as is without the parentheses.

share|improve this question
    
\mylist{111,[a]222,333[b],[c]444[d],(555)} is this typed in or added in by other macros i.e., how do you collect the input? –  Yiannis Lazarides Mar 11 '12 at 15:41
    
@YiannisLazarides: It is typed by me. So it is very easy to define another input scheme –  Tobi Mar 11 '12 at 15:54
    
I want to call several \cite macros (and do some other stuff why I can’t use \cites) with \mylist. –  Tobi Mar 11 '12 at 15:55
    
and pre and post always single letters? –  Yiannis Lazarides Mar 11 '12 at 17:01
    
@YiannisLazarides: I added an update that shows the application. –  Tobi Mar 11 '12 at 17:07
show 4 more comments

4 Answers

up vote 6 down vote accepted

This answers (at least partially) the parsing problem. But of course here I'm using specific features of math mode. But the principle should be clear.

\documentclass{article}
\makeatletter
\def\mylist#1{%
  \@for\next:=#1\do{%
    \expandafter\@mylistaux\next[]\@nil
    \@mylist@separator}%
  }
\def\@mylistaux{\@ifnextchar(\@mylistauxi\@mylistauxii}
\def\@mylistauxi(#1)[]\@nil{\@mylist@parentheses{#1}}
\def\@mylistauxii{\@ifnextchar[\@mylistauxiii{\@mylistauxiii[]}}
\def\@mylistauxiii[#1]#2[#3]#4\@nil{\@mylist@brackets{#1}{#2}{#3}}

%% final macros
\def\@mylist@parentheses#1{%
  % do something for the case (xxx)
  $(#1)_{E}$}
\def\@mylist@parentheses#1#2#3{%
  % do something for the case [a]xxx[b]
  % #1 is a, #2 is xxx, #3 is b
  $_{#1}#2_{#3}$
\def\@mylist@separator{\quad$\bullet$\quad}
\makeatother

\begin{document}
\mylist{1,[a]2,3[b],[c]444[d],(5)}
\end{document}

enter image description here

A LaTeX3 implementation that makes clearer how to use the gathered arguments:

\usepackage{xparse}

\ExplSyntaxOn
\seq_new:N \l_tobi_list_seq
\tl_new:N \l_tobi_last_item_tl
\NewDocumentCommand{\mylist}{m}
  {
   \seq_set_split:Nnn \l_tobi_list_seq { , }{ #1 }
   \seq_pop_right:NN \l_tobi_list_seq \l_tobi_last_item_tl
   \seq_map_inline:Nn \l_tobi_list_seq { \tobi_process:w ##1 [ ] \q_stop \tobi_separator: }
   \exp_after:wN \tobi_process:w \l_tobi_last_item_tl [ ] \q_stop
  }
\cs_new:Npn \tobi_process:w 
  {
   \peek_charcode:NTF ( { \tobi_process_aux_i:w } { \tobi_process_aux_ii:w }
  }
\cs_new:Npn \tobi_process_aux_i:w ( #1 ) [ ] \q_stop
  {
   \tobi_final_parentheses:n { #1 }
  }
\cs_new:Npn \tobi_process_aux_ii:w
  {
   \peek_charcode:NTF [ { \tobi_process_aux_iii:w } { \tobi_process_aux_iii:w [ ] }
  }
\cs_new:Npn \tobi_process_aux_iii:w [ #1 ] #2 [ #3 ] #4 \q_stop
  {
   \tobi_final_brackets:nnn { #1 } { #2 } { #3 }
  }
%% Final macros
\cs_new:Npn \tobi_final_parentheses:n #1
  {
   $(#1)\sb{E}$
  }
\cs_new:Npn \tobi_final_brackets:nnn #1 #2 #3
  {
   $\sb{#1}#2\sb{#3}$
  }
\cs_new:Npn \tobi_separator: { \quad\textbullet\quad }
\ExplSyntaxOff

One now has to suitably define \tobi_final_parentheses:n, \tobi_final_brackets:nnn and \tobi_separator:.

It's not necessary to use these names: just use what you prefer; the code might be

\usepackage{xparse}

\ExplSyntaxOn
\seq_new:N \l_tobi_list_seq
\tl_new:N \l_tobi_last_item_tl
\NewDocumentCommand{\mylist}{m}
  {
   \seq_set_split:Nnn \l_tobi_list_seq { , }{ #1 }
   \seq_pop_right:NN \l_tobi_list_seq \l_tobi_last_item_tl
   \seq_map_inline:Nn \l_tobi_list_seq { \tobi_process:w ##1 [ ] \q_stop \mylistseparator }
   \exp_after:wN \tobi_process:w \l_tobi_last_item_tl [ ] \q_stop
  }
\cs_new:Npn \tobi_process:w 
  {
   \peek_charcode:NTF ( { \tobi_process_aux_i:w } { \tobi_process_aux_ii:w }
  }
\cs_new:Npn \tobi_process_aux_i:w ( #1 ) [ ] \q_stop
  {
   \mylistelementinparentheses { #1 }
  }
\cs_new:Npn \tobi_process_aux_ii:w
  {
   \peek_charcode:NTF [ { \tobi_process_aux_iii:w } { \tobi_process_aux_iii:w [ ] }
  }
\cs_new:Npn \tobi_process_aux_iii:w [ #1 ] #2 [ #3 ] #4 \q_stop
  {
   \mylistelementinbrackets { #1 } { #2 } { #3 }
  }
\ExplSyntaxOff

%% Final macros
\newcommand\mylistelementinparentheses[1]{$(#1)\sb{E}$}
\newcommand\mylistelementinbrackets[3]{$\sb{#1}#2\sb{#3}$}
\newcommand\mylistseparator {\quad\textbullet\quad}

so the hard work making macros can be defined as you're accustomed to (the names can be changed, of course).

EDIT

How to define \tobi_final_parentheses:n, \tobi_final_brackets:nnn and \tobi_separator: for the desired output. As you can see, LaTeX3 syntax allows for very easy management of empty arguments.

%% Final macros
% aaa -> \cite{aaa}
% [a]bbb -> \cite[][a]{bbb}
% ccc[b] -> \cite[b]{ccc}
% [c]ddd[d] -> \cite[c][d]{ddd}
% (eee) -> eee

\cs_new:Npn \tobi_final_parentheses:n #1 { #1 }
\cs_new:Npn \tobi_final_brackets:nnn #1 #2 #3
  {
   \tl_if_empty:nTF { #3 }
     {
      \tl_if_empty:nTF { #1 }
        { \cite{#2} }
        { \cite[][#1]{#2} }
     }
     {
      \tl_if_empty:nTF { #1 }
        { \cite[#3]{#2} }
        { \cite[#1][#3]{#2} }
     }
  }
\cs_new:Npn \tobi_separator: {, ~ }
\ExplSyntaxOff
share|improve this answer
1  
That's the intermission solution ;-) –  Marco Daniel Mar 11 '12 at 16:24
    
Thanks but I can’t transfer that to my code. The original application has nothing to do with math; I only used it as a dummy in my example. Your L3 solution makes nothing clearer … I guess its time to learn L3 O:-) –  Tobi Mar 11 '12 at 16:55
    
I added the real application to my question –  Tobi Mar 11 '12 at 16:59
    
@Tobi It's quite easy to adapt the first solution to any setting. I'll make some changes to show how. –  egreg Mar 11 '12 at 17:00
    
OK, thanks. :-) –  Tobi Mar 11 '12 at 17:07
show 2 more comments

This is not a final solution, as I think what you are asking can get very complicated, but is indicative of building a finite state machine. For demonstration purposes, I will use @tfor to scan the list letter by letter. LaTeX3, I am sure (as egreg's solution shows) offers better possibilities.

\documentclass{article}
\begin{document}
\makeatletter
\newif\if@gather
\newif\if@store
\DeclareRobustCommand\temp{}
\let\@ex\expandafter
\edef\alist{[a]2[b],3[b],[c]4[d],(eee)}
\def\parse#1{%
\@ex\@tfor\@ex\next\@ex:\@ex=#1\do{%
    \@gathertrue\@storetrue
    \if\next,\@gathertrue\@storetrue\def\next{$\bullet$}\fi
    \if\next[\@gathertrue\@storefalse\fi
    \if\next]\@storefalse\@gathertrue\fi
    \if\next(\@gathertrue\@storefalse\fi 
    \if\next)\@storefalse\@gathertrue\fi
    \if@store\edef\temp{\temp\next}\fi
}}
\parse\alist
\temp
\end{document}

The advantage of this method, besides being more readable is that you can capture every letter and either store it in a macro or token list or do something else for it. In the example I just removed all the [], the () and replaced the commas with $\bullet. It will need much more work to achieve what you are after and to make it robust to check for errors and edge cases. A much better and more robust scanner can be build using \futurelet. In this respect you might be able to either borrow code from the soul package, which has an excellent parser or you can use the \SOUL@everytoken macro.

share|improve this answer
    
Thank you :-) I’ll have a look at it in a few days (No computer for some days …) –  Tobi Mar 11 '12 at 20:47
add comment

If a luatex solution is ok, then you can try the lpeg parser to achieve what you want.

First the lua file (save it as listparsing.lua). Note that I use -2 as the first argument to make tex.sprint prints thing verbatim. You have to change this value to -1 to use the standard catcode régime. The parser accepts only words (that is a succession of letters) but it can be easily expanded to other types of word (like e.g. control sequences). An other possible enhancement would be to check for balanced brackets (a bit more difficult, as it implies the use of a real grammar).

lpeg = require('lpeg')

local P, R, S, C, Cs, V = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.V
local match = lpeg.match

local space = S(' \n\t')
local lbracket, rbracket = P('['), P(']')
local lparen, rparen = P('('), P(')')
local comma = P(',') * space^0 / ', '

local letter = R('az') + R('AZ')
local word = letter^1

local digit = R('09')
local number = digit^1

local pretextpost = lbracket * C(word) * rbracket * C(word) * lbracket * C(word) * rbracket / function (a,b,c) return string.format('\\cite[%s][%s]{%s}',a,c,b) end
local pretext = lbracket * C(word) * rbracket * C(word) / function (a,b) return string.format('\\cite[][%s]{%s}',a,b) end
local posttext = C(word) * lbracket * C(word) * rbracket / function (a,b) return string.format('\\cite[%s]{%s}',b,a) end
local text = C(word) / function (a) return string.format('\\cite{%s}',a) end
local special = lparen * C(word) * rparen / function (a) return a  end
local pattern = pretextpost + pretext + posttext + text + special

local parser = Cs(pattern * (comma * pattern)^0)

function parse_and_texprint(s)
   return tex.sprint(-2,match(parser,s))
end

Then the tex file.

\documentclass{standalone}
\directlua{dofile('listparsing.lua')}
\def\mylist#1{%
  \directlua{%
    parse_and_texprint('#1')}}
\begin{document}
\texttt{\mylist{aaa,[a]bbb,ccc[b],[c]ddd[d],(eee)}}
\end{document}

enter image description here

share|improve this answer
add comment

This is a simple solution using the skills of biblatex.

\documentclass{scrartcl}

\usepackage[style=numeric,backend=biber]{biblatex}
\addbibresource{biblatex-examples.bib}

\usepackage{xcolor}

\newcommand{\cfwrapper}[1]{
   \colorbox{green!50!black}{%
      \color{white}\sffamily
      \textbf{i}\enskip
      #1
   }%
}
\DeclareMultiCiteCommand{\cf}[\cfwrapper]{\cite}{, }
\DeclareFieldFormat{postnote}{(#1)}
\renewcommand{\postnotedelim}{ }

\begin{document}
Text \cf{kastenholz,hyman}[S. 2]{markey}
\end{document}

It works fine for most of all cases but it misses a way to bypass the \cite command. Like a list element in parens (pass it) in the above question and answers.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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