2 # latexdiff - differences two latex files on the word level
3 # and produces a latex file with the differences marked up.
5 # Copyright (C) 2004-2007 F J Tilmann (tilmann@esc.cam.ac.uk)
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License Version 2 as published by
9 # the Free Software Foundation.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 # Detailed usage information at the end of the file
22 # Version 0.5 A number of minor improvements based on feedback
23 # Deleted blocks are now shown before added blocks
24 # Package specific processing
26 # Version 0.43 unreleased typo in list of styles at the end
27 # Add protect to all \cbstart, \cbend commands
28 # More robust substitution of deleted math commands
30 # Version 0.42 November 06 Bug fixes only
32 # Version 0.4 March 06 option for fast differencing using UNIX diff command, several minor bug fixes (\par bug, improved highlighting of textcmds)
34 # Version 0.3 August 05 improved parsing of displayed math, --allow-spaces
35 # option, several minor bug fixes
37 # Version 0.25 October 04 Fix bug with deleted equations, add math mode commands to safecmd, add | to allowed interpunctuation signs
38 # Version 0.2 September 04 extension to utf-8 and variable encodings
39 # Version 0.1 August 04 First public release
41 # Inserted block for differenceing
42 # use Algorithm::Diff qw(traverse_sequences);
44 # The following BEGIN block contains a verbatim copy of
45 # Ned Konz' Algorithm::Diff package version 1.15 except
46 # that subroutine _longestCommonSubsequence has been replace by
47 # a routine which internally uses the UNIX diff command for
48 # the differencing rather than the Perl routines if the
49 # length of the sequences exceeds some threshold.
50 # Also, all POD documentation has been stripped out.
52 # (the distribution on which this modification is based is available
53 # from http://search.cpan.org/~nedkonz/Algorithm-Diff-1.15
54 # the most recent version can be found via http://search.cpan.org/search?module=Algorithm::Diff )
55 # Please note that the LICENCSE for Algorithm::Diff :
56 # "Copyright (c) 2000-2002 Ned Konz. All rights reserved.
57 # This program is free software;
58 # you can redistribute it and/or modify it under the same terms
60 # The fast-differencing version of latexdiff is provided as a convenience
61 # for latex users under Unix-like systems which have a 'diff' command.
63 # the inlining of Algorithm::Diff violates its license please contact
64 # me and I will modify the latexdiff distribution accordingly.
65 # Frederik Tilmann (tilmann@esc.cam.ac.uk)
66 # Jonathan Paisley is acknowledged for the idea of using the system diff
67 # command to achieve shorter running times
69 package Algorithm::Diff;
71 use vars qw($VERSION @EXPORT_OK @ISA @EXPORT);
72 use integer; # see below in _replaceNextLargerWith() for mod to make
73 # if you don't use this
77 @EXPORT_OK = qw(LCS diff traverse_sequences traverse_balanced sdiff);
78 $VERSION = sprintf('%d.%02d fast', (q$Revision: 1.15 $ =~ /\d+/g));
82 use File::Temp qw/tempfile/;
83 # if larger number of elements in longestCommonSubsequence smaller than
84 # this number, then use internal algorithm, otherwise use UNIX diff
85 use constant THRESHOLD => 100 ;
86 # Detect whether diff --minimal option is available
88 use constant MINIMAL => ( system('diff','--minimal','/dev/null','/dev/null') >> 8 ==0 ? "--minimal" : "" ) ;
92 # McIlroy-Hunt diff algorithm
93 # Adapted from the Smalltalk code of Mario I. Wolczko, <mario@wolczko.com>
94 # by Ned Konz, perl@bike-nomad.com
97 # Create a hash that maps each element of $aCollection to the set of positions
98 # it occupies in $aCollection, restricted to the elements within the range of
99 # indexes specified by $start and $end.
100 # The fourth parameter is a subroutine reference that will be called to
101 # generate a string to use as a key.
102 # Additional parameters, if any, will be passed to this subroutine.
104 # my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen );
106 sub _withPositionsOfInInterval
108 my $aCollection = shift; # array ref
114 for ( $index = $start ; $index <= $end ; $index++ )
116 my $element = $aCollection->[$index];
117 my $key = &$keyGen( $element, @_ );
118 if ( exists( $d{$key} ) )
120 unshift ( @{ $d{$key} }, $index );
127 return wantarray ? %d : \%d;
130 # Find the place at which aValue would normally be inserted into the array. If
131 # that place is already occupied by aValue, do nothing, and return undef. If
132 # the place does not exist (i.e., it is off the end of the array), add it to
133 # the end, otherwise replace the element at that point with aValue.
134 # It is assumed that the array's values are numeric.
135 # This is where the bulk (75%) of the time is spent in this module, so try to
138 sub _replaceNextLargerWith
140 my ( $array, $aValue, $high ) = @_;
144 if ( $high == -1 || $aValue > $array->[-1] )
146 push ( @$array, $aValue );
150 # binary search for insertion point...
154 while ( $low <= $high )
156 $index = ( $high + $low ) / 2;
158 # $index = int(( $high + $low ) / 2); # without 'use integer'
159 $found = $array->[$index];
161 if ( $aValue == $found )
165 elsif ( $aValue > $found )
175 # now insertion point is in $low.
176 $array->[$low] = $aValue; # overwrite next larger
180 # This method computes the longest common subsequence in $a and $b.
182 # Result is array or ref, whose contents is such that
183 # $a->[ $i ] == $b->[ $result[ $i ] ]
184 # foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined.
186 # An additional argument may be passed; this is a hash or key generating
187 # function that should return a string that uniquely identifies the given
188 # element. It should be the case that if the key is the same, the elements
189 # will compare the same. If this parameter is undef or missing, the key
190 # will be the element as a string.
192 # By default, comparisons will use "eq" and elements will be turned into keys
193 # using the default stringizing operator '""'.
195 # Additional parameters, if any, will be passed to the key generation routine.
197 sub _longestCommonSubsequence
199 my $a = shift; # array ref
200 my $b = shift; # array ref
201 my $keyGen = shift; # code ref
202 my $compare; # code ref
205 # Note that these are optimized.
206 if ( !defined($keyGen) ) # optimize for strings
208 $keyGen = sub { $_[0] };
209 $compare = sub { my ( $a, $b ) = @_; $a eq $b };
216 &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ );
220 my ( $aStart, $aFinish, $bStart, $bFinish, $matchVector ) =
221 ( 0, $#$a, 0, $#$b, [] );
223 # Check whether to use internal routine (small number of elements)
224 # or use it as a wrapper for UNIX diff
225 if ( ( $#$a > $#$b ? $#$a : $#$b) < THRESHOLD ) {
226 ### print STDERR "DEBUG: regular longestCommonSubsequence\n";
227 # First we prune off any common elements at the beginning
228 while ( $aStart <= $aFinish
229 and $bStart <= $bFinish
230 and &$compare( $a->[$aStart], $b->[$bStart], @_ ) )
232 $matchVector->[ $aStart++ ] = $bStart++;
236 while ( $aStart <= $aFinish
237 and $bStart <= $bFinish
238 and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) )
240 $matchVector->[ $aFinish-- ] = $bFinish--;
243 # Now compute the equivalence classes of positions of elements
245 _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ );
249 my ( $i, $ai, $j, $k );
250 for ( $i = $aStart ; $i <= $aFinish ; $i++ )
252 $ai = &$keyGen( $a->[$i], @_ );
253 if ( exists( $bMatches->{$ai} ) )
256 for $j ( @{ $bMatches->{$ai} } )
259 # optimization: most of the time this will be true
260 if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j )
266 $k = _replaceNextLargerWith( $thresh, $j, $k );
269 # oddly, it's faster to always test this (CPU cache?).
273 [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ];
281 for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] )
283 $matchVector->[ $link->[1] ] = $link->[2];
288 my ($fha,$fhb,$fna,$fnb,$ele,$key);
289 my ($alines,$blines,$alb,$alf,$blb,$blf);
290 my ($minimal)=MINIMAL;
291 # large number of elements, use system diff
292 ### print STDERR "DEBUG: fast (diff) longestCommonSubsequence\n";
294 ($fha,$fna)=tempfile("DiffA-XXXX") or die "_longestCommonSubsequence: Cannot open tempfile for sequence A";
295 ($fhb,$fnb)=tempfile("DiffB-XXXX") or die "_longestCommonSubsequence: Cannot open tempfile for sequence B";
297 foreach $ele ( @$a ) {
298 $key=&$keyGen( $ele, @_ );
299 $key =~ s/\\/\\\\/g ;
300 $key =~ s/\n/\\n/sg ;
301 print $fha "$key\n" ;
305 foreach $ele ( @$b ) {
306 $key=&$keyGen( $ele, @_ );
307 $key =~ s/\\/\\\\/g ;
308 $key =~ s/\n/\\n/sg ;
309 print $fhb "$key\n" ;
313 open(DIFFPIPE, "diff $minimal $fna $fnb |") or die "_longestCommonSubsequence: Cannot launch diff process. $!" ;
314 # The diff line numbering begins with 1, but Perl subscripts start with 0
315 # We follow the diff numbering but substract 1 when assigning to matchVector
316 $aStart++; $bStart++ ; $aFinish++ ; $bFinish++ ;
317 while( <DIFFPIPE> ) {
318 if ( ($alines,$blines) = ( m/^(\d*(?:,\d*)?)?c(\d*(?:,\d*)?)?$/ ) ) {
319 ($alb,$alf)=split(/,/,$alines);
320 ($blb,$blf)=split(/,/,$blines);
321 $alf=$alb unless defined($alf);
322 $blf=$blb unless defined($blf);
323 while($aStart < $alb ) {
324 $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ;
326 # check for consistency
327 $bStart==$blb or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency in changed sequence";
331 elsif ( ($alb,$blines) = ( m/^(\d*)a(\d*(?:,\d*)?)$/ ) ) {
332 ($blb,$blf)=split(/,/,$blines);
333 $blf=$blb unless defined($blf);
334 while ( $bStart < $blb ) {
335 $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ;
337 $aStart==$alb+1 or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency in appended sequence near elements $aStart and $bStart";
340 elsif ( ($alines,$blb) = ( m/^(\d*(?:,\d*)?)d(\d*)$/ ) ) {
341 ($alb,$alf)=split(/,/,$alines);
342 $alf=$alb unless defined($alf);
343 while ( $aStart < $alb ) {
344 $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ;
346 $bStart==$blb+1 or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency in deleted sequence near elements $aStart and $bStart";
349 elsif ( m/^Binary files/ ) {
350 # if diff reports it is a binary file force --text mode. I do not like
351 # to always use this option because it is probably only available in GNU diff
352 open(DIFFPIPE, "diff --text $fna $fnb |") or die "Cannot launch diff process. $!" ;
354 # Default: just skip line
356 while ($aStart <= $aFinish ) {
357 $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ;
359 $bStart==$bFinish+1 or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency at end";
361 # check whether a system error has occurred or return status is greater than or equal to 5
362 if ( $! || ($? >> 8) > 5) {
363 print STDERR "diff process failed with exit code ", ($? >> 8), " $!\n";
368 return wantarray ? @$matchVector : $matchVector;
371 sub traverse_sequences
373 my $a = shift; # array ref
374 my $b = shift; # array ref
375 my $callbacks = shift || {};
377 my $matchCallback = $callbacks->{'MATCH'} || sub { };
378 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
379 my $finishedACallback = $callbacks->{'A_FINISHED'};
380 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
381 my $finishedBCallback = $callbacks->{'B_FINISHED'};
382 my $matchVector = _longestCommonSubsequence( $a, $b, $keyGen, @_ );
384 # Process all the lines in @$matchVector
390 for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ )
392 my $bLine = $matchVector->[$ai];
393 if ( defined($bLine) ) # matched
395 &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine;
396 &$matchCallback( $ai, $bi++, @_ );
400 &$discardACallback( $ai, $bi, @_ );
404 # The last entry (if any) processed was a match.
405 # $ai and $bi point just past the last matching lines in their sequences.
407 while ( $ai <= $lastA or $bi <= $lastB )
411 if ( $ai == $lastA + 1 and $bi <= $lastB )
413 if ( defined($finishedACallback) )
415 &$finishedACallback( $lastA, @_ );
416 $finishedACallback = undef;
420 &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB;
425 if ( $bi == $lastB + 1 and $ai <= $lastA )
427 if ( defined($finishedBCallback) )
429 &$finishedBCallback( $lastB, @_ );
430 $finishedBCallback = undef;
434 &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA;
438 &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA;
439 &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB;
445 sub traverse_balanced
447 my $a = shift; # array ref
448 my $b = shift; # array ref
449 my $callbacks = shift || {};
451 my $matchCallback = $callbacks->{'MATCH'} || sub { };
452 my $discardACallback = $callbacks->{'DISCARD_A'} || sub { };
453 my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { };
454 my $changeCallback = $callbacks->{'CHANGE'};
455 my $matchVector = _longestCommonSubsequence( $a, $b, $keyGen, @_ );
457 # Process all the lines in match vector
468 # Find next match indices $ma and $mb
469 do { $ma++ } while ( $ma <= $#$matchVector && !defined $matchVector->[$ma] );
471 last if $ma > $#$matchVector; # end of matchVector?
472 $mb = $matchVector->[$ma];
474 # Proceed with discard a/b or change events until
476 while ( $ai < $ma || $bi < $mb )
479 if ( $ai < $ma && $bi < $mb )
483 if ( defined $changeCallback )
485 &$changeCallback( $ai++, $bi++, @_ );
489 &$discardACallback( $ai++, $bi, @_ );
490 &$discardBCallback( $ai, $bi++, @_ );
495 &$discardACallback( $ai++, $bi, @_ );
501 &$discardBCallback( $ai, $bi++, @_ );
506 &$matchCallback( $ai++, $bi++, @_ );
509 while ( $ai <= $lastA || $bi <= $lastB )
511 if ( $ai <= $lastA && $bi <= $lastB )
515 if ( defined $changeCallback )
517 &$changeCallback( $ai++, $bi++, @_ );
521 &$discardACallback( $ai++, $bi, @_ );
522 &$discardBCallback( $ai, $bi++, @_ );
525 elsif ( $ai <= $lastA )
527 &$discardACallback( $ai++, $bi, @_ );
533 &$discardBCallback( $ai, $bi++, @_ );
542 my $a = shift; # array ref
543 my $matchVector = _longestCommonSubsequence( $a, @_ );
546 for ( $i = 0 ; $i <= $#$matchVector ; $i++ )
548 if ( defined( $matchVector->[$i] ) )
550 push ( @retval, $a->[$i] );
553 return wantarray ? @retval : \@retval;
558 my $a = shift; # array ref
559 my $b = shift; # array ref
562 my $discard = sub { push ( @$hunk, [ '-', $_[0], $a->[ $_[0] ] ] ) };
563 my $add = sub { push ( @$hunk, [ '+', $_[1], $b->[ $_[1] ] ] ) };
564 my $match = sub { push ( @$retval, $hunk ) if scalar(@$hunk); $hunk = [] };
565 traverse_sequences( $a, $b,
566 { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ );
568 return wantarray ? @$retval : $retval;
573 my $a = shift; # array ref
574 my $b = shift; # array ref
576 my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) };
577 my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) };
579 push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] );
582 push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] );
589 DISCARD_A => $discard,
595 return wantarray ? @$retval : $retval;
600 import Algorithm::Diff qw(traverse_sequences);
601 # End of inserted block for stand-alone version
608 my ($algodiffversion)=split(/ /,$Algorithm::Diff::VERSION);
611 my ($versionstring)=<<EOF ;
612 This is LATEXDIFF 0.5 (Algorithm::Diff $Algorithm::Diff::VERSION)
613 (c) 2004-2007 F J Tilmann
616 # Configuration variables: these have to be visible from the subroutines
617 my $MINWORDSBLOCK=3; # minimum number of tokens to form an independent block
618 # shorter identical blocks will be merged to the previous word
619 my $FLOATENV='(?:figure|table|plate)[\w\d*@]*' ; # Environments in which FL variants of defined commands are used
620 my $PICTUREENV='(?:picture|DIFnomarkup)[\w\d*@]*' ; # Environments in which all change markup is removed
621 my $MATHENV='(?:equation|displaymath|DOLLARDOLLAR)' ; # Environments turning on display math mode (code also knows about \[ and \]
622 my $MATHREPL='displaymath'; # Environment introducing deleted maths blocks
623 my $MATHARRENV='(?:eqnarray|align|gather|multiline|flalign)[*]?' ; # Environments turning on eqnarray math mode
624 my $MATHARRREPL='eqnarray*'; # Environment introducing deleted maths blocks
625 my $ARRENV='(?:array|[pbvBV]matrix)'; # Enviroments making arrays in math mode. The underlining style does not cope well with those - as a result in-text math environments are surrounded by \mbox{ } if any of these commands is used in an inline math block
626 my $COUNTERCMD='(?:footnote|part|section|subsection|subsubsection|paragraph|subparagraph)'; # textcmds which are associated with a counter
627 # If any of these commands occur in a deleted block
628 # they will be succeeded by an \addtocounter{...}{-1}
629 # for the associated counter such that the overall numbers
630 # should be the same as in the new file
633 # If at all possible, do not change these as parts of the program
634 # depend on the actual name (particularly post-processing)
635 # At the very least adapt subroutine postprocess to new tokens.
636 my $ADDMARKOPEN='\DIFaddbegin '; # Token to mark begin of appended text
637 my $ADDMARKCLOSE='\DIFaddend '; # Token to mark end of appended text
638 my $ADDOPEN='\DIFadd{'; # To mark begin of added text passage
639 my $ADDCLOSE='}'; # To mark end of added text passage
640 my $ADDCOMMENT='DIF > '; # To mark added comment line
641 my $DELMARKOPEN='\DIFdelbegin '; # Token to mark begin of deleted text
642 my $DELMARKCLOSE='\DIFdelend '; # Token to mark end of deleted text
643 my $DELOPEN='\DIFdel{'; # To mark begin of deleted text passage
644 my $DELCLOSE='}'; # To mark end of deleted text passage
645 my $DELCMDOPEN='%DIFDELCMD < '; # To mark begin of deleted commands (must begin with %, i.e., be a comment
646 my $DELCMDCLOSE="%%%\n"; # To mark end of deleted commands (must end with a new line)
647 my $AUXCMD='%DIFAUXCMD' ; # follows auxiliary commands put in by latexdiff to make difference file legal
648 # auxiliary commands must be on a line of their own
649 my $DELCOMMENT='DIF < '; # To mark deleted comment line
652 # main local variables:
653 my @TEXTCMDLIST=(); # array containing patterns of commands with text arguments
654 my @TEXTCMDEXCL=(); # array containing patterns of commands without text arguments (if a pattern
655 # matches both TEXTCMDLIST and TEXTCMDEXCL it is excluded)
656 my @CONTEXT1CMDLIST=(); # array containing patterns of commands with text arguments (subset of text commands),
657 # but which cause confusion if used out of context (e.g. \caption).
658 # In deleted passages, the command will be disabled but its argument is marked up
659 # Otherwise they behave exactly like TEXTCMD's
660 my @CONTEXT1CMDEXCL=(); # exclude list for above, but always empty
661 my @CONTEXT2CMDLIST=(); # array containing patterns of commands with text arguments, but which fail cause confusion
662 # if used out of context (e.g. \title). They and their arguments will be disabled in deleted
664 my @CONTEXT2CMDEXCL=(); # exclude list for above, but always empty
666 my @SAFECMDLIST=(); # array containing patterns of safe commands (which do not break when in the argument of DIFadd or DIFDEL)
672 my ($newpreamble,$oldpreamble);
673 my (@newpreamble,@oldpreamble,@diffpreamble,@diffbody);
674 my ($latexdiffpreamble);
675 my ($oldbody, $newbody, $diffbo);
676 my ($oldpost, $newpost);
679 my ($type,$subtype,$floattype,$config,$preamblefile,$encoding,
680 $showpreamble,$showsafe,$showtext,$showconfig,$showall,
681 $replacesafe,$appendsafe,$excludesafe,
682 $replacetext,$appendtext,$excludetext,
683 $replacecontext1,$appendcontext1,
684 $replacecontext2,$appendcontext2,
685 $help,$verbose,$version,$ignorewarnings,$allowspaces,$flatten,$debug);
687 @appendsafelist,@excludesafelist,
688 @appendtextlist,@excludetextlist,
689 @appendcontext1list,@appendcontext2list,
691 my ($assign,@config);
692 # Hash where keys corresponds to the names of all included packages (including the documentclass as another package
693 # the optional arguments to the package are the values of the hash elements
698 $floattype='FLOATSAFE';
702 # define character properties
703 sub IsNonAsciiPunct { return <<'END' # Unicode punctuation but excluding ASCII punctuation
708 sub IsNonAsciiS { return <<'END' # Unicode symbol but excluding ASCII
718 Getopt::Long::Configure('bundling');
719 GetOptions('type|t=s' => \$type,
720 'subtype|s=s' => \$subtype,
721 'floattype|f=s' => \$floattype,
722 'config|c=s' => \@configlist,
723 'preamble|p=s' => \$preamblefile,
724 'encoding|e=s' => \$encoding,
725 'exclude-safecmd|A=s' => \@excludesafelist,
726 'replace-safecmd=s' => \$replacesafe,
727 'append-safecmd|a=s' => \@appendsafelist,
728 'exclude-textcmd|X=s' => \@excludetextlist,
729 'replace-textcmd=s' => \$replacetext,
730 'append-textcmd|x=s' => \@appendtextlist,
731 'replace-context1cmd=s' => \$replacecontext1,
732 'append-context1cmd=s' => \@appendcontext1list,
733 'replace-context2cmd=s' => \$replacecontext2,
734 'append-context2cmd=s' => \@appendcontext2list,
735 'show-preamble' => \$showpreamble,
736 'show-safecmd' => \$showsafe,
737 'show-textcmd' => \$showtext,
738 'show-config' => \$showconfig,
739 'show-all' => \$showall,
740 'packages=s' => \@packagelist,
741 'verbose|V' => \$verbose,
742 'ignore-warnings' => \$ignorewarnings,
743 'allow-spaces' => \$allowspaces,
744 'flatten' => \$flatten,
745 'version' => \$version,
746 'help|h|H' => \$help);
757 print STDERR $versionstring if $verbose;
759 if (defined($showall)){
760 $showpreamble=$showsafe=$showtext=$showconfig=1;
763 # setting extra preamble commands
764 if (defined($preamblefile)) {
765 $latexdiffpreamble=join "\n",(extrapream($preamblefile),"");
767 $latexdiffpreamble=join "\n",(extrapream($type,$subtype,$floattype),"");
770 # setting up @SAFECMDLIST and @SAFECMDEXCL
771 if (defined($replacesafe)) {
772 init_regex_arr_ext(\@SAFECMDLIST,$replacesafe);
774 init_regex_arr_data(\@SAFECMDLIST, "SAFE COMMANDS");
776 foreach $appendsafe ( @appendsafelist ) {
777 init_regex_arr_ext(\@SAFECMDLIST, $appendsafe);
779 foreach $excludesafe ( @excludesafelist ) {
780 init_regex_arr_ext(\@SAFECMDEXCL, $excludesafe);
783 # Special: treat all cite commands as safe except in UNDERLINE and FONTSTRIKE mode
784 # (there is a conflict between citation and ulem package, see
785 # package documentation)
786 if ( uc($type) ne "UNDERLINE" && uc($type) ne "FONTSTRIKE" && uc($type) ne "CULINECHBAR" ) {
787 push (@SAFECMDLIST, qr/^cite.*$/);
790 # setting up @TEXTCMDLIST and @TEXTCMDEXCL
791 if (defined($replacetext)) {
792 init_regex_arr_ext(\@TEXTCMDLIST,$replacetext);
794 init_regex_arr_data(\@TEXTCMDLIST, "TEXT COMMANDS");
796 foreach $appendtext ( @appendtextlist ) {
797 init_regex_arr_ext(\@TEXTCMDLIST, $appendtext);
799 foreach $excludetext ( @excludetextlist ) {
800 init_regex_arr_ext(\@TEXTCMDEXCL, $excludetext);
804 # setting up @CONTEXT1CMDLIST ( @CONTEXT1CMDEXCL exist but is always empty )
805 if (defined($replacecontext1)) {
806 init_regex_arr_ext(\@CONTEXT1CMDLIST,$replacecontext1);
808 init_regex_arr_data(\@CONTEXT1CMDLIST, "CONTEXT1 COMMANDS");
810 foreach $appendcontext1 ( @appendcontext1list ) {
811 init_regex_arr_ext(\@CONTEXT1CMDLIST, $appendcontext1);
815 # setting up @CONTEXT2CMDLIST ( @CONTEXT2CMDEXCL exist but is always empty )
816 if (defined($replacecontext2)) {
817 init_regex_arr_ext(\@CONTEXT2CMDLIST,$replacecontext2);
819 init_regex_arr_data(\@CONTEXT2CMDLIST, "CONTEXT2 COMMANDS");
821 foreach $appendcontext2 ( @appendcontext2list ) {
822 init_regex_arr_ext(\@CONTEXT2CMDLIST, $appendcontext2);
828 # setting configuration variables
830 foreach $config ( @configlist ) {
832 open(FILE,$config) or die ("Couldn't open configuration file $config: $!");
835 next if /^\s*#/ || /^\s*%/ || /^\s*$/ ;
841 # foreach ( split(",",$config) ) {
844 push @config,split(",",$config)
847 foreach $assign ( @config ) {
848 $assign=~ m/\s*(\w*)\s*=\s*(\S*)\s*$/ or die "Illegal assignment $assign in configuration list (must be variable=value)";
849 if ( $1 eq "MINWORDSBLOCK" ) { $MINWORDSBLOCK = $2; }
850 elsif ( $1 eq "FLOATENV" ) { $FLOATENV = $2 ; }
851 elsif ( $1 eq "PICTUREENV" ) { $PICTUREENV = $2 ; }
852 elsif ( $1 eq "MATHENV" ) { $MATHENV = $2 ; }
853 elsif ( $1 eq "MATHREPL" ) { $MATHREPL = $2 ; }
854 elsif ( $1 eq "MATHARRENV" ) { $MATHARRENV = $2 ; }
855 elsif ( $1 eq "MATHARRREPL" ) { $MATHARRREPL = $2 ; }
856 elsif ( $1 eq "ARRENV" ) { $ARRENV = $2 ; }
857 elsif ( $1 eq "COUNTERCMD" ) { $COUNTERCMD = $2 ; }
858 else { die "Unknown variable $1 in assignment.";}
862 foreach $pkg ( @packagelist ) {
863 map { $packages{$_}="" } split(/,/,$pkg) ;
867 print "\nPreamble commands:\n";
868 print $latexdiffpreamble ;
872 print "\nCommands safe within scope of $ADDOPEN $ADDCLOSE and $DELOPEN $DELCLOSE (unless excluded):\n";
873 print_regex_arr(@SAFECMDLIST);
874 print "\nCommands not safe within scope of $ADDOPEN $ADDCLOSE and $DELOPEN $DELCLOSE :\n";
875 print_regex_arr(@SAFECMDEXCL);
879 print "\nCommands with last argument textual (unless excluded) and safe in every context:\n";
880 print_regex_arr(@TEXTCMDLIST);
881 print "\nContext1 commands (last argument textual, command will be disabled in deleted passages, last argument will be shown as plain text):\n";
882 print_regex_arr(@CONTEXT1CMDLIST);
883 print "\nContext2 commands (last argument textual, command ant its argument will be disabled in deleted passages):\n";
884 print_regex_arr(@CONTEXT2CMDLIST);
885 print "\nExclude list of Commands with last argument not textual (overrides patterns above):\n";
886 print_regex_arr(@TEXTCMDEXCL);
890 print "Configuration variables:\n";
891 print "MINWORDSBLOCK=$MINWORDSBLOCK\n";
892 print "FLOATENV=$FLOATENV\n";
893 print "PICTUREENV=$PICTUREENV\n";
894 print "MATHENV=$MATHENV\n";
895 print "MATHREPL=$MATHREPL\n";
896 print "MATHARRENV=$MATHARRENV\n";
897 print "MATHARRREPL=$MATHARRREPL\n";
898 print "ARRENV=$ARRENV\n";
899 print "COUNTERCMD=$COUNTERCMD\n";
901 if ($showconfig || $showtext || $showsafe || $showpreamble) {
904 print STDERR "2 and only 2 non-option arguments required. Write latexdiff -h to get help\n";
908 # Are extra spaces between command arguments permissible?
916 # append context lists to text lists (as text property is implied)
917 push @TEXTCMDLIST, @CONTEXT1CMDLIST;
918 push @TEXTCMDLIST, @CONTEXT2CMDLIST;
921 # Patterns. These are used by some of the subroutines, too
922 # I can only define them down here because value of extraspace depends on an option
923 my $pat0 = '(?:[^{}]|\\\{|\\\})*';
924 my $pat1 = '(?:[^{}]|\\\{|\\\}|\{'.$pat0.'\})*';
925 my $pat2 = '(?:[^{}]|\\\{|\\\}|\{'.$pat1.'\})*'; #
926 my $pat3 = '(?:[^{}]|\\\{|\\\}|\{'.$pat2.'\})*';
927 my $pat4 = '(?:[^{}]|\\\{|\\\}|\{'.$pat3.'\})*';
928 my $brat0 = '(?:[^\[\]]|\\\[|\\\])*';
930 my $quotemarks = '(?:\'\')|(?:\`\`)';
931 my $punct='[0.,\/\'\`:;\"\?\(\)\[\]!~\p{IsNonAsciiPunct}\p{IsNonAsciiS}]';
932 my $number='-?\d*\.\d*';
933 my $mathpunct='[+=<>\-\|]';
935 my $coords= '[\-.,\s\d]*';
936 # word: sequence of letters or accents followed by letter
937 my $word='(?:[-\w\d*]|\\\\[\"\'\`~^][A-Za-z\*])+';
938 my $cmdleftright='\\\\(?:left|right)\s*(?:[()\[\]|]|\\\\(?:[|{}]|\w+))';
940 my $cmdoptseq='\\\\[\w\d\*]+'.$extraspace.'(?:(?:\['.$brat0.'\]|\{'. $pat4 . '\}|\(' . $coords .'\))'.$extraspace.')*';
941 my $oneletcmd='\\\\.(?:\['.$brat0.'\]|\{'. $pat4 . '\})*';
942 my $math='\$(?:[^$]|\\\$)*?\$|\\\\[(].*?\\\\[)]';
943 # my $math='\$(?:[^$]|\\\$)*\$';
944 my $comment='%.*?\n';
945 my $pat=qr/(?:\A\s*)?(?:${and}|${quotemarks}|${number}|${word}|$cmdleftright|${cmdoptseq}|${math}|${oneletcmd}|${comment}|${punct}|${mathpunct}|\{|\})\s*/ ;
949 # now we are done setting up and can start working
950 my ($oldfile, $newfile) = @ARGV;
952 $encoding=guess_encoding($newfile) unless defined($encoding);
954 $encoding = "utf8" if $encoding =~ m/^utf8/i ;
955 if (lc($encoding) eq "utf8" ) {
956 binmode(STDOUT, ":utf8");
957 binmode(STDERR, ":utf8");
960 $old=read_file_with_encoding($oldfile,$encoding);
961 $new=read_file_with_encoding($newfile,$encoding);
968 ($oldpreamble,$oldbody,$oldpost)=splitdoc($old,'\\\\begin\{document\}','\\\\end\{document\}');
971 ($newpreamble,$newbody,$newpost)=splitdoc($new,'\\\\begin\{document\}','\\\\end\{document\}');
975 $oldbody=flatten($oldbody,$oldpreamble,$oldfile,$encoding);
976 $newbody=flatten($newbody,$newpreamble,$newfile,$encoding);
979 if ( length $oldpreamble && length $newpreamble ) {
980 @oldpreamble = split /\n/, $oldpreamble;
981 @newpreamble = split /\n/, $newpreamble;
983 %packages=list_packages(@newpreamble) unless %packages;
984 if (defined $packages{"hyperref"} ) {
985 print STDERR "hyperref package detected.\n" if $verbose ;
986 $latexdiffpreamble =~ s/\{\\DIFadd\}/{\\DIFaddtex}/g;
987 $latexdiffpreamble =~ s/\{\\DIFdel\}/{\\DIFdeltex}/g;
988 $latexdiffpreamble .= join "\n",(extrapream("HYPERREF"),"");
990 # insert dummy first line such that line count begins with line 1 (rather than perl's line 0) - just so that line numbers inserted by linediff are correct
991 unshift @newpreamble,'';
992 unshift @oldpreamble,'';
993 print STDERR "Differencing preamble.\n" if $verbose;
994 @diffpreamble = linediff(\@oldpreamble, \@newpreamble);
995 # remove dummy line again
997 push @diffpreamble,$latexdiffpreamble;
998 push @diffpreamble,'\begin{document}';
1000 elsif ( !length $oldpreamble && !length $newpreamble ) {
1003 print STDERR "Either both texts must have preamble or neither text must have the preamble.\n";
1007 if (defined $packages{"amsmath"} ) {
1008 print STDERR "amsmath package detected.\n" if $verbose ;
1009 $MATHARRREPL='align*';
1012 print STDERR "Preprocessing body. " if $verbose;
1013 my ($oldleadin,$newleadin)=preprocess($oldbody,$newbody);
1016 # run difference algorithm
1017 @diffbody=bodydiff($oldbody, $newbody);
1018 $diffbo=join("",@diffbody);
1019 print STDERR "(",exetime()," s)\n","Postprocessing body. \n " if $verbose;
1020 postprocess($diffbo);
1021 $diffall =join("\n",@diffpreamble) ;
1022 $diffall .= "$newleadin$diffbo" ;
1023 $diffall .= "\\end{document}$newpost" if length $newpreamble ;
1024 if ( lc($encoding) ne "utf8" && lc($encoding) ne "ascii" ) {
1025 print STDERR "Encoding output file to $encoding\n" if $verbose;
1026 $diffall=Encode::encode($encoding,$diffall);
1032 print STDERR "(",exetime()," s)\n","Done.\n" if $verbose;
1036 ## guess_encoding(filename)
1037 ## reads the first 20 lines of filename and looks for call of inputenc package
1038 ## if found, return the option of this package (encoding), otherwise return ascii
1039 sub guess_encoding {
1042 open (FH, $filename) or die("Couldn't open $filename: $!");
1045 next if /^\s*%/; # skip comment lines
1046 if (m/\\usepackage\[(\w*?)\]\{inputenc\}/) {
1050 last if (++$i > 20 ); # scan at most 20 non-comment lines
1057 sub read_file_with_encoding {
1059 my ($filename, $encoding) = @_;
1061 if (lc($encoding) eq "utf8" ) {
1062 open (FILE, "<:utf8",$filename) or die("Couldn't open $filename: $!");
1063 local $/ ; # locally set record operator to undefined, ie. enable whole-file mode
1065 } elsif ( lc($encoding) eq "ascii") {
1066 open (FILE, $filename) or die("Couldn't open $filename: $!");
1067 local $/ ; # locally set record operator to undefined, ie. enable whole-file mode
1071 open (FILE, "<",$filename) or die("Couldn't open $filename: $!");
1072 local $/ ; # locally set record operator to undefined, ie. enable whole-file mode
1074 print STDERR "Converting $filename from $encoding to utf8\n" if $verbose;
1075 $output=Encode::decode($encoding,$output);
1081 # %packages=list_packages(@preamble)
1082 # scans the arguments for \documentclass and \usepackage statements and constructs a hash
1083 # whose keys are the included packages, and whose values are the associated optional arguments
1087 foreach $line ( @preamble ) {
1088 # get rid of comments
1089 $line=~s/(?<!\\)%.*$// ;
1090 if ( $line =~ m/\\(?:documentclass|usepackage)(?:\[(.+?)\])?\{(.*?)\}/ ) {
1091 # print STDERR "Found something: |$line|\n";
1102 # flatten($text,$preamble,$filename,$encoding)
1103 # expands \input and \include commands within text
1104 # premable is scanned for includeonly commands
1105 # encoding is the encoding
1107 my ($text,$preamble,$filename,$encoding)=@_;
1108 my ($includeonly,$dirname,$fname,$newpage,$replacement);
1109 require File::Basename ;
1110 require File::Spec ;
1111 $dirname = File::Basename::dirname($filename);
1113 if ( ($includeonly) = ($preamble =~ m/\\includeonly{(.*?)}/ ) ) {
1114 $includeonly =~ s/,/|/g;
1116 $includeonly = '.*?';
1119 print STDERR "DEBUG: includeonly $includeonly\n";
1121 $text=~s/\\input{(.*?)}|\\include{(${includeonly}(?:\.tex)?)}/{
1122 $fname = $1 if defined($1) ;
1123 $fname = $2 if defined($2) ;
1124 # # add tex extension unless there is a three letter extension already
1125 $fname .= ".tex" unless $fname =~ m|\.\w{3}|;
1126 print STDERR "Include file $fname\n" if $verbose;
1127 print STDERR "DEBUG looking for file ",File::Spec->catfile($dirname,$fname), "\n";
1128 # content of file becomes replacement value, add \newpage if the command was include
1129 $replacement=read_file_with_encoding(File::Spec->catfile($dirname,$fname), $encoding) or die
1130 "Couldn't find file ",File::Spec->catfile($dirname,$fname),": $!";
1131 $newpage=(defined($2)? " \\newpage " : "") ;
1132 "$newpage$replacement$newpage";
1139 # print_regex_arr(@arr)
1140 # prints regex array without x-ism expansion put in by pearl to stdout
1141 sub print_regex_arr {
1143 $dumstring = join(" ",@_); # PERL generates string (?-xism:^ref$) for quoted refex ^ref$
1144 $dumstring =~ s/\(\?-xism:\^(.*?)\$\)/$1/g; # remove string and ^,$ marks before output
1145 print $dumstring,"\n";
1149 # @lines=extrapream($type)
1150 # reads line from appendix (end of file after __END__ token)
1153 my @retval=("%DIF PREAMBLE EXTENSION ADDED BY LATEXDIFF") ;
1160 open (FILE,$type) or die "Cannot open preamble file $type: $!";
1161 print STDERR "Reading preamble file $type\n" if $verbose ;
1164 if ( $_ =~ m/%DIF PREAMBLE/ ) {
1165 push (@retval,"$_");
1167 push (@retval,"$_ %DIF PREAMBLE");
1171 else { # not (-f $type)
1172 $type=uc($type); # upcase argument
1173 print STDERR "Preamble Internal Type $type\n" if $verbose;
1175 if ( m/^%DIF $type/ ) {
1177 elsif ( m/^%DIF END $type/ ) {
1180 push (@retval,"$_ %DIF PREAMBLE") if $copy;
1183 print STDERR "\nPreamble style $type not implemented.\n";
1184 print STDERR "Write latexdiff -h to get help with available styles\n";
1187 seek DATA,0,0; # rewind DATA handle to file begin
1190 push (@retval,"%DIF END PREAMBLE EXTENSION ADDED BY LATEXDIFF") ;
1195 # ($part1,$part2,$part3)=splitdoc($text,$word1,$word2)
1196 # splits $text into 3 parts at $word1 and $word2.
1197 # if neither $word1 nor $word2 exist, $part1 and $part3 are empty, $part2 is $text
1198 # If only $word1 or $word2 exist but not the other, output an error message.
1200 # NB this version avoids $` and $' for performance reason although it only makes a tiny difference
1201 # (in one test gain a tenth of a second for a 30s run)
1203 my ($text,$word1,$word2)=@_;
1204 my ($part1,$part2,$part3)=("","","");
1207 if ( $text =~ m/(^[^%]*)($word1)/mg ) {
1209 $part1=substr($text,0,$pos-length($2));
1210 $rest=substr($text,$pos);
1211 if ( $rest =~ m/(^[^%]*)($word2)/mg ) {
1213 $part2=substr($rest,0,$pos-length($2));
1214 $part3=substr($rest,$pos);
1217 die "$word1 and $word2 not in the correct order or not present as a pair." ;
1221 die "$word2 present but not $word1." if ( $text =~ m/(^[^%]*)$word2/ms );
1223 return ($part1,$part2,$part3);
1230 # bodydiff($old,$new)
1232 my ($oldwords, $newwords) = @_;
1235 print STDERR "(",exetime()," s)\n","Splitting into latex tokens \n" if $verbose;
1236 print STDERR "Parsing $oldfile \n" if $verbose;
1237 my @oldwords = splitlatex($oldwords);
1238 print STDERR "Parsing $newfile \n" if $verbose;
1239 my @newwords = splitlatex($newwords);
1242 print STDERR "(",exetime()," s)\n","Pass 1: Expanding text commands and merging isolated identities with changed blocks " if $verbose;
1243 pass1(\@oldwords, \@newwords);
1246 print STDERR "(",exetime()," s)\n","Pass 2: inserting DIF tokens and mark up. " if $verbose;
1248 @retwords=pass2(\@oldwords, \@newwords);
1256 # @words=splitlatex($string)
1257 # split string according to latex rules
1258 # Each element of words is either
1259 # a word (including trailing spaces and punctuation)
1263 my @retval=($string =~ m/$pat/osg);
1265 if (length($string) != length(join("",@retval))) {
1266 print STDERR "\nWARNING: Inconsistency in length of input string and parsed string:\n This often indicates faulty or non-standard latex code.\n In many cases you can ignore this and the following warning messages.\n Note that character numbers in the following are counted beginning after \\begin{document} and are only approximate." unless $ignorewarnings;
1267 print STDERR "DEBUG Original length ",length($string)," Parsed length ",length(join("",@retval)),"\n" if $debug;
1268 print STDERR "DEBUG Input string: |$string|\n" if (length($string)<500) && $debug;
1269 print STDERR "DEBUG Token parsing: |",join("+",@retval),"|\n" if (length($string)<500) && $debug ;
1271 # slow way only do this if other m//sg method fails
1273 while ( $string =~ m/$pat/osg ) {
1275 if ($last + length $& != pos $string ) {
1276 my $pos=pos($string);
1277 my $offset=30<$last ? 30 : $last;
1278 my $dum=substr($string,$last-$offset,$pos-$last+2*$offset);
1283 unless ($ignorewarnings) {
1284 print STDERR "\n$dum1\n";
1285 print STDERR " " x 30,"^" x ($pos-$last)," " x 30,"\n";
1286 print STDERR "Missing characters near word " . (scalar @retval) . " character index: " . $last . "-" . pos($string) . " Length: " . length($match) . " Match: |$match| (expected match marked above).\n";
1288 # put in missing characters `by hand'
1289 push (@retval, substr($dum,$offset,$pos-$last-length($match)));
1290 # Note: there seems to be a bug in substr with utf8 that made the following line output substr which were too long,
1291 # using dum instead appears to work
1292 # push (@retval, substr($string,$last, pos($string)-$last-length($match)));
1294 push (@retval, $match);
1303 # pass1( \@seq1,\@seq2)
1304 # Look for differences between seq1 and seq2.
1305 # Where an common-subsequence block is flanked by deleted or appended blocks,
1306 # and is shorter than $MINWORDSBLOCK words it is appended
1307 # to the last deleted or appended word. If the block contains tokens other than words
1308 # or punctuation it is not merged.
1309 # Deleted or appended block consisting of words and safe commands only are
1310 # also merged, to prevent break-up in pass2 (after previous isolated words have been removed)
1311 # If there are commands with textual arguments (e.g. \caption) both in corresponding
1312 # appended and deleted blocks split them such that the command and opening bracket
1313 # are one token, then the rest is split up following standard rules, and the closing
1314 # bracket is a separate token, ie. turn
1315 # "\caption{This is a textual argument}" into
1316 # ("\caption{","This ","is ","a ","textual ","argument","}")
1317 # No return value. Destructively changes sequences
1322 my $len1 = scalar @$seq1;
1323 my $len2 = scalar @$seq2;
1324 my $wpat=qr/^(?:[a-zA-Z.,'`:;?()!]*)[\s~]*$/; #'
1326 my ($last1,$last2)=(-1,-1) ;
1334 my (@delmid,@addmid,@dummy);
1336 my ($addcmds,$delcmds,$matchindex);
1337 my ($addtextblocks,$deltextblocks);
1338 my ($addtokcnt,$deltokcnt,$mattokcnt)=(0,0,0);
1339 my ($addblkcnt,$delblkcnt,$matblkcnt)=(0,0,0);
1341 my $adddiscard = sub {
1344 # just after an unchanged block
1345 # print STDERR "Unchanged block $cnt, $last1,$last2 \n";
1346 if ($cnt < $MINWORDSBLOCK
1348 grep { /^$wpat/ || ( /^\\([\w\d\*]+)((?:\[$brat0\]|\{$pat4\})*)/o
1349 && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL)
1350 && scalar(@dummy=split(" ",$2))<3 ) }
1352 # merge identical blocks shorter than $MINWORDSBLOCK
1353 # and only containing ordinary words
1354 # with preceding different word
1355 # We cannot carry out this merging immediately as this
1356 # would change the index numbers of seq1 and seq2 and confuse
1357 # the algorithm, instead we store in @$todo where we have to merge
1358 push(@$todo, [ $last1,$last2,$cnt,@$block ]);
1361 $cnt=0; $last1=-1; $last2=-1;
1364 my $discard=sub { $deltokcnt++;
1365 &$adddiscard; #($_[0],$_[1]);
1366 push(@$delblock,[ $seq1->[$_[0]],$_[0] ]);
1369 my $add = sub { $addtokcnt++;
1370 &$adddiscard; #($_[0],$_[1]);
1371 push(@$addblock,[ $seq2->[$_[1]],$_[1] ]);
1374 my $match = sub { $mattokcnt++;
1375 if ($cnt==0) { # first word of matching sequence after changed sequence or at beginning of word sequence
1376 $deltextblocks = extracttextblocks($delblock);
1377 $delblkcnt++ if scalar @$delblock;
1378 $addtextblocks = extracttextblocks($addblock);
1379 $addblkcnt++ if scalar @$addblock;
1381 $delcmds = extractcommands($delblock);
1382 $addcmds = extractcommands($addblock);
1383 # keygen(third argument of _longestCommonSubsequence) implies to sort on command (0th elements of $addcmd elements)
1384 # the calling format for longestCommonSubsequence has changed between versions of
1385 # Algorithm::Diff so we need to check which one we are using
1386 if ( $algodiffversion > 1.15 ) {
1387 ### Algorithm::Diff 1.19
1388 $matchindex=Algorithm::Diff::_longestCommonSubsequence($delcmds,$addcmds, 0, sub { $_[0]->[0] } );
1390 ### Algorithm::Diff 1.15
1391 $matchindex=Algorithm::Diff::_longestCommonSubsequence($delcmds,$addcmds, sub { $_[0]->[0] } );
1394 for ($i=0 ; $i<=$#$matchindex ; $i++) {
1395 if (defined($matchindex->[$i])){
1396 $j=$matchindex->[$i];
1397 @delmid=splitlatex($delcmds->[$i][3]);
1398 @addmid=splitlatex($addcmds->[$j][3]);
1399 while (scalar(@$deltextblocks) && $deltextblocks->[0][0]<$delcmds->[$i][1]) {
1400 my ($index,$block,$cnt)=@{ shift(@$deltextblocks) };
1401 push(@$todo, [$index,-1,$cnt,@$block]);
1403 push(@$todo, [ $delcmds->[$i][1],-1,-1,$delcmds->[$i][2],@delmid,$delcmds->[$i][4]]);
1405 while (scalar(@$addtextblocks) && $addtextblocks->[0][0]<$addcmds->[$j][1]) {
1406 my ($index,$block,$cnt)=@{ shift(@$addtextblocks) };
1407 push(@$todo, [-1,$index,$cnt,@$block]);
1409 push(@$todo, [ -1,$addcmds->[$j][1],-1,$addcmds->[$j][2],@addmid,$addcmds->[$j][4]]);
1412 # mop up remaining textblocks
1413 while (scalar(@$deltextblocks)) {
1414 my ($index,$block,$cnt)=@{ shift(@$deltextblocks) } ;
1415 push(@$todo, [$index,-1,$cnt,@$block]);
1417 while (scalar(@$addtextblocks)) {
1418 my ($index,$block,$cnt)=@{ shift(@$addtextblocks) };
1419 push(@$todo, [-1,$index,$cnt,@$block]);
1425 push(@$block,$seq2->[$_[1]]);
1428 my $keyfunc = sub { join(" ",split(" ",shift())) };
1430 traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc );
1433 # now carry out the merging/splitting. Refer to elements relative from
1434 # the end (with negative indices) as these offsets don't change before the instruction is executed
1435 # cnt>0: merged small unchanged groups with previous changed blocks
1436 # cnt==-1: split textual commands into components
1437 foreach $instruction ( @$todo) {
1438 ($last1,$last2,$cnt,@$block)=@$instruction ;
1440 splice(@$seq1,$last1-$len1,1+$cnt,join("",$seq1->[$last1-$len1],@$block)) if $last1>=0;
1441 splice(@$seq2,$last2-$len2,1+$cnt,join("",$seq2->[$last2-$len2],@$block)) if $last2>=0;
1443 splice(@$seq1,$last1-$len1,1,@$block) if $last1>=0;
1444 splice(@$seq2,$last2-$len2,1,@$block) if $last2>=0;
1450 print STDERR " $mattokcnt matching tokens in $matblkcnt blocks.\n";
1451 print STDERR " $deltokcnt discarded tokens in $delblkcnt blocks.\n";
1452 print STDERR " $addtokcnt appended tokens in $addblkcnt blocks.\n";
1457 # extracttextblocks(\@blockindex)
1458 # $blockindex has the following format
1459 # [ [ token1, index1 ], [token2, index2],.. ]
1460 # where index refers to the index in the original old or new word sequence
1461 # Returns: reference to an array of the form
1462 # [[ $index, $textblock, $cnt ], ..
1463 # where $index index of block to be merged
1464 # $textblock contains all the words to be merged with the word at $index (but does not contain this word)
1465 # $cnt is length of block
1469 sub extracttextblocks {
1471 my ($i,$token,$index);
1474 my $wpat=qr/^(?:[a-zA-Z.,'`:;?()!]*)[\s~]*$/; #'
1477 for ($i=0;$i< scalar @$block;$i++) {
1478 ($token,$index)=@{ $block->[$i] };
1479 # store pure text blocks
1480 if ($token =~ /$wpat/ || ( $token =~/^\\([\w\d\*]+)((?:${extraspace}\[$brat0\]${extraspace}|${extraspace}\{$pat4\})*)/o
1481 && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL)
1482 && !iscmd($1,\@TEXTCMDLIST,\@TEXTCMDEXCL))) {
1483 # we have text or a command which can be treated as text
1485 # new pure-text block
1488 # add to pure-text block
1489 push(@$textblock, $token);
1493 if (scalar(@$textblock)) {
1494 push(@$retval,[ $last, $textblock, scalar(@$textblock) ]);
1500 # finish processing a possibly unfinished block before returning
1501 if (scalar(@$textblock)) {
1502 push(@$retval,[ $last, $textblock, scalar(@$textblock) ]);
1509 # extractcommands( \@blockindex )
1510 # $blockindex has the following format
1511 # [ [ token1, index1 ], [token2, index2],.. ]
1512 # where index refers to the index in the original old or new word sequence
1513 # Returns: reference to an array of the form
1514 # [ [ "\cmd1", index, "\cmd1[optarg]{arg1}{", "arg2" ,"} " ],..
1515 # where index is just taken from input array
1516 # command must have a textual argument as last argument
1520 sub extractcommands {
1522 my ($i,$token,$index,$cmd,$open,$mid,$closing);
1525 for ($i=0;$i< scalar @$block;$i++) {
1526 ($token,$index)=@{ $block->[$i] };
1527 # check if token is an alphanumeric command sequence with at least one non-optional argument
1528 # \cmd[...]{...}{last argument}
1529 # Capturing in the following results in these associations
1530 # $1: \cmd[...]{...}{
1533 # $4: } + trailing spaces
1534 if ( ( $token =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat4\})*${extraspace}\{)($pat4)(\}\s*)$/so )
1535 && iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL) ) {
1536 # push(@$retval,[ $2,$index,$1,$3,$4 ]);
1537 ($cmd,$open,$mid,$closing) = ($2,$1,$3,$4) ;
1538 $closing =~ s/\}/\\RIGHTBRACE/ ;
1539 push(@$retval,[ $cmd,$index,$open,$mid,$closing ]);
1545 # iscmd($cmd,\@regexarray,\@regexexcl) checks
1546 # return 1 if $cmd matches any of the patterns in the
1547 # array $@regexarray, and none of the patterns in \@regexexcl, otherwise return 0
1549 my ($cmd,$regexar,$regexexcl)=@_;
1551 foreach $pat ( @$regexar ) {
1552 if ( $cmd =~ m/^${pat}$/ ) {
1557 return 0 unless $ret;
1558 foreach $pat ( @$regexexcl ) {
1559 return 0 if ( $cmd =~ m/^${pat}$/ );
1565 # pass2( \@seq1,\@seq2)
1566 # Look for differences between seq1 and seq2.
1567 # Mark begin and end of deleted and appended sequences with tags $DELOPEN and $DELCLOSE
1568 # and $ADDOPEN and $ADDCLOSE, respectively, however exclude { } & and all comands, unless
1569 # they match an element of the whitelist (SAFECMD)
1570 # For words in TEXTCMD but not in SAFECMD, enclose interior with $ADDOPEN and $ADDCLOSE brackets
1571 # Deleted comment lines are marked with %DIF <
1572 # Added comment lines are marked with %DIF >
1577 my ($addtokcnt,$deltokcnt,$mattokcnt)=(0,0,0);
1578 my ($addblkcnt,$delblkcnt,$matblkcnt)=(0,0,0);
1584 my $discard = sub { $deltokcnt++;
1585 push ( @$delhunk, $seq1->[$_[0]]) };
1587 my $add = sub { $addtokcnt++;
1588 push ( @$addhunk, $seq2->[$_[1]]) };
1590 my $match = sub { $mattokcnt++;
1591 if ( scalar @$delhunk ) {
1593 # mark up changes, but comment out commands
1594 push @$retval,marktags($DELMARKOPEN,$DELMARKCLOSE,$DELOPEN,$DELCLOSE,$DELCMDOPEN,$DELCMDCLOSE,$DELCOMMENT,$delhunk);
1597 if ( scalar @$addhunk ) {
1599 # we mark up changes, but simply quote commands
1600 push @$retval,marktags($ADDMARKOPEN,$ADDMARKCLOSE,$ADDOPEN,$ADDCLOSE,"","",$ADDCOMMENT,$addhunk);
1603 push(@$retval,$seq2->[$_[1]]) };
1605 my $keyfunc = sub { join(" ",split(" ",shift())) };
1607 traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc );
1608 # clear up unprocessed hunks
1609 push @$retval,marktags($DELMARKOPEN,$DELMARKCLOSE,$DELOPEN,$DELCLOSE,$DELCMDOPEN,$DELCMDCLOSE,$DELCOMMENT,$delhunk) if scalar @$delhunk;
1610 push @$retval,marktags($ADDMARKOPEN,$ADDMARKCLOSE,$ADDOPEN,$ADDCLOSE,"","",$ADDCOMMENT,$addhunk) if scalar @$addhunk;
1615 print STDERR " $mattokcnt matching tokens. \n";
1616 print STDERR " $deltokcnt discarded tokens in $delblkcnt blocks.\n";
1617 print STDERR " $addtokcnt appended tokens in $addblkcnt blocks.\n";
1623 # marktags($openmark,$closemark,$open,$close,$opencmd,$closecmd,$comment,\@block)
1624 # returns ($openmark,$open,$block,$close,$closemark) if @block only contains no commands (except white-listed ones),
1625 # braces, ampersands, or comments
1626 # mark comments with $comment
1627 # exclude all other exceptions from scope of open, close like this
1628 # ($openmark, $open,...,$close, $opencomd,command, command,$closecmd, $open, ..., $close, $closemark)
1629 # If $opencmd begins with "%" marktags assumes it is operating on a deleted block, otherwise on an added block
1631 my ($openmark,$closemark,$open,$close,$opencmd,$closecmd,$comment,$block)=@_;
1636 my $cmd=-1; # -1 at beginning 0: last token written is a ordinary word
1637 # 1: last token written is a command
1638 # for keeping track whether we are just in a command sequence or in a word sequence
1639 my $cmdcomment= ($opencmd =~ m/^%/); # Flag to indicate whether opencmd is a comment (i.e. if we intend to simply comment out changed commands)
1640 my ($command,$commandword,$closingbracket) ; # temporary variables needed below to remember sub-pattern matches
1642 # split this block to flatten out sequences joined in pass1
1643 @$block=splitlatex(join "",@$block);
1646 if ( $word =~ s/^%/%$comment/ ) {
1649 push (@$retval,$closecmd) ;
1652 push (@$retval,$word);
1655 if (! $noncomment) {
1656 push (@$retval,$openmark);
1659 # negative lookahead pattern (?!) in second clause is put in to avoid mathcing \( .. \) patterns
1660 if ( $word =~ /^[&{}\[\]]/ || ( $word =~ /^\\(?!\()([\w*@]*)/ && !iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL)) ) {
1661 # word is a command or other significant token (not in SAFECMDLIST)
1662 ## same conditions as in subroutine extractcommand:
1663 # check if token is an alphanumeric command sequence with at least one non-optional argument
1664 # \cmd[...]{...}{last argument}
1665 # Capturing in the following results in these associations
1666 # $1: \cmd[...]{...}{
1669 # $4: } + trailing spaces
1670 ### pre-0.3 if ( ( $token =~ m/^(\\([\w\d\*]+)(?:\[$brat0\]|\{$pat4\})*\{)($pat4)(\}\s*)$/so )
1671 if ( ( $word =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat4\})*${extraspace}\{)($pat4)(\}\s*)$/so )
1672 && iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL) && ( !$cmdcomment || !iscmd($2,\@CONTEXT2CMDLIST, \@CONTEXT2CMDEXCL) ) ) {
1673 # word is a text command - we mark up the interior of the word. But if we are in a deleted block ($cmdcomment=1) and
1674 # $2 (the command) is in context2, just treat it as an ordinary command (i.e. comment it open with $opencmd)
1675 # Because we do not want to disable this command
1676 # here we do not use $opencmd and $closecmd($opencmd is empty)
1678 push (@$retval,$closecmd) ;
1680 push (@$retval,$close) ;
1682 $command=$1; $commandword=$2; $closingbracket=$4;
1683 @argtext=splitlatex($3); # split textual argument into tokens
1684 # and mark it up (but we do not need openmark and closemark)
1685 # insert command with initial arguments, marked-up final argument, and closing bracket
1686 if ( $cmdcomment && iscmd($commandword,\@CONTEXT1CMDLIST, \@CONTEXT1CMDEXCL) ) {
1687 # context1cmd in a deleted environment; delete command itself but keep last argument, marked up
1688 push (@$retval,$opencmd);
1689 $command =~ s/\n/\n${opencmd}/sg ; # repeat opencmd at the beginning of each line
1690 # argument, note that the additional comment character is included
1691 # to suppress linebreak after opening parentheses, which is important
1693 push (@$retval,$command,"%\n{$AUXCMD\n",marktags("","",$open,$close,$opencmd,$closecmd,$comment,\@argtext),$closingbracket);
1695 # normal textcmd or context1cmd in an added block
1696 push (@$retval,$command,marktags("","",$open,$close,$opencmd,$closecmd,$comment,\@argtext),$closingbracket);
1698 push (@$retval,$AUXCMD,"\n") if $cmdcomment ;
1702 push (@$retval,$opencmd) if $cmd==-1 ;
1703 push (@$retval,$close,$opencmd) if $cmd==0 ;
1704 $word =~ s/\n/\n${opencmd}/sg if $cmdcomment ; # if opencmd is a comment, repeat this at the beginning of every line
1705 push (@$retval,$word);
1709 # just an ordinary word or word in SAFECMD
1710 push (@$retval,$open) if $cmd==-1 ;
1711 push (@$retval,$closecmd,$open) if $cmd==1 ;
1712 push (@$retval,$word);
1716 push (@$retval,$close) if $cmd==0;
1717 push (@$retval,$closecmd) if $cmd==1;
1719 push (@$retval,$closemark) if ($noncomment);
1723 # preprocess($string, ..)
1724 # carry out the following pre-processing steps for all arguments:
1725 # 1. Remove leading white-space
1726 # 2. mark all first empty line (in block of several) with \PAR tokens
1727 # 3. Convert all '\%' into '\PERCENTAGE ' to make parsing regular expressions easier
1728 # 4. Convert all \verb|some verbatim text| commands (where | can be an arbitrary character)
1730 # 5. Convert \begin{verbatim} some verbatim text \end{verbatim} into \verbatim{hash}
1731 # 6. Convert _n into \SUBSCRIPTNB{n} and _{nnn} into \SUBSCRIPT{nn}
1732 # 7. Convert ^n into \SUPERSCRIPTNB{n} and ^{nnn} into \SUPERSCRIPT{nn}
1733 # 8. a. Convert $$ $$ into \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR}
1734 # b. Convert \[ \] into \begin{SQUAREBRACKET} \end{SQUAREBRACKET}
1736 # 9. Add final token STOP to the very end. This is put in because the algorithm works better if the last token is identical. This is removed again in postprocessing.
1738 # NB: step 6 and 7 is likely to convert some "_" inappropriately, e.g. in file
1739 # names or labels but it does not matter because they are converted back in the postprocessing step
1740 # Returns: leading white space removed in step 1
1746 s/\n(\s*?)\n((?:\s*\n)*)/\n$1\\PAR\n$2/g ;
1747 s/(?<!\\)\\%/\\PERCENTAGE /g ; # (?<! is negative lookbehind assertion to prevent \\% from being converted
1748 s/(\\verb\*?)(\S)(.*?)\2/"${1}{". tohash(\%verbhash,"${2}${3}${2}") ."}"/esg;
1749 s/\\begin\{(verbatim\*?)\}(.*?)\\end\{\1\}/"\\${1}{". tohash(\%verbhash,"${2}") . "}"/esg;
1750 # Convert _n or _\cmd into \SUBSCRIPTNB{n} or \SUBSCRIPTNB{\cmd} and _{nnn} into \SUBSCRIPT{nn}
1751 s/_([^{\\]|\\\w+)/\\SUBSCRIPTNB{$1}/g ;
1752 s/_{($pat4)}/\\SUBSCRIPT{$1}/g ;
1753 # Convert ^n into \SUPERSCRIPTNB{n} and ^{nnn} into \SUPERSCRIPT{nn}
1754 s/\^([^{\\]|\\\w+)/\\SUPERSCRIPTNB{$1}/g ;
1755 s/\^{($pat4)}/\\SUPERSCRIPT{$1}/g ;
1756 # Convert $$ $$ into \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR}
1757 s/\$\$(.*?)\$\$/\\begin{DOLLARDOLLAR}$1\\end{DOLLARDOLLAR}/sg;
1758 # Convert \[ \] into \begin{SQUAREBRACKET} \end{SQUAREBRACKET}
1759 s/(?<!\\)\\\[/\\begin{SQUAREBRACKET}/sg;
1760 s/\\\]/\\end{SQUAREBRACKET}/sg;
1761 # add final token " STOP"
1767 #hashstring=tohash(\%hash,$string)
1768 # creates a hash value based on string and stores in %hash
1770 my ($hash,$string)=@_;
1775 @arr=unpack('c*',$string);
1777 foreach $val (@arr) {
1782 if (defined($hash->{$hstr}) && $string ne $hash->{$hstr}) {
1783 warn "Repeated hash value for verbatim mode in spite of different content.";
1786 $hash->{$hstr}=$string;
1790 #string=fromhash(\%hash,$fromstring)
1791 # restores string value stored in hash
1792 #string=fromhash(\%hash,$fromstring,$prependstring)
1793 # additionally begins each line with prependstring
1795 my ($hash,$hstr)=($_[0],$_[1]);
1796 my $retstr=$hash->{$hstr};
1798 $retstr =~ s/^/$_[2]/mg;
1804 # postprocess($string, ..)
1805 # carry out the following post-processing steps for all arguments:
1806 # * Remove STOP token from the end
1807 # * Replace \RIGHTBRACE by }
1808 # 1. Check all deleted blocks:
1809 # a.where a deleted block contains a matching \begin and
1810 # \end environment (these will be disabled by a %DIFDELCMD statements), for selected environment enable enable
1811 # these commands again (such that for example displayed math in a deleted equation
1812 # is properly within math mode. For math mode environments replace numbered equation
1813 # environments with their display only variety (so that equation numbers in new file and
1814 # diff file are identical). Where the correct type of math environment cannot be determined
1815 # use a place holder MATHMODE
1816 # b.where one of the commands matching $COUNTERCMD is used as a DIFAUXCMD, add a statement
1817 # subtracting one from the respective counter to keep numbering consistent with new file
1818 # Replace all MATHMODE environment commands by the correct environment to achieve matching
1820 # c. If in-line math mode contains array environment, enclose the whole environment in \mbox'es
1821 # 2. Remove DIFadd, DIFdel, DIFFaddbegin , ... from picture environments
1822 # 3. Convert DIFadd, DIFdel, DIFFaddbegin , ... into FL varieties
1823 # within floats (currently recognised float environments: plate,table,figure
1824 # plus starred varieties).
1825 # 4. Remove empty %DIFDELCMD < lines
1826 # 4. Convert \begin{SQUAREBRACKET} \end{SQUAREBRACKET} into \[ \]
1827 # Convert \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} into $$ $$
1828 # 5. Convert \SUPERSCRIPTNB{n} into ^n and \SUPERSCRIPT{nn} into ^{nnn}
1829 # 6. Convert \SUBSCRIPTNB{n} into _n and \SUBCRIPT{nn} into _{nnn}
1830 # 7. Expand hashes of verb and verbatim environments
1831 # 8. Convert '\PERCENTAGE ' back into '\%'
1832 # 9.. remove all \PAR tokens
1833 # 10. package specific processing: endfloat: make sure \begin{figure} and \end{figure} are always
1834 # on a line by themselves, similarly for table environment
1835 # 4, undo renaming of the \begin and \end in comments
1838 # Note have to manually synchronize substitution commands below and
1839 # DIF.. command names in the header
1841 my ($begin,$len,$cnt,$float,$delblock,$addblock);
1842 # second level blocks
1843 my ($begin2,$cnt2,$len2,$eqarrayblock,$mathblock);
1847 # change $'s in comments to something harmless
1848 1 while s/(%.*)\$/$1DOLLARDIF/mg ;
1850 # Remove final STOP token
1852 # Replace \RIGHTBRACE by }
1854 # Check all deleted blocks: where a deleted block contains a matching \begin and
1855 # \end environment (these will be disabled by a %DIFDELCMD statements), enable
1856 # these commands again (such that for example displayed math in a deleted equation
1857 # is properly within math mode. For math mode environments replace numbered equation
1858 # environments with their display only variety (so that equation numbers in new file and
1859 # diff file are identical
1860 while ( m/\\DIFdelbegin.*?\\DIFdelend/sg ) {
1863 $begin=pos($_) - $len;
1867 ### (.*?[^\n]?)\n? construct is necessary to avoid empty lines in math mode, which result in
1869 # displayed math environments
1870 $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\end\{\2\})/\\begin{$MATHREPL}$AUXCMD\n$1$3\n\\end{$MATHREPL}$AUXCMD\n$4/sg;
1871 # also transform the opposite pair \end{displaymath} .. \begin{displaymath} but we have to be careful not to interfere with the results of the transformation in the line directly above
1872 $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/\\end{MATHMODE}$AUXCMD\n$1$3\n\\begin{MATHMODE}$AUXCMD\n$4/sg;
1874 # now look for unpaired %DIFDELCMD < \begin{MATHENV}; if found add \begin{$MATHREPL} and insert \end{$MATHREPL}
1875 # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted
1876 if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\begin{$MATHREPL}$AUXCMD\n/sg ) {
1877 $delblock =~ s/(\\DIFdelend$)/\\end{$MATHREPL}$AUXCMD\n$1/s ;
1879 # now look for unpaired %DIFDELCMD < \end{MATHENV}; if found add \end{MATHMODE} and insert \begin{MATHMODE}
1880 # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted
1881 if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\end{MATHMODE}$AUXCMD\n/sg ) {
1882 $delblock =~ s/(\\DIFdelend$)/\\begin{MATHMODE}$AUXCMD\n$1/s ;
1886 # equation array environment
1887 $delblock=~ s/(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(\%DIFDELCMD < \s*\\end\{\2\})/\\begin{$MATHARRREPL}$AUXCMD\n$1$3\n\\end{$MATHARRREPL}$AUXCMD\n$4/sg;
1888 $delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))(.*?[^\n]?)\n?(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{\2\})/\\end{MATHMODE}$AUXCMD\n$1$3\n\\begin{MATHMODE}$AUXCMD\n$4/sg;
1890 # now look for unpaired %DIFDELCMD < \begin{MATHARRENV}; if found add \begin{$MATHARRREPL} and insert \end{$MATHARRREPL}
1891 # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted
1892 if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\begin{$MATHARRREPL}$AUXCMD\n/sg ) {
1893 $delblock =~ s/(\\DIFdelend$)/\\end{$MATHARRREPL}$AUXCMD\n$1/s ;
1895 # now look for unpaired %DIFDELCMD < \end{MATHENV}; if found add \end{MATHMODE} and insert \begin{MATHMODE}
1896 # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted
1897 if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\end{MATHMODE}$AUXCMD\n/sg ) {
1898 $delblock =~ s/(\\DIFdelend$)/\\begin{MATHMODE}$AUXCMD\n$1/s ;
1901 # parse $delblock for deleted and reinstated eqnarray* environments - within those reinstate \\ and & commands
1902 while ( $delblock =~ m/\\begin\Q{$MATHARRREPL}$AUXCMD\E\n.*?\n\\end\Q{$MATHARRREPL}$AUXCMD\E\n/sg ) {
1905 $begin2=pos($delblock) - $len2;
1907 # reinstate deleted & and \\ commands
1908 $eqarrayblock=~ s/(\%DIFDELCMD < \s*(\&|\\\\)\s*?(?:$DELCMDCLOSE|\n))/$1$2$AUXCMD\n/sg ;
1910 substr($delblock,$begin2,$len2)=$eqarrayblock;
1911 pos($delblock) = $begin2 + length($eqarrayblock);
1916 # b.where one of the commands matching $COUNTERCMD is used as a DIFAUXCMD, add a statement
1917 # subtracting one from the respective counter to keep numbering consistent with new file
1918 $delblock=~ s/\\($COUNTERCMD)((?:${extraspace}\[$brat0\]${extraspace}|${extraspace}\{$pat4\})*\s*${AUXCMD}\n)/\\$1$2\\addtocounter{$1}{-1}${AUXCMD}\n/sg ;
1919 # c. If in-line math mode contains array environment, enclose the whole environment in \mbox'es
1920 while ( $delblock =~ m/($math)(\s*)/sg ) {
1923 $begin2=pos($delblock) - $len2;
1924 $mathblock="%\n\\mbox{$AUXCMD\n$1\n}$AUXCMD\n";
1925 next unless $mathblock =~ m/\{$ARRENV\}/ ;
1926 substr($delblock,$begin2,$len2)=$mathblock;
1927 pos($delblock) = $begin2 + length($mathblock);
1930 # splice in modified delblock
1931 substr($_,$begin,$len)=$delblock;
1932 pos = $begin + length($delblock);
1934 # make the array modification in added blocks
1935 while ( m/\\DIFaddbegin.*?\\DIFaddend/sg ) {
1938 $begin=pos($_) - $len;
1940 while ( $addblock =~ m/($math)(\s*)/sg ) {
1943 $begin2=pos($addblock) - $len2;
1944 $mathblock="%\n\\mbox{$AUXCMD\n$1\n}$AUXCMD\n";
1945 next unless $mathblock =~ m/\{$ARRENV\}/ ;
1946 substr($addblock,$begin2,$len2)=$mathblock;
1947 pos($addblock) = $begin2 + length($mathblock);
1950 # splice in modified addblock
1951 substr($_,$begin,$len)=$addblock;
1952 pos = $begin + length($addblock);
1956 ### old place for BEGINDIF, ENDDIF replacement
1957 # change begin and end commands within comments such that they
1958 # don't disturb the pattern matching (if there are several \begin or \end in one line
1959 # this substitution is insufficient but that appears unlikely)
1960 s/(%.*)\\begin\{(.*)$/$1\\BEGINDIF\{$2/mg ;
1961 s/(%.*)\\end\{(.*)$/$1\\ENDDIF\{$2/mg ;
1963 # Replace MATHMODE environments from step 1a above by the correct Math environment
1965 # The next line is complicated. The negative look-ahead insertion makes sure that no \end{$MATHENV} (or other mathematical
1966 # environments) are between the \begin{$MATHENV} and \end{MATHMODE} commands. This is necessary as the minimal matching
1967 # is not globally minimal but only 'locally' (matching is beginning from the left side of the string)
1968 1 while s/\\begin{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}((?:.(?!(?:\\end{(?:(?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}|\\begin{MATHMODE})))*?)\\end{MATHMODE}/\\begin{$1}$2\\end{$1}/s;
1969 1 while s/\\begin{MATHMODE}((?:.(?!\\end{MATHMODE}))*?)\\end{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}/\\begin{$2}$1\\end{$2}/s;
1970 # convert remaining \begin{MATHMODE} \end{MATHMODE} (and not containing & or \\ )into MATHREPL environments
1971 s/\\begin{MATHMODE}((?:(.(?!(?<!\\)\&|\\\\))*)?)\\end{MATHMODE}/\\begin{$MATHREPL}$1\\end{$MATHREPL}/sg;
1972 # others into MATHARRREPL
1973 s/\\begin{MATHMODE}(.*?)\\end{MATHMODE}/\\begin{$MATHARRREPL}$1\\end{$MATHARRREPL}/sg;
1975 # now look for AUXCMD math-mode pairs which have only comments (or empty lines between them), and remove the added commands
1976 s/\\begin{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}$AUXCMD\n((?:\s*%.[^\n]*\n)*)\\end{\1}$AUXCMD\n/$2/sg;
1979 # Remove all mark up within picture environments
1980 while ( m/\\begin\{($PICTUREENV)\}.*?\\end\{\1\}/sg ) {
1983 $begin=pos($_) - $len;
1985 $float =~ s/\\DIFaddbegin //g;
1986 $float =~ s/\\DIFaddend //g;
1987 $float =~ s/\\DIFadd\{($pat4)\}/$1/g;
1988 $float =~ s/\\DIFdelbegin //g;
1989 $float =~ s/\\DIFdelend //g;
1990 $float =~ s/\\DIFdel\{($pat4)\}//g;
1991 substr($_,$begin,$len)=$float;
1992 pos = $begin + length($float);
1994 # Convert DIFadd, DIFdel, DIFFaddbegin , ... into varieties
1995 # within floats (currently recognised float environments: plate,table,figure
1996 # plus starred varieties).
1997 while ( m/\\begin\{($FLOATENV)\}.*?\\end\{\1\}/sg ) {
2000 $begin=pos($_) - $len;
2002 $float =~ s/\\DIFaddbegin /\\DIFaddbeginFL /g;
2003 $float =~ s/\\DIFaddend /\\DIFaddendFL /g;
2004 $float =~ s/\\DIFadd\{/\\DIFaddFL{/g;
2005 $float =~ s/\\DIFdelbegin /\\DIFdelbeginFL /g;
2006 $float =~ s/\\DIFdelend /\\DIFdelendFL /g;
2007 $float =~ s/\\DIFdel\{/\\DIFdelFL{/g;
2008 substr($_,$begin,$len)=$float;
2009 pos = $begin + length($float);
2011 ### former location of undo renaming of \begin and \end in comments
2013 # remove empty DIFCMD < lines
2014 s/^\Q${DELCMDOPEN}\E\n//msg;
2016 # Expand hashes of verb and verbatim environments (note negative look behind assertion to not leak out of DIFDELCMD comments
2017 s/(\\verb\*?)\{([-\d]*?)\}/"${1}". fromhash(\%verbhash,$2)/esg;
2018 s/${DELCMDOPEN}\\(verbatim\*?)\{([-\d]*?)\}/"${DELCMDOPEN}\\begin{${1}}".fromhash(\%verbhash,$2,$DELCMDOPEN)."${DELCMDOPEN}\\end{${1}}"/esg;
2019 s/\\(verbatim\*?)\{([-\d]*?)\}/"\\begin{${1}}".fromhash(\%verbhash,$2)."\\end{${1}}"/esg;
2020 # Convert '\PERCENTAGE ' back into '\%'
2021 s/\\PERCENTAGE /\\%/g;
2022 # remove all \PAR tokens (taking care to properly keep commented out PAR's
2023 # from introducing uncommented newlines - next line)
2024 s/(%DIF < )([^\n]*?)\\PAR\n/$1$2\n$1\n/sg;
2025 # convert PAR commands which are on a line by themselves
2026 s/\n(\s*?)\\PAR\n/\n\n/sg;
2027 # convert remaining PAR commands (which are preceded by non-white space characters, usually "}" ($ADDCLOSE)
2030 # package specific processing:
2031 if ( defined($packages{"endfloat"})) {
2032 #endfloat: make sure \begin{figure} and \end{figure} are always
2033 # on a line by themselves, similarly for table environment
2034 print STDERR "endfloat package detected.\n" if $verbose ;
2035 # eliminate whitespace before and after
2036 s/^(\s*)(\\(?:begin|end)\{(?:figure|table)\})(\s*)$/$2/mg;
2037 # split lines with remaining characters before float enviroment conmmand
2038 s/^(.+)(\\(?:begin|end)\{(?:figure|table)\})/$1\n$2/mg;
2039 # split lines with remaining characters after float enviroment conmmand
2040 s/(\\(?:begin|end)\{(?:figure|table)\})(.+)$/$1\n$2/mg;
2042 # undo renaming of the \begin and \end and dollars in comments
2043 s/(%.*)\\BEGINDIF\{(.*)$/$1\\begin\{$2/mg ;
2044 s/(%.*)\\ENDDIF\{(.*)$/$1\\end\{$2/mg ;
2045 1 while s/(%.*)DOLLARDIF/$1\$/mg ;
2046 # Convert \begin{SQUAREBRACKET} \end{SQUAREBRACKET} into \[ \]
2047 s/\\end{SQUAREBRACKET}/\\\]/sg;
2048 s/\\begin{SQUAREBRACKET}/\\\[/sg;
2049 # 4. Convert \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} into $$ $$
2050 s/\\begin\{DOLLARDOLLAR\}(.*?)\\end\{DOLLARDOLLAR\}/\$\$$1\$\$/sg;
2051 # 5. Convert \SUPERSCRIPTNB{n} into ^n and \SUPERSCRIPT{nn} into ^{nnn}
2052 s/\\SUPERSCRIPTNB{($pat0)}/^$1/g ;
2053 s/\\SUPERSCRIPT{($pat4)}/^{$1}/g ;
2054 # Convert \SUBSCRIPNB{n} into _n and \SUBCRIPT{nn} into _{nnn}
2055 s/\\SUBSCRIPTNB{($pat0)}/_$1/g ;
2056 s/\\SUBSCRIPT{($pat4)}/_{$1}/g ;
2063 # @diffs=linediff(\@seq1, \@seq2)
2064 # mark up lines like this
2066 #%< old deleted line(s)
2069 #new appended line %<
2071 # Future extension: mark change explicitly
2072 # Assumes: traverse_sequence traverses deletions before insertions in changed sequences
2073 # all line numbers relative to line 0 (first line of real file)
2080 my @begin=('','',''); # dummy initialisation
2083 my $discard = sub { @begin=('d',$_[0],$_[1]) unless scalar @$block ;
2084 push(@$block, "%DIF < " . $seq1->[$_[0]]) };
2085 my $add = sub { if (! scalar @$block) {
2086 @begin=('a',$_[0],$_[1]) ;}
2087 elsif ( $begin[0] eq 'd' ) {
2088 $begin[0]='c'; $begin[2]=$_[1];
2089 push(@$block, "%DIF -------") }
2090 push(@$block, $seq2->[$_[1]] . " %DIF > " ) };
2091 my $match = sub { if ( scalar @$block ) {
2092 if ( $begin[0] eq 'd' && $begin[1]!=$_[0]-1) {
2093 $instring = sprintf "%%DIF %d-%dd%d",$begin[1],$_[0]-1,$begin[2]; }
2094 elsif ( $begin[0] eq 'a' && $begin[2]!=$_[1]-1) {
2095 $instring = sprintf "%%DIF %da%d-%d",$begin[1],$begin[2],$_[1]-1; }
2096 elsif ( $begin[0] eq 'c' ) {
2097 $instring = sprintf "%%DIF %sc%s",
2098 ($begin[1]==$_[0]-1) ? "$begin[1]" : $begin[1]."-".($_[0]-1) ,
2099 ($begin[2]==$_[1]-1) ? "$begin[2]" : $begin[2]."-".($_[1]-1) ; }
2101 $instring = sprintf "%%DIF %d%s%d",$begin[1],$begin[0],$begin[2]; }
2102 push @$retseq, $instring,@$block, "%DIF -------" ;
2105 push @$retseq, $seq2->[$_[1]]
2107 # key function: remove multiple spaces (such that insertion or deletion of redundant white space is not reported)
2108 my $keyfunc = sub { join(" ",split(" ",shift())) };
2110 traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc );
2111 push @$retseq, @$block if scalar @$block;
2113 return wantarray ? @$retseq : $retseq ;
2118 # init_regex_arr_data(\@array,"TOKEN INIT")
2119 # scans DATA file handel for line "%% TOKEN INIT" line
2120 # then appends each line not beginning with % into array (as a quoted regex)
2121 sub init_regex_arr_data {
2122 my ($arr,$token)=@_;
2125 if ( m/^%%BEGIN $token\s*$/ ) {
2127 elsif ( m/^%%END $token\s*/ ) {
2130 push (@$arr,qr/^$_$/) if ( $copy && !/^%/ ) ;
2132 seek DATA,0,0; # rewind DATA handle to file begin
2136 # init_regex_arr_ext(\@array,$arg)
2137 # fills array with regular expressions.
2138 # if arg is a file name, then read in list of regular expressions from that file
2139 # (one expression per line)
2140 # Otherwise treat arg as a comma separated list of regular expressions
2141 sub init_regex_arr_ext {
2145 open(FILE,"$arg") or die ("Couldn't open $arg: $!");
2148 next if /^\s*#/ || /^\s*%/ || /^\s*$/ ;
2149 push (@$arr,qr/^$_$/);
2154 # assume it is a comma-separated list of reg-ex
2155 foreach $regex (split(qr/(?<!\\),/,$arg)) {
2156 $regex =~ s/\\,/,/g;
2157 push (@$arr,qr/^$regex$/);
2162 #exetime() returns time since last execution of this command
2163 #exetime(1) resets this time
2164 my $lasttime=-1; # global variable for persistence
2168 if ((scalar @_) >=1) {
2175 $retval=times()-$lasttime;
2176 $lasttime=$lasttime+$retval;
2184 Usage: $0 [options] old.tex new.tex > diff.tex
2186 Compares two latex files and writes tex code to stdout, which has the same
2187 format as new.tex but has all changes relative to old.tex marked up or commented.
2190 -t markupstyle Add code to preamble for selected markup style
2191 Available styles: UNDERLINE CTRADITIONAL TRADITIONAL CFONT FONTSTRIKE INVISIBLE
2192 CHANGEBAR CCHANGEBAR CULINECHBAR CFONTCBHBAR
2193 [ Default: UNDERLINE ]
2196 -s markstyle Add code to preamble for selected style for bracketing
2197 commands (e.g. to mark changes in margin)
2198 Available styles: SAFE MARGINAL DVIPSCOL
2201 --floattype=markstyle
2202 -f markstyle Add code to preamble for selected style which
2203 replace standard marking and markup commands within floats
2204 (e.g., marginal remarks cause an error within floats
2205 so marginal marking can be disabled thus)
2206 Available styles: FLOATSAFE IDENTICAL
2207 [ Default: FLOATSAFE ]
2210 -e enc Specify encoding of old.tex and new.tex. Typical encodings are
2211 ascii, utf8, latin1, latin9. A list of available encodings can be
2212 obtained by executing
2213 perl -MEncode -e 'print join ("\\n",Encode->encodings( ":all" )) ;'
2214 [Default encoding is utf8 unless the first few lines of the preamble contain
2215 an invocation "\\usepackage[..]{inputenc} in which case the
2216 encoding chosen by this command is asssumed. Note that ASCII (standard
2217 latex) is a subset of utf8]
2220 -p file Insert file at end of preamble instead of auto-generating
2221 preamble. The preamble must define the following commands
2222 \\DIFaddbegin,\\DIFaddend,\\DIFadd{..},
2223 \\DIFdelbegin,\\DIFdelend,\\DIFdel{..},
2224 and varieties for use within floats
2225 \\DIFaddbeginFL,\\DIFaddendFL,\\DIFaddFL{..},
2226 \\DIFdelbeginFL,\\DIFdelendFL,\\DIFdelFL{..}
2227 (If this option is set -t, -s, and -f options
2230 --exclude-safecmd=exclude-file
2231 --exclude-safecmd="cmd1,cmd2,..."
2233 --replace-safecmd=replace-file
2234 --append-safecmd=append-file
2235 --append-safecmd="cmd1,cmd2,..."
2236 -a append-file Exclude from, replace or append to the list of regex
2237 matching commands which are safe to use within the
2238 scope of a \\DIFadd or \\DIFdel command. The file must contain
2239 one Perl-RegEx per line (Comment lines beginning with # or % are
2240 ignored). A literal comma within the comma-separated list must be
2241 escaped thus "\\,", Note that the RegEx needs to match the whole of
2242 the token, i.e., /^regex\$/ is implied and that the initial
2243 "\\" of the command is not included. The --exclude-safecmd
2244 and --append-safecmd options can be combined with the --replace-safecmd
2245 option and can be used repeatedly to add cumulatively to the lists.
2247 --exclude-textcmd=exclude-file
2248 --exclude-textcmd="cmd1,cmd2,..."
2250 --replace-textcmd=replace-file
2251 --append-textcmd=append-file
2252 --append-textcmd="cmd1,cmd2,..."
2253 -x append-file Exclude from, replace or append to the list of regex
2254 matching commands whose last argument is text. See
2255 entry for --exclude-safecmd directly above for further details.
2257 --replace-context1cmd=replace-file
2258 --append-context1cmd=append-file
2259 --append-context1cmd="cmd1,cmd2,..."
2260 Replace or append to the list of regex matching commands
2261 whose last argument is text but which require a particular
2262 context to work, e.g. \\caption will only work within a figure
2263 or table. These commands behave like text commands, except when
2264 they occur in a deleted section, when they are disabled, but their
2265 argument is shown as deleted text.
2267 --replace-context2cmd=replace-file
2268 --append-context2cmd=append-file
2269 --append-context2cmd="cmd1,cmd2,..."
2270 As corresponding commands for context1. The only difference is that
2271 context2 commands are completely disabled in deleted sections, including
2275 --config var1=val1,var2=val2,...
2276 -c var1=val1,.. Set configuration variables.
2277 -c configfile Available variables:
2278 MINWORDSBLOCK (integer)
2284 MATHARRREPL (String)
2287 This option can be repeated.
2290 --packages=pkg1,pkg2,..
2291 Tell latexdiff that .tex file is processed with the packages in list
2292 loaded. This is normally not necessary if the .tex file includes the
2293 preamble, as the preamble is automatically scanned for \\usepackage commands.
2294 Use of the --packages option disables automatic scanning, so if for any
2295 reason package specific parsing needs to be switched off, use --packages=none.
2296 The following packages trigger special behaviour:
2297 endfloat hyperref amsmath
2298 [ Default: scan the preamble for \\usepackage commands to determine
2301 --show-preamble Print generated or included preamble commands to stdout.
2303 --show-safecmd Print list of regex matching and excluding safe commands.
2305 --show-textcmd Print list of regex matching and excluding commands with text argument.
2307 --show-config Show values of configuration variables
2309 --show-all Show all of the above
2311 NB For all --show commands, no old.tex or new.tex file needs to be given, and no
2312 differencing takes place.
2314 --allow-spaces Allow spaces between bracketed or braced arguments to commands
2315 [Default requires arguments to directly follow each other without
2318 --flatten Replace \\input and \\include commands within body by the content
2319 of the files in their argument. If \\includeonly is present in the
2320 preamble, only those files are expanded into the document. However,
2321 no recursion is done, i.e. \\input and \\include commands within
2322 included sections are not expanded. The included files are assumed to
2323 be located in the same directories as the old and new master files,
2324 respectively, making it possible to organise files into old and new directories.
2327 -h Show this help text.
2329 --ignore-warnings Suppress warnings about inconsistencies in length between input
2330 and parsed strings and missing characters.
2333 -V Output various status information to stderr during processing.
2334 Default is to work silently.
2336 --version Show version number.
2342 latexdiff - determine and markup differences between two latex files
2346 B<latexdiff> [ B<OPTIONS> ] F<old.tex> F<new.tex> > F<diff.tex>
2350 Briefly, I<latexdiff> is a utility program to aid in the management of
2351 revisions of latex documents. It compares two valid latex files, here
2352 called C<old.tex> and C<new.tex>, finds significant differences
2353 between them (i.e., ignoring the number of white spaces and position
2354 of line breaks), and adds special commands to highlight the
2355 differences. Where visual highlighting is not possible, e.g. for changes
2356 in the formatting, the differences are
2357 nevertheless marked up in the source.
2359 The program treats the preamble differently from the main document.
2360 Differences between the preambles are found using line-based
2361 differencing (similarly to the Unix diff command, but ignoring white
2362 spaces). A comment, "S<C<%DIF E<gt>>>" is appended to each added line, i.e. a
2363 line present in C<new.tex> but not in C<old.tex>. Discarded lines
2364 are deactivated by prepending "S<C<%DIF E<lt>>>". Changed blocks are preceded by
2365 comment lines giving information about line numbers in the original files. Where there are insignificant
2366 differences, the resulting file C<diff.tex> will be similar to
2367 C<new.tex>. At the end of the preamble, the definitions for I<latexdiff> markup commands are inserted.
2368 In differencing the main body of the text, I<latexdiff> attempts to
2369 satisfy the following guidelines (in order of priority):
2375 If both C<old.tex> and C<new.tex> are valid LaTeX, then the resulting
2376 C<diff.tex> should also be valid LateX. (NB If a few plain TeX commands
2377 are used within C<old.tex> or C<new.tex> then C<diff.tex> is not
2378 guaranteed to work but usually will).
2382 Significant differences are determined on the level of
2383 individual words. All significant differences, including differences
2384 between comments should be clearly marked in the resulting source code
2389 If a changed passage contains text or text-producing commands, then
2390 running C<diff.tex> through LateX should produce output where added
2391 and discarded passages are highlighted.
2395 Where there are insignificant differences, e.g. in the positioning of
2396 line breaks, C<diff.tex> should follow the formatting of C<new.tex>
2400 For differencing the same algorithm as I<diff> is used but words
2401 instead of lines are compared. An attempt is made to recognize
2402 blocks which are completely changed such that they can be marked up as a unit.
2403 Comments are differenced line by line
2404 but the number of spaces within comments is ignored. Commands including
2405 all their arguments are generally compared as one unit, i.e., no mark-up
2406 is inserted into the arguments of commands. However, for a selected
2407 number of commands (for example, C<\caption> and all sectioning
2408 commands) the last argument is known to be text. This text is
2409 split into words and differenced just as ordinary text (use options to
2410 show and change the list of text commands, see below). As the
2411 algorithm has no detailed knowledge of LaTeX, it assumes all pairs of
2412 curly braces immediately following a command (i.e. a sequence of
2413 letters beginning with a backslash) are arguments for that command.
2414 As a restriction to condition 1 above it is thus necessary to surround
2415 all arguments with curly braces, and to not insert
2416 extraneous spaces. For example, write
2418 \section{\textem{This is an emphasized section title}}
2422 \section {\textem{This is an emphasized section title}}
2426 \section\textem{This is an emphasized section title}
2428 even though all varieties are the same to LaTeX (but see
2429 B<--allow-spaces> option which allows the second variety).
2431 For environments whose content does not conform to standard LaTeX or
2432 where graphical markup does not make sense all markup commands can be
2433 removed by setting the PICTUREENV configuration variable, set by
2434 default to C<picture> and C<DIFnomarkup> environments; see B<--config>
2435 option). The latter environment (C<DIFnomarkup>) can be used to
2436 protect parts of the latex file where the markup results in illegal
2437 markup. You have to surround the offending passage in both the old and
2438 new file by C<\begin{DIFnomarkup}> and C<\end{DIFnomarkup}>. You must
2439 define the environment in the preambles of both old and new
2440 documents. I prefer to define it as a null-environment,
2442 C<\newenvironment{DIFnomarkup}{}{}>
2444 but the choice is yours. Any markup within the environment will be
2445 removed, and generally everything within the environment will just be
2446 taken from the new file.
2448 It is also possible to difference files which do not have a preamble.
2449 In this case, the file is processed in the main document
2450 mode, but the definitions of the markup commands are not inserted.
2452 All markup commands inserted by I<latexdiff> begin with "C<\DIF>". Added
2453 blocks containing words, commands or comments which are in C<new.tex>
2454 but not in C<old.tex> are marked by C<\DIFaddbegin> and C<\DIFaddend>.
2455 Discarded blocks are marked by C<\DIFdelbegin> and C<\DIFdelend>.
2456 Within added blocks all text is highlighted with C<\DIFadd> like this:
2457 C<\DIFadd{Added text block}>
2458 Selected `safe' commands can be contained in these text blocks as well
2459 (use options to show and change the list of safe commands, see below).
2460 All other commands as well as braces "{" and "}" are never put within
2461 the scope of C<\DIFadd>. Added comments are marked by prepending
2462 "S<C<%DIF E<gt> >>".
2464 Within deleted blocks text is highlighted with C<\DIFdel>. Deleted
2465 comments are marked by prepending "S<C<%DIF E<lt> >>". Non-safe command
2466 and curly braces within deleted blocks are commented out with
2467 "S<C<%DIFDELCMD E<lt> >>".
2475 The following options determine the visual markup style by adding the appropriate
2476 command definitions to the preamble. See the end of this section for a description of
2481 =item B<--type=markupstyle> or
2484 Add code to preamble for selected markup style. This option defines
2485 C<\DIFadd> and C<\DIFdel> commands.
2488 C<UNDERLINE CTRADITIONAL TRADITIONAL CFONT FONTSTRIKE INVISIBLE
2489 CHANGEBAR CCHANGEBAR CULINECHBAR CFONTCBHBAR>
2491 [ Default: C<UNDERLINE> ]
2493 =item B<--subtype=markstyle> or
2496 Add code to preamble for selected style for bracketing
2497 commands (e.g. to mark changes in margin). This option defines
2498 C<\DIFaddbegin>, C<\DIFaddend>, C<\DIFdelbegin> and C<\DIFdelend> commands.
2499 Available styles: C<SAFE MARGINAL DVIPSCOL>
2501 [ Default: C<SAFE> ]
2503 =item B<--floattype=markstyle> or
2506 Add code to preamble for selected style which
2507 replace standard marking and markup commands within floats
2508 (e.g., marginal remarks cause an error within floats
2509 so marginal marking can be disabled thus). This option defines all
2510 C<\DIF...FL> commands.
2511 Available styles: C<FLOATSAFE TRADITIONALSAFE IDENTICAL>
2513 [ Default: C<FLOATSAFE> ]
2515 =item B<--encoding=enc> or
2518 Specify encoding of old.tex and new.tex. Typical encodings are
2519 C<ascii>, C<utf8>, C<latin1>, C<latin9>. A list of available encodings can be
2520 obtained by executing
2522 C<perl -MEncode -e 'print join ("\n",Encode->encodings( ":all" )) ;' >
2524 [Default encoding is utf8 unless the first few lines of the preamble contain
2525 an invocation C<\usepackage[..]{inputenc}> in which case the
2526 encoding chosen by this command is asssumed. Note that ASCII (standard
2527 latex) is a subset of utf8]
2529 =item B<--preamble=file> or
2532 Insert file at end of preamble instead of generating
2533 preamble. The preamble must define the following commands
2534 C<\DIFaddbegin, \DIFaddend, \DIFadd{..},
2535 \DIFdelbegin,\DIFdelend,\DIFdel{..},>
2536 and varieties for use within floats
2537 C<\DIFaddbeginFL, \DIFaddendFL, \DIFaddFL{..},
2538 \DIFdelbeginFL, \DIFdelendFL, \DIFdelFL{..}>
2539 (If this option is set B<-t>, B<-s>, and B<-f> options
2542 =item B<--packages=pkg1,pkg2,..>
2544 Tell latexdiff that .tex file is processed with the packages in list
2545 loaded. This is normally not necessary if the .tex file includes the
2546 preamble, as the preamble is automatically scanned for C<\usepackage> commands.
2547 Use of the B<--packages> option disables automatic scanning, so if for any
2548 reason package specific parsing needs to be switched off, use B<--packages=none>.
2549 The following packages trigger special behaviour:
2555 Configuration variable amsmath is set to C<align*> (Default: C<eqnarray*>)
2559 Ensure that C<\begin{figure}> and C<\end{figure}> always appear by themselves on a line.
2563 Change name of C<\DIFadd> and C<\DIFdel> commands to C<\DIFaddtex> and C<\DIFdeltex> and
2564 define new C<\DIFadd> and C<\DIFdel> commands, which provide a wrapper for these commands,
2565 using them for the text but not for the link defining command (where any markup would cause
2570 [ Default: scan the preamble for C<\\usepackage> commands to determine
2575 =item B<--show-preamble>
2577 Print generated or included preamble commands to stdout.
2581 =head2 Configuration
2585 =item B<--exclude-safecmd=exclude-file> or
2586 B<-A exclude-file> or B<--exclude-safecmd="cmd1,cmd2,...">
2588 =item B<--replace-safecmd=replace-file>
2590 =item B<--append-safecmd=append-file> or
2591 B<-a append-file> or B<--append-safecmd="cmd1,cmd2,...">
2593 Exclude from, replace or append to the list of regular expressions (RegEx)
2594 matching commands which are safe to use within the
2595 scope of a C<\DIFadd> or C<\DIFdel> command. The file must contain
2596 one Perl-RegEx per line (Comment lines beginning with # or % are
2597 ignored). Note that the RegEx needs to match the whole of
2598 the token, i.e., /^regex$/ is implied and that the initial
2599 "\" of the command is not included.
2600 The B<--exclude-safecmd> and B<--append-safecmd> options can be combined with the -B<--replace-safecmd>
2601 option and can be used repeatedly to add cumulatively to the lists.
2602 B<--exclude-safecmd>
2603 and B<--append-safecmd> can also take a comma separated list as input. If a
2604 comma for one of the regex is required, escape it thus "\,". In most cases it
2605 will be necessary to protect the comma-separated list from the shell by putting
2606 it in quotation marks.
2608 =item B<--exclude-textcmd=exclude-file> or
2609 B<-X exclude-file> or B<--exclude-textcmd="cmd1,cmd2,...">
2611 =item B<--replace-textcmd=replace-file>
2613 =item B<--append-textcmd=append-file> or
2614 B<-x append-file> or B<--append-textcmd="cmd1,cmd2,...">
2616 Exclude from, replace or append to the list of regular expressions
2617 matching commands whose last argument is text. See
2618 entry for B<--exclude-safecmd> directly above for further details.
2621 =item B<--replace-context1cmd=replace-file>
2623 =item B<--append-context1cmd=append-file> or
2624 =item B<--append-context1cmd="cmd1,cmd2,...">
2626 Replace or append to the list of regex matching commands
2627 whose last argument is text but which require a particular
2628 context to work, e.g. \caption will only work within a figure
2629 or table. These commands behave like text commands, except when
2630 they occur in a deleted section, when they are disabled, but their
2631 argument is shown as deleted text.
2633 =item B<--replace-context1cmd=replace-file>
2635 =item B<--append-context2cmd=append-file> or
2636 =item B<--append-context2cmd="cmd1,cmd2,...">
2637 As corresponding commands for context1. The only difference is that
2638 context2 commands are completely disabled in deleted sections, including
2643 =item B<--config var1=val1,var2=val2,...> or B<-c var1=val1,..>
2645 =item B<-c configfile>
2647 Set configuration variables. The option can be repeated to set different
2648 variables (as an alternative to the comma-separated list).
2649 Available variables (see below for further explanations):
2651 C<MINWORDSBLOCK> (integer)
2655 C<PICTUREENV> (RegEx)
2659 C<MATHREPL> (String)
2661 C<MATHARRENV> (RegEx)
2663 C<MATHARRREPL> (String)
2667 C<COUNTERCMD> (RegEx)
2669 =item B<--show-safecmd>
2671 Print list of RegEx matching and excluding safe commands.
2673 =item B<--show-textcmd>
2675 Print list of RegEx matching and excluding commands with text argument.
2677 =item B<--show-config>
2679 Show values of configuration variables.
2683 Combine all --show commands.
2685 NB For all --show commands, no C<old.tex> or C<new.tex> file needs to be specified, and no
2686 differencing takes place.
2691 =head2 Miscellaneous
2695 =item B<--verbose> or B<-V>
2697 Output various status information to stderr during processing.
2698 Default is to work silently.
2700 =item B<--ignore-warnings>
2702 Suppress warnings about inconsistencies in length between input and
2703 parsed strings and missing characters. These warning messages are
2704 often related to non-standard latex or latex constructions with a
2705 syntax unknown to C<latexdiff> but the resulting difference argument
2706 is often fully functional anyway, particularly if the non-standard
2707 latex only occurs in parts of the text which have not changed.
2709 =item B<--allow-spaces>
2711 Allow spaces between bracketed or braced arguments to commands. Note
2712 that this option might have undesirable side effects (unrelated scope
2713 might get lumpeded with preceding commands) so should only be used if the
2714 default produces erroneous results. (Default requires arguments to
2715 directly follow each other without intervening spaces).
2719 Replace C<\input> and C<\include> commands within body by the content
2720 of the files in their argument. If C<\includeonly> is present in the
2721 preamble, only those files are expanded into the document. However,
2722 no recursion is done, i.e. C<\input> and C<\include> commands within
2723 included sections are not expanded. The included files are assumed to
2724 be located in the same directories as the old and new master files,
2725 respectively, making it possible to organise files into old and new directories.
2727 Use of this option is not recommended
2728 primarily the processing time for the large documents is prohibitive, and
2729 the resulting difference document no longer reflects the structure of the
2743 =head2 Predefined styles
2747 The major type determine the markup of plain text and some selected latex commands outside floats by defining the markup commands C<\DIFadd{...}> and C<\DIFdel{...}> .
2753 Added text is wavy-underlined and blue, discarded text is struck out and red
2754 (Requires color and ulem packages). Overstriking does not work in displayed math equations such that deleted parts of equation are underlined, not struck out (this is a shortcoming inherent to the ulem package).
2756 =item C<CTRADITIONAL>
2758 Added text is blue and set in sans-serif, and a red footnote is created for each discarded
2759 piece of text. (Requires color package)
2761 =item C<TRADITIONAL>
2763 Like C<CTRADITIONAL> but without the use of color.
2767 Added text is blue and set in sans-serif, and discarded text is red and very small size.
2771 Added tex is set in sans-serif, discarded text small and struck out
2775 Added text is blue, and discarded text is red. Additionally, the changed text is marked with a bar in the margin (Requires color and changebar packages).
2779 Like C<CFONT> but with additional changebars (Requires color and changebar packages).
2781 =item C<CULINECHBAR>
2783 Like C<UNDERLINE> but with additional changebars (Requires color, ulem and changebar packages).
2787 No mark up of text, but mark margins with changebars (Requires changebar package).
2791 No visible markup (but generic markup commands will still be inserted.
2797 The subtype defines the commands that are inserted at the begin and end of added or discarded blocks, irrespectively of whether these blocks contain text or commands (Defined commands: C<\DIFaddbegin, \DIFaddend, \DIFdelbegin, \DIFdelend>)
2803 No additional markup (Recommended choice)
2807 Mark beginning and end of changed blocks with symbols in the margin nearby (using
2808 the standard C<\marginpar> command - note that this sometimes moves somewhat
2809 from the intended position.
2813 An alternative way of marking added passages in blue, and deleted ones in red. Note
2814 that C<DVIPSCOL> only works with the dvips converter, e.g. not pdflatex.
2815 (it is recommeneded to use instead the main types to effect colored markup,
2816 although in some cases coloring with dvipscol can be more complete).
2822 Some of the markup used in the main text might cause problems when used within
2823 floats (e.g. figures or tables). For this reason alternative versions of all
2824 markup commands are used within floats. The float type defines these alternative commands.
2830 Use identical markup for text as in the main body, but set all commands marking the begin and end of changed blocks to null-commands. You have to choose this float type if your subtype is C<MARGIN> as C<\marginpar> does not work properly within floats.
2832 =item C<TRADITIONALSAFE>
2834 Mark additions the same way as in the main text. Deleted environments are marked by angular brackets \[ and \] and the deleted text is set in scriptscript size. This float type should always be used with the C<TRADITIONAL> and C<CTRADITIONAL> markup types as the \footnote command does not work properly in floating environments.
2838 Make no difference between the main text and floats.
2843 =head2 Configuration Variables
2847 =item C<MINWORDSBLOCK>
2849 Minimum number of tokens required to form an independent block. This value is
2850 used in the algorithm to detect changes of complete blocks by merging identical text parts of less than C<MINWORDSBLOCK> to the preceding added and discarded parts.
2856 Environments whose name matches the regular expression in C<FLOATENV> are
2857 considered floats. Within these environments, the I<latexdiff> markup commands
2858 are replaced by their FL variaties.
2860 [ Default: S<C<(?:figure|table|plate)[\w\d*@]*> >]
2864 Within environments whose name matches the regular expression in C<PICTUREENV>
2865 all latexdiff markup is removed (in pathologic cases this might lead to
2866 inconsistent markup but this situation should be rare).
2868 [ Default: S<C<(?:picture|DIFnomarkup)[\w\d*@]*> >]
2870 =item C<MATHENV>,C<MATHREPL>
2872 If both \begin and \end for a math environment (environment name matching C<MATHENV>
2874 are within the same deleted block, they are replaced by a \begin and \end commands for C<MATHREPL>
2875 rather than being commented out.
2877 [ Default: C<MATHENV>=S<C<(?:displaymath|equation)> >, C<MATHREPL>=S<C<displaymath> >]
2879 =item C<MATHARRENV>,C<MATHARRREPL>
2881 as C<MATHENV>,C<MATHREPL> but for equation arrays
2883 [ Default: C<MATHARRENV>=S<C<eqnarray\*?> >, C<MATHREPL>=S<C<eqnarray> >]
2887 If a match to C<ARRENV> is found within an inline math environment within a deleted or added block, then the inlined math
2888 is surrounded by C<\mbox{>...C<}>. This is necessary as underlining does not work within inlined array environments.
2890 [ Default: C<ARRENV>=S<C<(?:array|[pbvBV]matrix)> >
2894 If a command in a deleted block which is also in the textcmd list matches C<COUNTERCMD> then an
2895 additional command C<\addtocounter{>F<cntcmd>C<}{-1}>, where F<cntcmd> is the matching command, is appended in the diff file such that the numbering in the diff file remains synchronized with the
2896 numbering in the new file.
2898 [ Default: C<COUNTERCMD>=C<(?:footnote|part|section|subsection> ...
2900 C<|subsubsection|paragraph|subparagraph)> ]
2906 UTF-8 support requires a relatively new version of perl (5.8.0 is sufficient
2907 but 5.6.2 up would work OK, too).
2909 Option allow-spaces not implemented entirely consistently. It breaks
2910 the rules that number and type of white space does not matter, as
2911 different numbers of inter-argument spaces are treated as significant.
2913 Please send bug reports
2914 to I<tilmann@esc.cam.ac.uk>. Include the serial number of I<latexdiff>
2915 (from comments at the top of the source or use B<--version>). If you come across latex
2916 files that are error-free and conform to the specifications set out
2917 above, and whose differencing still does not result in error-free
2918 latex, please send me those files, ideally edited to only contain the
2919 offending passage as long as that still reproduces the problem.
2927 I<latexdiff> does not make use of external commands and thus should run
2928 on any platform supporting Perl 5.6 or higher. If files with encodings
2929 other than ASCII or UTF-8 are processed, Perl 5.8 or higher is required.
2931 The standard version of I<latexdiff> requires installation of the Perl package
2932 C<Algorithm::Diff> (available from I<www.cpan.org> -
2933 I<http://search.cpan.org/~nedkonz/Algorithm-Diff-1.15>) but a stand-alone
2934 version, I<latexdiff-so>, which has this package inlined, is available, too.
2935 I<latexdiff-fast> requires the I<diff> command to be present.
2939 Copyright (C) 2004-2007 Frederik Tilmann
2941 This program is free software; you can redistribute it and/or modify
2942 it under the terms of the GNU General Public License Version 2
2944 Contributors of fixes and additions: V. Kuhlmann, J. Paisley, N. Becker, T. Doerges, K. Huebner
2945 Thanks to everyone who send in bug reports.
2950 %%BEGIN SAFE COMMANDS
2951 % Regex matching commands which can safely be in the
2952 % argument of a \DIFadd or \DIFdel command (leave out the \)
3068 (?:sq)?su[bp]set(?:eq)?
3140 (?:arc)?(?:cos|sin|tan|cot)h?
3166 %%BEGIN TEXT COMMANDS
3167 % Regex matching commands with a text argument (leave out the \)
3181 (sub){0,2}section\*?
3199 %%BEGIN CONTEXT1 COMMANDS
3200 % Regex matching commands with a text argument (leave out the \), which will fail out of context, but who's arguemtn should be printed as plain text
3202 %%END CONTEXT1 COMMANDS
3204 %%BEGIN CONTEXT2 COMMANDS
3205 % Regex matching commands with a text argument (leave out the \), which will fail out of context, but who's arguemtn should be printed as plain text
3208 %%END CONTEXT2 COMMANDS
3211 %% TYPES (Commands for highlighting changed blocks)
3213 %DIF UNDERLINE PREAMBLE
3214 \RequirePackage[normalem]{ulem}
3215 \RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1}
3216 \providecommand{\DIFadd}[1]{{\protect\color{blue}\uwave{#1}}}
3217 \providecommand{\DIFdel}[1]{{\protect\color{red}\sout{#1}}}
3218 %DIF END UNDERLINE PREAMBLE
3220 %DIF CTRADITIONAL PREAMBLE
3221 \RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1}
3222 \RequirePackage[stable]{footmisc}
3223 \providecommand{\DIFadd}[1]{{\protect\color{blue} \sf #1}}
3224 \providecommand{\DIFdel}[1]{{\protect\color{red} [..\footnote{removed: #1} ]}}
3225 %DIF END CTRADITIONAL PREAMBLE
3227 %DIF TRADITIONAL PREAMBLE
3228 \RequirePackage[stable]{footmisc}
3229 \providecommand{\DIFadd}[1]{{\sf #1}}
3230 \providecommand{\DIFdel}[1]{{[..\footnote{removed: #1} ]}}
3231 %DIF END TRADITIONAL PREAMBLE
3234 \RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1}
3235 \providecommand{\DIFadd}[1]{{\protect\color{blue} \sf #1}}
3236 \providecommand{\DIFdel}[1]{{\protect\color{red} \scriptsize #1}}
3237 %DIF END CFONT PREAMBLE
3239 %DIF FONTSTRIKE PREAMBLE
3240 \RequirePackage[normalem]{ulem}
3241 \providecommand{\DIFadd}[1]{{\sf #1}}
3242 \providecommand{\DIFdel}[1]{{\footnotesize \sout{#1}}}
3243 %DIF END FONTSTRIKE PREAMBLE
3245 %DIF CCHANGEBAR PREAMBLE
3246 \RequirePackage[dvips]{changebar}
3247 \RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1}
3248 \providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}#1}\protect\cbend}
3249 \providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}#1}\protect\cbdelete}
3250 %DIF END CCHANGEBAR PREAMBLE
3252 %DIF CFONTCHBAR PREAMBLE
3253 \RequirePackage[dvips]{changebar}
3254 \RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1}
3255 \providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}\sf #1}\protect\cbend}
3256 \providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}\scriptsize #1}\protect\cbdelete}
3257 %DIF END CFONTCHBAR PREAMBLE
3259 %DIF CULINECHBAR PREAMBLE
3260 \RequirePackage[normalem]{ulem}
3261 \RequirePackage[dvips]{changebar}
3262 \RequirePackage{color}
3263 \providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}\uwave{#1}}\protect\cbend}
3264 \providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}\sout{#1}}\protect\cbdelete}
3265 %DIF END CULINECHBAR PREAMBLE
3267 %DIF CHANGEBAR PREAMBLE
3268 \RequirePackage[dvips]{changebar}
3269 \providecommand{\DIFadd}[1]{\protect\cbstart{#1}\protect\cbend}
3270 \providecommand{\DIFdel}[1]{\protect\cbdelete}
3271 %DIF END CHANGEBAR PREAMBLE
3273 %DIF INVISIBLE PREAMBLE
3274 \providecommand{\DIFadd}[1]{#1}
3275 \providecommand{\DIFdel}[1]{}
3276 %DIF END INVISIBLE PREAMBLE
3279 %% SUBTYPES (Markers for beginning and end of changed blocks)
3282 \providecommand{\DIFaddbegin}{}
3283 \providecommand{\DIFaddend}{}
3284 \providecommand{\DIFdelbegin}{}
3285 \providecommand{\DIFdelend}{}
3286 %DIF END SAFE PREAMBLE
3288 %DIF MARGIN PREAMBLE
3289 \providecommand{\DIFaddbegin}{\protect\marginpar{a[}}
3290 \providecommand{\DIFaddend}{\protect\marginpar{]}}
3291 \providecommand{\DIFdelbegin}{\protect\marginpar{d[}}
3292 \providecommand{\DIFdelend}{\protect\marginpar{]}}
3293 %DIF END BRACKET PREAMBLE
3295 %DIF DVIPSCOL PREAMBLE
3296 %Note: only works with dvips converter
3297 \RequirePackage{color}
3298 \RequirePackage{dvipscol}
3299 \providecommand{\DIFaddbegin}{\protect\nogroupcolor{blue}}
3300 \providecommand{\DIFaddend}{\protect\nogroupcolor{black}}
3301 \providecommand{\DIFdelbegin}{\protect\nogroupcolor{red}}
3302 \providecommand{\DIFdelend}{\protect\nogroupcolor{black}}
3303 %DIF END DVIPSCOL PREAMBLE
3308 %DIF FLOATSAFE PREAMBLE
3309 \providecommand{\DIFaddFL}[1]{\DIFadd{#1}}
3310 \providecommand{\DIFdelFL}[1]{\DIFdel{#1}}
3311 \providecommand{\DIFaddbeginFL}{}
3312 \providecommand{\DIFaddendFL}{}
3313 \providecommand{\DIFdelbeginFL}{}
3314 \providecommand{\DIFdelendFL}{}
3315 %DIF END FLOATSAFE PREAMBLE
3317 %DIF IDENTICAL PREAMBLE
3318 \providecommand{\DIFaddFL}[1]{\DIFadd{#1}}
3319 \providecommand{\DIFdelFL}[1]{\DIFdel{#1}}
3320 \providecommand{\DIFaddbeginFL}{\DIFaddbegin}
3321 \providecommand{\DIFaddendFL}{\DIFaddend}
3322 \providecommand{\DIFdelbeginFL}{\DIFdelbegin}
3323 \providecommand{\DIFdelendFL}{\DIFdelend}
3324 %DIF END IDENTICAL PREAMBLE
3326 %DIF TRADITIONALSAFE PREAMBLE
3327 % procidecommand color to make this work for TRADITIONAL and CTRADITIONAL
3328 \providecommand{\color}[1]{}
3329 \providecommand{\DIFaddFL}[1]{\DIFadd{#1}}
3330 \providecommand{\DIFdel}[1]{{\protect\color{red}[..{\scriptsize {removed: #1}} ]}}
3331 \providecommand{\DIFaddbeginFL}{}
3332 \providecommand{\DIFaddendFL}{}
3333 \providecommand{\DIFdelbeginFL}{}
3334 \providecommand{\DIFdelendFL}{}
3335 %DIF END FLOATSAFE PREAMBLE
3337 %% SPECIAL PACKAGE PREAMBLE COMMANDS
3339 % Standard \DIFadd and \DIFdel are redefined as \DIFaddtex and \DIFdeltex
3340 % when hyperref package is included.
3341 %DIF HYPERREF PREAMBLE
3342 \providecommand{\DIFadd}[1]{\texorpdfstring{\DIFaddtex{#1}}{#1}}
3343 \providecommand{\DIFdel}[1]{\texorpdfstring{\DIFdeltex{#1}}{}}
3344 %DIF END HYPERREF PACKAGE