TeX ファイルを読み取り、\newcommand のすべてのインスタンスを置き換えるスクリプトはありますか?

TeX ファイルを読み取り、\newcommand のすべてのインスタンスを置き換えるスクリプトはありますか?

ファイルを読み取り、非標準の TeX コマンドのすべてのインスタンスを、置き換えたいものに置き換えるスクリプトがあるかどうか知りたいです.tex。私が求めているものが明確かどうかわかりませんが、例を挙げてみましょう。

仮に入力は:

\documentclass{amsart} 
\usepackage{amsmath,amssymb}
\newcommand{\N}{\mathbb{N}}
\DeclareMathOperator{\End}{End}

\begin{document}
In this lecture we'll study the ring of Endomorphisms of an Abelian group $A$.
Let's denote this ring by $\End(A)$. Throughout the lecture, $\N$ will denote
the set of natural numbers.
\end{document} 

そして、望ましい出力このようなスクリプトの例は次のとおりです。

\documentclass{amsart}
\usepackage{amsmath, amssymb}
\begin{document}
In this lecture we'll study the ring of Endomorphisms of an Abelian group $A$.
Let's denote this ring by $\operatorname{End}(A)$. Throughout the lecture,  
 $\mathbb{N}$ will denote the set of natural numbers.
\end{document}

追伸:同じようなことを見たことがあるような気がしますが、場所も Google を起動するキーワードも覚えていません。


それを書こうと思ったのですが、ここにある答えはどれも素晴らしいのですが、4つのうち2つを数え間違えてしまいました。:(

答え1

情報 私は強制されてTeX.sx チャットルームマフィアかわいそうだけど、バグだらけで、ひどくて、トラウマになるような、終末後の貧乏人による代替スクリプトの実装を投稿します。:)

残念ながら、これは TeX の回答にはなりません。:)私が苦手なスクリプト言語を使った、ささやかな試みをご紹介します。

(Python、君のことだよ!)

import re
import sys

if len(sys.argv) != 3:
    print('We need two arguments.')
    sys.exit()

inputHandler = open(sys.argv[1], 'r')

mathDictionary = {}
commandDictionary = {}

print('Extracting commands...')
for line in inputHandler:
    mathOperator = re.search('\\\\DeclareMathOperator{\\\\([A-Za-z]*)}{(.*)}', line)
    if mathOperator:
        mathDictionary[mathOperator.group(1)] = mathOperator.group(2)
    newCommand = re.search('\\\\newcommand{\\\\([A-Za-z]*)}{(.*)}', line)
    if newCommand:
        commandDictionary[newCommand.group(1)] = newCommand.group(2)

inputHandler.seek(0)

print('Replacing occurrences...')
outputHandler = open(sys.argv[2],'w')
for line in inputHandler:
    current = line
    for x in mathDictionary:
        current = re.sub('\\\\DeclareMathOperator{\\\\' + x + '}{(.*)}', '', current)
        current = re.sub('\\\\' + x + '(?!\w)', '\\operatorname{' + mathDictionary[x] + '}', current)
    for x in commandDictionary:
        current = re.sub('\\\\newcommand{\\\\' + x + '}{(.*)}', '', current)
        current = re.sub('\\\\' + x + '(?!\w)', commandDictionary[x], current)
    outputHandler.write(current)

print('Done.')

inputHandler.close()
outputHandler.close()

今では、単に次のように呼んでいます。

$ python myconverter.py input.tex output.tex
Extracting commands...
Replacing occurrences...
Done.

input.tex

\documentclass{amsart} 
\usepackage{amsmath,amssymb}
\newcommand{\N}{\mathbb{N}}
\DeclareMathOperator{\End}{End}

\begin{document}
In this lecture we'll study the ring of Endomorphisms of an Abelian group $A$.
Let's denote this ring by $\End(A)$. Throughout the lecture, $\N$ will denote
the set of natural numbers.
\end{document} 

output.tex

\documentclass{amsart} 
\usepackage{amsmath,amssymb}



\begin{document}
In this lecture we'll study the ring of Endomorphisms of an Abelian group $A$.
Let's denote this ring by $\operatorname{End}(A)$. Throughout the lecture, $\mathbb{N}$ will denote
the set of natural numbers.
\end{document} 

制限事項:

  • それは私のコードなので注意してください!:)
  • \DeclareMathOperator{...}{...}およびでのみ動作します\newcommand{...}{...}
  • ではオプションの引数はサポートされていません\newcommand
  • 宣言は 1 行のみで記述する必要があります。
  • 中括弧のバランスをとってください。:)

正規表現は TeX の解析には適していないことは承知していますが、非常に単純な置換には機能するはずです。

正規表現についての美しい読み物です。 楽しむ。:)

答え2

偶然に出会ったde-macroこれはそのような目的のための Python スクリプトです。これは TeX Live に含まれています。

制限事項: 、、にのみ影響します\newcommand。星\renewcommand印付きバージョンとオプション引数は処理されません。以下は\newenvironment\renewenvironmentウィリー・ウォンによる別の質問への回答、制限事項についての詳細は以下の通りです。

Torbjørn T. と cfr の提案に基づいて、de-macroパッケージをさらに詳しく調べました。ある程度は機能します。次の点に注意してください。

  • ドキュメントで示されているものと異なり、私がインストールしたバージョン<filename>では、 ではなく というデータベース ファイルが作成されます<filename>.db。ただし、明らかに は定義データベースの名前として をテストします<filename>.db。したがって、現在のバージョンでは、実行するたびに定義データベースが最初から再作成されます。小さなドキュメントの場合、これは問題になりません。大きなドキュメントの場合は、<filename>.db潜在的な高速化を活用するために、データベースを にコピー (移動ではなく!) する必要があります。

  • まだ解決すべきバグがいくつかあります。}コードに誤った内容を挿入することで、プリアンブルが壊れることがあります。その理由やトリガー/MWE はまだわかりません。この点については、私が試した小さなテスト ケースはすべて正常に動作しました。

  • 非常に重要: ドキュメントが示唆しているように、スワップアウトしたいすべての定義は、 で終わる別のパッケージ内に存在する必要があります -private.sty。メイン.texファイルでは、そのパッケージを使用する必要があります。

  • また、非常に重要な点として、プログラムは\newcommandと を処理します\renewcommandが、星印の付いたバリアントは処理しません\newcommand*(ただし、これはPythonコードで正規表現を少し修正することで修正できると思います)。これが理由です。私の最初の試み失敗しました。(私はいつも星印の付いたバージョンはベストプラクティスだと知って以来

  • また、非常に重要な点として、星印を削除した後、プログラムがエラーを吐きました。結局、代わりに \newcommand\cmdname{<replacement}と 書く私の習慣が原因であることがわかりました\newcommand{\cmdname}{<replacement>}。この余分な括弧のペアは、解析にとって重要です。

  • 最後に、私にとって非常に残念なことに、このプログラムはオプションの引数を処理できません。 \newcommand{\cmdname}[2]{blah #1 blah #2} 正常に動作しますが、\newcommand{\cmdname}[2][nothing]{blah #1 blah #2} 例外がスローされます。

星と中括弧の問題は、マクロ定義 (覚えていると思いますが、この演習のポイントは、最終的には破棄されるということです) を星なしで書き直し、余分な中括弧を追加することで、簡単に修正/回避できます。

ただし、オプション引数の処理に関する問題により、現時点では、このプログラムの有用性はやや低下しています。現時点では、オプション コマンドと非オプション コマンドを 2 つの別々のコマンドに分割することで、この問題を回避できます。将来、時間があれば、元の Python スクリプトのロジックを理解した後、サポートの追加を検討するかもしれません。

答え3

同じ作業を行うスクリプトがこちらにありますperl。Paulo のコードと同じ制限がありますが、テストケースではうまく機能します。改善の余地があると思います。:)

使い方は以下になります

perl replacenewcommands.plx myfile.tex

端末に出力するか、

perl replacenewcommands.plx myfile.tex > outputfile.tex

出力はoutputfile.tex

新しいコマンドを置き換える.plx

#!/usr/bin/perl

use strict;
use warnings;

# for newcommands
my @newcommandmacro=();
my %newcommandcontent=();

# for DeclareMathoperator
my @declaremathoperator=();
my %declaremathoperatorcontent=();

# for use as an index
my $macro;

# loop through the lines in the INPUT file
while(<>)
{
    # check for 
    #   \newcommand...
    # and make sure not to match
    #   %\newcommand
    # which is commented
    if($_ =~ m/\\newcommand{(.*)}{(.*)}/ and $_ !~ m/^%/)
    {
        push(@newcommandmacro,$1);
        $newcommandcontent{$1}=$2;

        # remove the \newcommand from the preamble
        s/\\newcommand.*//;
    }


    # loop through the newcommands in the 
    # main document
    foreach $macro (@newcommandmacro)
    {
      # make the substitution, making sure to escape the \
      # uinsg \Q and \E for begining and end respectively
      s/\Q$macro\E/$newcommandcontent{$macro}/g;
    }

    # check for 
    #   \DeclareMathOperator...
    # and make sure not to match
    #   %\DeclareMathOperator
    # which is commented
    if($_ =~ m/\\DeclareMathOperator{(.*)}{(.*)}/ and $_ !~ m/^%/)
    {
        push(@declaremathoperator,$1);
        $declaremathoperatorcontent{$1}=$2;

        # remove the \DeclareMathOperator from the preamble
        s/\\DeclareMathOperator.*//;
    }

    # loop through the DeclareMathOperators in the 
    # main document
    foreach $macro (@declaremathoperator)
    {
      # make the substitution, making sure to escape the \
      # uinsg \Q and \E for begining and end respectively
      s/\Q$macro\E(\(.*\))/\\operatorname{$declaremathoperatorcontent{$macro}}$1/g;
    }
    print $_;
}

テストケースでは

myfile.tex (オリジナル)

\documentclass{amsart} 
\usepackage{amsmath,amssymb}
\newcommand{\N}{\mathbb{N}}
\newcommand{\mycommand}{something else}
\DeclareMathOperator{\End}{End}

\begin{document}
In this lecture we'll study the ring of Endomorphisms of an Abelian group $A$.
Let's $\N$ denote this ring by $\End(A)$. Throughout the lecture, $\N$ will denote
the set of natural numbers. \mycommand

and \mycommand again
\end{document} 

outputfile.tex (新規)

\documentclass{amsart} 
\usepackage{amsmath,amssymb}




\begin{document}
In this lecture we'll study the ring of Endomorphisms of an Abelian group $A$.
Let's $\mathbb{N}$ denote this ring by $\operatorname{End}(A)$. Throughout the lecture, $\mathbb{N}$ will denote
the set of natural numbers. something else

and something else again
\end{document} 

答え4

、、、、、、、、、および\defで定義されたマクロを展開するためのJavaScriptをいくつか書きました。試してみてください。\gdef\edef\xdef\newcommand\newcommand*\renewcommand\renewcommand*\DeclareMathOperator\DeclareMathOperator*ここ

function expandMacros(tex) {
    function nestBrackets(level) {
        var level = level || 5, re = c = "(?:[^\\r\\n\\{\\}]|\\\\[\\{\\}]|\\r?\\n(?!\\r?\\n))*?";
        while (level--) re = c + "(?:\\{" + re + "\}" + c + ")*?";
        return " *(\\{" + re + "\\}|[^\\{])";
    }    
    function getRegExp(name, macro) {
        var num = macro.num, def = macro.def, re = "";
        while (num--) re += nestBrackets();
        re = "\\" + name + "(?![a-zA-Z\\}])" + re;
        return new RegExp(re, "g");
    }
    function trimString(s) {
        return s.replace(/^ +| +$/g, '').replace(/^\{|\}$/g, "");
    }
    function extractMacros() {
        var cs = "\\\\\\w+", re;
        // \def, \gdef, \edef and \xdef
        re = new RegExp("\\\\[gex]?def\\*? *(" + cs + ") *(#\\d)*" + nestBrackets(), "g");
        tex = tex.replace(re, function(match){
            var m = arguments;
            var macro = {
                num:  m[2] ? Math.min(m[2].length / 2, 9) : 0,
                def:  trimString(m[3])
            };
            macros[trimString(m[1])] = macro;
            return "";
        });
        // \newcommand, \newcommand*, \renewcommand and \renewcommand*
        re = new RegExp("\\\\(?:re)?newcommand\\*? *(" + cs + "|\\{" + cs + "\}) *(\\[(\\d)\\])?"
                        + nestBrackets(), "g");
        tex = tex.replace(re, function(match){
            var m = arguments;
            var macro = {
                num:  m[3] || 0,
                def:  trimString(m[4])
            };
            macros[trimString(m[1])] = macro;
            return "";
        });
        // \DeclareMathOperator and \DeclareMathOperator* inside amsmath
        re = new RegExp("\\\\DeclareMathOperator(\\*?) *(" + cs + "|\\{" + cs + "\}) *"
                        + nestBrackets(), "g");
        tex = tex.replace(re, function(match){
            var m = arguments;
            var macro = {
                num:  0,
                def:  "\\operatorname" + m[1] + "{" + trimString(m[3]) + "}"
            };
            macros[trimString(m[2])] = macro;
            return "";
        });
    }
    function replaceMacros() {
        var i = 0, m, re, num;
        for (name in macros) {
            m = macros[name];
            re = getRegExp(name, m), num = m.num;
            //console.log(re);
            tex = tex.replace(re, function(match){
                //console.log(arguments);
                var args = [], result = m.def, k;
                for (k = 1; k <= num; k++) {
                    args[k] = trimString(arguments[k]);
                }
                //console.log(args);
                for (k = 1; k <= num; k++) {
                    result = result.replace(new RegExp("#" + k, "g"), args[k]);
                }
                return result;
            });
        }
    }
    var macros = {};
    extractMacros();
    replaceMacros();
    return tex;
}

document.getElementById("run").onclick = function() {
    var input = document.getElementById("input"),
        output = document.getElementById("output");
    output.value = expandMacros(input.value);
}

関連情報