1

Here is a MWE:

\documentclass{extbook}

\usepackage{xspace}
\usepackage{xstring}
\usepackage{ifthen}

\newcommand\foo[1]{%
\def\tempfoo{#1}%
#1%
}

\newcommand\separation[1]{%
[\the\numexpr #1\relax ]%
}

\immediate\newread\myfile
\newboolean{isdisplaying}
\newcommand\displayfile[1]{
\edef\todisplay{\separation{#1} }
\edef\aftertodisplay{\separation{#1+1} }
\typeout{\todisplay}
\typeout{\aftertodisplay}
\setboolean{isdisplaying}{false}
\immediate\openin\myfile=myfile.tex
\loop\unless\ifeof\myfile
\immediate\read\myfile to\fileline
\ifthenelse{\equal{\aftertodisplay}{\fileline}}{\setboolean{isdisplaying}{false}}{}
\ifthenelse{\boolean{isdisplaying}}{\fileline}{}
\ifthenelse{\equal{\todisplay}{\fileline}}{\setboolean{isdisplaying}{true}}{}
\repeat
\immediate\closein\myfile

}


\begin{document}
\displayfile{2}
\end{document}

Here is the content of myfile.tex:

[1]
foo

[2]
\foo{bar}

[3]
baz

It causes this error:

 Undefined control sequence.
\foo #1->\def \tempfoo 
                       {#1}#1
l.39 \displayfile{2}
                    

because of the \foo command and the \def\tempfoo{#1} line.

As @DavidCarliste said, it can be caused by a context of expansion: https://tex.stackexchange.com/a/667865/237709

But I don't understand here.

EDIT:

SO, I suppose it is the test \equal{\aftertodisplay}{\fileline} that causes the error.

[edit: that is bullshits i have written: Maybe \equal prevent the expansion of \fileline? How can I force the expansion of \fileline in this situation?]

EDIT (2): But, in my situation, the test:

\ifthenelse{\equal{\todisplay}{\protect\fileline}}

is always false, while it was true with:

\ifthenelse{\equal{\todisplay}{\fileline}}

However, in my example, \fileline and \protect\fileline have to give the same [2]?

In my example, \displayfile{2} with \protect\fileline produces 0 pages...

EDIT (3):

a test with

\typeout{\fileline}
\typeout{\expandafter\protect\fileline}

gives an interesting output:

...
[2] 
[2] 
bar 
\foo{bar} 
\par 
\par
...

EDIT (4):

I just don't understand why \protect\fileline, which displays [2], is not equal with \todisplay, while \fileline, which displays (apparently) the same [2], is equal.

EDIT (5): (@egreg answer) I can't explain why \unexpanded\expandafter{\fileline}, instead of \protect\fileline, works in the test \ifthenelse.

8
  • 1
    \ifthenelse{\equal{\aftertodisplay}{\fileline}} does edef of each argument then compares with \ifx but the edef fails here as you can not use \foo in an edef you need \protect\foo Dec 8, 2022 at 16:30
  • 1
    You do not want to force the expansion of \fileline, you want to prevent it expanding as it contains tokens such as \foo which are not safe in an expansion context Dec 8, 2022 at 16:32
  • So, yes, with \ifthenelse{\equal{\aftertodisplay}{\protect\fileline} it works. But why "edef" needs "protect", David? Dec 8, 2022 at 16:32
  • 1
    well ifthenelse uses \protected@edef which defines \protect as \noexpand\protect\noexpand so \protect\foo is \noexpand\protect\noexpand\foo which expands to \protect\foo then the edef moves on... Dec 8, 2022 at 16:36
  • 1
    I did write ifthenelse, you shouldn't be so surprised I have an idea about what it does:-) Dec 8, 2022 at 16:39

2 Answers 2

2

The problem is that \ifthenelse does full expansion, and with \edef{\def\tempfoo{#1}} you get almost surely an error, whether or not \tempfoo is defined.

You can stop full expansion by using \protect\fileline or

\unexpanded\expandafter{\fileline}

Remove all those \immediate tokens that do nothing at all.

But there are far better methods nowadays.

\begin{filecontents*}{\jobname-data.tex}
[1]
foo

[2]
\foo{bar}

[3]
baz
\end{filecontents*}

\begin{filecontents*}{\jobname-data2.tex}
-1-
foo

-2-
\foo{bar}

-3-
baz
\end{filecontents*}

\documentclass{article}

\ExplSyntaxOn

%% user interface

\NewDocumentCommand{\displayfile}{omm}
 {
  % #1 = separation format
  % #2 = filename
  % #3 = part to display
  \dufays_displayfile:nnn { #1 } { #2 } { #3 }
 }

%% variables

\ior_new:N \g_dufays_displayfile_ior
\str_new:N \l__dufays_displayfile_before_str
\str_new:N \l__dufays_displayfile_after_str
\bool_new:N \l__dufays_displayfile_show_bool

%% private functions and needed variants

\cs_new:Nn \__dufays_displayfile_sep:n { }
\cs_generate_variant:Nn \__dufays_displayfile_sep:n { e }

%% public functions

\cs_new_protected:Nn \dufays_displayfile:nnn
 {
  % if there's no optional argument, use a default separator format
  \tl_if_novalue:nTF { #1 }
   {
    \cs_set:Nn \__dufays_displayfile_sep:n { [##1] }
   }
   {
    \cs_set:Nn \__dufays_displayfile_sep:n { #1 }
   }
  \str_set:Nx \l__dufays_displayfile_before_str { \__dufays_displayfile_sep:n { #3 } ~ }
  \str_set:Nx \l__dufays_displayfile_after_str { \__dufays_displayfile_sep:e { \int_eval:n { #3+1 } } ~ }
  \ior_open:Nn \g_dufays_displayfile_ior { #2 }

  \bool_set_false:N \l__dufays_displayfile_show_bool
  \ior_map_inline:Nn \g_dufays_displayfile_ior
   {
    \str_if_eq:VnTF \l__dufays_displayfile_before_str { ##1 }
     {% OK, next we print
      \bool_set_true:N \l__dufays_displayfile_show_bool
     }
     {% print or set false
      \str_if_eq:VnTF \l__dufays_displayfile_after_str { ##1 }
       { \bool_set_false:N \l__dufays_displayfile_show_bool }
       { \bool_if:NT \l__dufays_displayfile_show_bool { ##1 } }
     }
   }
  \ior_close:N \g_dufays_displayfile_ior
 }

\ExplSyntaxOff

\newcommand\foo[1]{%
  \def\tempfoo{#1}%
  #1%
}

\begin{document}

\displayfile{\jobname-data}{2}

This is \verb|\tempfoo|: \tempfoo

\displayfile[-#1-]{\jobname-data2}{3}

\end{document}

We can set the separator on the spots and of course input whatever file we please.

The \ior_map_inline:Nn function reads the file line by line. If the line equals the stated starting point, we set the show boolean to true. If the line equals the end point, the show boolean is set to false. In all other cases, the line is printed provided the boolean is true.

enter image description here

2
  • Your mastery of expl3 is very impressive. I have, indeed, to learn this new methods. Very impressive. Dec 8, 2022 at 17:52
  • In my case, \unexpanded\expandafter{\fileline} works with the test ifthenelse. I do not understand why protect fileline does not the same, but... Thank you @egreg (and David alos of course). Dec 8, 2022 at 17:56
2

If I understand what is your intend, you want to read and process only a part of the file after [num] label and before empty line when the \displayfile{num} macro is used. The \displayfile macro can be definded as follows:

\expandafter\let \expandafter\primitiveinput \csname @@input\endcsname
\def\displayfile#1{%
   \everyeof={\par}
   \long\def\scanfile ##1[#1]##2\par{##2\endinput}
   \expandafter\scanfile\primitiveinput myfile 
   \everyeof={}
}
\def\foo#1{\def\tempfoo{#1}#1}

Test:
\displayfile{1}

\displayfile{3}

\displayfile{2}

Note that the first line \expandafter\let ... followed by usage \primitiveinput (instead \input) is needed only when you are using LaTeX, because (unfortunately) LaTeX redefines \input primitive.

1
  • Thank you @wipet. I have to test your (I'm sure) great proposal to understand it and improve my (little) knowledge of TeX. Dec 9, 2022 at 14:24

You must log in to answer this question.

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