# Is there a script that reads a TeX file and replaces every instance of a \newcommand?

I'd like to know if there's a script that reads a .tex file and replace every instance of a non-standard TeX command with whatever it is replacing. I am not sure if what I want is clear but let me give an example:

Suppose the input is:

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


Then, a desirable output of such a script is:

\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.: I think I had seen something to this effect but I neither remember the place nor a key word to fire up Google.

I meant to write that, all answers here are awesome, but I miscounted 2 for 4. :(

• You want something like a preprocessor. See here or here for similar questions. – Juri Robl Aug 18 '12 at 15:56
• How deep do you want to go? Just one level or completely expanded? – mrf Aug 18 '12 at 16:25
• @mrf I'd like to replace, for instance, $\N$ with $\mathbb {N}$. So, basically, I'd like the script to replace foo with oof if the preamble has the line \newcommand{foo}{oof}. – kan Aug 18 '12 at 16:31
• Personally I think perl will be ideal for this... but are you after something in TeX? – cmhughes Aug 18 '12 at 16:45
• this may be what you want. But you have to define yourself which macros you want replaced. – Juri Robl Aug 18 '12 at 16:48

I've been forced by the TeX.sx chatroom mafia to post my lovely, buggy, terrible, traumatic, post-apocalyptic poor man's implementation of a replacement script. :)

Well, sadly this won't be a TeX answer. :) Here's my humble attempt, with a script language I'm terrible at.

(I'm looking at you, 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:
}


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


outputfile.tex (new)

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

• Awesome, Chris! :) – Paulo Cereda Aug 18 '12 at 19:58

As it stands, the regex he uses doesn't support nested brackets, and, indeed, the language doesn't allow for nested, recursive structures of arbitrary depth.

That said, we can robustify the regex for one additional level of nesting, like this:

r"\\newcommand{\\([A-Za-z]*)}{((:?[^{}]*{[^}]*})*[^}]*)}"
**             **              |****************|
#1             #2              #3


Notes:

1. The Python r"..." construction for regexes is helpful because it doesn't need Python's text escaping (though it still requires regex escaping).
2. That means we only need \ to represent \ in the regex, making us all less cross-eyed
3. The main addition is a subpattern that looks for zero or more balanced brackets, followed by the tail end of the original pattern.

It's also possible to improve efficiency slightly by pre-compiling some of the regexes. Here's my adaptation of Paulo's solution:

#! python.exe

import re
import sys

if len(sys.argv) != 3:
print('We need two arguments.')
sys.exit()

mathDictionary = {}
commandDictionary = {}

inputHandler = open(sys.argv[1], 'r')
outputHandler = open(sys.argv[2],'w')

mathpat    = re.compile( r"\\DeclareMathOperator{\\([A-Za-z]*)}{((:?[^{}]*{[^}]*})*[^}]*)}" )
searchmath = mathpat.search
newcpat    = re.compile( r"\\newcommand{\\([A-Za-z]*)}{((:?[^{}]*{[^}]*})*[^}]*)}" )
searchnewc = newcpat.search

print('Processing...')

for line in inputHandler:
mathOperator = searchmath(line)
if mathOperator:
mathDictionary[mathOperator.group(1)] = mathOperator.group(2)
newCommand = searchnewc(line)
if newCommand:
commandDictionary[newCommand.group(1)] = newCommand.group(2)
current = line
for x in mathDictionary:
current = re.sub(mathpat, '', current)
current = re.sub(r"\\" + x + r"(?!\w)", '\\operatorname{' + mathDictionary[x] + '}', current)
for x in commandDictionary:
current = re.sub(newcpat, '', current)
current = re.sub(r"\\" + x + "(?!\w)", commandDictionary[x], current)
outputHandler.write(current)

inputHandler.close()
outputHandler.close()

print('Done.')


I have also made it one-pass; defining a newcommand after using it doesn't make too much sense.

• By the way, if you think from this that regex is for loonies, you're probably right. – Brent.Longborough Jan 31 '14 at 23:21


function expandMacros(tex) {
function nestBrackets(level) {
+ 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);
}

• Oh, Thank you very much for something that works online! Will the link always work? (The thing about codes like this is that, they would help reduce manual labour. So, I am pretty happy with this implementation!) – kan Jan 13 '14 at 13:38
• In fact, the web page is hosted by http://jsfiddle.net. Therefore the link will always work as long as JSFiddle website is online. You may also save the web page to your computer. – Z.H. Jan 13 '14 at 14:01