8

I'm writing some lines to a file but I've found that robust commands damages the written file. In this context \plural can be a fragile command but in the real application it must be robust. MWE:

\documentclass{article}
\usepackage{xifthen}
\immediate\newwrite\txtfile
\immediate\openout\txtfile=\jobname.txt
\DeclareRobustCommand{\plural}[3]{\ifthenelse{\equal{#1}{1}}{#2}{#3}}
\newcommand{\woman}[1]{#1 \plural{#1}{woman}{women}}
\begin{document}
\immediate\write\txtfile{\woman{1}}
\immediate\write\txtfile{\woman{2}}
\end{document}

I want a way in which the text file shows '1 woman' and in the next line '2 women', defining \plural as robust.

10

Only a subset of the TeX language is available, if text is written to a file. The expandable machinery is working, but assignments and other unexpandable stuff is not.

In this case \plural can be made expandable:

\documentclass{article}
\immediate\newwrite\txtfile
\immediate\openout\txtfile=\jobname.txt

\makeatletter

\newcommand*{\plural}[1]{%
  \ifnum#1=1 %
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
}
\makeatother

\newcommand*{\woman}[1]{#1 \plural{#1}{woman}{women}}

\begin{document}
\immediate\write\txtfile{\woman{1}}
\immediate\write\txtfile{\woman{2}}
\end{document}

Result:

1 woman
2 women

Remarks:

  • Because \plural is full expandable, it is already robust and \DeclareRobustCommand is not needed.
  • The comparison is done via the expandable \ifnum, \ifthenelse is not expandable.
4

You shouldn't be using \write when the argument contains macros that should be “protected” or are defined with \DeclareRobustCommand. A \protected@immediatewrite macro can be defined using the definition of \protected@write as a model:

\documentclass{article}
\usepackage{xifthen}
\immediate\newwrite\txtfile
\immediate\openout\txtfile=\jobname.txt

\makeatletter
\long\def\protected@immediatewrite#1#2#3{%
  \begingroup
  \let\thepage\relax
  #2%
  \let\protect\@unexpandable@protect
  \edef\reserved@a{\immediate\write#1{#3}}%
  \reserved@a
  \endgroup
  \if@nobreak\ifvmode\nobreak\fi\fi
}

\newcommand\lazowrite[1]{\protected@immediatewrite\txtfile{}{#1}}
\makeatother

\DeclareRobustCommand{\plural}[3]{\ifthenelse{\equal{#1}{1}}{#2}{#3}}
\newcommand{\woman}[1]{#1 \plural{#1}{woman}{women}}
\begin{document}
\lazowrite{\woman{1}}
\lazowrite{\woman{2}}
\end{document}

Here \lazowrite is just syntactic sugar around \protected@immediatewrite.

In case you plan to use \nofiles, you should add

\makeatletter
\g@addto@macro\nofiles{%
  \long\def\protected@immediatewrite#1#2#3{%
    \immediate\write\m@ne{}\if@nobreak\ifvmode\nobreak\fi\fi
  }%
}
\makeatother

before \documentclass.


A LaTeX3 implementation; with \int_case:nnF the command can be more flexible, allowing for many cases in an expandable way.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
% allocate a write stream
\iow_new:N \g_lazo_output_iow
\iow_open:Nn \g_lazo_output_iow { \c_job_name_tl .txt }

% User interface
\DeclareExpandableDocumentCommand{\plural}{mmm}
 {
  \lazo_plural:nnn { #1 } { #2 } { #3 }
 }
% Inner function
\cs_new:Npn \lazo_plural:nnn #1 #2 #3
 {
  \int_case:nnF { #1 }
   { { 1 }{ #2 } }
   { #3 }
 }
% User interface
\NewDocumentCommand{\lazowrite}{m}
 {
  \lazo_write:n { #1 }
 }
% Inner function
\cs_new_protected:Npn \lazo_write:n #1
 {
  \iow_now:Nx \g_lazo_output_iow { #1 }
 }
\ExplSyntaxOff

\newcommand{\woman}[1]{#1 \plural{#1}{woman}{women}}

\begin{document}

\lazowrite{\woman{1}}
\lazowrite{\woman{2}}

\end{document}
  • But \jobname.txt reads: \protect \plural {1}{woman}{women} \protect \plural {2}{woman}{women} – Emilio Lazo Aug 6 '13 at 23:52
  • @EmilioLazo That's exactly what's expected, isn't it? – egreg Aug 7 '13 at 7:44
  • Nope, sorry... '\protect \plural {1}{woman}{women} [...]' isn't what I want in txt file. I want in txt file the output of '\woman{x}' like issuing it in the document; e.g. '\woman{1}' would put '1 woman' in txt file, like Heiko's solution, but I'm interested also in yours! – Emilio Lazo Aug 8 '13 at 15:47
  • @EmilioLazo If you want 1 woman in the text file, you need fully expandable functions. I'll add something later. – egreg Aug 8 '13 at 15:58
  • Thanks! An upgrade of the l3kernel was needed on my system. I was using 2013-07-12 and your code gave an error on \int_case:nnF but with latest LaTeX3 implementation (2013-07-28) worked like expected. Also writelatex.com needs this update! – Emilio Lazo Aug 9 '13 at 1:03

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.