source: trunk/www.guidonia.net/wp/wp-includes/Text/Diff.php@ 44

Last change on this file since 44 was 44, checked in by luciano, 14 years ago
File size: 10.8 KB
Line 
1<?php
2/**
3 * General API for generating and formatting diffs - the differences between
4 * two sequences of strings.
5 *
6 * The original PHP version of this code was written by Geoffrey T. Dairiki
7 * <dairiki@dairiki.org>, and is used/adapted with his permission.
8 *
9 * $Horde: framework/Text_Diff/Diff.php,v 1.26 2008/01/04 10:07:49 jan Exp $
10 *
11 * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
12 * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
13 *
14 * See the enclosed file COPYING for license information (LGPL). If you did
15 * not receive this file, see http://opensource.org/licenses/lgpl-license.php.
16 *
17 * @package Text_Diff
18 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
19 */
20class Text_Diff {
21
22 /**
23 * Array of changes.
24 *
25 * @var array
26 */
27 var $_edits;
28
29 /**
30 * Computes diffs between sequences of strings.
31 *
32 * @param string $engine Name of the diffing engine to use. 'auto'
33 * will automatically select the best.
34 * @param array $params Parameters to pass to the diffing engine.
35 * Normally an array of two arrays, each
36 * containing the lines from a file.
37 */
38 function Text_Diff($engine, $params)
39 {
40 // Backward compatibility workaround.
41 if (!is_string($engine)) {
42 $params = array($engine, $params);
43 $engine = 'auto';
44 }
45
46 if ($engine == 'auto') {
47 $engine = extension_loaded('xdiff') ? 'xdiff' : 'native';
48 } else {
49 $engine = basename($engine);
50 }
51
52 // WP #7391
53 require_once dirname(__FILE__).'/Diff/Engine/' . $engine . '.php';
54 $class = 'Text_Diff_Engine_' . $engine;
55 $diff_engine = new $class();
56
57 $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params);
58 }
59
60 /**
61 * Returns the array of differences.
62 */
63 function getDiff()
64 {
65 return $this->_edits;
66 }
67
68 /**
69 * Computes a reversed diff.
70 *
71 * Example:
72 * <code>
73 * $diff = new Text_Diff($lines1, $lines2);
74 * $rev = $diff->reverse();
75 * </code>
76 *
77 * @return Text_Diff A Diff object representing the inverse of the
78 * original diff. Note that we purposely don't return a
79 * reference here, since this essentially is a clone()
80 * method.
81 */
82 function reverse()
83 {
84 if (version_compare(zend_version(), '2', '>')) {
85 $rev = clone($this);
86 } else {
87 $rev = $this;
88 }
89 $rev->_edits = array();
90 foreach ($this->_edits as $edit) {
91 $rev->_edits[] = $edit->reverse();
92 }
93 return $rev;
94 }
95
96 /**
97 * Checks for an empty diff.
98 *
99 * @return boolean True if two sequences were identical.
100 */
101 function isEmpty()
102 {
103 foreach ($this->_edits as $edit) {
104 if (!is_a($edit, 'Text_Diff_Op_copy')) {
105 return false;
106 }
107 }
108 return true;
109 }
110
111 /**
112 * Computes the length of the Longest Common Subsequence (LCS).
113 *
114 * This is mostly for diagnostic purposes.
115 *
116 * @return integer The length of the LCS.
117 */
118 function lcs()
119 {
120 $lcs = 0;
121 foreach ($this->_edits as $edit) {
122 if (is_a($edit, 'Text_Diff_Op_copy')) {
123 $lcs += count($edit->orig);
124 }
125 }
126 return $lcs;
127 }
128
129 /**
130 * Gets the original set of lines.
131 *
132 * This reconstructs the $from_lines parameter passed to the constructor.
133 *
134 * @return array The original sequence of strings.
135 */
136 function getOriginal()
137 {
138 $lines = array();
139 foreach ($this->_edits as $edit) {
140 if ($edit->orig) {
141 array_splice($lines, count($lines), 0, $edit->orig);
142 }
143 }
144 return $lines;
145 }
146
147 /**
148 * Gets the final set of lines.
149 *
150 * This reconstructs the $to_lines parameter passed to the constructor.
151 *
152 * @return array The sequence of strings.
153 */
154 function getFinal()
155 {
156 $lines = array();
157 foreach ($this->_edits as $edit) {
158 if ($edit->final) {
159 array_splice($lines, count($lines), 0, $edit->final);
160 }
161 }
162 return $lines;
163 }
164
165 /**
166 * Removes trailing newlines from a line of text. This is meant to be used
167 * with array_walk().
168 *
169 * @param string $line The line to trim.
170 * @param integer $key The index of the line in the array. Not used.
171 */
172 function trimNewlines(&$line, $key)
173 {
174 $line = str_replace(array("\n", "\r"), '', $line);
175 }
176
177 /**
178 * Determines the location of the system temporary directory.
179 *
180 * @static
181 *
182 * @access protected
183 *
184 * @return string A directory name which can be used for temp files.
185 * Returns false if one could not be found.
186 */
187 function _getTempDir()
188 {
189 $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp',
190 'c:\windows\temp', 'c:\winnt\temp');
191
192 /* Try PHP's upload_tmp_dir directive. */
193 $tmp = ini_get('upload_tmp_dir');
194
195 /* Otherwise, try to determine the TMPDIR environment variable. */
196 if (!strlen($tmp)) {
197 $tmp = getenv('TMPDIR');
198 }
199
200 /* If we still cannot determine a value, then cycle through a list of
201 * preset possibilities. */
202 while (!strlen($tmp) && count($tmp_locations)) {
203 $tmp_check = array_shift($tmp_locations);
204 if (@is_dir($tmp_check)) {
205 $tmp = $tmp_check;
206 }
207 }
208
209 /* If it is still empty, we have failed, so return false; otherwise
210 * return the directory determined. */
211 return strlen($tmp) ? $tmp : false;
212 }
213
214 /**
215 * Checks a diff for validity.
216 *
217 * This is here only for debugging purposes.
218 */
219 function _check($from_lines, $to_lines)
220 {
221 if (serialize($from_lines) != serialize($this->getOriginal())) {
222 trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
223 }
224 if (serialize($to_lines) != serialize($this->getFinal())) {
225 trigger_error("Reconstructed final doesn't match", E_USER_ERROR);
226 }
227
228 $rev = $this->reverse();
229 if (serialize($to_lines) != serialize($rev->getOriginal())) {
230 trigger_error("Reversed original doesn't match", E_USER_ERROR);
231 }
232 if (serialize($from_lines) != serialize($rev->getFinal())) {
233 trigger_error("Reversed final doesn't match", E_USER_ERROR);
234 }
235
236 $prevtype = null;
237 foreach ($this->_edits as $edit) {
238 if ($prevtype == get_class($edit)) {
239 trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
240 }
241 $prevtype = get_class($edit);
242 }
243
244 return true;
245 }
246
247}
248
249/**
250 * @package Text_Diff
251 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
252 */
253class Text_MappedDiff extends Text_Diff {
254
255 /**
256 * Computes a diff between sequences of strings.
257 *
258 * This can be used to compute things like case-insensitve diffs, or diffs
259 * which ignore changes in white-space.
260 *
261 * @param array $from_lines An array of strings.
262 * @param array $to_lines An array of strings.
263 * @param array $mapped_from_lines This array should have the same size
264 * number of elements as $from_lines. The
265 * elements in $mapped_from_lines and
266 * $mapped_to_lines are what is actually
267 * compared when computing the diff.
268 * @param array $mapped_to_lines This array should have the same number
269 * of elements as $to_lines.
270 */
271 function Text_MappedDiff($from_lines, $to_lines,
272 $mapped_from_lines, $mapped_to_lines)
273 {
274 assert(count($from_lines) == count($mapped_from_lines));
275 assert(count($to_lines) == count($mapped_to_lines));
276
277 parent::Text_Diff($mapped_from_lines, $mapped_to_lines);
278
279 $xi = $yi = 0;
280 for ($i = 0; $i < count($this->_edits); $i++) {
281 $orig = &$this->_edits[$i]->orig;
282 if (is_array($orig)) {
283 $orig = array_slice($from_lines, $xi, count($orig));
284 $xi += count($orig);
285 }
286
287 $final = &$this->_edits[$i]->final;
288 if (is_array($final)) {
289 $final = array_slice($to_lines, $yi, count($final));
290 $yi += count($final);
291 }
292 }
293 }
294
295}
296
297/**
298 * @package Text_Diff
299 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
300 *
301 * @access private
302 */
303class Text_Diff_Op {
304
305 var $orig;
306 var $final;
307
308 function &reverse()
309 {
310 trigger_error('Abstract method', E_USER_ERROR);
311 }
312
313 function norig()
314 {
315 return $this->orig ? count($this->orig) : 0;
316 }
317
318 function nfinal()
319 {
320 return $this->final ? count($this->final) : 0;
321 }
322
323}
324
325/**
326 * @package Text_Diff
327 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
328 *
329 * @access private
330 */
331class Text_Diff_Op_copy extends Text_Diff_Op {
332
333 function Text_Diff_Op_copy($orig, $final = false)
334 {
335 if (!is_array($final)) {
336 $final = $orig;
337 }
338 $this->orig = $orig;
339 $this->final = $final;
340 }
341
342 function &reverse()
343 {
344 $reverse = &new Text_Diff_Op_copy($this->final, $this->orig);
345 return $reverse;
346 }
347
348}
349
350/**
351 * @package Text_Diff
352 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
353 *
354 * @access private
355 */
356class Text_Diff_Op_delete extends Text_Diff_Op {
357
358 function Text_Diff_Op_delete($lines)
359 {
360 $this->orig = $lines;
361 $this->final = false;
362 }
363
364 function &reverse()
365 {
366 $reverse = &new Text_Diff_Op_add($this->orig);
367 return $reverse;
368 }
369
370}
371
372/**
373 * @package Text_Diff
374 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
375 *
376 * @access private
377 */
378class Text_Diff_Op_add extends Text_Diff_Op {
379
380 function Text_Diff_Op_add($lines)
381 {
382 $this->final = $lines;
383 $this->orig = false;
384 }
385
386 function &reverse()
387 {
388 $reverse = &new Text_Diff_Op_delete($this->final);
389 return $reverse;
390 }
391
392}
393
394/**
395 * @package Text_Diff
396 * @author Geoffrey T. Dairiki <dairiki@dairiki.org>
397 *
398 * @access private
399 */
400class Text_Diff_Op_change extends Text_Diff_Op {
401
402 function Text_Diff_Op_change($orig, $final)
403 {
404 $this->orig = $orig;
405 $this->final = $final;
406 }
407
408 function &reverse()
409 {
410 $reverse = &new Text_Diff_Op_change($this->final, $this->orig);
411 return $reverse;
412 }
413
414}
Note: See TracBrowser for help on using the repository browser.