Existe um script que lê um arquivo TeX e substitui todas as instâncias de um \newcommand?

Existe um script que lê um arquivo TeX e substitui todas as instâncias de um \newcommand?

Gostaria de saber se existe um script que lê um .texarquivo e substitui cada instância de um comando TeX não padrão pelo que ele está substituindo. Não tenho certeza se o que quero está claro, mas deixe-me dar um exemplo:

Suponha queentradaé:

\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} 

Então, um desejávelsaídade tal script é:

\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}

PS:Acho que já vi algo nesse sentido, mas não me lembro do local nem de uma palavra-chave para acionar o Google.


Eu pretendia escrever isso, todas as respostas aqui são fantásticas, mas contei errado 2 em vez de 4. :(

Responder1

Informações Fui forçado peloMáfia da sala de bate-papo TeX.sxpara postar minha adorável, cheia de erros, terrível, traumática e pós-apocalíptica implementação de um script de substituição pelo pobre homem.:)

Bem, infelizmente esta não será uma resposta TeX. :)Aqui está minha humilde tentativa, com uma linguagem de script na qual sou péssimo.

(Estou olhando para você, 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()

Agora, simplesmente chamamos isso de:

$ 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} 

Limitações:

  • É o meu código, então cuidado!:)
  • Funciona apenas com \DeclareMathOperator{...}{...}e \newcommand{...}{...}.
  • Nenhum argumento opcional é suportado para \newcommand.
  • A declaração deve estar em apenas uma linha.
  • Colchetes balanceados, por favor.:)

Eu sei que expressões regulares não são adequadas para analisar TeX, mas devem funcionar para substituições muito simples.

Aqui está uma bela leitura sobre regex. Divirta-se.:)

Responder2

Por acidente me depareide-macro, que é um script Python para esse fim. Ele está incluído no TeX Live.

Limitação: afeta apenas \newcommand, e . Versões marcadas com estrela e argumentos opcionais não são tratados. O seguinte é citado de\renewcommand\newenvironment\renewenvironmentesta resposta de Willie Wong para outra perguntae tem mais detalhes sobre as limitações:

Com base na sugestão de Torbjørn T. e CFR, analisei mais profundamente o de-macropacote. Funciona até certo ponto. A seguir estão advertências:

  • Ao contrário do que a documentação sugere, a versão que instalei cria o arquivo de banco de dados <filename>em vez de <filename>.db. No entanto, evidentemente, ele testa <filename>.dbcomo o nome do banco de dados de definições. Portanto, em sua encarnação atual, ele recriará o banco de dados de definições do zero a cada execução. Para documentos pequenos, isso não é um problema. Para documentos maiores, deve-se copiar (não mover!) o banco de dados para <filename>.dbaproveitar qualquer potencial aceleração.

  • Ainda existem alguns bugs a serem resolvidos. Ocasionalmente, ele altera preâmbulos inserindo informações falsas }no código. Ainda não encontrei o motivo ou o gatilho/MWE para isso. Os pequenos casos de teste em que experimentei funcionaram bem nesse aspecto.

  • Muito importante: como sugere a documentação, todas as definições que você deseja trocar devem estar dentro de um pacote separado que termine em -private.sty. No .texarquivo principal deve-se usar esse pacote.

  • Também é muito importante: o programa manipula \newcommandand \renewcommand, mas não a variante com estrela \newcommand*(embora isso possa ser corrigido alterando um pouco a expressão regular no código python, suponho). Isso é por queminha primeira tentativafracassado. (eu sempre usoa variante marcada com estrela desde que descobri que ela é uma prática recomendada.)

  • Também muito importante: após retirar as estrelas, o programa apresentou um erro. O que \newcommand\cmdname{<replacement}acabei descobrindo é devido ao meu hábito de escrever \newcommand{\cmdname}{<replacement>}. Esse par extra de chaves é importante para a análise!

  • Por último, muito decepcionante para mim, o programanão pode lidar com argumentos opcionais. \newcommand{\cmdname}[2]{blah #1 blah #2} funciona bem, mas \newcommand{\cmdname}[2][nothing]{blah #1 blah #2} lança uma exceção.

O problema com a estrela e os colchetes eu posso facilmente corrigir/solucionar reescrevendo minhas definições de macro (que, como você se lembra, serão descartadas no final de qualquer maneira, sendo o objetivo deste exercício) sem as estrelas e adicionando os colchetes extras.

O problema com o tratamento opcional de argumentos, entretanto, atualmente torna o programa um pouco menos útil para mim. Posso contornar isso por enquanto dividindo os comandos opcionais e não opcionais em dois comandos separados. Talvez, se eu tiver algum tempo no futuro, tentarei adicionar suporte para ele, depois de descobrir a lógica do script python original.

Responder3

Aqui está um perlscript para fazer o mesmo trabalho. Possui as mesmas limitações do código do Paulo, mas funciona bem no seu caso de teste. Não duvido que possa ser melhorado:)

Você o usa da seguinte maneira

perl replacenewcommands.plx myfile.tex

que sai para o terminal, ou

perl replacenewcommands.plx myfile.tex > outputfile.tex

que irá produzir paraoutputfile.tex

substituirnovoscomandos.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 $_;
}

No seu caso de teste

meuarquivo.tex (original)

\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} 

arquivo de saída.tex (novo)

\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} 

Responder4

Eu escrevi alguns javascript para expandir macros definidas por \def, \gdef, \edef, \xdef, \newcommand, \newcommand*, \renewcommand, \renewcommand*e \DeclareMathOperator. \DeclareMathOperator*Você pode tentaraqui.

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

informação relacionada