From 321936e3f029ee5b2a879f17a39d919b95dd6aed Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Wed, 17 Oct 2018 21:09:38 +0000 Subject: latexdiff (17oct18) git-svn-id: svn://tug.org/texlive/trunk@48926 c570f23f-e606-0410-a88d-b1316a301751 --- .../linked_scripts/latexdiff/latexdiff-vc.pl | 42 +- .../texlive/linked_scripts/latexdiff/latexdiff.pl | 801 +++++++++++++++++---- 2 files changed, 686 insertions(+), 157 deletions(-) (limited to 'Build') diff --git a/Build/source/texk/texlive/linked_scripts/latexdiff/latexdiff-vc.pl b/Build/source/texk/texlive/linked_scripts/latexdiff/latexdiff-vc.pl index 907f38ece50..21bf1f3adf3 100755 --- a/Build/source/texk/texlive/linked_scripts/latexdiff/latexdiff-vc.pl +++ b/Build/source/texk/texlive/linked_scripts/latexdiff/latexdiff-vc.pl @@ -27,7 +27,11 @@ # # TODO/IDEAS: - option to call external pre-processing codes # - choose type of latex processor / bibtex (luatex, xelatex etc) -# version 1.2.1 (22 June 2017) +# version 1.3.0 ((7 October 2018) +# - option --only-changes with hyperref will suppress hyperrefs (pull request jprotze)_ +# - option --only-changes now moves (rather than copies) file with only changes +# version 1.2.1 (22 June 2017): +# - update: use qpdf (instead of pdftk) to select particular pages of output (pull request #102 submited by Tom Scogland via github). This was necessary because pdftk is being deprecated. pdftk is still used as a fall-back # - bug fix: --hg option was not recognised (partially fixes github issue #93 ) # - wrap passed-through options to latexdiff in quotation marks (fix github issue #58 ) # - program names for latexdiff, latex, dvips, bibtex configurable (fixes issue #40) @@ -68,8 +72,8 @@ use strict ; use warnings ; my $versionstring=<0 ) { - push @ldoptions, "--config $configlatexdiff"; + push @ldoptions, "--config=$configlatexdiff"; } if ( defined($flatten) ) { push @ldoptions, "--flatten" ; } +if ( defined($debug) && $debug ) { + push @ldoptions, "--debug" ; +} + # impose ZLABEL subtype if --only-changes option if ( $onlychanges ) { - push @ldoptions, "-s", "ZLABEL","-f","IDENTICAL" ; + push @ldoptions, "-s", "ZLABEL","-f","IDENTICAL","--no-links" ; } if ( scalar(@revs) == 0 ) { @@ -525,18 +534,25 @@ foreach $diff ( @difffiles ) { } elsif ( $run ) { if ( $onlychanges ) { my @pages=findchangedpages("$diffbase.aux"); - ### print ("Running pdftk \"$diffbase.pdf\" cat " . join(" ",@pages) . " output \"$diffbase-changedpage.pdf\"\n") or + my $gs = `which gs`; + $gs =~ s/^\s+|\s+$//g; my $qpdf = `which qpdf`; $qpdf =~ s/^\s+|\s+$//g; my $pdftk = `which pdftk`; $pdftk =~ s/^\s+|\s+$//g; - if (-x $qpdf) { - system("qpdf --linearize \"$diffbase.pdf\" --pages \"$diffbase.pdf\" " . join(",", @pages) . " -- \"$diffbase-changedpage.pdf\" ") == 0 - or die("could not execute qpdf to strip pages. Return code: $?"); + my $command; + if (-x $gs && `gs --version` >= 9.20) { + $command="gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dSAFER -sPageList=" . join(",", @pages) . " -sOutputFile=\"$diffbase-changedpage.pdf\" \"$diffbase.pdf\""; } elsif (-x $pdftk) { - system ("pdftk \"$diffbase.pdf\" cat " . join(" ",@pages) . " output \"$diffbase-changedpage.pdf\"")==0 or - die ("Could not execute . Return code: $?"); + $command="pdftk \"$diffbase.pdf\" cat " . join(" ",@pages) . " output \"$diffbase-changedpage.pdf\""; + } elsif (-x $qpdf) { + $command="qpdf --linearize \"$diffbase.pdf\" --pages \"$diffbase.pdf\" " . join(",", @pages) . " -- \"$diffbase-changedpage.pdf\" "; + } else { + die ("could not find any of gs, pdftk or qpdf"); } + print "Executing ".$command; + system($command) == 0 + or die("could not execute <".$command."> to strip pages. Return code: $?"); move("$diffbase-changedpage.pdf","$diffbase.pdf"); } push @ptmpfiles, "$diffbase.aux","$diffbase.log"; @@ -641,12 +657,14 @@ file in postscript or pdf format. =item B<--rcs>, B<--svn>, B<--cvs>, B<--git> or B<--hg> -Set the version system. +Set the version control system used. If no version system is specified, latexdiff-vc will venture a guess. latexdiff-cvs, latexdiff-rcs etc are variants of latexdiff-vc which default to the respective versioning system. However, this default can still be overridden using the options above. +Note that hg needs to support the C<--root> option (version >= 2.9) + =item B<-r>, B<-r> F or B<--revision>, B<--revision=>F Choose revision (under RCS, CVS, SVN, GIT or HG). One or two B<-r> options can be diff --git a/Build/source/texk/texlive/linked_scripts/latexdiff/latexdiff.pl b/Build/source/texk/texlive/linked_scripts/latexdiff/latexdiff.pl index 3fd8ea469c7..6d07c9a88f8 100755 --- a/Build/source/texk/texlive/linked_scripts/latexdiff/latexdiff.pl +++ b/Build/source/texk/texlive/linked_scripts/latexdiff/latexdiff.pl @@ -22,7 +22,22 @@ # # Detailed usage information at the end of the file # -# ToDo: + +# Version 1.3.0 (7 October 2018): +# - treat options to \documentclass as potential package names (some packages allow implicit loading of or imply selected packages +# - improved pattern matching: now allows nested angular brackets, and is no longer confused by escaped curly braces +# - improved pattern matching in COARSE mode: occasionally, the closing bracket or some other elements would be matched in an 'unnatural' way due to another sequence being more minimal in the computational sense, sometimes even causing errors due to tokens moving in or out of the scope of math environments. This is now discouraged by adding internal \DIFANCHOR commands (which are removed again in post-processing) (fixes issues reported via email by li_ruomeng . +# - verbatim and lstlisting environments are marked-up with line-by-line in a similar style to non-verbatim text (requires the listing package to be installed) +# (see new configuration variable VERBATIMLINEENV) (several issues and pull requests by jprotze) +# - --flatten: now supports \verbatiminput and \lstlistinput +# - --flatten: if file is not found, do not fail, simply warn and leave command unexpanded (inspired by issue #112). Don't warn if file name contains #[0-9] as it is then most likely an argument within a command definition rather than an actual file (applies to \input, \subfile, \include commands) +# - added to textcmds: \intertext +# - new config variable CUSTOMDIFCMD to allow defining special versions of commands in added or deleted blocks (Pull request by github user jprotze) +# - added option -no-links (mostly for use by latexdiff-vc in only-changes modes) (Pull request by github user jprotze) +# Bug fixes: +# - pattern matching of \verb and \lstinline commands had an error which meant they would trigger on commands beginning with \verb. +# - In description environments, mark up item descriptions by effectively reating the insides of item commannds as text commands (fixes #161) +# # # Version 1.2.1 (22 June 2017) # - add "DeclareOldFontCommand" to styles using \bf or \sf old style font commands (fixies issue #92 ) @@ -619,8 +634,8 @@ my ($algodiffversion)=split(/ /,$Algorithm::Diff::VERSION); my ($versionstring)=< undef , # list making environments - they will generally be kept - VERBATIMENV => undef, # Environments whose content should be treated as verbatim text + VERBATIMENV => undef, # Environments whose content should be treated as verbatim text and not be touched + VERBATIMLINEENV => undef, # Environments whose content should be treated as verbatim text and processed in line diff mode + CUSTOMDIFCMD => undef,# Custom dif command. Is defined in the document as a \DELcommand and \ADDcommand version to be replaced by the diff ITEMCMD => 'item' # command marking item in a list environment ); # Configuration variables: these have to be visible from the subroutines @@ -660,8 +677,10 @@ my ($ARRENV, $MINWORDSBLOCK, $PICTUREENV, $SCALEDELGRAPHICS, - $VERBATIMENV -); + $VERBATIMENV, + $VERBATIMLINEENV, + $CUSTOMDIFCMD + ); # my $MINWORDSBLOCK=3; # minimum number of tokens to form an independent block # # shorter identical blocks will be merged to the previous word @@ -673,7 +692,7 @@ my ($ARRENV, # my $MATHREPL='displaymath'; # Environment introducing deleted maths blocks # my $MATHARRENV='(?:eqnarray|align|alignat|gather|multline|flalign)[*]?' ; # Environments turning on eqnarray math mode # my $MATHARRREPL='eqnarray*'; # Environment introducing deleted maths blocks -# my $ARRENV='(?:aligned|array|[pbvBV]?matrix|smallmatrix|cases|split)'; # Environments 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 $ARRENV='(?:aligned|gathered|array|[pbvBV]?matrix|smallmatrix|cases|split)'; # Environments 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|chapter|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} @@ -707,8 +726,13 @@ my $DELCMDOPEN='%DIFDELCMD < '; # To mark begin of deleted commands (must begin 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 + # Note that for verbatim environment openings the %DIFAUXCMD cannot be placed in + # the same line as this would mean they are shown + # so the special form "%DIFAUXCMD NEXT" is used to indicate that the next line + # is an auxiliary command + # Similarly "%DIFAUXCMD LAST" would indicate the auxiliary command is in previous line (not currently used) my $DELCOMMENT='DIF < '; # To mark deleted comment line - +my $VERBCOMMENT='DIFVRB '; # to mark lines which are within a verbatim environment # main local variables: my @TEXTCMDLIST=(); # array containing patterns of commands with text arguments @@ -754,7 +778,7 @@ my ($type,$subtype,$floattype,$config,$preamblefile,$encoding,$nolabel,$visiblel $replacecontext1,$appendcontext1, $replacecontext2,$appendcontext2, $help,$verbose,$driver,$version,$ignorewarnings, - $enablecitmark,$disablecitmark,$allowspaces,$flatten,$debug,$earlylatexdiffpreamble); ###$disablemathmark, + $enablecitmark,$disablecitmark,$allowspaces,$flatten,$nolinks,$debug,$earlylatexdiffpreamble); ###$disablemathmark, my ($mboxsafe); # MNEMNONICS for mathmarkup my $mathmarkup; @@ -816,7 +840,7 @@ GetOptions('type|t=s' => \$type, 'subtype|s=s' => \$subtype, 'floattype|f=s' => \$floattype, 'config|c=s' => \@configlist, - 'add-to-config|c=s' => \@addtoconfiglist, + 'add-to-config=s' => \@addtoconfiglist, 'preamble|p=s' => \$preamblefile, 'encoding|e=s' => \$encoding, 'label|L=s' => \@labels, @@ -849,9 +873,10 @@ GetOptions('type|t=s' => \$type, 'ignore-warnings' => \$ignorewarnings, 'driver=s'=> \$driver, 'flatten' => \$flatten, + 'no-links' => \$nolinks, 'version' => \$version, 'help|h' => \$help, - 'debug!' => \$debug ); + 'debug!' => \$debug ) or die "Use latexdiff -h to get help.\n" ; if ( $help ) { usage() ; @@ -1018,12 +1043,13 @@ foreach $key ( keys(%CONFIG) ) { foreach $assign ( @addtoconfig ) { + ###print STDERR "assign:|$assign|\n"; $assign=~ m/\s*(\w*)\s*=\s*(\S*)\s*$/ or die "Illegal assignment $assign in configuration list (must be variable=value)"; exists $CONFIG{$1} or die "Unknown configuration variable $1."; $CONFIG{$1}.=";$2"; } -# Map from hash to variables (we do this to have more concise code later, change from comma-separeted list) +# Map from hash to variables (we do this to have more concise code later, change from comma-separated list) foreach ( keys(%CONFIG) ) { if ( $_ eq "MINWORDSBLOCK" ) { $MINWORDSBLOCK = $CONFIG{$_}; } elsif ( $_ eq "FLOATENV" ) { $FLOATENV = liststringtoregex($CONFIG{$_}) ; } @@ -1036,6 +1062,8 @@ foreach ( keys(%CONFIG) ) { elsif ( $_ eq "MATHARRREPL" ) { $MATHARRREPL = $CONFIG{$_} ; } elsif ( $_ eq "ARRENV" ) { $ARRENV = liststringtoregex($CONFIG{$_}) ; } elsif ( $_ eq "VERBATIMENV" ) { $VERBATIMENV = liststringtoregex($CONFIG{$_}) ; } + elsif ( $_ eq "VERBATIMLINEENV" ) { $VERBATIMLINEENV = liststringtoregex($CONFIG{$_}) ; } + elsif ( $_ eq "CUSTOMDIFCMD" ) { $CUSTOMDIFCMD = liststringtoregex($CONFIG{$_}) ; } elsif ( $_ eq "COUNTERCMD" ) { $COUNTERCMD = liststringtoregex($CONFIG{$_}) ; } elsif ( $_ eq "SCALEDELGRAPHICS" ) { $SCALEDELGRAPHICS = $CONFIG{$_} ; } else { die "Unknown configuration variable $_.";} @@ -1088,10 +1116,20 @@ push(@SAFECMDLIST, qr/^QLEFTBRACE$/, qr/^QRIGHTBRACE$/); my $pat_n = $pat0; # if you get "undefined control sequence MATHBLOCKmath" error, increase the maximum value in this loop for (my $i_pat = 0; $i_pat < 20; ++$i_pat){ - $pat_n = '(?:[^{}]|\{'.$pat_n.'\})*'; + $pat_n = '(?:[^{}]|\{'.$pat_n.'\}|\\\\\{|\\\\\})*'; + # Actually within the text body, quoted braces are replaced in pre-processing. The only place where + # the last part of the pattern matters is when processing the arguments of context2cmds in the preamble + # and these contain a \{ or \} combination, probably rare. + # It should thus be fine to use the simpler version below. + ### $pat_n = '(?:[^{}]|\{'.$pat_n.'\})*'; } my $brat0 = '(?:[^\[\]]|\\\[|\\\])*'; + my $brat_n = $brat0; + for (my $i_pat = 0; $i_pat < 4; ++$i_pat){ + $brat_n = '(?:[^\[\]]|\['.$brat_n.'\]|\\\[|\\\])*'; + ### $brat_n = '(?:[^\[\]]|\['.$brat_n.'\])*'; # Version not taking into account escaped \[ and \] + } my $abrat0 = '(?:[^<>])*'; my $quotemarks = '(?:\'\')|(?:\`\`)'; @@ -1107,13 +1145,14 @@ push(@SAFECMDLIST, qr/^QLEFTBRACE$/, qr/^QRIGHTBRACE$/); my $word_ja='\p{Han}+|\p{InHiragana}+|\p{InKatakana}+'; my $word='(?:' . $word_ja . '|(?:(?:[-\w\d*]|\\\\[\"\'\`~^][A-Za-z\*])(?!(?:' . $word_ja . ')))+)'; my $cmdleftright='\\\\(?:left|right|[Bb]igg?[lrm]?|middle)\s*(?:[<>()\[\]|\.]|\\\\(?:[|{}]|\w+))'; - my $cmdoptseq='\\\\[\w\d@\*]+'.$extraspace.'(?:(?:<'.$abrat0.'>|\['.$brat0.'\]|\{'. $pat_n . '\}|\(' . $coords .'\))'.$extraspace.')*'; + my $cmdoptseq='\\\\[\w\d@\*]+'.$extraspace.'(?:(?:<'.$abrat0.'>|\['.$brat_n.'\]|\{'. $pat_n . '\}|\(' . $coords .'\))'.$extraspace.')*'; + my $defseq='\\\\def\\\\[\w\d@\*]+(?:#\d+|\[#\d+\])+(?:\{'. $pat_n . '\})?'; my $backslashnl='\\\\\n'; - my $oneletcmd='\\\\.\*?(?:\['.$brat0.'\]|\{'. $pat_n . '\})*'; + my $oneletcmd='\\\\.\*?(?:\['.$brat_n.'\]|\{'. $pat_n . '\})*'; my $math='\$(?:[^$]|\\\$)*?\$|\\\\[(](?:.|\n)*?\\\\[)]'; ## the current maths command cannot cope with newline within the math expression - my $comment='%.*?\n'; - my $pat=qr/(?:\A\s*)?(?:${and}|${quotemarks}|${number}|${word}|$quotedunderscore|$cmdleftright|${cmdoptseq}|${math}|${backslashnl}|${oneletcmd}|${comment}|${punct}|${mathpunct}|\{|\})\s*/ ; + my $comment='%[^\n]*\n'; + my $pat=qr/(?:\A\s*)?(?:${and}|${quotemarks}|${number}|${word}|$quotedunderscore|${defseq}|$cmdleftright|${cmdoptseq}|${math}|${backslashnl}|${oneletcmd}|${comment}|${punct}|${mathpunct}|\{|\})\s*/ ; @@ -1239,6 +1278,9 @@ if (defined $packages{"hyperref"} ) { $latexdiffpreamble =~ s/\{\\DIFadd\}/{\\DIFaddtex}/g; $latexdiffpreamble =~ s/\{\\DIFdel\}/{\\DIFdeltex}/g; $latexdiffpreamble .= join "\n",(extrapream("HYPERREF"),""); + if($nolinks){ + $latexdiffpreamble .= "\n\\hypersetup{bookmarks=false}"; + } ### $latexdiffpreamble .= '%DIF PREAMBLE EXTENSION ADDED BY LATEXDIFF FOR HYPERREF PACKAGE' . "\n"; ### $latexdiffpreamble .= '\providecommand{\DIFadd}[1]{\texorpdfstring{\DIFaddtex{#1}}{#1}}' . "\n"; ### $latexdiffpreamble .= '\providecommand{\DIFdel}[1]{\texorpdfstring{\DIFdeltex{#1}}{}}' . "\n"; @@ -1269,9 +1311,50 @@ if ($graphicsmarkup != NONE ) { } } -# If listings is being used and latexdiffpreamble uses color markup -if (defined($packages{"listings"} and $latexdiffpreamble =~ /\\RequirePackage(?:\[$brat0\])?\{color\}/)) { - $latexdiffpreamble .= join "\n",(extrapream("LISTINGS"),""); +$ulem = ($latexdiffpreamble =~ /\\RequirePackage(?:\[$brat_n\])?\{ulem\}/ || defined $packages{"ulem"}); + + +# If listings is being used or can be found in the latexdiff search path, add to the preamble auxiliary code to enable line-by-line markup +if ( defined($packages{"listings"}) or `kpsewhich listings.sty` ne "" ) { + my @listingpreamble=extrapream("LISTINGS"); + my @listingDIFcode=(); + my $replaced; + # note that in case user supplies preamblefile the type might not reflect well the + @listingDIFcode=extrapream("-nofail","DIFCODE_" . $type) unless defined($preamblefile); + if (!(@listingDIFcode)) { + # if listingDIFcode is empty try to guess a suitable one from the preamble + if ($latexdiffpreamble =~ /\\RequirePackage(?:\[$brat_n\])?\{color\}/ and $ulem ) { + @listingDIFcode=extrapream("DIFCODE_UNDERLINE"); + } elsif ( $latexdiffpreamble =~ /\\RequirePackage(?:\[$brat_n\])?\{color\}/ ) { + # only colour used + @listingDIFcode=extrapream("DIFCODE_CFONT"); + } else { + # fall-back solution + @listingDIFcode=extrapream("DIFCODE_BOLD"); + } + } + # now splice it in + $replaced=0; + ###print STDERR "DEBUG: listingDIFcode: ",join("\n",@listingDIFcode),"|||\n" if $debug; + + @listingpreamble=grep { + # only replace if this has not been done already (use short-circuit property of and) + if (!$replaced and $_ =~ s/^.*%DIFCODE TEMPLATE.*$/join("\n",@listingDIFcode)/e ) { + ###print STDERR "DEBUG: Replaced text $_\n" if $debug; + $replaced=1; + 1; + } else { + # return false for those lines matching %DIFCODE TEMPLATE (so that they are not included in output) + not m/%DIFCODE TEMPLATE/; + } + } @listingpreamble; + ### print STDERR "DEBUG: listingpreamble @listingpreamble\n"; + $latexdiffpreamble .= join "\n",(@listingpreamble,""); +} else { + print STDERR "WARNING: listings package not detected. Disabling mark-up in verbatim environments \n" ; + # if listings does not exist disable line-by-line markup and treat all verbatim environments as opaque + $VERBATIMENV = liststringtoregex($CONFIG{VERBATIMENV}.";".$CONFIG{VERBATIMLINEENV}); + $VERBATIMLINEENV = ""; } # adding begin and end marker lines to preamble @@ -1305,6 +1388,9 @@ if ( length $oldpreamble && length $newpreamble ) { push @diffpreamble,$latexdiffpreamble; } push @diffpreamble,'\begin{document}'; + if (defined $packages{"hyperref"} && $nolinks) { + push @diffpreamble, '\begin{NoHyper}'; + } } elsif ( !length $oldpreamble && !length $newpreamble ) { @diffpreamble=(); @@ -1319,8 +1405,6 @@ elsif ( !length $oldpreamble && !length $newpreamble ) { # Use post-processing # and $packages{"apacite"}!~/natbibpapa/ -$ulem = ($latexdiffpreamble =~ /\\RequirePackage(?:\[$brat0\])?\{ulem\}/ || defined $packages{"ulem"}); - if (defined $packages{"units"} && $ulem ) { # protect inlined maths environments by surrounding with an \mbox @@ -1438,11 +1522,8 @@ preprocess($oldbody,$newbody); # run difference algorithm @diffbody=bodydiff($oldbody, $newbody); $diffbo=join("",@diffbody); -if ( $debug ) { - open(RAWDIFF,">","latexdiff.debug.bodydiff"); - print RAWDIFF $diffbo; - close(RAWDIFF); -} +writedebugfile($diffbo,"bodydiff"); + print STDERR "(",exetime()," s)\n","Postprocessing body. \n" if $verbose; postprocess($diffbo); $diffall =join("\n",@diffpreamble) ; @@ -1455,6 +1536,9 @@ if (defined($visiblelabel)) { } $diffall .= "$diffbo" ; +if (defined $packages{"hyperref"} && $nolinks) { + $diffall .= "\\end{NoHyper}\n"; +} $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; @@ -1483,7 +1567,7 @@ sub liststringtoregex { # show_configuration # note that this is not encapsulated but uses variables from the main program # It is provided for convenience because in the future it is planned to allow output -# to be modified based on what packages are read etc - this works only if the input files are actually red +# to be modified based on what packages are read etc - this works only if the input files are actually read # whether or not additional files are provided sub show_configuration { if ($showpreamble) { @@ -1527,6 +1611,10 @@ sub show_configuration { print "MATHREPL=$MATHREPL\n"; print "MINWORDSBLOCK=$MINWORDSBLOCK\n"; print "PICTUREENV=$PICTUREENV\n"; + print "SCALEDELGRAPHICS=$SCALEDELGRAPHICS\n"; + print "VERBATIMENV=$VERBATIMENV\n"; + print "VERBATIMLINEENV=$VERBATIMLINEENV\n"; + print "CUSTOMDIFCMD=$CUSTOMDIFCMD\n"; } } @@ -1615,7 +1703,7 @@ sub list_packages { # remove comments $preamble=~s/(?catfile($dirname,$fname); print STDERR "DEBUG Beg of line match |$1|\n" if defined($1) && $debug ; print STDERR "Include file $fname\n" if $verbose; - print STDERR "DEBUG looking for file ",File::Spec->catfile($dirname,$fname), "\n" if $debug; + print STDERR "DEBUG looking for file ",$fullfile, "\n" if $debug; # content of file becomes replacement value (use recursion), 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),": $!"; - $replacement=flatten(read_file_with_encoding(File::Spec->catfile($dirname,$fname), $encoding), $preamble,$filename,$encoding) or die "Couldn't find file ",File::Spec->catfile($dirname,$fname),": $!"; - $replacement = remove_endinput($replacement); - # \include always starts a new page; use explicit \newpage command to simulate this - $newpage=(defined($3)? " \\newpage " : "") ; + if ( -f $fullfile ) { + # If file exists, replace input or include command with expanded input + $replacement=flatten(read_file_with_encoding($fullfile, $encoding), $preamble,$filename,$encoding) or die "Could not open file ",$fullfile,": $!"; + $replacement = remove_endinput($replacement); + # \include always starts a new page; use explicit \newpage command to simulate this + $newpage=(defined($4)? " \\newpage " : "") ; + } else { + # if file does not exist, do not expand include or input command (do not warn if fname contains #[0-9] as it is then likely part of a command definition + # and is not meant to be expanded directly + print STDERR "WARNING: Could not find included file ",$fullfile,". I will continue but not expand |$2|\n" unless $fname =~ m(#[0-9]) ; + $replacement = $2 ; # i.e. just the original command again -> make no change file does not exist + $newpage=""; + } "$begline$newpage$replacement$newpage"; }/exgm; + # replace bibliography with bbl file if it exists $text=~s/(^(?:[^%\n]|\\%)*)\\bibliography\{(.*?)\}/{ if ( -f $bblfile ){ @@ -1747,23 +1854,61 @@ sub flatten { $begline=(defined($1)? $1 : "") ; "$begline$replacement"; }/exgm; + # replace subfile with contents (subfile package) $text=~s/(^(?:[^%\n]|\\%)*)\\subfile\{(.*?)\}/{ $begline=(defined($1)? $1 : "") ; $fname = $2; - # # add tex extension unless there is a three letter extension already - $fname .= ".tex" unless $fname =~ m|\.\w{3}|; + # # add tex extension unless there is a three or four letter extension already + $fname .= ".tex" unless $fname =~ m|\.\w{3,4}|; print STDERR "Include file as subfile $fname\n" if $verbose; # content of file becomes replacement value (use recursion) # now strip away everything outside and including \begin{document} and \end{document} pair# # # note: no checking for comments is made - $subfile=read_file_with_encoding(File::Spec->catfile($dirname,$fname), $encoding) or die "Couldn't find file ",File::Spec->catfile($dirname,$fname),": $!"; - ($subpreamble,$subbody,$subpost)=splitdoc($subfile,'\\\\begin\{document\}','\\\\end\{document\}'); - $replacement=flatten($subbody, $preamble,$filename,$encoding); - $replacement = remove_endinput($replacement); + $fullfile=File::Spec->catfile($dirname,$fname); + if ( -f $fullfile) { + # if file exists, expand \subfile command by contents of file + $subfile=read_file_with_encoding($fullfile,$encoding) or die "Could not open included subfile ",$fullfile,": $!"; + ($subpreamble,$subbody,$subpost)=splitdoc($subfile,'\\\\begin\{document\}','\\\\end\{document\}'); + ### $subfile=~s|^.*\\begin{document}||s; + ### $subfile=~s|\\end{document}.*$||s; + $replacement=flatten($subbody, $preamble,$filename,$encoding); + ### $replacement = remove_endinput($replacement); + } else { + # if file does not exist, do not expand subfile + print STDERR "WARNING: Could not find subfile ",$fullfile,". I will continue but not expand |$2|\n" unless $fname =~ m(#[0-9]) ; + $replacement = "\\subfile\{$2\}" ; # i.e. just the original command again -> make no change file does not exist + } + "$begline$replacement"; }/exgm; + # replace \verbatiminput and \lstlistinginput + $text=~s/(^(?:[^%\n]|\\%)*)\\(verbatiminput\*?|lstinputlisting)$extraspace(\[$brat_n\])?$extraspace\{(.*?)\}/{ + $begline=(defined($1)? $1 : "") ; + $command = $2 ; + $fname = $4 ; + $verboptions = defined($3)? $3 : "" ; + if ($command eq 'verbatiminput' ) { + $verbenv = "verbatim" ; + } elsif ($command eq 'verbatiminput*' ) { + $verbenv = "verbatim*" ; + } elsif ($command eq 'lstinputlisting' ) { + $verbenv = "lstlisting" ; + } else { + die "Internal errorL Unexpected verbatim input type $command.\n"; + } + print STDERR "DEBUG Beg of line match |$begline|\n" if $debug ; + print STDERR "Include file $fname verbatim\n" if $verbose; + print STDERR "DEBUG looking for file ",File::Spec->catfile($dirname,$fname), "\n" if $debug; + # content of file becomes replacement value (do not use recursion), 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),": $!"; + $replacement=read_file_with_encoding(File::Spec->catfile($dirname,$fname), $encoding) or die "Couldn't find file ",File::Spec->catfile($dirname,$fname),": $!"; + # Add a new line if it not already there (note that the matching operator needs to use different delimiters, as we are still inside an outer scope that takes precedence + $replacement .= "\n" unless $replacement =~ m(\n$) ; + "$begline\\begin{$verbenv}$verboptions\n$replacement\\end{$verbenv}\n"; + }/exgm; + return($text); } @@ -1778,17 +1923,29 @@ sub print_regex_arr { } -# @lines=extrapream($type) -# reads line from appendix (end of file after __END__ token) +# @lines=extrapream($type,...) +# reads line from appendix or external file +# (end of file after __END__ token) +# if $type is a filename, it will read the file instead of reading from the appendix +# otherwise it will screen appendix for line "%DIF $TYPE" and copy everything up to line +# '%DIF END $TYPE' (where $TYPE is upcased version of $type) +# extrapream('-nofail',$type) will---instead of failing---simply return nothing if +# it does not find the matching line in a appendix (do not use -nofail option with multiple types!) sub extrapream { - my $type; + my @types=@_; + my ($type,$arg); + my $nofail=0; ###my @retval=("%DIF PREAMBLE EXTENSION ADDED BY LATEXDIFF") ; my @retval=(); my ($copy); - while (@_) { + foreach $arg ( @types ) { + if ( $arg eq '-nofail' ) { + $nofail=1; + next; + } + $type=$arg; $copy=0; - $type=shift ; if ( -f $type || lc $type eq '/dev/null' ) { open (FILE,$type) or die "Cannot open preamble file $type: $!"; print STDERR "Reading preamble file $type\n" if $verbose ; @@ -1800,25 +1957,31 @@ sub extrapream { push (@retval,"$_ %DIF PREAMBLE"); } } - } - else { # not (-f $type) - $type=uc($type); # upcase argument - print STDERR "Preamble Internal Type $type\n" if $verbose; - while () { - 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 - } + } else { # not (-f $type) + $type=uc($type); # upcase argument + print STDERR "Preamble Internal Type $type\n" if $verbose; + # save filehandle position (before first read this points to line after __END__) + # but seek DATA,0,0 resets it to the beginning of the file + # see https://stackoverflow.com/questions/4459601/how-can-i-use-data-twice + my $data_start = tell DATA; + while () { + if ( m/^%DIF $type/ ) { + $copy=1; + } elsif ( m/^%DIF END $type/ ) { + last; + } + chomp; + push (@retval,"$_ %DIF PREAMBLE") if $copy; + } + if ( $copy == 0 ) { + unless ($nofail) { + print STDERR "\nPreamble style $type not implemented.\n"; + print STDERR "Write latexdiff -h to get help with available styles\n"; + exit(2); + } + } + seek DATA,$data_start,0; # rewind DATA handle to beginning of data record + } } ###push (@retval,"%DIF END PREAMBLE EXTENSION ADDED BY LATEXDIFF") ; return @retval; @@ -2003,7 +2166,7 @@ sub pass1 { # print STDERR "Unchanged block $cnt, $last1,$last2 \n"; if ($cnt < $MINWORDSBLOCK && $cnt==scalar ( - grep { /^$wpat/ || ( /^\\((?:[`'^"~=.]|[\w\d@*]+))((?:\[$brat0\]|\{$pat_n\})*)/o + grep { /^$wpat/ || ( /^\\((?:[`'^"~=.]|[\w\d@*]+))((?:\[$brat_n\]|\{$pat_n\})*)/o && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL) && scalar(@dummy=split(" ",$2))<3 ) } @$block) ) { @@ -2036,8 +2199,10 @@ sub pass1 { $addtextblocks = extracttextblocks($addblock); $addblkcnt++ if scalar @$addblock; + # make a list of all TEXTCMDLIST commands in deleted and added blocks $delcmds = extractcommands($delblock); $addcmds = extractcommands($addblock); + # now find those text commands, which are found in both deleted and added blocks, and expand them # 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 @@ -2135,7 +2300,7 @@ sub extracttextblocks { 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}\{$pat_n\})*)/o + if ($token =~ /$wpat/ || ( $token =~/^\\((?:[`'^"~=.]|[\w\d@\*]+))((?:${extraspace}\[$brat_n\]${extraspace}|${extraspace}\{$pat_n\})*)/o && iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL) && !iscmd($1,\@TEXTCMDLIST,\@TEXTCMDEXCL))) { # we have text or a command which can be treated as text @@ -2189,7 +2354,7 @@ sub extractcommands { # $2: \cmd # $3: last argument # $4: } + trailing spaces - if ( ( $token =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat_n\})*${extraspace}\{)($pat_n)(\}\s*)$/so ) + if ( ( $token =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat_n\]|${extraspace}\{$pat_n\})*${extraspace}\{)($pat_n)(\}\s*)$/so ) && iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL) ) { # push(@$retval,[ $2,$index,$1,$3,$4 ]); ($cmd,$open,$mid,$closing) = ($2,$1,$3,$4) ; @@ -2206,16 +2371,20 @@ sub extractcommands { sub iscmd { my ($cmd,$regexar,$regexexcl)=@_; my ($ret)=0; + print STDERR "DEBUG: iscmd($cmd)=" if $debug; foreach $pat ( @$regexar ) { if ( $cmd =~ m/^${pat}$/ ) { $ret=1 ; last; } } + print STDERR "0\n" if ($debug && !$ret) ; return 0 unless $ret; foreach $pat ( @$regexexcl ) { + print STDERR "0\n" if ( $debug && $cmd =~ m/^${pat}$/) ; return 0 if ( $cmd =~ m/^${pat}$/ ); } + print STDERR "1\n" if $debug; return 1; } @@ -2328,7 +2497,7 @@ sub marktags { } # negative lookahead pattern (?!) in second clause is put in to avoid matching \( .. \) patterns # also note that second pattern will match \\ - ### print STDERR "DEBUG marktags: Considering word |$word|\n"; + print STDERR "DEBUG marktags: Considering word |$word|\n" if $debug; if ( $word =~ /^[&{}\[\]]/ || ( $word =~ /^\\(?!\()(\\|[`'^"~=.]|[\w*@]+)/ && !iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL)) ) { ###print STDERR "DEBUG MARKTAGS is a non-safe command ($1)\n" if $debug; ### if ( $word =~ /^[&{}\[\]]/ || ( $word =~ /^\\([\w*@\\% ]+)/ && !iscmd($1,\@SAFECMDLIST,\@SAFECMDEXCL)) ) { @@ -2342,7 +2511,7 @@ sub marktags { # $3: last argument # $4: } + trailing spaces ### pre-0.3 if ( ( $token =~ m/^(\\([\w\d\*]+)(?:\[$brat0\]|\{$pat_n\})*\{)($pat_n)(\}\s*)$/so ) - if ( ( $word =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat0\]|${extraspace}\{$pat_n\})*${extraspace}\{)($pat_n)(\}\s*)$/so ) + if ( ( $word =~ m/^(\\([\w\d\*]+)(?:${extraspace}\[$brat_n\]|${extraspace}\{$pat_n\})*${extraspace}\{)($pat_n)(\}\s*)$/so ) && (iscmd($2,\@TEXTCMDLIST,\@TEXTCMDEXCL)|| iscmd($2,\@MATHTEXTCMDLIST,\@MATHTEXTCMDEXCL)) && ( !$cmdcomment || !iscmd($2,\@CONTEXT2CMDLIST, \@CONTEXT2CMDEXCL) ) ) { # Condition 1: word is a command? - if yes, $1,$2,.. will be set as above @@ -2483,7 +2652,7 @@ sub take_comments_and_enter_from_frac() { # 8. a. Convert $$ $$ into \begin{DOLLARDOLLAR} \end{DOLLARDOLLAR} # b. Convert \[ \] into \begin{SQUAREBRACKET} \end{SQUAREBRACKET} # 9. Convert all picture environmentent (\begin{PICTUREENV} .. \end{PICTUREENV} \PICTUREBLOCKenv -# For --block-math-markup option -convert all \begin{MATH} .. \end{MATH} +# For math-mode COARSE,WHOLE or NONE option -convert all \begin{MATH} .. \end{MATH} # into \MATHBLOCKmath{...} commands, where MATH/math is any valid math environment # 10. 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. @@ -2500,9 +2669,9 @@ sub preprocess { # transform \lstinline{...} # s/\\lstinline(\[$brat0\])?(\{(?:.*?)\})/"\\DIFlstinline". $1 ."{". tohash(\%verbhash,"$2") ."}"/esg; # s/\\lstinline(\[$brat0\])?((\S).*?\2)/"\\DIFlstinline". $1 ."{". tohash(\%verbhash,"$2") ."}"/esg; - s/\\lstinline((?:\[$brat0\])?)(\{(?:.*?)\})/"\\DIFlstinline". $1 ."{". tohash(\%verbhash,"$2") ."}"/esg; - s/\\lstinline((?:\[$brat0\])?)((\S).*?\3)/"\\DIFlstinline". $1 ."{". tohash(\%verbhash,"$2") ."}"/esg; - s/\\(verb\*?|lstinline)(\S)(.*?)\2/"\\DIF${1}{". tohash(\%verbhash,"${2}${3}${2}") ."}"/esg; + s/\\lstinline((?:\[$brat_n\])?)(\{(?:.*?)\})/"\\DIFlstinline". $1 ."{". tohash(\%verbhash,"$2") ."}"/esg; + s/\\lstinline((?:\[$brat_n\])?)(([^\s\w]).*?\3)/"\\DIFlstinline". $1 ."{". tohash(\%verbhash,"$2") ."}"/esg; + s/\\(verb\*?|lstinline)([^\s\w])(.*?)\2/"\\DIF${1}{". tohash(\%verbhash,"${2}${3}${2}") ."}"/esg; # Change \{ to \QLEFTBRACE, \} to \QRIGHTBRACE, and \& to \AMPERSAND s/(? to file latexdiff.debug.