%\iffalse
%<*package>
%% \CharacterTable
%%  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%%   Lower-case    \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%%   Digits        \0\1\2\3\4\5\6\7\8\9
%%   Exclamation   \!     Double quote  \"     Hash (number) \#
%%   Dollar        \$     Percent       \%     Ampersand     \&
%%   Acute accent  \'     Left paren    \(     Right paren   \)
%%   Asterisk      \*     Plus          \+     Comma         \,
%%   Minus         \-     Point         \.     Solidus       \/
%%   Colon         \:     Semicolon     \;     Less than     \<
%%   Equals        \=     Greater than  \>     Question mark \?
%%   Commercial at \@     Left bracket  \[     Backslash     \\
%%   Right bracket \]     Circumflex    \^     Underscore    \_
%%   Grave accent  \`     Left brace    \{     Vertical bar  \|
%%   Right brace   \}     Tilde         \~}
%%\CheckSum{833}
%</package>
%\fi
%\def\filedate{19 Oct 2004}
%\def\fileversion{1.1}
%\def\filename{csvtools.dtx}
%\def\docdate{19 October 2004}
%\iffalse
% Doc-Source file to use with LaTeX2e
% Copyright (C) 2003 Nicola Talbot, all rights reserved.
%\fi
%
% \DeleteShortVerb{\|}
% \MakeShortVerb{"}
% \newcounter{example}
% \newenvironment{example}[1][\relax]{\refstepcounter{example}%
%\subsection*{Example~\theexample\ \ifx#1\relax\else(#1)\ \fi}%
% \addcontentsline{loe}{section}{\protect\numberline{\theexample}#1}}{\par\centerline{\rule{2in}{1pt}}\par}
% \title{csvtools.sty v\fileversion\ : A \LaTeXe\ Package Providing Access to Data Saved in a CSV File}
% \author{Nicola Talbot}
% \date{\docdate}
% \maketitle
% \tableofcontents
% \section*{List of Examples}\makeatletter\@starttoc{loe}\makeatother
% \section{Introduction}
% The "csvtools" package allows you to repeatedly perform
% a set of \LaTeX\ commands on data in each row
% of a comma separated variable (CSV) file.
% This can be used for mail merging, generating
% tables etc.
%
% \section{Mail Merging and Similar Applications}
%
%\DescribeMacro{\applyCSVfile}\noindent
% "\applyCSVfile["\meta{n}"]{"\meta{filename}"}{"\meta{text}"}"\\[10pt]
% Letters can be generated using data given in 
% each line from \meta{filename}.
% If the CSV file contains a header row, the
% unstarred version of "\applyCSVfile" should
% be used, otherwise the starred version "\applyCSVfile*"
% should be used.  The optional argument \meta{n}
% specifies on which line the actual data (not header line)
% starts.  The unstarred version defaults to line 2 
% (the header row is always assumed to be on line 1)
% and the starred version defaults to 1.
%
% With the unstarred version, the entries in the header row
% are used to generate commands of the form 
% \DescribeMacro{\insert...}"\insert"\meta{identifier}\footnote{See Note~\ref{itm:insert} in Section~\ref{sec:drawbacks}}
% to access corresponding elements
% in the row currently being processed.  For example,
% suppose the first line of the CSV file looks like:
%\begin{verbatim}
%Name,Address,Time,Date
%\end{verbatim}
% then the commands "\insertName", "\insertAddress",
% "\insertTime" and "\insertDate" are
% created, allowing you to use the entries in the first,
% second, third and fourth columns of the current row.
% If the header text contains non-alphabetical characters,
% e.g.\ "Full Name", then you will need to use 
% \DescribeMacro{\insertbyname}"\insertbyname{"\meta{text}"}",
% e.g.\ "\insertbyname{Full Name}".
%
% \begin{example}[Mail Merging]\label{ex:mail}
% Suppose there is a file called "details.csv" that has the 
% following contents:
%\begin{verbatim}
%Name,Address,Time,Date
%Miss A. Person,1 The Road\\The Town\\AB1 2XY,15.00,4th May 2004
%Mr A. N. Other,2 The Road\\The Town\\AB1 2XY,15.30,11th May 2004
%\end{verbatim}
% then the following code can be used to generate a letter for each person
% in the CSV file:
%\begin{verbatim}
%\applyCSVfile{details.csv}{%
%\begin{letter}{\insertName\\\insertAddress}
%\opening{Dear \insertName}
%
%You are invited to an interview at \insertTime\ on the \insertDate.
%
%\closing{Yours Sincerely}
%\end{letter}}
%\end{verbatim}
% Note that you could also use "\insertbyname{Name}" etc instead
% of "\insertName" etc.  Also note that you need to specify the
% file extension when specifying the filename.
%\end{example}
%
% \begin{example}[Multiple Figures]\label{ex:ps}
% Suppose "details.csv" looks like:
%\begin{verbatim}
%File,Caption
%circle.ps,A Circle
%rectangle.ps,A Rectangle
%triangle.ps,A Triangle
%\end{verbatim}
% Assuming that the files "circle.ps", "rectangle.ps"
% and "triangle.ps" exist, then the following code will
% generate a figure for each graphics file\footnote{The 
% \texttt{graphicx} package will be needed.}:
%\begin{verbatim}
%\applyCSVfile{sample3.csv}{
%\begin{figure}
%\centerline{\includegraphics{\insertFile}}
%\caption{\insertCaption}
%\end{figure}}
%\end{verbatim}
% Note that in this example, you can't use "\insertbyname{File}".
% (See Note~\ref{itm:psbyname} in Section~\ref{sec:drawbacks}.)
%\end{example}
%
%\vspace{10pt}%
%\DescribeMacro{\applyCSVfile*}\noindent
%"\applyCSVfile*["\meta{n}"]{"\meta{filename}"}{"\meta{text}"}"\\[10pt]
% In this case the CSV file has no header row, so
% there are no "\insert"\meta{identifier} or "\insertbyname{"\meta{label}"}" commands available,
% instead, the command \DescribeMacro{\field}"\field{"\meta{col}"}" should be used,
% where \meta{col} is the column number.
%
% \begin{example}[Mail Merging using \texttt{\textbackslash field}]
% Suppose there is a file called "details.csv" that has the 
% following contents:
%\begin{verbatim}
%Miss A. Person,1 The Road\\The Town\\AB1 2XY,15.00,4th May 2004
%Mr A. N. Other,2 The Road\\The Town\\AB1 2XY,15.30,11th May 2004
%\end{verbatim}
% then the following code can be used to generate a letter for each person
% in the CSV file:
%\begin{verbatim}
%\applyCSVfile*{details.csv}{%
%\begin{letter}{\field{1}\\\field{2}}
%\opening{Dear \field{1}}
%
%You are invited to an interview at \field{3}\ on the \field{4}.
%
%\closing{Yours Sincerely}
%\end{letter}}
%\end{verbatim}
%\end{example}
%
% \section{Converting CSV file into a tabular environment}
% \DescribeMacro{\CSVtotabular}
% "\CSVtotabular{"\meta{filename}"}{"\meta{col-align}"}{"\meta{first}"}{"\meta{middle}"}{"\meta{last}"}"\\[10pt]
% \meta{filename} is the name of the CSV file which must have a header row on line~1, 
% \meta{col-align} is the column alignment argument that gets passed
% to the "tabular" environment, \meta{first}
% is the code for the first line, \meta{middle} is the code
% for the middle lines and \meta{last} is the code for the last line.
% This is best demonstrated with an example.
%
% \begin{example}[Aligning Data from a CSV file]\label{ex:tab1}
% Suppose the file "sample.csv" looks like:
%\begin{verbatim}
%Name,Assignment 1,Assignment 2,Total
%A. Smith,80,70,150
%B. Jones,60,80,140
%J. Doe,85,75,160
%,75,75,150
%\end{verbatim}
% then the following code can be used to align the data:
%\begin{verbatim}
%\CSVtotabular{sample.csv}{lccc}{%
%\bfseries Name &
%\bfseries Assignment 1&
%\bfseries Assignment 2&
%\bfseries Total\\}{%
%\insertName &
%\insertbyname{Assignment 1} &
%\insertbyname{Assignment 2} &
%\insertTotal\\}{%
% &
%\insertbyname{Assignment 1} &
%\insertbyname{Assignment 2} &
%\insertTotal}
%\end{verbatim}
% The result of this code is shown in 
% Table~\ref{tab:ex1}\footnote{Note that \texttt{\textbackslash CSVtotabular} only 
% puts the data in a \texttt{tabular} environment not in a table}.
%\begin{table}
%\caption{Example~\ref{ex:tab1}}
%\label{tab:ex1}
%\vspace{10pt}
%\centerline{%
%\begin{tabular}{lccc}
%\bfseries Name &
%\bfseries Assignment 1 &
%\bfseries Assignment 2 &
%\bfseries Total\\
%A. Smith&80&70&150\\
%B. Jones&60&80&140\\
%J. Doe&85&75&160\\
% &75&75&150
%\end{tabular}}
%\end{table}
%\end{example}
%
% \vspace{10pt}
% \DescribeMacro{\ifnextrowlast}
% "\ifnextrowlast{"\meta{last-code}"}{"\meta{not-last-code}"}"\\[10pt]
% The command "\ifnextrowlast" can be used to vary what happens
% on the last but one row.  The following example illustrates
% this by placing "\hline\hline" after the penultimate row.
%
% \begin{example}[Adding Lines]\label{ex:tab2}
%\begin{verbatim}
%\CSVtotabular{sample.csv}{|l|ccc|}{%
%\hline\bfseries Name &
%\bfseries Assignment 1&
%\bfseries Assignment 2&
%\bfseries Total\\\hline\hline}{%
%\insertName &
%\insertbyname{Assignment 1} &
%\insertbyname{Assignment 2} &
%\insertTotal
%\ifnextrowlast{\\\hline\hline}{\\}}{%
% &
%\insertbyname{Assignment 1} &
%\insertbyname{Assignment 2} &
%\insertTotal\\\hline}
%\end{verbatim}
% This result of this code is shown in Table~\ref{tab:ex2}.
%\begin{table}
%\caption{Example~\ref{ex:tab2}}
%\label{tab:ex2}
%\vspace{10pt}
%\centerline{%
%\begin{tabular}{|l|ccc|}
%\hline\bfseries Name &
%\bfseries Assignment 1 &
%\bfseries Assignment 2 &
%\bfseries Total\\\hline\hline
%A. Smith&80&70&150\\
%B. Jones&60&80&140\\
%J. Doe&85&75&160\\\hline\hline
% &75&75&150\\\hline
%\end{tabular}}
%\end{table}
%\end{example}
%
% \begin{example}[Added Complexity]\label{ex:tab3}
% In this example, "\multicolumn" is used to override
% the column specifier for the first column in the 
% last row.
%\begin{verbatim}
%\CSVtotabular{sample2.csv}{|l|ccc|}{%
%\hline\bfseries Name & 
%\bfseries Assignment 1 & 
%\bfseries Assignment 2 & 
%\bfseries Total\\\hline\hline
%}{%
%\insertName & 
%\insertbyname{Assignment 1} & 
%\insertbyname{Assignment 2} & 
%\insertTotal
%\ifnextrowlast{\\\hline\multicolumn{1}{l|}{}}{\\}
%}{%
% & 
%\insertbyname{Assignment 1} & 
%\insertbyname{Assignment 2} & 
%\insertTotal\\\cline{2-4}
%}
%\end{verbatim}
% Notice that instead of placing "\multicolumn{1}{l|}{}"
% at the start of the final argument, it is instead
% placed in the first argument to "\ifnextrowlast"\footnote{See 
% Note~\ref{itm:noalign} in Section~\ref{sec:drawbacks}}.
% The result of this code is shown in Table~\ref{tab:ex3}.
%\begin{table}
%\caption{Example~\ref{ex:tab3}}
%\label{tab:ex3}
%\vspace{10pt}
%\centerline{%
%\begin{tabular}{|l|ccc|}
%\hline\bfseries Name &
%\bfseries Assignment 1 &
%\bfseries Assignment 2 &
%\bfseries Total\\\hline\hline
%A. Smith&80&70&150\\
%B. Jones&60&80&140\\
%J. Doe&85&75&160\\\hline
% \multicolumn{1}{l|}{}&75&75&150\\\cline{2-4}
%\end{tabular}}
%\end{table}
%\end{example}
%
% \section{Converting CSV file into longtable environment}
%\DescribeMacro{\CSVtolongtable}
%The command "\CSVtolongtable" works in the same way as "\CSVtotabular" but
% creates a "longtable" environment instead of a "tabular" environment.
%
%\begin{example}[Using a longtable environment]
%Suppose the CSV file in the previous example, contains, say, 100 entries.
%This will no longer fit onto one page, so it would be better to use
%"\CSVtolongtable" instead. For example:
%\begin{verbatim}
%\CSVtolongtable{sample.csv}{|l|ccc|}{%
%\caption{Student Marks}\label{tab:students}\\
%\hline
%\bfseries Name &
%\bfseries Assignment 1 &
%\bfseries Assignment 2 &
%\bfseries Total\\\hline
%\endfirsthead
%\caption[]{Student Marks}\\
%\hline
%\bfseries Name &
%\bfseries Assignment 1 &
%\bfseries Assignment 2 &
%\bfseries Total\\\hline
%\endhead
%\hline
%\multicolumn{3}{r}{\em Continued on next page}
%\endfoot
%\hline
%\endlastfoot}{%
%\insertName &
%\insertbyname{Assignment 1} &
%\insertbyname{Assignment 2} &
%\insertTotal
%\ifnextrowlast{\\\hline\hline}{\\}}{%
% & \insertbyname{Assignment 1} &
% \insertbyname{Assignment 2} &
%\insertTotal\\}
%\end{verbatim}
%\end{example}
%
% \section{Associated Counters}
%
% Within the "\CSVtotabular", "\CSVtolongtable" and "\applyCSVfile" commands, there are two
% counters, \DescribeMacro{csvlinenum}\DescribeMacro{csvrownumber}%
% "csvlinenum" and "csvrownumber".
% The former, "csvlinenum", is the current line number in the CSV
% file, whereas the latter, "csvrownumber", is the current
% data row. Of the two counters, "csvrownumber" is likely to be
% the most useful.
%
% \begin{example}[Stripy Table]
% The package "colortbl" defines the command "\rowcolor"
% which enables you to specify the row colour. Suppose
% you want a stripy table\footnote{This is designed as an example
% of how to use the package, not incouragement to produce garish tables!}, 
% this can be achieved as follows:
%\begin{verbatim}
%\CSVtotabular{sample2.csv}{lccc}{%
%\rowcolor{green}\bfseries Name & 
%\bfseries Assignment 1 & 
%\bfseries Assignment 2 & 
%\bfseries Total\\\rowcolor{blue}
%}{%
%\insertName & 
%\insertbyname{Assignment 1} & 
%\insertbyname{Assignment 2} & 
%\insertTotal
%\ifthenelse{\isodd{\value{csvrownumber}}}{%
%\\\rowcolor{blue}}{\\\rowcolor{green}}
%}{%
% & 
%\insertbyname{Assignment 1} & 
%\insertbyname{Assignment 2} & 
%\insertTotal
%}
%\end{verbatim} 
%\end{example}
%
%\begin{example}[More Mail Merging]
% This is an example of mail merging where the letter reference
% is generated from the value of "csvrownumber".  The CSV file is
% as used in Example~\ref{ex:mail} on page~\pageref{ex:mail}.
%\begin{verbatim}
%\applyCSVfile{details.csv}{%
%\begin{letter}{\insertName\\\insertAddress}
%\opening{Dear \insertName}
%
%\textbf{Ref : } interview.\thecsvrownumber
%
%You are invited to an interview at \insertTime\ on the \insertDate.
%
%\closing{Yours Sincerely}
%\end{letter}}
%\end{verbatim}
%\end{example}
%
% \section{Cross-Referencing}
%
% Labels can be generated using the standard
% "\label" command, but you will need some way
% to make each label unique.  Example~\ref{ex:ref1}
% does this by using "\thecsvrownumber",
% whereas Example~\ref{ex:ref2} uses "\insert"\meta{identifier}.
%
% \begin{example}[Labelling within \texttt{\textbackslash applyCSVfile}]\label{ex:ref1}
% Example~\ref{ex:ps} on page~\pageref{ex:ps} can be modified to 
% label each figure:
%\begin{verbatim}
%\applyCSVfile{sample3.csv}{
%\begin{figure}
%\centerline{\includegraphics{\insertFile}}
%\caption{\insertCaption}
%\label{fig:pic\thecsvrownumber}
%\end{figure}}
%\end{verbatim}
% This example uses
% "\label{fig:pic\thecsvrownumber}", so the first figure generated
% by this "\applyCSVfile" command will have the label "fig:pic1",
% the second "fig:pic2" etc.
%\end{example}
%
% \begin{example}[Labelling within \texttt{\textbackslash applyCSVfile}]\label{ex:ref2}
% Modifying the previous example, we now have:
%\begin{verbatim}
%\applyCSVfile{sample3.csv}{
%\begin{figure}
%\centerline{\includegraphics{\insertFile}}
%\caption{\insertCaption}
%\label{fig:\insertFile}
%\end{figure}}
%\end{verbatim}
% The labels for each figure are now:
% "fig:circle.ps", "fig:rectangle.ps"
% and "fig:triangle.ps", respectively.
%\end{example}
%
%\begin{example}[Labelling within \texttt{CSVtotabular}]\label{ex:timetogrowth}
% This example is slightly more complicated.  
% The CSV file, "data.csv" looks like:
%\begin{verbatim}
%Incubation Temperature,Incubation Time,Time to Growth
%40,120,40
%40,90,60
%35,180,20
%\end{verbatim}
%The following code generates a table using the data
% with an additional column that generates the experiment
% number. (See note~\ref{itm:csvrownumber}.)
%\begin{verbatim}
%\begin{table}
%\caption{Time to Growth Experiments}
%\label{tab:exp}
%\vspace{10pt}
%\centering
%\CSVtotabular{data.csv}{cccc}{%
% % Header Row
%\bfseries Experiment &
%\bfseries \begin{tabular}{c}Incubation\\Temperature\end{tabular} &
%\bfseries \begin{tabular}{c}Incubation\\Time\end{tabular} &
%\bfseries \begin{tabular}{c}Time\\to\\Growth\end{tabular}\\}{%
% % Middle Rows
%\label{exp:\insertbyname{Incubation Temperature}:\insertbyname{Incubation Time}}
%\thecsvrownumber &
%\insertbyname{Incubation Temperature} &
%\insertbyname{Incubation Time} &
%\insertbyname{Time to Growth} \\}{%
% % Final Row
%\label{exp:\insertbyname{Incubation Temperature}:\insertbyname{Incubation Time}}
%\thecsvrownumber &
%\insertbyname{Incubation Temperature} &
%\insertbyname{Incubation Time} &
%\insertbyname{Time to Growth}}
%\par
%\end{table}
%
%It can be seen from Table~\ref{tab:exp}, that 
%Experiment~\ref{exp:35:180} had the shortest time to growth.
%\end{verbatim}
% In this example, each experiment has the corresponding label
% "exp:"\meta{Incubation Temperature}":"\meta{Incubation Time}
% so the first experiment has label "exp:40:120", the
% second experiment has the label "exp:40:90" and the
% third experiment has the label "exp:35:180".
%
% Table~\ref{tab:timetogrowth} shows the resulting table for
% this example.
% \begin{table}
%\caption{Time to Growth Experiments}
%\label{tab:timetogrowth}
%\vspace{10pt}
%\centering
%\begin{tabular}{cccc}
%\bfseries Experiment &
%\bfseries \begin{tabular}{c}Incubation\\Temperature\end{tabular} &
%\bfseries \begin{tabular}{c}Incubation\\Time\end{tabular} &
%\bfseries \begin{tabular}{c}Time\\to\\Growth\end{tabular}\\
% 1 & 40 & 120 & 40\\
% 2 & 40 & 90 & 60\\
% 3 & 35 & 180 & 20
%\end{tabular}
%\par
% \end{table}
%\end{example}
%
% The following example is more refined in that it 
% takes advantage of the fact that the time to growth data consists
% of integers only, so the experiment with the maximum growth can be 
% determined by \LaTeX.
%
% \begin{example}[Labelling within \texttt{CSVtotabular}]
%\begin{verbatim}
%\newcounter{maxgrowth}
%\newcounter{incT} % incubation temperature
%\newcounter{inct} % incubation time
%
%\begin{table}
%\caption{Time to Growth Experiments}
%\label{tab:exp}
%\vspace{10pt}
%\centering
%\CSVtotabular{data.csv}{cccc}{%
% % Header row
%\bfseries Experiment &
%\bfseries \begin{tabular}{c}Incubation\\Temperature\end{tabular} &
%\bfseries \begin{tabular}{c}Incubation\\Time\end{tabular} &
%\bfseries \begin{tabular}{c}Time\\to\\Growth\end{tabular}\\}{%
% % Middle rows
%\label{exp:\insertbyname{Incubation Temperature}:\insertbyname{Incubation Time}}
%\thecsvrownumber &
%\insertbyname{Incubation Temperature} &
%\insertbyname{Incubation Time} &
%\insertbyname{Time to Growth}%
%\ifthenelse{\value{maxgrowth}<\insertbyname{Time to Growth}}{%
%\setcounter{maxgrowth}{\insertbyname{Time to Growth}}%
%\setcounter{incT}{\insertbyname{Incubation Temperature}}%
%\setcounter{inct}{\insertbyname{Incubation Time}}}{}%
%\\}{%
% % Last row
%\label{exp:\insertbyname{Incubation Temperature}:\insertbyname{Incubation Time}}
%\thecsvrownumber &
%\insertbyname{Incubation Temperature} &
%\insertbyname{Incubation Time} &
%\insertbyname{Time to Growth}%
%\ifthenelse{\value{maxgrowth}<\insertbyname{Time to Growth}}{%
%\setcounter{maxgrowth}{\insertbyname{Time to Growth}}%
%\setcounter{incT}{\insertbyname{Incubation Temperature}}%
%\setcounter{inct}{\insertbyname{Incubation Time}}}{}%
%}
%\par
%\end{table}
%
%As can be seen from Table~\ref{tab:exp}, 
%Experiment~\ref{exp:\theincT:\theinct}
%had the maximum time to growth, with
%incubation time \theinct,
%incubation temperature \theincT\ and
%time to growth, \themaxgrowth.
%\end{verbatim}
% \end{example}
%
% \section{Saving Entries}
% Entries can be saved using the command:\\[10pt]
% \DescribeMacro{\csvSaveEntry}
% "\csvSaveEntry["\meta{counter}"]{"\meta{identifier}"}"\\[10pt]
% where \meta{counter} is a \LaTeX\ counter, by default "csvrownumber",
% and \meta{identifier} is the header entry.  The entry
% can then be used with the command:\\[10pt]
% \DescribeMacro{\csvGetEntry}
% "\csvGetEntry{"\meta{counter}"}{"\meta{identifier}"}"\\[10pt]
% The following example illustrates the use of these commands.
%
% \begin{example}[Saving Entries]\label{ex:index}
% This example illustrates how you can use one CSV
% file to access data in other CSV files.
% This example has several CSV files:
%\\[10pt]
% File "index.csv":
%\begin{verbatim}
%File,Temperature,NaCl,pH
%exp25a.csv,25,4.7,0.5
%exp25b.csv,25,4.8,1.5
%exp30a.csv,30,5.12,4.5
%\end{verbatim}
% File "exp25a.csv":
%\begin{verbatim}
%Time,Logcount
%0,3.75
%23,3.9
%45,4.0
%\end{verbatim}
% File "exp25b.csv":
%\begin{verbatim}
%Time,Logcount
%0,3.6
%60,3.8
%120,4.0
%\end{verbatim}
% File "exp30a.csv":
%\begin{verbatim}
%Time,Logcount
%0,3.73
%23,3.67
%60,4.9
%\end{verbatim}
%
% It is not possible to nest "\CSVtotabular", "\CSVtolongtable" and "\applyCSVfile",
% so if you need to go through "index.csv" and use each file
% named in there, you can first go through "index.csv" storing
% the information using "\csvSaveEntry" as follows:
%\begin{verbatim}
%\newcounter{maxexperiments}
%\applyCSVfile{sample5.csv}{%
%\stepcounter{maxexperiments}
%\csvSaveEntry{File}
%\csvSaveEntry{Temperature}
%\csvSaveEntry{NaCl}
%\csvSaveEntry{pH}
%}
%\end{verbatim}
% The counter "maxexperiments" simply counts the number of 
% entries in "index.csv".
% The entries can now be used to generate a table for each
% file listed in "index.csv" (the "\whiledo" command is 
% defined in the "ifthen" package):
%\begin{verbatim}
%\newcounter{experiment}
%\whiledo{\value{experiment}<\value{maxexperiments}}{%
%\stepcounter{experiment}
%\begin{table}
%\caption{Temperature = \protect\csvGetEntry{experiment}{Temperature},
%NaCl = \protect\csvGetEntry{experiment}{NaCl},
%pH = \protect\csvGetEntry{experiment}{pH}}
%\vspace{10pt}
%\centering
%\CSVtotabular{\csvGetEntry{experiment}{File}}{ll}{%
%Time & Log Count\\}{%
%\insertTime & \insertLogcount\\}{%
%\insertTime & \insertLogcount}
%
%\end{table}
%}
%\end{verbatim}
% Note that "\csvGetEntry" needs to be "\protect"ed within
% the "\caption" command.
%
% This example can be modified if, say, you only want the
% tables where the temperature is 25:
%\begin{verbatim}
%\setcounter{experiment}{0}
%\whiledo{\value{experiment}<\value{maxexperiments}}{%
%\stepcounter{experiment}
%\ifthenelse{\equal{\csvGetEntry{experiment}{Temperature}}{25}}{%
%\begin{table}
%\caption{Temperature = \protect\csvGetEntry{experiment}{Temperature},
%NaCl = \protect\csvGetEntry{experiment}{NaCl},
%pH = \protect\csvGetEntry{experiment}{pH}}
%\vspace{10pt}
%\centering
%\CSVtotabular{\csvGetEntry{experiment}{File}}{ll}{%
%Time & Log Count\\}{%
%\insertTime & \insertLogcount\\}{%
%\insertTime & \insertLogcount}\par
%\end{table}}{}
%}
%\end{verbatim}
% \end{example}
%
%\section{The csvtools.pl Perl Script}
%
% Suppose you have several large CSV files, and you have included the information into
% your document using "\applyCSVfile", "\CSVtolongtable" or "\CSVtotabular", which has made life so much easier for
% you, but you are now required by a journal to submit your source code in a single ".tex" file.
% They don't want all your CSV files, so what do you do?  If you have Perl installed on your system
% you can use the "csvtools.pl" Perl script.  This has the following syntax:\\[5pt]
% "csvtools.pl" \meta{in-file} \meta{out-file}\\[5pt]
% where \meta{in-file} is the name of your file that contains the "\applyCSVfile", "\CSVtotabular" etc
% commands, and \meta{out-file} is a new file which will be created by "csvtools.pl".  This new
% file will be the same as \meta{in-file} except that all occurances of "\applyCSVfile", "\CSVtolongtable" and
% "\CSVtotabular" will be replaced by the relevant data extracted from the named CSV files.
%
%\begin{example}[csvtools.pl --- Aligning Data]
% Suppose the file "mydoc.tex" contains the code given in Example~\ref{ex:tab1}, with the associated
% CSV file "sample.csv" also given in that example.  Then if you do:
%\begin{verbatim}
%csvtools.pl mydoc.tex mydocnew.tex
%\end{verbatim}
%the file "mydocnew.tex" will be created which will be identical to "mydoc.tex" except the lines
% containing the code "\CSVtotabular{sample.csv}{lccc}{"\ldots"}{"\dots"}{"\ldots"}" will be replaced
%with the lines:
%\begin{verbatim}
% % \CSVtotabular{sample.csv}... converted using csvtools.pl
% %>> START INSERT
% \begin{tabular}{lccc}
% \bfseries Name &
% \bfseries Assignment 1 &
% \bfseries Assignment 2 &
% \bfseries Total\\
% A. Smith&80&70&150\\
% B. Jones&60&80&140\\
% J. Doe&85&75&160\\
%  &75&75&150
% \end{tabular}%<< END INSERT
%\end{verbatim}
%\end{example}
%
%Similarly, "csvtools.pl" will substitute all occurrances of "\CSVtolongtable" and "\applyCSVfile".
%
%\subsection{Notes}
%\begin{enumerate}
%\item The "csvtools.pl" file is bundled up with the code and documentation in the file
% "csvtools.dtx".  It will be extracted along with the code when you \LaTeX\ the 
% installation script "csvtools.ins".  If you are using UNIX or Linux etc, you will need
% to set the permissions so that the file can be executed:
%\begin{verbatim}
%chmod u+x csvtools.pl
%\end{verbatim}
%If perl is located in a directory other than "/usr/bin/" you will need to edit the
%first line of "csvtools.pl" as appropriate. You can find the location using the command:
%\begin{verbatim}
%which perl
%\end{verbatim}
%
%\item If you can't directly execute a Perl script,
% you can do:\\[5pt]
% "perl csvtools.pl" \meta{in-file} \meta{out-file}
%
%\item You must first \LaTeX\ your document before using "csvtools.pl" as it checks the
% log file for any counters that have been defined.
%
%\item "csvtools.pl" only knows about a very limited set of \LaTeX\ commands.  It should
% be able to understand:
%\begin{verbatim}
%\CSVtotabular{\csvGetEntry{experiment}{File}}{ll}{...
%\end{verbatim}
%(see Example~\ref{ex:index}), but it won't be able to understand, say,
%\begin{verbatim}
%\newcommand{\filename}{\csvGetEntry{experiment}{File}}
%\CSVtotabular{\filename}{ll}{...
%\end{verbatim}
%It can pick up on "\addtocounter", "\stepcounter", "\refstepcounter" and "\setcounter"
% but only if they are used explicitly in the named ".tex" file. (It ignores any files
% that have been included using "\input", "\include" etc.)
%
%\item This Perl script has only been tested under Linux, but it ought to work under other
% systems.
%\end{enumerate}
%
% \section{Bugs/Drawbacks/``Features''}\label{sec:drawbacks}
% \begin{enumerate}
% \item\label{itm:insert}
% The package doesn't check to see whether "\insert"\meta{identifier}
% exists, otherwise you would not be able to use multiple CSV
% files with the same headers, as in Example~\ref{ex:index}.  Therefore it is recommended that
% you check to make sure that the command does not already exist.
% For example, the \TeX\ commands "\insert" and "\insertpenalties"
% already exist, so a blank header or a header named "penalties"
% would cause problems. (These two will now cause an error as from version 1.1, but it's something bear in mind.)
%
% \item Note also that "\insertbyname" doesn't check
% if you've given a valid label, so if no text appears,
% check you've spelt it correctly, checking punctuation, spaces and case.
%
% \item\label{itm:psbyname}
% Note that in Example~\ref{ex:ps}, replacing line~3 with:
%\begin{verbatim}
%\centerline{\includegraphics{\insertbyname{File}}}
%\end{verbatim}
% will cause an error, as "\insertbyname{File}" doesn't get
% fully expanded by the time it gets passed to 
% "\includegraphics", and will prevent "\includegraphics" from
% finding the file.  It is possible to get around this using
% \TeX's "\edef" command:
%\begin{verbatim}
%\edef\psfilename{\insertbyname{File}}
%\centerline{\includegraphics{\psfilename}}
%\end{verbatim}
%
% \item\label{itm:noalign} You can't have commands like "\hline", "\cline"
% and "\multicolumn" in the first column of the
% \meta{middle} or \meta{last} code of "\CSVtotabular" or "\CSVtolongtable".  If you do, it will generate
% a "misplaced \noalign" error, instead you need to put it
% at the end of the \meta{first} or \meta{middle} code.
% (See Example~\ref{ex:tab3}.)
%
% \item You can't have nested "\applyCSVfile", "\CSVtolongtable" and "\CSVtotabular"
% commands. (See Example~\ref{ex:index})
%
% \item If the CSV file has a header row, it must be on the first line.
%
% \item It is possible for \TeX\ to run out of memory if you use
% "\csvSaveEntry" on a large file.
%
% \item Commas within an entry in a CSV file may cause problems.  You will need to enclose
% the entry in braces, e.g.\ "{Joe Smith, Jr}".
%
% \item\label{itm:csvrownumber} In version 1.0, there was an inconsistency with "csvrownumber" within
%"\applyCSVfile" and "\CSVtotabular".  In the former it excluded the header row, whereas the latter
%included it.  This has been changed in version 1.1 so that within "\applyCSVfile", "\CSVtotabular"
% and "\CSVtolongtable", "csvrownumber" refers to the data row (excluding header row.)  I hope this
% doesn't cause problems, but it makes more sense that they should be consistent.  So if you have no
%blank lines in your CSV file, "csvrownumber" should always be 1 more than "csvlinenumber".
%\end{enumerate}
%
% \section{Contact Details}
%
% Dr Nicola Talbot\\
% School of Computing Sciences\\
% University of East Anglia\\
% Norwich.  NR4 7TJ. England.
% \\[10pt]
% \url{http://theoval.cmp.uea.ac.uk/~nlct/}
%
%
%\StopEventually{}
% \section{The Code}
% \iffalse
% First we have the driver to get the documentation
%    \begin{macrocode}
%<*driver>
\documentclass{ltxdoc}
\usepackage[colorlinks,bookmarks]{hyperref}
\begin{document}
\OnlyDescription
\DocInput{csvtools.dtx}
\end{document}
%</driver>
%    \end{macrocode}
% \fi
% At the start of the package, specify that the \LaTeXe\ format is
% required.  And specify the package provided.
%    \begin{macrocode}
%<*package>
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{csvtools}[2003/12/18 v1.0]
%    \end{macrocode}
% Conditionals will be needed, so include "ifthen" package
%    \begin{macrocode}
\RequirePackage{ifthen}
\RequirePackage{longtable}
%    \end{macrocode}
% The following lines are modified from `The TeXBook' Appendix D
%   \begin{macrocode}
\def\lop#1\to#2{\expandafter\lopoff#1\lopoff#1#2}
\long\def\lopoff,#1,#2\lopoff#3#4{\def#4{#1}\def#3{,#2}}
%    \end{macrocode}
%
% Define variables needed later
%    \begin{macrocode}
\newread\csvfile
\newif\ifmore
\newcount\c@field
\newcounter{csvlinenum}
\toksdef\ta=0 \toksdef\tb=2
%    \end{macrocode}
% Define "\field" command.  Takes a number as the argument.
% Used to access the nth field.  If there is no nth field
% command has no effect.
%    \begin{macrocode}
\newcommand{\field}[1]{\csname @field\romannumeral#1\endcsname}
%    \end{macrocode}
% Instead of doing, say, "\insertAddress", need to be able to
% do "\insertbyname{Address}".  Useful if the field name
% contains non-alphabetical characters.
%    \begin{macrocode}
\newcommand{\insertbyname}[1]{\csname insert#1\endcsname}
%    \end{macrocode}
%
% "\read" appends a space at the end of the line, so define
% command to trim final space.
%    \begin{macrocode}
\newcommand{\trim}[1]{\def\@trmstr{}\expandafter\@trim#1\end\edef#1{\@trmstr}}
\def\@trim#1 \end{\def\@trmstr{#1}}
%    \end{macrocode}
%
% Define starred version of command 
%    \begin{macrocode}
\newcommand{\@sapplyCSVfile}[3][1]{%
\IfFileExists{#2}{%
\openin\csvfile=#2
%
\ifeof\csvfile\morefalse\else\moretrue\fi

\ifmore
{\c@csvlinenum=1\relax
\global\c@csvrownumber=0\relax
 \loop
 \read\csvfile to\csvline
 \advance\c@csvlinenum by 1\relax
 \ifnum\c@csvlinenum>#1\relax
   \trim{\csvline}
   \tb=\expandafter{\csvline}
   \edef\@csvlin@{,\the\tb,}
   \c@field = 0\relax
   \whiledo{\not\equal{\@csvlin@}{,\par,} \and \not\equal{\@csvlin@}{,,} \and \not\equal{\@csvlin@}{,}}{%
   \lop\@csvlin@\to\param
   \tb=\expandafter{\param}
   \advance\c@field by 1\relax
   \expandafter\xdef\csname @field\romannumeral\c@field\endcsname{\the\tb}
   }
   \ifthenelse{\not\equal{\csvline}{\par} \and \not\equal{\csvline}{}}{%
   \refstepcounter{csvrownumber}\relax
   #3}{}
 \fi
%
 \ifeof\csvfile\morefalse\else\moretrue\fi
 \ifmore
 \repeat}
%
 \closein\csvfile
%
\fi
}{\PackageError{csvtools}{Can't find file '#2'}{}}
}
%    \end{macrocode}
% Now define unstarred version of the command
%    \begin{macrocode}
\newcommand{\@applyCSVfile}[3][2]{%
\ifnum#1<2
   \PackageError{csvtools}{Header line required}{The optional argument to `\protect\applyCSVfile'
needs to be > 1.  The header line should be on line 1}
\else
\IfFileExists{#2}{%
\openin\csvfile=#2
%
\ifeof\csvfile\morefalse\else\moretrue\fi
\ifmore
\global\c@csvlinenum=1\relax
\global\c@csvrownumber=0\relax
 {\loop
 \read\csvfile to\csvline
 \ifnum\c@csvlinenum=1\relax
%header line
   \ifthenelse{\not\equal{\csvline}{\par} \and \not\equal{\csvline}{}}{%
%trim end of line character
   \trim{\csvline}
%delimit start and end with commas
   \tb=\expandafter{\csvline}
   \edef\@csvlin@{,\the\tb,}
%read in each entry
   \c@field = 0\relax
   \whiledo{\not\equal{\@csvlin@}{,\par,} \and \not\equal{\@csvlin@}{,,} \and \not\equal{\@csvlin@}{,}}{%
   \lop\@csvlin@\to\param
   \advance\c@field by 1\relax
   \tb=\expandafter{\param}
   %\expandafter\xdef\csname insert\the\tb\endcsname{\field{\the\c@field}}
   \expandafter\xdef\csname @fieldlabel\romannumeral\c@field\endcsname{\the\tb}
   }
   }{\PackageError{csvtools}{Header line missing in file #2}{Header line required on line 1}}
 \fi
 \advance\c@csvlinenum by 1\relax
 \ifnum\c@csvlinenum>#1\relax
   \tb=\expandafter{\csvline}
   \edef\@csvlin@{,\the\tb,}
   \c@field = 0\relax
   \whiledo{\not\equal{\@csvlin@}{,\par,} \and \not\equal{\@csvlin@}{,,} \and \not\equal{\@csvlin@}{,}}{%
   \lop\@csvlin@\to\param
   \tb=\expandafter{\param}
   \advance\c@field by 1\relax
   \expandafter\xdef\csname @field\romannumeral\c@field\endcsname{\the\tb}
   \edef\@fieldlabel{\csname @fieldlabel\romannumeral\c@field\endcsname}
   \ifthenelse{\equal{\@fieldlabel}{} 
        \TE@or \equal{\@fieldlabel}{penalties}
        \TE@or \equal{\@fieldlabel}{byname}}{\PackageError{csvtools}{%
       \string\insert\@fieldlabel \space already defined}{%
       You can't have the label '\@fieldlabel' in your header row.}}{}\relax
   \expandafter\xdef\csname insert\@fieldlabel\endcsname{\the\tb}
   }
   \ifthenelse{\not\equal{\csvline}{\par} \and \not\equal{\csvline}{}}{%
   \global\advance\c@csvrownumber by 1\relax
   #3}{}
 \fi
%
 \ifeof\csvfile\morefalse\else\moretrue\fi
 \ifmore
 \repeat}
%
 \closein\csvfile
%
\fi
}{\PackageError{csvtools}{Can't find file '#2'}{}}
\fi
}
%    \end{macrocode}
% Define "\applyCSVfile" to call "\@sapplyCSVfile" if starred version
% and "\@applyCSVfile" if not starred version
%    \begin{macrocode}
\newcommand{\applyCSVfile}{\@ifstar\@sapplyCSVfile\@applyCSVfile}
%    \end{macrocode}
% Define command to convert CSV file into tabular environment.
% Takes one argument that specifies the argument to the
% tabular environment.
%    \begin{macrocode}
\newcount\maxlines
\newcount\csvlastbutone
\newcounter{csvrownumber}
\newcommand{\CSVtotabular}[5]{%
\openin\csvfile=#1
\c@csvlinenum=0\relax
 \loop
 \advance\c@csvlinenum by 1\relax
 \read\csvfile to\csvline
 \expandafter\xdef\csname @csvline\romannumeral\c@csvlinenum\endcsname{\csvline}
 \ifthenelse{\not\equal{\csvline}{\par}}{\trim{\csvline}}{}
 \ifnum\c@csvlinenum=1\relax
   \tb=\expandafter{\csvline}
   \edef\@csvlin@{,\the\tb,}
   \c@field = 0\relax
   \whiledo{\not\equal{\@csvlin@}{,\par,} \and \not\equal{\@csvlin@}{,,} \and \not\equal{\@csvlin@}{,}}{%
   \lop\@csvlin@\to\param
   \tb=\expandafter{\param}
   \advance\c@field by 1\relax
   \expandafter\xdef\csname insert\the\tb\endcsname{\noexpand\field{\the\c@field}}
   }
 \fi
   \tb=\expandafter{\csvline}
   \edef\@csvlin@{,\the\tb,}
   \c@field = 0\relax
   \whiledo{\not\equal{\@csvlin@}{,\par,} \and \not\equal{\@csvlin@}{,,} \and \not\equal{\@csvlin@}{,}}{%
   \lop\@csvlin@\to\param
   \tb=\expandafter{\param}
   \advance\c@field by 1\relax
   \expandafter\xdef\csname @l\romannumeral\c@csvlinenum @field\romannumeral\c@field\endcsname{\the\tb}
   }
 \ifeof\csvfile\morefalse\else\moretrue\fi
 \ifmore
 \repeat
\closein\csvfile
{\def\field##1{\csname @l\romannumeral\c@csvlinenum @field\romannumeral##1\endcsname}
\def\@r@wh{\begin{tabular}{#2}#3}
\def\@r@w{#4}
\def\@r@wl{#5\end{tabular}}
\maxlines=\c@csvlinenum
\advance\maxlines by -1\relax
\csvlastbutone=\maxlines
\advance\csvlastbutone by -1\relax
\c@csvlinenum=1\relax
\setcounter{csvrownumber}{0}\relax
\whiledo{\not{\c@csvlinenum>\maxlines}}{%
\ifthenelse{\expandafter\equal{\csname @csvline\romannumeral\c@csvlinenum\endcsname}{\par}}{\relax}{%
\ifnum\c@csvlinenum=1\relax
%header row
\@r@wh
\else
\refstepcounter{csvrownumber}%
\ifnum\c@csvlinenum=\maxlines\@r@wl\else\@r@w\fi
\fi}%
\global\advance\c@csvlinenum by 1\relax
}\relax
}}
%    \end{macrocode}
%    \begin{macrocode}
%    \end{macrocode}
\newcommand{\CSVtolongtable}[5]{%
\openin\csvfile=#1
\c@csvlinenum=0\relax
 \loop
 \advance\c@csvlinenum by 1\relax
 \read\csvfile to\csvline
 \expandafter\xdef\csname @csvline\romannumeral\c@csvlinenum\endcsname{\csvline}
 \ifthenelse{\not\equal{\csvline}{\par}}{\trim{\csvline}}{}
 \ifnum\c@csvlinenum=1\relax
   \tb=\expandafter{\csvline}
   \edef\@csvlin@{,\the\tb,}
   \c@field = 0\relax
   \whiledo{\not\equal{\@csvlin@}{,\par,} \and \not\equal{\@csvlin@}{,,} \and \not\equal{\@csvlin@}{,}}{%
   \lop\@csvlin@\to\param
   \tb=\expandafter{\param}
   \advance\c@field by 1\relax
   \expandafter\xdef\csname insert\the\tb\endcsname{\noexpand\field{\the\c@field}}
   }
 \fi
   \tb=\expandafter{\csvline}
   \edef\@csvlin@{,\the\tb,}
   \c@field = 0\relax
   \whiledo{\not\equal{\@csvlin@}{,\par,} \and \not\equal{\@csvlin@}{,,} \and \not\equal{\@csvlin@}{,}}{%
   \lop\@csvlin@\to\param
   \tb=\expandafter{\param}
   \advance\c@field by 1\relax
   \expandafter\xdef\csname @l\romannumeral\c@csvlinenum @field\romannumeral\c@field\endcsname{\the\tb}
   }
 \ifeof\csvfile\morefalse\else\moretrue\fi
 \ifmore
 \repeat
\closein\csvfile
{\def\field##1{\csname @l\romannumeral\c@csvlinenum @field\romannumeral##1\endcsname}
\def\@r@wh{\begin{longtable}{#2}#3}
\def\@r@w{#4}
\def\@r@wl{#5\end{longtable}}
\maxlines=\c@csvlinenum
\advance\maxlines by -1\relax
\csvlastbutone=\maxlines
\advance\csvlastbutone by -1\relax
\c@csvlinenum=1\relax
\setcounter{csvrownumber}{0}\relax
\whiledo{\not{\c@csvlinenum>\maxlines}}{%
\ifthenelse{\expandafter\equal{\csname @csvline\romannumeral\c@csvlinenum\endcsname}{\par}}{\relax}{%
\ifnum\c@csvlinenum=1\relax
%header row
\@r@wh
\else
\refstepcounter{csvrownumber}%
\ifnum\c@csvlinenum=\maxlines\@r@wl\else\@r@w\fi
\fi}%
\global\advance\c@csvlinenum by 1\relax
}\relax
}}
%    \begin{macrocode}
\newcommand{\ifnextrowlast}[2]{\ifnum\c@csvlinenum=\csvlastbutone#1\else#2\fi}
%    \end{macrocode}
%    \begin{macrocode}
\newcommand{\csvSaveEntry}[2][csvrownumber]{%
\edef\@entry{\insertbyname{#2}}%
\expandafter\xdef\csname #2\romannumeral\value{#1}\endcsname{\@entry}}

\newcommand{\csvGetEntry}[2]{%
\csname #2\romannumeral\value{#1}\endcsname}
%</package>
%    \end{macrocode}
% Perl script
%    \begin{macrocode}
%<*perl>
#!/usr/bin/perl

# File        : csvtools.pl
# Author      : Dr Nicola Talbot
# Date        : 14 Oct 2004
# Description : Perl script to accompany csvtools.sty
#             : Allows you to substitute \CSVtotabular, \CSVtolongtable and \applyCSVfile commands with the appropriate LaTeX code
# Version     : 0.5b (18 October 2004).

# usage : csvtools <in-file> <out-file>

if ($#ARGV != 1)
{
   die "Syntax : $0 <in-file> <out-file>\n";
}

($FILENAME,$OUTPUT) = @ARGV;

open FILENAME or die "Can't open '$FILENAME'\n";

$ext = substr($FILENAME, -4);

if (($ext eq ".tex") or ($ext eq ".dtx") or ($ext eq ".ltx"))
{
   $LOGFILE =  substr($FILENAME,0,length($FILENAME)-4) . ".log";
}
else
{
   $LOGFILE = $FILENAME . ".log";
}

open LOGFILE or die "Can't open log file '$LOGFILE'.  Make sure you run LaTeX before using $0\n";

%counter = ();

while (<LOGFILE>)
{
   if (/\\c@([a-zA-Z]+)=\\count/)
   {
      $counter{$1} = 0;
   }
}

$counter{csvrownumber} = 0;

close LOGFILE;

open OUTPUT, ">$OUTPUT" or die;

%data = ();

while (<FILENAME>)
{
   $restofline = $_;

   while ($restofline=~/\\stepcounter(.*)/)
   {
      $line = $_;

      ($ctr,$restofline,$done) = getnextgroup($1);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\stepcounter on line $startline\n";
         }
      }

      $counter{$ctr}++;

      $_ = $line;
   }

   $restofline = $_;

   while ($restofline=~/\refstepcounter(.*)/)
   {
      $line = $_;

      ($ctr,$restofline,$done) = getnextgroup($1);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\stepcounter on line $startline\n";
         }
      }

      $counter{$ctr}++;

      $_ = $line;
   }

   $restofline = $_;

   while ($restofline=~/\\setcounter(.*)/)
   {
      $line = $_;

      ($ctr,$restofline,$done) = getnextgroup($1);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\stepcounter on line $startline\n";
         }
      }

      ($num,$restofline,$done) = getnextgroup($restofline);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($num,$restofline,$done) = getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning second argument to \\stepcounter on line $startline\n";
         }
      }

      $num=~s/\\value{(.+)}/$counter{$1}/;

      $counter{$ctr} = $num;

      $_ = $line;
   }

   $restofline = $_;

   while ($restofline=~/\\addtocounter(.*)/)
   {
      $line = $_;

      ($ctr,$restofline,$done) = getnextgroup($1);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\stepcounter on line $startline\n";
         }
      }

      ($num,$restofline,$done) = getnextgroup($restofline);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($num,$restofline,$done) = getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning second argument to \\stepcounter on line $startline\n";
         }
      }

      $num=~s/\\value{(.+)}/$counter{$1}/;

      $counter{$ctr} = $counter{$ctr} + $num;

      $_ = $line;
   }

   $restofline = $_;

   while ($restofline=~/^(.*)\\csvGetEntry(.*)$/)
   {
      $start = $1;
      $restofline = $2;

      ($ctr,$restofline,$done) = getnextgroup($restofline);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\csvGetEntry on line $startline\n";
         }
      }

      ($entry,$restofline,$done) = getnextgroup($restofline);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $restofline = $restofline . $nextline;

             ($entry,$restofline,$done) = getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\csvGetEntry on line $startline\n";
         }
      }
      $_ = $start . "\\csname $entry\\roman{$ctr}\\endcsname" . $restofline;
      #$_ = $start. $data{$entry}[$counter{$ctr}] . $restofline;
   }

   if (/^(.*)\\CSVtotabular(.*)$/)
   {
      print OUTPUT "$1\n";

      $line = $2;

      ($csvname,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

            ($csvname,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for CVS filename on line $.\n";
         }
      }

      $csvname=~s/\\csname (.*)\\roman{(.*)}\\endcsname/$data{$1}[$counter{$2}]/;

      $line = $restofline;

      ($alignment,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($alignment,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for CVS column alignment on line $.\n";
         }
      }

      $line = $restofline;

      ($FIRST,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($FIRST,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtotabular third argument on line $.\n";
         }
      }

      $line = $restofline;

      ($MIDDLE,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($MIDDLE,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtotabular fourth argument on line $.\n";
         }
      }

      $line = $restofline;

      ($LAST,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($LAST,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtotabular fifth argument on line $.\n";
         }
      }

      csvtotabular($csvname, $alignment, $FIRST, $MIDDLE, $LAST);

      print OUTPUT "$restofline\n";
   }
   elsif (/^(.*)\\CSVtolongtable(.*)$/)
   {
      print OUTPUT "$1\n";

      $line = $2;

      ($csvname,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

            ($csvname,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for CVS filename on line $.\n";
         }
      }

      $csvname=~s/\\csname (.*)\\roman{(.*)}\\endcsname/$data{$1}[$counter{$2}]/;

      $line = $restofline;

      ($alignment,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($alignment,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for CVS column alignment on line $.\n";
         }
      }

      $line = $restofline;

      ($FIRST,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($FIRST,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtolongtable third argument on line $.\n";
         }
      }

      $line = $restofline;

      ($MIDDLE,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($MIDDLE,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtolongtable fourth argument on line $.\n";
         }
      }

      $line = $restofline;

      ($LAST,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($LAST,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtolongtable fifth argument on line $.\n";
         }
      }

      csvtolongtable($csvname, $alignment, $FIRST, $MIDDLE, $LAST);

      print OUTPUT "$restofline\n";
   }
   elsif (/^(.*)\\applyCSVfile\*(.*)$/)
   {
      print OUTPUT $1;

      $restofline = $2;

      $restofline = eatinitialspaces($restofline);
      $restofline = eatcomments($restofline);

      $startrow=1;

      if (/^\[([0-9]+)\](.*)/)
      {
         $startrow = $1;

         $restofline = $2;
      }

      $line=$restofline;

      ($csvname,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($csvname,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\applyCSVfile*[$startrow] first argument on line $.\n";
         }
      }

      $csvname=~s/\\csname (.*)\\roman{(.*)}\\endcsname/$data{$1}[$counter{$2}]/;

      $line=$restofline;

      ($body,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($body,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\applyCSVfile*[$startrow] second argument on line $.\n";
         }
      }

      applyCSVfilestar($csvname, $body, $startrow);

      print OUTPUT "$restofline\n";
   }
   elsif (/^(.*)\\applyCSVfile(.*)$/)
   {
      print OUTPUT $1;

      $restofline = $2;

      $restofline = eatinitialspaces($restofline);
      $restofline = eatcomments($restofline);

      $startrow=2;

      if ($restofline=~/^\[(\d+)\](.*)/)
      {
         $startrow = $1;

         $restofline = $2;
      }

      $line=$restofline;

      ($csvname,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($csvname,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\applyCSVfile[$startrow] first argument on line $.\n";
         }
      }

      $line=$restofline;

      ($body,$restofline,$done) = getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($body,$restofline,$done) = getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\applyCSVfile[$startrow] second argument on line $.\n";
         }
      }

      applyCSVfile($csvname, $body, $startrow);

      print OUTPUT "$restofline\n";
   }
   else
   {
      print OUTPUT;
   }
}

sub applyCSVfile
{
   my ($CSVFILE,$body,$startrow) = @_;

   print "Converting \\applyCSVfile{$CSVFILE}";
   print OUTPUT "\% \\applyCSVfile{$CSVFILE}... converted using $0\n";
   print OUTPUT "\%>> START INSERT\n";
   print OUTPUT "\\setcounter{csvrownumber}{0}";
   $counter{csvrownumber} = 0;

   open FH, $CSVFILE or die "Can't open file '$CSVFILE'\n";

   $csvrow=0;

   while (<FH>)
   {
      if ($. == 1)
      {
         # get header row

         chop;

         @fields = split /,/;

         $numcols = 1;

         foreach $field (@fields)
         {
            $idx{$field} = $numcols;

            $numcols++;
         }
      }
      elsif ($. >= $startrow)
      {
         next unless /,/;

         chop;

         $csvrow++;
         $rec = {};

         $HoH[$csvrow] = $rec;

         @entries = split /,/;

         $i = 0;

         foreach $e (@entries)
         {
            $rec->{$fields[$i]} = $e;
            $i++;
         }
      }
   }

   close FH;

   foreach ($row=1; $row <= $csvrow; $row++)
   {
      print ".";

      $THISROW = $body;

      $THISROW = csvSaveEntry($THISROW,$row,%HoH);

      foreach $entry ( keys %{$HoH[$row]})
      {
          $replacementval=$HoH[$row]{$entry};

          $THISROW =~ s/\\insertbyname{$entry}/$replacementval/g;

          $THISROW =~ s/\\insert$entry/$replacementval/g;

          $column = $idx{$entry};

          $THISROW =~ s/\\field{$column}/$replacementval/g;

          $THISROW = ifnextrowlast($THISROW, ($row==$csvrow-1));
      }

      print OUTPUT "\\stepcounter{csvrownumber}$THISROW";
      $counter{csvrownumber}++;
   }

   print "\n";

   print OUTPUT "\%<< END INSERT\n";
}

sub csvSaveEntry
{
   my ($STR,$row,%HoH) = @_;

   $rowctr = "csvrownumber";

   while (($pos = index($STR, "\\csvSaveEntry")) > -1)
   {
      $start      = substr($STR,0,$pos);
      $restofline = substr($STR,$pos+13);

      $restofline = eatcomments($restofline);
      $restofline = eatinitialspaces($restofline);

      if ($restofline[0] eq "[")
      {
         if (($i = index($restofline,"]")) > -1)
         {
            $rowctr = substr($restofline, 1, $i-1);

            $restofline = substr($restofline, $i+1);
         }
         else
         {
            die "unmatched [ in \\csvSaveEntry\n";
         }

         $row = $counter{$rowctr};
      }

      ($group,$restofline,$done) = getnextgroup($restofline);

      if (!$done)
      {
         die "argument to \\csvSaveEntry[$rowctr] not found in >>$restofline<<\n";
      }

      $val = $HoH[$row]{$group};

      $STR = $start . "\\expandafter\\gdef\\csname $group\\roman{$rowctr}\\endcsname{$val}" . $restofline;

      $data{$group}[$row] = $HoH[$row]{$group};
   }

   return $STR;
}

sub applyCSVfilestar
{
   my ($CSVFILE,$body,$startrow) = @_;

   print "Converting \\applyCSVfile*{$CSVFILE}";
   print OUTPUT "\\setcounter{csvrownumber}{0}";
   $counter{csvrownumber} = 0;

   open FH, $CSVFILE or die "Can't open file '$CSVFILE'\n";

   $csvrow=0;

   while (<FH>)
   {
      if ($. >= $startrow)
      {
         $csvrow++;

         chop;

         @fields = split /,/;

         $numcols = 1;

         foreach $field (@fields)
         {
            $entry[$csvrow][$numcols-1] = $field;

            $numcols++;
         }
      }
   }

   close FH;

   for ($row=0; $row < $csvrow; $row++)
   {
      print ".";

      $THISROW = $body;

      for ($column=0; $column < $numcols; $column++)
      {
          $replacementval=$entry[$row][$column];

          $THISROW =~ s/\\field{$column}/$replacementval/g;

          $THISROW = ifnextrowlast($THISROW, ($row==$csvrow-1));
      }

      print OUTPUT "\\stepcounter{csvrownumber}$THISROW";
      $counter{csvrownumber}++;
   }

   print "\n";

   print OUTPUT "\%<< END INSERT\n";
}

sub csvtotabular
{
   my ($CSVFILE,$ALIGN,$START,$MID,$END) = @_;

   print "Converting \\CSVtotabular{$CSVFILE}";

   print OUTPUT "\% \\CSVtotabular{$CSVFILE}... converted using $0\n";
   print OUTPUT "\%>> START INSERT\n";

   print OUTPUT "\\setcounter{csvrownumber}{0}\n";
   $counter{csvrownumber} = 0;
   print OUTPUT "\\begin{tabular}{$ALIGN}\n";

   open FH, $CSVFILE or die "Can't open file '$CSVFILE'\n";

   $csvrow=0;

   while (<FH>)
   {
      if ($. == 1)
      {
         # get header row

         chop;

         @fields = split /,/;

         $numcols = 1;

         foreach $field (@fields)
         {
            $idx{$field} = $numcols;

            $numcols++;
         }

         $csvrow++;
         $rec = {};

         $HoH[$csvrow] = $rec;

         @entries = split /,/;

         $i = 0;

         foreach $e (@entries)
         {
            $rec->{$fields[$i]} = $e;
            $i++;
         }
      }
      elsif ($. > 1)
      {
         next unless /,/;

         chop;

         $csvrow++;
         $rec = {};

         $HoH[$csvrow] = $rec;

         @entries = split /,/;

         $i = 0;

         foreach $e (@entries)
         {
            $rec->{$fields[$i]} = $e;
            $i++;
         }
      }
   }

   close FH;

   foreach ($row=1; $row <= $csvrow; $row++ )
   {
      print ".";

      if ($row == 1)
      {
         $THISROW=$START;
      }
      elsif ($row == $csvrow)
      {
         $THISROW=$END;
      }
      else
      {
         $THISROW=$MID;
      }

      foreach $entry ( keys %{$HoH[$row]})
      {
          $replacementval=$HoH[$row]{$entry};

          $THISROW =~ s/\\insertbyname{$entry}/$replacementval/g;

          $THISROW =~ s/\\insert$entry/$replacementval/g;

          $column = $idx{$entry};

          $THISROW =~ s/\\field{$column}/$replacementval/g;

          $THISROW = ifnextrowlast($THISROW, ($row==$csvrow-1));
      }

      if ($row==1)
      {
         print OUTPUT "$THISROW";
      }
      else
      {
         print OUTPUT "\\refstepcounter{csvrownumber}$THISROW";
         $counter{csvrownumber}++;
      }
   }

   print OUTPUT "\\end{tabular}";
   print OUTPUT "\%<< END INSERT\n";

   print "\n";
}

sub csvtolongtable
{
   my ($CSVFILE,$ALIGN,$START,$MID,$END) = @_;

   print "Converting \\CSVtolongtable{$CSVFILE}";

   print OUTPUT "\% \\CSVtolongtable{$CSVFILE}... converted using $0\n";
   print OUTPUT "\%>> START INSERT\n";

   print OUTPUT "\\setcounter{csvrownumber}{0}\n";
   $counter{csvrownumber} = 0;
   print OUTPUT "\\begin{longtable}{$ALIGN}\n";

   open FH, $CSVFILE or die "Can't open file '$CSVFILE'\n";

   $csvrow=0;

   while (<FH>)
   {
      if ($. == 1)
      {
         # get header row

         chop;

         @fields = split /,/;

         $numcols = 1;

         foreach $field (@fields)
         {
            $idx{$field} = $numcols;

            $numcols++;
         }

         $csvrow++;
         $rec = {};

         $HoH[$csvrow] = $rec;

         @entries = split /,/;

         $i = 0;

         foreach $e (@entries)
         {
            $rec->{$fields[$i]} = $e;
            $i++;
         }
      }
      elsif ($. > 1)
      {
         next unless /,/;

         chop;

         $csvrow++;
         $rec = {};

         $HoH[$csvrow] = $rec;

         @entries = split /,/;

         $i = 0;

         foreach $e (@entries)
         {
            $rec->{$fields[$i]} = $e;
            $i++;
         }
      }
   }

   close FH;

   print "csvrow = $csvrow\n";

   for ($row=1; $row <= $csvrow; $row++)
   {
      print ".";

      if ($row == 1)
      {
         $THISROW=$START;
      }
      elsif ($row == $csvrow)
      {
         $THISROW=$END;
      }
      else
      {
         $THISROW=$MID;
      }

      foreach $entry ( keys %{$HoH[$row]})
      {
          $replacementval=$HoH[$row]{$entry};

          $THISROW =~ s/\\insertbyname{$entry}/$replacementval/g;

          $THISROW =~ s/\\insert$entry/$replacementval/g;

          $column = $idx{$entry};

          $THISROW =~ s/\\field{$column}/$replacementval/g;

          $THISROW = ifnextrowlast($THISROW, ($row==$csvrow-1));
      }

      if ($row==1)
      {
         print OUTPUT "$THISROW";
      }
      else
      {
         print OUTPUT "\\refstepcounter{csvrownumber}$THISROW";
         $counter{csvrownumber}++;
      }
   }

   print OUTPUT "\\end{longtable}";
   print OUTPUT "\%<< END INSERT\n";

   print "\n";
}

sub ifnextrowlast
{
   my ($STR, $nextislast) = @_;

   if (($pos = index($STR, "\\ifnextrowlast")) > -1)
   {
      $strbegin = substr($STR,0,$pos);
      $strend   = substr($STR,$pos+14);

      ($firstarg,$strend,$done) = getnextgroup($strend);

      if (!$done)
      {
         die "Can't find first argument to \\ifnextrowlast\n";
      }

      ($secondarg,$strend,$done) = getnextgroup($strend);

      if (!$done)
      {
         die "Can't find second argument to \\ifnextrowlast\n";
      }

      if ($nextislast)
      {
         $STR = $strbegin . $firstarg . $strend;
      }
      else
      {
         $STR = $strbegin . $secondarg . $strend;
      }
   }

   return $STR;
}

sub eatcomments
{
   my ($STR) = @_;

   if (substr($STR,0,1) eq "%")
   {
      return "";
   }

   my $pos=1;

   if (($pos = index($STR, "%", 1)) > -1)
   {
      if (not (substr($STR, $pos-1,1) eq "\\"))
      {
         return substr($STR,0,$pos);
      }

      while ((substr($STR, $pos-1,1) eq "\\") and ($pos > 0))
      {
         # count how many backlashes come before it.

         $i = $pos-1;

         $numbs = 1;

         while ((substr($STR, $i-1,1) eq "\\") and ($i > 0))
         {
            $numbs++;
            $i--;
         }

         # is $numbs is odd, we have a \%, otherwise we have \\%

         if ($numbs%2 == 0)
         {
            # it's a comment

            return substr($STR,0,$pos);
         }
         else
         {
            # it's not a comment, carry on looking

            $pos = index($STR, "%", $pos+1);
         }
      }
   }

   return $STR;
}

sub eatinitialspaces
{
   my ($STR) = @_;

   while (substr($STR,0,1) eq "\s")
   {
      $STR = substr($STR,1);
   }

   return $STR;
}

sub getnextgroup
{
   my($curline) = @_;

   $curline = eatcomments($curline);

   $curline = eatinitialspaces($curline);

   # check to see if current string is blank

   if ($curline!~/[^\s]+/m)
   {
      return ("","",0);
   }

   if (($group = substr($curline,0,1)) ne "{")
   {
       # next group hasn't been delimited with braces
       # return first non-whitespace character

       $curline = substr($curline,1);

       # unless it's a backslash, in which case get command name

       if ($group eq "\\")
       {
          if ($curline=~/([a-zA-Z]+)(^[a-zA-Z].*)/m)
          {
             $group = $1;

             $curline = $2;
          }
          else
          {
             # command is made up of backslash followed by symbol

             $curline=~/([\W_0-9\s\\])(.*)/m;

             $group = $1;

             $curline = $2;
          }
       }

       return ($group,$curline,1);
   }

   my $pos=index($curline, "{");
   my $startpos=$pos;
   my $posopen=0;
   my $posclose=0;

   my $bracelevel = 1;

   my $done=0;

   while (!$done)
   {
      $pos++;

      $posopen = index($curline, "{", $pos);

      # check to make sure it's not a \{

      while ((substr($curline, $posopen-1,1) eq "\\") and ($posopen > 0))
      {
         # count how many backlashes come before it.

         $i = $posopen-1;

         $numbs = 1;

         while ((substr($curline, $i-1,1) eq "\\") and ($i > 0))
         {
            $numbs++;
            $i--;
         }

         # is $numbs is odd, we have a \{, otherwise we have \\{

         if ($numbs%2 == 0)
         {
            last;
         }
         else
         {
            $posopen = index($curline, "{", $posopen+1);
         }
      }

      $posclose= index($curline, "}", $pos);

      # check to make sure it's not a \}

      while ((substr($curline, $posclose-1,1) eq "\\") and ($posclose > 0))
      {
         # count how many backlashes come before it.

         $i = $posclose-1;

         $numbs = 1;

         while ((substr($curline, $i-1,1) eq "\\") and ($i > 0))
         {
            $numbs++;
            $i--;
         }

         # is $numbs is odd, we have a \}, otherwise we have \\}

         if ($numbs%2 == 0)
         {
            last;
         }
         else
         {
            $posclose = index($curline, "}", $posclose+1);
         }
      }

      if (($posopen==-1) and ($posclose==-1))
      {
         $done=1;
      }
      elsif ($posopen==-1)
      {
         $pos=$posclose;

         $bracelevel--;

         if ($bracelevel==0)
         {
            $group = substr($curline, $startpos+1, $pos-$startpos-1);

            $curline = substr($curline, $pos+1);

            return ($group,$curline,1);
         }
      }
      elsif ($posclose==-1)
      {
         $pos=$posopen;

         $bracelevel++;
      }
      elsif ($posopen<$posclose)
      {
         $pos=$posopen;

         $bracelevel++;
      }
      elsif ($posclose<$posopen)
      {
         $pos=$posclose;

         $bracelevel--;

         if ($bracelevel==0)
         {
            $group = substr($curline, $startpos+1, $pos-$startpos-1);

            $curline = substr($curline, $pos+1);

            return ($group,$curline,1);
         }
      }
   }

   # closing brace must be on another line

   return ("", $curline, 0);
}

1;
%</perl>
%    \end{macrocode}
%\Finale
\endinput
