9

I have an external program who writes a text file as follows:

<number> "<nameA>"
<number> "<nameB>"
<number> "<nameC>"
...

How would I parse all lines and assign each number to a macro of the given name?

Here is a prototype of what I'm looking for:

\documentclass{article}
\usepackage{filecontents}% http://ctan.org/pkg/filecontents
\usepackage{siunitx}

\begin{filecontents}{myinput.txt}
0.45 "wingTaperRatio"
12.0 "wingSpanMT"
10.2 "wingAreaMTSquared"
\end{filecontents}

\begin{document}

%
% We want to assign 
%   0.45 to \wingTaperRatio
%   12.0 to \wingSpanMT
%   10.2 to \wingAreaMTSquared
%
\parseInput{myinput.txt}

% So that we can write
Given a wing whose planform has
a taper ratio $\lambda=\num{\wingTaperRatio}$,
a span $b=\SI{\wingSpanMT}{\metre}$, 
and a reference surface $S=\SI{\wingAreaMTSquared}{\metre^2}$,
solve the following problem \ldots

\end{document}

How the macro \parseInput would be implemented?

EDIT: There's one more need here, i.e. that the macro names should admit repetitions. This is because I want to prepare more subfolders, each one with its own myinput.txt. Each subfolder will be related to a different proposed exercise in a book. The data and results of the exercises are handled and written on file by the external program. But some of them may be related to the same physical quantity and values may change from one exercise to the next. So, I may have to use the macro \parseInput several times, with the need of redefining some macros. I may want to have something like:

\section{Exercise 1}
\parseInput{exerciseOne/myinput.txt}

Given a wing whose planform has
a taper ratio $\lambda=\num{\wingTaperRatio}$,
a span $b=\SI{\wingSpanMT}{\metre}$, 
and a reference surface $S=\SI{\wingAreaMTSquared}{\metre^2}$,
solve the following problem \ldots

\section{Exercise 2}
\parseInput{exerciseTwo/myinput.txt}

Now, the taper ratio $\lambda$ is not given,
while the wing has a new span $b=\SI{\wingSpanMT}{\metre}$, 
and a reference surface $S=\SI{\wingAreaMTSquared}{\metre^2}$.
Find the new value of $\lambda$.
2
  • The solutions provided by Andrey and me allow for redefining the macros without any problem.
    – egreg
    Mar 13, 2012 at 9:56
  • Yeah. I've edited my question further to explain why I need to handle a file like that.
    – agodemar
    Mar 13, 2012 at 14:32

3 Answers 3

8

The best approach would be to configure your software to output data in the format

\newcommand{\<nameA>}{<number>}

Then you could input the data with a mere \input{myinput.txt}.

But even as the data is now, parsing it is possible using various TeX and LaTeX constructs:

\documentclass{article}

\begin{filecontents*}{myinput.txt}
0.45 "wingTaperRatio"
12.0 "wingSpanMT"
10.2 "wingAreaMTSquared"
\end{filecontents*}

\makeatletter
\newread\myinput
\def\parseline#1 "#2"{\@namedef{#2}{#1}}
\newcommand{\parseinput}[1]{%
  \def\@tempb{\par}%
  \openin\myinput=#1
  \loop\unless\ifeof\myinput
    \read\myinput to \@tempa
    \ifx\@tempa\@tempb\else
      \expandafter\parseline\@tempa
    \fi
  \repeat
  \closein\myinput
}
\makeatother

\parseinput{myinput.txt}

\begin{document}

\wingTaperRatio, \wingSpanMT, \wingAreaMTSquared.

\end{document}

The loop will read the file line by line. Each line is then passed to the \parseline macro, which uses delimited arguments to extract the values. Then \@namedef creates a macro of the specified name. Beware that if an input line does not correspond to the parameter specification of \parseline, TeX will raise an error.

4
  • \newread must go outside the definition of \parseinput
    – egreg
    Mar 10, 2012 at 13:22
  • @egreg: Indeed, I just fixed it. Mar 10, 2012 at 13:24
  • @Andrey Vihroy: This answer suits my needs. The external program is 'rigid' and I cannot change the way the output is formatted. Hence the wording of this question. The purpose here is stripping off those pair of double-quote characters.
    – agodemar
    Mar 10, 2012 at 14:02
  • 1
    @agodemar Note that this will fail if you have a line starting with % in the data file.
    – egreg
    Mar 12, 2012 at 15:39
4

This LaTeX3 code ought to work

\usepackage{xparse}
\ExplSyntaxOn
\ior_new:N \l_ago_read_s
\tl_new:N \l_ago_read_tl
\NewDocumentCommand{\parseInput}{ m }
  {
   \ior_open:Nn \l_ago_read_s { #1 }
   \group_begin: \tex_endlinechar:D \c_minus_one
   \bool_until_do:nn { \ior_if_eof_p:N \l_ago_read_s }
     {
      \ior_to:NN \l_ago_read_s \l_ago_read_tl
      \tl_if_empty:NF \l_ago_read_tl
      { \exp_after:wN \ago_process_line:w \l_ago_read_tl \q_stop }
     }
   \group_end:
   \ior_close:N \l_ago_read_s
  }

\cs_new:Npn \ago_process_line:w #1 ~ " #2 " \q_stop
  {
   \cs_gset:cpn { #2 } { #1 }
  }

\ExplSyntaxOff

and it's very similar to Andrey Vihrov's code.

The \ago_processline:w function, when feeded with the line

0.45 "wingTaperRatio"

will perform the equivalent of

\gdef\wingTaperRatio{0.45}

so there will be no problem if a new \parseInput command reads another file: the definition of \wingTaperRatio will be silently overwritten.

Note

Unfortunately, it seems that a small bug in the expl3 packages prevents using \ior_open:Nn; the next update of the LaTeX3 package should solve the issue. For the moment one can do with \ior_open_unsafe:Nn instead of \ior_open:Nn.

5
  • Similar? as a train to a mini:)
    – yannisl
    Mar 10, 2012 at 16:49
  • @YiannisLazarides It parallels almost perfectly the other code.
    – egreg
    Mar 10, 2012 at 16:53
  • Sure it does and is probably more reliable, gets you there; but is so much more wider:)
    – yannisl
    Mar 10, 2012 at 16:55
  • +1 for the LaTeX3 example. Does \ago_process_line handle the case when more spaces are present between #1 and "#2" ?
    – agodemar
    Mar 11, 2012 at 9:09
  • @agodemar When TeX reads the file, it normalizes consecutive spaces into one space token.
    – egreg
    Mar 11, 2012 at 9:20
4

For completeness, here is a more general solution. If the starred (*) variant of the defined parser is used, then existing names can't be redefined.

\documentclass{article}
\usepackage{catoptions}

\begin{filecontents*}{myinput1.txt}
% Exaggerated example with large spaces:
   0.45   "   wingTaperRatio1  "
% No space between number and tag:
12.0"wingSpanMT1"
10.2 "wingAreaMTSquared1"
% No name tag:
10.2
% No number:
"wingAreaMTSquaredB1-b"
% The next one will given an error if the starred (*) variant of parser is
% called: existing name is being redefined.
% 11.3 "wingAreaMTSquared1"
\end{filecontents*}

% A different quotation mark (') is used in the next example:
\begin{filecontents*}{myinput2.tex}
0.45x 'wingTaperRatio2'
12.0x 'wingSpanMT2'
10.2x'wingAreaMTSquared2'
10.2x
'wingAreaMTSquared2-b'
% The next one will give an error for starred variant of parser:
% 11.3 'wingAreaMTSquared2'
\end{filecontents*}

\makeatletter
% You can redefine \doemptytag to specify what happens to numbers 
% having empty tags:
\robust@def*\doemptytag#1{%
  \typeout{Parsing input: empty tag for number '\cpttrimspace{#1}'}%
}
\robust@def*\DefineLineParser#1#2{%
  \ifescapedTF{#2}{}{\cpt@notescapederr{#2}}%
  \csn@def{\string#2@aux@a}##1#1##2#1##3\@nil{%
    \iflacus##2\dolacus
      \iflacus##1\dolacus\else
        \doemptytag{##1}%
      \fi
    \else
      \ifcpt@st
        \ifcsndefinable{\cpttrimspace{##2}}\relax
      \fi
      \csn@edef{\cpttrimspace{##2}}{\cpttrimspace{##1}}%
    \fi
  }%
  \robust@def*#2{\aftercsname\cpt@testst{\string#2@aux@b}}%
  \csn@def{\string#2@aux@b}##1{%
    \defpass\reserved@a####1.####2.####3\@nil{%
      \edef\reserved@a{\ifblankTF{####2}{tex}{####2}}%
      \openin\@inputcheck=####1.\reserved@a\space
      \ifeof\@inputcheck
        \@latex@error{File '####1.\reserved@a' doesn't exist}\@ehd
      \fi
    }%
    ##1..\@nil
    \def\par@tmp{\par}%
    \cptloop\ifeof\@inputcheck\else
      \read\@inputcheck to\reserved@a
      \ifx\reserved@a\par@tmp\else
        \aftercsname\expandafter{\string#2@aux@a}\reserved@a#1#1\@nil
      \fi
    \cptrepeat
    \closein\@inputcheck
    \undefcs\par@tmp
  }%
}
\makeatother

\DefineLineParser{"}\parseinput
\parseinput{myinput1.txt}

% The next call is starred and will raise errors for existing names:
% \parseinput*{myinput1.txt}

% The default file extension (tex) is assumed by the next example:
\DefineLineParser'\parseinputb
\parseinputb{myinput2}

\begin{document}
\let\use\usename
\use{wingTaperRatio1}, \use{wingSpanMT1}, \use{wingAreaMTSquared1}.
\par\medskip
\use{wingTaperRatio2}, \use{wingSpanMT2}, \use{wingAreaMTSquared2}.
\end{document}
6
  • +1 Does this solution handle the case <more spaces><number><more spaces>"<name>" ?
    – agodemar
    Mar 11, 2012 at 8:42
  • @agodemar: Yes, it works as well in that case. In <spaces><number><spaces><name> the first <spaces> is skipped by TeX and the second one is trimmed by the above code.
    – Ahmed Musa
    Mar 12, 2012 at 5:01
  • as I said in the edit to my question, I may want to admit repetitions in the macro names.
    – agodemar
    Mar 13, 2012 at 7:57
  • @agodemar: I have edited the answer to attend to your latest comment.
    – Ahmed Musa
    Mar 13, 2012 at 18:45
  • This is a very flexible solution. One minor inconvenience is that a code like \use{wingTaperRatio} requires more typing as compared to \wingTaperRatio. But, on the good side, it looks like your approach allows for variable names with digits. Am I correct?
    – agodemar
    Mar 13, 2012 at 22:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .