00001 <?php
00002
# See diff.doc
00003
00004 class DifferenceEngine {
00005 var
$mOldid,
$mNewid;
00006 var
$mOldtitle,
$mNewtitle;
00007 var
$mOldtext,
$mNewtext;
00008 var
$mOldUser,
$mNewUser;
00009 var
$mOldComment,
$mNewComment;
00010 var
$mOldPage,
$mNewPage;
00011
00012 function
DifferenceEngine( $old, $
new )
00013 {
00014 $this->mOldid = $old;
00015 $this->mNewid = $new;
00016 }
00017
00018 function
showDiffPage()
00019 {
00020 global
$wgUser,
$wgTitle,
$wgOut,
$wgLang;
00021 $fname =
"DifferenceEngine::showDiffPage";
00022
wfProfileIn( $fname );
00023
00024 $t =
$wgTitle->getPrefixedText() .
" (Diff: {$this->mOldid}, " .
00025
"{$this->mNewid})";
00026 $mtext =
wfMsg(
"missingarticle", $t );
00027
00028
$wgOut->setArticleFlag(
false );
00029
if ( ! $this->
loadText() ) {
00030
$wgOut->setPagetitle(
wfMsg(
"errorpagetitle" ) );
00031
$wgOut->addHTML( $mtext );
00032
wfProfileOut( $fname );
00033
return;
00034 }
00035
$wgOut->suppressQuickbar();
00036
00037 $oldTitle = $this->mOldPage->getPrefixedText();
00038 $newTitle = $this->mNewPage->getPrefixedText();
00039
if( $oldTitle == $newTitle ) {
00040
$wgOut->setPageTitle( $newTitle );
00041 }
else {
00042
$wgOut->setPageTitle( $oldTitle .
", " . $newTitle );
00043 }
00044
$wgOut->setSubtitle(
wfMsg(
"difference" ) );
00045
$wgOut->setRobotpolicy(
"noindex,follow" );
00046
00047
if ( !( $this->mOldPage->userCanRead() && $this->mNewPage->userCanRead() ) ) {
00048
$wgOut->loginToUse();
00049
$wgOut->output();
00050
wfProfileOut( $fname );
00051
exit;
00052 }
00053
00054 $sk =
$wgUser->getSkin();
00055 $talk =
$wgLang->getNsText(
NS_TALK );
00056 $contribs =
wfMsg(
"contribslink" );
00057
00058
00059 $this->mOldComment = $sk->formatComment($this->mOldComment);
00060 $this->mNewComment = $sk->formatComment($this->mNewComment);
00061
00062
00063 $oldUserLink = $sk->makeLinkObj( Title::makeTitle(
NS_USER, $this->mOldUser ), $this->mOldUser );
00064 $newUserLink = $sk->makeLinkObj( Title::makeTitle(
NS_USER, $this->mNewUser ), $this->mNewUser );
00065 $oldUTLink = $sk->makeLinkObj( Title::makeTitle(
NS_USER_TALK, $this->mOldUser ), $talk );
00066 $newUTLink = $sk->makeLinkObj( Title::makeTitle(
NS_USER_TALK, $this->mNewUser ), $talk );
00067 $oldContribs = $sk->makeKnownLinkObj( Title::makeTitle(
NS_SPECIAL,
"Contributions" ), $contribs,
00068
"target=" . urlencode($this->mOldUser) );
00069 $newContribs = $sk->makeKnownLinkObj( Title::makeTitle(
NS_SPECIAL,
"Contributions" ), $contribs,
00070
"target=" . urlencode($this->mNewUser) );
00071
if ( !$this->mNewid &&
$wgUser->isSysop() ) {
00072 $rollback =
" <strong>[" . $sk->makeKnownLinkObj( $wgTitle,
wfMsg(
"rollbacklink" ),
00073
"action=rollback&from=" . urlencode($this->mNewUser) ) .
"]</strong>";
00074 }
else {
00075 $rollback =
"";
00076 }
00077
00078 $oldHeader =
"<strong>{$this->mOldtitle}</strong><br />$oldUserLink ($oldUTLink | $oldContribs)<br />".
00079 $this->mOldComment;
00080 $newHeader =
"<strong>{$this->mNewtitle}</strong><br />$newUserLink ($newUTLink | $newContribs) $rollback<br />".
00081 $this->mNewComment;
00082
00083
DifferenceEngine::showDiff( $this->mOldtext, $this->mNewtext,
00084 $oldHeader, $newHeader );
00085
$wgOut->addHTML(
"<hr /><h2>{$this->mNewtitle}</h2>\n" );
00086
$wgOut->addWikiText( $this->mNewtext );
00087
00088
wfProfileOut( $fname );
00089 }
00090
00091 function
showDiff( $otext, $ntext, $otitle, $ntitle )
00092 {
00093 global
$wgOut;
00094
00095 $ota = explode(
"\n", str_replace(
"\r\n",
"\n",
00096 htmlspecialchars( $otext ) ) );
00097 $nta = explode(
"\n", str_replace(
"\r\n",
"\n",
00098 htmlspecialchars( $ntext ) ) );
00099
00100
$wgOut->addHTML(
"<table border='0' width='98%'
00101
cellpadding='0' cellspacing='4px' class='diff'><tr>
00102
<td colspan='2' width='50%' align='center' class='diff-otitle'>
00103
{$otitle}</td>
00104
<td colspan='2' width='50%' align='center' class='diff-ntitle'>
00105
{$ntitle}</td>
00106
</tr>\n" );
00107
00108 $diffs =
new Diff( $ota, $nta );
00109 $formatter =
new TableDiffFormatter();
00110 $formatter->format( $diffs );
00111
$wgOut->addHTML(
"</table>\n" );
00112 }
00113
00114
# Load the text of the articles to compare. If newid is 0, then compare
00115
# the old article in oldid to the current article; if oldid is 0, then
00116
# compare the current article to the immediately previous one (ignoring
00117
# the value of newid).
00118
#
00119 function
loadText()
00120 {
00121 global
$wgTitle,
$wgOut,
$wgLang,
$wgIsMySQL,
$wgIsPg;
00122 $fname =
"DifferenceEngine::loadText";
00123
00124 $oldtable=
$wgIsPg?'
"old"':'old';
00125
if ( 0 == $this->mNewid || 0 == $this->mOldid ) {
00126
$wgOut->setArticleFlag(
true );
00127 $this->mNewtitle =
wfMsg(
"currentrev" );
00128 $id =
$wgTitle->getArticleID();
00129
00130
$sql =
"SELECT cur_text, cur_user_text, cur_comment FROM cur WHERE cur_id={$id}";
00131
$res =
wfQuery( $sql,
DB_READ, $fname );
00132
if ( 0 ==
wfNumRows( $res ) ) {
return false; }
00133
00134
$s =
wfFetchObject( $res );
00135 $this->mNewPage = &
$wgTitle;
00136 $this->mNewtext =
$s->cur_text;
00137 $this->mNewUser =
$s->cur_user_text;
00138 $this->mNewComment =
$s->cur_comment;
00139 }
else {
00140
$sql =
"SELECT old_namespace,old_title,old_timestamp,old_text,old_flags,old_user_text,old_comment FROM $oldtable WHERE " .
00141
"old_id={$this->mNewid}";
00142
00143
$res =
wfQuery( $sql,
DB_READ, $fname );
00144
if ( 0 ==
wfNumRows( $res ) ) {
return false; }
00145
00146
$s =
wfFetchObject( $res );
00147 $this->mNewtext = Article::getRevisionText( $s );
00148
00149 $t =
$wgLang->timeanddate( $s->old_timestamp,
true );
00150 $this->mNewPage = Title::MakeTitle( $s->old_namespace, $s->old_title );
00151 $this->mNewtitle =
wfMsg(
"revisionasof", $t );
00152 $this->mNewUser =
$s->old_user_text;
00153 $this->mNewComment =
$s->old_comment;
00154 }
00155
if ( 0 == $this->mOldid ) {
00156 $use_index=
$wgIsMySQL?
"USE INDEX (name_title_timestamp)":
"";
00157
$sql =
"SELECT old_namespace,old_title,old_timestamp,old_text,old_flags,old_user_text,old_comment " .
00158
"FROM $oldtable $use_index WHERE " .
00159
"old_namespace=" . $this->mNewPage->getNamespace() .
" AND " .
00160
"old_title='" .
wfStrencode( $this->mNewPage->getDBkey() ) .
00161
"' ORDER BY inverse_timestamp LIMIT 1";
00162
$res =
wfQuery( $sql,
DB_READ, $fname );
00163 }
else {
00164
$sql =
"SELECT old_namespace,old_title,old_timestamp,old_text,old_flags,old_user_text,old_comment FROM $oldtable WHERE " .
00165
"old_id={$this->mOldid}";
00166
$res =
wfQuery( $sql,
DB_READ, $fname );
00167 }
00168
if ( 0 ==
wfNumRows( $res ) ) {
return false; }
00169
00170
$s =
wfFetchObject( $res );
00171 $this->mOldPage = Title::MakeTitle( $s->old_namespace, $s->old_title );
00172 $this->mOldtext = Article::getRevisionText( $s );
00173
00174 $t =
$wgLang->timeanddate( $s->old_timestamp,
true );
00175 $this->mOldtitle =
wfMsg(
"revisionasof", $t );
00176 $this->mOldUser =
$s->old_user_text;
00177 $this->mOldComment =
$s->old_comment;
00178
00179
return true;
00180 }
00181 }
00182
00183
00184
00185
00186
00187
00188
00189 define('USE_ASSERTS', function_exists('assert'));
00190
00191 class _DiffOp {
00192 var
$type;
00193 var
$orig;
00194 var
$closing;
00195
00196 function
reverse() {
00197 trigger_error(
"pure virtual", E_USER_ERROR);
00198 }
00199
00200 function
norig() {
00201
return $this->orig ?
sizeof($this->orig) : 0;
00202 }
00203
00204 function
nclosing() {
00205
return $this->closing ?
sizeof($this->closing) : 0;
00206 }
00207 }
00208
00209 class _DiffOp_Copy extends _DiffOp {
00210 var
$type = 'copy';
00211
00212 function
_DiffOp_Copy ($orig, $closing =
false) {
00213
if (!is_array($closing))
00214 $closing = $orig;
00215 $this->orig = $orig;
00216 $this->closing = $closing;
00217 }
00218
00219 function
reverse() {
00220
return new _DiffOp_Copy($this->closing, $this->orig);
00221 }
00222 }
00223
00224 class _DiffOp_Delete extends _DiffOp {
00225 var
$type = '
delete';
00226
00227 function
_DiffOp_Delete ($lines) {
00228 $this->orig =
$lines;
00229 $this->closing =
false;
00230 }
00231
00232 function
reverse() {
00233
return new _DiffOp_Add($this->orig);
00234 }
00235 }
00236
00237 class _DiffOp_Add extends _DiffOp {
00238 var
$type = 'add';
00239
00240 function
_DiffOp_Add ($lines) {
00241 $this->closing =
$lines;
00242 $this->orig =
false;
00243 }
00244
00245 function
reverse() {
00246
return new _DiffOp_Delete($this->closing);
00247 }
00248 }
00249
00250 class _DiffOp_Change extends _DiffOp {
00251 var
$type = 'change';
00252
00253 function
_DiffOp_Change ($orig, $closing) {
00254 $this->orig = $orig;
00255 $this->closing = $closing;
00256 }
00257
00258 function
reverse() {
00259
return new _DiffOp_Change($this->closing, $this->orig);
00260 }
00261 }
00262
00263
00284 class _DiffEngine
00285 {
00286 function
diff ($from_lines, $to_lines) {
00287 $n_from =
sizeof($from_lines);
00288 $n_to =
sizeof($to_lines);
00289
00290 $this->xchanged = $this->ychanged = array();
00291 $this->xv = $this->yv = array();
00292 $this->xind = $this->yind = array();
00293 unset($this->seq);
00294 unset($this->in_seq);
00295 unset($this->lcs);
00296
00297
00298
for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
00299
if ($from_lines[$skip] != $to_lines[$skip])
00300
break;
00301 $this->xchanged[$skip] = $this->ychanged[$skip] =
false;
00302 }
00303
00304 $xi = $n_from; $yi = $n_to;
00305
for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
00306
if ($from_lines[$xi] != $to_lines[$yi])
00307
break;
00308 $this->xchanged[$xi] = $this->ychanged[$yi] =
false;
00309 }
00310
00311
00312
for ($xi = $skip; $xi < $n_from - $endskip; $xi++)
00313 $xhash[$from_lines[$xi]] = 1;
00314
for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
00315
$line = $to_lines[$yi];
00316
if ( ($this->ychanged[$yi] = empty($xhash[$line])) )
00317
continue;
00318 $yhash[
$line] = 1;
00319 $this->yv[] =
$line;
00320 $this->yind[] = $yi;
00321 }
00322
for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
00323
$line = $from_lines[$xi];
00324
if ( ($this->xchanged[$xi] = empty($yhash[$line])) )
00325
continue;
00326 $this->xv[] =
$line;
00327 $this->xind[] = $xi;
00328 }
00329
00330
00331 $this->
_compareseq(0,
sizeof($this->xv), 0,
sizeof($this->yv));
00332
00333
00334 $this->
_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
00335 $this->
_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
00336
00337
00338 $edits = array();
00339 $xi = $yi = 0;
00340
while ($xi < $n_from || $yi < $n_to) {
00341
USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
00342
USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
00343
00344
00345 $copy = array();
00346
while ( $xi < $n_from && $yi < $n_to
00347 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
00348 $copy[] = $from_lines[$xi++];
00349 ++$yi;
00350 }
00351
if ($copy)
00352 $edits[] =
new _DiffOp_Copy($copy);
00353
00354
00355 $delete = array();
00356
while ($xi < $n_from && $this->xchanged[$xi])
00357 $delete[] = $from_lines[$xi++];
00358
00359 $add = array();
00360
while ($yi < $n_to && $this->ychanged[$yi])
00361 $add[] = $to_lines[$yi++];
00362
00363
if ($delete && $add)
00364 $edits[] =
new _DiffOp_Change($
delete, $add);
00365 elseif ($
delete)
00366 $edits[] =
new _DiffOp_Delete($
delete);
00367 elseif ($add)
00368 $edits[] =
new _DiffOp_Add($add);
00369 }
00370
return $edits;
00371 }
00372
00373
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390 function
_diag ($xoff, $xlim, $yoff, $ylim, $nchunks) {
00391 $flip =
false;
00392
00393
if ($xlim - $xoff > $ylim - $yoff) {
00394
00395
00396 $flip =
true;
00397 list ($xoff, $xlim, $yoff, $ylim)
00398 = array( $yoff, $ylim, $xoff, $xlim);
00399 }
00400
00401
if ($flip)
00402
for (
$i = $ylim - 1;
$i >= $yoff;
$i--)
00403 $ymatches[$this->xv[
$i]][] =
$i;
00404
else
00405
for (
$i = $ylim - 1;
$i >= $yoff;
$i--)
00406 $ymatches[$this->yv[
$i]][] =
$i;
00407
00408 $this->lcs = 0;
00409 $this->seq[0]= $yoff - 1;
00410 $this->in_seq = array();
00411 $ymids[0] = array();
00412
00413 $numer = $xlim - $xoff + $nchunks - 1;
00414 $x = $xoff;
00415
for ($chunk = 0; $chunk < $nchunks; $chunk++) {
00416
if ($chunk > 0)
00417
for (
$i = 0;
$i <= $this->lcs;
$i++)
00418 $ymids[
$i][$chunk-1] = $this->seq[
$i];
00419
00420 $x1 = $xoff + (
int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
00421
for ( ; $x < $x1; $x++) {
00422
$line = $flip ? $this->yv[$x] : $this->xv[$x];
00423
if (empty($ymatches[$line]))
00424
continue;
00425 $matches = $ymatches[
$line];
00426 reset($matches);
00427
while (list ($junk, $y) = each($matches))
00428
if (empty($this->in_seq[$y])) {
00429 $k = $this->
_lcs_pos($y);
00430
USE_ASSERTS && assert($k > 0);
00431 $ymids[$k] = $ymids[$k-1];
00432
break;
00433 }
00434
while (list ($junk, $y) = each($matches)) {
00435
if ($y > $this->seq[$k-1]) {
00436
USE_ASSERTS && assert($y < $this->seq[$k]);
00437
00438
00439 $this->in_seq[$this->seq[$k]] =
false;
00440 $this->seq[$k] = $y;
00441 $this->in_seq[$y] = 1;
00442 }
00443
else if (empty($this->in_seq[$y])) {
00444 $k = $this->
_lcs_pos($y);
00445
USE_ASSERTS && assert($k > 0);
00446 $ymids[$k] = $ymids[$k-1];
00447 }
00448 }
00449 }
00450 }
00451
00452 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
00453 $ymid = $ymids[$this->lcs];
00454
for (
$n = 0;
$n < $nchunks - 1;
$n++) {
00455 $x1 = $xoff + (
int)(($numer + ($xlim - $xoff) *
$n) / $nchunks);
00456 $y1 = $ymid[
$n] + 1;
00457 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
00458 }
00459 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
00460
00461
return array($this->lcs, $seps);
00462 }
00463
00464 function
_lcs_pos ($ypos) {
00465 $end = $this->lcs;
00466
if ($end == 0 || $ypos > $this->seq[$end]) {
00467 $this->seq[++$this->lcs] = $ypos;
00468 $this->in_seq[$ypos] = 1;
00469
return $this->lcs;
00470 }
00471
00472 $beg = 1;
00473
while ($beg < $end) {
00474 $mid = (
int)(($beg + $end) / 2);
00475
if ( $ypos > $this->seq[$mid] )
00476 $beg = $mid + 1;
00477
else
00478 $end = $mid;
00479 }
00480
00481
USE_ASSERTS && assert($ypos != $this->seq[$end]);
00482
00483 $this->in_seq[$this->seq[$end]] =
false;
00484 $this->seq[$end] = $ypos;
00485 $this->in_seq[$ypos] = 1;
00486
return $end;
00487 }
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497
00498
00499
00500 function
_compareseq ($xoff, $xlim, $yoff, $ylim) {
00501
00502
while ($xoff < $xlim && $yoff < $ylim
00503 && $this->xv[$xoff] == $this->yv[$yoff]) {
00504 ++$xoff;
00505 ++$yoff;
00506 }
00507
00508
00509
while ($xlim > $xoff && $ylim > $yoff
00510 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
00511 --$xlim;
00512 --$ylim;
00513 }
00514
00515
if ($xoff == $xlim || $yoff == $ylim)
00516 $lcs = 0;
00517
else {
00518
00519
00520
00521 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
00522 list ($lcs, $seps)
00523 = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
00524 }
00525
00526
if ($lcs == 0) {
00527
00528
00529
while ($yoff < $ylim)
00530 $this->ychanged[$this->yind[$yoff++]] = 1;
00531
while ($xoff < $xlim)
00532 $this->xchanged[$this->xind[$xoff++]] = 1;
00533 }
00534
else {
00535
00536 reset($seps);
00537 $pt1 = $seps[0];
00538
while ($pt2 = next($seps)) {
00539 $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
00540 $pt1 = $pt2;
00541 }
00542 }
00543 }
00544
00545
00546
00547
00548
00549
00550
00551
00552
00553
00554
00555
00556
00557 function
_shift_boundaries ($lines, &$changed, $other_changed) {
00558
$i = 0;
00559 $j = 0;
00560
00561
USE_ASSERTS && assert('
sizeof($lines) ==
sizeof($changed)');
00562 $len =
sizeof(
$lines);
00563 $other_len =
sizeof($other_changed);
00564
00565
while (1) {
00566
00567
00568
00569
00570
00571
00572
00573
00574
00575
00576
00577
while ($j < $other_len && $other_changed[$j])
00578 $j++;
00579
00580
while (
$i < $len && ! $changed[
$i]) {
00581
USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
00582
$i++; $j++;
00583
while ($j < $other_len && $other_changed[$j])
00584 $j++;
00585 }
00586
00587
if (
$i == $len)
00588
break;
00589
00590 $start =
$i;
00591
00592
00593
while (++
$i < $len && $changed[
$i])
00594
continue;
00595
00596
do {
00597
00598
00599
00600
00601 $runlength =
$i - $start;
00602
00603
00604
00605
00606
00607
00608
while ($start > 0 &&
$lines[$start - 1] ==
$lines[
$i - 1]) {
00609 $changed[--$start] = 1;
00610 $changed[--
$i] =
false;
00611
while ($start > 0 && $changed[$start - 1])
00612 $start--;
00613
USE_ASSERTS && assert('$j > 0');
00614
while ($other_changed[--$j])
00615
continue;
00616
USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
00617 }
00618
00619
00620
00621
00622
00623
00624 $corresponding = $j < $other_len ?
$i : $len;
00625
00626
00627
00628
00629
00630
00631
00632
00633
while (
$i < $len &&
$lines[$start] ==
$lines[
$i]) {
00634 $changed[$start++] =
false;
00635 $changed[
$i++] = 1;
00636
while (
$i < $len && $changed[
$i])
00637
$i++;
00638
00639
USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
00640 $j++;
00641
if ($j < $other_len && $other_changed[$j]) {
00642 $corresponding =
$i;
00643
while ($j < $other_len && $other_changed[$j])
00644 $j++;
00645 }
00646 }
00647 }
while ($runlength !=
$i - $start);
00648
00649
00650
00651
00652
00653
while ($corresponding <
$i) {
00654 $changed[--$start] = 1;
00655 $changed[--
$i] = 0;
00656
USE_ASSERTS && assert('$j > 0');
00657
while ($other_changed[--$j])
00658
continue;
00659
USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
00660 }
00661 }
00662 }
00663 }
00664
00668 class Diff
00669 {
00670 var
$edits;
00671
00680 function
Diff($from_lines, $to_lines) {
00681 $eng =
new _DiffEngine;
00682 $this->edits = $eng->diff($from_lines, $to_lines);
00683
00684 }
00685
00696 function
reverse () {
00697 $rev = $this;
00698 $rev->edits = array();
00699 foreach ($this->edits as $edit) {
00700 $rev->edits[] = $edit->reverse();
00701 }
00702
return $rev;
00703 }
00704
00710 function
isEmpty () {
00711 foreach ($this->edits as $edit) {
00712
if ($edit->type != 'copy')
00713
return false;
00714 }
00715
return true;
00716 }
00717
00725 function
lcs () {
00726 $lcs = 0;
00727 foreach ($this->edits as $edit) {
00728
if ($edit->type == 'copy')
00729 $lcs +=
sizeof($edit->orig);
00730 }
00731
return $lcs;
00732 }
00733
00742 function
orig() {
00743
$lines = array();
00744
00745 foreach ($this->edits as $edit) {
00746
if ($edit->orig)
00747 array_splice($lines,
sizeof($lines), 0, $edit->orig);
00748 }
00749
return $lines;
00750 }
00751
00760 function
closing() {
00761
$lines = array();
00762
00763 foreach ($this->edits as $edit) {
00764
if ($edit->closing)
00765 array_splice($lines,
sizeof($lines), 0, $edit->closing);
00766 }
00767
return $lines;
00768 }
00769
00775 function
_check ($from_lines, $to_lines) {
00776
if (serialize($from_lines) != serialize($this->orig()))
00777 trigger_error(
"Reconstructed original doesn't match", E_USER_ERROR);
00778
if (serialize($to_lines) != serialize($this->closing()))
00779 trigger_error(
"Reconstructed closing doesn't match", E_USER_ERROR);
00780
00781 $rev = $this->
reverse();
00782
if (serialize($to_lines) != serialize($rev->orig()))
00783 trigger_error(
"Reversed original doesn't match", E_USER_ERROR);
00784
if (serialize($from_lines) != serialize($rev->closing()))
00785 trigger_error(
"Reversed closing doesn't match", E_USER_ERROR);
00786
00787
00788 $prevtype = 'none';
00789 foreach ($this->edits as $edit) {
00790
if ( $prevtype == $edit->type )
00791 trigger_error(
"Edit sequence is non-optimal", E_USER_ERROR);
00792 $prevtype = $edit->type;
00793 }
00794
00795 $lcs = $this->
lcs();
00796 trigger_error(
"Diff okay: LCS = $lcs", E_USER_NOTICE);
00797 }
00798 }
00799
00803 class MappedDiff
00804
extends Diff
00805 {
00829 function
MappedDiff($from_lines, $to_lines,
00830 $mapped_from_lines, $mapped_to_lines) {
00831
00832 assert(
sizeof($from_lines) ==
sizeof($mapped_from_lines));
00833 assert(
sizeof($to_lines) ==
sizeof($mapped_to_lines));
00834
00835 $this->
Diff($mapped_from_lines, $mapped_to_lines);
00836
00837 $xi = $yi = 0;
00838
for (
$i = 0;
$i <
sizeof($this->edits);
$i++) {
00839 $orig = &$this->edits[
$i]->orig;
00840
if (is_array($orig)) {
00841 $orig = array_slice($from_lines, $xi,
sizeof($orig));
00842 $xi +=
sizeof($orig);
00843 }
00844
00845 $closing = &$this->edits[
$i]->closing;
00846
if (is_array($closing)) {
00847 $closing = array_slice($to_lines, $yi,
sizeof($closing));
00848 $yi +=
sizeof($closing);
00849 }
00850 }
00851 }
00852 }
00853
00861 class DiffFormatter
00862 {
00869 var
$leading_context_lines = 0;
00870
00877 var
$trailing_context_lines = 0;
00878
00885 function
format($diff) {
00886
00887 $xi = $yi = 1;
00888 $block =
false;
00889 $context = array();
00890
00891 $nlead = $this->leading_context_lines;
00892 $ntrail = $this->trailing_context_lines;
00893
00894 $this->
_start_diff();
00895
00896 foreach ($diff->edits as $edit) {
00897
if ($edit->type == 'copy') {
00898
if (is_array($block)) {
00899
if (
sizeof($edit->orig) <= $nlead + $ntrail) {
00900 $block[] = $edit;
00901 }
00902
else{
00903
if ($ntrail) {
00904 $context = array_slice($edit->orig, 0, $ntrail);
00905 $block[] =
new _DiffOp_Copy($context);
00906 }
00907 $this->
_block($x0, $ntrail + $xi - $x0,
00908 $y0, $ntrail + $yi - $y0,
00909 $block);
00910 $block =
false;
00911 }
00912 }
00913 $context = $edit->orig;
00914 }
00915
else {
00916
if (! is_array($block)) {
00917 $context = array_slice($context,
sizeof($context) - $nlead);
00918 $x0 = $xi -
sizeof($context);
00919 $y0 = $yi -
sizeof($context);
00920 $block = array();
00921
if ($context)
00922 $block[] =
new _DiffOp_Copy($context);
00923 }
00924 $block[] = $edit;
00925 }
00926
00927
if ($edit->orig)
00928 $xi +=
sizeof($edit->orig);
00929
if ($edit->closing)
00930 $yi +=
sizeof($edit->closing);
00931 }
00932
00933
if (is_array($block))
00934 $this->
_block($x0, $xi - $x0,
00935 $y0, $yi - $y0,
00936 $block);
00937
00938
return $this->
_end_diff();
00939 }
00940
00941 function
_block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
00942 $this->
_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
00943 foreach ($edits as $edit) {
00944
if ($edit->type == 'copy')
00945 $this->
_context($edit->orig);
00946 elseif ($edit->type == 'add')
00947 $this->
_added($edit->closing);
00948 elseif ($edit->type == '
delete')
00949 $this->
_deleted($edit->orig);
00950 elseif ($edit->type == 'change')
00951 $this->
_changed($edit->orig, $edit->closing);
00952
else
00953 trigger_error(
"Unknown edit type", E_USER_ERROR);
00954 }
00955 $this->
_end_block();
00956 }
00957
00958 function
_start_diff() {
00959 ob_start();
00960 }
00961
00962 function
_end_diff() {
00963 $val = ob_get_contents();
00964 ob_end_clean();
00965
return $val;
00966 }
00967
00968 function
_block_header($xbeg, $xlen, $ybeg, $ylen) {
00969
if ($xlen > 1)
00970 $xbeg .=
"," . ($xbeg + $xlen - 1);
00971
if ($ylen > 1)
00972 $ybeg .=
"," . ($ybeg + $ylen - 1);
00973
00974
return $xbeg . ($xlen ? ($ylen ?
'c' :
'd') :
'a') . $ybeg;
00975 }
00976
00977 function
_start_block($header) {
00978 echo $header;
00979 }
00980
00981 function
_end_block() {
00982 }
00983
00984 function
_lines($lines, $prefix =
' ') {
00985 foreach ($lines as $line)
00986 echo
"$prefix $line\n";
00987 }
00988
00989 function
_context($lines) {
00990 $this->_lines($lines);
00991 }
00992
00993 function
_added($lines) {
00994 $this->_lines($lines,
">");
00995 }
00996 function
_deleted($lines) {
00997 $this->_lines($lines,
"<");
00998 }
00999
01000 function
_changed($orig, $closing) {
01001 $this->_deleted($orig);
01002 echo
"---\n";
01003 $this->_added($closing);
01004 }
01005 }
01006
01007
01013 define('NBSP',
"\xA0");
01014
01015 class _HWLDF_WordAccumulator {
01016 function
_HWLDF_WordAccumulator () {
01017 $this->_lines = array();
01018 $this->_line = '';
01019 $this->_group = '';
01020 $this->_tag = '';
01021 }
01022
01023 function
_flushGroup ($new_tag) {
01024
if ($this->_group !== '') {
01025
if ($this->_tag == 'mark')
01026 $this->_line .= '<span
class=
"diffchange">'.$this->_group.'</span>';
01027
else
01028 $this->_line .= $this->_group;
01029 }
01030 $this->_group = '';
01031 $this->_tag = $new_tag;
01032 }
01033
01034 function
_flushLine ($new_tag) {
01035 $this->_flushGroup($new_tag);
01036
if ($this->_line != '')
01037 $this->_lines[] = $this->_line;
01038 $this->_line = '';
01039 }
01040
01041 function
addWords ($words, $tag = '') {
01042
if ($tag != $this->_tag)
01043 $this->_flushGroup($tag);
01044
01045 foreach ($words as $word) {
01046
01047
if ($word == '')
01048
continue;
01049
if ($word[0] ==
"\n") {
01050 $this->_group .=
NBSP;
01051 $this->_flushLine($tag);
01052 $word = substr($word, 1);
01053 }
01054 assert(!strstr($word,
"\n"));
01055 $this->_group .= $word;
01056 }
01057 }
01058
01059 function
getLines() {
01060 $this->_flushLine('~done');
01061
return $this->_lines;
01062 }
01063 }
01064
01065
class WordLevelDiff
extends MappedDiff
01066 {
01067 function WordLevelDiff ($orig_lines, $closing_lines) {
01068 list ($orig_words, $orig_stripped) = $this->_split($orig_lines);
01069 list ($closing_words, $closing_stripped) = $this->_split($closing_lines);
01070
01071
01072 $this->
MappedDiff($orig_words, $closing_words,
01073 $orig_stripped, $closing_stripped);
01074 }
01075
01076 function _split($lines) {
01077
01078
# if (!preg_match_all('/ ( [^\S\n]+ | [[:alnum:]]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
01079
if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
01080 implode(
"\n", $lines),
01081 $m)) {
01082
return array(array(''), array(''));
01083 }
01084
return array($m[0], $m[1]);
01085 }
01086
01087 function
orig () {
01088 $orig =
new _HWLDF_WordAccumulator;
01089
01090 foreach ($this->edits as $edit) {
01091
if ($edit->type == 'copy')
01092 $orig->addWords($edit->orig);
01093 elseif ($edit->orig)
01094 $orig->addWords($edit->orig, 'mark');
01095 }
01096 return $orig->getLines();
01097 }
01098
01099 function closing () {
01100 $closing =
new _HWLDF_WordAccumulator;
01101
01102 foreach ($this->edits as $edit) {
01103
if ($edit->type == 'copy')
01104 $closing->addWords($edit->closing);
01105 elseif ($edit->closing)
01106 $closing->addWords($edit->closing, 'mark');
01107 }
01108 return $closing->getLines();
01109 }
01110 }
01111
01116 class TableDiffFormatter extends
DiffFormatter
01117 {
01118 function TableDiffFormatter() {
01119 $this->leading_context_lines = 2;
01120 $this->trailing_context_lines = 2;
01121 }
01122
01123 function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
01124 $l1 =
wfMsg(
"lineno", $xbeg );
01125 $l2 =
wfMsg(
"lineno", $ybeg );
01126
01127 $r = '<tr><td colspan=
"2" align=
"left"><strong>'.$l1.
"</strong></td>\n" .
01128 '<td colspan=
"2" align=
"left"><strong>'.$l2.
"</strong></td></tr>\n";
01129
return $r;
01130 }
01131
01132 function _start_block( $header ) {
01133 global
$wgOut;
01134
$wgOut->addHTML( $header );
01135 }
01136
01137 function _end_block() {
01138 }
01139
01140 function _lines( $lines, $prefix=
' ', $color=
"white" ) {
01141 }
01142
01143 function addedLine( $line ) {
01144
return '<td>+</td><td
class=
"diff-addedline">' .
01145
$line.'</td>';
01146 }
01147
01148 function deletedLine( $line ) {
01149
return '<td>-</td><td
class=
"diff-deletedline">' .
01150
$line.'</td>';
01151 }
01152
01153 function emptyLine() {
01154
return '<td colspan=
"2"> </td>';
01155 }
01156
01157 function contextLine( $line ) {
01158
return '<td> </td><td
class=
"diff-context">'.$line.'</td>';
01159 }
01160
01161 function _added($lines) {
01162 global
$wgOut;
01163 foreach ($lines as $line) {
01164
$wgOut->addHTML( '<tr>' . $this->emptyLine() .
01165 $this->addedLine( $line ) .
"</tr>\n" );
01166 }
01167 }
01168
01169 function _deleted($lines) {
01170 global
$wgOut;
01171 foreach ($lines as $line) {
01172
$wgOut->addHTML( '<tr>' . $this->deletedLine( $line ) .
01173 $this->emptyLine() .
"</tr>\n" );
01174 }
01175 }
01176
01177 function _context( $lines ) {
01178 global
$wgOut;
01179 foreach ($lines as $line) {
01180
$wgOut->addHTML( '<tr>' . $this->contextLine( $line ) .
01181 $this->contextLine( $line ) .
"</tr>\n" );
01182 }
01183 }
01184
01185 function _changed( $orig, $closing ) {
01186 global
$wgOut;
01187 $diff =
new WordLevelDiff( $orig, $closing );
01188 $del = $diff->orig();
01189 $add = $diff->closing();
01190
01191
while (
$line = array_shift( $del ) ) {
01192 $aline = array_shift( $add );
01193
$wgOut->addHTML( '<tr>' . $this->deletedLine( $line ) .
01194 $this->addedLine( $aline ) .
"</tr>\n" );
01195 }
01196 $this->_added( $add ); # If any leftovers
01197 }
01198 }
01199
01200 ?>