10

I'm trying to redefine a \tableofcontents-like command.

I proceed this way : the displayed elements are written to a file, and the file is input when I want the display.

If I know the toc will be called at the beginning (resp. the end) of the document, it's fine : all I have to do is to add the \openout (resp \closeout) commands to the toc command. However, I'd prefer not to work under that assumption (mostly because I won't be the only one using that code, and I want to avoid obscure side effects).

So I want the \openout and \closeout commands to be automatically issued at \begin{document} and \end{document}. The problem is that in that case, I can't \input the auxiliary file in the middle of the document. I've been trying to store the content of the file, before the \begin{document}, in a macro, but I am facing various problems which I guess are related to expansion.

After browsing some related questions, I managed to write the following code :

\documentclass{article}

\newwrite\sommaire

\AtBeginDocument{%
  \immediate\openout\sommaire=\jobname.som%
}
\AtEndDocument{%
  \immediate\closeout\sommaire}

\newcommand{\sommaireline}[1]{%
  \immediate\write\sommaire{#1}
}

\makeatletter
\IfFileExists{\jobname.som}%
{\edef\mysommaire{\@@input \jobname.som }} 
{\edef\mysommaire{\message{No file \jobname.som}}}
\makeatother

\begin{document}

\sommaireline{aaaaa}

\mysommaire

\sommaireline{bbbbb}

\end{document}

But it still doesn't work, and now I don't understand why.

More precisely, the first compilation goes fine, and the .som file is written as expected, but the second compilation fails with :

ERROR: File ended while scanning definition of \mysommaire.
...
ERROR: Too many }'s

both on the line of the first \edef.

Some related questions :

5
  • I hit against the same problem and concluded (perhaps wrongly) that it is impossible (due to the end of file being encountered) to \input a generic (text) file into a macro or token list: rather it must be read line by line with an input stream, see my answer. If someone has another method and proves me wrong and that one can use in one go the \input rather than \read line by line I am very interested to hear about it!
    – user4686
    Dec 7, 2012 at 18:09
  • As added in my updated answer, one can use \input to fill a token list or a macro, but the file has to end with an \endinput.
    – user4686
    Dec 7, 2012 at 20:24
  • You can use \CatchFileDef from the catchfile package.
    – egreg
    Dec 7, 2012 at 20:28
  • @egreg I apologize to everybody for the silly statements about \endinput. This was made much too quickly, whereas what I really know for having practiced it is the method of using \read as explained in my initial answer. Sorry folks for the rather stupid things I then added and took away.
    – user4686
    Dec 7, 2012 at 20:46
  • @egreg HO seems to have solved very thorny problems with catchfile. I am so ignorant that I don't even see the reasons why this task is so complicated. Any pointer?
    – user4686
    Dec 7, 2012 at 21:02

3 Answers 3

9

Just use Heiko Oberdiek's catchfile package:

\documentclass{article}
\usepackage{catchfile}
\newwrite\sommaire

\AtBeginDocument{\immediate\openout\sommaire=\jobname.som }
\AtEndDocument{\immediate\closeout\sommaire}

\newcommand{\sommaireline}[1]{%
  \immediate\write\sommaire{#1}
}

\IfFileExists{\jobname.som}%
  {\CatchFileDef\mysommaire{\jobname.som}{}}
  {\def\mysommaire{\message{No file \jobname.som}}}

\begin{document}

X\sommaireline{aaaaa}

\mysommaire

\sommaireline{bbbbb}X

\end{document}

You can use the final argument to \CatchFileDef for making assignments, for instance

\CatchFileDef\mysommaire{\jobname.som}{\endlinechar=-1 }

will suppress the final space due to the end-of-line characters in the input file.

5
  • 1
    Thank you very very much. I am still very curious about "why is it needed", though. Any clue?
    – T. Verron
    Dec 7, 2012 at 21:19
  • 3
    @T.Verron When you look at the code in catchfile.sty, you'll realize that the business is not at all easy and that Heiko's a real wizard. :)
    – egreg
    Dec 7, 2012 at 21:42
  • 1
    I did look at the code and it indeed looked like black magic to me. But that's not an explanation, merely a justification... What makes it so hard?
    – T. Verron
    Dec 7, 2012 at 21:56
  • @T.Verron Actually it's somewhat easier to use \edef instead of \def; the main point is that one has to be careful about the end of the file; in the case of \edef a \noexpand is inserted at the end, which makes TeX happy. For \def it's hairier.
    – egreg
    Dec 7, 2012 at 22:02
  • @T.Verron @egreg I looked at catchfile.sty and it is way above my understanding. But here the situation is different as we create the file \jobname.som. See my newer answer.
    – user4686
    Dec 7, 2012 at 22:23
2

[completely misleading code and statements retracted]

[strike] The trick is thus that the file to be input into a token list or a macro must end with \endinput. [/strike] Sorry, I made a fool of myself here.

I maintain the following initial answer of mine using \read:


(in the lines below, replace everywhere .toc by .som so that it is more closely a reply to T.Verron)

I had a similar problem when trying to \input the .toc file into a token list, some weeks ago when developing a package of mine. So here is how I do these things now:

\newtoks\my@toctoks
\def\my@readtoc#1{%
  \ifeof #1
     \let\my@nextread\@gobble
     \global\my@toctoks=\expandafter{\the\my@toctoks}%
  \else
     \let\my@nextread\my@readtoc
     \read #1 to \my@buffer
     \my@toctoks=\expandafter\expandafter\expandafter
       {\expandafter\the\expandafter\my@toctoks\my@buffer}%
  \fi
  \my@nextread{#1}}
\IfFileExists{\jobname .toc}
    {{\endlinechar=-1 \makeatletter
        \newread\my@tf
        \openin\my@tf\@filef@und
           \my@readtoc\my@tf
        \closein\my@tf}}{}

This is for reading the .toc file (and not typesetting it immediately). Note that I put \endlinechar=-1 but this was for some specific reasons in my package. An aftereffect is that there will be no \par at the end, whereas if you don't modify \endlinechar there will be one generated by TeX when reading the input stream.

Regarding the opening for writing you can pick up the code from inside the \@starttoc command. If you do this at \begin{document} no more input of the .toc file will be possible by some other package or macro, so perhaps it is better to do it only at the first typesetting of a toc.

In my package I also did these things at the \begin{document} but in the latest version (not yet on CTAN) I read the .toc file as above at the \usepackage and I open for writing at the first TOC typesetting command.

3
  • Thanks for this great answer. However, if possible, I'd prefer to keep the complexity of the code to a minimum (once again, because I won't be the only one to use and maintain it). If not possible, I'll gladly accept your answer, if you can convince me there's no easier way... But from your other comment, I understand it's a mystery for you too, so there's still hope. :)
    – T. Verron
    Dec 7, 2012 at 19:25
  • @T.Verron sorry for the noise, my previous comment on \endinput is just wrong.
    – user4686
    Dec 7, 2012 at 20:44
  • As explained in my other answer, it is also possible to use \read only once and it will feed a macro with the totality of the file, as long as this file (as is the case in the OP's context), is actually created by us, so can be configurated in a special manner which will prepare it for this special use of \read.
    – user4686
    Dec 7, 2012 at 22:43
1

I have found a method using the fact that the read operation always must find balanced braces, and reads lines until the braces are balanced. The trick then is to start the file \jobname.som with a brace and end it with a brace. But \write also wants balanced expressions, so we do a little of catcode hackery.

\documentclass{article}

\newcommand{\sommaireline}[1]{\immediate\write\sommaire{#1}}

\begingroup
\catcode1=12
\catcode2=12
\AtBeginDocument{\newwrite\sommaire
                 \immediate\openout\sommaire=\jobname.som
                 \sommaireline{^^A}}
\AtEndDocument  {\sommaireline{^^B}}

\catcode1=1
\catcode2=2
\IfFileExists{\jobname.som}%
  {\newread\somfile 
   \openin\somfile=\jobname.som
   \read\somfile to \mysomm
   \expandafter\gdef\expandafter\mysommaire\mysomm}
  {\gdef\mysommaire{No file \jobname.som}}
\endgroup

\begin{document}

X\sommaireline{aaaaa}

\mysommaire

\sommaireline{bbbbb}X

\end{document}
4
  • Well, if it comes to that, I could as well define the whole command inside the file. ;) Thanks for the work, but since Heiko Oberdiek's package gives a single-line solution, I'll stick with that one.
    – T. Verron
    Dec 7, 2012 at 23:05
  • @T.Verron right... ;) both my answers use \read and there is the issue of line endings too. The repeated use of \read from my first post is still the simpler thing, and I wish somebody would come here and explain to us what exactly are the problems solved by Heiko's catchfile. Nothing as complicated (I think) is done by the LaTeX kernel and classes to deal with TOCs, so are you sure you do have to go that way? (except for the good excuse that it uses the wizardry of a wizard... ;) )
    – user4686
    Dec 7, 2012 at 23:18
  • The good excuse is it uses no @ and fits in one line. Plus, I tend to trust established packages more than one-time hacks, since they have obviously been tested more often for errors. Reinventing the wheel in TeX is a dangerous task.
    – T. Verron
    Dec 7, 2012 at 23:34
  • I agree completely as you have to get the job done and well done.
    – user4686
    Dec 8, 2012 at 7:32

You must log in to answer this question.

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