%\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} % %\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} % % \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} % % \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 if ($#ARGV != 1) { die "Syntax : $0 \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 () { if (/\\c@([a-zA-Z]+)=\\count/) { $counter{$1} = 0; } } $counter{csvrownumber} = 0; close LOGFILE; open OUTPUT, ">$OUTPUT" or die; %data = (); while () { $restofline = $_; while ($restofline=~/\\stepcounter(.*)/) { $line = $_; ($ctr,$restofline,$done) = getnextgroup($1); $startline=$.; while (!$done) { if ($nextline = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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 = ) { $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= ) { $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= ) { $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= ) { $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= ) { $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 () { 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 () { 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 () { 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 () { 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; % % \end{macrocode} %\Finale \endinput