# Write values to a file

I'd like to write the contents of a macro to an external file. The code below

\documentclass{article}
\begin{document}
\newcommand\foo[3]{1: #1, 2: #2 and 3: #3}

\foo{foo}{bar}{baz}

\foo{See \ref{sec}}{\begin{description}
\item[whatever] Hello
\end{description}}{Some text}

\section{A section}
\label{sec}

\end{document}


writes 1: foo, 2: bar and 3: baz and 1: See 1, 2: whatever Hello and 3: Some text into the PDF file. This should go to an external file, perhaps as XML or CSV or something else parsable. I only need the output (line breaks can be ignored) of the macro, not the source (\ref{...} for example)

I use LuaLaTeX, so I might just go ahead and parse the output nodes, but there might be a solution at the macro level?

• what you say is probably what you should do. what for example tex4ht does to get that into an html/xml markup is write \specials into the dvi and then use a dvi driver that generates xml markup from the specials and the text from the dvi, but working at that dvi level is conceptually the same as iterating over the lua node tree so you should probably self-answer as you're probably better at that than most people:-) – David Carlisle Jan 29 '16 at 12:30
• the first foo bar baz case would of course be easy to do just in tex but working out what text results from a complex environment isn't really something easy to do at that level. – David Carlisle Jan 29 '16 at 12:32
• @DavidCarlisle Thanks for the answer. I have experiemented with that a bit at the macro level, but I am such a loser at macro programming so I thought I ask here.... – topskip Jan 29 '16 at 12:45

Judging from the comments, you know this already but you can open a file with something like

\newwrite\myoutput
\immediate\openout\myoutput=\jobname.output


I have used the tex file name \jobname for the output file with an "output" extension. You can write to this file using

\write\myputout{stuff}


With your MWE, things go horribly wrong with expansion. To get around this in the past I have used an \unexpandedwrite macro that I found somewhere in TeXbook. Using this I can get your MWE to compile and produce the output:

1: foo
2: bar
and 3: baz
1: See 1\hbox {}
2: \begin {description} \item [whatever] Hello \end {description}
and 3: Some text


Depending on what you expect for #1, #2, and #3 you could of course use \write for some of them and \unexpandedwrite for others. This is what I have done below.

EDIT

@jfbu points out in the comments that there is now an \unexpanded macro so \unexpandedwrite is not needed anymore, so my MWe can be shortened to:

\documentclass{article}

\newwrite\myoutput
\immediate\openout\myoutput=\jobname.output

\newcommand\foo[3]{%
\write\myoutput{1: #1}
\write\myoutput{\unexpanded{2: #2}}
\write\myoutput{ and 3: #3}
}

\begin{document}
\foo{foo}{bar}{baz}

\foo{See \ref{sec}}{\begin{description}
\item[whatever] Hello
\end{description}}{Some text}

\section{A section}
\label{sec}
\end{document}


Here is the original code:

\documentclass{article}

%% from the TeXbook
\def\\{\let\stoken= } \\
\long\def\unexpandedwrite#1#2{\def\finwrite{\write#1}%
{\aftergroup\finwrite\aftergroup{\sanitize#2\endsanity}%
}}
\def\sanitize{\futurelet\next\sanswitch}
\def\sanswitch{\ifx\next\endsanity
\else\ifcat\noexpand\next\stoken\aftergroup\space\let\next=\eat
\else\ifcat\noexpand\next\bgroup\aftergroup{\let\next=\eat
\else\ifcat\noexpand\next\egroup\aftergroup}\let\next=\eat
\else\let\next=\copytoken\fi\fi\fi\fi \next
}
\def\eat{\afterassignment\sanitize \let\next= }
\long\def\copytoken#1{%
\ifcat\noexpand#1\relax\aftergroup\noexpand
\else\ifcat\noexpand#1\noexpand~\aftergroup\noexpand\fi\fi
\aftergroup#1\sanitize%
}
\def\endsanity\endsanity{}

\newwrite\myoutput
\immediate\openout\myoutput=\jobname.output

\newcommand\foo[3]{%
\write\myoutput{1: #1}
\unexpandedwrite\myoutput{2: #2}
\write\myoutput{ and 3: #3}
}

\begin{document}
\foo{foo}{bar}{baz}

\foo{See \ref{sec}}{\begin{description}
\item[whatever] Hello
\end{description}}{Some text}

\section{A section}
\label{sec}
\end{document}

• Nowadays, one could simply do \write\myoutput{\unexpanded{1: #1}}% etc ... with eTeX's \unexpanded which is available for many years automatically to PDFTeX, LuaTeX, XeTeX engines. – user4686 Jan 29 '16 at 13:27
• @jfbu Really!? Thanks. I'll add a comment. – Andrew Jan 29 '16 at 13:42
• Thanks a lot! This is a nice (yet to decipher) piece of code, which comes close to what I want. A bonus would be to remove the TeX commands.., – topskip Jan 29 '16 at 13:53

A quick proof of concept:

\documentclass{article}
\usepackage{atbegshi}
\AtBeginShipout{\directlua{analyzebox(tex.box["AtBeginShipoutBox"])}}
\directlua{dofile("foo.lua")}

\begin{document}
\newcommand\foo[3]{1: \directlua{starttoken(1)}#1\directlua{stoptoken(1)},%
2: \directlua{starttoken(2)}#2\directlua{stoptoken(2)}%
and 3: \directlua{starttoken(3)}#3\directlua{stoptoken(3)}}

\foo{foo}{bar}{baz}

\foo{See \ref{sec}}{\begin{description}
\item[whatever] Hellö
\end{description}}{Some text}

\section{A section}
\label{sec}

\end{document}


and foo.lua is

function starttoken(n)
local ud = node.new("whatsit","user_defined")
ud.user_id = 41564
ud.type = 100
ud.value = n  * 2
node.write(ud)
end

function stoptoken(n)
local ud = node.new("whatsit","user_defined")
ud.user_id = 41564
ud.type = 100
ud.value = n * 2 + 1
node.write(ud)
end

local hlist = node.id("hlist")
local vlist = node.id("vlist")
local glyph = node.id("glyph")
local glue = node.id("glue")
local whatsit = node.id("whatsit")
local user_defined
for k,v in pairs(node.whatsits()) do
if v == "user_defined" then
user_defined = k
end
end

local inlist = false
local charlist = {}
local charlists = {}

function analyzebox( box )
while box do
if box.id == hlist or box.id == vlist then
analyzebox(box.list)
elseif box.id == glyph and inlist then
charlist[#charlist + 1] = unicode.utf8.char(box.char)
elseif inlist and box.id == glue and #charlist > 0 and charlist[#charlist] ~= " " then
charlist[#charlist + 1] = " "
elseif box.id == whatsit and box.subtype == user_defined and box.user_id == 41564 then
local curlist
local val = box.value
if val % 2 == 0 then
inlist = true
else
inlist = false
curlist = (val - 1) / 2
if charlist[#charlist] == " " then
table.remove(charlist)
end
if curlist < 3 then
charlists[curlist] = charlist
else
texio.write_nl(string.format("%q, %q, %q",table.concat(charlists[1],""),table.concat(charlists[2],""),table.concat(charlist,"")))
charlists = {}
end
charlist = {}
end
end
box = box.next
end
end


writes

"foo", "bar", "baz"
"See 1", "whatever Hellö", "Some text"


to the output...

• The first problem I've seen is when a page break occurs »within the macro«. This will insert the page number in the output. And I should take care of ligatures... Otherwise it looks fine. – topskip Jan 29 '16 at 14:18
• do it in a minipage:) (you can throw that away and re-set it including page breaks if you need it also in the output) – David Carlisle Jan 29 '16 at 14:40