Subversion Repositories Applications.papyrus

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
433 ddelon 1
<div class="page">
2
<?php
3
 
4
if ($this->HasAccess("read"))
5
{
6
 
7
/* A php wdiff  (word diff) for wakka, adapted by David Delon
8
   based on wdiff and phpwiki diff (copyright below).
9
   TODO : Since wdiff use only directive lines, all stuff in diff class
10
   related to line and context display should be removed.
11
 
12
   This program is free software; you can redistribute it and/or modify
13
   it under the terms of the GNU General Public License as published by
14
   the Free Software Foundation; either version 2, or (at your option)
15
   any later version. */
16
 
17
 
18
/* A PHP diff engine for phpwiki.
19
 
20
   Copyright (C) 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
21
   You may copy this code freely under the conditions of the GPL.
22
*/
23
 
24
/* wdiff -- front end to diff for comparing on a word per word basis.
25
   Copyright (C) 1992 Free Software Foundation, Inc.
26
   Francois Pinard <pinard@iro.umontreal.ca>.
27
 
28
   This program is free software; you can redistribute it and/or modify
29
   it under the terms of the GNU General Public License as published by
30
   the Free Software Foundation; either version 2, or (at your option)
31
   any later version.
32
 
33
 */
34
 
35
// If asked, call original diff
36
 
37
	if ($_REQUEST["fastdiff"]) {
38
 
39
		/* NOTE: This is a really cheap way to do it. I think it may be more intelligent to write the two pages to temporary files and run /usr/bin/diff over them. Then again, maybe not.        */
40
		// load pages
41
		  $pageA = $this->LoadPageById($_REQUEST["a"]);
42
		  $pageB = $this->LoadPageById($_REQUEST["b"]);
43
 
44
		// prepare bodies
45
		  $bodyA = explode("\n", $pageA["body"]);
46
		  $bodyB = explode("\n", $pageB["body"]);
47
 
48
		  $added = array_diff($bodyA, $bodyB);
49
		  $deleted = array_diff($bodyB, $bodyA);
50
 
51
		  $output .= "<b>Comparaison de <a href=\"".$this->href("", "", "time=".urlencode($pageA["time"]))."\">".$pageA["time"]."</a> à <a href=\"".$this->href("", "", "time=".urlencode($pageB["time"]))."\">".$pageB["time"]."</a></b><br />\n";
52
 
53
		  if ($added)
54
		  {
55
			// remove blank lines
56
			$output .= "<br />\n<b>Additions:</b><br />\n";
57
			$output .= "<div class=\"additions\">".$this->Format(implode("\n", $added))."</div>";
58
		  }
59
 
60
		  if ($deleted)
61
		  {
62
			$output .= "<br />\n<b>Deletions:</b><br />\n";
63
			$output .= "<div class=\"deletions\">".$this->Format(implode("\n", $deleted))."</div>";
64
		  }
65
 
66
		  if (!$added && !$deleted)
67
		  {
68
			$output .= "<br />\nNo differences.";
69
		  }
70
		  print($output);
71
 
72
	}
73
 
74
	else {
75
 
76
	// load pages
77
 
78
		$pageA = $this->LoadPageById($_REQUEST["b"]);
79
		$pageB = $this->LoadPageById($_REQUEST["a"]);
80
 
81
		// extract text from bodies
82
		$textA = $pageA["body"];
83
		$textB = $pageB["body"];
84
 
85
		$sideA = new Side($textA);
86
		$sideB = new Side($textB);
87
 
88
		$bodyA='';
89
		$sideA->split_file_into_words($bodyA);
90
 
91
		$bodyB='';
92
		$sideB->split_file_into_words($bodyB);
93
 
94
		// diff on these two file
95
		$diff = new Diff(split("\n",$bodyA),split("\n",$bodyB));
96
 
97
		// format output
98
		$fmt = new DiffFormatter();
99
 
100
		$sideO = new Side($fmt->format($diff));
101
 
102
		$resync_left=0;
103
		$resync_right=0;
104
 
105
		$count_total_right=$sideB->getposition() ;
106
 
107
		$sideA->init();
108
		$sideB->init();
109
 
110
		$output='';
111
 
112
		  while (1) {
113
 
114
		      $sideO->skip_line();
115
		      if ($sideO->isend()) {
116
			  break;
117
		      }
118
 
119
		      if ($sideO->decode_directive_line()) {
120
			$argument=$sideO->getargument();
121
			$letter=$sideO->getdirective();
122
		      switch ($letter) {
123
			    case 'a':
124
			      $resync_left = $argument[0];
125
			      $resync_right = $argument[2] - 1;
126
			      break;
127
 
128
			    case 'd':
129
			      $resync_left = $argument[0] - 1;
130
			      $resync_right = $argument[2];
131
			      break;
132
 
133
			    case 'c':
134
			      $resync_left = $argument[0] - 1;
135
			      $resync_right = $argument[2] - 1;
136
			      break;
137
 
138
			    }
139
 
140
			    $sideA->skip_until_ordinal($resync_left);
141
			    $sideB->copy_until_ordinal($resync_right,$output);
142
 
143
	// deleted word
144
 
145
			    if (($letter=='d') || ($letter=='c')) {
146
			      $sideA->copy_whitespace($output);
147
			      $output .="@@";
148
			      $sideA->copy_word($output);
149
			      $sideA->copy_until_ordinal($argument[1],$output);
150
			      $output .="@@";
151
			    }
152
 
153
	// inserted word
154
			    if ($letter == 'a' || $letter == 'c') {
155
				$sideB->copy_whitespace($output);
156
				$output .="££";
157
				$sideB->copy_word($output);
158
				$sideB->copy_until_ordinal($argument[3],$output);
159
				$output .="££";
160
			    }
161
 
162
		  }
163
 
164
		}
165
 
166
		  $sideB->copy_until_ordinal($count_total_right,$output);
167
		  $sideB->copy_whitespace($output);
168
		  $out=$this->Format($output);
169
		  print $out;
170
 
171
	}
172
 
173
}
174
else{
175
	print("<i>Vous n'etes pas autoris&eacute; &agrave; lire cette page.</i>");
176
}
177
 
178
	// Side : a string for wdiff
179
 
180
	class Side {
181
	    var $position;
182
	    var $cursor;
183
	    var $content;
184
	    var $character;
185
	    var $directive;
186
	    var $argument;
187
	    var $length;
188
 
189
	    function Side($content) {
190
	       $this->content=$content;
191
	       $this->position=0;
192
	       $this->cursor=0;
193
	       $this->directive='';
194
	       $this->argument=array();
195
	       $this->length=strlen($this->content);
196
	       $this->character=substr($this->content,0,1);
197
 
198
	    }
199
 
200
	    function getposition() {
201
	       return $this->position;
202
	    }
203
 
204
	    function getcharacter() {
205
	       return $this->character;
206
	    }
207
 
208
	    function getdirective() {
209
	       return $this->directive;
210
	    }
211
 
212
	    function getargument() {
213
	       return $this->argument;
214
	    }
215
 
216
	    function nextchar() {
217
	       $this->cursor++;
218
	       $this->character=substr($this->content,$this->cursor,1);
219
	    }
220
 
221
	    function copy_until_ordinal($ordinal,&$out)  {
222
	       while ($this->position < $ordinal) {
223
		  $this->copy_whitespace($out);
224
		  $this->copy_word($out);
225
	       }
226
	    }
227
 
228
	    function skip_until_ordinal($ordinal) {
229
	       while ($this->position < $ordinal) {
230
		  $this->skip_whitespace();
231
		  $this->skip_word();
232
	       }
233
	    }
234
 
235
	    function split_file_into_words (&$out) {
236
	       while (!$this->isend()) {
237
		 $this->skip_whitespace();
238
		 if ($this->isend()) {
239
		   break;
240
		 }
241
		 $this->copy_word($out);
242
		 $out .="\n";
243
	       }
244
	    }
245
 
246
	    function init() {
247
	       $this->position=0;
248
	       $this->cursor=0;
249
	       $this->directive='';
250
	       $this->argument=array();
251
	       $this->character=substr($this->content,0,1);
252
	    }
253
 
254
	    function isspace($char) {
255
	       if (ereg('[[:space:]]',$char)) {
256
		  return true;
257
	       }
258
	       else {
259
		  return false;
260
	       }
261
	    }
262
 
263
	    function isdigit($char) {
264
	       if (ereg('[[:digit:]]',$char)) {
265
		  return true;
266
	       }
267
	       else {
268
		  return false;
269
	       }
270
	    }
271
 
272
	    function isend() {
273
	       if (($this->cursor)>=($this->length)) {
274
		  return true;
275
	       }
276
	       else {
277
		  return false;
278
	       }
279
	    }
280
 
281
 
282
 
283
	    function copy_whitespace(&$out)  {
284
		 while (!$this->isend() && $this->isspace($this->character)) {
285
		    $out .=$this->character;
286
		    $this->nextchar();
287
		 }
288
	    }
289
 
290
	    function skip_whitespace()  {
291
		 while (!$this->isend() && $this->isspace($this->character)) {
292
		    $this->nextchar();
293
		 }
294
	    }
295
 
296
	    function skip_line()  {
297
	      while (!$this->isend() && !$this->isdigit($this->character)) {
298
		  while (!$this->isend() && $this->character!="\n")
299
		  $this->nextchar();
300
		  if($this->character=="\n")
301
		      $this->nextchar();
302
	       }
303
	     }
304
 
305
 
306
 
307
	     function copy_word(&$out) {
308
		 while (!$this->isend() && !($this->isspace($this->character))) {
309
		    $out.=$this->character;
310
		    $this->nextchar();
311
		 }
312
		 $this->position++;
313
	     }
314
 
315
	     function skip_word() {
316
 
317
		 while (!$this->isend() && !($this->isspace($this->character))) {
318
		    $this->nextchar();
319
		 }
320
		 $this->position++;
321
	     }
322
 
323
 
324
	 function decode_directive_line() {
325
 
326
	  $value=0;
327
	  $state=0;
328
	  $error=0;
329
 
330
	  while (!$error && $state < 4) {
331
	      if ($this->isdigit($this->character)) {
332
		  $value = 0;
333
		  while($this->isdigit($this->character)) {
334
		      $value = 10 * $value + $this->character - '0';
335
		      $this->nextchar();
336
		  }
337
	      }
338
	      else if ($state != 1 && $state != 3)
339
	      $error = 1;
340
 
341
	      /* Assign the proper value.  */
342
 
343
	      $this->argument[$state] = $value;
344
 
345
	      /* Skip the following character.  */
346
 
347
	      switch ($state) {
348
		case 0:
349
		case 2:
350
		  if ($this->character == ',')
351
		     $this->nextchar();
352
		  break;
353
 
354
		case 1:
355
		  if ($this->character == 'a' || $this->character == 'd' || $this->character == 'c') {
356
		      $this->directive = $this->character;
357
		      $this->nextchar();
358
		    }
359
		  else
360
		    $error = 1;
361
		  break;
362
 
363
		  case 3:
364
		  if ($this->character != "\n")
365
		    $error = 1;
366
		    break;
367
		  }
368
		 $state++;
369
	      }
370
 
371
	  /* Complete reading of the line and return success value.  */
372
 
373
	  while ((!$this->isend()) && ($this->character != "\n")) {
374
	      $this->nextchar();
375
	  }
376
	  if ($this->character == "\n")
377
	      $this->nextchar();
378
 
379
	  return !$error;
380
	}
381
 
382
 
383
 
384
	}
385
 
386
	// difflib
387
	//
388
	// A PHP diff engine for phpwiki.
389
	//
390
	// Copyright (C) 2000, 2001 Geoffrey T. Dairiki <dairiki@dairiki.org>
391
	// You may copy this code freely under the conditions of the GPL.
392
	//
393
 
394
 
395
	// PHP3 does not have assert()
396
	define('USE_ASSERTS', function_exists('assert'));
397
 
398
	class _DiffOp {
399
	    var $type;
400
	    var $orig;
401
	    var $final;
402
 
403
 
404
	    function norig() {
405
		return $this->orig ? sizeof($this->orig) : 0;
406
	    }
407
 
408
	    function nfinal() {
409
		return $this->final ? sizeof($this->final) : 0;
410
	    }
411
	}
412
 
413
	class _DiffOp_Copy extends _DiffOp {
414
	    var $type = 'copy';
415
 
416
	    function _DiffOp_Copy ($orig, $final = false) {
417
		if (!is_array($final))
418
		    $final = $orig;
419
		$this->orig = $orig;
420
		$this->final = $final;
421
	    }
422
 
423
	}
424
 
425
	class _DiffOp_Delete extends _DiffOp {
426
	    var $type = 'delete';
427
 
428
	    function _DiffOp_Delete ($lines) {
429
		$this->orig = $lines;
430
		$this->final = false;
431
	    }
432
 
433
	}
434
 
435
	class _DiffOp_Add extends _DiffOp {
436
	    var $type = 'add';
437
 
438
	    function _DiffOp_Add ($lines) {
439
		$this->final = $lines;
440
		$this->orig = false;
441
	    }
442
 
443
	}
444
 
445
	class _DiffOp_Change extends _DiffOp {
446
	    var $type = 'change';
447
 
448
	    function _DiffOp_Change ($orig, $final) {
449
		$this->orig = $orig;
450
		$this->final = $final;
451
	    }
452
 
453
	}
454
 
455
 
456
	/**
457
	 * Class used internally by Diff to actually compute the diffs.
458
	 *
459
	 * The algorithm used here is mostly lifted from the perl module
460
	 * Algorithm::Diff (version 1.06) by Ned Konz, which is available at:
461
	 *   http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip
462
	 *
463
	 * More ideas are taken from:
464
	 *   http://www.ics.uci.edu/~eppstein/161/960229.html
465
	 *
466
	 * Some ideas are (and a bit of code) are from from analyze.c, from GNU
467
	 * diffutils-2.7, which can be found at:
468
	 *   ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz
469
	 *
470
	 * Finally, some ideas (subdivision by NCHUNKS > 2, and some optimizations)
471
	 * are my own.
472
	 *
473
	 * @author Geoffrey T. Dairiki
474
	 * @access private
475
	 */
476
	class _DiffEngine
477
	{
478
	    function diff ($from_lines, $to_lines) {
479
		$n_from = sizeof($from_lines);
480
		$n_to = sizeof($to_lines);
481
 
482
		$this->xchanged = $this->ychanged = array();
483
		$this->xv = $this->yv = array();
484
		$this->xind = $this->yind = array();
485
		unset($this->seq);
486
		unset($this->in_seq);
487
		unset($this->lcs);
488
 
489
		// Skip leading common lines.
490
		for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
491
		    if ($from_lines[$skip] != $to_lines[$skip])
492
			break;
493
		    $this->xchanged[$skip] = $this->ychanged[$skip] = false;
494
		}
495
		// Skip trailing common lines.
496
		$xi = $n_from; $yi = $n_to;
497
		for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {
498
		    if ($from_lines[$xi] != $to_lines[$yi])
499
			break;
500
		    $this->xchanged[$xi] = $this->ychanged[$yi] = false;
501
		}
502
 
503
		// Ignore lines which do not exist in both files.
504
		for ($xi = $skip; $xi < $n_from - $endskip; $xi++)
505
		    $xhash[$from_lines[$xi]] = 1;
506
		for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {
507
		    $line = $to_lines[$yi];
508
		    if ( ($this->ychanged[$yi] = empty($xhash[$line])) )
509
			continue;
510
		    $yhash[$line] = 1;
511
		    $this->yv[] = $line;
512
		    $this->yind[] = $yi;
513
		}
514
		for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {
515
		    $line = $from_lines[$xi];
516
		    if ( ($this->xchanged[$xi] = empty($yhash[$line])) )
517
			continue;
518
		    $this->xv[] = $line;
519
		    $this->xind[] = $xi;
520
		}
521
 
522
		// Find the LCS.
523
		$this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv));
524
 
525
		// Merge edits when possible
526
		$this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged);
527
		$this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged);
528
 
529
		// Compute the edit operations.
530
		$edits = array();
531
		$xi = $yi = 0;
532
		while ($xi < $n_from || $yi < $n_to) {
533
		    USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]);
534
		    USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]);
535
 
536
		    // Skip matching "snake".
537
		    $copy = array();
538
		    while ( $xi < $n_from && $yi < $n_to
539
			    && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {
540
			$copy[] = $from_lines[$xi++];
541
			++$yi;
542
		    }
543
		    if ($copy)
544
			$edits[] = new _DiffOp_Copy($copy);
545
 
546
		    // Find deletes & adds.
547
		    $delete = array();
548
		    while ($xi < $n_from && $this->xchanged[$xi])
549
			$delete[] = $from_lines[$xi++];
550
 
551
		    $add = array();
552
		    while ($yi < $n_to && $this->ychanged[$yi])
553
			$add[] = $to_lines[$yi++];
554
 
555
		    if ($delete && $add)
556
			$edits[] = new _DiffOp_Change($delete, $add);
557
		    elseif ($delete)
558
			$edits[] = new _DiffOp_Delete($delete);
559
		    elseif ($add)
560
			$edits[] = new _DiffOp_Add($add);
561
		}
562
		return $edits;
563
	    }
564
 
565
 
566
	    /* Divide the Largest Common Subsequence (LCS) of the sequences
567
	     * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
568
	     * sized segments.
569
	     *
570
	     * Returns (LCS, PTS).  LCS is the length of the LCS. PTS is an
571
	     * array of NCHUNKS+1 (X, Y) indexes giving the diving points between
572
	     * sub sequences.  The first sub-sequence is contained in [X0, X1),
573
	     * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on.  Note
574
	     * that (X0, Y0) == (XOFF, YOFF) and
575
	     * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).
576
	     *
577
	     * This function assumes that the first lines of the specified portions
578
	     * of the two files do not match, and likewise that the last lines do not
579
	     * match.  The caller must trim matching lines from the beginning and end
580
	     * of the portions it is going to specify.
581
	     */
582
	    function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) {
583
		$flip = false;
584
 
585
		if ($xlim - $xoff > $ylim - $yoff) {
586
		    // Things seems faster (I'm not sure I understand why)
587
		    // when the shortest sequence in X.
588
		    $flip = true;
589
		    list ($xoff, $xlim, $yoff, $ylim)
590
			= array( $yoff, $ylim, $xoff, $xlim);
591
		}
592
 
593
		if ($flip)
594
		    for ($i = $ylim - 1; $i >= $yoff; $i--)
595
			$ymatches[$this->xv[$i]][] = $i;
596
		else
597
		    for ($i = $ylim - 1; $i >= $yoff; $i--)
598
			$ymatches[$this->yv[$i]][] = $i;
599
 
600
		$this->lcs = 0;
601
		$this->seq[0]= $yoff - 1;
602
		$this->in_seq = array();
603
		$ymids[0] = array();
604
 
605
		$numer = $xlim - $xoff + $nchunks - 1;
606
		$x = $xoff;
607
		for ($chunk = 0; $chunk < $nchunks; $chunk++) {
608
		    if ($chunk > 0)
609
			for ($i = 0; $i <= $this->lcs; $i++)
610
			    $ymids[$i][$chunk-1] = $this->seq[$i];
611
 
612
		    $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks);
613
		    for ( ; $x < $x1; $x++) {
614
			$line = $flip ? $this->yv[$x] : $this->xv[$x];
615
			if (empty($ymatches[$line]))
616
			    continue;
617
			$matches = $ymatches[$line];
618
			reset($matches);
619
			while (list ($junk, $y) = each($matches))
620
			    if (empty($this->in_seq[$y])) {
621
				$k = $this->_lcs_pos($y);
622
				USE_ASSERTS && assert($k > 0);
623
				$ymids[$k] = $ymids[$k-1];
624
				break;
625
			    }
626
			while (list ($junk, $y) = each($matches)) {
627
			    if ($y > $this->seq[$k-1]) {
628
				USE_ASSERTS && assert($y < $this->seq[$k]);
629
				// Optimization: this is a common case:
630
				//  next match is just replacing previous match.
631
				$this->in_seq[$this->seq[$k]] = false;
632
				$this->seq[$k] = $y;
633
				$this->in_seq[$y] = 1;
634
			    }
635
			    else if (empty($this->in_seq[$y])) {
636
				$k = $this->_lcs_pos($y);
637
				USE_ASSERTS && assert($k > 0);
638
				$ymids[$k] = $ymids[$k-1];
639
			    }
640
			}
641
		    }
642
		}
643
 
644
		$seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);
645
		$ymid = $ymids[$this->lcs];
646
		for ($n = 0; $n < $nchunks - 1; $n++) {
647
		    $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);
648
		    $y1 = $ymid[$n] + 1;
649
		    $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);
650
		}
651
		$seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);
652
 
653
		return array($this->lcs, $seps);
654
	    }
655
 
656
	    function _lcs_pos ($ypos) {
657
		$end = $this->lcs;
658
		if ($end == 0 || $ypos > $this->seq[$end]) {
659
		    $this->seq[++$this->lcs] = $ypos;
660
		    $this->in_seq[$ypos] = 1;
661
		    return $this->lcs;
662
		}
663
 
664
		$beg = 1;
665
		while ($beg < $end) {
666
		    $mid = (int)(($beg + $end) / 2);
667
		    if ( $ypos > $this->seq[$mid] )
668
			$beg = $mid + 1;
669
		    else
670
			$end = $mid;
671
		}
672
 
673
		USE_ASSERTS && assert($ypos != $this->seq[$end]);
674
 
675
		$this->in_seq[$this->seq[$end]] = false;
676
		$this->seq[$end] = $ypos;
677
		$this->in_seq[$ypos] = 1;
678
		return $end;
679
	    }
680
 
681
	    /* Find LCS of two sequences.
682
	     *
683
	     * The results are recorded in the vectors $this->{x,y}changed[], by
684
	     * storing a 1 in the element for each line that is an insertion
685
	     * or deletion (ie. is not in the LCS).
686
	     *
687
	     * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
688
	     *
689
	     * Note that XLIM, YLIM are exclusive bounds.
690
	     * All line numbers are origin-0 and discarded lines are not counted.
691
	     */
692
	    function _compareseq ($xoff, $xlim, $yoff, $ylim) {
693
		// Slide down the bottom initial diagonal.
694
		while ($xoff < $xlim && $yoff < $ylim
695
		       && $this->xv[$xoff] == $this->yv[$yoff]) {
696
		    ++$xoff;
697
		    ++$yoff;
698
		}
699
 
700
		// Slide up the top initial diagonal.
701
		while ($xlim > $xoff && $ylim > $yoff
702
		       && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {
703
		    --$xlim;
704
		    --$ylim;
705
		}
706
 
707
		if ($xoff == $xlim || $yoff == $ylim)
708
		    $lcs = 0;
709
		else {
710
		    // This is ad hoc but seems to work well.
711
		    //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5);
712
		    //$nchunks = max(2,min(8,(int)$nchunks));
713
		    $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;
714
		    list ($lcs, $seps)
715
			= $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks);
716
		}
717
 
718
		if ($lcs == 0) {
719
		    // X and Y sequences have no common subsequence:
720
		    // mark all changed.
721
		    while ($yoff < $ylim)
722
			$this->ychanged[$this->yind[$yoff++]] = 1;
723
		    while ($xoff < $xlim)
724
			$this->xchanged[$this->xind[$xoff++]] = 1;
725
		}
726
		else {
727
		    // Use the partitions to split this problem into subproblems.
728
		    reset($seps);
729
		    $pt1 = $seps[0];
730
		    while ($pt2 = next($seps)) {
731
			$this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);
732
			$pt1 = $pt2;
733
		    }
734
		}
735
	    }
736
 
737
	    /* Adjust inserts/deletes of identical lines to join changes
738
	     * as much as possible.
739
	     *
740
	     * We do something when a run of changed lines include a
741
	     * line at one end and has an excluded, identical line at the other.
742
	     * We are free to choose which identical line is included.
743
	     * `compareseq' usually chooses the one at the beginning,
744
	     * but usually it is cleaner to consider the following identical line
745
	     * to be the "change".
746
	     *
747
	     * This is extracted verbatim from analyze.c (GNU diffutils-2.7).
748
	     */
749
	    function _shift_boundaries ($lines, &$changed, $other_changed) {
750
		$i = 0;
751
		$j = 0;
752
 
753
		USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)');
754
		$len = sizeof($lines);
755
		$other_len = sizeof($other_changed);
756
 
757
		while (1) {
758
		    /*
759
		     * Scan forwards to find beginning of another run of changes.
760
		     * Also keep track of the corresponding point in the other file.
761
		     *
762
		     * Throughout this code, $i and $j are adjusted together so that
763
		     * the first $i elements of $changed and the first $j elements
764
		     * of $other_changed both contain the same number of zeros
765
		     * (unchanged lines).
766
		     * Furthermore, $j is always kept so that $j == $other_len or
767
		     * $other_changed[$j] == false.
768
		     */
769
		    while ($j < $other_len && $other_changed[$j])
770
			$j++;
771
 
772
		    while ($i < $len && ! $changed[$i]) {
773
			USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
774
			$i++; $j++;
775
			while ($j < $other_len && $other_changed[$j])
776
			    $j++;
777
		    }
778
 
779
		    if ($i == $len)
780
			break;
781
 
782
		    $start = $i;
783
 
784
		    // Find the end of this run of changes.
785
		    while (++$i < $len && $changed[$i])
786
			continue;
787
 
788
		    do {
789
			/*
790
			 * Record the length of this run of changes, so that
791
			 * we can later determine whether the run has grown.
792
			 */
793
			$runlength = $i - $start;
794
 
795
			/*
796
			 * Move the changed region back, so long as the
797
			 * previous unchanged line matches the last changed one.
798
			 * This merges with previous changed regions.
799
			 */
800
			while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {
801
			    $changed[--$start] = 1;
802
			    $changed[--$i] = false;
803
			    while ($start > 0 && $changed[$start - 1])
804
				$start--;
805
			    USE_ASSERTS && assert('$j > 0');
806
			    while ($other_changed[--$j])
807
				continue;
808
			    USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
809
			}
810
 
811
			/*
812
			 * Set CORRESPONDING to the end of the changed run, at the last
813
			 * point where it corresponds to a changed run in the other file.
814
			 * CORRESPONDING == LEN means no such point has been found.
815
			 */
816
			$corresponding = $j < $other_len ? $i : $len;
817
 
818
			/*
819
			 * Move the changed region forward, so long as the
820
			 * first changed line matches the following unchanged one.
821
			 * This merges with following changed regions.
822
			 * Do this second, so that if there are no merges,
823
			 * the changed region is moved forward as far as possible.
824
			 */
825
			while ($i < $len && $lines[$start] == $lines[$i]) {
826
			    $changed[$start++] = false;
827
			    $changed[$i++] = 1;
828
			    while ($i < $len && $changed[$i])
829
				$i++;
830
 
831
			    USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]');
832
			    $j++;
833
			    if ($j < $other_len && $other_changed[$j]) {
834
				$corresponding = $i;
835
				while ($j < $other_len && $other_changed[$j])
836
				    $j++;
837
			    }
838
			}
839
		    } while ($runlength != $i - $start);
840
 
841
		    /*
842
		     * If possible, move the fully-merged run of changes
843
		     * back to a corresponding run in the other file.
844
		     */
845
		    while ($corresponding < $i) {
846
			$changed[--$start] = 1;
847
			$changed[--$i] = 0;
848
			USE_ASSERTS && assert('$j > 0');
849
			while ($other_changed[--$j])
850
			    continue;
851
			USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]');
852
		    }
853
		}
854
	    }
855
	}
856
 
857
	/**
858
	 * Class representing a 'diff' between two sequences of strings.
859
	 */
860
	class Diff
861
	{
862
	    var $edits;
863
 
864
	    /**
865
	     * Constructor.
866
	     * Computes diff between sequences of strings.
867
	     *
868
	     * @param $from_lines array An array of strings.
869
	     *        (Typically these are lines from a file.)
870
	     * @param $to_lines array An array of strings.
871
	     */
872
	    function Diff($from_lines, $to_lines) {
873
		$eng = new _DiffEngine;
874
		$this->edits = $eng->diff($from_lines, $to_lines);
875
	    }
876
 
877
	}
878
 
879
 
880
 
881
	/**
882
	 * A class to format Diffs
883
	 *
884
	 * This class formats the diff in classic diff format.
885
	 * It is intended that this class be customized via inheritance,
886
	 * to obtain fancier outputs.
887
	 */
888
	class DiffFormatter
889
	{
890
 
891
	    /**
892
	     * Format a diff.
893
	     *
894
	     * @param $diff object A Diff object.
895
	     * @return string The formatted output.
896
	     */
897
	    function format($diff) {
898
 
899
		$xi = $yi = 1;
900
		$block = false;
901
		$context = array();
902
 
903
		$this->_start_diff();
904
 
905
		foreach ($diff->edits as $edit) {
906
		    if ($edit->type == 'copy') {
907
			if (is_array($block)) {
908
			    if (sizeof($edit->orig) <= 0) {
909
				$block[] = $edit;
910
			    }
911
			    else{
912
				$this->_block($x0, + $xi - $x0,
913
					      $y0, + $yi - $y0,
914
					      $block);
915
				$block = false;
916
			    }
917
			}
918
		    }
919
		    else {
920
			if (! is_array($block)) {
921
			    $x0 = $xi;
922
			    $y0 = $yi;
923
			    $block = array();
924
			}
925
			$block[] = $edit;
926
		    }
927
 
928
		    if ($edit->orig)
929
			$xi += sizeof($edit->orig);
930
		    if ($edit->final)
931
			$yi += sizeof($edit->final);
932
		}
933
 
934
		if (is_array($block))
935
		    $this->_block($x0, $xi - $x0,
936
				  $y0, $yi - $y0,
937
				  $block);
938
 
939
		return $this->_end_diff();
940
	    }
941
 
942
	    function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) {
943
		$this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen));
944
	    }
945
 
946
	    function _start_diff() {
947
		ob_start();
948
	    }
949
 
950
	    function _end_diff() {
951
		$val = ob_get_contents();
952
		ob_end_clean();
953
		return $val;
954
	    }
955
 
956
	    function _block_header($xbeg, $xlen, $ybeg, $ylen) {
957
		if ($xlen > 1)
958
		    $xbeg .= "," . ($xbeg + $xlen - 1);
959
		if ($ylen > 1)
960
		    $ybeg .= "," . ($ybeg + $ylen - 1);
961
 
962
		return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
963
	    }
964
 
965
	    function _start_block($header) {
966
		echo $header."\n";
967
	    }
968
 
969
	}
970
 
971
 
972
?>
973
</div>