12

I want to replace all TeXish math $ $ in a tex file by LaTeXish \( \) and also $$ $$ by \[ \]. How can I do this cleverly?

Please beware that in TikZ environment, one can not replace $ $ by \( \).

  • In the tikz environment, I have \( \) and everything is fine. – dustin Jun 7 '13 at 1:13
  • 1
    Would the $...$ and $$..$$ be split across lines? Would you be open to a perl solution? – cmhughes Jun 7 '13 at 1:26
  • @cmhughes I would like to see the perl solution. – dustin Jun 7 '13 at 1:34
  • If you have $...$ inside moving arguments, you'll need to load the fixltx2e package; otherwise, unprotected \(...\) will break. – Gonzalo Medina Jun 7 '13 at 1:34
  • 3
    @dustin In node text? Yes, there \( and \) are just fine. But the calc library uses $ to activate its syntax (without having anything to do with math or a math shift). — @MaMing In Are ( and ) preferable to dollar signs for math mode? Andrew Stacey talks about parsing $ vs \(/\). Though, his mathgrep script doesn’t seem to use $ as a valid “math section”. – Qrrbrbirlbel Jun 7 '13 at 1:50
5

Here's a perl script to get you started, subDollars.pl. As a disclaimer, you should test it significantly before using it on anything important. I can't guarantee the results, but it should get you started.

You can use it in the following way

  • perl subDollars.pl test.tex will simply output to the terminal with the substitutions
  • perl subDollars.pl -s test.tex will not output to the terminal
  • perl subDollars.pl -w test.tex will overwrite test.tex with the substitutions in place
  • perl subDollars.pl -o test.tex output.tex will output to output.tex with the substitutions in place.

Any environments that you don't want it to operate on should be included in

my %nosubstitutions = ("tikzpicture"=>1, "verbatim"=>1, "nosubblock"=>1);

This hash can consist of stand environments such as tikzpicture

\begin{tikzpicture}
\draw[red] ($(#2)+(-.5em,.9em)$) rectangle ($(RightPoint)+(0.2em,-0.3em)$);
\end{tikzpicture}

or any blocks of code that are not necessarily within an environment, but you can wrap them in a 'commented' environment, such as

%\begin{nosubblock}
\draw[red] ($(#2)+(-.5em,.9em)$) rectangle ($(RightPoint)+(0.2em,-0.3em)$);
%\end{nosubblock}

The script won't account for $...$ and $$...$$ split across lines- that's certainly doable, but a little more work.

subDollars.pl

#!/usr/bin/perl

use strict;
use warnings;           
use File::Copy;         # to copy the original file to backup (if overwrite option set)
use Getopt::Std;        # to get the switches/options/flags

# get the options
my %options=();
getopts("wos", \%options);

# standard output
my $out = *STDOUT;

# overwrite option
my $overwrite = 0;
$overwrite = $options{w};

# output file option
my $outputToFile = $options{o};

# can't call the script with MORE THAN 2 files
if(scalar(@ARGV)>2)
{
print $out <<ENDQUOTE

ERROR:
\t You're calling subDollars.pl with more than two file names
\t The script can take at MOST two file names, but you 
\t need to call it with the -o switch; for example

\t subDollars.pl -o originalfile.tex outputfile.tex

Exiting...
ENDQUOTE
;
    exit(2);
}

# check for output file
if($outputToFile and scalar(@ARGV)==1)
{
print $out <<ENDQUOTE
ERROR: When using the -o flag you need to call this script with 2 arguments

subDollars.pl -o "$ARGV[0]" [needs another name here]

Exiting...
ENDQUOTE
;
    exit(2);
}

# don't call the script with 2 files unless the -o flag is active
if(!$outputToFile and scalar(@ARGV)==2)
{
print $out <<ENDQUOTE

ERROR:
\t You're calling subDollars.pl with two file names, but not the -o flag.
\t Did you mean to use the -o flag ?

Exiting...
ENDQUOTE
;
    exit(2);
}

# array to store the modified lines
my @lines;

# hash naming environments that contain lines 
# that should not be substituted
my %nosubstitutions = ("tikzpicture"=>1, "verbatim"=>1, "nosubblock"=>1);

# switch to toggle nosubstitutions- initially off
my $nosubs = 0;

# if we want to over write the current file
# create a backup first
if ($overwrite)
{
    # original name of file
    my $filename = $ARGV[0];
    # copy it
    my $backupFile = $filename;
    my $backupExtension='.bak';

    $backupFile =~ s/\.tex/$backupExtension/;

    copy($filename,$backupFile) or die "Could not write to backup file $backupFile. Please check permissions. Exiting.\n";
}

# open the file
open(MAINFILE, $ARGV[0]) or die "Could not open input file";

# loop through the lines in the INPUT file
while(<MAINFILE>)
{
    # check for BEGIN of an environment that doesn't want substitutions
    $nosubs = 1 if( $_ =~ m/^\s*\\begin{(.*?)}/ and $nosubstitutions{$1} );

    # check for %\begin{nosubblock}
    $nosubs = 1 if( $_ =~ m/^\s*%\s*\\begin{(.*?)}/ and $nosubstitutions{$1} );

    # check for END of an environment that doesn't want substitutions
    $nosubs = 0 if( $_ =~ m/^\s*\\end{(.*?)}/ and $nosubstitutions{$1});

    # check for %\end{nosubblock}
    $nosubs = 0 if( $_ =~ m/^\s*%\s*\\end{(.*?)}/ and $nosubstitutions{$1} );

    # substitute $.*$ with \(.*\) 
    # note: this does NOT match $$.*$$
    s/(?<!\$)\$([^\$].*?)\$/\\\($1\\\)/g unless($nosubs);

    # substitute $$.*$$ with \[.*\]
    s/\$\$(.*?)\$\$/\\\[$1\\\]/g unless($nosubs);

    push(@lines,$_);

}

# output the formatted lines to the terminal
print @lines if(!$options{s});

# if -w is active then output to $ARGV[0]
if($overwrite)
{
    open(OUTPUTFILE,">",$ARGV[0]);
    print OUTPUTFILE @lines;
    close(OUTPUTFILE);
}

# if -o is active then output to $ARGV[1]
if($outputToFile)
{
    open(OUTPUTFILE,">",$ARGV[1]);
    print OUTPUTFILE @lines;
    close(OUTPUTFILE);
}

exit;

Here's a test file to test it on

before.tex

\documentclass{article}

\begin{document}

$x=a+b$

$x=a+b$ and another $x=a+b$, $x=a+b$ and another $x=a+b$

$$x=a+b$$ and another $$x=a+b$$, $$x=a+b$$ and another $$x=a+b$$

\begin{tikzpicture}
\draw[red] ($(#2)+(-.5em,.9em)$) rectangle ($(RightPoint)+(0.2em,-0.3em)$);
\end{tikzpicture}

\begin{verbatim}
\draw[red] ($(#2)+(-.5em,.9em)$) rectangle ($(RightPoint)+(0.2em,-0.3em)$);
\end{verbatim}

$$x=a+b$$ and another $$x=a+b$$, $$x=a+b$$ and another $$x=a+b$$

%\begin{nosubblock}
\draw[red] ($(#2)+(-.5em,.9em)$) rectangle ($(RightPoint)+(0.2em,-0.3em)$);
%\end{nosubblock}

$x=a+b$ and another $x=a+b$, $x=a+b$ and another $x=a+b$

$$x=a+b$$ and another $$x=a+b$$, $$x=a+b$$ and another $$x=a+b$$
\end{document}

after.tex

\documentclass{article}

\begin{document}

\(x=a+b\)

\(x=a+b\) and another \(x=a+b\), \(x=a+b\) and another \(x=a+b\)

\[x=a+b\] and another \[x=a+b\], \[x=a+b\] and another \[x=a+b\]

\begin{tikzpicture}
\draw[red] ($(#2)+(-.5em,.9em)$) rectangle ($(RightPoint)+(0.2em,-0.3em)$);
\end{tikzpicture}

\begin{verbatim}
\draw[red] ($(#2)+(-.5em,.9em)$) rectangle ($(RightPoint)+(0.2em,-0.3em)$);
\end{verbatim}

\[x=a+b\] and another \[x=a+b\], \[x=a+b\] and another \[x=a+b\]

%\begin{nosubblock}
\draw[red] ($(#2)+(-.5em,.9em)$) rectangle ($(RightPoint)+(0.2em,-0.3em)$);
%\end{nosubblock}

\(x=a+b\) and another \(x=a+b\), \(x=a+b\) and another \(x=a+b\)

\[x=a+b\] and another \[x=a+b\], \[x=a+b\] and another \[x=a+b\]
\end{document}
  • I won’t be needing this, so don’t see it as an request: The pseudo-environment nosubblock will also be needed for \tikz{ … ($(#2)+(-.5em,.9em)$) … } and (even worse) \tikz … ($(#2)+(-.5em,.9em)$) … ;. Or can this be easily implemented? – Qrrbrbirlbel Jun 9 '13 at 0:47
  • @Qrrbrbirlbel as it stands, it would be up to you to wrap it: %\begin{nosubblock}\n \tikz.... \n %\end{nosubblock}. I'm not sure how much time to invest in this script- I don't know how useful it's going to be... – cmhughes Jun 9 '13 at 1:00
1

You may want to check this thread. That should give you at least good starting point. However in the case that $ $ and$$ $$ are spread over multiple lines the solutions from the thread will need to be significantly modified. You are also talking about exceptions in the case of TikZ code. That will further make this a very interesting exercise in regex. My personal preference would be to attack the problem with Python but Perl is definitely the right to do do the job. sed solution would have to be exceptionally clever since sed normally operates on single lines and only with basic Posix regular expressions. Any multiline sed edits are considered very advanced stuff. I would avoid awk for this particular problem.

1

Quite obviously, the language that is best-suited for this is Emacs Lisp. In fact, I have a working solution (though not taking tikz into account, which is an interesting issue; also, $a$$b$ would make it trip - but it's meant for real life, not academia;)). It is not good, idiomatic Elisp (probably), but should work OK (I'm going to make it better some day...) Also, it's a bit more general -- it handles Polish diacritical signs as well, and in general it is capable to replace a given regex by a cycling list of replacements (though the single and double dollar signs are the only application of that currently). As mentioned, it's not fool-proof, but I've used it on real documents from various sources and never got into problems. (Now I can see that \$ would not work; extending the regexen to handle this is left as an exercise for the reader;).)

(defcustom amrr-pattern-list
  '(("\\$\\$" "\\\\[" "\\\\]")
    ("\\$" "\\\\(" "\\\\)")
    ("\\\\[ck] +a\\|{\\\\[ck] +a}\\|\\\\[ck] *{a}" "ą")
    ("\\\\'c\\|{\\\\'c}\\|\\\\'{c}" "ć")
    ("\\\\[ck] +e\\|{\\\\[ck] +e}\\|\\\\[ck] *{e}" "ę")
    ("\\\\l +\\|{\\\\l}\\|\\\\l{}" "ł")
    ("\\\\'n\\|{\\\\'n}\\|\\\\'{n}" "ń")
    ("\\\\'o\\|{\\\\'o}\\|\\\\'{o}" "ó")
    ("\\\\'s\\|{\\\\'s}\\|\\\\'{s}" "ś")
    ("\\\\'z\\|{\\\\'z}\\|\\\\'{z}" "ź")
    ("\\\\\\.z\\|{\\\\\\.z}\\|\\\\\\.{z}" "ż")
    ("\\\\[ck] +A\\|{\\\\[ck] +A}\\|\\\\[ck] *{A}" "Ą")
    ("\\\\'C\\|{\\\\'C}\\|\\\\'{C}" "Ć")
    ("\\\\[ck] +E\\|{\\\\[ck] +E}\\|\\\\[ck] *{E}" "Ę")
    ("\\\\L +\\|{\\\\L}\\|\\\\L{}" "Ł")
    ("\\\\'N\\|{\\\\'N}\\|\\\\'{N}" "Ń")
    ("\\\\'O\\|{\\\\'O}\\|\\\\'{O}" "Ó")
    ("\\\\'S\\|{\\\\'S}\\|\\\\'{S}" "Ś")
    ("\\\\'Z\\|{\\\\'Z}\\|\\\\'{Z}" "Ź")
    ("\\\\\\.Z\\|{\\\\\\.Z}\\|\\\\\\.{Z}" "Ż"))
  "A list of patterns for the amrr-replace function.  Each entry is a
list.  The car of each list is a regexp to be found; the cdr is a list
of replacement variants.  They will be used in succession: the nth of
them will be used on the nth match, cycling modulo the number of them
if necessary.")

(defmacro advance (list)
  `(setq ,list (cdr ,list)))

(defmacro cycle (position list)
  `(prog1
     (advance ,position)
     (when (null ,position)
       (setq ,position ,list))))

(defvar amrr-replacement-list)
(defvar amrr-current-replacement)

(defun amrr-replace (beg end)
  "Replace all patterns according to amrr-pattern-list (in region if
it is present)."
  (interactive (if (use-region-p)
           (list (region-beginning) (region-end))
         (list (point-min) (point-max))))
  (goto-char beg)
  (dolist (pattern amrr-pattern-list)
    (setq amrr-replacement-list (cdr pattern)
      amrr-current-replacement amrr-replacement-list)
    (while (let (old-cfs case-fold-search)
         (prog2
         (setq case-fold-search nil)
         (re-search-forward
          (car pattern)
          end
          t)
           (setq case-fold-search old-cfs)))
      (replace-match (car amrr-current-replacement) t)
      (cycle amrr-current-replacement amrr-replacement-list))
    (goto-char beg)))
  • AUCTeX has a function, texmathp, that can help you checking if point is inside math mode or not and what started it ($, $$, \(, \[, equation environment, \ensuremath, or \mbox) and is able to recognize comments, verbatim constructs, and if dollar sign is escaped. The Elisp library providing this function shouldn't require actually AUCTeX. You can find it here: git.savannah.gnu.org/cgit/auctex.git/tree/texmathp.el Moreover, AUCTeX has functions to determine current environment or current macro at point, in order to skip tikz stuff. – giordano Jun 9 '13 at 9:25
  • @giordano: thanks, I'll have to look at AUCTeX source some day... – mbork Jun 9 '13 at 12:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.