Saving the body of an environment to a file verbatim using xparse

environmentslatex3verbatimxparse

Variations of this question have been asked before, but those answers don't seem to work when used within \NewDocumentEnvironment from xparse. For example, see
Write environment body verbatim to a file

The minimal goal is to save the body of the environment verbatim to a file, and replace the body with some other file. Reading in the replacement file is easy, but saving the body is hard.

\documentclass[10pt]{article}

\ExplSyntaxOn

% I'm using xparse because I'm doing messy things with the arguments. That part works.
\NewDocumentEnvironment{intfig} { > { \SplitArgument { 1 } { , } } m o }
{
  \group_begin:

  \str_set:Nn \l_intfig_fout_str { test.out }
  \str_set:Nn \l_intfig_fin_str { test.in }

  % Here, I want to save the body to test.out.
  % I tried variations with \VerbatimOut and \endVerbatimOut, along
  % with elements of the listings package (\lst@BeginWriteFile).

  % Reading in the replacement works.
  \file_if_exist:nTF { \l_intfig_fin_str } { \input { \l_intfig_fin_str }}{}

  \group_end:
}{
}

\ExplSyntaxOff

\begin{document}

\begin{intfig}{junk,14.4pt}[2cm,rubbish]
  Send me to an external file, you !#^%?

  Don't mess with anything in here either. I want

  every            space, semi-colon;;; etc.
\end{intfig}

\end{document}

@Phelype says that doing this is currently impossible, and links to some information about the current status.

Is there a way to wrap several lines in an environment so that they are passed to the containing environment as a verbatim body? Something like this

\begin{intfig}{junk,14.4pt}[2cm,rubbish]
 \begin{makeintoverbatim}
  Send me to an external file, you !#^%?

  Don't mess with anything in here either. I want

  every            space, semi-colon;;; etc.
  \end{makeintoverbatim}
\end{intfig}

The idea is that this would pass what is in makeintoverbatim to \NewDocumentEnvironment unmolested. If that were possible, then using b (or b+) might work when defining the intfig environment.

Best Answer

The xsimverb package seems to provide what you want:

\documentclass[10pt]{article}

\usepackage{xsimverb,listings}

\ExplSyntaxOn
\cs_generate_variant:Nn \file_if_exist:nT {V}
\cs_generate_variant:Nn \file_input:n {V}

\str_new:N \l_intfig_fout_str
\str_new:N \l_intfig_fin_str

\NewDocumentEnvironment{intfig}{ >{ \SplitArgument {1} {,} } m!o }
  {
    \str_set:Nn \l_intfig_fout_str {test.out}
    \str_set:Nn \l_intfig_fin_str  {test.in}
    \IfValueTF {#2}
      { \xsim_file_write_start:nn { \c_true_bool } }
      { \xsim_file_write_start:nn { \c_false_bool } }
    { \l_intfig_fout_str }
  }
  {
    \xsim_file_write_stop:
    \file_if_exist:VT \l_intfig_fin_str { \file_input:V \l_intfig_fin_str }
  }
\ExplSyntaxOff

\begin{document}

\begin{intfig}{junk,14.4pt}[2cm,rubbish]
  Send me to an external file, you !#^%?

  Don't mess with anything in here either. I want

  every            space, semi-colon;;; etc.
\end{intfig}
\lstset{basicstyle=\ttfamily,showspaces=true}
\lstinputlisting{test.out}

\begin{intfig}{junk,14.4pt}
  another test
\end{intfig}
\lstinputlisting{test.out}

\end{document}

enter image description here

Some remarks:

The first argument to \xsim_file_write_start:nn is a boolean which denotes if an optional argument follows or not (only important if the optional argument is the last argument of the environment) and the second the file name to write the contents to.

Also note that then the optional argument should be specified as !o as otherwise the leading spaces of the first line are gobbled if no optional argument is given. See the section about optional arguments and spaces in the xparse manual and check the difference in the output if you leave the ! away.


The commands \xsim_file_write_start:nn and \xsim_file_write_stop: may not be documented (yet?) but are public and can be used. Their 2e counterparts \XSIMfilewritestart and \XSIMfilewritestop are documented.