TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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?

share|improve this question
1  
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 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 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 at 12:45
up vote 4 down vote accepted

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...

share|improve this answer
    
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 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 at 14:40
    
@DavidCarlisle good idea! Thanks – topskip Jan 29 at 17:39

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}
share|improve this answer
1  
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. – jfbu Jan 29 at 13:27
    
@jfbu Really!? Thanks. I'll add a comment. – Andrew Jan 29 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 at 13:53

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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