
Я хотел бы узнать, есть ли скрипт, который читает файл .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);
}