diff options
Diffstat (limited to 'Master/texmf-dist/doc/latex/latexdiff/latexdiff-fast')
-rwxr-xr-x | Master/texmf-dist/doc/latex/latexdiff/latexdiff-fast | 3344 |
1 files changed, 0 insertions, 3344 deletions
diff --git a/Master/texmf-dist/doc/latex/latexdiff/latexdiff-fast b/Master/texmf-dist/doc/latex/latexdiff/latexdiff-fast deleted file mode 100755 index ee6c7419ac9..00000000000 --- a/Master/texmf-dist/doc/latex/latexdiff/latexdiff-fast +++ /dev/null @@ -1,3344 +0,0 @@ -#!/usr/bin/perl -w -# latexdiff - differences two latex files on the word level -# and produces a latex file with the differences marked up. -# -# Copyright (C) 2004-2007 F J Tilmann (tilmann@esc.cam.ac.uk) -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License Version 2 as published by -# the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# Detailed usage information at the end of the file -# -# Version 0.5 A number of minor improvements based on feedback -# Deleted blocks are now shown before added blocks -# Package specific processing -# -# Version 0.43 unreleased typo in list of styles at the end -# Add protect to all \cbstart, \cbend commands -# More robust substitution of deleted math commands -# -# Version 0.42 November 06 Bug fixes only -# -# Version 0.4 March 06 option for fast differencing using UNIX diff command, several minor bug fixes (\par bug, improved highlighting of textcmds) -# -# Version 0.3 August 05 improved parsing of displayed math, --allow-spaces -# option, several minor bug fixes -# -# Version 0.25 October 04 Fix bug with deleted equations, add math mode commands to safecmd, add | to allowed interpunctuation signs -# Version 0.2 September 04 extension to utf-8 and variable encodings -# Version 0.1 August 04 First public release - -# Inserted block for differenceing -# use Algorithm::Diff qw(traverse_sequences); -# in standard version -# The following BEGIN block contains a verbatim copy of -# Ned Konz' Algorithm::Diff package version 1.15 except -# that subroutine _longestCommonSubsequence has been replace by -# a routine which internally uses the UNIX diff command for -# the differencing rather than the Perl routines if the -# length of the sequences exceeds some threshold. -# Also, all POD documentation has been stripped out. -# -# (the distribution on which this modification is based is available -# from http://search.cpan.org/~nedkonz/Algorithm-Diff-1.15 -# the most recent version can be found via http://search.cpan.org/search?module=Algorithm::Diff ) -# Please note that the LICENCSE for Algorithm::Diff : -# "Copyright (c) 2000-2002 Ned Konz. All rights reserved. -# This program is free software; -# you can redistribute it and/or modify it under the same terms -# as Perl itself." -# The fast-differencing version of latexdiff is provided as a convenience -# for latex users under Unix-like systems which have a 'diff' command. -# If you believe -# the inlining of Algorithm::Diff violates its license please contact -# me and I will modify the latexdiff distribution accordingly. -# Frederik Tilmann (tilmann@esc.cam.ac.uk) -# Jonathan Paisley is acknowledged for the idea of using the system diff -# command to achieve shorter running times -BEGIN { -package Algorithm::Diff; -use strict; -use vars qw($VERSION @EXPORT_OK @ISA @EXPORT); -use integer; # see below in _replaceNextLargerWith() for mod to make - # if you don't use this -require Exporter; -@ISA = qw(Exporter); -@EXPORT = qw(); -@EXPORT_OK = qw(LCS diff traverse_sequences traverse_balanced sdiff); -$VERSION = sprintf('%d.%02d fast', (q$Revision: 1.15 $ =~ /\d+/g)); - -# Global parameters - -use File::Temp qw/tempfile/; -# if larger number of elements in longestCommonSubsequence smaller than -# this number, then use internal algorithm, otherwise use UNIX diff -use constant THRESHOLD => 100 ; -# Detect whether diff --minimal option is available -# if yes we use it -use constant MINIMAL => ( system('diff','--minimal','/dev/null','/dev/null') >> 8 ==0 ? "--minimal" : "" ) ; - - - -# McIlroy-Hunt diff algorithm -# Adapted from the Smalltalk code of Mario I. Wolczko, <mario@wolczko.com> -# by Ned Konz, perl@bike-nomad.com - - -# Create a hash that maps each element of $aCollection to the set of positions -# it occupies in $aCollection, restricted to the elements within the range of -# indexes specified by $start and $end. -# The fourth parameter is a subroutine reference that will be called to -# generate a string to use as a key. -# Additional parameters, if any, will be passed to this subroutine. -# -# my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen ); - -sub _withPositionsOfInInterval -{ - my $aCollection = shift; # array ref - my $start = shift; - my $end = shift; - my $keyGen = shift; - my %d; - my $index; - for ( $index = $start ; $index <= $end ; $index++ ) - { - my $element = $aCollection->[$index]; - my $key = &$keyGen( $element, @_ ); - if ( exists( $d{$key} ) ) - { - unshift ( @{ $d{$key} }, $index ); - } - else - { - $d{$key} = [$index]; - } - } - return wantarray ? %d : \%d; -} - -# Find the place at which aValue would normally be inserted into the array. If -# that place is already occupied by aValue, do nothing, and return undef. If -# the place does not exist (i.e., it is off the end of the array), add it to -# the end, otherwise replace the element at that point with aValue. -# It is assumed that the array's values are numeric. -# This is where the bulk (75%) of the time is spent in this module, so try to -# make it fast! - -sub _replaceNextLargerWith -{ - my ( $array, $aValue, $high ) = @_; - $high ||= $#$array; - - # off the end? - if ( $high == -1 || $aValue > $array->[-1] ) - { - push ( @$array, $aValue ); - return $high + 1; - } - - # binary search for insertion point... - my $low = 0; - my $index; - my $found; - while ( $low <= $high ) - { - $index = ( $high + $low ) / 2; - - # $index = int(( $high + $low ) / 2); # without 'use integer' - $found = $array->[$index]; - - if ( $aValue == $found ) - { - return undef; - } - elsif ( $aValue > $found ) - { - $low = $index + 1; - } - else - { - $high = $index - 1; - } - } - - # now insertion point is in $low. - $array->[$low] = $aValue; # overwrite next larger - return $low; -} - -# This method computes the longest common subsequence in $a and $b. - -# Result is array or ref, whose contents is such that -# $a->[ $i ] == $b->[ $result[ $i ] ] -# foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined. - -# An additional argument may be passed; this is a hash or key generating -# function that should return a string that uniquely identifies the given -# element. It should be the case that if the key is the same, the elements -# will compare the same. If this parameter is undef or missing, the key -# will be the element as a string. - -# By default, comparisons will use "eq" and elements will be turned into keys -# using the default stringizing operator '""'. - -# Additional parameters, if any, will be passed to the key generation routine. - -sub _longestCommonSubsequence -{ - my $a = shift; # array ref - my $b = shift; # array ref - my $keyGen = shift; # code ref - my $compare; # code ref - - # set up code refs - # Note that these are optimized. - if ( !defined($keyGen) ) # optimize for strings - { - $keyGen = sub { $_[0] }; - $compare = sub { my ( $a, $b ) = @_; $a eq $b }; - } - else - { - $compare = sub { - my $a = shift; - my $b = shift; - &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ ); - }; - } - - my ( $aStart, $aFinish, $bStart, $bFinish, $matchVector ) = - ( 0, $#$a, 0, $#$b, [] ); - - # Check whether to use internal routine (small number of elements) - # or use it as a wrapper for UNIX diff - if ( ( $#$a > $#$b ? $#$a : $#$b) < THRESHOLD ) { - ### print STDERR "DEBUG: regular longestCommonSubsequence\n"; - # First we prune off any common elements at the beginning - while ( $aStart <= $aFinish - and $bStart <= $bFinish - and &$compare( $a->[$aStart], $b->[$bStart], @_ ) ) - { - $matchVector->[ $aStart++ ] = $bStart++; - } - - # now the end - while ( $aStart <= $aFinish - and $bStart <= $bFinish - and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) ) - { - $matchVector->[ $aFinish-- ] = $bFinish--; - } - - # Now compute the equivalence classes of positions of elements - my $bMatches = - _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ ); - my $thresh = []; - my $links = []; - - my ( $i, $ai, $j, $k ); - for ( $i = $aStart ; $i <= $aFinish ; $i++ ) - { - $ai = &$keyGen( $a->[$i], @_ ); - if ( exists( $bMatches->{$ai} ) ) - { - $k = 0; - for $j ( @{ $bMatches->{$ai} } ) - { - - # optimization: most of the time this will be true - if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j ) - { - $thresh->[$k] = $j; - } - else - { - $k = _replaceNextLargerWith( $thresh, $j, $k ); - } - - # oddly, it's faster to always test this (CPU cache?). - if ( defined($k) ) - { - $links->[$k] = - [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ]; - } - } - } - } - - if (@$thresh) - { - for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] ) - { - $matchVector->[ $link->[1] ] = $link->[2]; - } - } - } - else { - my ($fha,$fhb,$fna,$fnb,$ele,$key); - my ($alines,$blines,$alb,$alf,$blb,$blf); - my ($minimal)=MINIMAL; - # large number of elements, use system diff - ### print STDERR "DEBUG: fast (diff) longestCommonSubsequence\n"; - - ($fha,$fna)=tempfile("DiffA-XXXX") or die "_longestCommonSubsequence: Cannot open tempfile for sequence A"; - ($fhb,$fnb)=tempfile("DiffB-XXXX") or die "_longestCommonSubsequence: Cannot open tempfile for sequence B"; - # prepare sequence A - foreach $ele ( @$a ) { - $key=&$keyGen( $ele, @_ ); - $key =~ s/\\/\\\\/g ; - $key =~ s/\n/\\n/sg ; - print $fha "$key\n" ; - } - close($fha); - # prepare sequence B - foreach $ele ( @$b ) { - $key=&$keyGen( $ele, @_ ); - $key =~ s/\\/\\\\/g ; - $key =~ s/\n/\\n/sg ; - print $fhb "$key\n" ; - } - close($fhb); - - open(DIFFPIPE, "diff $minimal $fna $fnb |") or die "_longestCommonSubsequence: Cannot launch diff process. $!" ; - # The diff line numbering begins with 1, but Perl subscripts start with 0 - # We follow the diff numbering but substract 1 when assigning to matchVector - $aStart++; $bStart++ ; $aFinish++ ; $bFinish++ ; - while( <DIFFPIPE> ) { - if ( ($alines,$blines) = ( m/^(\d*(?:,\d*)?)?c(\d*(?:,\d*)?)?$/ ) ) { - ($alb,$alf)=split(/,/,$alines); - ($blb,$blf)=split(/,/,$blines); - $alf=$alb unless defined($alf); - $blf=$blb unless defined($blf); - while($aStart < $alb ) { - $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ; - } - # check for consistency - $bStart==$blb or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency in changed sequence"; - $aStart=$alf+1; - $bStart=$blf+1; - } - elsif ( ($alb,$blines) = ( m/^(\d*)a(\d*(?:,\d*)?)$/ ) ) { - ($blb,$blf)=split(/,/,$blines); - $blf=$blb unless defined($blf); - while ( $bStart < $blb ) { - $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ; - } - $aStart==$alb+1 or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency in appended sequence near elements $aStart and $bStart"; - $bStart=$blf+1; - } - elsif ( ($alines,$blb) = ( m/^(\d*(?:,\d*)?)d(\d*)$/ ) ) { - ($alb,$alf)=split(/,/,$alines); - $alf=$alb unless defined($alf); - while ( $aStart < $alb ) { - $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ; - } - $bStart==$blb+1 or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency in deleted sequence near elements $aStart and $bStart"; - $aStart=$alf+1; - } - elsif ( m/^Binary files/ ) { - # if diff reports it is a binary file force --text mode. I do not like - # to always use this option because it is probably only available in GNU diff - open(DIFFPIPE, "diff --text $fna $fnb |") or die "Cannot launch diff process. $!" ; - } - # Default: just skip line - } - while ($aStart <= $aFinish ) { - $matchVector->[ -1 + $aStart++ ] = -1 + $bStart++ ; - } - $bStart==$bFinish+1 or die "_longestCommonSubsequence: Fatal error in interpreting diff output: Inconsistency at end"; - close DIFFPIPE; - # check whether a system error has occurred or return status is greater than or equal to 5 - if ( $! || ($? >> 8) > 5) { - print STDERR "diff process failed with exit code ", ($? >> 8), " $!\n"; - die; - } - unlink $fna,$fnb ; - } - return wantarray ? @$matchVector : $matchVector; -} - -sub traverse_sequences -{ - my $a = shift; # array ref - my $b = shift; # array ref - my $callbacks = shift || {}; - my $keyGen = shift; - my $matchCallback = $callbacks->{'MATCH'} || sub { }; - my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; - my $finishedACallback = $callbacks->{'A_FINISHED'}; - my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; - my $finishedBCallback = $callbacks->{'B_FINISHED'}; - my $matchVector = _longestCommonSubsequence( $a, $b, $keyGen, @_ ); - - # Process all the lines in @$matchVector - my $lastA = $#$a; - my $lastB = $#$b; - my $bi = 0; - my $ai; - - for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ ) - { - my $bLine = $matchVector->[$ai]; - if ( defined($bLine) ) # matched - { - &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine; - &$matchCallback( $ai, $bi++, @_ ); - } - else - { - &$discardACallback( $ai, $bi, @_ ); - } - } - - # The last entry (if any) processed was a match. - # $ai and $bi point just past the last matching lines in their sequences. - - while ( $ai <= $lastA or $bi <= $lastB ) - { - - # last A? - if ( $ai == $lastA + 1 and $bi <= $lastB ) - { - if ( defined($finishedACallback) ) - { - &$finishedACallback( $lastA, @_ ); - $finishedACallback = undef; - } - else - { - &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB; - } - } - - # last B? - if ( $bi == $lastB + 1 and $ai <= $lastA ) - { - if ( defined($finishedBCallback) ) - { - &$finishedBCallback( $lastB, @_ ); - $finishedBCallback = undef; - } - else - { - &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA; - } - } - - &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA; - &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB; - } - - return 1; -} - -sub traverse_balanced -{ - my $a = shift; # array ref - my $b = shift; # array ref - my $callbacks = shift || {}; - my $keyGen = shift; - my $matchCallback = $callbacks->{'MATCH'} || sub { }; - my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; - my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; - my $changeCallback = $callbacks->{'CHANGE'}; - my $matchVector = _longestCommonSubsequence( $a, $b, $keyGen, @_ ); - - # Process all the lines in match vector - my $lastA = $#$a; - my $lastB = $#$b; - my $bi = 0; - my $ai = 0; - my $ma = -1; - my $mb; - - while (1) - { - - # Find next match indices $ma and $mb - do { $ma++ } while ( $ma <= $#$matchVector && !defined $matchVector->[$ma] ); - - last if $ma > $#$matchVector; # end of matchVector? - $mb = $matchVector->[$ma]; - - # Proceed with discard a/b or change events until - # next match - while ( $ai < $ma || $bi < $mb ) - { - - if ( $ai < $ma && $bi < $mb ) - { - - # Change - if ( defined $changeCallback ) - { - &$changeCallback( $ai++, $bi++, @_ ); - } - else - { - &$discardACallback( $ai++, $bi, @_ ); - &$discardBCallback( $ai, $bi++, @_ ); - } - } - elsif ( $ai < $ma ) - { - &$discardACallback( $ai++, $bi, @_ ); - } - else - { - - # $bi < $mb - &$discardBCallback( $ai, $bi++, @_ ); - } - } - - # Match - &$matchCallback( $ai++, $bi++, @_ ); - } - - while ( $ai <= $lastA || $bi <= $lastB ) - { - if ( $ai <= $lastA && $bi <= $lastB ) - { - - # Change - if ( defined $changeCallback ) - { - &$changeCallback( $ai++, $bi++, @_ ); - } - else - { - &$discardACallback( $ai++, $bi, @_ ); - &$discardBCallback( $ai, $bi++, @_ ); - } - } - elsif ( $ai <= $lastA ) - { - &$discardACallback( $ai++, $bi, @_ ); - } - else - { - - # $bi <= $lastB - &$discardBCallback( $ai, $bi++, @_ ); - } - } - - return 1; -} - -sub LCS -{ - my $a = shift; # array ref - my $matchVector = _longestCommonSubsequence( $a, @_ ); - my @retval; - my $i; - for ( $i = 0 ; $i <= $#$matchVector ; $i++ ) - { - if ( defined( $matchVector->[$i] ) ) - { - push ( @retval, $a->[$i] ); - } - } - return wantarray ? @retval : \@retval; -} - -sub diff -{ - my $a = shift; # array ref - my $b = shift; # array ref - my $retval = []; - my $hunk = []; - my $discard = sub { push ( @$hunk, [ '-', $_[0], $a->[ $_[0] ] ] ) }; - my $add = sub { push ( @$hunk, [ '+', $_[1], $b->[ $_[1] ] ] ) }; - my $match = sub { push ( @$retval, $hunk ) if scalar(@$hunk); $hunk = [] }; - traverse_sequences( $a, $b, - { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ ); - &$match(); - return wantarray ? @$retval : $retval; -} - -sub sdiff -{ - my $a = shift; # array ref - my $b = shift; # array ref - my $retval = []; - my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) }; - my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) }; - my $change = sub { - push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] ); - }; - my $match = sub { - push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] ); - }; - traverse_balanced( - $a, - $b, - { - MATCH => $match, - DISCARD_A => $discard, - DISCARD_B => $add, - CHANGE => $change, - }, - @_ - ); - return wantarray ? @$retval : $retval; -} - -1; -} -import Algorithm::Diff qw(traverse_sequences); -# End of inserted block for stand-alone version - - -use Getopt::Long ; -use strict ; -use utf8 ; - -my ($algodiffversion)=split(/ /,$Algorithm::Diff::VERSION); - - -my ($versionstring)=<<EOF ; -This is LATEXDIFF 0.5 (Algorithm::Diff $Algorithm::Diff::VERSION) - (c) 2004-2007 F J Tilmann -EOF - -# Configuration variables: these have to be visible from the subroutines -my $MINWORDSBLOCK=3; # minimum number of tokens to form an independent block - # shorter identical blocks will be merged to the previous word -my $FLOATENV='(?:figure|table|plate)[\w\d*@]*' ; # Environments in which FL variants of defined commands are used -my $PICTUREENV='(?:picture|DIFnomarkup)[\w\d*@]*' ; # Environments in which all change markup is removed -my $MATHENV='(?:equation|displaymath|DOLLARDOLLAR)' ; # Environments turning on display math mode (code also knows about \[ and \] -my $MATHREPL='displaymath'; # Environment introducing deleted maths blocks -my $MATHARRENV='(?:eqnarray|align|gather|multiline|flalign)[*]?' ; # Environments turning on eqnarray math mode -my $MATHARRREPL='eqnarray*'; # Environment introducing deleted maths blocks -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 -my $COUNTERCMD='(?:footnote|part|section|subsection|subsubsection|paragraph|subparagraph)'; # textcmds which are associated with a counter - # If any of these commands occur in a deleted block - # they will be succeeded by an \addtocounter{...}{-1} - # for the associated counter such that the overall numbers - # should be the same as in the new file - -# Markup strings -# If at all possible, do not change these as parts of the program -# depend on the actual name (particularly post-processing) -# At the very least adapt subroutine postprocess to new tokens. -my $ADDMARKOPEN='\DIFaddbegin '; # Token to mark begin of appended text -my $ADDMARKCLOSE='\DIFaddend '; # Token to mark end of appended text -my $ADDOPEN='\DIFadd{'; # To mark begin of added text passage -my $ADDCLOSE='}'; # To mark end of added text passage -my $ADDCOMMENT='DIF > '; # To mark added comment line -my $DELMARKOPEN='\DIFdelbegin '; # Token to mark begin of deleted text -my $DELMARKCLOSE='\DIFdelend '; # Token to mark end of deleted text -my $DELOPEN='\DIFdel{'; # To mark begin of deleted text passage -my $DELCLOSE='}'; # To mark end of deleted text passage -my $DELCMDOPEN='%DIFDELCMD < '; # To mark begin of deleted commands (must begin with %, i.e., be a comment -my $DELCMDCLOSE="%%%\n"; # To mark end of deleted commands (must end with a new line) -my $AUXCMD='%DIFAUXCMD' ; # follows auxiliary commands put in by latexdiff to make difference file legal - # auxiliary commands must be on a line of their own -my $DELCOMMENT='DIF < '; # To mark deleted comment line - - -# main local variables: -my @TEXTCMDLIST=(); # array containing patterns of commands with text arguments -my @TEXTCMDEXCL=(); # array containing patterns of commands without text arguments (if a pattern - # matches both TEXTCMDLIST and TEXTCMDEXCL it is excluded) -my @CONTEXT1CMDLIST=(); # array containing patterns of commands with text arguments (subset of text commands), - # but which cause confusion if used out of context (e.g. \caption). - # In deleted passages, the command will be disabled but its argument is marked up - # Otherwise they behave exactly like TEXTCMD's -my @CONTEXT1CMDEXCL=(); # exclude list for above, but always empty -my @CONTEXT2CMDLIST=(); # array containing patterns of commands with text arguments, but which fail cause confusion - # if used out of context (e.g. \title). They and their arguments will be disabled in deleted - # passages -my @CONTEXT2CMDEXCL=(); # exclude list for above, but always empty - -my @SAFECMDLIST=(); # array containing patterns of safe commands (which do not break when in the argument of DIFadd or DIFDEL) -my @SAFECMDEXCL=(); - -my ($i,$j,$l); -my ($old,$new); -my ($line); -my ($newpreamble,$oldpreamble); -my (@newpreamble,@oldpreamble,@diffpreamble,@diffbody); -my ($latexdiffpreamble); -my ($oldbody, $newbody, $diffbo); -my ($oldpost, $newpost); -my ($diffall); -# Option names -my ($type,$subtype,$floattype,$config,$preamblefile,$encoding, - $showpreamble,$showsafe,$showtext,$showconfig,$showall, - $replacesafe,$appendsafe,$excludesafe, - $replacetext,$appendtext,$excludetext, - $replacecontext1,$appendcontext1, - $replacecontext2,$appendcontext2, - $help,$verbose,$version,$ignorewarnings,$allowspaces,$flatten,$debug); -my (@configlist, - @appendsafelist,@excludesafelist, - @appendtextlist,@excludetextlist, - @appendcontext1list,@appendcontext2list, - @packagelist); -my ($assign,@config); -# Hash where keys corresponds to the names of all included packages (including the documentclass as another package -# the optional arguments to the package are the values of the hash elements -my ($pkg,%packages); -# Defaults -$type='UNDERLINE'; -$subtype='SAFE'; -$floattype='FLOATSAFE'; - -$verbose=0; -$debug=1; -# define character properties -sub IsNonAsciiPunct { return <<'END' # Unicode punctuation but excluding ASCII punctuation -+utf8::IsPunct --utf8::IsASCII -END -} -sub IsNonAsciiS { return <<'END' # Unicode symbol but excluding ASCII -+utf8::IsS --utf8::IsASCII -END -} - - - - my %verbhash; - -Getopt::Long::Configure('bundling'); -GetOptions('type|t=s' => \$type, - 'subtype|s=s' => \$subtype, - 'floattype|f=s' => \$floattype, - 'config|c=s' => \@configlist, - 'preamble|p=s' => \$preamblefile, - 'encoding|e=s' => \$encoding, - 'exclude-safecmd|A=s' => \@excludesafelist, - 'replace-safecmd=s' => \$replacesafe, - 'append-safecmd|a=s' => \@appendsafelist, - 'exclude-textcmd|X=s' => \@excludetextlist, - 'replace-textcmd=s' => \$replacetext, - 'append-textcmd|x=s' => \@appendtextlist, - 'replace-context1cmd=s' => \$replacecontext1, - 'append-context1cmd=s' => \@appendcontext1list, - 'replace-context2cmd=s' => \$replacecontext2, - 'append-context2cmd=s' => \@appendcontext2list, - 'show-preamble' => \$showpreamble, - 'show-safecmd' => \$showsafe, - 'show-textcmd' => \$showtext, - 'show-config' => \$showconfig, - 'show-all' => \$showall, - 'packages=s' => \@packagelist, - 'verbose|V' => \$verbose, - 'ignore-warnings' => \$ignorewarnings, - 'allow-spaces' => \$allowspaces, - 'flatten' => \$flatten, - 'version' => \$version, - 'help|h|H' => \$help); - -if ( $help ) { - usage() ; -} - - -if ( $version ) { - die $versionstring ; -} - -print STDERR $versionstring if $verbose; - -if (defined($showall)){ - $showpreamble=$showsafe=$showtext=$showconfig=1; -} - -# setting extra preamble commands -if (defined($preamblefile)) { - $latexdiffpreamble=join "\n",(extrapream($preamblefile),""); -} else { - $latexdiffpreamble=join "\n",(extrapream($type,$subtype,$floattype),""); -} - -# setting up @SAFECMDLIST and @SAFECMDEXCL -if (defined($replacesafe)) { - init_regex_arr_ext(\@SAFECMDLIST,$replacesafe); -} else { - init_regex_arr_data(\@SAFECMDLIST, "SAFE COMMANDS"); -} -foreach $appendsafe ( @appendsafelist ) { - init_regex_arr_ext(\@SAFECMDLIST, $appendsafe); -} -foreach $excludesafe ( @excludesafelist ) { - init_regex_arr_ext(\@SAFECMDEXCL, $excludesafe); -} - -# Special: treat all cite commands as safe except in UNDERLINE and FONTSTRIKE mode -# (there is a conflict between citation and ulem package, see -# package documentation) -if ( uc($type) ne "UNDERLINE" && uc($type) ne "FONTSTRIKE" && uc($type) ne "CULINECHBAR" ) { - push (@SAFECMDLIST, qr/^cite.*$/); -} - -# setting up @TEXTCMDLIST and @TEXTCMDEXCL -if (defined($replacetext)) { - init_regex_arr_ext(\@TEXTCMDLIST,$replacetext); -} else { - init_regex_arr_data(\@TEXTCMDLIST, "TEXT COMMANDS"); -} -foreach $appendtext ( @appendtextlist ) { - init_regex_arr_ext(\@TEXTCMDLIST, $appendtext); -} -foreach $excludetext ( @excludetextlist ) { - init_regex_arr_ext(\@TEXTCMDEXCL, $excludetext); -} - - -# setting up @CONTEXT1CMDLIST ( @CONTEXT1CMDEXCL exist but is always empty ) -if (defined($replacecontext1)) { - init_regex_arr_ext(\@CONTEXT1CMDLIST,$replacecontext1); -} else { - init_regex_arr_data(\@CONTEXT1CMDLIST, "CONTEXT1 COMMANDS"); -} -foreach $appendcontext1 ( @appendcontext1list ) { - init_regex_arr_ext(\@CONTEXT1CMDLIST, $appendcontext1); -} - - -# setting up @CONTEXT2CMDLIST ( @CONTEXT2CMDEXCL exist but is always empty ) -if (defined($replacecontext2)) { - init_regex_arr_ext(\@CONTEXT2CMDLIST,$replacecontext2); -} else { - init_regex_arr_data(\@CONTEXT2CMDLIST, "CONTEXT2 COMMANDS"); -} -foreach $appendcontext2 ( @appendcontext2list ) { - init_regex_arr_ext(\@CONTEXT2CMDLIST, $appendcontext2); -} - - - - -# setting configuration variables -@config=(); -foreach $config ( @configlist ) { - if (-f $config ) { - open(FILE,$config) or die ("Couldn't open configuration file $config: $!"); - while (<FILE>) { - chomp; - next if /^\s*#/ || /^\s*%/ || /^\s*$/ ; - push (@config,$_); - } - close(FILE); - } - else { -# foreach ( split(",",$config) ) { -# push @config,$_; -# } - push @config,split(",",$config) - } -} -foreach $assign ( @config ) { - $assign=~ m/\s*(\w*)\s*=\s*(\S*)\s*$/ or die "Illegal assignment $assign in configuration list (must be variable=value)"; - if ( $1 eq "MINWORDSBLOCK" ) { $MINWORDSBLOCK = $2; } - elsif ( $1 eq "FLOATENV" ) { $FLOATENV = $2 ; } - elsif ( $1 eq "PICTUREENV" ) { $PICTUREENV = $2 ; } - elsif ( $1 eq "MATHENV" ) { $MATHENV = $2 ; } - elsif ( $1 eq "MATHREPL" ) { $MATHREPL = $2 ; } - elsif ( $1 eq "MATHARRENV" ) { $MATHARRENV = $2 ; } - elsif ( $1 eq "MATHARRREPL" ) { $MATHARRREPL = $2 ; } - elsif ( $1 eq "ARRENV" ) { $ARRENV = $2 ; } - elsif ( $1 eq "COUNTERCMD" ) { $COUNTERCMD = $2 ; } - else { die "Unknown variable $1 in assignment.";} -} - - -foreach $pkg ( @packagelist ) { - map { $packages{$_}="" } split(/,/,$pkg) ; -} - -if ($showpreamble) { - print "\nPreamble commands:\n"; - print $latexdiffpreamble ; -} - -if ($showsafe) { - print "\nCommands safe within scope of $ADDOPEN $ADDCLOSE and $DELOPEN $DELCLOSE (unless excluded):\n"; - print_regex_arr(@SAFECMDLIST); - print "\nCommands not safe within scope of $ADDOPEN $ADDCLOSE and $DELOPEN $DELCLOSE :\n"; - print_regex_arr(@SAFECMDEXCL); -} - -if ($showtext) { - print "\nCommands with last argument textual (unless excluded) and safe in every context:\n"; - print_regex_arr(@TEXTCMDLIST); - print "\nContext1 commands (last argument textual, command will be disabled in deleted passages, last argument will be shown as plain text):\n"; - print_regex_arr(@CONTEXT1CMDLIST); - print "\nContext2 commands (last argument textual, command ant its argument will be disabled in deleted passages):\n"; - print_regex_arr(@CONTEXT2CMDLIST); - print "\nExclude list of Commands with last argument not textual (overrides patterns above):\n"; - print_regex_arr(@TEXTCMDEXCL); -} - -if ($showconfig) { - print "Configuration variables:\n"; - print "MINWORDSBLOCK=$MINWORDSBLOCK\n"; - print "FLOATENV=$FLOATENV\n"; - print "PICTUREENV=$PICTUREENV\n"; - print "MATHENV=$MATHENV\n"; - print "MATHREPL=$MATHREPL\n"; - print "MATHARRENV=$MATHARRENV\n"; - print "MATHARRREPL=$MATHARRREPL\n"; - print "ARRENV=$ARRENV\n"; - print "COUNTERCMD=$COUNTERCMD\n"; -} -if ($showconfig || $showtext || $showsafe || $showpreamble) { - exit 0; } -if ( @ARGV != 2 ) { - print STDERR "2 and only 2 non-option arguments required. Write latexdiff -h to get help\n"; - exit(2); -} - -# Are extra spaces between command arguments permissible? -my $extraspace; -if ($allowspaces) { - $extraspace='\s*'; -} else { - $extraspace=''; -} - -# append context lists to text lists (as text property is implied) -push @TEXTCMDLIST, @CONTEXT1CMDLIST; -push @TEXTCMDLIST, @CONTEXT2CMDLIST; - - -# Patterns. These are used by some of the subroutines, too -# I can only define them down here because value of extraspace depends on an option - my $pat0 = '(?:[^{}]|\\\{|\\\})*'; - my $pat1 = '(?:[^{}]|\\\{|\\\}|\{'.$pat0.'\})*'; - my $pat2 = '(?:[^{}]|\\\{|\\\}|\{'.$pat1.'\})*'; # - my $pat3 = '(?:[^{}]|\\\{|\\\}|\{'.$pat2.'\})*'; - my $pat4 = '(?:[^{}]|\\\{|\\\}|\{'.$pat3.'\})*'; - my $brat0 = '(?:[^\[\]]|\\\[|\\\])*'; - - my $quotemarks = '(?:\'\')|(?:\`\`)'; - my $punct='[0.,\/\'\`:;\"\?\(\)\[\]!~\p{IsNonAsciiPunct}\p{IsNonAsciiS}]'; - my $number='-?\d*\.\d*'; - my $mathpunct='[+=<>\-\|]'; - my $and = '&'; - my $coords= '[\-.,\s\d]*'; -# word: sequence of letters or accents followed by letter - my $word='(?:[-\w\d*]|\\\\[\"\'\`~^][A-Za-z\*])+'; - my $cmdleftright='\\\\(?:left|right)\s*(?:[()\[\]|]|\\\\(?:[|{}]|\w+))'; - - my $cmdoptseq='\\\\[\w\d\*]+'.$extraspace.'(?:(?:\['.$brat0.'\]|\{'. $pat4 . '\}|\(' . $coords .'\))'.$extraspace.')*'; - my $oneletcmd='\\\\.(?:\['.$brat0.'\]|\{'. $pat4 . '\})*'; - my $math='\$(?:[^$]|\\\$)*?\$|\\\\[(].*?\\\\[)]'; -# my $math='\$(?:[^$]|\\\$)*\$'; - my $comment='%.*?\n'; - my $pat=qr/(?:\A\s*)?(?:${and}|${quotemarks}|${number}|${word}|$cmdleftright|${cmdoptseq}|${math}|${oneletcmd}|${comment}|${punct}|${mathpunct}|\{|\})\s*/ ; - - - -# now we are done setting up and can start working -my ($oldfile, $newfile) = @ARGV; - -$encoding=guess_encoding($newfile) unless defined($encoding); - -$encoding = "utf8" if $encoding =~ m/^utf8/i ; -if (lc($encoding) eq "utf8" ) { - binmode(STDOUT, ":utf8"); - binmode(STDERR, ":utf8"); -} - -$old=read_file_with_encoding($oldfile,$encoding); -$new=read_file_with_encoding($newfile,$encoding); - - - - -# reset time -exetime(1); -($oldpreamble,$oldbody,$oldpost)=splitdoc($old,'\\\\begin\{document\}','\\\\end\{document\}'); - - -($newpreamble,$newbody,$newpost)=splitdoc($new,'\\\\begin\{document\}','\\\\end\{document\}'); - - -if ($flatten) { - $oldbody=flatten($oldbody,$oldpreamble,$oldfile,$encoding); - $newbody=flatten($newbody,$newpreamble,$newfile,$encoding); -} - -if ( length $oldpreamble && length $newpreamble ) { - @oldpreamble = split /\n/, $oldpreamble; - @newpreamble = split /\n/, $newpreamble; - - %packages=list_packages(@newpreamble) unless %packages; - if (defined $packages{"hyperref"} ) { - print STDERR "hyperref package detected.\n" if $verbose ; - $latexdiffpreamble =~ s/\{\\DIFadd\}/{\\DIFaddtex}/g; - $latexdiffpreamble =~ s/\{\\DIFdel\}/{\\DIFdeltex}/g; - $latexdiffpreamble .= join "\n",(extrapream("HYPERREF"),""); - } - # 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 - unshift @newpreamble,''; - unshift @oldpreamble,''; - print STDERR "Differencing preamble.\n" if $verbose; - @diffpreamble = linediff(\@oldpreamble, \@newpreamble); - # remove dummy line again - shift @diffpreamble; - push @diffpreamble,$latexdiffpreamble; - push @diffpreamble,'\begin{document}'; -} -elsif ( !length $oldpreamble && !length $newpreamble ) { - @diffpreamble=(); -} else { - print STDERR "Either both texts must have preamble or neither text must have the preamble.\n"; - exit(2); -} - -if (defined $packages{"amsmath"} ) { - print STDERR "amsmath package detected.\n" if $verbose ; - $MATHARRREPL='align*'; -} - -print STDERR "Preprocessing body. " if $verbose; -my ($oldleadin,$newleadin)=preprocess($oldbody,$newbody); - - -# run difference algorithm -@diffbody=bodydiff($oldbody, $newbody); -$diffbo=join("",@diffbody); -print STDERR "(",exetime()," s)\n","Postprocessing body. \n " if $verbose; -postprocess($diffbo); -$diffall =join("\n",@diffpreamble) ; -$diffall .= "$newleadin$diffbo" ; -$diffall .= "\\end{document}$newpost" if length $newpreamble ; -if ( lc($encoding) ne "utf8" && lc($encoding) ne "ascii" ) { - print STDERR "Encoding output file to $encoding\n" if $verbose; - $diffall=Encode::encode($encoding,$diffall); - binmode STDOUT; -} -print $diffall; - - -print STDERR "(",exetime()," s)\n","Done.\n" if $verbose; - - - -## guess_encoding(filename) -## reads the first 20 lines of filename and looks for call of inputenc package -## if found, return the option of this package (encoding), otherwise return ascii -sub guess_encoding { - my ($filename)=@_; - my ($i,$enc); - open (FH, $filename) or die("Couldn't open $filename: $!"); - $i=0; - while (<FH>) { - next if /^\s*%/; # skip comment lines - if (m/\\usepackage\[(\w*?)\]\{inputenc\}/) { - close(FH); - return($1); - } - last if (++$i > 20 ); # scan at most 20 non-comment lines - } - close(FH); - return("ascii"); -} - - -sub read_file_with_encoding { - my ($output); - my ($filename, $encoding) = @_; - - if (lc($encoding) eq "utf8" ) { - open (FILE, "<:utf8",$filename) or die("Couldn't open $filename: $!"); - local $/ ; # locally set record operator to undefined, ie. enable whole-file mode - $output=<FILE>; - } elsif ( lc($encoding) eq "ascii") { - open (FILE, $filename) or die("Couldn't open $filename: $!"); - local $/ ; # locally set record operator to undefined, ie. enable whole-file mode - $output=<FILE>; - } else { - require Encode; - open (FILE, "<",$filename) or die("Couldn't open $filename: $!"); - local $/ ; # locally set record operator to undefined, ie. enable whole-file mode - $output=<FILE>; - print STDERR "Converting $filename from $encoding to utf8\n" if $verbose; - $output=Encode::decode($encoding,$output); - } - close FILE; - return $output; -} - -# %packages=list_packages(@preamble) -# scans the arguments for \documentclass and \usepackage statements and constructs a hash -# whose keys are the included packages, and whose values are the associated optional arguments -sub list_packages { - my (@preamble)=@_; - my %packages=(); - foreach $line ( @preamble ) { - # get rid of comments - $line=~s/(?<!\\)%.*$// ; - if ( $line =~ m/\\(?:documentclass|usepackage)(?:\[(.+?)\])?\{(.*?)\}/ ) { -# print STDERR "Found something: |$line|\n"; - if (defined($1)) { - $packages{$2}=$1; - } else { - $packages{$2}=""; - } - } - } - return (%packages); -} - -# flatten($text,$preamble,$filename,$encoding) -# expands \input and \include commands within text -# premable is scanned for includeonly commands -# encoding is the encoding -sub flatten { - my ($text,$preamble,$filename,$encoding)=@_; - my ($includeonly,$dirname,$fname,$newpage,$replacement); - require File::Basename ; - require File::Spec ; - $dirname = File::Basename::dirname($filename); - - if ( ($includeonly) = ($preamble =~ m/\\includeonly{(.*?)}/ ) ) { - $includeonly =~ s/,/|/g; - } else { - $includeonly = '.*?'; - } - - print STDERR "DEBUG: includeonly $includeonly\n"; - - $text=~s/\\input{(.*?)}|\\include{(${includeonly}(?:\.tex)?)}/{ - $fname = $1 if defined($1) ; - $fname = $2 if defined($2) ; - # # add tex extension unless there is a three letter extension already - $fname .= ".tex" unless $fname =~ m|\.\w{3}|; - print STDERR "Include file $fname\n" if $verbose; - print STDERR "DEBUG looking for file ",File::Spec->catfile($dirname,$fname), "\n"; - # content of file becomes replacement value, add \newpage if the command was include - $replacement=read_file_with_encoding(File::Spec->catfile($dirname,$fname), $encoding) or die - "Couldn't find file ",File::Spec->catfile($dirname,$fname),": $!"; - $newpage=(defined($2)? " \\newpage " : "") ; - "$newpage$replacement$newpage"; - }/exg; - - return($text); -} - - -# print_regex_arr(@arr) -# prints regex array without x-ism expansion put in by pearl to stdout -sub print_regex_arr { - my $dumstring; - $dumstring = join(" ",@_); # PERL generates string (?-xism:^ref$) for quoted refex ^ref$ - $dumstring =~ s/\(\?-xism:\^(.*?)\$\)/$1/g; # remove string and ^,$ marks before output - print $dumstring,"\n"; -} - - -# @lines=extrapream($type) -# reads line from appendix (end of file after __END__ token) -sub extrapream { - my $type; - my @retval=("%DIF PREAMBLE EXTENSION ADDED BY LATEXDIFF") ; - my ($copy); - - while (@_) { - $copy=0; - $type=shift ; - if ( -f $type ) { - open (FILE,$type) or die "Cannot open preamble file $type: $!"; - print STDERR "Reading preamble file $type\n" if $verbose ; - while (<FILE>) { - chomp ; - if ( $_ =~ m/%DIF PREAMBLE/ ) { - push (@retval,"$_"); - } else { - push (@retval,"$_ %DIF PREAMBLE"); - } - } - } - else { # not (-f $type) - $type=uc($type); # upcase argument - print STDERR "Preamble Internal Type $type\n" if $verbose; - while (<DATA>) { - if ( m/^%DIF $type/ ) { - $copy=1; } - elsif ( m/^%DIF END $type/ ) { - last; } - chomp; - push (@retval,"$_ %DIF PREAMBLE") if $copy; - } - if ( $copy == 0 ) { - print STDERR "\nPreamble style $type not implemented.\n"; - print STDERR "Write latexdiff -h to get help with available styles\n"; - exit(2); - } - seek DATA,0,0; # rewind DATA handle to file begin - } - } - push (@retval,"%DIF END PREAMBLE EXTENSION ADDED BY LATEXDIFF") ; - return @retval; -} - - -# ($part1,$part2,$part3)=splitdoc($text,$word1,$word2) -# splits $text into 3 parts at $word1 and $word2. -# if neither $word1 nor $word2 exist, $part1 and $part3 are empty, $part2 is $text -# If only $word1 or $word2 exist but not the other, output an error message. - -# NB this version avoids $` and $' for performance reason although it only makes a tiny difference -# (in one test gain a tenth of a second for a 30s run) -sub splitdoc { - my ($text,$word1,$word2)=@_; - my ($part1,$part2,$part3)=("","",""); - my ($rest,$pos); - - if ( $text =~ m/(^[^%]*)($word1)/mg ) { - $pos=pos $text; - $part1=substr($text,0,$pos-length($2)); - $rest=substr($text,$pos); - if ( $rest =~ m/(^[^%]*)($word2)/mg ) { - $pos=pos $rest; - $part2=substr($rest,0,$pos-length($2)); - $part3=substr($rest,$pos); - } - else { - die "$word1 and $word2 not in the correct order or not present as a pair." ; - } - } else { - $part2=$text; - die "$word2 present but not $word1." if ( $text =~ m/(^[^%]*)$word2/ms ); - } - return ($part1,$part2,$part3); -} - - - - - -# bodydiff($old,$new) -sub bodydiff { - my ($oldwords, $newwords) = @_; - my @retwords; - - print STDERR "(",exetime()," s)\n","Splitting into latex tokens \n" if $verbose; - print STDERR "Parsing $oldfile \n" if $verbose; - my @oldwords = splitlatex($oldwords); - print STDERR "Parsing $newfile \n" if $verbose; - my @newwords = splitlatex($newwords); - my $token; - - print STDERR "(",exetime()," s)\n","Pass 1: Expanding text commands and merging isolated identities with changed blocks " if $verbose; - pass1(\@oldwords, \@newwords); - - - print STDERR "(",exetime()," s)\n","Pass 2: inserting DIF tokens and mark up. " if $verbose; - - @retwords=pass2(\@oldwords, \@newwords); - - return(@retwords); -} - - - - -# @words=splitlatex($string) -# split string according to latex rules -# Each element of words is either -# a word (including trailing spaces and punctuation) -# a latex command -sub splitlatex { - my ($string) = @_ ; - my @retval=($string =~ m/$pat/osg); - - if (length($string) != length(join("",@retval))) { - 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; - print STDERR "DEBUG Original length ",length($string)," Parsed length ",length(join("",@retval)),"\n" if $debug; - print STDERR "DEBUG Input string: |$string|\n" if (length($string)<500) && $debug; - print STDERR "DEBUG Token parsing: |",join("+",@retval),"|\n" if (length($string)<500) && $debug ; - @retval=(); - # slow way only do this if other m//sg method fails - my $last = 0; - while ( $string =~ m/$pat/osg ) { - my $match=$&; - if ($last + length $& != pos $string ) { - my $pos=pos($string); - my $offset=30<$last ? 30 : $last; - my $dum=substr($string,$last-$offset,$pos-$last+2*$offset); - my $dum1=$dum; - my $cnt=$#retval; - my $i; - $dum1 =~ s/\n/ /g; - unless ($ignorewarnings) { - print STDERR "\n$dum1\n"; - print STDERR " " x 30,"^" x ($pos-$last)," " x 30,"\n"; - print STDERR "Missing characters near word " . (scalar @retval) . " character index: " . $last . "-" . pos($string) . " Length: " . length($match) . " Match: |$match| (expected match marked above).\n"; - } - # put in missing characters `by hand' - push (@retval, substr($dum,$offset,$pos-$last-length($match))); -# Note: there seems to be a bug in substr with utf8 that made the following line output substr which were too long, -# using dum instead appears to work -# push (@retval, substr($string,$last, pos($string)-$last-length($match))); - } - push (@retval, $match); - $last=pos $string; - } - - } - return @retval; -} - - -# pass1( \@seq1,\@seq2) -# Look for differences between seq1 and seq2. -# Where an common-subsequence block is flanked by deleted or appended blocks, -# and is shorter than $MINWORDSBLOCK words it is appended -# to the last deleted or appended word. If the block contains tokens other than words -# or punctuation it is not merged. -# Deleted or appended block consisting of words and safe commands only are -# also merged, to prevent break-up in pass2 (after previous isolated words have been removed) -# If there are commands with textual arguments (e.g. \caption) both in corresponding -# appended and deleted blocks split them such that the command and opening bracket -# are one token, then the rest is split up following standard rules, and the closing -# bracket is a separate token, ie. turn -# "\caption{This is a textual argument}" into -# ("\caption{","This ","is ","a ","textual ","argument","}") -# No return value. Destructively changes sequences -sub pass1 { - my $seq1 = shift ; - my $seq2 = shift ; - - my $len1 = scalar @$seq1; - my $len2 = scalar @$seq2; - my $wpat=qr/^(?:[a-zA-Z.,'`:;?()!]*)[\s~]*$/; #' - - my ($last1,$last2)=(-1,-1) ; - my $cnt=0; - my $block=[]; - my $addblock=[]; - my $delblock=[]; - my $todo=[]; - my $instruction=[]; - my $i; - my (@delmid,@addmid,@dummy); - - my ($addcmds,$delcmds,$matchindex); - my ($addtextblocks,$deltextblocks); - my ($addtokcnt,$deltokcnt,$mattokcnt)=(0,0,0); - my ($addblkcnt,$delblkcnt,$matblkcnt)=(0,0,0); - - my $adddiscard = sub { - if ($cnt > 0 ) { - $matblkcnt++; - # just after an unchanged block -# print STDERR "Unchanged block $cnt, $last1,$last2 \n"; - if ($cnt < $MINWORDSBLOCK - && $cnt==scalar ( - grep { /^$wpat/ || ( /^\\([\w\d\*]+)((?:\[$brat0\]|\{$pat4\})*)/o - && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL) - && scalar(@dummy=split(" ",$2))<3 ) } - @$block) ) { - # merge identical blocks shorter than $MINWORDSBLOCK - # and only containing ordinary words - # with preceding different word - # We cannot carry out this merging immediately as this - # would change the index numbers of seq1 and seq2 and confuse - # the algorithm, instead we store in @$todo where we have to merge - push(@$todo, [ $last1,$last2,$cnt,@$block ]); - } - $block = []; - $cnt=0; $last1=-1; $last2=-1; - } - }; - my $discard=sub { $deltokcnt++; - &$adddiscard; #($_[0],$_[1]); - push(@$delblock,[ $seq1->[$_[0]],$_[0] ]); - $last1=$_[0] }; - - my $add = sub { $addtokcnt++; - &$adddiscard; #($_[0],$_[1]); - push(@$addblock,[ $seq2->[$_[1]],$_[1] ]); - $last2=$_[1] }; - - my $match = sub { $mattokcnt++; - if ($cnt==0) { # first word of matching sequence after changed sequence or at beginning of word sequence - $deltextblocks = extracttextblocks($delblock); - $delblkcnt++ if scalar @$delblock; - $addtextblocks = extracttextblocks($addblock); - $addblkcnt++ if scalar @$addblock; - - $delcmds = extractcommands($delblock); - $addcmds = extractcommands($addblock); - # keygen(third argument of _longestCommonSubsequence) implies to sort on command (0th elements of $addcmd elements) - # the calling format for longestCommonSubsequence has changed between versions of - # Algorithm::Diff so we need to check which one we are using - if ( $algodiffversion > 1.15 ) { - ### Algorithm::Diff 1.19 - $matchindex=Algorithm::Diff::_longestCommonSubsequence($delcmds,$addcmds, 0, sub { $_[0]->[0] } ); - } else { - ### Algorithm::Diff 1.15 - $matchindex=Algorithm::Diff::_longestCommonSubsequence($delcmds,$addcmds, sub { $_[0]->[0] } ); - } - - for ($i=0 ; $i<=$#$matchindex ; $i++) { - if (defined($matchindex->[$i])){ - $j=$matchindex->[$i]; - @delmid=splitlatex($delcmds->[$i][3]); - @addmid=splitlatex($addcmds->[$j][3]); - while (scalar(@$deltextblocks) && $deltextblocks->[0][0]<$delcmds->[$i][1]) { - my ($index,$block,$cnt)=@{ shift(@$deltextblocks) }; - push(@$todo, [$index,-1,$cnt,@$block]); - } - push(@$todo, [ $delcmds->[$i][1],-1,-1,$delcmds->[$i][2],@delmid,$delcmds->[$i][4]]); - - while (scalar(@$addtextblocks) && $addtextblocks->[0][0]<$addcmds->[$j][1]) { - my ($index,$block,$cnt)=@{ shift(@$addtextblocks) }; - push(@$todo, [-1,$index,$cnt,@$block]); - } - push(@$todo, [ -1,$addcmds->[$j][1],-1,$addcmds->[$j][2],@addmid,$addcmds->[$j][4]]); - } - } - # mop up remaining textblocks - while (scalar(@$deltextblocks)) { - my ($index,$block,$cnt)=@{ shift(@$deltextblocks) } ; - push(@$todo, [$index,-1,$cnt,@$block]); - } - while (scalar(@$addtextblocks)) { - my ($index,$block,$cnt)=@{ shift(@$addtextblocks) }; - push(@$todo, [-1,$index,$cnt,@$block]); - } - - $addblock=[]; - $delblock=[]; - } - push(@$block,$seq2->[$_[1]]); - $cnt++ }; - - my $keyfunc = sub { join(" ",split(" ",shift())) }; - - traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc ); - - - # now carry out the merging/splitting. Refer to elements relative from - # the end (with negative indices) as these offsets don't change before the instruction is executed - # cnt>0: merged small unchanged groups with previous changed blocks - # cnt==-1: split textual commands into components - foreach $instruction ( @$todo) { - ($last1,$last2,$cnt,@$block)=@$instruction ; - if ($cnt>=0) { - splice(@$seq1,$last1-$len1,1+$cnt,join("",$seq1->[$last1-$len1],@$block)) if $last1>=0; - splice(@$seq2,$last2-$len2,1+$cnt,join("",$seq2->[$last2-$len2],@$block)) if $last2>=0; - } else { - splice(@$seq1,$last1-$len1,1,@$block) if $last1>=0; - splice(@$seq2,$last2-$len2,1,@$block) if $last2>=0; - } - } - - if ($verbose) { - print STDERR "\n"; - print STDERR " $mattokcnt matching tokens in $matblkcnt blocks.\n"; - print STDERR " $deltokcnt discarded tokens in $delblkcnt blocks.\n"; - print STDERR " $addtokcnt appended tokens in $addblkcnt blocks.\n"; - } -} - - -# extracttextblocks(\@blockindex) -# $blockindex has the following format -# [ [ token1, index1 ], [token2, index2],.. ] -# where index refers to the index in the original old or new word sequence -# Returns: reference to an array of the form -# [[ $index, $textblock, $cnt ], .. -# where $index index of block to be merged -# $textblock contains all the words to be merged with the word at $index (but does not contain this word) -# $cnt is length of block -# -# requires: iscmd -# -sub extracttextblocks { - my $block=shift; - my ($i,$token,$index); - my $textblock=[]; - my $last=-1; - my $wpat=qr/^(?:[a-zA-Z.,'`:;?()!]*)[\s~]*$/; #' - my $retval=[]; - - for ($i=0;$i< scalar @$block;$i++) { - ($token,$index)=@{ $block->[$i] }; - # store pure text blocks - if ($token =~ /$wpat/ || ( $token =~/^\\([\w\d\*]+)((?:${extraspace}\[$brat0\]${extraspace}|${extraspace}\{$pat4\})*)/o - && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL) - && !iscmd($1,\@TEXTCMDLIST,\@TEXTCMDEXCL))) { - # we have text or a command which can be treated as text - if ($last<0) { - # new pure-text block - $last=$index; - } else { - # add to pure-text block - push(@$textblock, $token); - } - } else { - # it is not text - if (scalar(@$textblock)) { - push(@$retval,[ $last, $textblock, scalar(@$textblock) ]); - } - $textblock=[]; - $last=-1; - } - } - # finish processing a possibly unfinished block before returning - if (scalar(@$textblock)) { - push(@$retval,[ $last, $textblock, scalar(@$textblock) ]); - } - return($retval) -} - - - -# extractcommands( \@blockindex ) -# $blockindex has the following format -# [ [ token1, index1 ], [token2, index2],.. ] -# where index refers to the index in the original old or new word sequence -# Returns: reference to an array of the form -# [ [ "\cmd1", index, "\cmd1[optarg]{arg1}{", "arg2" ,"} " ],.. -# where index is just taken from input array -# command must have a textual argument as last argument -# -# requires: iscmd -# -sub extractcommands { - my $block=shift; - my ($i,$token,$index,$cmd,$open,$mid,$closing); - my $retval=[]; - - for ($i=0;$i< scalar @$block;$i++) { - ($token,$index)=@{ $block->[$i] }; - # check if token is an alphanumeric command sequence with at least one non-optional argument - # \cmd[...]{...}{last argument} - # Capturing in the following results in these associations - # $1: \cmd[...]{...}{ - # $2: \cmd - # $3: last argument - # $4: } + trailing spaces - if ( ( $token =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat4\})*${extraspace}\{)($pat4)(\}\s*)$/so ) - && iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL) ) { - # push(@$retval,[ $2,$index,$1,$3,$4 ]); - ($cmd,$open,$mid,$closing) = ($2,$1,$3,$4) ; - $closing =~ s/\}/\\RIGHTBRACE/ ; - push(@$retval,[ $cmd,$index,$open,$mid,$closing ]); - } - } - return $retval; -} - -# iscmd($cmd,\@regexarray,\@regexexcl) checks -# return 1 if $cmd matches any of the patterns in the -# array $@regexarray, and none of the patterns in \@regexexcl, otherwise return 0 -sub iscmd { - my ($cmd,$regexar,$regexexcl)=@_; - my ($ret)=0; - foreach $pat ( @$regexar ) { - if ( $cmd =~ m/^${pat}$/ ) { - $ret=1 ; - last; - } - } - return 0 unless $ret; - foreach $pat ( @$regexexcl ) { - return 0 if ( $cmd =~ m/^${pat}$/ ); - } - return 1; -} - - -# pass2( \@seq1,\@seq2) -# Look for differences between seq1 and seq2. -# Mark begin and end of deleted and appended sequences with tags $DELOPEN and $DELCLOSE -# and $ADDOPEN and $ADDCLOSE, respectively, however exclude { } & and all comands, unless -# they match an element of the whitelist (SAFECMD) -# For words in TEXTCMD but not in SAFECMD, enclose interior with $ADDOPEN and $ADDCLOSE brackets -# Deleted comment lines are marked with %DIF < -# Added comment lines are marked with %DIF > -sub pass2 { - my $seq1 = shift ; - my $seq2 = shift ; - - my ($addtokcnt,$deltokcnt,$mattokcnt)=(0,0,0); - my ($addblkcnt,$delblkcnt,$matblkcnt)=(0,0,0); - - my $retval = []; - my $delhunk = []; - my $addhunk = []; - - my $discard = sub { $deltokcnt++; - push ( @$delhunk, $seq1->[$_[0]]) }; - - my $add = sub { $addtokcnt++; - push ( @$addhunk, $seq2->[$_[1]]) }; - - my $match = sub { $mattokcnt++; - if ( scalar @$delhunk ) { - $delblkcnt++; - # mark up changes, but comment out commands - push @$retval,marktags($DELMARKOPEN,$DELMARKCLOSE,$DELOPEN,$DELCLOSE,$DELCMDOPEN,$DELCMDCLOSE,$DELCOMMENT,$delhunk); - $delhunk = []; - } - if ( scalar @$addhunk ) { - $addblkcnt++; - # we mark up changes, but simply quote commands - push @$retval,marktags($ADDMARKOPEN,$ADDMARKCLOSE,$ADDOPEN,$ADDCLOSE,"","",$ADDCOMMENT,$addhunk); - $addhunk = []; - } - push(@$retval,$seq2->[$_[1]]) }; - - my $keyfunc = sub { join(" ",split(" ",shift())) }; - - traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc ); - # clear up unprocessed hunks - push @$retval,marktags($DELMARKOPEN,$DELMARKCLOSE,$DELOPEN,$DELCLOSE,$DELCMDOPEN,$DELCMDCLOSE,$DELCOMMENT,$delhunk) if scalar @$delhunk; - push @$retval,marktags($ADDMARKOPEN,$ADDMARKCLOSE,$ADDOPEN,$ADDCLOSE,"","",$ADDCOMMENT,$addhunk) if scalar @$addhunk; - - - if ($verbose) { - print STDERR "\n"; - print STDERR " $mattokcnt matching tokens. \n"; - print STDERR " $deltokcnt discarded tokens in $delblkcnt blocks.\n"; - print STDERR " $addtokcnt appended tokens in $addblkcnt blocks.\n"; - } - - return(@$retval); -} - -# marktags($openmark,$closemark,$open,$close,$opencmd,$closecmd,$comment,\@block) -# returns ($openmark,$open,$block,$close,$closemark) if @block only contains no commands (except white-listed ones), -# braces, ampersands, or comments -# mark comments with $comment -# exclude all other exceptions from scope of open, close like this -# ($openmark, $open,...,$close, $opencomd,command, command,$closecmd, $open, ..., $close, $closemark) -# If $opencmd begins with "%" marktags assumes it is operating on a deleted block, otherwise on an added block -sub marktags { - my ($openmark,$closemark,$open,$close,$opencmd,$closecmd,$comment,$block)=@_; - my $word; - my (@argtext); - my $retval=[]; - my $noncomment=0; - my $cmd=-1; # -1 at beginning 0: last token written is a ordinary word - # 1: last token written is a command - # for keeping track whether we are just in a command sequence or in a word sequence - my $cmdcomment= ($opencmd =~ m/^%/); # Flag to indicate whether opencmd is a comment (i.e. if we intend to simply comment out changed commands) - my ($command,$commandword,$closingbracket) ; # temporary variables needed below to remember sub-pattern matches - -# split this block to flatten out sequences joined in pass1 - @$block=splitlatex(join "",@$block); - foreach (@$block) { - $word=$_; - if ( $word =~ s/^%/%$comment/ ) { - # a comment - if ($cmd==1) { - push (@$retval,$closecmd) ; - $cmd=-1; - } - push (@$retval,$word); - next; - } - if (! $noncomment) { - push (@$retval,$openmark); - $noncomment=1; - } - # negative lookahead pattern (?!) in second clause is put in to avoid mathcing \( .. \) patterns - if ( $word =~ /^[&{}\[\]]/ || ( $word =~ /^\\(?!\()([\w*@]*)/ && !iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL)) ) { - # word is a command or other significant token (not in SAFECMDLIST) - ## same conditions as in subroutine extractcommand: - # check if token is an alphanumeric command sequence with at least one non-optional argument - # \cmd[...]{...}{last argument} - # Capturing in the following results in these associations - # $1: \cmd[...]{...}{ - # $2: \cmd - # $3: last argument - # $4: } + trailing spaces - ### pre-0.3 if ( ( $token =~ m/^(\\([\w\d\*]+)(?:\[$brat0\]|\{$pat4\})*\{)($pat4)(\}\s*)$/so ) - if ( ( $word =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat4\})*${extraspace}\{)($pat4)(\}\s*)$/so ) - && iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL) && ( !$cmdcomment || !iscmd($2,\@CONTEXT2CMDLIST, \@CONTEXT2CMDEXCL) ) ) { - # word is a text command - we mark up the interior of the word. But if we are in a deleted block ($cmdcomment=1) and - # $2 (the command) is in context2, just treat it as an ordinary command (i.e. comment it open with $opencmd) - # Because we do not want to disable this command - # here we do not use $opencmd and $closecmd($opencmd is empty) - if ($cmd==1) { - push (@$retval,$closecmd) ; - } elsif ($cmd==0) { - push (@$retval,$close) ; - } - $command=$1; $commandword=$2; $closingbracket=$4; - @argtext=splitlatex($3); # split textual argument into tokens - # and mark it up (but we do not need openmark and closemark) - # insert command with initial arguments, marked-up final argument, and closing bracket - if ( $cmdcomment && iscmd($commandword,\@CONTEXT1CMDLIST, \@CONTEXT1CMDEXCL) ) { - # context1cmd in a deleted environment; delete command itself but keep last argument, marked up - push (@$retval,$opencmd); - $command =~ s/\n/\n${opencmd}/sg ; # repeat opencmd at the beginning of each line - # argument, note that the additional comment character is included - # to suppress linebreak after opening parentheses, which is important - # for latexrevise - push (@$retval,$command,"%\n{$AUXCMD\n",marktags("","",$open,$close,$opencmd,$closecmd,$comment,\@argtext),$closingbracket); - } else { - # normal textcmd or context1cmd in an added block - push (@$retval,$command,marktags("","",$open,$close,$opencmd,$closecmd,$comment,\@argtext),$closingbracket); - } - push (@$retval,$AUXCMD,"\n") if $cmdcomment ; - $cmd=-1 ; - } else { - # ordinary command - push (@$retval,$opencmd) if $cmd==-1 ; - push (@$retval,$close,$opencmd) if $cmd==0 ; - $word =~ s/\n/\n${opencmd}/sg if $cmdcomment ; # if opencmd is a comment, repeat this at the beginning of every line - push (@$retval,$word); - $cmd=1; - } - } else { - # just an ordinary word or word in SAFECMD - push (@$retval,$open) if $cmd==-1 ; - push (@$retval,$closecmd,$open) if $cmd==1 ; - push (@$retval,$word); - $cmd=0; - } - } - push (@$retval,$close) if $cmd==0; - push (@$retval,$closecmd) if $cmd==1; - - push (@$retval,$closemark) if ($noncomment); - return @$retval; -} - -# preprocess($string, ..) -# carry out the following pre-processing steps for all arguments: -# 1. Remove leading white-space -# 2. mark all first empty line (in block of several) with \PAR tokens -# 3. Convert all '\%' into '\PERCENTAGE ' to make parsing regular expressions easier -# 4. Convert all \verb|some verbatim text| commands (where | can be an arbitrary character) -# into \verb{hash} -# 5. Convert \begin{verbatim} some verbatim text \end{verbatim} into \verbatim{hash} -# 6. Convert _n into \SUBSCRIPTNB{n} and _{nnn} into \SUBSCRIPT{nn} -# 7. Convert ^n into \SUPERSCRIPTNB{n} and ^{nnn} into \SUPERSCRIPT{nn} -# 8. a. Convert $$ $$ into \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} -# b. Convert \[ \] into \begin{SQUAREBRACKET} \end{SQUAREBRACKET} - -# 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. -# -# NB: step 6 and 7 is likely to convert some "_" inappropriately, e.g. in file -# names or labels but it does not matter because they are converted back in the postprocessing step -# Returns: leading white space removed in step 1 -sub preprocess { - my @leadin=() ; - for (@_) { - s/^(\s*)//s; - push(@leadin,$1); - s/\n(\s*?)\n((?:\s*\n)*)/\n$1\\PAR\n$2/g ; - s/(?<!\\)\\%/\\PERCENTAGE /g ; # (?<! is negative lookbehind assertion to prevent \\% from being converted - s/(\\verb\*?)(\S)(.*?)\2/"${1}{". tohash(\%verbhash,"${2}${3}${2}") ."}"/esg; - s/\\begin\{(verbatim\*?)\}(.*?)\\end\{\1\}/"\\${1}{". tohash(\%verbhash,"${2}") . "}"/esg; - # Convert _n or _\cmd into \SUBSCRIPTNB{n} or \SUBSCRIPTNB{\cmd} and _{nnn} into \SUBSCRIPT{nn} - s/_([^{\\]|\\\w+)/\\SUBSCRIPTNB{$1}/g ; - s/_{($pat4)}/\\SUBSCRIPT{$1}/g ; - # Convert ^n into \SUPERSCRIPTNB{n} and ^{nnn} into \SUPERSCRIPT{nn} - s/\^([^{\\]|\\\w+)/\\SUPERSCRIPTNB{$1}/g ; - s/\^{($pat4)}/\\SUPERSCRIPT{$1}/g ; - # Convert $$ $$ into \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} - s/\$\$(.*?)\$\$/\\begin{DOLLARDOLLAR}$1\\end{DOLLARDOLLAR}/sg; - # Convert \[ \] into \begin{SQUAREBRACKET} \end{SQUAREBRACKET} - s/(?<!\\)\\\[/\\begin{SQUAREBRACKET}/sg; - s/\\\]/\\end{SQUAREBRACKET}/sg; - # add final token " STOP" - $_ .= " STOP" - } - return(@leadin); -} - -#hashstring=tohash(\%hash,$string) -# creates a hash value based on string and stores in %hash -sub tohash { - my ($hash,$string)=@_; - my (@arr,$val); - my ($sum,$i)=(0,1); - my ($hstr); - - @arr=unpack('c*',$string); - - foreach $val (@arr) { - $sum += $i*$val; - $i++; - } - $hstr= "$sum"; - if (defined($hash->{$hstr}) && $string ne $hash->{$hstr}) { - warn "Repeated hash value for verbatim mode in spite of different content."; - $hstr="-$hstr"; - } - $hash->{$hstr}=$string; - return($hstr); -} - -#string=fromhash(\%hash,$fromstring) -# restores string value stored in hash -#string=fromhash(\%hash,$fromstring,$prependstring) -# additionally begins each line with prependstring -sub fromhash { - my ($hash,$hstr)=($_[0],$_[1]); - my $retstr=$hash->{$hstr}; - if ( $#_ >= 2) { - $retstr =~ s/^/$_[2]/mg; - } - return $retstr; -} - - -# postprocess($string, ..) -# carry out the following post-processing steps for all arguments: -# * Remove STOP token from the end -# * Replace \RIGHTBRACE by } -# 1. Check all deleted blocks: -# a.where a deleted block contains a matching \begin and -# \end environment (these will be disabled by a %DIFDELCMD statements), for selected environment enable enable -# these commands again (such that for example displayed math in a deleted equation -# is properly within math mode. For math mode environments replace numbered equation -# environments with their display only variety (so that equation numbers in new file and -# diff file are identical). Where the correct type of math environment cannot be determined -# use a place holder MATHMODE -# b.where one of the commands matching $COUNTERCMD is used as a DIFAUXCMD, add a statement -# subtracting one from the respective counter to keep numbering consistent with new file -# Replace all MATHMODE environment commands by the correct environment to achieve matching -# pairs -# c. If in-line math mode contains array environment, enclose the whole environment in \mbox'es -# 2. Remove DIFadd, DIFdel, DIFFaddbegin , ... from picture environments -# 3. Convert DIFadd, DIFdel, DIFFaddbegin , ... into FL varieties -# within floats (currently recognised float environments: plate,table,figure -# plus starred varieties). -# 4. Remove empty %DIFDELCMD < lines -# 4. Convert \begin{SQUAREBRACKET} \end{SQUAREBRACKET} into \[ \] -# Convert \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} into $$ $$ -# 5. Convert \SUPERSCRIPTNB{n} into ^n and \SUPERSCRIPT{nn} into ^{nnn} -# 6. Convert \SUBSCRIPTNB{n} into _n and \SUBCRIPT{nn} into _{nnn} -# 7. Expand hashes of verb and verbatim environments -# 8. Convert '\PERCENTAGE ' back into '\%' -# 9.. remove all \PAR tokens -# 10. package specific processing: endfloat: make sure \begin{figure} and \end{figure} are always -# on a line by themselves, similarly for table environment -# 4, undo renaming of the \begin and \end in comments - -# -# Note have to manually synchronize substitution commands below and -# DIF.. command names in the header -sub postprocess { - my ($begin,$len,$cnt,$float,$delblock,$addblock); - # second level blocks - my ($begin2,$cnt2,$len2,$eqarrayblock,$mathblock); - - for (@_) { - - # change $'s in comments to something harmless - 1 while s/(%.*)\$/$1DOLLARDIF/mg ; - - # Remove final STOP token - s/ STOP$//; - # Replace \RIGHTBRACE by } - s/\\RIGHTBRACE/}/g; - # Check all deleted blocks: where a deleted block contains a matching \begin and - # \end environment (these will be disabled by a %DIFDELCMD statements), enable - # these commands again (such that for example displayed math in a deleted equation - # is properly within math mode. For math mode environments replace numbered equation - # environments with their display only variety (so that equation numbers in new file and - # diff file are identical - while ( m/\\DIFdelbegin.*?\\DIFdelend/sg ) { - $cnt=0; - $len=length($&); - $begin=pos($_) - $len; - $delblock=$&; - - - ### (.*?[^\n]?)\n? construct is necessary to avoid empty lines in math mode, which result in - ### an error - # displayed math environments - $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; - # 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 - $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; - - # now look for unpaired %DIFDELCMD < \begin{MATHENV}; if found add \begin{$MATHREPL} and insert \end{$MATHREPL} - # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted - if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\begin{$MATHREPL}$AUXCMD\n/sg ) { - $delblock =~ s/(\\DIFdelend$)/\\end{$MATHREPL}$AUXCMD\n$1/s ; - } - # now look for unpaired %DIFDELCMD < \end{MATHENV}; if found add \end{MATHMODE} and insert \begin{MATHMODE} - # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted - if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{((?:$MATHENV)|SQUAREBRACKET)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\end{MATHMODE}$AUXCMD\n/sg ) { - $delblock =~ s/(\\DIFdelend$)/\\begin{MATHMODE}$AUXCMD\n$1/s ; - } - - - # equation array environment - $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; - $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; - - # now look for unpaired %DIFDELCMD < \begin{MATHARRENV}; if found add \begin{$MATHARRREPL} and insert \end{$MATHARRREPL} - # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted - if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\begin\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\begin{$MATHARRREPL}$AUXCMD\n/sg ) { - $delblock =~ s/(\\DIFdelend$)/\\end{$MATHARRREPL}$AUXCMD\n$1/s ; - } - # now look for unpaired %DIFDELCMD < \end{MATHENV}; if found add \end{MATHMODE} and insert \begin{MATHMODE} - # just before end of block; again we use look-behind assertion to avoid matching constructions which have already been converted - if ($delblock=~ s/(?<!${AUXCMD}\n)(\%DIFDELCMD < \s*\\end\{($MATHARRENV)\}\s*?(?:$DELCMDCLOSE|\n))/$1\\end{MATHMODE}$AUXCMD\n/sg ) { - $delblock =~ s/(\\DIFdelend$)/\\begin{MATHMODE}$AUXCMD\n$1/s ; - } - - # parse $delblock for deleted and reinstated eqnarray* environments - within those reinstate \\ and & commands - while ( $delblock =~ m/\\begin\Q{$MATHARRREPL}$AUXCMD\E\n.*?\n\\end\Q{$MATHARRREPL}$AUXCMD\E\n/sg ) { - $cnt2=0; - $len2=length($&); - $begin2=pos($delblock) - $len2; - $eqarrayblock=$&; - # reinstate deleted & and \\ commands - $eqarrayblock=~ s/(\%DIFDELCMD < \s*(\&|\\\\)\s*?(?:$DELCMDCLOSE|\n))/$1$2$AUXCMD\n/sg ; - - substr($delblock,$begin2,$len2)=$eqarrayblock; - pos($delblock) = $begin2 + length($eqarrayblock); - } - - - -# b.where one of the commands matching $COUNTERCMD is used as a DIFAUXCMD, add a statement -# subtracting one from the respective counter to keep numbering consistent with new file - $delblock=~ s/\\($COUNTERCMD)((?:${extraspace}\[$brat0\]${extraspace}|${extraspace}\{$pat4\})*\s*${AUXCMD}\n)/\\$1$2\\addtocounter{$1}{-1}${AUXCMD}\n/sg ; -# c. If in-line math mode contains array environment, enclose the whole environment in \mbox'es - while ( $delblock =~ m/($math)(\s*)/sg ) { - $cnt2=0; - $len2=length($&); - $begin2=pos($delblock) - $len2; - $mathblock="%\n\\mbox{$AUXCMD\n$1\n}$AUXCMD\n"; - next unless $mathblock =~ m/\{$ARRENV\}/ ; - substr($delblock,$begin2,$len2)=$mathblock; - pos($delblock) = $begin2 + length($mathblock); - } - -# splice in modified delblock - substr($_,$begin,$len)=$delblock; - pos = $begin + length($delblock); - } - # make the array modification in added blocks - while ( m/\\DIFaddbegin.*?\\DIFaddend/sg ) { - $cnt=0; - $len=length($&); - $begin=pos($_) - $len; - $addblock=$&; - while ( $addblock =~ m/($math)(\s*)/sg ) { - $cnt2=0; - $len2=length($&); - $begin2=pos($addblock) - $len2; - $mathblock="%\n\\mbox{$AUXCMD\n$1\n}$AUXCMD\n"; - next unless $mathblock =~ m/\{$ARRENV\}/ ; - substr($addblock,$begin2,$len2)=$mathblock; - pos($addblock) = $begin2 + length($mathblock); - } - -# splice in modified addblock - substr($_,$begin,$len)=$addblock; - pos = $begin + length($addblock); - } - - - ### old place for BEGINDIF, ENDDIF replacement - # change begin and end commands within comments such that they - # don't disturb the pattern matching (if there are several \begin or \end in one line - # this substitution is insufficient but that appears unlikely) - s/(%.*)\\begin\{(.*)$/$1\\BEGINDIF\{$2/mg ; - s/(%.*)\\end\{(.*)$/$1\\ENDDIF\{$2/mg ; - - # Replace MATHMODE environments from step 1a above by the correct Math environment - - # The next line is complicated. The negative look-ahead insertion makes sure that no \end{$MATHENV} (or other mathematical - # environments) are between the \begin{$MATHENV} and \end{MATHMODE} commands. This is necessary as the minimal matching - # is not globally minimal but only 'locally' (matching is beginning from the left side of the string) - 1 while s/\\begin{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}((?:.(?!(?:\\end{(?:(?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}|\\begin{MATHMODE})))*?)\\end{MATHMODE}/\\begin{$1}$2\\end{$1}/s; - 1 while s/\\begin{MATHMODE}((?:.(?!\\end{MATHMODE}))*?)\\end{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}/\\begin{$2}$1\\end{$2}/s; - # convert remaining \begin{MATHMODE} \end{MATHMODE} (and not containing & or \\ )into MATHREPL environments - s/\\begin{MATHMODE}((?:(.(?!(?<!\\)\&|\\\\))*)?)\\end{MATHMODE}/\\begin{$MATHREPL}$1\\end{$MATHREPL}/sg; - # others into MATHARRREPL - s/\\begin{MATHMODE}(.*?)\\end{MATHMODE}/\\begin{$MATHARRREPL}$1\\end{$MATHARRREPL}/sg; - - # now look for AUXCMD math-mode pairs which have only comments (or empty lines between them), and remove the added commands - s/\\begin{((?:$MATHENV)|(?:$MATHARRENV)|SQUAREBRACKET)}$AUXCMD\n((?:\s*%.[^\n]*\n)*)\\end{\1}$AUXCMD\n/$2/sg; - - - # Remove all mark up within picture environments - while ( m/\\begin\{($PICTUREENV)\}.*?\\end\{\1\}/sg ) { - $cnt=0; - $len=length($&); - $begin=pos($_) - $len; - $float=$&; - $float =~ s/\\DIFaddbegin //g; - $float =~ s/\\DIFaddend //g; - $float =~ s/\\DIFadd\{($pat4)\}/$1/g; - $float =~ s/\\DIFdelbegin //g; - $float =~ s/\\DIFdelend //g; - $float =~ s/\\DIFdel\{($pat4)\}//g; - substr($_,$begin,$len)=$float; - pos = $begin + length($float); - } - # Convert DIFadd, DIFdel, DIFFaddbegin , ... into varieties - # within floats (currently recognised float environments: plate,table,figure - # plus starred varieties). - while ( m/\\begin\{($FLOATENV)\}.*?\\end\{\1\}/sg ) { - $cnt=0; - $len=length($&); - $begin=pos($_) - $len; - $float=$&; - $float =~ s/\\DIFaddbegin /\\DIFaddbeginFL /g; - $float =~ s/\\DIFaddend /\\DIFaddendFL /g; - $float =~ s/\\DIFadd\{/\\DIFaddFL{/g; - $float =~ s/\\DIFdelbegin /\\DIFdelbeginFL /g; - $float =~ s/\\DIFdelend /\\DIFdelendFL /g; - $float =~ s/\\DIFdel\{/\\DIFdelFL{/g; - substr($_,$begin,$len)=$float; - pos = $begin + length($float); - } - ### former location of undo renaming of \begin and \end in comments - - # remove empty DIFCMD < lines - s/^\Q${DELCMDOPEN}\E\n//msg; - - # Expand hashes of verb and verbatim environments (note negative look behind assertion to not leak out of DIFDELCMD comments - s/(\\verb\*?)\{([-\d]*?)\}/"${1}". fromhash(\%verbhash,$2)/esg; - s/${DELCMDOPEN}\\(verbatim\*?)\{([-\d]*?)\}/"${DELCMDOPEN}\\begin{${1}}".fromhash(\%verbhash,$2,$DELCMDOPEN)."${DELCMDOPEN}\\end{${1}}"/esg; - s/\\(verbatim\*?)\{([-\d]*?)\}/"\\begin{${1}}".fromhash(\%verbhash,$2)."\\end{${1}}"/esg; - # Convert '\PERCENTAGE ' back into '\%' - s/\\PERCENTAGE /\\%/g; - # remove all \PAR tokens (taking care to properly keep commented out PAR's - # from introducing uncommented newlines - next line) - s/(%DIF < )([^\n]*?)\\PAR\n/$1$2\n$1\n/sg; - # convert PAR commands which are on a line by themselves - s/\n(\s*?)\\PAR\n/\n\n/sg; - # convert remaining PAR commands (which are preceded by non-white space characters, usually "}" ($ADDCLOSE) - s/\\PAR\n/\n\n/sg; - - # package specific processing: - if ( defined($packages{"endfloat"})) { - #endfloat: make sure \begin{figure} and \end{figure} are always - # on a line by themselves, similarly for table environment - print STDERR "endfloat package detected.\n" if $verbose ; - # eliminate whitespace before and after - s/^(\s*)(\\(?:begin|end)\{(?:figure|table)\})(\s*)$/$2/mg; - # split lines with remaining characters before float enviroment conmmand - s/^(.+)(\\(?:begin|end)\{(?:figure|table)\})/$1\n$2/mg; - # split lines with remaining characters after float enviroment conmmand - s/(\\(?:begin|end)\{(?:figure|table)\})(.+)$/$1\n$2/mg; - } - # undo renaming of the \begin and \end and dollars in comments - s/(%.*)\\BEGINDIF\{(.*)$/$1\\begin\{$2/mg ; - s/(%.*)\\ENDDIF\{(.*)$/$1\\end\{$2/mg ; - 1 while s/(%.*)DOLLARDIF/$1\$/mg ; -# Convert \begin{SQUAREBRACKET} \end{SQUAREBRACKET} into \[ \] - s/\\end{SQUAREBRACKET}/\\\]/sg; - s/\\begin{SQUAREBRACKET}/\\\[/sg; -# 4. Convert \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} into $$ $$ - s/\\begin\{DOLLARDOLLAR\}(.*?)\\end\{DOLLARDOLLAR\}/\$\$$1\$\$/sg; -# 5. Convert \SUPERSCRIPTNB{n} into ^n and \SUPERSCRIPT{nn} into ^{nnn} - s/\\SUPERSCRIPTNB{($pat0)}/^$1/g ; - s/\\SUPERSCRIPT{($pat4)}/^{$1}/g ; - # Convert \SUBSCRIPNB{n} into _n and \SUBCRIPT{nn} into _{nnn} - s/\\SUBSCRIPTNB{($pat0)}/_$1/g ; - s/\\SUBSCRIPT{($pat4)}/_{$1}/g ; - - return; - } -} - - -# @diffs=linediff(\@seq1, \@seq2) -# mark up lines like this -#%DIF mm-mmdnn -#%< old deleted line(s) -#%DIF ------- -#%DIF mmann-nn -#new appended line %< -#%DIF ------- -# Future extension: mark change explicitly -# Assumes: traverse_sequence traverses deletions before insertions in changed sequences -# all line numbers relative to line 0 (first line of real file) -sub linediff { - my $seq1 = shift ; - my $seq2 = shift ; - - my $block = []; - my $retseq = []; - my @begin=('','',''); # dummy initialisation - my $instring ; - - my $discard = sub { @begin=('d',$_[0],$_[1]) unless scalar @$block ; - push(@$block, "%DIF < " . $seq1->[$_[0]]) }; - my $add = sub { if (! scalar @$block) { - @begin=('a',$_[0],$_[1]) ;} - elsif ( $begin[0] eq 'd' ) { - $begin[0]='c'; $begin[2]=$_[1]; - push(@$block, "%DIF -------") } - push(@$block, $seq2->[$_[1]] . " %DIF > " ) }; - my $match = sub { if ( scalar @$block ) { - if ( $begin[0] eq 'd' && $begin[1]!=$_[0]-1) { - $instring = sprintf "%%DIF %d-%dd%d",$begin[1],$_[0]-1,$begin[2]; } - elsif ( $begin[0] eq 'a' && $begin[2]!=$_[1]-1) { - $instring = sprintf "%%DIF %da%d-%d",$begin[1],$begin[2],$_[1]-1; } - elsif ( $begin[0] eq 'c' ) { - $instring = sprintf "%%DIF %sc%s", - ($begin[1]==$_[0]-1) ? "$begin[1]" : $begin[1]."-".($_[0]-1) , - ($begin[2]==$_[1]-1) ? "$begin[2]" : $begin[2]."-".($_[1]-1) ; } - else { - $instring = sprintf "%%DIF %d%s%d",$begin[1],$begin[0],$begin[2]; } - push @$retseq, $instring,@$block, "%DIF -------" ; - $block = []; - } - push @$retseq, $seq2->[$_[1]] - }; - # key function: remove multiple spaces (such that insertion or deletion of redundant white space is not reported) - my $keyfunc = sub { join(" ",split(" ",shift())) }; - - traverse_sequences($seq1,$seq2, { MATCH=>$match, DISCARD_A=>$discard, DISCARD_B=>$add }, $keyfunc ); - push @$retseq, @$block if scalar @$block; - - return wantarray ? @$retseq : $retseq ; -} - - - -# init_regex_arr_data(\@array,"TOKEN INIT") -# scans DATA file handel for line "%% TOKEN INIT" line -# then appends each line not beginning with % into array (as a quoted regex) -sub init_regex_arr_data { - my ($arr,$token)=@_; - my ($copy); - while (<DATA>) { - if ( m/^%%BEGIN $token\s*$/ ) { - $copy=1; } - elsif ( m/^%%END $token\s*/ ) { - last; } - chomp; - push (@$arr,qr/^$_$/) if ( $copy && !/^%/ ) ; - } - seek DATA,0,0; # rewind DATA handle to file begin -} - - -# init_regex_arr_ext(\@array,$arg) -# fills array with regular expressions. -# if arg is a file name, then read in list of regular expressions from that file -# (one expression per line) -# Otherwise treat arg as a comma separated list of regular expressions -sub init_regex_arr_ext { - my ($arr,$arg)=@_; - my $regex; - if ( -f $ arg ) { - open(FILE,"$arg") or die ("Couldn't open $arg: $!"); - while (<FILE>) { - chomp; - next if /^\s*#/ || /^\s*%/ || /^\s*$/ ; - push (@$arr,qr/^$_$/); - } - close(FILE); - } - else { - # assume it is a comma-separated list of reg-ex - foreach $regex (split(qr/(?<!\\),/,$arg)) { - $regex =~ s/\\,/,/g; - push (@$arr,qr/^$regex$/); - } - } -} - -#exetime() returns time since last execution of this command -#exetime(1) resets this time -my $lasttime=-1; # global variable for persistence -sub exetime { - my $reset=0; - my $retval; - if ((scalar @_) >=1) { - $reset=shift; - } - if ($reset) { - $lasttime=times(); - } - else { - $retval=times()-$lasttime; - $lasttime=$lasttime+$retval; - return($retval); - } -} - - -sub usage { - die <<"EOF"; -Usage: $0 [options] old.tex new.tex > diff.tex - -Compares two latex files and writes tex code to stdout, which has the same -format as new.tex but has all changes relative to old.tex marked up or commented. - ---type=markupstyle --t markupstyle Add code to preamble for selected markup style - Available styles: UNDERLINE CTRADITIONAL TRADITIONAL CFONT FONTSTRIKE INVISIBLE - CHANGEBAR CCHANGEBAR CULINECHBAR CFONTCBHBAR - [ Default: UNDERLINE ] - ---subtype=markstyle --s markstyle Add code to preamble for selected style for bracketing - commands (e.g. to mark changes in margin) - Available styles: SAFE MARGINAL DVIPSCOL - [ Default: SAFE ] - ---floattype=markstyle --f markstyle Add code to preamble for selected style which - replace standard marking and markup commands within floats - (e.g., marginal remarks cause an error within floats - so marginal marking can be disabled thus) - Available styles: FLOATSAFE IDENTICAL - [ Default: FLOATSAFE ] - ---encoding=enc --e enc Specify encoding of old.tex and new.tex. Typical encodings are - ascii, utf8, latin1, latin9. A list of available encodings can be - obtained by executing - perl -MEncode -e 'print join ("\\n",Encode->encodings( ":all" )) ;' - [Default encoding is utf8 unless the first few lines of the preamble contain - an invocation "\\usepackage[..]{inputenc} in which case the - encoding chosen by this command is asssumed. Note that ASCII (standard - latex) is a subset of utf8] - ---preamble=file --p file Insert file at end of preamble instead of auto-generating - preamble. The preamble must define the following commands - \\DIFaddbegin,\\DIFaddend,\\DIFadd{..}, - \\DIFdelbegin,\\DIFdelend,\\DIFdel{..}, - and varieties for use within floats - \\DIFaddbeginFL,\\DIFaddendFL,\\DIFaddFL{..}, - \\DIFdelbeginFL,\\DIFdelendFL,\\DIFdelFL{..} - (If this option is set -t, -s, and -f options - are ignored.) - ---exclude-safecmd=exclude-file ---exclude-safecmd="cmd1,cmd2,..." --A exclude-file ---replace-safecmd=replace-file ---append-safecmd=append-file ---append-safecmd="cmd1,cmd2,..." --a append-file Exclude from, replace or append to the list of regex - matching commands which are safe to use within the - scope of a \\DIFadd or \\DIFdel command. The file must contain - one Perl-RegEx per line (Comment lines beginning with # or % are - ignored). A literal comma within the comma-separated list must be - escaped thus "\\,", Note that the RegEx needs to match the whole of - the token, i.e., /^regex\$/ is implied and that the initial - "\\" of the command is not included. The --exclude-safecmd - and --append-safecmd options can be combined with the --replace-safecmd - option and can be used repeatedly to add cumulatively to the lists. - ---exclude-textcmd=exclude-file ---exclude-textcmd="cmd1,cmd2,..." --X exclude-file ---replace-textcmd=replace-file ---append-textcmd=append-file ---append-textcmd="cmd1,cmd2,..." --x append-file Exclude from, replace or append to the list of regex - matching commands whose last argument is text. See - entry for --exclude-safecmd directly above for further details. - ---replace-context1cmd=replace-file ---append-context1cmd=append-file ---append-context1cmd="cmd1,cmd2,..." - Replace or append to the list of regex matching commands - whose last argument is text but which require a particular - context to work, e.g. \\caption will only work within a figure - or table. These commands behave like text commands, except when - they occur in a deleted section, when they are disabled, but their - argument is shown as deleted text. - ---replace-context2cmd=replace-file ---append-context2cmd=append-file ---append-context2cmd="cmd1,cmd2,..." - As corresponding commands for context1. The only difference is that - context2 commands are completely disabled in deleted sections, including - their arguments. - - ---config var1=val1,var2=val2,... --c var1=val1,.. Set configuration variables. --c configfile Available variables: - MINWORDSBLOCK (integer) - FLOATENV (RegEx) - PICTUREENV (RegEx) - MATHENV (RegEx) - MATHREPL (String) - MATHARRENV (RegEx) - MATHARRREPL (String) - ARRENV (RegEx) - COUNTERCMD (RegEx) - This option can be repeated. - - ---packages=pkg1,pkg2,.. - Tell latexdiff that .tex file is processed with the packages in list - loaded. This is normally not necessary if the .tex file includes the - preamble, as the preamble is automatically scanned for \\usepackage commands. - Use of the --packages option disables automatic scanning, so if for any - reason package specific parsing needs to be switched off, use --packages=none. - The following packages trigger special behaviour: - endfloat hyperref amsmath - [ Default: scan the preamble for \\usepackage commands to determine - loaded packages.] - ---show-preamble Print generated or included preamble commands to stdout. - ---show-safecmd Print list of regex matching and excluding safe commands. - ---show-textcmd Print list of regex matching and excluding commands with text argument. - ---show-config Show values of configuration variables - ---show-all Show all of the above - - NB For all --show commands, no old.tex or new.tex file needs to be given, and no - differencing takes place. - ---allow-spaces Allow spaces between bracketed or braced arguments to commands - [Default requires arguments to directly follow each other without - intervening spaces] - ---flatten Replace \\input and \\include commands within body by the content - of the files in their argument. If \\includeonly is present in the - preamble, only those files are expanded into the document. However, - no recursion is done, i.e. \\input and \\include commands within - included sections are not expanded. The included files are assumed to - be located in the same directories as the old and new master files, - respectively, making it possible to organise files into old and new directories. - ---help --h Show this help text. - ---ignore-warnings Suppress warnings about inconsistencies in length between input - and parsed strings and missing characters. - ---verbose --V Output various status information to stderr during processing. - Default is to work silently. - ---version Show version number. -EOF -} - -=head1 NAME - -latexdiff - determine and markup differences between two latex files - -=head1 SYNOPSIS - -B<latexdiff> [ B<OPTIONS> ] F<old.tex> F<new.tex> > F<diff.tex> - -=head1 DESCRIPTION - -Briefly, I<latexdiff> is a utility program to aid in the management of -revisions of latex documents. It compares two valid latex files, here -called C<old.tex> and C<new.tex>, finds significant differences -between them (i.e., ignoring the number of white spaces and position -of line breaks), and adds special commands to highlight the -differences. Where visual highlighting is not possible, e.g. for changes -in the formatting, the differences are -nevertheless marked up in the source. - -The program treats the preamble differently from the main document. -Differences between the preambles are found using line-based -differencing (similarly to the Unix diff command, but ignoring white -spaces). A comment, "S<C<%DIF E<gt>>>" is appended to each added line, i.e. a -line present in C<new.tex> but not in C<old.tex>. Discarded lines - are deactivated by prepending "S<C<%DIF E<lt>>>". Changed blocks are preceded by -comment lines giving information about line numbers in the original files. Where there are insignificant -differences, the resulting file C<diff.tex> will be similar to -C<new.tex>. At the end of the preamble, the definitions for I<latexdiff> markup commands are inserted. -In differencing the main body of the text, I<latexdiff> attempts to -satisfy the following guidelines (in order of priority): - -=over 3 - -=item 1 - -If both C<old.tex> and C<new.tex> are valid LaTeX, then the resulting -C<diff.tex> should also be valid LateX. (NB If a few plain TeX commands -are used within C<old.tex> or C<new.tex> then C<diff.tex> is not -guaranteed to work but usually will). - -=item 2 - -Significant differences are determined on the level of -individual words. All significant differences, including differences -between comments should be clearly marked in the resulting source code -C<diff.tex>. - -=item 3 - -If a changed passage contains text or text-producing commands, then -running C<diff.tex> through LateX should produce output where added -and discarded passages are highlighted. - -=item 4 - -Where there are insignificant differences, e.g. in the positioning of -line breaks, C<diff.tex> should follow the formatting of C<new.tex> - -=back - -For differencing the same algorithm as I<diff> is used but words -instead of lines are compared. An attempt is made to recognize -blocks which are completely changed such that they can be marked up as a unit. -Comments are differenced line by line -but the number of spaces within comments is ignored. Commands including -all their arguments are generally compared as one unit, i.e., no mark-up -is inserted into the arguments of commands. However, for a selected -number of commands (for example, C<\caption> and all sectioning -commands) the last argument is known to be text. This text is -split into words and differenced just as ordinary text (use options to -show and change the list of text commands, see below). As the -algorithm has no detailed knowledge of LaTeX, it assumes all pairs of -curly braces immediately following a command (i.e. a sequence of -letters beginning with a backslash) are arguments for that command. -As a restriction to condition 1 above it is thus necessary to surround -all arguments with curly braces, and to not insert -extraneous spaces. For example, write - - \section{\textem{This is an emphasized section title}} - -and not - - \section {\textem{This is an emphasized section title}} - -or - - \section\textem{This is an emphasized section title} - -even though all varieties are the same to LaTeX (but see -B<--allow-spaces> option which allows the second variety). - -For environments whose content does not conform to standard LaTeX or -where graphical markup does not make sense all markup commands can be -removed by setting the PICTUREENV configuration variable, set by -default to C<picture> and C<DIFnomarkup> environments; see B<--config> -option). The latter environment (C<DIFnomarkup>) can be used to -protect parts of the latex file where the markup results in illegal -markup. You have to surround the offending passage in both the old and -new file by C<\begin{DIFnomarkup}> and C<\end{DIFnomarkup}>. You must -define the environment in the preambles of both old and new -documents. I prefer to define it as a null-environment, - -C<\newenvironment{DIFnomarkup}{}{}> - -but the choice is yours. Any markup within the environment will be -removed, and generally everything within the environment will just be -taken from the new file. - -It is also possible to difference files which do not have a preamble. - In this case, the file is processed in the main document -mode, but the definitions of the markup commands are not inserted. - -All markup commands inserted by I<latexdiff> begin with "C<\DIF>". Added -blocks containing words, commands or comments which are in C<new.tex> -but not in C<old.tex> are marked by C<\DIFaddbegin> and C<\DIFaddend>. -Discarded blocks are marked by C<\DIFdelbegin> and C<\DIFdelend>. -Within added blocks all text is highlighted with C<\DIFadd> like this: -C<\DIFadd{Added text block}> -Selected `safe' commands can be contained in these text blocks as well -(use options to show and change the list of safe commands, see below). -All other commands as well as braces "{" and "}" are never put within -the scope of C<\DIFadd>. Added comments are marked by prepending -"S<C<%DIF E<gt> >>". - -Within deleted blocks text is highlighted with C<\DIFdel>. Deleted -comments are marked by prepending "S<C<%DIF E<lt> >>". Non-safe command -and curly braces within deleted blocks are commented out with -"S<C<%DIFDELCMD E<lt> >>". - - - -=head1 OPTIONS - -=head2 Preamble - -The following options determine the visual markup style by adding the appropriate -command definitions to the preamble. See the end of this section for a description of -available styles. - -=over 4 - -=item B<--type=markupstyle> or -B<-t markupstyle> - -Add code to preamble for selected markup style. This option defines -C<\DIFadd> and C<\DIFdel> commands. -Available styles: - -C<UNDERLINE CTRADITIONAL TRADITIONAL CFONT FONTSTRIKE INVISIBLE -CHANGEBAR CCHANGEBAR CULINECHBAR CFONTCBHBAR> - -[ Default: C<UNDERLINE> ] - -=item B<--subtype=markstyle> or -B<-s markstyle> - -Add code to preamble for selected style for bracketing -commands (e.g. to mark changes in margin). This option defines -C<\DIFaddbegin>, C<\DIFaddend>, C<\DIFdelbegin> and C<\DIFdelend> commands. -Available styles: C<SAFE MARGINAL DVIPSCOL> - -[ Default: C<SAFE> ] - -=item B<--floattype=markstyle> or -B<-f markstyle> - -Add code to preamble for selected style which -replace standard marking and markup commands within floats -(e.g., marginal remarks cause an error within floats -so marginal marking can be disabled thus). This option defines all -C<\DIF...FL> commands. -Available styles: C<FLOATSAFE TRADITIONALSAFE IDENTICAL> - -[ Default: C<FLOATSAFE> ] - -=item B<--encoding=enc> or -B<-e enc> - -Specify encoding of old.tex and new.tex. Typical encodings are -C<ascii>, C<utf8>, C<latin1>, C<latin9>. A list of available encodings can be -obtained by executing - -C<perl -MEncode -e 'print join ("\n",Encode->encodings( ":all" )) ;' > - -[Default encoding is utf8 unless the first few lines of the preamble contain -an invocation C<\usepackage[..]{inputenc}> in which case the -encoding chosen by this command is asssumed. Note that ASCII (standard -latex) is a subset of utf8] - -=item B<--preamble=file> or -B<-p file> - -Insert file at end of preamble instead of generating -preamble. The preamble must define the following commands -C<\DIFaddbegin, \DIFaddend, \DIFadd{..}, -\DIFdelbegin,\DIFdelend,\DIFdel{..},> -and varieties for use within floats -C<\DIFaddbeginFL, \DIFaddendFL, \DIFaddFL{..}, -\DIFdelbeginFL, \DIFdelendFL, \DIFdelFL{..}> -(If this option is set B<-t>, B<-s>, and B<-f> options -are ignored.) - -=item B<--packages=pkg1,pkg2,..> - -Tell latexdiff that .tex file is processed with the packages in list -loaded. This is normally not necessary if the .tex file includes the -preamble, as the preamble is automatically scanned for C<\usepackage> commands. -Use of the B<--packages> option disables automatic scanning, so if for any -reason package specific parsing needs to be switched off, use B<--packages=none>. -The following packages trigger special behaviour: - -=over 8 - -=item C<amsmath> - -Configuration variable amsmath is set to C<align*> (Default: C<eqnarray*>) - -=item C<endfloat> - -Ensure that C<\begin{figure}> and C<\end{figure}> always appear by themselves on a line. - -=item C<hyperref> - -Change name of C<\DIFadd> and C<\DIFdel> commands to C<\DIFaddtex> and C<\DIFdeltex> and -define new C<\DIFadd> and C<\DIFdel> commands, which provide a wrapper for these commands, -using them for the text but not for the link defining command (where any markup would cause -errors). - -=back - -[ Default: scan the preamble for C<\\usepackage> commands to determine - loaded packages.] - - - -=item B<--show-preamble> - -Print generated or included preamble commands to stdout. - -=back - -=head2 Configuration - -=over 4 - -=item B<--exclude-safecmd=exclude-file> or -B<-A exclude-file> or B<--exclude-safecmd="cmd1,cmd2,..."> - -=item B<--replace-safecmd=replace-file> - -=item B<--append-safecmd=append-file> or -B<-a append-file> or B<--append-safecmd="cmd1,cmd2,..."> - -Exclude from, replace or append to the list of regular expressions (RegEx) -matching commands which are safe to use within the -scope of a C<\DIFadd> or C<\DIFdel> command. The file must contain -one Perl-RegEx per line (Comment lines beginning with # or % are -ignored). Note that the RegEx needs to match the whole of -the token, i.e., /^regex$/ is implied and that the initial -"\" of the command is not included. -The B<--exclude-safecmd> and B<--append-safecmd> options can be combined with the -B<--replace-safecmd> -option and can be used repeatedly to add cumulatively to the lists. - B<--exclude-safecmd> -and B<--append-safecmd> can also take a comma separated list as input. If a -comma for one of the regex is required, escape it thus "\,". In most cases it -will be necessary to protect the comma-separated list from the shell by putting -it in quotation marks. - -=item B<--exclude-textcmd=exclude-file> or -B<-X exclude-file> or B<--exclude-textcmd="cmd1,cmd2,..."> - -=item B<--replace-textcmd=replace-file> - -=item B<--append-textcmd=append-file> or -B<-x append-file> or B<--append-textcmd="cmd1,cmd2,..."> - -Exclude from, replace or append to the list of regular expressions -matching commands whose last argument is text. See -entry for B<--exclude-safecmd> directly above for further details. - - -=item B<--replace-context1cmd=replace-file> - -=item B<--append-context1cmd=append-file> or -=item B<--append-context1cmd="cmd1,cmd2,..."> - -Replace or append to the list of regex matching commands -whose last argument is text but which require a particular -context to work, e.g. \caption will only work within a figure -or table. These commands behave like text commands, except when -they occur in a deleted section, when they are disabled, but their -argument is shown as deleted text. - -=item B<--replace-context1cmd=replace-file> - -=item B<--append-context2cmd=append-file> or -=item B<--append-context2cmd="cmd1,cmd2,..."> -As corresponding commands for context1. The only difference is that -context2 commands are completely disabled in deleted sections, including -their arguments. - - - -=item B<--config var1=val1,var2=val2,...> or B<-c var1=val1,..> - -=item B<-c configfile> - -Set configuration variables. The option can be repeated to set different -variables (as an alternative to the comma-separated list). -Available variables (see below for further explanations): - -C<MINWORDSBLOCK> (integer) - -C<FLOATENV> (RegEx) - -C<PICTUREENV> (RegEx) - -C<MATHENV> (RegEx) - -C<MATHREPL> (String) - -C<MATHARRENV> (RegEx) - -C<MATHARRREPL> (String) - -C<ARRENV> (RegEx) - -C<COUNTERCMD> (RegEx) - -=item B<--show-safecmd> - -Print list of RegEx matching and excluding safe commands. - -=item B<--show-textcmd> - -Print list of RegEx matching and excluding commands with text argument. - -=item B<--show-config> - -Show values of configuration variables. - -=item B<--show-all> - -Combine all --show commands. - -NB For all --show commands, no C<old.tex> or C<new.tex> file needs to be specified, and no -differencing takes place. - - -=back - -=head2 Miscellaneous - -=over 4 - -=item B<--verbose> or B<-V> - -Output various status information to stderr during processing. -Default is to work silently. - -=item B<--ignore-warnings> - -Suppress warnings about inconsistencies in length between input and -parsed strings and missing characters. These warning messages are -often related to non-standard latex or latex constructions with a -syntax unknown to C<latexdiff> but the resulting difference argument -is often fully functional anyway, particularly if the non-standard -latex only occurs in parts of the text which have not changed. - -=item B<--allow-spaces> - -Allow spaces between bracketed or braced arguments to commands. Note -that this option might have undesirable side effects (unrelated scope -might get lumpeded with preceding commands) so should only be used if the -default produces erroneous results. (Default requires arguments to -directly follow each other without intervening spaces). - -=item B<--flatten> - -Replace C<\input> and C<\include> commands within body by the content -of the files in their argument. If C<\includeonly> is present in the -preamble, only those files are expanded into the document. However, -no recursion is done, i.e. C<\input> and C<\include> commands within -included sections are not expanded. The included files are assumed to - be located in the same directories as the old and new master files, -respectively, making it possible to organise files into old and new directories. - -Use of this option is not recommended -primarily the processing time for the large documents is prohibitive, and -the resulting difference document no longer reflects the structure of the -input documents. - -=item B<--help> or -B<-h> - -Show help text - -=item B<--version> - -Show version number - -=back - -=head2 Predefined styles - -=head2 Major types - -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{...}> . - -=over 10 - -=item C<UNDERLINE> - -Added text is wavy-underlined and blue, discarded text is struck out and red -(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). - -=item C<CTRADITIONAL> - -Added text is blue and set in sans-serif, and a red footnote is created for each discarded -piece of text. (Requires color package) - -=item C<TRADITIONAL> - -Like C<CTRADITIONAL> but without the use of color. - -=item C<CFONT> - -Added text is blue and set in sans-serif, and discarded text is red and very small size. - -=item C<FONTSTRIKE> - -Added tex is set in sans-serif, discarded text small and struck out - -=item C<CCHANGEBAR> - -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). - -=item C<CFONTCHBAR> - -Like C<CFONT> but with additional changebars (Requires color and changebar packages). - -=item C<CULINECHBAR> - -Like C<UNDERLINE> but with additional changebars (Requires color, ulem and changebar packages). - -=item C<CHANGEBAR> - -No mark up of text, but mark margins with changebars (Requires changebar package). - -=item C<INVISIBLE> - -No visible markup (but generic markup commands will still be inserted. - -=back - -=head2 Subtypes - -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>) - -=over 10 - -=item C<SAFE> - -No additional markup (Recommended choice) - -=item C<MARGIN> - -Mark beginning and end of changed blocks with symbols in the margin nearby (using -the standard C<\marginpar> command - note that this sometimes moves somewhat -from the intended position. - -=item C<DVIPSCOL> - -An alternative way of marking added passages in blue, and deleted ones in red. Note -that C<DVIPSCOL> only works with the dvips converter, e.g. not pdflatex. -(it is recommeneded to use instead the main types to effect colored markup, -although in some cases coloring with dvipscol can be more complete). - -=back - -=head2 Float Types - -Some of the markup used in the main text might cause problems when used within -floats (e.g. figures or tables). For this reason alternative versions of all -markup commands are used within floats. The float type defines these alternative commands. - -=over 10 - -=item C<FLOATSAFE> - -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. - -=item C<TRADITIONALSAFE> - -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. - -=item C<IDENTICAL> - -Make no difference between the main text and floats. - -=back - - -=head2 Configuration Variables - -=over 10 - -=item C<MINWORDSBLOCK> - -Minimum number of tokens required to form an independent block. This value is -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. - -[ Default: 3 ] - -=item C<FLOATENV> - -Environments whose name matches the regular expression in C<FLOATENV> are -considered floats. Within these environments, the I<latexdiff> markup commands -are replaced by their FL variaties. - -[ Default: S<C<(?:figure|table|plate)[\w\d*@]*> >] - -=item C<PICTUREENV> - -Within environments whose name matches the regular expression in C<PICTUREENV> -all latexdiff markup is removed (in pathologic cases this might lead to - inconsistent markup but this situation should be rare). - -[ Default: S<C<(?:picture|DIFnomarkup)[\w\d*@]*> >] - -=item C<MATHENV>,C<MATHREPL> - -If both \begin and \end for a math environment (environment name matching C<MATHENV> -or \[ and \]) -are within the same deleted block, they are replaced by a \begin and \end commands for C<MATHREPL> -rather than being commented out. - -[ Default: C<MATHENV>=S<C<(?:displaymath|equation)> >, C<MATHREPL>=S<C<displaymath> >] - -=item C<MATHARRENV>,C<MATHARRREPL> - -as C<MATHENV>,C<MATHREPL> but for equation arrays - -[ Default: C<MATHARRENV>=S<C<eqnarray\*?> >, C<MATHREPL>=S<C<eqnarray> >] - -=item C<ARRENV> - -If a match to C<ARRENV> is found within an inline math environment within a deleted or added block, then the inlined math -is surrounded by C<\mbox{>...C<}>. This is necessary as underlining does not work within inlined array environments. - -[ Default: C<ARRENV>=S<C<(?:array|[pbvBV]matrix)> > - -=item C<COUNTERCMD> - -If a command in a deleted block which is also in the textcmd list matches C<COUNTERCMD> then an -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 -numbering in the new file. - -[ Default: C<COUNTERCMD>=C<(?:footnote|part|section|subsection> ... - -C<|subsubsection|paragraph|subparagraph)> ] - -=back - -=head1 BUGS - -UTF-8 support requires a relatively new version of perl (5.8.0 is sufficient -but 5.6.2 up would work OK, too). - -Option allow-spaces not implemented entirely consistently. It breaks -the rules that number and type of white space does not matter, as -different numbers of inter-argument spaces are treated as significant. - -Please send bug reports -to I<tilmann@esc.cam.ac.uk>. Include the serial number of I<latexdiff> -(from comments at the top of the source or use B<--version>). If you come across latex -files that are error-free and conform to the specifications set out -above, and whose differencing still does not result in error-free -latex, please send me those files, ideally edited to only contain the -offending passage as long as that still reproduces the problem. - -=head1 SEE ALSO - -L<latexrevise> - -=head1 PORTABILITY - -I<latexdiff> does not make use of external commands and thus should run -on any platform supporting Perl 5.6 or higher. If files with encodings -other than ASCII or UTF-8 are processed, Perl 5.8 or higher is required. - -The standard version of I<latexdiff> requires installation of the Perl package -C<Algorithm::Diff> (available from I<www.cpan.org> - -I<http://search.cpan.org/~nedkonz/Algorithm-Diff-1.15>) but a stand-alone -version, I<latexdiff-so>, which has this package inlined, is available, too. -I<latexdiff-fast> requires the I<diff> command to be present. - -=head1 AUTHOR - -Copyright (C) 2004-2007 Frederik Tilmann - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License Version 2 - -Contributors of fixes and additions: V. Kuhlmann, J. Paisley, N. Becker, T. Doerges, K. Huebner -Thanks to everyone who send in bug reports. - -=cut - -__END__ -%%BEGIN SAFE COMMANDS -% Regex matching commands which can safely be in the -% argument of a \DIFadd or \DIFdel command (leave out the \) -arabic -dashbox -emph -fbox -framebox -hspace -math.* -makebox -mbox -pageref -ref -symbol -raisebox -rule -text.* -shortstack -usebox -dag -ddag -copyright -pounds -S -P -oe -OE -ae -AE -aa -AA -o -O -l -L -frac -ss -sqrt -ldots -cdots -vdots -ddots -alpha -beta -gamma -delta -epsilon -varepsilon -zeta -eta -theta -vartheta -iota -kappa -lambda -mu -nu -xi -pi -varpi -rho -varrho -sigma -varsigma -tau -upsilon -phi -varphi -chi -psi -omega -Gamma -Delta -Theta -Lambda -Xi -Pi -Sigma -Upsilon -Phi -Psi -Omega -ps -mp -times -div -ast -star -circ -bullet -cdot -cap -cup -uplus -sqcap -vee -wedge -setminus -wr -diamond -(?:big)?triangle.* -lhd -rhd -unlhd -unrhd -oplus -ominus -otimes -oslash -odot -bigcirc -d?dagger -amalg -leq -prec -preceq -ll -(?:sq)?su[bp]set(?:eq)? -in -vdash -geq -succ(?:eq)? -gg -ni -dashv -equiv -sim(?:eq)? -asymp -approx -cong -neq -doteq -propto -models -perp -mid -parallel -bowtie -Join -smile -frown -.*arrow -(?:long)?mapsto -.*harpoon.* -leadsto -aleph -hbar -imath -jmath -ell -wp -Re -Im -mho -prime -emptyset -nabla -surd -top -bot -angle -forall -exists -neg -flat -natural -sharp -backslash -partial -infty -Box -Diamond -triangle -clubsuit -diamondsuit -heartsuit -spadesuit -sum -prod -coprod -int -oint -big(?:sq)?c[au]p -bigvee -bigwedge -bigodot -bigotimes -bigoplus -biguplus -(?:arc)?(?:cos|sin|tan|cot)h? -csc -arg -deg -det -dim -exp -gcd -hom -inf -ker -lg -lim -liminf -limsup -ln -log -max -min -Pr -sec -sup -(SUPER|SUB)SCRIPTNB -(SUPER|SUB)SCRIPT -%%END SAFE COMMANDS - -%%BEGIN TEXT COMMANDS -% Regex matching commands with a text argument (leave out the \) -addcontents.* -cc -closing -chapter -dashbox -emph -encl -fbox -framebox -footnote -footnotetext -framebox -part -(sub){0,2}section\*? -(sub)?paragraph\*? -makebox -mbox -opening -parbox -raisebox -savebox -sbox -shortstack -signature -text.* -value -underline -sqrt -(SUPER|SUB)SCRIPT -%%END TEXT COMMANDS - -%%BEGIN CONTEXT1 COMMANDS -% 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 -caption -%%END CONTEXT1 COMMANDS - -%%BEGIN CONTEXT2 COMMANDS -% 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 -title -author -%%END CONTEXT2 COMMANDS - - -%% TYPES (Commands for highlighting changed blocks) - -%DIF UNDERLINE PREAMBLE -\RequirePackage[normalem]{ulem} -\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} -\providecommand{\DIFadd}[1]{{\protect\color{blue}\uwave{#1}}} -\providecommand{\DIFdel}[1]{{\protect\color{red}\sout{#1}}} -%DIF END UNDERLINE PREAMBLE - -%DIF CTRADITIONAL PREAMBLE -\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} -\RequirePackage[stable]{footmisc} -\providecommand{\DIFadd}[1]{{\protect\color{blue} \sf #1}} -\providecommand{\DIFdel}[1]{{\protect\color{red} [..\footnote{removed: #1} ]}} -%DIF END CTRADITIONAL PREAMBLE - -%DIF TRADITIONAL PREAMBLE -\RequirePackage[stable]{footmisc} -\providecommand{\DIFadd}[1]{{\sf #1}} -\providecommand{\DIFdel}[1]{{[..\footnote{removed: #1} ]}} -%DIF END TRADITIONAL PREAMBLE - -%DIF CFONT PREAMBLE -\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} -\providecommand{\DIFadd}[1]{{\protect\color{blue} \sf #1}} -\providecommand{\DIFdel}[1]{{\protect\color{red} \scriptsize #1}} -%DIF END CFONT PREAMBLE - -%DIF FONTSTRIKE PREAMBLE -\RequirePackage[normalem]{ulem} -\providecommand{\DIFadd}[1]{{\sf #1}} -\providecommand{\DIFdel}[1]{{\footnotesize \sout{#1}}} -%DIF END FONTSTRIKE PREAMBLE - -%DIF CCHANGEBAR PREAMBLE -\RequirePackage[dvips]{changebar} -\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} -\providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}#1}\protect\cbend} -\providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}#1}\protect\cbdelete} -%DIF END CCHANGEBAR PREAMBLE - -%DIF CFONTCHBAR PREAMBLE -\RequirePackage[dvips]{changebar} -\RequirePackage{color}\definecolor{RED}{rgb}{1,0,0}\definecolor{BLUE}{rgb}{0,0,1} -\providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}\sf #1}\protect\cbend} -\providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}\scriptsize #1}\protect\cbdelete} -%DIF END CFONTCHBAR PREAMBLE - -%DIF CULINECHBAR PREAMBLE -\RequirePackage[normalem]{ulem} -\RequirePackage[dvips]{changebar} -\RequirePackage{color} -\providecommand{\DIFadd}[1]{\protect\cbstart{\protect\color{blue}\uwave{#1}}\protect\cbend} -\providecommand{\DIFdel}[1]{\protect\cbdelete{\protect\color{red}\sout{#1}}\protect\cbdelete} -%DIF END CULINECHBAR PREAMBLE - -%DIF CHANGEBAR PREAMBLE -\RequirePackage[dvips]{changebar} -\providecommand{\DIFadd}[1]{\protect\cbstart{#1}\protect\cbend} -\providecommand{\DIFdel}[1]{\protect\cbdelete} -%DIF END CHANGEBAR PREAMBLE - -%DIF INVISIBLE PREAMBLE -\providecommand{\DIFadd}[1]{#1} -\providecommand{\DIFdel}[1]{} -%DIF END INVISIBLE PREAMBLE - - -%% SUBTYPES (Markers for beginning and end of changed blocks) - -%DIF SAFE PREAMBLE -\providecommand{\DIFaddbegin}{} -\providecommand{\DIFaddend}{} -\providecommand{\DIFdelbegin}{} -\providecommand{\DIFdelend}{} -%DIF END SAFE PREAMBLE - -%DIF MARGIN PREAMBLE -\providecommand{\DIFaddbegin}{\protect\marginpar{a[}} -\providecommand{\DIFaddend}{\protect\marginpar{]}} -\providecommand{\DIFdelbegin}{\protect\marginpar{d[}} -\providecommand{\DIFdelend}{\protect\marginpar{]}} -%DIF END BRACKET PREAMBLE - -%DIF DVIPSCOL PREAMBLE -%Note: only works with dvips converter -\RequirePackage{color} -\RequirePackage{dvipscol} -\providecommand{\DIFaddbegin}{\protect\nogroupcolor{blue}} -\providecommand{\DIFaddend}{\protect\nogroupcolor{black}} -\providecommand{\DIFdelbegin}{\protect\nogroupcolor{red}} -\providecommand{\DIFdelend}{\protect\nogroupcolor{black}} -%DIF END DVIPSCOL PREAMBLE - - -%% FLOAT TYPES - -%DIF FLOATSAFE PREAMBLE -\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} -\providecommand{\DIFdelFL}[1]{\DIFdel{#1}} -\providecommand{\DIFaddbeginFL}{} -\providecommand{\DIFaddendFL}{} -\providecommand{\DIFdelbeginFL}{} -\providecommand{\DIFdelendFL}{} -%DIF END FLOATSAFE PREAMBLE - -%DIF IDENTICAL PREAMBLE -\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} -\providecommand{\DIFdelFL}[1]{\DIFdel{#1}} -\providecommand{\DIFaddbeginFL}{\DIFaddbegin} -\providecommand{\DIFaddendFL}{\DIFaddend} -\providecommand{\DIFdelbeginFL}{\DIFdelbegin} -\providecommand{\DIFdelendFL}{\DIFdelend} -%DIF END IDENTICAL PREAMBLE - -%DIF TRADITIONALSAFE PREAMBLE -% procidecommand color to make this work for TRADITIONAL and CTRADITIONAL -\providecommand{\color}[1]{} -\providecommand{\DIFaddFL}[1]{\DIFadd{#1}} -\providecommand{\DIFdel}[1]{{\protect\color{red}[..{\scriptsize {removed: #1}} ]}} -\providecommand{\DIFaddbeginFL}{} -\providecommand{\DIFaddendFL}{} -\providecommand{\DIFdelbeginFL}{} -\providecommand{\DIFdelendFL}{} -%DIF END FLOATSAFE PREAMBLE - -%% SPECIAL PACKAGE PREAMBLE COMMANDS - -% Standard \DIFadd and \DIFdel are redefined as \DIFaddtex and \DIFdeltex -% when hyperref package is included. -%DIF HYPERREF PREAMBLE -\providecommand{\DIFadd}[1]{\texorpdfstring{\DIFaddtex{#1}}{#1}} -\providecommand{\DIFdel}[1]{\texorpdfstring{\DIFdeltex{#1}}{}} -%DIF END HYPERREF PACKAGE |