21

I want to store contents of an environment and add them to a macro or command I could easily retrieve them, but I do not want to use the collect package. Are there any alternatives?

\documentclass{article}
\usepackage{environ}
\usepackage{blindtext}



\NewEnviron{env}{
\global\let\content\BODY
%i want to add content to running list and later get whole contents
}

\begin{document}
  \begin{env}
 text text text\\
 \end{env}

\blindtext


\begin{env}
  moretext moretext more text\\
\end{env}

\end{document}
5

4 Answers 4

8

You can print the saved environments by “name” or all of them.

This requires two runs (when the saved environments change), because of the usage of \label.

Update

Since etex methods are now discouraged, here's a reimplementation with expl3.

\documentclass{article}
\usepackage{refcount}

\ExplSyntaxOn

\newcounter{savedenv}
\seq_new:N \g_ephraim_savedenv_seq

\NewDocumentEnvironment{savedenv}{ o +b }
 {
  \refstepcounter{savedenv}
  \IfValueT{#1}{\label{savedenv@#1}}
  \seq_gput_right:Nn \g_ephraim_savedenv_seq { #2 }
}{}

\NewDocumentCommand{\printsaved}{m}
 {% #1 is a list of labels
  \clist_map_inline:nn { #1 }
   {
    \seq_item:Nn \g_ephraim_savedenv_seq { \getrefnumber{savedenv@##1} }
    \par
   }
 }
\NewDocumentCommand{\printallsaved}{}
 {
  \seq_use:Nn \g_ephraim_savedenv_seq { \par }
 }


\ExplSyntaxOff

\begin{document}

\begin{savedenv}[a]
Something for a
\end{savedenv}

\begin{savedenv}[b]
Something for b
\end{savedenv}

\begin{savedenv}
Something with no label
\end{savedenv}

\textbf{Let's print them}

This is a: \printsaved{a}

This is b: \printsaved{b}

\textbf{Let's print them in different order}

\printsaved{b,a}

\textbf{Print all of them}

\printallsaved

\end{document}

enter image description here

Original code

\documentclass{article}
\usepackage{etex,environ,refcount}

\globtoksblk\savedenvtoks{1000}
\newcounter{savedenvcount}

\NewEnviron{savedenv}[1][]{%
  \refstepcounter{savedenvcount}%
  \if\relax\detokenize{#1}\relax
  \else
    \label{#1}%
  \fi
  \global\toks\numexpr\savedenvtoks+\value{savedenvcount}\relax=\expandafter{\BODY}%
}
\toks\savedenvtoks={??}

\newcommand{\printsaved}[1]{%
  \the\toks\numexpr\savedenvtoks+\getrefnumber{#1}\relax
}

\makeatletter
\newcommand{\printallsaved}{%
  \@tempcnta=\z@
  \loop
    \ifnum\@tempcnta<\value{savedenvcount}
    \advance\@tempcnta\@ne
    \the\toks\numexpr\savedenvtoks+\@tempcnta\relax\par
  \repeat
}
\makeatother

\begin{document}

\begin{savedenv}[a]
Something for a
\end{savedenv}

\begin{savedenv}[b]
Something for b
\end{savedenv}

\begin{savedenv}
Something with no label
\end{savedenv}

\textbf{Let's print them}

This is a: \printsaved{a}

This is b: \printsaved{b}

\textbf{Print all of them}

\printallsaved

\end{document}

You can save up to 1000 environments, but you can change the number in the obvious way. Don't try setting the number to much more than 30000.

enter image description here

5
  • how about if i want to create multiple \globtoksblk eg \savedenvtoks1{10}, need help making savedenv to take a \globtoksblk parameter and also for printallsaved to take a parameter indicating which \globtoksblk...? Jun 13, 2014 at 7:58
  • @EphraimNcory It's not very clear. Do you want to have different classes of "saved environments"?
    – egreg
    Jun 13, 2014 at 8:00
  • yeah something like that, i want to create multiple tok blocks and be able to get their toks. Jun 13, 2014 at 8:03
  • @EphraimNcory I think you should ask a new question, with more details explaining your needs.
    – egreg
    Jun 13, 2014 at 8:05
  • i asked a new question with more details, hope you find time to help. Jun 13, 2014 at 10:12
8

I suggest to use newenviron package instead of environ, because it defines a \envnamebody command.

Note: Do not call your environment env, because it will collide with the implicit \envbody command always being defined for any environment by newenviron.

After changing to that, one can use the etoolbox - list commands \listgadd etc. and forlistloop do add the current content to the list and processing later on, e.g. printing it.

You need a 'printing' command, I named it \showlist.

The content is glued together without any spacing between at the end of the environment code.

Edited version -- glueing 'arbitrary(?)' texts together

\documentclass{article}
\usepackage{tcolorbox}
\usepackage{newenviron}
\usepackage{blindtext}
\usepackage{etoolbox}

\def\mylist{}%
\listadd{\mylist}{}% Initialize list

\newrobustcmd{\myexpandingcommand}[1]{%
\listgadd{\mylist}{#1}%
}%

\newenviron{content}{%
}{%
\noindent\textbf{\LARGE \textcolor{blue}{Environment content}}  % Remove later on!

\noindent\envbody%
\expandafter\myexpandingcommand\expandafter{\envbody}%
\endgraf\bigskip\bigskip% Can be removed
}%




% Macro showing the current list element%
\newrobustcmd{\showlist}[1]{%
#1%
}%

\begin{document}

\begin{content}  % Store some content%
\blindtext%
\end{content}

\noindent\textbf{\LARGE \textcolor{green}{Text content outside of environment}}  % Can be removed, just for diagnosis/test

\noindent\blindtext
\endgraf\bigskip

\begin{content}

\textbf{\textcolor{red}{Even more text and now even some math: \huge \(\displaystyle\int\limits^{b}_{a} f(x) dx\)}}%

\end{content}


\begin{center}
\begin{tcolorbox}[width=0.8\textwidth,title={Now the combined content of the list}]
\forlistloop{\showlist}{\mylist}%
\end{tcolorbox}%
\end{center}

\end{document}

enter image description here

5
  • 1
    The last version of environ allows for changing the macro the body is stored in.
    – egreg
    Jun 12, 2014 at 7:44
  • @egreg: I will look into it and post another solution, eventually...
    – user31729
    Jun 12, 2014 at 9:03
  • i need to create more lists which could be different but \myexpandingcommand will only work for \mylist, help. Jun 13, 2014 at 8:02
  • @EphraimNcory: I know, there is some problem with that, I will look after later on, but I had no idea, that you need more than one list, it was not stated in your question
    – user31729
    Jun 13, 2014 at 8:06
  • Unfortunately newenviron appears to be incompatible with a new version. It raises an error Extra \endgroup only by including the package (\usepackage). ____________________________________________________________________________________________ To clarify: \envnamebody doesn't mean that literally, but \<insert your environment name here>body i.e. if the environment name is test then the macro is \testbody
    – user202729
    Nov 13, 2021 at 2:14
5

I leave this answer here because they were inspirational for the creation of the scontents package. With this package it is quite easy to do what you are looking for. Everything stored in memory including verbatim content. If you wish you can also save the content in external files using the key write-env=file.tex. I am always inclined to store the content in memory:)

\documentclass{article}
\usepackage{scontents}
\pagestyle{empty}
\begin{document}
\begin{scontents}[store-env=main]
Something for main A.
\end{scontents}

\begin{scontents}[store-env=main]
Something for \verb|main B|.
\end{scontents}

\begin{scontents}[store-env=other]
Something for \verb|other|.
\end{scontents}

\textbf{Let's print them}

This is first stored in main: \getstored[1]{main}\par
This is second stored in main: \getstored[2]{main}\par
This is stored in other: \getstored[1]{other}

\textbf{Print all of stored in main}\par
\foreachsc[sep={\\[10pt]}]{main}
\end{document}

Saludos

7
  • For other uses, I think getfrom_seq internal (warning: internal, API may change any time!) function can be used to get the value of the variable. Otherwise there's still an option of extending VerbatimOut
    – user202729
    Nov 1, 2021 at 14:24
  • Remark: internally it's possible to use \tl_set:Nx \l__tmpa_tl { \__scontents_getfrom_seq:nn {1} {main} } \tl_remove_once:NV \l__tmpa_tl \c__scontents_hidden_space_str to store the raw content of scontents into some token list.
    – user202729
    Nov 2, 2021 at 2:43
  • @user202729 Do you have a specific application in mind for that? Nov 3, 2021 at 16:31
  • I was looking for some environment that stores the inside text verbatim and accessible from other programming code (expl3) to pass it to Python/shell/write to file or something -- although for LuaTeX there's also the option of using record_env or something tex.stackexchange.com/questions/38150/… , something similar is not possible for XeLaTeX/PDFLaTeX
    – user202729
    Nov 3, 2021 at 17:47
  • 1
    @user202729 I think you could take a look at filecontentsdef and create something from there, that package was the base to create scontents, of course (at first sight) Lua is the best option. Nov 4, 2021 at 20:00
3

This answer concerns collecting and storing the content of the environment verbatim. (some advantages includes executing the content with catcode equal to catcode at retrieval time, which can be done with e.g. \scantokens), or otherwise you may want to pass the selected content to Python or something similar)

Other options:


Method Tab Space Newline Final newline Letters Others
+v argument type in xparse (*) Does not work (link contains patch to workaround) space catcode \ExplSyntax-Off: char 13 cat other, \ExplSyntax-On: first character is same character as \endlinechar (inherent TeX limitation, cannot fix), remaining are same as above user specified other cat other cat
\collectverb and \Collectverb (*) \ExplSyntax-On: ignored; \ExplSyntax-Off: Start of line: stripped, otherwise: space with space catcode active space \ExplSyntax-On: active space, \ExplSyntax-Off: active char 13 user specified letter cat other cat
\collectverb* and \Collectverb* (*) \ExplSyntax-On: ignored; \ExplSyntax-Off: Start of line: stripped, otherwise: same as space → space with other cat \ExplSyntax-On: space, \ExplSyntax-Off: active char 13 user specified letter cat other cat
\collect-verbenv and \Collect-verbenv (strips first line in \ExplSyntaxOn environment) active tab (char 9) active space active newline (char 13) user specified letter cat other cat
file-contents-defmacro environment Controlled by \FCDtab-tomacro, default: same as space Active, default expand to space (space cat) Active, char 13, default expand to \par Always present letter cat other cat
scontents internal macro other cat other cat char 10 cat other Stripped letter cat other cat

(*): not really an environment

Remark:

  1. For the ones delimited with {}: {} can be used as delimiter, as long as inner {} is balanced.
  2. The line \begin{...} and \end{...} usually have to be on separate line.
  3. Most of the time, the behavior with tab/newline is "the default one defined by the implementation" if the author does not do anything special with it. (can be changed by redefining \endlinechar or setting their catcode manually etc.)
  4. What the active characters expanded to can be changed by redefining their meaning.
  5. scontents macro is an internal method, might break at any time.
  6. As mentioned in the documentation, it can be nested in most cases.
  7. \getstored is not expandable.
  8. However there's an internal method (see example code below)
\begin{scontents}[store-env=test]
\a test & test %comment

second line
third line
\end{scontents}

\ExplSyntaxOn

\tl_new:N \my__a
are stored as characters. 
\tl_set:Nx \my__a { \__scontents_getfrom_seq:nn {1} {test} }

\tl_remove_once:NV \my__a \c__scontents_hidden_space_str
\tl_analysis_show:N \my__a

\ExplSyntaxOff

  1. The environments defined by the environ package does not store the content verbatim.
  2. The environments defined by the newenviron package does not store the content verbatim. (plus another issue I mentioned in a comment Collecting contents of environment and store them for later retrieval )

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.