24

In LuaLaTeX, how would I go about capturing the content of an environment for (verbatim) processing by Lua? For example, consider something like

\begin{foobar}
  Hello {World}
\end{foobar}

I'd like to be able to take the content of the environment and pass it to Lua, as in

foobar("  Hello {World}\n")

(and Lua would then eventually pass the processed version back to LaTeX, but I can presumably handle that).

1

3 Answers 3

25

Start the "recording" mode when you enter \begin{foobar} and end it when you close the environment. That way you get the pure buffer contents.

\documentclass{article}
\usepackage{luacode}
\begin{luacode*}
do 
  local mybuf = ""
  function readbuf( buf )
    mybuf = mybuf .. buf .. "\n" 
  end
  function startrecording()
    luatexbase.add_to_callback('process_input_buffer', readbuf, 'readbuf')
  end

  function stoprecording()
    luatexbase.remove_from_callback('process_input_buffer', 'readbuf')
    local buf_without_end = mybuf:gsub("\\end{foobar}\n","")
    print(string.format("Lua: %q", buf_without_end))
  end
end
\end{luacode*}
\begin{document}
  \newenvironment{foobar}{\directlua{startrecording()}}{\directlua{stoprecording()}}
\begin{foobar}
    Hello {World}
\end{foobar}

\end{document}
5
  • 2
    Amazing! Why do you include the lua code inside a closure do...end? Just best practice in lua coding?
    – cjorssen
    Dec 14, 2011 at 11:29
  • @cjorssen I define a local variable mybuf (which act as a kind of global variable for these two functions) and I don't want it to be visible outside the scope of the functions where I use it (to avoid clashes). In this case it is actually not necessary as the do / end pair is implicitly given by the luacode environment, but I like the explicit scoping here so anyone can see "hey, there is something inside that scope that must not be visible outside".
    – topskip
    Dec 14, 2011 at 11:44
  • In your example: Is it also possible to suppress the TeX output of the contents of {foobar}? I.e. read the environement contents to Lua, but hide it from TeX?
    – arney
    Mar 24, 2013 at 23:55
  • @arney I am not sure (didn't try) - did you try to return nil or an empty string from readbuf()?
    – topskip
    Mar 25, 2013 at 7:15
  • 1
    @topskip I have an improvement to your answer to ask here : tex.stackexchange.com/questions/207500/…
    – Tarass
    Oct 16, 2014 at 18:17
10

In ConTeXt, you can simply use buffers for this.

\def\startfoobar
    {\definebuffer[foobar][startfoobar][stopfoobar]}

\def\stopfoobar
    {\ctxlua{userdata.luafunction(buffers.getcontents('foobar'))}

The function userdata.luafunction will then receive the contents of the environment.

See the ConTeXt wiki for more examples.

5

A bit late to the party, but this fix to https://tex.stackexchange.com/users/243/topskip's answer does the job:

in response to question in comments:

In topskip's answer Hello World is still written to the pdf file, which is not typically what one would want. So I've taken his answer and made the change that if the line read does not contain "\end{foobar}" then nothing is returned to the tex interpreter, but if it does contain "\end{foobar}" it gets spit back (via the nil return value) to the tex interpreter which then ends the foobar environment. If the "\end{foobar}" line would not get returned then tex would never end the environment, which is clearly not what you want.

The example is still imperfect in that it doesn't give the desired result if there is text before "\end{foobar}" on the same line.

\documentclass{article}
\usepackage{luacode}
\begin{luacode*}
do 
  local mybuf = ""
  function readbuf( buf )
   print("buf=" .. buf)
    i,j = string.find(buf,"\\end{foobar}")
    print(i,j)
    if i==nil then
     mybuf = mybuf .. buf .. "\n" 
     print("[" .. mybuf .. "]")
     return ""
    else 
    return nil
    end
   end

  function startrecording()
    luatexbase.add_to_callback('process_input_buffer', readbuf, 'readbuf')
  end

  function stoprecording()
    luatexbase.remove_from_callback('process_input_buffer', 'readbuf')
    local buf_without_end = mybuf:gsub("\\end{foobar}\n","")
    print(string.format("Lua: %s", buf_without_end))
  end
end
\end{luacode*}

\begin{document}
  \newenvironment{foobar}{\directlua{startrecording()}}{\directlua{stoprecording()}}
\begin{foobar}
    Hello {World}
\end{foobar}

\end{document}
0

You must log in to answer this question.

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