# Best way to print key-value pairs: table, array or matrix?

I have a series of values that need to be printed row-wise in multiple columns. (These values, held as \def variables, are generated programatically and some of them can be empty strings.)

The problem is how to avoid the empty cells and have the next cell automatically take the previous cell place if its empty. For example, I have these non-empty values: Author, Composer, Publisher, Year to be arranged in 3 columns as:

Author    Composer    Publisher

Year


That is in two rows. If we add one more entry, it should go to next cell after Year.

However, if the Publisher is empty then Year should automatically come to the first row as:

Author    Composer    Year


I have \Author, \Composer, \Publisher, \Year and so on list of variables that need to be arranged as this. I just would like to write something like:

\SomeEnvironment{\Author, \Composer, \Publisher, \Year, \something so on}


and let the environment figure out automatically to remove the empty ones and let the next ones flow.

How to achieve this?

PS: For those curios, this is part of CarMusTy: Carnatic Music Typesetting Environment project.

-
I just wonder why you must end up with control sequences and not \SomeEnvironment{Author, Composer, Publisher, Year, something, somethingelse, and on}. –  Ahmed Musa Feb 20 '12 at 17:18
Control sequences for multiple reasons (hopefully sensible ones): 1. These are generated programatically and are supplied as arguments for other packages - so I have no control over skipping some arguments. 2. Need to add indices over these values and control sequence offers me to do that easily be changing the sequence definition 3. for checking if empty etc.. its easy to do it with control sequences. Originally tried with 'directly placing' the values in the env commands, but control squences are more powerful to manipulate them as macros etc.. –  Gopalakrishna Palem Feb 21 '12 at 5:56

## 3 Answers

Not sure whether you wanted plain or latex, the macros should work with both, although this is a plain document

\def\SomeEnvironment#1{%
\par\noindent
\xSomeEnvironment #1\relax}

\def\xSomeEnvironment#1{%
\ifx\relax#1%
\hfill
\else
\ifx\empty#1%
\else
\hbox to 0.3333\hsize{#1\hfill}\hfil
\fi
\expandafter\xSomeEnvironment
\fi}

\def\Author{Author}
\def\Composer{Composer}
\def\Publisher{}
\def\Year{Year}
\def\something{something}
\def\somethingelse{somethingelse}

\SomeEnvironment{\Author \Composer \Publisher \Year \something \somethingelse}

\bye


This loops by inspecting each token, if it is \relax it stops, if it has teh same definition as \empty it skips that token, otherwise it sets the content in a box of with 1/3 of the textwidth (0.333330). So no explicit line breaking or alignment points are needed, when the paragraph is broken up into lines in the usual way, the only place that breaks can happen is every third entry as boxes never break. To get more columns just change the 0.33333 eg 0.25 would give you four columns.

EDIT:

Your real case was a bit more complicated as rather than just a single token you either wanted a brace group or \var followed by a brace group, and your definitions are not then empty but defined to be \relax due to \csname weirdness.

\documentclass{article}

\newcommand{\var}[1]{\csname #1\endcsname}
\newcommand{\SetVar}[2]{\ifcsname #1\endcsname
\expandafter\renewcommand\csname #1\endcsname{#2}
\else \DefVar{#1}{#2} \fi}
\newcommand{\Var}{\var}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\SomeEnvironment#1{%
\par\noindent
\xSomeEnvironment #1\relax}

\def\xSomeEnvironment#1{%
\ifx\var#1\relax
\expandafter\xxxSomeEnvironment
\else
\ifx\relax#1\relax
\hfill
\else
\def\tempa{#1}%
\expandafter\expandafter\expandafter\xxSomeEnvironment
\expandafter\expandafter\expandafter\tempa
\fi
\fi}

\def\xxSomeEnvironment#1{%
\hbox to 0.3333\hsize{#1\hfill}\hfil
\expandafter\xSomeEnvironment
}

\def\xxxSomeEnvironment#1{%
\expandafter\let\expandafter\tempa\csname#1\endcsname
\ifx\relax\tempa
\expandafter\xSomeEnvironment
\else
\expandafter\xxSomeEnvironment\csname#1\expandafter\endcsname
\fi}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\begin{document}

\def\index[#1]#2{}

% Below attributes are programatically set by user in the CarMusTy Document
%
% Note that these could be unicode values in real documents, hence we need these as \csname in the \var{} definitions
%
% Als note that if a variable is not set any value, such as Song2Composer below, it will not be defined (which is fine because the \var{} allows it to be \relax)
%  For testing these are commented out below, making them undefined.
%
\DefVar{BookTOC}{Book Title}
\DefVar{BookTitle}{Book Title}
\DefVar{BookSubTitle}{Sub Title}
\DefVar{BookAuthor}{Book Author}
\DefVar{BookPublisher}{Book Publisher}
\DefVar{BookYear}{Book Year}
\DefVar{Song1Title}{Song Title1}
\DefVar{Song1Author}{Author1\index[Author]{Author1!\var{SongTitle}}}
\DefVar{Song1Composer}{Composer1\index[Composer]{Composer1!\var{SongTitle}}}
\DefVar{Song1Singers}{Singers1\index[Singers]{Singers1!\var{SongTitle}}}
\DefVar{Song1Movie}{Movie1\index[Movie]{Movie1!\var{SongTitle}}}
\DefVar{Song1Year}{Year1}
\DefVar{Song1Raga}{RAGAVARDHINI\index[Raga]{RAGAVARDHINI!\var{SongTitle}}}
\DefVar{Song1Tala}{tripuTa\index[Tala]{tripuTa!\var{SongTitle}}}
\DefVar{Song1Verse1Title}{Verse}
\DefVar{Song1Verse2Title}{Verse}
\DefVar{Song2Title}{Song Title2}
\DefVar{Song2Author}{Author2\index[Author]{Author2!\var{SongTitle}}}
%\DefVar{Song2Composer}{Composer1\index[Composer]{Composer1!\var{SongTitle}}}
\DefVar{Song2Singers}{Singers1\index[Singers]{Singers1!\var{SongTitle}}}
\DefVar{Song2Movie}{Movie2\index[Movie]{Movie2!\var{SongTitle}}}
\DefVar{Song2Year}{Year2}
\DefVar{Song2Raga}{Raga2\index[Raga]{Raga2!\var{SongTitle}}}
%\DefVar{Song2Tala}{Tala2\index[Tala]{Tala2!\var{SongTitle}}}
\DefVar{Song2Verse1Title}{Verse}
\DefVar{Song2Verse2Title}{Verse}

%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% Lets Print them

\section*{}
{\huge{Song Title}}
\SomeEnvironment{{Author} {Composer} {Singers} {Movie} {Year} }

% Uncomment the below lines to see the error
\section*{}
{\huge\var{Song1Title}}
\SomeEnvironment{\var{Song1Author} \var{Song1Composer} \var{Song1Singers} \var{Song1Movie} \var{Song1Year}} % << - Uncomment this line to see the error

\section*{}
{\huge\var{Song2Title}}
\SomeEnvironment{\var{Song1Author} \var{Song1Composer} \var{Song1Singers} \var{Song1Movie} \var{Song1Year}}  % << - Uncomment this line to see the error

\end{document}

-
Thank you David ...this is very close - Since my \Author etc. definitions are little bit complex (they allow unicode characters, hence \csname'd, and allow indexes). So, when I used this environment on them, encountering few compiler errors, and also the empty cell space is not going away. For your convenience, I have extracted portions and created MWE gpalem.web.officelive.com/Test.tex Could you please check it ? The issue is, for each song there are authors, composer etc. so the variable names are held as Song1Author1 etc. which are allowed only as \csname - so we have this \var{} –  Gopalakrishna Palem Feb 21 '12 at 8:46
Also, could you please give some info on how this is created? I see that you are trying to compare with \relax and \empty etc.. and then use \hbox to print - but I could not get how is it going to next row and how to tweak number of columns, row height etc.. If you can share 'behind the scenes' info I can learn to create my own or tune it. Thx. –  Gopalakrishna Palem Feb 21 '12 at 8:55
oh your real example has \var{Song1Year} not \Song1Year so it will fail as my loop grabs one toen at a time however that is easy to fix, to either grab two tokens (\var and the brace group) or probably more simply just define \var locally to do the right thing, then no loop is needed at all. Will send something later once ive caught up with day job. –  David Carlisle Feb 21 '12 at 9:26
3 \expandafter in a row doesn't really count as "all those" (once you get to 31 in a row you need to start wonder if you're doing it right:-) One notable benefit of the LaTeX3 xparse argument syntax is that such explicit expansion tricks are largely hidden and each command just specifies if its arguments are to be expanded before the call so no \expandafter are needed to expand re-arrange the expansion order. (Well they are needed of course but hidden in the underlying LaTeX kernel code). –  David Carlisle Feb 22 '12 at 1:30

It's not quite clear how you want them listed. The following is a quick-and-dirty solution in expl3. I implemented a main document and a package that does the work. The following is the main.

\documentclass{article}

\usepackage{Palem}

\def\definedA{A}
\def\definedB{B}
\def\definedC{C}
\def\definedD{D}
\def\definedE{E}

\begin{document}

\ListIt{3}{ {\definedA, \definedB, \definedC},
{\definedA, \undefinedB, \definedC, \definedD, \definedE},
{\definedA, \undefinedB, \definedC} }

\end{document}


If you tell me exactly what you want with the rows, I can implement it better. The following is the package.

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{Palem}[2012/02/02 Printing Support]

\RequirePackage{xparse}
\RequirePackage{l3int}
\RequirePackage{l3clist}

\ExplSyntaxOn

\int_new:N \l_col_int
\int_new:N \l_max_col_int

\cs_new_nopar:Npn \Palem_list_cs:N #1 {
\cs_if_exist:NT #1 {
\int_incr:N \l_col_int
#1,
\int_compare:nNnT \l_col_int = \l_max_col_int {\\}
}
}

\cs_new_nopar:Npn \Palem_list_rows:n #1 {
\int_set:Nn \l_col_int {0}
\clist_map_function:nN {#1} \Palem_list_cs:N
\int_compare:nNnF \l_col_int = \l_max_col_int {\\}
}

% #1 is the maximum number of things per row.
% #2 are lists of lists of commands.
\DeclareDocumentCommand\ListIt{ m m }{
\int_set:Nn \l_max_col_int {#1}
\clist_map_function:nN {#2} \Palem_list_rows:n
}

\ExplSyntaxOff

-
\int_new:N \l_col_int: the variable type goes last. –  egreg Feb 20 '12 at 16:45
Thank you Marc. Saved both files and tried them - Unfortunately, getting "undefined control sequence error at ...cr:N \l_int_col A,\int_compare:nNnT" with pdflatex. May be I missed something. But I can see that somehow you are going over the list and printing them with a \\ separator conditionally inserted at the end of each row - though I could not really understand the syntax. I will try to give some info. We have this song data for each song that need be printed below the song title. Right now its like this gpalem.web.officelive.com/SongHeader.jpg - but need beautify –  Gopalakrishna Palem Feb 21 '12 at 6:57
@Marc - I cannot use Latex3 right now - because its xelatex is broke with unicode glyphs and I had very hard time with it - so reverted back to latex2 for this project. I guess this new syntax is for L3 is it? I was hoping there is a way of doing this version independent way. –  Gopalakrishna Palem Feb 21 '12 at 7:51
Yes compiling with l3 packages works fine - but I cannot use l3 packages as they are not ready for unicode glyphs - font kerning does not work correctly with them (like the l2 packages do). –  Gopalakrishna Palem Feb 22 '12 at 8:55

Well, defying David Carlisle, the patent holder of key-value system, I decided to go key-value syntax.

\documentclass{article}
\usepackage{keyreader}[2012/01/14]
\usepackage{catoptions}[2011/12/17]
% Load geometry to enlarge the print area:
\usepackage[left=2cm,right=2cm]{geometry}

\makeatletter
\new@def*\var#1{\usename{carm@#1}}
\newletcs\Var\var
\def\Arrange#1{\par\noindent\xArrange#1\@nnil}
\def\xArrange#1{%
\expandafter\ifcseqTF\@car#1\@nil\var{%
\xxArrange
}{%
\expandafter\ifcseqTF\@car#1\@nil\@nnil{%
\hfill
}{%
\hb@xt@\dimexpr\carm@alignment{#1\hfill}\hfil
\xArrange
}%
}%
}
\def\xxArrange#1{%
\ifcsndefFT{carm@\cpttrimspaces{#1}}{}{%
\hb@xt@\dimexpr\carm@alignment{\@nameuse{carm@\cpttrimspaces{#1}}\hfill}\hfil
}%
\xArrange
}
% I don't want us to redefine \index, which is a LaTeX command. Hence
% I use \iindex instead. I assume that \iindex has one optional argument.
\new@def*\iindex{\cpt@testopt\iindex@a{}} % no \robust@def here!
\new@def*\iindex@a[#1]#2{}

\robust@def*\carm@getcallback#1#2{%
\def\@tempa##1#1##2\carm@nil{%
\edef\call@back{\cpttrimspaces{##2}}%
\carm@split##1==\carm@nil
}%
\@tempa#2\carm@nil
}
\robust@def*\carm@split#1=#2=#3\carm@nil{%
\edef\@tempa{\cpttrimspaces{#1}}%
\edef\@tempb{\cpttrimspaces{#2}}%
\edef\key@values{\csliststack,\key@values
\expandcsonce\@tempa=\expandcsonce\@tempb}%
\ifcsndefFT{CARM@carmusty@\@tempa}{}{%
\if@carmst
\@latex@error{CarMusty key '\@tempa' redefined}\@ehc
\else
\@latex@warning@no@line{CarMusty key '\@tempa' redefined}%
\fi
}%
\cptexpandsecond{\define@cmdkey[CARM]{carmusty}[carm@]}%
{{\expandcsonce\@tempa}[{\expandcsonce\@tempb}]%
{\expandcsonce\call@back}}%
}
\robust@def*\carmustykeys{\cpt@teststopt\carmustykeys@a{define}}
\robust@def*\carmustykeys@a[#1]{%
\let\if@carmst\ifcpt@st
\begingroup
\endlinechar\m@ne
\xifstrcmpTF{\cpttrimspaces{#1}}{define}{%
\definecarmustykeys
}{%
\xifstrcmpTF{\cpttrimspaces{#1}}{set}{%
\setcarmustykeys
}{%
\@latex@error{Unknown carmusty action '\detokenize{#1}'}\@ehc
}%
}%
}
\robust@def*\definecarmustykeys#1{%
\endgroup
\def\key@values{}%
\cptdocommalist{#1}{%
\ifinsetTF{.code=}{##1}{%
\carm@getcallback{.code=}{##1}%
}{%
\ifinsetTF{.code =}{##1}{%
\carm@getcallback{.code =}{##1}%
}{%
\carm@getcallback{.code=}{##1.code=}%
}%
}%
}%
\cptexpandargonce{\krdsetkeys[CARM]{carmusty}}\key@values
}
\robust@def*\setcarmustykeys#1{%
\endgroup
\cptexpandsecond\krdsetkeys{\if@carmst*\fi}[CARM]{carmusty}{#1}%
}

% When defining keys, the star (*) variant of \carmustykeys will
% raise an error when an existing key is being redefined.

% When setting keys, the star (*) variant of \carmustykeys will use
% the star variant of \setkeys. See the documentation of 'keyreader'
% or 'xkeyval' to see the meaning of the starred form of \setkeys.
% Actually, we don't use \setkeys but the more robust \krdsetkeys.

% Examples:
\carmustykeys*[define]{%
keya = .3333\hsize .code = \def\x##1{#1##1},
% Uncomment the following line to get an error (keya is being redefined):
% keya = default of keya .code = \def\result{generate error}
}
\carmustykeys*[set]{%
keya = valuea
}

% Real business:
\carmustykeys{%
alignment = .3333\hsize,
BookTOC = Book Title,
BookTOC = Book Title, % redefined - see log file for warning
BookTitle = Book Title,
BookSubTitle = Sub Title,
BookAuthor = Book Author,
BookPublisher = Book Publisher,
BookYear = Book Year,
Song1Title = Song Title1,
Song1Author = Author1\iindex[Author]{Author1!\var{SongTitle}},
Song1Composer = Composer1\iindex[Composer]{Composer1!\var{SongTitle}},
Song1Singers = Singers1\iindex[Singers]{Singers1!\var{SongTitle}},
Song1Movie = Movie1\iindex[Movie]{Movie1!\var{SongTitle}},
Song1Year = Year1,
Song1Raga = RAGAVARDHINI\iindex[Raga]{RAGAVARDHINI!\var{SongTitle}},
Song1Tala = tripuTa\iindex[Tala]{tripuTa!\var{SongTitle}},
Song1Verse1Title = Verse,
Song1Verse2Title = Verse,
Song2Title = Song Title2,
Song2Author = Author2\iindex[Author]{Author2!\var{SongTitle}},
Song2Composer = Composer1\iindex[Composer]{Composer1!\var{SongTitle}},
Song2Singers = Singers1\iindex[Singers]{Singers1!\var{SongTitle}},
Song2Movie = Movie2\iindex[Movie]{Movie2!\var{SongTitle}},
Song2Year = Year2,
Song2Raga = Raga2\iindex[Raga]{Raga2!\var{SongTitle}},
Song2Tala = Tala2\iindex[Tala]{Tala2!\var{SongTitle}},
Song2Verse1Title = Verse,
Song2Verse2Title = Verse,
}
\makeatother

% If needed, you can add new keys by doing:
% \carmustykeys[define]{key1=value1,<...>,key2=value2}

% To change the alignment, you can do:
% \carmustykeys[set]{alignment=<some value>}

% Let's print them:
\begin{document}
\parindent0pt
\carmustykeys[set]{alignment=.1333\hsize}

\textbf{Song Title}
\Arrange{{Author} {Composer} {Singers} {Movie} {Year} }

\par\medskip
\textbf{\var{Song1Title}}
\Arrange{\var{Song1Author} \var{Song1Composer} \var{Song1Singers}
\var{Song1Movie} \var{Song1Year} \var{Song1Verse2Title}}

\par\medskip
\textbf{\var{Song2Title}}
\Arrange{\var{Song1Author} \var{Song1Composer} \var{Song1Singers}
\var{Song1Movie} \var{Song1Year}}
\end{document}


-
wow...nice and tidy all keys in one place - I would really like to use this - but I am not able to understand how this is being achieved. I tried compiling on latex2, but got some errors (I cannot use L3 for this project as it has unicode glyph problems). Does this approach support unicode values? For example, Author1, Movie1 etc. could be unicode glyphs. –  Gopalakrishna Palem Feb 22 '12 at 4:48
This is all LaTeX2e and not LaTeX3. I don't foresee any problem with unicode. Can you please post examples of the errors you said you have encountered? Before posting my solution I generated several trial outputs. I will post a sample output in a moment. –  Ahmed Musa Feb 22 '12 at 14:50
My bad..sorry, its compiling fine and giving the output. Though I could not understand how it works - but its another great way of doing this. Thanks for taking time on this and presenting the solution –  Gopalakrishna Palem Feb 23 '12 at 6:30