# Automatically formatting a table from a file with a list of words

I have a list of ~1000 words in a text file. The words are listed in a text file "my_words.txt" with no breaklines as follows:

word1, word2, word3, etc.


I would like to display these words in a figure/table with vertical alignment (and central alignment within each column), i.e.

word1 word2 word3 word4
word5 word6 word7 word7
word8


where either I specify the number of rows or columns, or LaTeX figures them out from the width that I specify for the table.

I thought the package datatool could help with this problem, but I can't get it to work. I tried:

\usepackage{datatool}
% ...

\begin{document}
% ...

\DTLsetseparator{,}

\begin{table}[htbp]
\centering
\DTLdisplaydb{mytable}
\caption{CSV Table test}
\end{table}


but I get the error:

Missing \$ inserted
\DTLdisplaydb{mytable}


Why is datatool not working? Are there other tools more appropiate for this task?

-
I don't think that a unique record for 1000 words is the best format. I'd use one word per line. –  egreg Nov 19 '12 at 21:58
The underscores in full_concept_list.csv may be causing problems. LaTeX sometimes doesn't cope well with underscores in labels. Trying adding the underscore package. Alternatively, check your word list for any non-ascii characters. –  Faheem Mitha Jan 12 at 17:53

Here's a way with expl3 and Heiko Oberdiek's catchfile:

\documentclass{article}
\usepackage[T1]{fontenc} % for the underscore
\usepackage{catchfile}
\usepackage{xparse}

\ExplSyntaxOn
\cs_set_eq:NN \egreg_catchfiledef:nnn \CatchFileDef
\cs_generate_variant:Nn \egreg_catchfiledef:nnn { c }
\cs_generate_variant:Nn \seq_set_split:Nnn { Nnv }

\NewDocumentCommand{\storefile}{ m m } % #1 = symbolic name, #2 = file name
{
\egreg_catchfiledef:cnn { l_egreg_ #1 _list_tl } { #2 } { \char_set_catcode_other:N \_ }
}
\NewDocumentCommand{\printtable}{ m m } % #1 = number of columns, #2 = symbolic name
{
\egreg_printtable:nn { #1 } { #2 }
}

\tl_new:N \l_egreg_temp_table_tl
\seq_new:N \l_egreg_temp_list_seq
\int_new:N \l_egreg_col_count_int

\cs_new_protected:Npn \egreg_printtable:nn #1 #2
{
\seq_set_split:Nnv \l_egreg_temp_list_seq { , } { l_egreg_ #2 _list_tl }
\tl_clear:N \l_egreg_temp_table_tl
\int_zero:N \l_egreg_col_count_int
\seq_map_inline:Nn \l_egreg_temp_list_seq
{
\int_incr:N \l_egreg_col_count_int
\tl_put_right:Nn \l_egreg_temp_table_tl { ##1 }
\int_compare:nTF { \l_egreg_col_count_int = #1 }
{ \tl_put_right:Nn \l_egreg_temp_table_tl { \\ } \int_zero:N \l_egreg_col_count_int }
{ \tl_put_right:Nn \l_egreg_temp_table_tl { & } }
}
\begin{tabular}{*{#1}{c}}
\l_egreg_temp_table_tl
\end{tabular}
}
\ExplSyntaxOff

\begin{document}

\storefile{words}{my_words.txt}

\noindent Three words per line

\noindent\printtable{3}{words}

\bigskip

\noindent Five words per line

\noindent\printtable{5}{words}

\bigskip

\noindent Eight words per line

\noindent\printtable{8}{words}

\end{document}


The \storefile stores the contents of the file under a symbolic name (in this case words). Then \printtable can print the table using the data.

We first define an expl3 equivalent to \CatchFileDef for being able to generate variants for it. Then the \storefile command is defined: it takes a symbolic name for the word list and the file name as arguments. It stores the entire file in a token list variable (underscores will be absorbed as printable characters).

The \printtable gets the desired number of columns and the symbolic name of the list as arguments. It's, as usual, simply translated into a function call.

The function splits the token list corresponding to the symbolic name at commas and does a mapping on the sequence thus obtained. At each step we increment a counter and add the word to a temporary token list; if the counter's value is not the number of columns we add also a &, otherwise we add \\ and reset the counter to zero.

Finally, the entire list is printed inside a tabular. You might want to load longtable and change tabular into longtable if the word list is long.

For choosing between "row" and "column" order, you can modify it like this:

\documentclass{article}
\usepackage[T1]{fontenc} % for the underscore
\usepackage{catchfile,multicol}
\usepackage{xparse}

\ExplSyntaxOn
\cs_set_eq:NN \egreg_catchfiledef:nnn \CatchFileDef
\cs_generate_variant:Nn \egreg_catchfiledef:nnn { c }
\cs_generate_variant:Nn \seq_set_split:Nnn { Nnv }

\NewDocumentCommand{\storefile}{ m m } % #1 = symbolic name, #2 = file name
{
\egreg_catchfiledef:cnn { l_egreg_ #1 _list_tl } { #2 } { \char_set_catcode_other:N \_ }
}
\NewDocumentCommand{\printtable}{ s m m } % #2 = number of columns, #3 = symbolic name
{
\IfBooleanTF{#1}
{ \egreg_printtablev:nn { #2 } { #3 } }
{ \egreg_printtable:nn { #2 } { #3 } }
}

\tl_new:N \l_egreg_temp_table_tl
\seq_new:N \l_egreg_temp_list_seq
\int_new:N \l_egreg_col_count_int

\cs_new_protected:Npn \egreg_printtable:nn #1 #2
{
\seq_set_split:Nnv \l_egreg_temp_list_seq { , } { l_egreg_ #2 _list_tl }
\tl_clear:N \l_egreg_temp_table_tl
\int_zero:N \l_egreg_col_count_int
\seq_map_inline:Nn \l_egreg_temp_list_seq
{
\int_incr:N \l_egreg_col_count_int
\tl_put_right:Nn \l_egreg_temp_table_tl { ##1 }
\int_compare:nTF { \l_egreg_col_count_int = #1 }
{ \tl_put_right:Nn \l_egreg_temp_table_tl { \\ } \int_zero:N \l_egreg_col_count_int }
{ \tl_put_right:Nn \l_egreg_temp_table_tl { & } }
}
\begin{tabular}{*{#1}{c}}
\l_egreg_temp_table_tl
\end{tabular}
}

\cs_new_protected:Npn \egreg_printtablev:nn #1 #2
{
\seq_set_split:Nnv \l_egreg_temp_list_seq { , } { l_egreg_ #2 _list_tl }
\begin{multicols}{3}\centering
\seq_map_inline:Nn \l_egreg_temp_list_seq { ##1 \par }
\end{multicols}
}
\ExplSyntaxOff

\begin{document}

\storefile{words}{my_words.txt}

\noindent Three words per line

\noindent\printtable{3}{words}

\bigskip

\noindent Five words per line

\noindent\printtable{5}{words}

\bigskip

\noindent Three equispaced columns (column order)

\noindent\printtable*{14}{words}

\end{document}

-
This is fantastic! Thanks @egreg! Is it OK if the words have spaces or underscores in them? –  user815423426 Nov 19 '12 at 22:32
@user27915816 Underscores can be a problem, but solvable. Spaces shouldn't be, but initial and final ones will be trimmed. I'll add a couple of examples and change the macro to cope with underscores. –  egreg Nov 19 '12 at 22:34
Thanks! If I want to change the ordering to column-order (as opposed to row-order), what would I have to do? (happy to look into this myself if it's too much to ask) –  user815423426 Nov 20 '12 at 16:36
@user27915816 For column order the best way is to use the multicol package (or \valign). I'll think about it to get a good interface. –  egreg Nov 20 '12 at 17:02

\documentclass{article}
\makeatletter
\def\wordtable{{%
\everyeof{,\relax,}%
\dimen@\z@
\def\word@{\setbox\z@\hbox}%
\def\word@@{}%
\expandafter\@wordtable\@@input words.txt
\def\word@{\hbox to \dimen@}%
\def\word@@{ \hfill}%
\noindent\expandafter\@wordtable\@@input words.txt
}}

\def\@wordtable#1,{%
\ifx\relax#1\else
\word@{\ignorespaces#1\unskip\hfill}\word@@
\ifdim\dimen@<\wd\z@\dimen@\wd\z@\fi
\expandafter\@wordtable\fi}

\makeatother
\begin{document}

\wordtable

\end{document}


From a word file

red, yellow, blue, Automatically, formatting, a, table, from, a, file, with, a, list, of, words

-