
Gostaria de saber se existe um script que lê um .tex
arquivo 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
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
\renewenvironment
esta 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-macro
pacote. 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>.db
como 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>.db
aproveitar 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.tex
arquivo principal deve-se usar esse pacote.Também é muito importante: o programa manipula
\newcommand
and\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 perl
script 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);
}