在逐字标记列表中进行搜索和替换

在逐字标记列表中进行搜索和替换

我维护一个 R 编程语言包,其中包含一个使用 Sweave 文学编程系统进行处理的插图。现在,Sweave 的一个烦恼是它总是产生包裹在两层环境中的输出,如下所示:

\begin{Schunk}
\begin{Sinput}
> Some R command
\end{Sinput}
\begin{Soutput}
The result
\end{Soutput}
\end{Schunk}

现在,我想通过抛弃 , 命令将其折叠成仅使用单个环境的东西\begin\end或者,可以选择将它们替换为listings包可以通过定义挂接的分隔符moredelim

\begin{Schunk}
> Some R command
swe@veSt@rtOutput
The result
swe@veEndOutput
\end{Schunk}

我目前通过一种黑客技术来实现这种效果,即在 Sweave 加载文件时重写 R 输出驱动程序.Rnw。然而,事实证明这是一种维护负担,因为驱动程序的结构会随着每个 R 版本的发布而发生变化,我必须更新这种黑客技术。所以,我正在寻找一种从 TeX 端解决这个问题的方法。

下面是一些 Sweave 输出的最小示例以及我当前将其包装到列表中的方法:

\documentclass{minimal}
\usepackage{verbatim}
\usepackage{listings}

\newwrite\listinginput

\makeatletter
\def\startcapture{%
  \begingroup
  \@bsphack
    \immediate\openout\listinginput=\jobname.lst%
    \let\do\@makeother\dospecials\catcode`\^^M\active
    \def\verbatim@processline{%
        % The macro now pipes it's input to our temp file.
        \immediate\write\listinginput{\the\verbatim@line}
    }%
    \verbatim@start
}
\def\stopcapture{%
    \immediate\closeout\listinginput
    \@esphack
  \endgroup
}
\makeatother

\newenvironment{Schunk}{\startcapture}%
  {\stopcapture\lstinputlisting{\jobname.lst}}

\begin{document}

\begin{Schunk}
\begin{Sinput}
> getLatexStrWidth("The symbol: alpha")
\end{Sinput}
\begin{Soutput}
[1] 82.5354
\end{Soutput}
\end{Schunk}

\end{document}

在我的完整版本中,Schunk环境还将整个节目包装到 TikZ 节点中,这就是为什么我使用逐字逐句地将内容写入临时文件然后在使用时读回它们lstinputlisting(列表不适合嵌入到另一个环境中)。

现在,在中verbatim@processline,我有机会逐行检查事物,因此我想知道是否有办法执行相当于:

s/\\begin{Sinput}/customDelimiter/

在存储在中的令牌列表的内容上verbatim@line。我得到的最接近的方法是使用泰德包裹:

\def\verbatim@processline{%
  \Substitute*{\the\verbatim@line}{Sinput}{swe@veSt@rtOutput}
  % The macro now pipes it's input to our temp file.
  \immediate\write\listinginput{\the\ted@toks}
}%

这给了我以下输出:

\begin{swe@veSt@rtOutput}
> getLatexStrWidth("The symbol: alpha")
\end{swe@veSt@rtOutput}
\begin{Soutput}
[1] 82.5354
\end{Soutput}

但是,我无法使以下操作正常工作:

\Substitute*{\the\verbatim@line}{\begin{Sinput}}{swe@veSt@rtOutput}

如能提供任何关于如何实现这一源术行为的指点,我们将不胜感激!


更新

亚伦的评论让我发现了如何正确地转义诸如此类的东西\begin{Sinput},以便listings将它们视为分隔符:

\lstdefinestyle{sweave}{
  moredelim=[is][]
    {\\begin\{Sinput\}}
    {\\end\{Sinput\}},
  moredelim=[is][]
    {\\begin\{Soutput\}}
    {\\end\{Soutput\}}
}

与 一起使用时\lstinputlisting[style=sweave],将产生以下输出

. 
> getLatexStrWidth("The symbol: alpha")
.
.
[1] 82.5354
.

这些.字符实际上并不在输出中,它们只是为了显示留下了空行。

我认为我可以使用这个结果,但我将问题保留为开放的,以防有人对“我如何s/\\begin{this}/that/在令牌列表中?”这个一般问题有答案。


更新 2

我在使用 中的模式匹配宏时遇到了困难\verbatim@processline。我怀疑这是因为该宏是在 verbatim 环境处于活动状态并且 catcode 全部移位时执行的。因此,在\verbatim@processline定义时指定的任何模式都没有正确的 catcode 来生成匹配。我盯着 verbatim 文档看了很久,头疼不已,但我仍然不知道如何正确设置。

我想到的解决方法是将我原来的示例扩展为一个三步流程,将逐行捕获的内容写入临时文件。然后在逐行环境之外逐行重新读取此输出,其中 catcode 处于正常状态并且模式匹配有效。然后将此处理步骤的结果写入lstinputlisting读取和处理的最终文件。

代码现在如下所示:

\documentclass{minimal}
\usepackage{verbatim}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{xstring}

\newwrite\listinginput
\newread\tempin

\newif\ifskipline
\newif\ifhaveoutput
\newtoks\linebuffer
\def\addtobuffer#1{%
  \toks0={#1}%
  \edef\act{\noexpand\linebuffer={\the\linebuffer \the\toks0}}%
  \act}
\def\addoutput#1{%
  \expandafter\addtobuffer\expandafter{#1}%
  \haveoutputtrue}
\newtoks\parpattern\parpattern={\par}

\makeatletter
\def\startcapture{%
  \begingroup
  \@bsphack
    \immediate\openout\listinginput=\jobname.tmp%
    \let\do\@makeother\dospecials\catcode`\^^M\active
    \def\verbatim@processline{%
        % The macro now pipes it's input to our temp file.
        \immediate\write\listinginput{\the\verbatim@line}
    }%
    \verbatim@start
}
\def\stopcapture{%
    \immediate\closeout\listinginput
    \@esphack
  \endgroup
}
\makeatother

\def\replaceenvs#1{%
  \immediate\openin\tempin=\jobname.tmp
  \immediate\openout\listinginput=\jobname.lst
  \def\prependcode{}
  \begingroup
    % Xstring doesn't like parameter and comment characters
    \catcode`\#=11
    \catcode`\%=11
    \loop
      \ifeof\tempin
        \immediate\write\listinginput{\the\linebuffer}
      \else
        \immediate\read\tempin to \codeline
        \IfBeginWith{\expandafter\string\codeline}{\string\begin{Sinput}}{\addtobuffer{Swe@veBeginInput}\skiplinetrue}{}
        \IfBeginWith{\expandafter\string\codeline}{\string\end{Sinput}}{\addtobuffer{Swe@veEndInput}\skiplinetrue}{}
        \IfBeginWith{\expandafter\string\codeline}{\string\begin{Soutput}}{\addtobuffer{Swe@veBeginOutput}\skiplinetrue}{}
        \IfBeginWith{\expandafter\string\codeline}{\string\end{Soutput}}{\addtobuffer{Swe@veEndOutput}\skiplinetrue}{}
        \IfBeginWith{\expandafter\string\codeline}{\expandafter\string\the\parpattern}{\skiplinetrue}{}
        \ifskipline
          \skiplinefalse
        \else
          \ifhaveoutput
            \immediate\write\listinginput{\the\linebuffer}
            \linebuffer={}\haveoutputfalse
          \fi
          \addoutput{\codeline}
        \fi
    \repeat   
  \endgroup

  \immediate\closein\tempin
  \immediate\closeout\listinginput
}

\lstdefinestyle{sweave}{
  moredelim=[is][\color{red}]
    {Swe@veBeginOutput}
    {Swe@veEndOutput},
  moredelim=[is][\color{blue}]
    {Swe@veBeginInput}
    {Swe@veEndInput}
}

\newenvironment{Schunk}{%
  \startcapture%
}{%
  \stopcapture%
  \replaceenvs{\jobname.tmp}%
  \lstinputlisting[style=sweave]{\jobname.lst}%
}

\begin{document}

\begin{Schunk}
\begin{Sinput}
# This is a comment
> getLatexStrWidth("The symbol: alpha")
\end{Sinput}
\begin{Soutput}
[1] 82.5354%
\end{Soutput}
\end{Schunk}

\end{document}

verbatim\startcapture和宏之间像以前一样发挥它的魔力,\endcapture生成一个\jobname.tmp包含以下内容的文件:

\begin{Sinput}
# This is a comment
> getLatexStrWidth("The symbol: alpha")
\end{Sinput}
\begin{Soutput}
[1] 82.5354%
\end{Soutput}

\replaceenvs这是通过使用xstring包创建的新代码重新处理的\jobname.lst

Swe@veBeginInput# This is a comment 
> getLatexStrWidth("The symbol: alpha") Swe@veEndInputSwe@veBeginOutput
[1] 82.5354% Swe@veEndOutput

然后列表处理\jobname.lst生成:

# This is a comment 
> getLatexStrWidth("The symbol: alpha")
[1] 82.5354%

前两行以红色突出显示,最后一行以蓝色突出显示。这成功删除了SinputandSoutput环境,将其替换为listings可用于设置代码样式的分隔符,并且没有留下任何空行。

但是,我真的觉得我在读取和写入两个文件时走了很长的路。因此,我将这个问题保留下来,并为任何能想出使用更少遍数的解决方案的人提供赏金。


最后的想法

最后,我在模式匹配方面遇到的问题\verbatim@processline是由于我必须提供一个能够匹配两个字符的模式和 catcodes我正在寻找的字符串。

对于像我这样处于 TeX 用户和 TeX 程序员之间的过渡区的人来说,以下是详细信息:在逐字环境中,许多字符的 catcode 被重新分配给 12。这是通过\let\do\@makeother和的组合实现\dospecials的。盯着软件包手册中记录 TeX 代码的部分让我头疼,所以我最终开始在交互模式下使用 TeX 编译器(到目前为止我一直忽略这一点)以及\show解开宏定义:

grendel:~ sharpie$ pdflatex
This is pdfTeX, Version 3.1415926-1.40.11 (TeX Live 2010)
**\documentclass{minimal}
*\usepackage{verbatim}
*\makeatletter

*\show\@makeother
> \@makeother=macro:
#1->\catcode `#112\relax .

*\show\dospecials
> \dospecials=macro:
->\do \ \do \\\do \{\do \}\do \$\do \&\do \#\do \^\do \_\do \%\do \~.

\let\do\@makeother定义\do为一个宏,将其参数的 catcode 更改为 12。适用\dospecials于特殊\do字符列表:,,,,等。因此,如果您想在逐字环境中匹配包含其中一个字符的字符串,则必须在这些字符的 catcode 为 12 的环境中定义匹配模式。\{}

Leo Liu 和 unbonpetit 因指出了如何实现这一点而获奖。要决定谁能获得赏金真是一个艰难的决定——我希望可以将其分成两半。最后,我将其授予 ubonpetit,因为他们在示例中付出了额外的努力。然而,Leo Liu 确实首先回答了问题,并提供了信息,让我找到了更好的实现方式,因此我将浏览他的答案列表并投票支持他的一些出色贡献。

我最终得到的版本可以作为包在 GitHub 上获取:

http://github.com/Sharpie/SweaveToLst

感谢所有回答的人!

答案1

轮到我尝试一下了:

\documentclass{minimal}
\usepackage{verbatim}
\usepackage{listings}
\usepackage{ted}
\newwrite\listinginput

\makeatletter

\def\newsubstitution{\begingroup\let\do\@makeother\dospecials\newsubstitution@}
\def\newsubstitution@||#1||<->||#2||{%
    \endgroup
    \expandafter\def\expandafter\subst@list\expandafter{\subst@list\Substitute*[\verbatim@line]{\the\verbatim@line}{#1}{#2}}}
\def\clearsubstlist{\let\subst@list\@empty}
\clearsubstlist

\newenvironment{Schunk}
    {\begingroup
    \@bsphack
    \immediate\openout\listinginput=\jobname.lst%
    \let\do\@makeother\dospecials\catcode`\^^M\active
    \def\verbatim@processline{%
        \subst@list
        \immediate\write\listinginput{\the\verbatim@line}}%
    \verbatim@start}%
    {\immediate\closeout\listinginput
    \@esphack
    \endgroup
    \lstinputlisting{\jobname.lst}}

\makeatother
\begin{document}
\newsubstitution||\begin{Sinput}||<->||Swe@veBeginInput||
\newsubstitution||\end{Sinput}||<->||Swe@veEndInput||
\newsubstitution||\begin{Soutput}||<->||Swe@veBeginOutput||
\newsubstitution||\end{Soutput}||<->||Swe@veEndOutput||
With a substitution list:
\begin{Schunk}
\begin{Sinput}
> getLatexStrWidth("The symbol: alpha")
\end{Sinput}
\begin{Soutput}
[1] 82.5354
\end{Soutput}
\end{Schunk}

\clearsubstlist
With no substitution list:
\begin{Schunk}
\begin{Sinput}
> getLatexStrWidth("The symbol: alpha")
\end{Sinput}
\begin{Soutput}
[1] 82.5354
\end{Soutput}
\end{Schunk}
\end{document}

答案2

快速而肮脏的方法:

\documentclass{article}
\usepackage{verbatim}
\usepackage{xstring}

\makeatletter
\newenvironment{subsvrb}[2]{%
  \def\verbatim@nolig@list{}%
  \def\verbatim@processline{%
    \StrSubstitute{\the\verbatim@line}{#1}{#2}\par}
  \verbatim
}{%
  \endverbatim
}
\begingroup
\let\do\@makeother
\catcode`|=0 \catcode`[=1 \catcode`]=2
|dospecials
|gdef|bslash[\]
|gdef|lgroup[{]
|gdef|rgroup[}]
|endgroup
\makeatother
\begin{document}

\begin{subsvrb}{\bslash begin\lgroup foo\rgroup}{BEGIN OF FOO}
\begin{foo}
 bar
\end{foo}
\end{subsvrb}
\end{document}

答案3

\replace以下是命令的定义

\def\stripp#1>{}
\def\strip{\expandafter\stripp}
\def\replace#1#2#3{%
    \ifnum\pdfmatch subcount 4 {(.*)(#1)(.*)}{#3}=1%
        \strip\pdflastmatch1 #2\strip\pdflastmatch3%
    \else
        #3%
    \fi
}

为了做你的s/\\begin{Sinput}/customDelimiter/你可以写\replace{\unexpanded{\\begin\{Sinput\}}}{customDelimeter}{\the\verbatim@line}

答案4

你在找这样的东西吗? 可以%Start - %Stop用环境Sinput处理的所有注释来替换

\documentclass{minimal}
\usepackage{listings}
\begin{document}

\begin{lstlisting}[
  linerange={Start1-Stop1,Start2-Stop2},
  rangeprefix=\%,includerangemarker=false]
\begin{Schunk}
\begin{Sinput}
%Start1
> options(width = 80, continue = " ", size = "scriptsize")
> formula(mdl)
%Stop1
\end{Sinput}
\end{Schunk}
\begin{Schunk}
\begin{Sinput}
%Start2
> options(width = 80, continue = " ", size = "scriptsize")
> formula(mdl)
%Stop2
\end{Sinput}
\end{Schunk}

\end{lstlisting}

\end{document}

相关内容