-1

As a followup of How to use file content as a numeric value for a length?, I'm trying to do this (I really need to read the data.txt file inside this new command and use the value later, when the command is called):

\documentclass{article}
\usepackage{calc}
\begin{document}
\newcommand\x{
  \newread\foo
  \openin\foo=data.txt
  \read\foo to \temp
  \temp
}
\setlength\parskip{1pt * \x}
\end{document}

I'm getting:

! Missing number, treated as zero.
<to be read again>
                   \global
l.35 \setlength\parskip{1pt * \x}

What's wrong?

yegor256
  • 12,021

4 Answers4

2

You can do this, but it requires LuaTeX. There you can use \beginlocalcontrol/\endlocalcontrol to define a block of code which gets fully executed (including opening file) during TeX's expansion stage:

\documentclass{article}
\usepackage{calc}

% First define \beginlocalcontrol based on tex.runtoks \newluafunction\beginlocalcontrol \directlua{lua.get_functions_table()[\the\allocationnumber] = function() return tex.runtoks(token.get_next) end} \luadef\beginlocalcontrol\allocationnumber \begin{document} \newcommand\x{% \beginlocalcontrol % This block gets executed during expansion \begingroup % Let's keep our definitions local to avoid confusion macros which might not expect definitions to change during expansion \newread\foo \openin\foo=data.txt \read\foo to \temp \closein\foo \expandafter\endgroup % The two \expandafter's ensure that the final \temp gets the value from inside of the \endgroup and \endlocalcontrol blocks. \expandafter\endlocalcontrol \temp } \setlength\parskip{1pt * \x} \end{document}

I would recommend though to move at least the \newread \foo outside of the command, otherwise you waste one input file every time the macro gets expanded.

0

Actually this question happens to be solvable:

\documentclass{article}
\begin{document}

\makeatletter

\setlength\parskip{@@input{b.tex}pt} \typeout{\the\parskip}

\setlength\parskip{\dimexpr 1pt*@@input{b.tex}\relax} \typeout{\the\parskip}

\makeatother

\end{document}

Assuming b.tex contains 123, both will print out 123.0pt.

user202729
  • 7,143
  • p/s: if some experienced users gives clunky workarounds in answers, chances are they actually want to say "your original request is unsolvable"... which given the quirks of TeX is more likely than not. – user202729 Jan 17 '23 at 07:28
  • and \@@input has some quirks that makes it not work inside an edef, see https://tex.stackexchange.com/questions/516031/why-is-everyeof-noexpand-needed-to-avoid-file-ended-while-scanning-definitio. – user202729 Jan 17 '23 at 07:29
0

\read is not expandable but is an assignment-command itself.
Thus it cannot be carried out in the course of carrying out another assignment-command like \setlength.
As you intend to define a scratch-macro \x you can do s.th. like this:

\begin{filecontents*}{mydata.txt}
8
\end{filecontents*}

\documentclass{article} \usepackage{calc} \newread\foo

\begin{document} \immediate\openin\foo=mydata.txt \immediate\read\foo to \x \immediate\closein\foo %\setlength\parskip{1pt * \x} \setlength\parskip{\x pt} \showthe\parskip \end{document}

\@@input is LaTeX 2ε's copy of the expandable TeX-primitive \input.
\input/\@@input cannot be carried out in the course of directly obtaining a set of tokens that is to form a macro argument because in any case the end of the file will be encountered which is treated like an \outer token.
But you can arrange things in a way where they are not processed as macro arguments:

\begin{filecontents*}{mydata.txt}
8
\end{filecontents*}

\documentclass{article} \begin{document} \parskip=\csname @@input\endcsname mydata.txt pt % %\parskip=\dimexpr1pt*(\csname @@input\endcsname mydata.txt )\relax \showthe\parskip \end{document}

Ulrich Diez
  • 28,770
0

Since you want to do an assignment, expandability is not a factor.

The simplest method is to define a suitable command, say

\setlengthfromfile{<length>}{<file>}{<expression>}

where <length> is the register you want to set, <file> is the file you want to read a value from and <expression> contains #1 to denote the file contents, so your case would be

\setlengthfromfile{\parskip}{data.txt}{1pt * #1}

Code with examples:

\begin{filecontents*}{\jobname.dat}
20
\end{filecontents*}

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\setlengthfromfile}{mmm} {% #1 = length parameter % #2 = file name % #3 = expression \yegor_setlengthfromfile:nnn { #1 } { #2 } { #3 } }

\cs_new:Nn __yegor_setlengthfromfile_aux:n { } % initialize \cs_generate_variant:Nn __yegor_setlengthfromfile_aux:n { V }

\tl_new:N \l__yegor_setlengthfromfile_tl

\cs_new_protected:Nn \yegor_setlengthfromfile:nnn { \file_get:nnN { #2 } { } \l__yegor_setlengthfromfile_tl \cs_set:Nn __yegor_setlengthfromfile_aux:n { #3 } \skip_set:Nn #1 { __yegor_setlengthfromfile_aux:V \l__yegor_setlengthfromfile_tl } }

\ExplSyntaxOff

\setlengthfromfile{\parskip}{\jobname.dat}{1pt * #1} \showthe\parskip

\setlengthfromfile{\parskip}{\jobname.dat}{1pt * #1-12pt} \showthe\parskip

\setlengthfromfile{\parskip}{\jobname.dat}{#1\parskip} \showthe\parskip

\setlengthfromfile{\parskip}{\jobname.dat}{#1pt plus 0.1pt} \showthe\parskip

\stop

The console will show

(./yegordim.dat
)
> 20.0pt.
l.31 \showthe\parskip

? (./yegordim.dat) > 8.0pt. l.34 \showthe\parskip

? (./yegordim.dat) > 160.0pt. l.37 \showthe\parskip

? (./yegordim.dat) > 20.0pt plus 0.1pt. l.40 \showthe\parskip

In the third example we reused the previous value of \parskip.

egreg
  • 1,121,712