Существует ли скрипт, который считывает файл 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}

P.S.:Кажется, я видел что-то подобное, но не помню ни места, ни ключевого слова, по которому можно было бы запустить Google.


Я хотел написать, что все ответы здесь потрясающие, но я пересчитал 2 из 4. :(

решение1

Информация Я был вынужденTeX.sx чат мафиячтобы опубликовать мою прекрасную, глючную, ужасную, травматичную, постапокалиптическую реализацию сценария замены для бедняков.:)

Ну, к сожалению, это не будет ответом TeX. :)Вот моя скромная попытка, с языком скриптов, в котором я ужасен.

(Я смотрю на тебя, Питон!)

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 . Помеченные версии и необязательные аргументы не обрабатываются. Ниже приведена цитата изэтот ответ Вилли Вонга на другой вопрос, и содержит более подробную информацию об ограничениях:

На основе предложения 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скрипт для выполнения той же работы. Он имеет те же ограничения, что и код Пауло, но хорошо работает в вашем тестовом случае. Я не сомневаюсь, что его можно улучшить:)

Вы используете его следующим образом

perl replacenewcommands.plx myfile.tex

который выводит на терминал, или

perl replacenewcommands.plx myfile.tex > outputfile.tex

который выведет на экранoutputfile.tex

replacenewcommands.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);
}

Связанный контент