Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/mod/wiki/diff/diff_nwiki.php
Go to the documentation of this file.
00001 <?php
00002 
00003 
00004 # See diff.doc
00005 
00006 // A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3)
00007 //
00008 // Copyright (C) 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
00009 // You may copy this code freely under the conditions of the GPL.
00010 //
00011 
00012 define('USE_ASSERTS_IN_WIKI', function_exists('assert'));
00013 
00014 class _WikiDiffOp {
00015     var $type;
00016     var $orig;
00017     var $closing;
00018 
00019     function reverse() {
00020         trigger_error("pure virtual", E_USER_ERROR);
00021     }
00022 
00023     function norig() {
00024         return $this->orig ? sizeof($this->orig) : 0;
00025     }
00026 
00027     function nclosing() {
00028         return $this->closing ? sizeof($this->closing) : 0;
00029     }
00030 }
00031 
00032 class _WikiDiffOp_Copy extends _WikiDiffOp {
00033     var $type = 'copy';
00034 
00035     function _WikiDiffOp_Copy ($orig, $closing = false) {
00036         if (!is_array($closing))
00037                 $closing = $orig;
00038         $this->orig = $orig;
00039         $this->closing = $closing;
00040     }
00041 
00042     function reverse() {
00043         return new _WikiDiffOp_Copy($this->closing, $this->orig);
00044     }
00045 }
00046 
00047 class _WikiDiffOp_Delete extends _WikiDiffOp {
00048     var $type = 'delete';
00049 
00050     function _WikiDiffOp_Delete ($lines) {
00051         $this->orig = $lines;
00052         $this->closing = false;
00053     }
00054 
00055     function reverse() {
00056         return new _WikiDiffOp_Add($this->orig);
00057     }
00058 }
00059 
00060 class _WikiDiffOp_Add extends _WikiDiffOp {
00061     var $type = 'add';
00062 
00063     function _WikiDiffOp_Add ($lines) {
00064         $this->closing = $lines;
00065         $this->orig = false;
00066     }
00067 
00068     function reverse() {
00069         return new _WikiDiffOp_Delete($this->closing);
00070     }
00071 }
00072 
00073 class _WikiDiffOp_Change extends _WikiDiffOp {
00074     var $type = 'change';
00075 
00076     function _WikiDiffOp_Change ($orig, $closing) {
00077         $this->orig = $orig;
00078         $this->closing = $closing;
00079     }
00080 
00081     function reverse() {
00082         return new _WikiDiffOp_Change($this->closing, $this->orig);
00083     }
00084 }
00085 
00086 
00107 class _WikiDiffEngine
00108 {
00109     function diff ($from_lines, $to_lines) {
00110         $n_from = sizeof($from_lines);
00111         $n_to = sizeof($to_lines);
00112 
00113         $this->xchanged = $this->ychanged = array();
00114         $this->xv = $this->yv = array();
00115         $this->xind = $this->yind = array();
00116         unset($this->seq);
00117         unset($this->in_seq);
00118         unset($this->lcs);
00119 
00120         // Skip leading common lines.
00121         for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
00122                 if ($from_lines[$skip] != $to_lines[$skip])
00123                         break;
00124                 $this->xchanged[$skip] = $this->ychanged[$skip] = false;
00125         }
00126         // Skip trailing common lines.
00127         $xi = $n_from; $yi = $n_to;
00128         for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
00129                 if ($from_lines[$xi] != $to_lines[$yi])
00130                         break;
00131                 $this->xchanged[$xi] = $this->ychanged[$yi] = false;
00132         }
00133 
00134         // Ignore lines which do not exist in both files.
00135         for ($xi = $skip; $xi < $n_from - $endskip; $xi++)
00136                 $xhash[$from_lines[$xi]] = 1;
00137         for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
00138                 $line = $to_lines[$yi];
00139                 if ( ($this->ychanged[$yi] = empty($xhash[$line])) )
00140                         continue;
00141                 $yhash[$line] = 1;
00142                 $this->yv[] = $line;
00143                 $this->yind[] = $yi;
00144         }
00145         for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
00146                 $line = $from_lines[$xi];
00147                 if ( ($this->xchanged[$xi] = empty($yhash[$line])) )
00148                         continue;
00149                 $this->xv[] = $line;
00150                 $this->xind[] = $xi;
00151         }
00152 
00153         // Find the LCS.
00154         $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv));
00155 
00156         // Merge edits when possible
00157         $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
00158         $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
00159 
00160         // Compute the edit operations.
00161         $edits = array();
00162         $xi = $yi = 0;
00163         while ($xi < $n_from || $yi < $n_to) {
00164                 USE_ASSERTS_IN_WIKI && assert($yi < $n_to || $this->xchanged[$xi]);
00165                 USE_ASSERTS_IN_WIKI && assert($xi < $n_from || $this->ychanged[$yi]);
00166 
00167                 // Skip matching "snake".
00168                 $copy = array();
00169                 while ( $xi < $n_from && $yi < $n_to
00170                                 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
00171                         $copy[] = $from_lines[$xi++];
00172                         ++$yi;
00173                 }
00174                 if ($copy)
00175                         $edits[] = new _WikiDiffOp_Copy($copy);
00176 
00177                 // Find deletes & adds.
00178                 $delete = array();
00179                 while ($xi < $n_from && $this->xchanged[$xi])
00180                         $delete[] = $from_lines[$xi++];
00181 
00182                 $add = array();
00183                 while ($yi < $n_to && $this->ychanged[$yi])
00184                         $add[] = $to_lines[$yi++];
00185 
00186                 if ($delete && $add)
00187                         $edits[] = new _WikiDiffOp_Change($delete, $add);
00188                 elseif ($delete)
00189                         $edits[] = new _WikiDiffOp_Delete($delete);
00190                 elseif ($add)
00191                         $edits[] = new _WikiDiffOp_Add($add);
00192         }
00193         return $edits;
00194     }
00195 
00196 
00197     /* Divide the Largest Common Subsequence (LCS) of the sequences
00198      * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
00199      * sized segments.
00200      *
00201      * Returns (LCS, PTS).      LCS is the length of the LCS. PTS is an
00202      * array of NCHUNKS+1 (X, Y) indexes giving the diving points between
00203      * sub sequences.  The first sub-sequence is contained in [X0, X1),
00204      * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on.  Note
00205      * that (X0, Y0) == (XOFF, YOFF) and
00206      * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).
00207      *
00208      * This function assumes that the first lines of the specified portions
00209      * of the two files do not match, and likewise that the last lines do not
00210      * match.  The caller must trim matching lines from the beginning and end
00211      * of the portions it is going to specify.
00212      */
00213     function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) {
00214     $flip = false;
00215 
00216     if ($xlim - $xoff > $ylim - $yoff) {
00217         // Things seems faster (I'm not sure I understand why)
00218                 // when the shortest sequence in X.
00219                 $flip = true;
00220         list ($xoff, $xlim, $yoff, $ylim)
00221         = array( $yoff, $ylim, $xoff, $xlim);
00222         }
00223 
00224     if ($flip)
00225         for ($i = $ylim - 1; $i >= $yoff; $i--)
00226         $ymatches[$this->xv[$i]][] = $i;
00227     else
00228         for ($i = $ylim - 1; $i >= $yoff; $i--)
00229         $ymatches[$this->yv[$i]][] = $i;
00230 
00231     $this->lcs = 0;
00232     $this->seq[0]= $yoff - 1;
00233     $this->in_seq = array();
00234     $ymids[0] = array();
00235 
00236     $numer = $xlim - $xoff + $nchunks - 1;
00237     $x = $xoff;
00238     for ($chunk = 0; $chunk < $nchunks; $chunk++) {
00239         if ($chunk > 0)
00240         for ($i = 0; $i <= $this->lcs; $i++)
00241                 $ymids[$i][$chunk-1] = $this->seq[$i];
00242 
00243         $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
00244         for ( ; $x < $x1; $x++) {
00245                         $line = $flip ? $this->yv[$x] : $this->xv[$x];
00246                         if (empty($ymatches[$line]))
00247                 continue;
00248         $matches = $ymatches[$line];
00249                         reset($matches);
00250         while (list ($junk, $y) = each($matches))
00251                 if (empty($this->in_seq[$y])) {
00252                 $k = $this->_lcs_pos($y);
00253                 USE_ASSERTS_IN_WIKI && assert($k > 0);
00254                 $ymids[$k] = $ymids[$k-1];
00255                 break;
00256                                 }
00257         while (list ($junk, $y) = each($matches)) {
00258                 if ($y > $this->seq[$k-1]) {
00259                 USE_ASSERTS_IN_WIKI && assert($y < $this->seq[$k]);
00260                 // Optimization: this is a common case:
00261                 //      next match is just replacing previous match.
00262                 $this->in_seq[$this->seq[$k]] = false;
00263                 $this->seq[$k] = $y;
00264                 $this->in_seq[$y] = 1;
00265                                 }
00266                 else if (empty($this->in_seq[$y])) {
00267                 $k = $this->_lcs_pos($y);
00268                 USE_ASSERTS_IN_WIKI && assert($k > 0);
00269                 $ymids[$k] = $ymids[$k-1];
00270                                 }
00271                         }
00272                 }
00273         }
00274 
00275     $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
00276     $ymid = $ymids[$this->lcs];
00277     for ($n = 0; $n < $nchunks - 1; $n++) {
00278         $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);
00279         $y1 = $ymid[$n] + 1;
00280         $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
00281         }
00282     $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
00283 
00284     return array($this->lcs, $seps);
00285     }
00286 
00287     function _lcs_pos ($ypos) {
00288     $end = $this->lcs;
00289     if ($end == 0 || $ypos > $this->seq[$end]) {
00290         $this->seq[++$this->lcs] = $ypos;
00291         $this->in_seq[$ypos] = 1;
00292         return $this->lcs;
00293         }
00294 
00295     $beg = 1;
00296     while ($beg < $end) {
00297         $mid = (int)(($beg + $end) / 2);
00298         if ( $ypos > $this->seq[$mid] )
00299         $beg = $mid + 1;
00300         else
00301         $end = $mid;
00302         }
00303 
00304     USE_ASSERTS_IN_WIKI && assert($ypos != $this->seq[$end]);
00305 
00306     $this->in_seq[$this->seq[$end]] = false;
00307     $this->seq[$end] = $ypos;
00308     $this->in_seq[$ypos] = 1;
00309     return $end;
00310     }
00311 
00312     /* Find LCS of two sequences.
00313      *
00314      * The results are recorded in the vectors $this->{x,y}changed[], by
00315      * storing a 1 in the element for each line that is an insertion
00316      * or deletion (ie. is not in the LCS).
00317      *
00318      * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
00319      *
00320      * Note that XLIM, YLIM are exclusive bounds.
00321      * All line numbers are origin-0 and discarded lines are not counted.
00322      */
00323     function _compareseq ($xoff, $xlim, $yoff, $ylim) {
00324     // Slide down the bottom initial diagonal.
00325     while ($xoff < $xlim && $yoff < $ylim
00326                    && $this->xv[$xoff] == $this->yv[$yoff]) {
00327         ++$xoff;
00328         ++$yoff;
00329         }
00330 
00331     // Slide up the top initial diagonal.
00332     while ($xlim > $xoff && $ylim > $yoff
00333                    && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
00334         --$xlim;
00335         --$ylim;
00336         }
00337 
00338     if ($xoff == $xlim || $yoff == $ylim)
00339         $lcs = 0;
00340     else {
00341         // This is ad hoc but seems to work well.
00342         //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
00343         //$nchunks = max(2,min(8,(int)$nchunks));
00344         $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
00345         list ($lcs, $seps)
00346         = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
00347         }
00348 
00349     if ($lcs == 0) {
00350         // X and Y sequences have no common subsequence:
00351         // mark all changed.
00352         while ($yoff < $ylim)
00353         $this->ychanged[$this->yind[$yoff++]] = 1;
00354         while ($xoff < $xlim)
00355         $this->xchanged[$this->xind[$xoff++]] = 1;
00356         }
00357     else {
00358         // Use the partitions to split this problem into subproblems.
00359         reset($seps);
00360         $pt1 = $seps[0];
00361         while ($pt2 = next($seps)) {
00362         $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
00363         $pt1 = $pt2;
00364                 }
00365         }
00366     }
00367 
00368     /* Adjust inserts/deletes of identical lines to join changes
00369      * as much as possible.
00370      *
00371      * We do something when a run of changed lines include a
00372      * line at one end and has an excluded, identical line at the other.
00373      * We are free to choose which identical line is included.
00374      * `compareseq' usually chooses the one at the beginning,
00375      * but usually it is cleaner to consider the following identical line
00376      * to be the "change".
00377      *
00378      * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
00379      */
00380     function _shift_boundaries ($lines, &$changed, $other_changed) {
00381     $i = 0;
00382     $j = 0;
00383 
00384     USE_ASSERTS_IN_WIKI && assert('sizeof($lines) == sizeof($changed)');
00385     $len = sizeof($lines);
00386     $other_len = sizeof($other_changed);
00387 
00388     while (1) {
00389         /*
00390          * Scan forwards to find beginning of another run of changes.
00391          * Also keep track of the corresponding point in the other file.
00392          *
00393          * Throughout this code, $i and $j are adjusted together so that
00394          * the first $i elements of $changed and the first $j elements
00395          * of $other_changed both contain the same number of zeros
00396          * (unchanged lines).
00397          * Furthermore, $j is always kept so that $j == $other_len or
00398          * $other_changed[$j] == false.
00399          */
00400         while ($j < $other_len && $other_changed[$j])
00401         $j++;
00402 
00403         while ($i < $len && ! $changed[$i]) {
00404         USE_ASSERTS_IN_WIKI && assert('$j < $other_len && ! $other_changed[$j]');
00405         $i++; $j++;
00406         while ($j < $other_len && $other_changed[$j])
00407                 $j++;
00408                 }
00409 
00410         if ($i == $len)
00411         break;
00412 
00413         $start = $i;
00414 
00415         // Find the end of this run of changes.
00416         while (++$i < $len && $changed[$i])
00417         continue;
00418 
00419         do {
00420         /*
00421          * Record the length of this run of changes, so that
00422          * we can later determine whether the run has grown.
00423          */
00424         $runlength = $i - $start;
00425 
00426         /*
00427          * Move the changed region back, so long as the
00428          * previous unchanged line matches the last changed one.
00429          * This merges with previous changed regions.
00430          */
00431         while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
00432                 $changed[--$start] = 1;
00433                 $changed[--$i] = false;
00434                 while ($start > 0 && $changed[$start - 1])
00435                 $start--;
00436                 USE_ASSERTS_IN_WIKI && assert('$j > 0');
00437                 while ($other_changed[--$j])
00438                 continue;
00439                 USE_ASSERTS_IN_WIKI && assert('$j >= 0 && !$other_changed[$j]');
00440                         }
00441 
00442         /*
00443          * Set CORRESPONDING to the end of the changed run, at the last
00444          * point where it corresponds to a changed run in the other file.
00445          * CORRESPONDING == LEN means no such point has been found.
00446          */
00447         $corresponding = $j < $other_len ? $i : $len;
00448 
00449         /*
00450          * Move the changed region forward, so long as the
00451          * first changed line matches the following unchanged one.
00452          * This merges with following changed regions.
00453          * Do this second, so that if there are no merges,
00454          * the changed region is moved forward as far as possible.
00455          */
00456         while ($i < $len && $lines[$start] == $lines[$i]) {
00457                 $changed[$start++] = false;
00458                 $changed[$i++] = 1;
00459                 while ($i < $len && $changed[$i])
00460                 $i++;
00461 
00462                 USE_ASSERTS_IN_WIKI && assert('$j < $other_len && ! $other_changed[$j]');
00463                 $j++;
00464                 if ($j < $other_len && $other_changed[$j]) {
00465                 $corresponding = $i;
00466                 while ($j < $other_len && $other_changed[$j])
00467                         $j++;
00468                                 }
00469                         }
00470                 } while ($runlength != $i - $start);
00471 
00472         /*
00473          * If possible, move the fully-merged run of changes
00474          * back to a corresponding run in the other file.
00475          */
00476         while ($corresponding < $i) {
00477         $changed[--$start] = 1;
00478         $changed[--$i] = 0;
00479         USE_ASSERTS_IN_WIKI && assert('$j > 0');
00480         while ($other_changed[--$j])
00481                 continue;
00482         USE_ASSERTS_IN_WIKI && assert('$j >= 0 && !$other_changed[$j]');
00483                 }
00484         }
00485     }
00486 }
00487 
00491 class WikiDiff
00492 {
00493     var $edits;
00494 
00503     function WikiDiff($from_lines, $to_lines) {
00504         $eng = new _WikiDiffEngine;
00505         $this->edits = $eng->diff($from_lines, $to_lines);
00506         //$this->_check($from_lines, $to_lines);
00507     }
00508 
00519     function reverse () {
00520     $rev = $this;
00521         $rev->edits = array();
00522         foreach ($this->edits as $edit) {
00523                 $rev->edits[] = $edit->reverse();
00524         }
00525     return $rev;
00526     }
00527 
00533     function isEmpty () {
00534         foreach ($this->edits as $edit) {
00535                 if ($edit->type != 'copy')
00536                         return false;
00537         }
00538         return true;
00539     }
00540 
00548     function lcs () {
00549     $lcs = 0;
00550         foreach ($this->edits as $edit) {
00551                 if ($edit->type == 'copy')
00552                         $lcs += sizeof($edit->orig);
00553         }
00554     return $lcs;
00555     }
00556 
00565     function orig() {
00566         $lines = array();
00567 
00568         foreach ($this->edits as $edit) {
00569                 if ($edit->orig)
00570                         array_splice($lines, sizeof($lines), 0, $edit->orig);
00571         }
00572         return $lines;
00573     }
00574 
00583     function closing() {
00584         $lines = array();
00585 
00586         foreach ($this->edits as $edit) {
00587                 if ($edit->closing)
00588                         array_splice($lines, sizeof($lines), 0, $edit->closing);
00589         }
00590         return $lines;
00591     }
00592 
00598     function _check ($from_lines, $to_lines) {
00599         if (serialize($from_lines) != serialize($this->orig()))
00600                 trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
00601         if (serialize($to_lines) != serialize($this->closing()))
00602                 trigger_error("Reconstructed closing doesn't match", E_USER_ERROR);
00603 
00604         $rev = $this->reverse();
00605         if (serialize($to_lines) != serialize($rev->orig()))
00606                 trigger_error("Reversed original doesn't match", E_USER_ERROR);
00607         if (serialize($from_lines) != serialize($rev->closing()))
00608                 trigger_error("Reversed closing doesn't match", E_USER_ERROR);
00609 
00610 
00611         $prevtype = 'none';
00612         foreach ($this->edits as $edit) {
00613                 if ( $prevtype == $edit->type )
00614                         trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
00615                 $prevtype = $edit->type;
00616         }
00617 
00618         $lcs = $this->lcs();
00619         trigger_error("WikiDiff okay: LCS = $lcs", E_USER_NOTICE);
00620     }
00621 }
00622 
00627 class MappedWikiDiff
00628 extends WikiDiff
00629 {
00653     function MappedWikiDiff($from_lines, $to_lines,
00654                                         $mapped_from_lines, $mapped_to_lines) {
00655 
00656         assert(sizeof($from_lines) == sizeof($mapped_from_lines));
00657         assert(sizeof($to_lines) == sizeof($mapped_to_lines));
00658 
00659         $this->WikiDiff($mapped_from_lines, $mapped_to_lines);
00660 
00661         $xi = $yi = 0;
00662         for ($i = 0; $i < sizeof($this->edits); $i++) {
00663                 $orig = &$this->edits[$i]->orig;
00664                 if (is_array($orig)) {
00665                         $orig = array_slice($from_lines, $xi, sizeof($orig));
00666                         $xi += sizeof($orig);
00667                 }
00668 
00669                 $closing = &$this->edits[$i]->closing;
00670                 if (is_array($closing)) {
00671                         $closing = array_slice($to_lines, $yi, sizeof($closing));
00672                         $yi += sizeof($closing);
00673                 }
00674         }
00675     }
00676 }
00677 
00685 class WikiDiffFormatter
00686 {
00693     var $leading_context_lines = 0;
00694 
00701     var $trailing_context_lines = 0;
00702 
00709     function format($diff) {
00710 
00711         $xi = $yi = 1;
00712         $block = false;
00713         $context = array();
00714 
00715         $nlead = $this->leading_context_lines;
00716         $ntrail = $this->trailing_context_lines;
00717 
00718         $this->_start_diff();
00719 
00720         foreach ($diff->edits as $edit) {
00721                 if ($edit->type == 'copy') {
00722                         if (is_array($block)) {
00723                                 if (sizeof($edit->orig) <= $nlead + $ntrail) {
00724                                         $block[] = $edit;
00725                                 }
00726                                 else{
00727                                         if ($ntrail) {
00728                                                 $context = array_slice($edit->orig, 0, $ntrail);
00729                                                 $block[] = new _WikiWikiDiffOp_Copy($context);
00730                                         }
00731                                         $this->_block($x0, $ntrail + $xi - $x0,
00732                                                                   $y0, $ntrail + $yi - $y0,
00733                                                                   $block);
00734                                         $block = false;
00735                                 }
00736                         }
00737                         $context = $edit->orig;
00738                 }
00739                 else {
00740                         if (! is_array($block)) {
00741                                 $context = array_slice($context, sizeof($context) - $nlead);
00742                                 $x0 = $xi - sizeof($context);
00743                                 $y0 = $yi - sizeof($context);
00744                                 $block = array();
00745                                 if ($context)
00746                                         $block[] = new _WikiWikiDiffOp_Copy($context);
00747                         }
00748                         $block[] = $edit;
00749                 }
00750 
00751                 if ($edit->orig)
00752                         $xi += sizeof($edit->orig);
00753                 if ($edit->closing)
00754                         $yi += sizeof($edit->closing);
00755         }
00756 
00757         if (is_array($block))
00758                 $this->_block($x0, $xi - $x0,
00759                                           $y0, $yi - $y0,
00760                                           $block);
00761 
00762         return $this->_end_diff();
00763     }
00764 
00765     function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
00766         $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
00767         foreach ($edits as $edit) {
00768                 if ($edit->type == 'copy')
00769                         $this->_context($edit->orig);
00770                 elseif ($edit->type == 'add')
00771                         $this->_added($edit->closing);
00772                 elseif ($edit->type == 'delete')
00773                         $this->_deleted($edit->orig);
00774                 elseif ($edit->type == 'change')
00775                         $this->_changed($edit->orig, $edit->closing);
00776                 else
00777                         trigger_error("Unknown edit type", E_USER_ERROR);
00778         }
00779         $this->_end_block();
00780     }
00781 
00782     function _start_diff() {
00783         ob_start();
00784     }
00785 
00786     function _end_diff() {
00787         $val = ob_get_contents();
00788         ob_end_clean();
00789         return $val;
00790     }
00791 
00792     function _block_header($xbeg, $xlen, $ybeg, $ylen) {
00793         if ($xlen > 1)
00794                 $xbeg .= "," . ($xbeg + $xlen - 1);
00795         if ($ylen > 1)
00796                 $ybeg .= "," . ($ybeg + $ylen - 1);
00797 
00798         return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
00799     }
00800 
00801     function _start_block($header) {
00802         echo $header;
00803     }
00804 
00805     function _end_block() {
00806     }
00807 
00808     function _lines($lines, $prefix = ' ') {
00809         foreach ($lines as $line)
00810                 echo "$prefix $line\n";
00811     }
00812 
00813     function _context($lines) {
00814         $this->_lines($lines);
00815     }
00816 
00817     function _added($lines) {
00818         $this->_lines($lines, ">");
00819     }
00820     function _deleted($lines) {
00821         $this->_lines($lines, "<");
00822     }
00823 
00824     function _changed($orig, $closing) {
00825         $this->_deleted($orig);
00826         echo "---\n";
00827         $this->_added($closing);
00828     }
00829 }
00830 
00831 
00837 define('NBSP', '&#160;');                       // iso-8859-x non-breaking space.
00838 
00839 class _WikiHWLDF_WordAccumulator {
00840     function _WikiHWLDF_WordAccumulator () {
00841         $this->_lines = array();
00842         $this->_line = '';
00843         $this->_group = '';
00844         $this->_tag = '';
00845     }
00846 
00847     function _flushGroup ($new_tag) {
00848         if ($this->_group !== '') {
00849       if ($this->_tag == 'mark')
00850                 $this->_line .= '<span class="diffchange">'.$this->_group.'</span>';
00851       else
00852         $this->_line .= $this->_group;
00853     }
00854         $this->_group = '';
00855         $this->_tag = $new_tag;
00856     }
00857 
00858     function _flushLine ($new_tag) {
00859         $this->_flushGroup($new_tag);
00860         if ($this->_line != '')
00861                 $this->_lines[] = $this->_line;
00862         $this->_line = '';
00863     }
00864 
00865     function addWords ($words, $tag = '') {
00866         if ($tag != $this->_tag)
00867                 $this->_flushGroup($tag);
00868 
00869         foreach ($words as $word) {
00870                 // new-line should only come as first char of word.
00871                 if ($word == '')
00872                         continue;
00873                 if ($word[0] == "\n") {
00874                         $this->_group .= NBSP;
00875                         $this->_flushLine($tag);
00876                         $word = substr($word, 1);
00877                 }
00878                 assert(!strstr($word, "\n"));
00879                 $this->_group .= $word;
00880         }
00881     }
00882 
00883     function getLines() {
00884         $this->_flushLine('~done');
00885         return $this->_lines;
00886     }
00887 }
00888 
00889 class WordLevelWikiDiff extends MappedWikiDiff
00890 {
00891     function WordLevelWikiDiff ($orig_lines, $closing_lines) {
00892         list ($orig_words, $orig_stripped) = $this->_split($orig_lines);
00893         list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
00894 
00895 
00896         $this->MappedWikiDiff($orig_words, $closing_words,
00897                                           $orig_stripped, $closing_stripped);
00898     }
00899 
00900     function _split($lines) {
00901         // FIXME: fix POSIX char class.
00902 #                if (!preg_match_all('/ ( [^\S\n]+ | [[:alnum:]]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
00903         if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
00904                                                 implode("\n", $lines),
00905                                                 $m)) {
00906                 return array(array(''), array(''));
00907         }
00908         return array($m[0], $m[1]);
00909     }
00910 
00911     function orig () {
00912         $orig = new _WikiHWLDF_WordAccumulator;
00913 
00914         foreach ($this->edits as $edit) {
00915                 if ($edit->type == 'copy')
00916                         $orig->addWords($edit->orig);
00917                 elseif ($edit->orig)
00918                         $orig->addWords($edit->orig, 'mark');
00919         }
00920         return $orig->getLines();
00921     }
00922 
00923     function closing () {
00924         $closing = new _WikiHWLDF_WordAccumulator;
00925 
00926         foreach ($this->edits as $edit) {
00927                 if ($edit->type == 'copy')
00928                         $closing->addWords($edit->closing);
00929                 elseif ($edit->closing)
00930                         $closing->addWords($edit->closing, 'mark');
00931         }
00932         return $closing->getLines();
00933     }
00934 }
00935 
00939 class TableWikiDiffFormatter extends WikiDiffFormatter
00940 {
00941     var $htmltable = array();
00942     
00943     function TableWikiDiffFormatter() {
00944         $this->leading_context_lines = 2;
00945         $this->trailing_context_lines = 2;
00946     }
00947     
00948     function _block_header( $xbeg, $xlen, $ybeg, $ylen) {
00949       
00950     }
00951     
00952     function _start_block ($header) {
00953 
00954     }
00955     
00956     function _end_block() {
00957 
00958     }
00959     
00960     function _lines($lines, $prefix=' ', $color="white") {
00961         
00962     }
00963     
00964     function _added($lines) {
00965         global $htmltable;
00966         foreach ($lines as $line) {
00967                 $htmltable[] = array('','+','<div class="wiki_diffadd">'.$line.'</div>');
00968         }
00969     }
00970 
00971     function _deleted($lines) {
00972         global $htmltable;
00973         foreach ($lines as $line) {
00974                 $htmltable[] = array('<div class="wiki_diffdel">'.$line.'</div>','-','');
00975         }
00976     }
00977     
00978     function _context($lines) {
00979         global $htmltable;
00980         foreach ($lines as $line) {
00981                 $htmltable[] = array($line,'',$line);
00982         }
00983     }
00984     
00985     function _changed( $orig, $closing ) {
00986         global $htmltable;
00987         $diff = new WordLevelWikiDiff( $orig, $closing );
00988         $del = $diff->orig();
00989         $add = $diff->closing();
00990 
00991         while ( $line = array_shift( $del ) ) {
00992                 $aline = array_shift( $add );
00993                 $htmltable[] = array('<div class="wiki_diffdel">'.$line.'</div>','-','<div class="wiki_diffadd">'.$aline.'</div>');
00994         }
00995         $this->_added( $add ); # If any leftovers
00996     }
00997     
00998     function get_result() {
00999         global $htmltable;
01000         return $htmltable;
01001     }
01002 
01003 }
01004 
01005 
01010 class TableWikiDiffFormatterOld extends WikiDiffFormatter
01011 {
01012     function TableWikiDiffFormatter() {
01013         $this->leading_context_lines = 2;
01014         $this->trailing_context_lines = 2;
01015     }
01016 
01017     function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
01018         $l1 = wfMsg( "lineno", $xbeg );
01019         $l2 = wfMsg( "lineno", $ybeg );
01020 
01021         $r = '<tr><td colspan="2" align="left"><strong>'.$l1."</strong></td>\n" .
01022           '<td colspan="2" align="left"><strong>'.$l2."</strong></td></tr>\n";
01023         return $r;
01024     }
01025 
01026     function _start_block( $header ) {
01027         global $wgOut;
01028         $wgOut->addHTML( $header );
01029     }
01030 
01031     function _end_block() {
01032     }
01033 
01034     function _lines( $lines, $prefix=' ', $color="white" ) {
01035     }
01036 
01037     function addedLine( $line ) {
01038         return '<td>+</td><td class="diff-addedline">' .
01039           $line.'</td>';
01040     }
01041 
01042     function deletedLine( $line ) {
01043         return '<td>-</td><td class="diff-deletedline">' .
01044           $line.'</td>';
01045     }
01046 
01047     function emptyLine() {
01048         return '<td colspan="2">&nbsp;</td>';
01049     }
01050 
01051     function contextLine( $line ) {
01052         return '<td> </td><td class="diff-context">'.$line.'</td>';
01053     }
01054 
01055     function _added($lines) {
01056         global $wgOut;
01057         foreach ($lines as $line) {
01058                 $wgOut->addHTML( '<tr>' . $this->emptyLine() .
01059                   $this->addedLine( $line ) . "</tr>\n" );
01060         }
01061     }
01062 
01063     function _deleted($lines) {
01064         global $wgOut;
01065         foreach ($lines as $line) {
01066                 $wgOut->addHTML( '<tr>' . $this->deletedLine( $line ) .
01067                   $this->emptyLine() . "</tr>\n" );
01068         }
01069     }
01070 
01071     function _context( $lines ) {
01072         global $wgOut;
01073         foreach ($lines as $line) {
01074                 $wgOut->addHTML( '<tr>' . $this->contextLine( $line ) .
01075                   $this->contextLine( $line ) . "</tr>\n" );
01076         }
01077     }
01078 
01079     function _changed( $orig, $closing ) {
01080         global $wgOut;
01081         $diff = new WordLevelWikiDiff( $orig, $closing );
01082         $del = $diff->orig();
01083         $add = $diff->closing();
01084 
01085         while ( $line = array_shift( $del ) ) {
01086                 $aline = array_shift( $add );
01087                 $wgOut->addHTML( '<tr>' . $this->deletedLine( $line ) .
01088                   $this->addedLine( $aline ) . "</tr>\n" );
01089         }
01090         $this->_added( $add ); # If any leftovers
01091     }
01092 }
01093 
 All Data Structures Namespaces Files Functions Variables Enumerations