6

I'm setting a complex user manual, made of several independant documents. For the overall TOC, I want each document to write some data into a file which is then parsed by the TOC file later.

Here's the point: I want to use a string in a macro as the filename, but it doesn't work. It must have something to do with correct expansion, but I can't find the error...

\documentclass[10pt]{article}

 \newcommand{\docnr}{foo bar}

 \usepackage{stringstrings}
 \newcommand{\tocfile}{../toc/\noblanks{\docnr}.tex}

 \newcommand{\SToc}{
   \newwrite\tempfile
   \immediate\openout\tempfile=\tocfile

   %other stuff
   \immediate\write\tempfile{\docnr}
   %other stuff

   \immediate\closeout\tempfile
}

\begin{document}
   \SToc
   Hello World.
\end{document}

EDIT: A completely different approach that would work for me would be the following: I'm looking for an environment that's full content is expanded as much as possible and then written to the file. By this, I could simply \input it in the TOC later. Something like

\newcommand{\docnr}{foo bar}
\begin{writetofile}[../toc/\noblanks{\docnr}.tex]
   \textbf{\docnr}
\end{writetofile}

And the file foobar.tex would contain

\textbf{foo bar}

There are many environments to write commands verbatim (without expansion) to a file - I've now searched for the exact opposite, but couldn't find anything. Grmpf!

4
  • Welcome to TeX.SX! The file name for \openout should be something that fully expands to a string, but \nospaces isn't. – egreg Sep 26 '15 at 12:23
  • Do you need page numbers in the \tocfile? – Heiko Oberdiek Sep 26 '15 at 16:16
  • Heiko: No, it's the front page of the ringfile and onyl gives the chapter numbers by a custom counter. I've got it running now quite fine, except that I can't put the file "upstream" with the ../toc/foobar.tex -- seems that I'm only allowed to write into the current folder or downstream of that. – Victor Sep 26 '15 at 16:40
  • Found something about the upstream navigation here: tex.stackexchange.com/questions/116926/… – Victor Sep 26 '15 at 16:47
5

The LaTeX kernel has a command \zap@space, which is expandable and can be used in file names, e.g.:

\newcommand*{\docnr}{foo bar}

\makeatletter
\edef\docnr@wosp{\docnr\space}
\newcommand{\tocfile}{}
\edef\tocfile{../toc/\expandafter\zap@space\docnr@wosp\@empty.tex}
\makeatother

\typeout{[tocfile=\tocfile]}

Result:

[tocfile=../toc/foobar.tex]

Writing without page numbers

When page numbers are not needed, then deferred writing is not needed and we can use \immediate\write. LaTeX's protection mechanism is supported by

\let\protect\noexpand

while writing the entry. Then \textbf{\docnr} becomes \textbf {foo bar}. TeX adds a space after command names and the other space comes from LaTeX's protected commands, which expand to a macro with the same name except that a trailing space was added to the macro name.

Fragile macros can be protected by prefixing it with \protect as usual. \string can also be used, it avoids the space after the command name.

\documentclass{article}

\newcommand*{\docnr}{foo bar}

\makeatletter
\edef\docnr@wosp{\docnr\space}
\newcommand{\tocfile}{}
\edef\tocfile{\expandafter\zap@space\docnr@wosp\@empty.tex}
\makeatother

% toc file management
\makeatletter
\if@filesw
  \let\toc@handle\relax
  \DeclareRobustCommand{\addtotoc}[1]{%
    \ifx\toc@handle\relax
      \newwrite\toc@handle
      \immediate\openout\toc@handle=\tocfile\relax
    \fi
    \begingroup
      \let\protect\noexpand
      \immediate\write\toc@handle{#1}%
    \endgroup
  }%
  \newcommand*{\closetoc}{%
    \immediate\closeout\toc@handle
  }%
\else
  \newcommand{\addtotoc}[1]{}%
  \newcommand*{\closetoc}{}%
\fi
\makeatother

\begin{document}
  \addtotoc{\textbf{\docnr}, %
    written via macro \string\verb|\string\addtotoc|.}
  \closetoc
  \input{\tocfile}
\end{document}

Result

File foobar.tex:

\textbf  {foo bar}, written via macro \verb|\addtotoc|.

The file can be read before the first \addtotoc and after \closetoc.

Deferred writing with page numbers

\documentclass{article}

\newcommand*{\docnr}{foo bar}

\makeatletter
\edef\docnr@wosp{\docnr\space}
\newcommand{\tocfile}{}
\edef\tocfile{\expandafter\zap@space\docnr@wosp\@empty.tex}
\makeatother

% toc file management
\makeatletter
\if@filesw
  \let\toc@handle\relax
  \DeclareRobustCommand*{\addtotoc}{%
    \ifx\toc@handle\relax
      \newwrite\toc@handle
      \immediate\openout\toc@handle=\tocfile\relax
    \fi
    \protected@write\toc@handle{}% + argument of \addtotoc
  }%
  \newcommand*{\closetoc}{%
    \closeout\toc@handle
  }%
\else
  \newcommand{\addtotoc}[1]{}%
  \newcommand*{\closetoc}{}%
\fi
\makeatother

\begin{document}
  Hello World!

  \addtotoc{\textbf{\docnr} on page \thepage,
    written via macro \string\verb|\string\addtotoc|.}
  \closetoc
  \newpage
  \input{\tocfile}
\end{document}

Result deferred writing

File foobar.tex:

\textbf  {foo bar} on page 1, written via macro \verb|\addtotoc|.

The file can be read before the first \addtotoc and after the page, where the deferred \closeout is called.

1
  • Servus Heiko & Christian! This is quick and easy -- and just works fine. Thanks a lot! If anyone has an idea for my alternative approach, this would also be highly appreciated ;-) Regards, Vic – Victor Sep 26 '15 at 15:41
2

You could try this with xstring and \StrDel, preprocessing the filename first and storing it to \tocfile macro.

The macro \noblanks from stringstrings isn't expandable, unfortunately. (Have a look into stringstrings package or use texdef to see this)

Some other note: It might occur that pdflatex will refuse to write on pathnames starting with a ..

Try to locate texmf.cnf and add this line at the end:

openout_any = a

However, this might be a security breach.

\documentclass[10pt]{article}


\newwrite\tempfile

\newcommand{\docnr}{foo bar}

%\usepackage{stringstrings}
\usepackage{xstring}
\gdef\tocfile{}
\StrDel[0]{../toc/\docnr.tex}{ }[\tocfile]


\newcommand{\SToc}{%
  \immediate\openout\tempfile=\tocfile
  % other stuff
  \immediate\write\tempfile{\docnr}
  % other stuff
  \immediate\closeout\tempfile
}

\begin{document}
\SToc
Hello World.
\end{document}
1

See Can filecontents write an external file in a parent directory? for the problem of writing files in directories above the current one or given an explicit path.

This said, we can solve your problem with an easy set of macros in expl3:

\documentclass{article}

\usepackage{xparse}

\ExplSyntaxOn

\iow_new:N \g_victor_output_stream

\tl_new:N \l_victor_filename_tl

%\tl_const:Nn \c_victor_prefix_tl { ../toc/ } % use this one
\tl_const:Nn \c_victor_prefix_tl { ./ } % for the test

\cs_generate_variant:Nn \iow_open:Nn { NV }

\NewDocumentCommand{\SToc}{ O{\docnr} }
 {
  \tl_set:Nx \l_victor_filename_tl { \c_victor_prefix_tl #1 }
  \tl_replace_all:Nnn \l_victor_filename_tl { ~ } { }
  \iow_open:NV \g_victor_output_stream \l_victor_filename_tl
  \STocStuffA
  \iow_now:Nx \g_victor_output_stream { #1 }
  \STocStuffB
  \iow_close:N \g_victor_output_stream
 }
\ExplSyntaxOff

\newcommand{\docnr}{foo bar}

\newcommand{\STocStuffA}{} % for the test
\newcommand{\STocStuffB}{} % for the test

\begin{document}

\SToc

Hello World.

\end{document}

The macros \STocStuffA and \STocStuffB will contain what you need to be done before and after the write operation as hinted in your code.

Just change the prefix to get a location different from the current working directory (the one where the main TeX file belongs).

You can call \SToc with no optional argument or with one, that will be used instead of the default \docnr. Possibly you don't need this feature, but it's so easy to add it that I didn't want to lose this opportunity for adding flexibility to your macros.

We prepare the string to be passed to \openout (in expl3 it becomes \iow_open:Nn, which we use a variant of) by removing all spaces. In the write operation, we can directly use the argument (\docnr by default).


Note about your code: don't put the \newwrite declaration in the macro \SToc, because you'd waste an output stream at each call. So \newwrite\tempfile must go outside the definition of \SToc (and a more specific name than \tempfile should be used).

2
  • Hi egreg! Isn't this just using LaTeX3-Syntax to loophole the reasonably "paranoid" setting in texmf.cnf? I'm afraid that one day this will be blocked as well. At the moment I'm working with "openout_any = a" (resp. MIKTEX_ALLOWUNSAFEOUTPUTFILES=1). --Regards, Vic – Victor Sep 27 '15 at 6:30
  • @Victor You can't overcome the setting in texmf.cnf with macros of any kind. I used a prefix for convenience and set it to ./ for testing; the other possibility won't work unless you set openout_any=a. – egreg Sep 27 '15 at 7:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.