¿Existe un script que lea un archivo TeX y reemplace cada instancia de un \newcommand?

¿Existe un script que lea un archivo TeX y reemplace cada instancia de un \newcommand?

Me gustaría saber si existe un script que lea un .texarchivo y reemplace cada instancia de un comando TeX no estándar con lo que sea que esté reemplazando. No estoy seguro de si lo que quiero está claro, pero déjame darte un ejemplo:

Supongamos que elaportees:

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

Entonces, un deseableproducciónde tal guión es:

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

PD:Creo que había visto algo parecido pero no recuerdo el lugar ni una palabra clave para abrir Google.


Quería escribir eso, todas las respuestas aquí son increíbles, pero conté mal 2 por 4. :(

Respuesta1

Información He sido obligado por elMafia de la sala de chat TeX.sxpara publicar la implementación de mi encantador, con errores, terrible, traumática y post-apocalíptica pobre de un guión de reemplazo.:)

Bueno, lamentablemente esta no será una respuesta TeX. :)Aquí está mi humilde intento, con un lenguaje de escritura en el que soy terrible.

(¡Te estoy mirando, 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()

Ahora simplemente lo llamamos:

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

Limitaciones:

  • Es mi código, ¡así que ten cuidado!:)
  • Funciona sólo con \DeclareMathOperator{...}{...}y \newcommand{...}{...}.
  • No se admiten argumentos opcionales para \newcommand.
  • La declaración debe estar en una sola línea.
  • Llaves equilibradas, por favor.:)

Sé que las expresiones regulares no son adecuadas para analizar TeX, pero deberían funcionar para reemplazos muy simples.

Aquí hay una hermosa lectura sobre expresiones regulares.. Divertirse.:)

Respuesta2

Por casualidad me encontréde-macro, que es un script de Python para tal fin. Está incluido en TeX Live.

Limitación: Sólo afecta a \newcommand, y . No se manejan versiones destacadas ni argumentos opcionales. Lo siguiente es citado de\renewcommand\newenvironment\renewenvironmentesta respuesta de Willie Wong a otra preguntay tiene más detalles sobre las limitaciones:

Basándome en la sugerencia de Torbjørn T. y cfr, analicé más profundamente el de-macropaquete. Funciona hasta cierto punto. Las siguientes son advertencias:

  • A diferencia de lo que sugiere la documentación, la versión que he instalado crea el archivo de base de datos <filename>en lugar de <filename>.db. Sin embargo, evidentemente prueba <filename>.dbcomo nombre de la base de datos de definiciones. Entonces, en su encarnación actual, recreará la base de datos de definiciones desde cero en cada ejecución. Para documentos pequeños esto no es un problema. Para documentos más grandes, se debe copiar (¡no mover!) la base de datos para <filename>.dbaprovechar cualquier posible aceleración.

  • Todavía quedan algunos errores por solucionar. Ocasionalmente destroza los preámbulos insertando elementos falsos }en el código. Todavía no he encontrado el motivo ni el desencadenante/MWE. Los pequeños casos de prueba en los que lo probé funcionaron bien en este sentido.

  • Muy importante: como sugiere la documentación, todas las definiciones que desee intercambiar deben estar dentro de un paquete separado que termine en -private.sty. En el .texarchivo principal se debe utilizar ese paquete.

  • También es muy importante: el programa maneja \newcommandy \renewcommand, pero no la variante destacada \newcommand*(aunque esto se puede solucionar modificando un poco la expresión regular en el código Python, supongo). Esta es la razón pormi primer intentofallido. (yo siempre usola variante destacada desde que supe que era la mejor práctica.)

  • También muy importante: después de quitar las estrellas, el programa arrojó un error. Lo que finalmente descubrí se debe a mi hábito de \newcommand\cmdname{<replacement}escribir \newcommand{\cmdname}{<replacement>}. ¡Ese par de llaves extra es importante para el análisis!

  • Por último, muy decepcionante para mí, el programa.no puede manejar argumentos opcionales. \newcommand{\cmdname}[2]{blah #1 blah #2} funciona bien, pero \newcommand{\cmdname}[2][nothing]{blah #1 blah #2} genera una excepción.

El problema con la estrella y las llaves lo puedo solucionar/solucionar fácilmente reescribiendo mis definiciones de macros (que, como recordarás, se desecharán al final de todos modos, siendo el objetivo de este ejercicio) sin las estrellas y agregando las llaves adicionales.

Sin embargo, el problema con el manejo de argumentos opcionales actualmente hace que el programa sea algo menos útil para mí. Puedo solucionarlo por ahora dividiendo los comandos opcionales y no opcionales en dos separados. Tal vez, si tengo algo de tiempo en el futuro, consideraré agregar soporte para ello, después de descubrir la lógica del script Python original.

Respuesta3

Aquí hay un perlscript para hacer el mismo trabajo. Tiene las mismas limitaciones que el código de Paulo, pero funciona bien en su caso de prueba. No dudo que se pueda mejorar:)

Lo usas de la siguiente manera.

perl replacenewcommands.plx myfile.tex

que sale al terminal, o

perl replacenewcommands.plx myfile.tex > outputfile.tex

que saldrá aoutputfile.tex

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

En su caso de prueba

miarchivo.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} 

archivo de salida.tex (nuevo)

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

Respuesta4

Escribí algo de javascript para expandir macros definidas por \def, \gdef, \edef, \xdef, \newcommand, \newcommand*, y\renewcommand\renewcommand*\DeclareMathOperator\DeclareMathOperator* expandir Puedes intentarloaquí.

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

información relacionada