4

I have a latex document containing some problems and their solutions. I also have a macro \excludecomment{sol} which if uncommented, excludes the solution environments. (Similar command is there for prob environment.)

The general question is whether I can include a command in these macros or otherwise so that depending on whether the macro is active or not is reflected in the filename of the PDF file (e.g., by adding a _sol to the filename when the solutions are present.)

2

3 Answers 3

6

You can't change the name of the output files/the name of the .pdf file during the TeX run/LaTeX run.

The reason is:

The output files are created anew and opened for writing right at the beginning of the TeX run/LaTeX run.
(E.g., the .log file is created anew and opened for writing right after starting the tex-executable or latex-executable. E.g., the .pdf file is created anew and opened for writing when shipping out the very first page of the document. )
The output files (and thus also the .pdf file) are left open for writing until the end of the TeX run/LaTeX run.
With most computer platforms you can't change the name of a file while that file is open for writing.

But with most TeX-platforms you can run tex-binaries/executables with command line options.

One of these options is the option -jobname.

If you have the main .tex-file foo.tex and compile it by running latex from the command line as

pdflatex --jobname=bar foo.tex

, you won't get the output files foo.aux, foo.log and foo.pdf, etc, but you will get output files bar.aux, bar.log and bar.pdf, etc.

Also the primitive \jobname won't deliver the phrase foo in this case but will deliver the phrase bar.

You can't use a macro for changing the names of output files during the LaTeX run but you can go the other direction:

Create a macro which examines whether the expansion of \jobname contains some specific phrase and forks accordingly. Be aware that \jobname spits out explicit space tokens of category code 10(space) and explicit character tokens of category code 12(other).


Example:

Save the following example as test.tex :

\documentclass{article}
\usepackage{comment}

\begingroup    
\makeatletter
%%----------------------------------------------------------------------
%% Check whether argument is empty:
%%......................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\long\def\UD@CheckWhetherNull#1{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\expandafter\expandafter\@firstoftwo{ }{}%
  \@secondoftwo}{\expandafter\expandafter\@firstoftwo{ }{}\@firstoftwo}%
}%
%%----------------------------------------------------------------------
%% Check whether expansion of \jobname contains no phase "_sol"
%%......................................................................
%% \UD@CheckWhetherNoSolInJobname{<Tokens to be delivered in case that
%%                                 \jobname contains no phrase "_sol">}%
%%                               {<Tokens to be delivered in case that
%%                                 \jobname contains the phrsae "_sol">}%
\def\UD@GobbleToSol{_sol}%
\@onelevel@sanitize\UD@GobbleToSol
\edef\CheckWhetherNoSolInJobname{%
  \noexpand\expandafter\noexpand\UD@CheckWhetherNull
  \noexpand\expandafter{\noexpand\UD@GobbleToSol\jobname\UD@GobbleToSol}%
}%
\expandafter\long\expandafter\def
\expandafter\UD@GobbleToSol\expandafter#\expandafter1\UD@GobbleToSol{}%
\CheckWhetherNoSolInJobname{%
  \endgroup
  \newcommand\CheckWhetherNoSolInJobname[2]{#1}%
}{%
  \endgroup
  \newcommand\CheckWhetherNoSolInJobname[2]{#2}%
}%
%% \makeatother not needed. Instead the group is closed.
%%----------------------------------------------------------------------

\CheckWhetherNoSolInJobname{%
  \excludecomment{sol}%
}{%
  \includecomment{sol}%
}%

\begin{document}

Expansion of \verb|\jobname| is: \texttt{\jobname}

\texttt{\string\jobname} does
\CheckWhetherNoSolInJobname{%
  not%
}{%
  indeed%
}
contain the phrase \verb|_sol|.

Thus solutions
\CheckWhetherNoSolInJobname{%
  are not included%
}{%
  get included also%
}.

\bigskip

This is the question.
\begin{sol}

This is the answer.
\end{sol}

\end{document}

Compiling from the command line via pdflatex test.tex yields test.pdf which looks like this:

enter image description here

Compiling from the command line via pdflatex --jobname=test_sol test.tex yields test_sol.pdf which looks like this:

enter image description here



Another approach could be:

File test_sol.tex:

\newcommand\solutioninorexclude{%
  \includecomment{sol}%
}%
\input test.tex

File test.tex:

\documentclass{article}
\usepackage{comment}
\providecommand\solutioninorexclude{%
  \excludecomment{sol}%
}%
\solutioninorexclude    

\begin{document}

This is the question.
\begin{sol}

This is the answer.
\end{sol}

\end{document}

Compiling test_sol.tex yields test_sol.pdf:

enter image description here

Compiling test.tex yields test.pdf:

enter image description here

A variation on this approach can be having test.tex create the file test_sol.tex for you automatically in case it does not exist:

When you save the example below as test.tex and compile it, you will get a file test.pdf which contains the text without solutions. You also get a file test_sol.tex. If you compile that, it will—via \input—import test.tex in a way where sol-environments are included. Thus you get a file test_sol.pdf which does also contain solutions:

\documentclass{article}
\usepackage{comment}
\providecommand\solutioninorexclude{%
  \excludecomment{sol}%
  \includecomment{solinputfile}%
}%
\solutioninorexclude    

\begin{solinputfile}
\begingroup
\makeatletter
\IfFileExists{\jobname\string_sol.tex}{%
  \@latex@warning@no@line{File \jobname\string_sol.tex already exists. Not generating it}%
}{%
  \@latex@warning@no@line{Generating file \jobname\string_sol.tex}%
  \newwrite\solinputfile
  \immediate\openout\solinputfile\jobname\string_sol.tex\relax
  \immediate\write\solinputfile{%
    \string\newcommand\string\solutioninorexclude{\@percentchar^^J%
    \space\space\string\includecomment{sol}\@percentchar^^J%
    \space\space\string\excludecomment{solinputfile}\@percentchar^^J%
    }\@percentchar^^J%
    \string\input\space\jobname.tex%
  }%
  \immediate\closeout\solinputfile
}%
\endgroup
\end{solinputfile}

\begin{document}

This is the question.
\begin{sol}

This is the answer.
\end{sol}

\end{document}


Yet another approach could be using something like the docstrip package. The purpose of the docstrip package is copying files of tex-source-code with having comments removed. It provides means for conditional inclusion of code into the copies.

E.g. if you save the following example as test.tex and compile it, you don't get a .pdf file but you get two new text files.

%<*ignore>
\input docstrip
\nopreamble
\nopostamble
\askforoverwritetrue
\generate{\file{\jobname\string_sol.tex}{\from{\jobname.tex}{sol}}}
\generate{\file{\jobname\string_nosol.tex}{\from{\jobname.tex}{}}}
% Here you can do some \immediate\write18-calls for calling LaTeX or
% TeXify or latexmk for compiling \jobname_sol.tex and
%\jobname_nosol.tex.
% You need to have the \write18-feature enabled for this.
% Some people take enabling \write18 for a security issue.
\csname stop\endcsname
\bye
%</ignore>
\documentclass{article}
% This comment won't occur in the generated files.
% This comment won't occur in the generated files.
\begin{document}
This is the question.
%<*sol>
    
This is the answer.
%</sol>
\end{document}

One of these text files is named test_nosol.tex and its content is:

\documentclass{article}
\begin{document}
This is the question.
\end{document}

The other of these text files is named test_sol.tex and its content is:

\documentclass{article}
\begin{document}
This is the question.

This is the answer.
\end{document}
3
  • -jobname is TeX option, no LaTeX option.
    – wipet
    Commented May 11, 2020 at 10:17
  • If I do ln -s /usr/local/texlive/2020/bin/x86_64-linux/tex bobo then I can do bobo --help or bobo --jobname=foo bar.tex. Everything works (the second example only if bobo.fmt format is present). But there is no bobo-binary, only TeX binary. The "latex binary" is equivalent with "bobo binary" in this example.
    – wipet
    Commented May 11, 2020 at 15:42
  • OK, you are right. Nice day.
    – wipet
    Commented May 11, 2020 at 18:23
1

I've been dealing with the same situation for years and I just came with the following solution today after browsing TeX.stackexchange. It does not strictly answer your question but I find it easy to use as a workaround solution. I use the exam package but this should work for any package where you have the option to comment out some portions using a macro. For the exam package, the code that is optionnally included is in the solution environment. I created three files with the following scheme (but shorter names in practice):

1) main_questions_only.tex whose entire content is

\documentclass[11pt]{exam}
\noprintanswers
\input{main.tex}

2) main_questions_with_solutions.tex whose entire content is

\documentclass[11pt]{exam}
\printanswers
\input{main.tex}

3) main.tex contains everything else.

0

Essentially this question is a special case of Can TeX post-process the PDF it produces? .

LuaTeX has a callback that can be used for this.

%! TEX program = lualatex
\documentclass{article}
\usepackage{luacode}
\begin{document}

123

\begin{luacode*}
luatexbase.add_to_callback("wrapup_run", function()
    local outfile=io.open(tex.jobname.."_sol.pdf", "wb")
    outfile:write(io.open(tex.jobname..".pdf", "rb"):read("a"))
    outfile:close()
    os.remove(tex.jobname..".pdf")
end, "final callback to rename pdf file")
\end{luacode*}

\end{document}

Note that the documentation (texdoc luatex) states...

Use it at your own risk.

So, if you use this solution be prepared to debug any issue that might happen later in new compiler versions.

2
  • Also, no shell-escape needed.
    – user202729
    Commented Jun 20, 2022 at 17:15
  • It remains the readers' exercise to figure out how to only do that operation when some "flag" is set. Also, the in-file is not closed but it will be garbage-collected immediately anyway.
    – user202729
    Commented Jun 20, 2022 at 17:17

You must log in to answer this question.

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