% \iffalse meta-comment % % Copyright (C) 2019 by Laurence R Taylor % ----------------------------------- % % This file may be distributed and/or modified under the % conditions of the LaTeX Project Public License, either version 1.3 % of this license or (at your option) any later version. % The latest version of this license is in: % % http://www.latex-project.org/lppl.txt % % and version 1.3 or later is part of all distributions of LaTeX % version 2005/12/01 or later. % % \fi % % \iffalse % %<*driver> \documentclass{ltxdoc} \usepackage{csvmerge} \usepackage{hyperref} \hypersetup{ colorlinks=true, linkcolor=blue, filecolor=magenta, urlcolor=cyan, } \DisableCrossrefs \CodelineNumbered \RecordChanges \CheckSum{525} \begin{document} \DocInput{csvmerge.dtx} \end{document} % % \fi % % % \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 \~} % % % % \changes{v1.0}{?2019?/?07?/?17?}{Initial version} % \title{csvmerge} % \author{Laurence R Taylor} % \date{August 8, 2019} % \maketitle % % \begin{abstract} % The package |csvmerge.sty| is designed to take a bit of \TeX\ code such as a letter or a line % in a table and produce % multiple copies of it with parts of each copy filled in from a data file. % It is invoked in the usual manner from a master-file, that is a standard % \LaTeX\ file with % % \null\noindent\hskip 1in|\documentclass{whatever}|\\ % \null\hskip 1in|\usepackage{csvmerge}|\\ % \null\hskip 1.5in$\vdots$ % % The data source is a standard csv file. % The bit of \TeX\ code resides in a blueprint file. % The |\usepackage{csvmerge}| can go anywhere in the preamble. % \end{abstract} % \tableofcontents % \def\Hyperlink#1{\hyperlink{#1Description}{\tt \mm #1}} % \def\Hypertarget#1#2{\hypertarget{#1Description}{#2}} % \xdef\mm{$\backslash\hskip-5pt\backslash$} % \section{Introduction} % % The |csvmerge.sty| merges \TeX\ code with a data file. % \begin{itemize} % \item % The \TeX\ code comes from a blueprint file, which looks like % a standard \TeX\ file except it contains no code which should % go into the preamble of a normal \TeX\ file. % The blueprint file should also not contain a |\begin{document}| % nor an |\end{document}|. This stuff goes in the master-file. % \item The data file is a standard csv file. % \end{itemize} % One difference between |csvmerge.sty| and other packages which % merge |csv| data into a \TeX\ file is that the user does not need to do % much with the |csv|-file. As long as the first row contains reasonable % entries describing the contents of its column you are good to go. % Extra columns are no problem; subsequent rows can be too short % or too long; characters which are special to \TeX\ such as |$| and |&| % can appear in the data; \dots\ % \noindent More details concerning the |csv|-file can be found in subsection 2.1. % % The |tex|-file consists of standard \TeX\ code. Each row of the |csv|-file is processed % and you have access to the values of all the entries on that row as well as the row % number. You also can tell if the entry in a particular column is empty. % \noindent More details concerning the |tex|-file can be found in subsection 2.2. % % The main macro, |\mergeFields|, supports two modes. % In one mode, \emph{stream mode}, the \TeX\ code from the |tex|-file % is added to the input stream at the location from which |\mergeFields| is invoked. % In the second mode, \emph{storage mode}, the code is written to a % temporary file. You can then use the standard \LaTeX\ |\input| to read the % temporary file back into the master-file. % % Good models for the two modes are the following. Most mail-merges can be % done in streaming mode. Because \TeX\ is doing rather complicated things behind % the scenes when it is setting a table or a matrix, you are usually better off using % storage mode with your blueprint file containing the code for one line of your table/matrix. % After the data is generated you can read it back into your table. % See \hyperlink{subsubsection.B.3.1}{here} for a typical example. % % \newpage % \section{Usage} % % \DescribeMacro{\mergeFields} % The main macro is |\mergeFields| which takes one argument. % The format is %\begin{verbatim} % \mergeFields{ % tex={} % csv = {} % tmp = {} % } %\end{verbatim} % The |tmp| entry is not required. If there is none, |mergeFields| operates in % \emph{streaming} mode, otherwise it acts in \emph{storage} mode. % The || entry should be a path to the corresponding sort of file. % If the file name has spaces you will need to quote it: life is easier if there are no % spaces in file names. % The format |prefix = {}| is quite forgiving: all leading and trailing spaces % are stripped out so |prefix = { }| works. % The three lines can be in any order and do not even need to be on separate lines. % The extensions of the three files are irrelevant: the |tex|-file contains \TeX\ code % but it can have an extension of |.txt| (or anything else or none at all) if you prefer. % All that matters is that % when \TeX\ reads the file the contents have the proper content. % If you have a simple project you may be able to complete it just by looking at % the examples in \hyperlink{appendix.B}{appendix B}. % % \subsection{The data file} % The data file is a |csv| spreadsheet file. % By default the field delimiter is the comma and the % text delimiter is the double-quotes character. % This is default behavior for most spreadsheets when % asked to produce a |cvs| file. % If there are no {\tt "}'s in your |csv|-file there should be no problems. % In some cases you may need a different set of delimiters % and this is supported. % See \Hyperlink{setDelimitersTabQuote}\ below for more details. % % The entries in the first row should consist solely of \emph{alphanumeric characters, % punctuation and spaces}. % Each entry is a \emph{label} for that column. % Labels need not be unique although they usually are except sometimes there are % several columns with blank labels. % % A \emph{field-name} is extracted from a label by deleting all spaces and making all % characters lowercase: e.g. a column labeled |First Name| has a field-name % |firstname|. Commas are also deleted so a column label of |Last, First Name| % becomes a field-name of |lastfirstname|. % Distinct labels can have the same field-name although again % this seems unlikely except for blank labels. % % Associated to each field-name is a \emph{field-name-macro}. % This macro is private: its name is found by prepending |@LRT@@| to the field-name. % When |\mergeFields| is invoked, the data file is read one row at a time. % All rows after the first are data. % When a row is read, each entry is peeled off and placed in the field-name-macro % whose field-name is the one associated to the label for that column % with |@LRT@| prepended. % For example, if row one of some column contains |First Name|, the field-name is |firstname| and % the field-name-macro is |\@LRT@@firstname|. When a row after the first is read, % |\@LRT@@firstname| will expand to the entry in the |First Name| column on that row. % % When |\mergeFields| has read a row and set the field-name-macros the |tex|-file is % read and the \TeX\ code evaluated. % In streaming mode the result is added to the input stream at % the current location whereas in storage mode the result is written to the |tmp|-file. % % \DescribeMacro{End of csv-file}\Hypertarget{End of csv-file} % This process continues until the last line in the |csv|-file is processed OR until an entirely % blank line is encountered. % \vskip10pt % The request for only alphanumeric, punctuation and blanks for entries in row one is % not actually enforced but the macro |caselower| from |stringstrings| is used to % produce lowercase letters and may do weird things to characters not on the requested list. % Without studying the |stringstrings| manual carefully % % you may not be able to identify the field-names if you insist on weird characters. % % It is quite possible that several columns can have the same field-name, and hence % the same field-name-macro. In that case the macro will expand to the contents of the % column with that field-name which is furthest to the right. % % Because of the prepended |@LRT@@| you can not use these field-name-macros directly % in your |tex|-file. % The solution is to use \Hyperlink{Field}{}|{field-name}| which will expand correctly. % See the examples in appendix B. % \subsubsection{Entry issues} % Except for issues discussed here, you should be able to use any % standard |csv|-file. % % The data file is read with the usual \TeX\ issues turned off: % |#|, |$|, |&|, |@|, |%| % , |^|, |~|, and \verb!|! % can all be in your data file. % \begin{itemize} % \item % In streaming mode, only |#|, |$|, |&|, |@|, |%|, |^|, |~| print correctly. % The characters \$, \#, \%, @ and \& often occur in data and they print correctly. % \TeX\ code will not be rendered correctly so for example, |\$| in the data file produces % {$``$\tt\$} when \TeX ed. % As long as you are not too creative in your entries you should get the results you want. % \item % In storage mode the characters above end up printing correctly in the |tmp|-file % BUT when you |\input| the |tmp|-file back into your master-file these characters % will resume their usual \TeX\ meaning and wreak havoc with your code. % If you absolutely must have them, writing |\&|, |\$|, |\#|, % etc. in the |csv|-file may save you. % \end{itemize} % % \DescribeMacro{Delimiters}\null\hskip-\parindent % There is one more issue. For most csv files, the field separator is the comma. % When a field has a comma in it, the entire field is delimited by double quotes ({\tt"}). % When the field has a double quote it is converted to a pair of double quotes ({\tt""}) and so on. % If you have both commas and quotes in a field the best case scenario is that % you will get |""|'s where you were hoping for |"|'s. This probably is not what you want. % Even worse, a |",| in a data entry will end the entry prematurely at this point % and things deteriorate from there. % % There is a mechanism to deal with this. Most spreadsheets can save csv files with other % field (and text) delimiters. The second most common version of csv has a tab as the % field delimiter and quotes for fields which have a tab in a field. % % These two cases are supported out of the box. By default you get the comma-quotes version. % If you add |\setDelimitersTabQuote| before calling |\mergeFields| you will get % the tab-quotes version. % If you have a second call to |\mergeFields| and the data file is comma-quotes, % adding |\setDelimitersCommaQuote| before |\mergeFields| will restore comma-quotes. % See \hyperlink{setDelimitersCommaQuoteDescription}{setDelimters\dots} % for a more thorough discussion. % % % \subsection{The blueprint file} % % The blueprint file is a normal \TeX\ file with macros defined as above from the |csv|-file. % It should not contain a \verb!\begin{document}! or an \verb!\end{document}! % nor anything that needs to be expanded in the preamble. % Such things belong in the master file. % % For mail-merges, the |tex|-file should end with a \verb!\newpage! % to guarantee that each copy of the message appears on a % different page. % If you need custom sizes or headers or you have page numbers that you want to % reset for each copy, you should do this in the |tex|-file. % % To use the data from the |csv|-file you need to write |\Field{some-field-name}| wherever % you want the data in your |tex|-file. % Some field-names may be rather long so if you are going to use them a lot in your % |tex|-file, feel free to write something like |\def\sfn{\Field{some-field-name}}| at the % top of your |tex|-file and then use |\sfn| thereafter. % % One additional issue occurs in storage mode. You may very well want \TeX\ code in your % |tex|-file. When |mergeFields| reads a line of data, you want the field-name-macros to % expand to their current values but you often do not want your \TeX\ code to expand. % It is good practice stick a |\noexpand| in front of each \TeX\ macro. % Each line is expanded once when the line is written to the |tmp| file, so the |\noexpand| % disappears and you are left with just your \TeX\ macros. % % If you do define macros like |\sfn| as above, you will need % to write\\ |\noexpand\def\noexpand\sfn{\Field{some-field-name}}|. % \StopEventually{\PrintIndex} % % \section{List of public macros} % There are the 6 public macros: links are to descriptions of the |@LRT@|-versions which % actually do the work. % % \null\hskip-10pt \begin{tabular}{lll} % \Hyperlink{mergeFields}% % &\Hyperlink{Field}% % &\hbox to 0pt{\Hyperlink{ifFieldEmpty}\hss}% % \\ % \Hyperlink{setDelimitersCommaQuote}&\Hyperlink{setDelimitersTabQuote}% % &\Hyperlink{makeMePublic}\\ % \end{tabular} % \begin{itemize} % \item |\mergeFields{tex={tex-file}csv={csv-file}}| or % \\|\mergeFields{tex={tex-file}csv={csv-file}{tmp={tmp-file}}|\\ % sets up to do the merge. % \hyperlink{section.2}{Section 2} is devoted to a detailed discussion. % \item |\Field{field-name}| expands to the current value of the field-name-macro. % \item |\ifFieldEmpty{field-name to test}{do if empty}{do if not}| % \item The two |\setDelimiters| set the delimiters to match those used in the |csv| file: % comma-quotes is the default. % \item |\makeMePublic| must be immediately followed by |\@LRT@| or you will get a % |Use of \makeMePublic doesn't match its definition.| error. Then you can write % |\makeMePublic \@LRT@foo\becomes\Foo| to make the public macro |\Foo| that % you just define equal to the private macro |\@LRT@foo|. You may not write % |\makeMePublic{\@LRT@foo}\becomes\Foo| or other variants: the first non-white-space % character \TeX\ sees after |\makeMePublic| needs to be the start of |\@LRT@|. % % As a useful example, % |\makeMePublic\@LRT@Row\becomes\Row| means you can get your hands on % the row number using |\Row|. % \end{itemize} % % \section{Implementation} % % \begin{macro}{Header} % First we have the standard package header stuff. % The line numbers in the typeset \jobname.dtx file match the line numbers % in the \jobname.sty file % generated by \TeX ing the \jobname.ins file. % \setcounter{CodelineNo}{15} % \begin{macrocode} \NeedsTeXFormat{LaTeX2e}[2005/12/01] \ProvidesPackage{csvmerge} [2019/07/17 v1.0 merges TeX code with csv data] % \end{macrocode} % \end{macro} % \begin{macro}{Required Packages} % These required packages are part of the standard \href{https://www.tug.org/texlive/}{{\tt TeX Live}} %and \href{https://miktex.org}{{\tt MiKTEX}} distributions. % \begin{macrocode} \RequirePackage{stringstrings} \RequirePackage{trimspaces} \RequirePackage{etoolbox} % \end{macrocode} % \end{macro} % \begin{macro}{Options} % |csvmerge| supports an option which is rarely needed. All the private macros % (except \hyperlink{exceptionDescription}{one}) in % this package have |@LRT@| or |@lrt@| somewhere in their name so are very % unlikely to produce name conflicts. In the very unlikely chance that one or more of % the six public macros has a name conflict, % this can be alleviated by putting |\usepackage[foo]{csvmerge}| % in your master file. Here |foo| can be any string of alphabetical characters. % Then the names of all six public macros are invoked with a leading |foo|: % |\mergeFields| no longer exists but becomes |\foomergeFields|. The evident change happens % with the other five macros. % % The next two lines of code merely save the option for use % \hyperlink{optionsDescription}{later} at the end. % \begin{macrocode} \DeclareOption*{\xdef\@LRT@CO{\CurrentOption}} \ProcessOptions\relax % \end{macrocode} % \end{macro} % \begin{macrocode} \makeatletter % \end{macrocode} % \begin{macro}{\mergeFields} % \Hypertarget{mergeFields} % |\@LRT@mergeFields| actually initiates the work. % It initializes some macros for later use. % Then it collects the |prefix={}| arguments using |\@LRT@getFilePaths| % and checks to be sure it has a |csv|-file % and a |tex|-file. If not it bails. % Then it checks if it has a |tmp| file and sets the file reading subroutine, % |\@LRT@Input| to |\@LRT@streaming| is there is no |tmp| file and to % |\@LRT@storage| if there is. If there is a |tmp| file, it is opened. % Then |\@LRT@mergeData| is called which actually does the merging. % Finally, if there is a |tmp| file, it is closed. % \begin{macrocode} \newcommand{\@LRT@mergeFields}[1]{% \gdef\@LRT@csv{}% \gdef\@LRT@tex{}% \gdef\@LRT@tmp{}% \setcounter{\@LRT@macroPrefix Row}{2}\relax% \gdef\@LRT@csvBlankLine{}% \@LRT@getFilePaths#1=@% \if@LRT@empty{\@LRT@csv}{\@LRT@missingArgumentMessage{csv}}{}% \if@LRT@empty{\@LRT@tex}{\@LRT@missingArgumentMessage{tex}}{}% \if@LRT@empty{\@LRT@tmp}% {\global\let\@LRT@Input\@LRT@streaming}% {\global\let\@LRT@Input\@LRT@storage}% \if@LRT@empty{\@LRT@tmp}{}{% \immediate\openout\@lrt@tmpFile=\@LRT@tmp\relax}% \@LRT@mergeData% \if@LRT@empty{\@LRT@tmp}{}{\immediate\closeout\@lrt@tmpFile\relax}% } % \end{macrocode} % \end{macro} % \Hypertarget{macroPrefix}{} % \begin{macro}{\@LRT@macroPrefix} % \begin{macrocode} \gdef\@LRT@macroPrefix{@LRT@@} % \end{macrocode} % \end{macro} % \Hypertarget{Field}{} % \begin{macro}{\Field} % |\@LRT@Field| is just a wrapper for a |\csname ... \endcsname|. % Notice it adds the |\@LRT@macroPrefix| prefix to the argument so % the input is always the field-name and the output is always the value of % the corresponding field-name-macro. % Because of the third |@| there is no danger that these macros defined from % entries in the |csv|-file will conflict with other macros defined in this package % since none of them have a |@@| in their names. % \begin{macrocode} \def\@LRT@Field#1{% \csname\@LRT@macroPrefix#1\endcsname% } % \end{macrocode} % \end{macro} % % \begin{macro}{\ifFieldEmpty} % \Hypertarget{ifFieldEmpty}{} % |\ifFieldEmpty| takes three arguments. % The first argument is a field-name and if the content of the corresponding % macro is empty then the second argument is executed: otherwise the % third argument is executed. % % \begin{macrocode} \long\gdef\@LRT@ifFieldEmpty#1#2#3{\ifbool{\@LRT@macroPrefix#1}{#2}{#3}} % \end{macrocode} % \end{macro} % % \begin{macro}{\setDelimitersCommaQuote} %\end{macro} % \begin{macro}{\setDelimitersTabQuote} % \Hypertarget{setDelimitersCommaQuote}{} % \Hypertarget{setDelimitersTabQuote}{} % These two |setDelimiters| are similar. % Recall how delimiters work. A row in a |csv|-file with comma delimiters % looks like |foo1,foo2,foo3,...| so the delimiter is the comma and if % |\@LRT@getFieldDelimited| is put in front of this string it will peal off % |foo1|. Since |^^I| is \TeX\ for the tab, % a row in a csv file with tab delimiters % looks like |foo1^^Ifoo2^^Ifoo3^^I...| and so the first two lines of % |\@LRT@setDelimitersCommaQuote| and |\@LRT@setDelimitersTabQuote| % look the same after switching |,| and |^^I|. % % There is a wrinkle in the tab case. The tab normally looks to \TeX\ like white space and % so by the time the line reaches |\@LRT@getFieldDelimited| the line looks like % |foo1foo2foo3...| and there is no way to break off |foo1|. The |\catcode`\^^I12\relax| % insures |\@LRT@getFieldDelimited| sees the line as |foo1^^Ifoo2^^Ifoo3^^I...|.. % This means that the catcode of |^^I| must also be 12 when each line is read and the line % and the macro |\gdef\@LRT@catcodes{|\\|\catcode`\^^I12\relax}| insures this. % In the comma delimited version no catcode heroics are needed so |\@LRT@catcodes| % is empty. % % If some field contains one or more |\@LRT@fieldDelimiterBack|'s then the |csv|-file will use % the text-delimiter at the front and back. When quote is the text delimiter and |foo2| contains % a comma but |foo1| and |foo3| do not, the comma delimited string looks like % |foo1,"foo2",foo3,...| and the tab delimited string looks like % |foo1^^I"foo2"^^Ifoo3^^I...|. % % The text delimiters have a front one, the double-quotes in both versions, and the back one which is % the front text delimiter followed by the back field delimiter. % % The macro |\@LRT@blankFieldItem| is used to construct what a blank line looks like. % In the comma delimited case, you need a string of commas of the same length as % the number of entries on line one of the |csv|-file. In the tab delimited case, the blank line % turns out to be a blank macro. % % The |\@LRT@catcodes| macro is a collection of catcodes that need to be changed: % nothing for the comma delimited case and |^^I| for the tab delimited case. % % See \hyperlink{newDelimiterAppendix}{Appendix A} for more discussion on writing your own delimiters. % \begin{macrocode} {{% \gdef\@LRT@setDelimitersCommaQuote{ \gdef\@LRT@fieldDelimiterBack{,} \gdef\@LRT@getFieldDelimited##1,{\@LRT@getFirstArg{##1}} \gdef\@LRT@textDelimiterFront{"} \gdef\@LRT@getTextDelimited"##1",{\@LRT@getFirstArg{##1}} \gdef\@LRT@blankFieldItem{,} \gdef\@LRT@catcodes{} }% }} {{\catcode`\^^I12\relax% \gdef\@LRT@setDelimitersTabQuote{ \gdef\@LRT@fieldDelimiterBack{^^I} \gdef\@LRT@getFieldDelimited##1^^I{\@LRT@getFirstArg{##1}} \gdef\@LRT@textDelimiterFront{"} \gdef\@LRT@getTextDelimited"##1"^^I{\@LRT@getFirstArg{##1}} \gdef\@LRT@blankFieldItem{} \gdef\@LRT@catcodes{\catcode`\^^I12\relax} }% }} % \end{macrocode} % \end{macro} % % \begin{macro}{\@LRT@makeMePublic} % \hypertarget{exceptionDescription}{} % \hypertarget{makeMePublicDescription}{} % The next line of code defines the only macro which does not have |@LRT@| % in its name. It seems sufficiently weird that there should be no name conflicts. % Notice that |\makeatletter| is still in effect so |@| has catcode 11. % % Then we switch to |\makeatother| so |@| has catcode 12. % The next line expands to |\@LRT@makeMePublic| with the initial % |\@LRT@| having its |@|'s of catcode 11 so it is a private macro. % In the |\@LRT@| that you see, the |@|'s have catcode 12 so it is really % the macro |\@| with extra stuff. Since it should never be expanded % it doesn't really matter. Then we drop back into |\makeatletter| to define % more private macros. % % The net result is that you can write \\ % \null\hskip1in |\makeMePublic \@LRT@foo\becomes\newMacro| \\in your master-file and % the private macro |\@LRT@foo| becomes the same as |\newMacro| and you can % use |\newMacro| in your |tex|-file. % \begin{macrocode} \expandafter\gdef\csname1238LRTLRTsvbneLRT\endcsname{@LRT@} \makeatother \expandafter\gdef\csname\csname1238LRTLRTsvbneLRT\endcsname makeMePublic\endcsname\@LRT@#1\becomes#2{\global\letcs{#2}{@LRT@#1}}% \makeatletter % \end{macrocode} % \end{macro} % \begin{macro}{private macros} % \end{macro} % \begin{macro}{\@LRT@Row} % \Hypertarget{Row}{} % The macro |\@LRT@Row| expands to the number of the row of the |csv|-file currently % being read. To get the |\xdef| to expand correctly we needed to define (and did) % |\@LRT@macroPrefix| first. Recall that the first line of actual data is row 2. % \begin{macrocode} \xdef\@LRT@Row{\noexpand\number\noexpand\value{\@LRT@macroPrefix Row}} % \end{macrocode} % \end{macro} % \begin{macro}{\if@LRT@empty}\Hypertarget{if@LRT@empty}{} % Takes three arguments: if |#1| expands to empty do the code in |#2|; % otherwise do the code in |#3|. % \begin{macrocode} \newcommand{\if@LRT@empty}[3]{\ifthenelse{\equal{#1}{}}{#2}{#3}} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@fieldName} % Takes an alphanumeric string, uses |\caselower| from the |stringstrings| package % to convert the string to all lower case; then it strips the spaces from the answer % using |\convertchar|, also from the |stringstrings| package. Next it strips commas from % the answer. % The final |\@LRT@TheString| makes the answer global. % \begin{macrocode} \gdef\@LRT@fieldName#1{% \caselower[q]{#1}% \convertchar[q]{\thestring}{ }{}% \convertchar[q]{\thestring}{,}{}% \xdef\@LRT@TheString{\thestring}% } % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@getFilePaths} % |\@LRT@getFilePaths#1=#2@| is a recursive macro which works through the % |prefix={}| argument list. % The first argument, |#1|, picks up one |prefix={}| and |#2| contains the rest of the arguments. % If |#1| is not empty it is passed to |\@LRT@extractKeyValue| which collects the % prefix and file path. % \begin{macrocode} \gdef\@LRT@getFilePaths#1=#2@{% \xdef\@LRT@hashKey{\trim@spaces{#1}}% \if@LRT@empty{\@LRT@hashKey}{}{ \def\@LRT@test{tex}\ifthenelse{\equal{\@LRT@hashKey}{\@LRT@test}}{}% {\def\@LRT@test{csv}\ifthenelse{\equal{\@LRT@hashKey}{\@LRT@test}}{}% {\def\@LRT@test{tmp}\ifthenelse{\equal{\@LRT@hashKey}{\@LRT@test}}{}% {\@LRT@unknownArgumentMessage{#1}}}}% \@LRT@extractKeyValue#2@% \expandafter\@LRT@getFilePaths\@LRT@doWeStillHaveStuffToDo @}} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@extractKeyValue} % |\@LRT@hashKey| contains the prefix and |\@LRT@hashValue| contains the file path. % If the prefix is not one of |tex|, |csv| or |tmp| you will be warned. % \begin{macrocode} \def\@LRT@extractKeyValue#1#2@{% \edef\@LRT@hashValue{\trim@spaces{#1}}% \xdef\@LRT@doWeStillHaveStuffToDo{\trim@spaces{#2}}% \expandafter\xdef\csname @LRT@\@LRT@hashKey\endcsname{\@LRT@hashValue}% } % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@SecondifFieldEmpty} % |\@LRT@SecondifFieldEmpty| checks for an empty macro directly. % The previously defined \Hyperlink{ifFieldEmpty} uses a different method so both are needed at % various points in the code. % \begin{macrocode} \newcommand{\@LRT@SecondifFieldEmpty}[3]{% \edef\@LRT@fieldValue{#1}% \ifx\@LRT@fieldValue\@empty\relax{#2}\else{#3}\fi\relax} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@getCommaFieldDelimited} % The macros |\@LRT@getCommaFieldDelimited|and % |\@LRT@getFieldDelimited| are the same when the field delimiter is the comma. % The list of field-names is always comma delimited and so % we need a |\@LRT@getCommaFieldDelimited| to iterate through it. % \begin{macrocode} \gdef\@LRT@getCommaFieldDelimited#1,{\@LRT@getFirstArg{#1}} % \end{macrocode} % \end{macro} % \begin{macro}{New counter} The counter containing the row number. % \begin{macrocode} \newcounter{\@LRT@macroPrefix Row} % \end{macrocode} % \end{macro} % \begin{macro}{New read/write's} % \begin{macrocode} \newread\@lrt@csvFile% \newread\@lrt@texFile% \newwrite\@lrt@tmpFile% % \end{macrocode} % \end{macro} % \begin{macro}{Messages} % \begin{macrocode} \gdef\@LRT@justProcessedMessage{% \immediate\typeout{}% \immediate\typeout{Just processed row % \@LRT@Row\space of the data file.}% \immediate\typeout{}% \stepcounter{\@LRT@macroPrefix Row}\relax% } \gdef\@LRT@failedToOpenMessage#1{% \immediate\typeout{}% \immediate\typeout{File #1 failed to open.}% \immediate\typeout{}\fi% \end{document}% } \gdef\@LRT@NoMoreDataMessage{% \immediate\typeout{No more data.}% \immediate\typeout{}% } \gdef\@LRT@missingArgumentMessage#1{ \immediate\typeout{}% \immediate\typeout{I was never given a path to the #1 file.}% \immediate\typeout{}% \end{document}% } \gdef\@LRT@unknownArgumentMessage#1{ \immediate\typeout{}% \immediate\typeout{I do not recognize an argument of type #1.}% \immediate\typeout{}% } % \end{macrocode} % \end{macro} % \begin{macro}{Inputs} % When |mergeFields| starts up, it knows immediately whether it is in streaming or in storage % mode. It sets |\@LRT@Input| to |\@LRT@streaming| or |\@LRT@storage|. % Each time a new line of data in the |csv|-file is read the % the macros are updated to reflect that data on the current line. % |\@LRT@streaming| uses the \LaTeX\ command |\input| to add the |tex|-file to the % stream. % |\@LRT@storage| works through the data in the |tex|-file. % First it opens the |tex| file; then it % reads the |tex|-file one line at a time; expands that line; and % writes the answer to a the |tmp|-file. When the |eof| for the |tex|-file is reached, % the |tex|-file is closed. % % |\@LRT@catcodeMagic| is used in two places to set the catcodes of certain characters % to values that won't cause trouble. % % |\@LRT@texNotAtEOF{#1}| is used to tighten up the code looping over the lines in % the |tex|-file in |\@LRT@storage|. % \begin{macrocode} \gdef\@LRT@streaming{\input{\@LRT@tex}} \gdef\@LRT@catcodeMagic{% \catcode`\^^M9\relax% \catcode`\#12\relax \catcode`\$12\relax \catcode`\&12\relax \catcode`\^12\relax \catcode`\_12\relax \catcode`\%12\relax } \gdef\@LRT@texNotAtEOF#1{% \ifeof\@lrt@texFile{\global\booltrue{@LRT@texEOF}}\else{#1}\fi% } \gdef\@LRT@storage{{% \@LRT@catcodeMagic% \openin\@lrt@texFile=\@LRT@tex\relax% \@LRT@texNotAtEOF{\global\boolfalse{@LRT@texEOF}}% \unlessboolexpr{bool{@LRT@texEOF}}{% \read\@lrt@texFile to \@LRT@preItem% \@LRT@texNotAtEOF{% \immediate\write\@lrt@tmpFile{\@LRT@preItem}\relax}% }{}% \immediate\closein\@lrt@texFile% }} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@dataLineIterator} % This macro looks to see if the next character is |\@LRT@textDelimiterFront|. % If it is the entry is collected using |\@LRT@getTextDelimited| and if not we collect it % using |\@LRT@getFieldDelimited|. % \begin{macrocode} \gdef\@LRT@dataLineIterator{% \expandafter\@ifnextchar\@LRT@textDelimiterFront% {\@LRT@getTextDelimited}{\@LRT@getFieldDelimited}} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@getFirstArg} % This is the |\@LRT@getFirstArg| from the |\@LRT@getFieldDelimited| and \\ % |\@LRT@getTextDelimited| macros. It collects the first field and then % uses |\@LRT@getSecondArg| to collect the rest of the line from the |csv|-file. % \begin{macrocode} \gdef\@LRT@getFirstArg#1{\gdef\@LRT@firstArg{#1}\@LRT@getSecondArg} \gdef\@LRT@getSecondArg#1@{\gdef\@LRT@restArg{#1}} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@getLOFirstArg} % These next two macros do the same thing with the first line of the |csv|-file. % The difference is that these two macros put their answers in a different pair of % macros than the previous two so we can iterate along both the first line (field-names) % and the current line of the |csv|-file. % \begin{macrocode} \gdef\@LRT@getLOFirstArg#1{\gdef\@LRT@ffirstArg{#1}\@LRT@getLOSecondArg} \gdef\@LRT@getLOSecondArg#1@{\gdef\@LRT@frestArg{#1}} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@getLineOne} % This is the version of |\@LRT@getFirstArg| which is used on the first line of the |csv|-file. % \begin{macrocode} \gdef\@LRT@fieldNameIterator#1,{\@LRT@getLOFirstArg{#1}} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@setFirstLineAndBlankLine} % This macro collects the first line of the |csv|-file and packages it for later use: % |\@lrt@firstline| is the first line when we are done. % We also construct |\@LRT@csvBlankLine| which is used to check if % a line of the |csv|-file is blank. % \begin{macrocode} \providebool{@LRT@iterator} \gdef\@LRT@setFirstLineAndBlankLine#1{% \gdef\@lrt@firstline{}% \gdef\@LRT@csvBlankLine{} \edef\@LRT@restArg{#1\@LRT@fieldDelimiterBack} \booltrue{@LRT@iterator}% \whileboolexpr{bool{@LRT@iterator}}{% \expandafter\@LRT@dataLineIterator\@LRT@restArg @% \@LRT@fieldName{\@LRT@firstArg}% \xdef\@lrt@firstline{\@lrt@firstline\@LRT@TheString,}% \if@LRT@empty{\@LRT@restArg}% {\boolfalse{@LRT@iterator}}{\booltrue{@LRT@iterator}}% \ifboolexpr{bool{@LRT@iterator}}% {\xdef\@LRT@csvBlankLine{\@LRT@csvBlankLine\@LRT@blankFieldItem}}{}% }{}% } % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@getAndProcessNextLine} % This is the recursive macro which processes each line of the |csv|-file % until there are no more. % The code in the first |{{| \dots |}}| pair % reads a line from the |csv|-file: the last line here converts % the answer from local to global. % The code in the second |{{| \dots |}}| pair checks % if the line is \hyperlink{End of csv-fileDescription}{blank} or not. % |\@LRT@exitGetAndProcessNextLine| exits the recursion and % |\@LRT@repeatGetAndProcessNextLine| reloops. % \begin{macrocode} \gdef\@LRT@getAndProcessNextLine{% \unlessboolexpr{bool{@LRT@csvEOF}}{% {{% \@LRT@catcodeMagic% \@LRT@catcodes \read\@lrt@csvFile to\@LRT@x@nextLine% \ifeof\@lrt@csvFile\relax{\global\booltrue{@LRT@csvEOF}}\else% {\global\boolfalse{@LRT@csvEOF}}\fi% \xdef\@LRT@nextLine{\@LRT@x@nextLine}% }}% {{% \if@LRT@empty{\@LRT@nextLine}% {\global\let\@LRT@next\@LRT@exitGetAndProcessNextLine\relax}% {\ifx\@LRT@csvBlankLine\@LRT@nextLine\relax% {\global\let\@LRT@next\@LRT@exitGetAndProcessNextLine\relax}\else% {\global\let\@LRT@next\@LRT@repeatGetAndProcessNextLine\relax}\fi}% \@LRT@next% }}% }{}% } % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@exitGetAndProcessNextLine} % This just exits. % \begin{macrocode} \gdef\@LRT@exitGetAndProcessNextLine{% \closein\@lrt@csvFile\relax\global\booltrue{@LRT@csvEOF}} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@repeatGetAndProcessNextLine} % This sets up to process the line we just read. % \begin{macrocode} \gdef\@LRT@repeatGetAndProcessNextLine{% \@LRT@processNextLine{\@LRT@nextLine\@LRT@fieldDelimiterBack}} % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@setMacrosForOneEntry} % This bit of code sets the macro and the if-empty boolean for a single item: % \#1 is the field-name and \#2 is the entry in that (row,column). % \begin{macrocode} \gdef\@LRT@setMacrosForOneEntry#1#2{% \expandafter\xdef\csname\@LRT@macroPrefix#1\endcsname{#2}% \@LRT@SecondifFieldEmpty{#2}% {\global\booltrue{\@LRT@macroPrefix#1}}% {\global\boolfalse{\@LRT@macroPrefix#1}}% } % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@processNextLine} % This code actually processes the line from the |csv|-file. % The first three lines are setup. % The two |\edef|'s do the following: |\@LRT@restArg{#1}| has the entire current row; and % |\@LRT@frestArg| has a comma separated list of field-names. % Setting |\booltrue{@LRT@iterator}| means the iteration will start. % The two |\expandafter| lines do the iteration: the current values are in |\ffirstArg| for % the field-name and |\firstArg| for the entry in the current row. % The line |\@LRT@setMacrosForOneEntry| sets the contents of the field-name-macro. % Finally the % |\if@LRT@empty| line checks if we are done. % % The |\edef| between the two |\expandafter|'s adds a blank entry to the back of % |\@LRT@restArg| so if the row is too short in the |csv|-file it will still scan correctly. % % The |\@LRT@Input| outputs the result either to the stream or to the |tmp|-file % and |\@LRT@justProcessedMessage| writes to the terminal which row was just processed. % If something suddenly goes wrong you know which row you were working on % when things messed up. % \begin{macrocode} \gdef\@LRT@processNextLine#1{% \edef\@LRT@restArg{#1}% \edef\@LRT@frestArg{\@lrt@firstline}% \booltrue{@LRT@iterator}% \whileboolexpr{bool{@LRT@iterator}}{% \expandafter\@LRT@fieldNameIterator\@LRT@frestArg @% \edef\@LRT@restArgA{\@LRT@restArg\@LRT@fieldDelimiterBack} \expandafter\@LRT@dataLineIterator\@LRT@restArgA @% \@LRT@setMacrosForOneEntry{\@LRT@ffirstArg}{\@LRT@firstArg}% \if@LRT@empty{\@LRT@frestArg}% {\boolfalse{@LRT@iterator}}{\booltrue{@LRT@iterator}}% }{}% \@LRT@Input\relax% \@LRT@justProcessedMessage% } % \end{macrocode} % \end{macro} % \begin{macro}{\@LRT@mergeData} % This macro opens the |csv|-file, reads the first row. % The code between the |{{ ... }}| makes a comma separated list of field-names % in the order in which they occur in the |csv|-file. It also creates a macro which % expands to what a blank line in the |csv|-file will look like. It also defines one % boolean for each field-name. % This requires an iteration over |\@lrtfirstline|. % % Because \TeX\ |\newif|'s are not global, we need to define the ones we need as soon % as possible, which is right here. % % Then |\@LRT@mergeData| calls |\@LRT@getAndProcessNextLine| which % reads and processes the subsequent lines of the |csv| file. % % Finally it puts out a no-more-data message to let you know |\mergeFields| is finished. % \begin{macrocode} \gdef\@LRT@mergeData{% \providebool{@LRT@texEOF}% \immediate\openin\@lrt@csvFile=\@LRT@csv\relax% \providebool{@LRT@csvEOF}% \boolfalse{@LRT@csvEOF}% \ifeof\@lrt@csvFile\@LRT@failedToOpenMessage{\@LRT@csv}\fi% {{\@LRT@catcodes \read\@lrt@csvFile to\@lrt@firstlineX% \@LRT@setFirstLineAndBlankLine{\@lrt@firstlineX} }}% \booltrue{@LRT@iterator} \edef\@LRT@restArg{\@lrt@firstline}% \whileboolexpr{bool{@LRT@iterator}}{% \expandafter\@LRT@getCommaFieldDelimited\@LRT@restArg @% \providebool{\@LRT@macroPrefix\@LRT@firstArg}% \if@LRT@empty{\@LRT@restArg}% {\boolfalse{@LRT@iterator}}{\booltrue{@LRT@iterator}}% }{}% \@LRT@getAndProcessNextLine% \@LRT@NoMoreDataMessage% } % \end{macrocode} % \end{macro} % \Hypertarget{options}{} % \begin{macro}{Options code} % Here is the code which % examines the option string and maps the public macro names to the % private codes which actually do the work. % \begin{macrocode} \gdef\@LRT@weirdOption#1{\@ifundefined{@LRT@CO}% {\global\csletcs{#1}{@LRT@#1}}{{\global\csletcs{\@LRT@CO #1}{@LRT@#1}}}% } \@LRT@weirdOption{mergeFields}% \@LRT@weirdOption{Field}% \@LRT@weirdOption{ifFieldEmpty}% \@LRT@weirdOption{setDelimitersCommaQuote}% \@LRT@weirdOption{setDelimitersTabQuote}% \@LRT@weirdOption{makeMePublic}% % \end{macrocode} % \end{macro} % \begin{macro}{Set the default delimiters} % Here \Hypertarget{defaults}{is} % where the default delimiters are set. % \begin{macrocode} \@LRT@setDelimitersCommaQuote % \end{macrocode} % \end{macro} % \begin{macro}{Finish up} % \begin{macrocode} \makeatother \endinput % \end{macrocode} % \end{macro} % \Finale % \newpage % \appendix % \section{Writing new delimiter macros}\hypertarget{newDelimiterAppendix}{} % If the field delimiter is not something \TeX\ sees as white space, life is fairly easy. % In your master-file, copy and paste one of the code examples below. % Change to suit your needs. % % \noindent*********************************************************************** % % \begin{verbatim} % {{% % \makeatletter % \gdef\setMyDelimiters{% % \gdef\@LRT@fieldDelimiterBack{,} % \gdef\@LRT@getFieldDelimited##1,{\@LRT@getFirstArg{##1}} % \gdef\@LRT@textDelimiterFront{"} % \gdef\@LRT@textDelimiterBack{",} % \gdef\@LRT@getTextDelimited"##1",{\@LRT@getFirstArg{##1}} % \gdef\@LRT@blankFieldItem{,} % \gdef\@LRT@catcodes{} % }% % \makeatother % }} % \end{verbatim} % % \noindent*********************************************************************** % % Replace all occurrences of {\tt ,} by your field delimiter. Replace all occurrences of {\tt "} % with your text deliminator % and you should be ready to go. % Just add |\setMyDelimiters| in your master-file before using |\mergeFields|. % % If the field delimiter is a white space character such as a tab, then copy and paste % the next example % % \noindent*********************************************************************** % % \begin{verbatim} % \makeatletter % {{\makeatletter % \catcode`\^^I12\relax% % \gdef\@LRT@setDelimitersTabQuote{ % \gdef\@LRT@fieldDelimiterBack{^^I} % \gdef\@LRT@getFieldDelimited##1^^I{\@LRT@getFirstArg{##1}} % \gdef\@LRT@textDelimiterFront{"} % \gdef\@LRT@textDelimiterBack{"^^I} % \gdef\@LRT@getTextDelimited"##1"^^I{\@LRT@getFirstArg{##1}} % \gdef\@LRT@blankFieldItem{} % \gdef\@LRT@catcodes{\catcode`\^^I12\relax} % }% % }} % \makeatother % }} % \end{verbatim} % % \noindent*********************************************************************** % % Use Chapter 8 of the \TeX\ book to find how to write your delimiter and replace % all occurrences of |\^^I| with the code for your delimiter. % Then replace all occurrences of {\tt "} with your text deliminator % and hope for the best. % Try leaving |\@LRT@blankFieldItem| blank and hope for the best. % Again add |\setMyDelimiters| in your master-file before using |\mergeFields|. % % \section{Some examples} % \subsection{\TeX\ code in a table} % A typical line in a tabular environment looks like |item1 & item2& item3\\|. % Suppose you have 4 columns in your |csv|-file with entries |Last Name|, % |First name|, |Numerator| and |Denominator| and you want |item1| to be % |Last Name|, |item2| to be |First name| and |item3| to be |\frac{Numerator}{Denominator}|. % Here is the needed code in your |tex|-file: % % \begin{verbatim} % \Field{lastname}&\Field{firstname}&% % $\noexpand\frac{\Field{numerator}}{\Field{denominator}}$\noexpand\\ % \end{verbatim} % You might have expected to see a |\noexpand| in front of the \$'s but remember \$ is one of % those characters which goes through unscathed to the |tmp|-file. % It certainly doesn't hurt to write |\noexpand$|. When you read the |tmp|-file back in % you do want |$| to resume its meaning of making math mode and it does. % % The |\noexpand| in front of the |\frac| and the |\\| are needed. % You don't want to try to expand either one % until the |tmp|-file is read back in. % % Assuming that the file |merge.tex| contains the code above and that the file |data.csv| contains your data, % the following code typesets the table. % \begin{verbatim} % \documentclass[12pt]{article} % \RequirePackage{csvmerge} % \begin{document} % \mergeFields{ % tmp={example.tmp} % tex ={merge.tex} % csv = {data.csv} % } % \begin{tabular}{lll} % \input{example.tmp} % \end{tabular} % \end{document} % \end{verbatim} % The |csv|-file needs to contain the four field-names that appear in the |tex|-file, % but it is fine if it contains additional columns. % % \subsection{\TeX\ code in a mail-merge} % Here is a snippet of code to do a header in a mail-merge business letter. % The field-names from the |csv|-file should be obvious. The interesting point % is the usage of |\ifFieldEmpty|. % \begin{verbatim} % \Field{firstname} \ifFieldEmpty{middleinitial}{} % {\Field{middleinitial}.~}\Field{lastname}\par % \ifFieldEmpty{streetline1}{}{\Field{streetline1}\par} % \ifFieldEmpty{streetline2}{}{\Field{streetline2}\par} % \ifFieldEmpty{streetline3}{}{\Field{streetline3}\par} % \Field{city}, \Field{stateprovince} \Field{postalcode} % \end{verbatim} % % The |middleinitial| column is a middle initial with no period. Some recipients will have no middle initial % but you don't want just a floating period. Also you don't want an additional space. % Knuth also suggests that names should be \TeX ed with |First M.~Last| [The \TeX\ book, Ch. 6]. % The |\ifFieldEmpty| with |streetline1| is probably redundant but often |streetline2| will be empty. % If it is not, we want a new line after it, but if it is empty we don't want a blank new line. % % In storage mode you need a |\noexpand| before the |~|, as well as before the |\par|'s. % \subsection{Some master-files} % \subsubsection{A table} % We are doing a table so we want storage mode. % We also want a horizontal line after each row of the table. % The master-file: % \begin{verbatim} % \documentclass[12pt]{amsart} % \RequirePackage{csvmerge} % \begin{document} % \mergeFields{ % tmp= { ./table.tmp} % csv = {./data.csv} % tex ={./table.tex } % } % \begin{tabular}{|l|l|l|l|l|} % \hline% % \input{./table.tmp} % \end{tabular} % \end{document} % \end{verbatim} % % The |tex|-file |table.tmp|: % \begin{verbatim} % % one table line % \Field{firstname} &\Field{id} & \Field{city} &\Field{lastname}& % \Field{postalcode} \noexpand\\ % \noexpand\hline % \end{verbatim} % % \subsubsection{A mail-merge} % We are doing a mail-merge so we can use streaming mode. % Since the |tex|-file name has spaces, we have quoted it. % The master-file: % \begin{verbatim} % \documentclass[12pt]{letter} % \RequirePackage{./csvmerge} % \begin{document} % \mergeFields{ tex = {"./Second letter.template" } % csv ={ ./Invites.csv}} % \end{document} % \end{verbatim} % % The |tex|-file |Second letter.template|: % % \begin{verbatim} % %First page of letter % \setcounter{page}{1} % % \Field{firstname} \ifFieldEmpty{middleinitial}{}% % {\Field{middleinitial}.~}\Field{lastname}\par % \ifFieldEmpty{streetline1}{}{\Field{streetline1} \par} % \ifFieldEmpty{streetline2}{}{\Field{streetline2}\par} % \ifFieldEmpty{streetline3}{}{\Field{streetline3}\par} % \Field{city}, \Field{stateprovince} \Field{postalcode} % % \vskip.25in % Dear \Field{preferredfirstname},\par % \vskip10pt % How come no RSVP? % \vskip10pt % Bilbo Baggins % \newpage % \end{verbatim} % % Here is an equivalent version using storage mode. % The master-file: % \begin{verbatim} % \documentclass[12pt]{letter} % \RequirePackage{./csvmerge} % \begin{document} % \mergeFields{ tex = {"./Second letter.template" } % csv ={ ./Invites.csv}tmp={letters}} % \input{letters} % \end{document} % \end{verbatim} % % We must adjust the |tex|-file |Second letter.template|: % % \begin{verbatim} % %First page of letter % \noexpand\setcounter{page}{1} % % \Field{firstname} \ifFieldEmpty{middleinitial}{}% % {\Field{middleinitial}.\noexpand~}\Field{lastname}\noexpand\par % \ifFieldEmpty{streetline1}{}{\Field{streetline1} \noexpand\par} % \ifFieldEmpty{streetline2}{}{\Field{streetline2}\noexpand\par} % \ifFieldEmpty{streetline3}{}{\Field{streetline3}\noexpand\par} % \Field{city}, \Field{stateprovince} \Field{postalcode} % % \noexpand\vskip.25in % Dear \Field{preferredfirstname},\par % \noexpand\vskip10pt % How come no RSVP? % \noexpand\vskip10pt % Bilbo Baggins % \noexpand\newpage % \end{verbatim} % % Usually using storage mode when you don't need it is wasteful since you have to create and % save a |tmp|-file. It can come in handy if your letter code has lots of field macros and % you can't get one |tex|-file to not have overfull/underfull boxes and the like after the % various expansions. The |tmp|-file contains all the letters and you can fine tune each one and % then just do % \begin{verbatim} % \documentclass[12pt]{letter} % \begin{document} % \input{letters} % \end{document} % \end{verbatim}