在 NewDocumentEnvironment 中插入逐字内容

在 NewDocumentEnvironment 中插入逐字内容

我在一个环境中工作,用于设置具有零个或多个相关提示的练习。如果我尝试在练习主体中插入逐字内容,则 Latex 解释器会挂起而不指定错误。此处显示了一个重现此问题的最小示例:

\documentclass{article}
\usepackage{listings}

\ExplSyntaxOn
% Create the exercise environment
\NewDocumentEnvironment{ex}{O{}+b}{%
\par\noindent
#2}{%
% End exercise
}
\ExplSyntaxOff

\begin{document}
\section{First section}

\begin{ex}
Create a new virtual working environment for python
% When the lstlisting environment below is uncommented
% latex hangs with this information in the console:
% Package Listings Warning: Text dropped after begin of listing on input line 24.
% 
% 
% (/home/henrik/.TinyTeX/texmf-dist/tex/latex/base/omscmr.fd))
% *
%\begin{lstlisting}
%pipenv install opencv-python
%\end{lstlisting}
\end{ex}

\end{document}

正如我解释这个答案时所说 https://tex.stackexchange.com/a/489459/1366 大卫·卡莱尔 (David Carlisle) 或许无法实现我的愿望。

我希望在最终版本中使用的语法如下

\begin{ex}
Description of the exercise.
Run the following command
%\begin{verbatim}
%print("Hello world")
%\end{verbatim}
\begin{hint}
Run the example from the command line with the python command
\end{hint}
\begin{hint}
The solution is 42.
\end{hint}
\end{ex}

解析此内容时,应包含一个标题,其中显示练习的编号并包含指向所包含提示(在本例中为两个)的链接,这些提示稍后插入到文档中。为此,我必须解析 ex 环境的内容/主体。输出应如下所示,生成此输出的代码包含在问题的末尾。

示例输出

\documentclass{article}
\usepackage{amsmath}
\usepackage[colorlinks, linkcolor=blue, citecolor=blue, urlcolor=blue]{hyperref}

\newcounter{ex}
\numberwithin{ex}{section}

\newcounter{hint}
\numberwithin{hint}{ex}

\newcounter{solution}
\numberwithin{solution}{ex}

\makeatletter
\newcommand{\linkdest}[1]{\raisebox{1.7\baselineskip}[0pt][0pt]{\hypertarget{#1}{}}}
\makeatother

\ExplSyntaxOn
% Define variables for storing the number of hints 
% and solutions given in the exercise.
\int_new:N \l_hintenv_int
\int_new:N \l_solenv_int

% Open files for storing hints and solutions.
\iow_new:N \g_hintfile_iow
\iow_new:N \g_solutionfile_iow
\iow_open:Nn \g_hintfile_iow {hintfile.tex}
\iow_open:Nn \g_solutionfile_iow {solutionfile.tex}

% Define strings to use in macros.
\tl_new:N \g_text_solution_tl
\tl_set:Nn \g_text_solution_tl { ~Solution:~ }
\tl_new:N \g_text_solution_head_tl
\tl_set:Nn \g_text_solution_head_tl { Solutino }
\tl_new:N \g_text_hint_tl
\tl_set:Nn \g_text_hint_tl { ~Hint:~ }
\tl_new:N \g_text_exercise_tl
\tl_set:Nn \g_text_exercise_tl { Exercise~ }
\tl_new:N \g_back_to_exercise_tl
\tl_set:Nn \g_back_to_exercise_tl { Back~to~exercise~ }

% Create the exercise environment
\NewDocumentEnvironment{ex}{O{}+b}{%
% Start exercise
\bigbreak
\refstepcounter{ex}
\label{exercise\theex}
\noindent
\textbf{\g_text_exercise_tl\theex{}:~#1}
\hfill 

% Run a regular expression on the body of the 
% exercise to count the number of hints present
% and store that number in a variable.
\regex_count:nnN {\c{begin}\{hint\}} {#2} \l_hintenv_int
\regex_count:nnN {\c{begin}\{sol\}} {#2} \l_solenv_int

% If at least one hint is provided start a list with 
% links to the inserted hints.
\int_compare:nTF { \l_hintenv_int > 0 } { \g_text_hint_tl } {  }
% For all integers in the range from one to 
% the number of inserted hints do.
\int_step_variable:nNn {\l_hintenv_int} \l_iterator_tl{
    \int_compare:nTF { \l_iterator_tl > 1 } { ,~ } {  }
    \hyperlink{hint\theex.\l_iterator_tl}{\l_iterator_tl}
}

% If at least one solution is provided start a list with 
% links to the inserted solutions.
\int_compare:nTF { \l_solenv_int > 0 } { \g_text_solution_tl } {  }
% For all integers in the range from one to 
% the number of inserted solutions do.
\int_step_variable:nNn {\l_solenv_int} \l_iterator_tl{
    \int_compare:nTF { \l_iterator_tl > 1 } { ,~ } {  }
    \hyperlink{solution\theex.\l_iterator_tl}{\l_iterator_tl}
}
\par\noindent
#2}{%
% End exercise
}


\NewDocumentEnvironment{hint}{O{}+b}{%
% hint start
\refstepcounter{hint}
\tl_set:Nx \l_temp_tl { hint\thehint }
\iow_now:Nx \g_hintfile_iow { \par\noindent}
\iow_now:Nx \g_hintfile_iow { \exp_not:N \textbf{Hint~\arabic{hint}~to~exercise~\theex}}
\iow_now:Nx \g_hintfile_iow { \hfill \g_back_to_exercise_tl }
\iow_now:Nx \g_hintfile_iow { \exp_not:N \ref{exercise\theex } }
\iow_now:Nx \g_hintfile_iow { \par\noindent}
\iow_now:Nx \g_hintfile_iow { \exp_not:N \linkdest{ \l_temp_tl } }
\iow_now:Nx \g_hintfile_iow { \exp_not:N \vspace{-0.4cm}\par\noindent}
\iow_now:Nn \g_hintfile_iow { #2 }
\iow_now:Nn \g_hintfile_iow { \bigskip}
\iow_now:Nn \g_hintfile_iow { \filbreak}
}{
% hint end
}

\NewDocumentEnvironment{sol}{O{}+b}{
% hint start
\refstepcounter{solution}
\tl_set:Nx \l_temp_tl { solution\thesolution }
\iow_now:Nx \g_solutionfile_iow { \par\noindent }
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \textbf{Solution~\arabic{solution}~to~exercise~\theex}}
\iow_now:Nx \g_solutionfile_iow { \hfill \g_back_to_exercise_tl }
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \ref{exercise\theex } }
\iow_now:Nx \g_solutionfile_iow { \par\noindent}
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \linkdest{ \l_temp_tl } }
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \vspace{-0.4cm}\par\noindent}
\iow_now:Nn \g_solutionfile_iow { #2 }
\iow_now:Nn \g_solutionfile_iow { \bigskip}
\iow_now:Nn \g_solutionfile_iow { \filbreak}
}{
% hint end
}

% Define command for closing the two files used 
% for storing hints and solutions.
\NewDocumentCommand{\closehintandsolutionfile}{}{
\iow_close:N \g_hintfile_iow
\iow_close:N \g_solutionfile_iow
}

\ExplSyntaxOff



\begin{document}
\section{Exercises}

\begin{ex}
Description of the exercise.
Run the following command
%\begin{verbatim}
%print("Hello world")
%\end{verbatim}
\begin{hint}
Run the example from the command line with the python command
\end{hint}
\begin{hint}
The solution is 42.
\end{hint}
\end{ex}

\closehintandsolutionfile

\section{Hints}
\input{hintfile.tex}


\end{document}

答案1

如果其他方法都失败了,您可以在进入环境之前将逐字记录放入并保存到一个盒子中ex

\documentclass{article}
\usepackage{amsmath}
\usepackage[colorlinks, linkcolor=blue, citecolor=blue, urlcolor=blue]{hyperref}

\newcounter{ex}
\numberwithin{ex}{section}

\newcounter{hint}
\numberwithin{hint}{ex}

\newcounter{solution}
\numberwithin{solution}{ex}

\makeatletter
\newcommand{\linkdest}[1]{\raisebox{1.7\baselineskip}[0pt][0pt]{\hypertarget{#1}{}}}
\makeatother

\ExplSyntaxOn
% Define variables for storing the number of hints 
% and solutions given in the exercise.
\int_new:N \l_hintenv_int
\int_new:N \l_solenv_int

% Open files for storing hints and solutions.
\iow_new:N \g_hintfile_iow
\iow_new:N \g_solutionfile_iow
\iow_open:Nn \g_hintfile_iow {hintfile.tex}
\iow_open:Nn \g_solutionfile_iow {solutionfile.tex}

% Define strings to use in macros.
\tl_new:N \g_text_solution_tl
\tl_set:Nn \g_text_solution_tl { ~Solution:~ }
\tl_new:N \g_text_solution_head_tl
\tl_set:Nn \g_text_solution_head_tl { Solutino }
\tl_new:N \g_text_hint_tl
\tl_set:Nn \g_text_hint_tl { ~Hint:~ }
\tl_new:N \g_text_exercise_tl
\tl_set:Nn \g_text_exercise_tl { Exercise~ }
\tl_new:N \g_back_to_exercise_tl
\tl_set:Nn \g_back_to_exercise_tl { Back~to~exercise~ }

% Create the exercise environment
\NewDocumentEnvironment{ex}{O{}+b}{%
% Start exercise
\bigbreak
\refstepcounter{ex}
\label{exercise\theex}
\noindent
\textbf{\g_text_exercise_tl\theex{}:~#1}
\hfill 

% Run a regular expression on the body of the 
% exercise to count the number of hints present
% and store that number in a variable.
\regex_count:nnN {\c{begin}\{hint\}} {#2} \l_hintenv_int
\regex_count:nnN {\c{begin}\{sol\}} {#2} \l_solenv_int

% If at least one hint is provided start a list with 
% links to the inserted hints.
\int_compare:nTF { \l_hintenv_int > 0 } { \g_text_hint_tl } {  }
% For all integers in the range from one to 
% the number of inserted hints do.
\int_step_variable:nNn {\l_hintenv_int} \l_iterator_tl{
    \int_compare:nTF { \l_iterator_tl > 1 } { ,~ } {  }
    \hyperlink{hint\theex.\l_iterator_tl}{\l_iterator_tl}
}

% If at least one solution is provided start a list with 
% links to the inserted solutions.
\int_compare:nTF { \l_solenv_int > 0 } { \g_text_solution_tl } {  }
% For all integers in the range from one to 
% the number of inserted solutions do.
\int_step_variable:nNn {\l_solenv_int} \l_iterator_tl{
    \int_compare:nTF { \l_iterator_tl > 1 } { ,~ } {  }
    \hyperlink{solution\theex.\l_iterator_tl}{\l_iterator_tl}
}
\par\noindent
#2}{%
% End exercise
}


\NewDocumentEnvironment{hint}{O{}+b}{%
% hint start
\refstepcounter{hint}
\tl_set:Nx \l_temp_tl { hint\thehint }
\iow_now:Nx \g_hintfile_iow { \par\noindent}
\iow_now:Nx \g_hintfile_iow { \exp_not:N \textbf{Hint~\arabic{hint}~to~exercise~\theex}}
\iow_now:Nx \g_hintfile_iow { \hfill \g_back_to_exercise_tl }
\iow_now:Nx \g_hintfile_iow { \exp_not:N \ref{exercise\theex } }
\iow_now:Nx \g_hintfile_iow { \par\noindent}
\iow_now:Nx \g_hintfile_iow { \exp_not:N \linkdest{ \l_temp_tl } }
\iow_now:Nx \g_hintfile_iow { \exp_not:N \vspace{-0.4cm}\par\noindent}
\iow_now:Nn \g_hintfile_iow { #2 }
\iow_now:Nn \g_hintfile_iow { \bigskip}
\iow_now:Nn \g_hintfile_iow { \filbreak}
}{
% hint end
}

\NewDocumentEnvironment{sol}{O{}+b}{
% hint start
\refstepcounter{solution}
\tl_set:Nx \l_temp_tl { solution\thesolution }
\iow_now:Nx \g_solutionfile_iow { \par\noindent }
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \textbf{Solution~\arabic{solution}~to~exercise~\theex}}
\iow_now:Nx \g_solutionfile_iow { \hfill \g_back_to_exercise_tl }
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \ref{exercise\theex } }
\iow_now:Nx \g_solutionfile_iow { \par\noindent}
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \linkdest{ \l_temp_tl } }
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \vspace{-0.4cm}\par\noindent}
\iow_now:Nn \g_solutionfile_iow { #2 }
\iow_now:Nn \g_solutionfile_iow { \bigskip}
\iow_now:Nn \g_solutionfile_iow { \filbreak}
}{
% hint end
}

% Define command for closing the two files used 
% for storing hints and solutions.
\NewDocumentCommand{\closehintandsolutionfile}{}{
\iow_close:N \g_hintfile_iow
\iow_close:N \g_solutionfile_iow
}

\ExplSyntaxOff

\usepackage{verbatimbox}

\begin{document}
\section{Exercises}

\begin{myverbbox}{\hw}
print("Hello world")
Verbatim &^%$&\content
\end{myverbbox}
\begin{ex}
Description of the exercise.
Run the following command

\smallskip\noindent\hw

\begin{hint}
Run the example from the command line with the python command
\end{hint}
\begin{hint}
The solution is 42.
\end{hint}
\end{ex}

\closehintandsolutionfile

\section{Hints}
\input{hintfile.tex}


\end{document}

在此处输入图片描述

答案2

如果有LuaTeX,您可以在Lua端存储和处理逐字内容,这有助于避免这个问题。

\documentclass{article}
\usepackage{amsmath}
\usepackage{luacode}
\usepackage{expl3, xparse}
\usepackage[colorlinks, linkcolor=blue, citecolor=blue, urlcolor=blue]{hyperref}

\newcounter{ex}
\numberwithin{ex}{section}

\newcounter{hint}
\numberwithin{hint}{ex}

\newcounter{solution}
\numberwithin{solution}{ex}

\makeatletter
\newcommand{\linkdest}[1]{\raisebox{1.7\baselineskip}[0pt][0pt]{\hypertarget{#1}{}}}
\makeatother

\begin{luacode*}
verb_table = {}

function store_lines (str)
  texio.write_nl("line:"..str)
  if string.find (str , [[\end{ex}]] ) then
    luatexbase.remove_from_callback (
      "process_input_buffer" , "store_lines")
    return [[\end{ex}]]
  else
    if str[1] ~= "%" then
      table.insert(verb_table, str)
    end
  end
  return ""
end

function register_verbatim()
  verb_table = {}
  luatexbase.add_to_callback(
    "process_input_buffer" , store_lines , "store_lines")
end
\end{luacode*}

\ExplSyntaxOn

\newcommand{\CurVerbatim}{}

\newcommand{\BeginEx}{
  \directlua{
    register_verbatim()
  }
}

% Define variables for storing the number of hints 
% and solutions given in the exercise.
\int_new:N \l_hintenv_int
\int_new:N \l_solenv_int

% Open files for storing hints and solutions.
\iow_new:N \g_hintfile_iow
\iow_new:N \g_solutionfile_iow
\iow_open:Nn \g_hintfile_iow {hintfile.tex}
\iow_open:Nn \g_solutionfile_iow {solutionfile.tex}

% Define strings to use in macros.
\tl_new:N \g_text_solution_tl
\tl_set:Nn \g_text_solution_tl { ~Solution:~ }
\tl_new:N \g_text_solution_head_tl
\tl_set:Nn \g_text_solution_head_tl { Solutino }
\tl_new:N \g_text_hint_tl
\tl_set:Nn \g_text_hint_tl { ~Hint:~ }
\tl_new:N \g_text_exercise_tl
\tl_set:Nn \g_text_exercise_tl { Exercise~ }
\tl_new:N \g_back_to_exercise_tl
\tl_set:Nn \g_back_to_exercise_tl { Back~to~exercise~ }

\cs_generate_variant:Nn \regex_count:nnN {nVN}

% Create the exercise environment
\NewDocumentEnvironment{ex}{O{}}{%
% Begin excercise
% capture verbatim on Lua side
\BeginEx
}{%
% End excercise
% retreive the content from lua side
% save it in \CurVerbatim
\directlua{
  token.set_macro("CurVerbatim", table.concat(verb_table, "~"))
}

\bigbreak
\refstepcounter{ex}
\label{exercise\theex}
\noindent
\textbf{\g_text_exercise_tl\theex{}:~#1}
\hfill 

% Run a regular expression on the body of the 
% exercise to count the number of hints present
% and store that number in a variable.
\regex_count:nVN {\c{begin}\{hint\}} \CurVerbatim \l_hintenv_int
\regex_count:nVN {\c{begin}\{sol\}} \CurVerbatim \l_solenv_int

% If at least one hint is provided start a list with 
% links to the inserted hints.
\int_compare:nTF { \l_hintenv_int > 0 } { \g_text_hint_tl } {  }
% For all integers in the range from one to 
% the number of inserted hints do.
\int_step_variable:nNn {\l_hintenv_int} \l_iterator_tl{
    \int_compare:nTF { \l_iterator_tl > 1 } { ,~ } {  }
    \hyperlink{hint\theex.\l_iterator_tl}{\l_iterator_tl}
}

% If at least one solution is provided start a list with 
% links to the inserted solutions.
\int_compare:nTF { \l_solenv_int > 0 } { \g_text_solution_tl } {  }
% For all integers in the range from one to 
% the number of inserted solutions do.
\int_step_variable:nNn {\l_solenv_int} \l_iterator_tl{
    \int_compare:nTF { \l_iterator_tl > 1 } { ,~ } {  }
    \hyperlink{solution\theex.\l_iterator_tl}{\l_iterator_tl}
}
\par\noindent
% write verbatim content out and read it back
\directlua{
  local~verb = table.concat(verb_table, "\string\n")
  local~file = io.open(tex.jobname..".tmp", "w")
  file:write(verb)
  file:close()
}
\input{\jobname.tmp}
}


\NewDocumentEnvironment{hint}{O{}+b}{%
% hint start
\refstepcounter{hint}
\tl_set:Nx \l_temp_tl { hint\thehint }
\iow_now:Nx \g_hintfile_iow { \par\noindent}
\iow_now:Nx \g_hintfile_iow { \exp_not:N \textbf{Hint~\arabic{hint}~to~exercise~\theex}}
\iow_now:Nx \g_hintfile_iow { \hfill \g_back_to_exercise_tl }
\iow_now:Nx \g_hintfile_iow { \exp_not:N \ref{exercise\theex } }
\iow_now:Nx \g_hintfile_iow { \par\noindent}
\iow_now:Nx \g_hintfile_iow { \exp_not:N \linkdest{ \l_temp_tl } }
\iow_now:Nx \g_hintfile_iow { \exp_not:N \vspace{-0.4cm}\par\noindent}
\iow_now:Nn \g_hintfile_iow { #2 }
\iow_now:Nn \g_hintfile_iow { \bigskip}
\iow_now:Nn \g_hintfile_iow { \filbreak}
}{
% hint end
}

\NewDocumentEnvironment{sol}{O{}+b}{
% hint start
\refstepcounter{solution}
\tl_set:Nx \l_temp_tl { solution\thesolution }
\iow_now:Nx \g_solutionfile_iow { \par\noindent }
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \textbf{Solution~\arabic{solution}~to~exercise~\theex}}
\iow_now:Nx \g_solutionfile_iow { \hfill \g_back_to_exercise_tl }
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \ref{exercise\theex } }
\iow_now:Nx \g_solutionfile_iow { \par\noindent}
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \linkdest{ \l_temp_tl } }
\iow_now:Nx \g_solutionfile_iow { \exp_not:N \vspace{-0.4cm}\par\noindent}
\iow_now:Nn \g_solutionfile_iow { #2 }
\iow_now:Nn \g_solutionfile_iow { \bigskip}
\iow_now:Nn \g_solutionfile_iow { \filbreak}
}{
% hint end
}

% Define command for closing the two files used 
% for storing hints and solutions.
\NewDocumentCommand{\closehintandsolutionfile}{}{
\iow_close:N \g_hintfile_iow
\iow_close:N \g_solutionfile_iow
}

\ExplSyntaxOff



\begin{document}
\section{Exercises}

\begin{ex}
Description of the exercise.
Run the following command
\begin{verbatim}
print("Hello world")
\end{verbatim} % need one blank line after verbatim, otherwise excaptions occur

\begin{hint}
Run the example from the command line with the python command
\end{hint}
\begin{hint}
The solution is 42.
\end{hint}
\end{ex}

\closehintandsolutionfile

\section{Hints}
\input{hintfile.tex}

\end{document}

相关内容