是否有一個腳本可以讀取 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}

附:我想我見過類似的東西,但我既不記得那個地方,也不記得啟動谷歌的關鍵字。


我本來想這麼寫的,這裡的所有答案都很棒,但我把 2 數錯為 4 了。

答案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
  • 聲明必須僅在一行中。
  • 請平衡大括號。:)

我知道正規表示式不適合解析 TeX,但它們應該適用於非常簡單的替換。

這是一篇關於正規表示式的精彩讀物。玩得開心。:)

答案2

偶然間我遇到了de-macro,這是用於此目的的 Python 腳本。它包含在 TeX Live 中。

限制:它只影響\newcommand\renewcommand\newenvironment\renewenvironment。不處理帶有星號的版本和可選參數。以下內容引自Willie Wong 對另一個問題的回答,並提供有關限制的更多詳細資訊:

根據 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} 拋出異常。

對於星號和大括號的問題,我可以透過重寫我的巨集定義(正如您所記得的,無論如何,作為本練習的重點,最終將被丟棄)來輕鬆修復/解決問題,而無需星號並添加額外的大括號。

然而,可選參數處理的問題目前使得該程式對我來說不太有用。我現在可以透過將可選命令和非可選命令分成兩個單獨的命令來解決這個問題。也許,如果我將來有時間,在弄清楚原始 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} 

輸出檔.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

我寫了一些javascript 來擴充\def\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);
}

相關內容