我有一个外部程序,它写入如下文本文件:
<number> "<nameA>"
<number> "<nameB>"
<number> "<nameC>"
...
我如何解析所有行并将每个数字分配给给定名称的宏?
这是我所寻找的原型:
\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}
宏如何\parseInput
实现?
编辑:这里还有一个需求,即宏名称应该允许重复。这是因为我想准备更多的子文件夹,每个子文件夹都有自己的myinput.txt
。每个子文件夹将与书中提出的不同练习相关。练习的数据和结果由外部程序处理并写入文件中。但其中一些可能与相同的物理量相关,并且值可能会因练习而异。所以,我可能需要\parseInput
多次使用宏,需要重新定义一些宏。我可能想要有类似的东西:
\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$.
答案1
最好的方法是将软件配置为以以下格式输出数据
\newcommand{\<名称A>}{<编号>}
然后您只需输入数据即可\input{myinput.txt}
。
但即使数据是现在的,也可以使用各种 TeX 和 LaTeX 结构对其进行解析:
\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}
循环将逐行读取文件。然后,每行都传递给\parseline
宏,宏使用分隔参数来提取值。然后\@namedef
创建指定名称的宏。请注意,如果输入行与的参数规范不符\parseline
,TeX 将引发错误。
答案2
这个 LaTeX3 代码应该可以工作
\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
它与 Andrey Vihrov 的代码非常相似。
该\ago_processline:w
函数在输入以下行时
0.45 "wingTaperRatio"
将执行相当于
\gdef\wingTaperRatio{0.45}
因此,如果新命令读取另一个文件,则不会出现问题\parseInput
:的定义\wingTaperRatio
将被默默覆盖。
笔记
不幸的是,expl3 软件包中的一个小错误似乎阻止了使用\ior_open:Nn
;LaTeX3 软件包的下一次更新应该可以解决这个问题。目前,我们可以使用\ior_open_unsafe:Nn
代替\ior_open:Nn
。
答案3
为了完整性,这里有一个更通用的解决方案。如果使用已定义解析器的星号 (*) 变体,则无法重新定义现有名称。
\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}