How to build two different .tex files from same .tex file

Update.

Added support to leave the empty environment in the copy, removing only its contents, or to remove also the \begin...\end pair (by default).


I programmed a LuaLaTeX solution and tried to make it flexible enough. These are the files which compose the solution:

remove-env.lua

-- remove-env.lua
omittedEnvironments = {}
omitFileSuffix = "-without"
leaveEmptyEnvs = false

function shouldOmit(line)
  for i,v in ipairs(omittedEnvironments) do
    if (string.find(line, "\\begin{"..v.."}")~=nil) then
       return true
    end
  end
  return false
end

function shouldResume(line)
  for i,v in ipairs(omittedEnvironments) do
    if (string.find(line, "\\end{"..v.."}")~=nil) then
       return true
    end
  end
  return false
end

function dumpfile()
    myout = io.open(tex.jobname..omitFileSuffix..".tex", "w")
    myin = io.open(tex.jobname..".tex", "r")
    omitting = false
    for line in myin:lines() do
      if (not omitting and shouldOmit(line)) then
          if (leaveEmptyEnvs) then myout:write(line.."\n") end
          omitting = true
      end
      if (not omitting) then
         myout:write(line.."\n")
      end
      if (omitting and shouldResume(line)) then
          if (leaveEmptyEnvs) then myout:write(line.."\n") end
          omitting = false
      end
    end
    myout:close()
    myin:close()
end

remove-env.tex

\directlua{dofile("remove-env.lua")}
\def\omitEnvironment#1{\directlua{table.insert(omittedEnvironments, "#1")}}
\def\omitFileSuffix#1{\directlua{omitFileSuffix="#1"}}
\def\leaveEmptyEnvs{\directlua{leaveEmptyEnvs=true}}
\def\removeEmptyEnvs{\directlua{leaveEmptyEnvs=false}}
\AtEndDocument{\directlua{dumpfile()}}

MWE.tex

\input remove-env
\documentclass{article}
\usepackage{fontspec}
\usepackage{lipsum}
\newenvironment{solution}{}{}
\omitEnvironment{solution}
\omitFileSuffix{-sans-sol}

\begin{document}\parindent0pt\parskip1em
  1. \lipsum[1]\hrulefill\par
  \begin{solution}
  2. \lipsum[2]\hrulefill\par
  \end{solution}
  3. \lipsum[3]\hrulefill\par
\end{document}

This MWE defines a no-op solution environment which acts simply as markup, but of course you can define it in a way that produces some effect in the pdf. Macro \omitEnvironment specifies the environment you want to omit. You can use this macro several times to specify several environments, and all of them will be omitted. Macro \omitFileSuffix specifies the suffix that will be appended to the output filename.

Run:

$ lualatex MWE.tex

And you will get two files (and all the usual auxiliar files, of course):

  • MWE.pdf will be generated as usually, and all the contents (including omitted environments) will be present.
  • MWE-sans-sol.tex is a copy of MWE.tex in which all solution environments are removed.
$ diff MWE.tex MWE-sans-sol.tex
11,13d10
<   \begin{solution}
<   2. \lipsum[2]\hrulefill\par
<   \end{solution}

If you want to remove only the contents of the solution but leave the empty environment, you only have to specify \leaveEmptyEnvs at some point of MWE.tex. In this case the diff will show:

$ diff MWE.tex MWE-sans-sol.tex
12d11
<   2. \lipsum[2]\hrulefill\par

PS: Thanks to Scott H. who suggested me not to use luatex callbacks, which was my first (and too convoluted) approach


A TeX only solution; it assumes that you don't have any other environment whose name starts with the string soluti other than solution.

Prepare the following extract.tex file:

\newread\texfileread
\newwrite\texfilewrite
\openin\texfileread=ignasimain.tex % put here the main file name
\immediate\openout\texfilewrite=ignasistudents.tex % put here the secondary file name

\edef\BEGINSOLUTI{\string\begin\string{soluti}
\edef\ENDSOLUTION{\string\end\string{solution}
\newif\ifwritesolution
\writesolutiontrue

\long\def\ignasidecide#1#2#3#4#5#6#7#8#9\relax{%
  \def\temp{#1#2#3#4#5#6#7#8}%
  \ignasidecideaux#9}
\long\def\ignasidecideaux#1#2#3#4#5#6\relax{%
  \ifnum\pdfstrcmp{\temp#1#2#3#4#5}{\BEGINSOLUTI}=0
    \immediate\write\texfilewrite{\ignasiline^^J}
    \writesolutionfalse
  \else
    \ifnum\pdfstrcmp{\temp#1#2#3#4#5}{\ENDSOLUTION}=0
      \writesolutiontrue
    \fi
  \fi
  \ifwritesolution
    \immediate\write\texfilewrite{\ignasiline}
  \fi
}

\endlinechar=-1 \newlinechar=`\^^J
\loop\unless\ifeof\texfileread%
\readline\texfileread to \ignasiline%
\expandafter\ignasidecide\ignasiline%
  \relax\relax\relax\relax%
  \relax\relax\relax\relax%
  \relax\relax\relax\relax%
  \relax\relax%
\repeat%
\csname bye\endcsname%
\csname @@end\endcsname

Change the file names as desired. Put this file along with the main file and compile with pdftex or pdflatex (it's the same).

With your example file, the resulting file will be

% This is main.tex file
\documentclass{article}
\begin{document}
\begin{exercise}
This is the first exercise
\begin{solution}

\end{solution}
\end{exercise}
\end{document}

Basically we read the main file line by line (ignoring category codes) with \readline; if the line starts with

\begin{soluti

then we write out the line with a blank line following it and set a conditional to false; if the line starts with

\end{solution

then the conditional is set again to true. The current line is written out if the conditional is true.


I had the same problem. I ended up using docstrip.

The system I set up produces:

  • (i) separate tex and pdf files for me (with solutions) and the students (without solutions:),

  • (ii) several versions of the exercise, for grouping students, and

  • (iii) typesets the only selected topic(s), allowing me to keep all the exercises in the same file.

Here's the outline of the main file containing exercises and solutions:

% This is exercises.tex
\documentclass{article}
\newenvironment{exercise}{}{}
\newenvironment{solution}{}{}

\title{Subject\\\normalsize homework topic:
%<topic1>topic 1
%<topic2>topic 2
}
\author{}

\begin{document}
\maketitle

%<*topic1>
\begin{exercise}
  Exercise text (topic 1)
%<A>  Version for group A,
%<B>  Version for group B,
%<C>  Version for group C,
%<D>  Version for group D.
  More exercise text.
\end{exercise}
%<*!student>
\begin{solution}
  Solution, visible only in my copy:
%<A> for group A,
%<B> for group B,
%<C> for group C,
%<D> for group D.
\end{solution}
%</!student>

%<*student>
% Template for student's answer.
\begin{solution}

\end{solution}
%</student>

%</topic1>



%<*topic2>
\begin{exercise}
  Exercise text (topic 1)
%<A>  Version for group A,
%<B>  Version for group B,
%<C>  Version for group C,
%<D>  Version for group D.
  More exercise text.
\end{exercise}
%<*!student>
\begin{solution}
  Solution, visible only in my copy:
%<A> for group A,
%<B> for group B,
%<C> for group C,
%<D> for group D.
\end{solution}
%</!student>

%<*student>
% Template for student's answer.
\begin{solution}

\end{solution}
%</student>

%</topic2>

\end{document}

And this is the .ins file for topic1:

% This is exercises.ins
\input docstrip
\nopreamble\nopostamble
\askforoverwritefalse
\generate{%
  \file{exercises-topic1-A.tex}{\from{exercises.tex}{student,topic1,A}}%
  \file{exercises-topic1-B.tex}{\from{exercises.tex}{student,topic1,B}}%
  \file{exercises-topic1-C.tex}{\from{exercises.tex}{student,topic1,C}}%
  \file{exercises-topic1-D.tex}{\from{exercises.tex}{student,topic1,D}}%
  \file{exercises-topic1-ME.tex}{\from{exercises.tex}{topic1,A,B,C,D}}%
  %\file{exercises-topic2-A.tex}{\from{exercises.tex}{student,topic2,A}}%
  %\file{exercises-topic2-B.tex}{\from{exercises.tex}{student,topic2,B}}%
  %\file{exercises-topic2-C.tex}{\from{exercises.tex}{student,topic2,C}}%
  %\file{exercises-topic2-D.tex}{\from{exercises.tex}{student,topic2,D}}%
  %\file{exercises-topic2-ME.tex}{\from{exercises.tex}{topic2,A,B,C,D}}%
}
\endbatchfile

Finally, a very badly written makefile --- nothing more than a camouflaged bash script, really. (I guess that ideally, running make should produce the .ins file automatically ... ahh, some day...)

all:
    pdftex exercises.ins
    bash -c 'for I in {A,B,C,D,ME}; do pdflatex exercises-topic1-$$I ; done'
    #bash -c 'for I in {A,B,C,D,ME}; do pdflatex exercises-topic2-$$I ; done'