Overview

Packages

  • Text
    • Diff

Classes

  • Horde_Text_Diff
  • Horde_Text_Diff_Engine_Native
  • Horde_Text_Diff_Engine_Shell
  • Horde_Text_Diff_Engine_String
  • Horde_Text_Diff_Engine_Xdiff
  • Horde_Text_Diff_Exception
  • Horde_Text_Diff_Mapped
  • Horde_Text_Diff_Op_Add
  • Horde_Text_Diff_Op_Base
  • Horde_Text_Diff_Op_Change
  • Horde_Text_Diff_Op_Copy
  • Horde_Text_Diff_Op_Delete
  • Horde_Text_Diff_Renderer
  • Horde_Text_Diff_Renderer_Context
  • Horde_Text_Diff_Renderer_Inline
  • Horde_Text_Diff_Renderer_Unified
  • Horde_Text_Diff_ThreeWay
  • Horde_Text_Diff_ThreeWay_BlockBuilder
  • Horde_Text_Diff_ThreeWay_Op_Base
  • Horde_Text_Diff_ThreeWay_Op_Copy
  • Overview
  • Package
  • Class
  • Tree
  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:  * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org>
 10:  * Copyright 2004-2012 Horde LLC (http://www.horde.org/)
 11:  *
 12:  * See the enclosed file COPYING for license information (LGPL). If you did
 13:  * not receive this file, see http://www.horde.org/licenses/lgpl21.
 14:  *
 15:  * @package Text_Diff
 16:  * @author  Geoffrey T. Dairiki <dairiki@dairiki.org>
 17:  */
 18: class Horde_Text_Diff
 19: {
 20:     /**
 21:      * Array of changes.
 22:      *
 23:      * @var array
 24:      */
 25:     protected $_edits;
 26: 
 27:     /**
 28:      * Computes diffs between sequences of strings.
 29:      *
 30:      * @param string $engine     Name of the diffing engine to use.  'auto'
 31:      *                           will automatically select the best.
 32:      * @param array $params      Parameters to pass to the diffing engine.
 33:      *                           Normally an array of two arrays, each
 34:      *                           containing the lines from a file.
 35:      */
 36:     public function __construct($engine, $params)
 37:     {
 38:         if ($engine == 'auto') {
 39:             $engine = extension_loaded('xdiff') ? 'Xdiff' : 'Native';
 40:         } else {
 41:             $engine = Horde_String::ucfirst(basename($engine));
 42:         }
 43: 
 44:         $class = 'Horde_Text_Diff_Engine_' . $engine;
 45:         $diff_engine = new $class();
 46: 
 47:         $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params);
 48:     }
 49: 
 50:     /**
 51:      * Returns the array of differences.
 52:      */
 53:     public function getDiff()
 54:     {
 55:         return $this->_edits;
 56:     }
 57: 
 58:     /**
 59:      * returns the number of new (added) lines in a given diff.
 60:      *
 61:      * @since Text_Diff 1.1.0
 62:      *
 63:      * @return integer The number of new lines
 64:      */
 65:     public function countAddedLines()
 66:     {
 67:         $count = 0;
 68:         foreach ($this->_edits as $edit) {
 69:             if ($edit instanceof Horde_Text_Diff_Op_Add ||
 70:                 $edit instanceof Horde_Text_Diff_Op_Change) {
 71:                 $count += $edit->nfinal();
 72:             }
 73:         }
 74:         return $count;
 75:     }
 76: 
 77:     /**
 78:      * Returns the number of deleted (removed) lines in a given diff.
 79:      *
 80:      * @since Text_Diff 1.1.0
 81:      *
 82:      * @return integer The number of deleted lines
 83:      */
 84:     public function countDeletedLines()
 85:     {
 86:         $count = 0;
 87:         foreach ($this->_edits as $edit) {
 88:             if ($edit instanceof Horde_Text_Diff_Op_Delete ||
 89:                 $edit instanceof Horde_Text_Diff_Op_Change) {
 90:                 $count += $edit->norig();
 91:             }
 92:         }
 93:         return $count;
 94:     }
 95: 
 96:     /**
 97:      * Computes a reversed diff.
 98:      *
 99:      * Example:
100:      * <code>
101:      * $diff = new Horde_Text_Diff($lines1, $lines2);
102:      * $rev = $diff->reverse();
103:      * </code>
104:      *
105:      * @return Horde_Text_Diff  A Diff object representing the inverse of the
106:      *                    original diff.  Note that we purposely don't return a
107:      *                    reference here, since this essentially is a clone()
108:      *                    method.
109:      */
110:     public function reverse()
111:     {
112:         if (version_compare(zend_version(), '2', '>')) {
113:             $rev = clone($this);
114:         } else {
115:             $rev = $this;
116:         }
117:         $rev->_edits = array();
118:         foreach ($this->_edits as $edit) {
119:             $rev->_edits[] = $edit->reverse();
120:         }
121:         return $rev;
122:     }
123: 
124:     /**
125:      * Checks for an empty diff.
126:      *
127:      * @return boolean  True if two sequences were identical.
128:      */
129:     public function isEmpty()
130:     {
131:         foreach ($this->_edits as $edit) {
132:             if (!($edit instanceof Horde_Text_Diff_Op_Copy)) {
133:                 return false;
134:             }
135:         }
136:         return true;
137:     }
138: 
139:     /**
140:      * Computes the length of the Longest Common Subsequence (LCS).
141:      *
142:      * This is mostly for diagnostic purposes.
143:      *
144:      * @return integer  The length of the LCS.
145:      */
146:     public function lcs()
147:     {
148:         $lcs = 0;
149:         foreach ($this->_edits as $edit) {
150:             if ($edit instanceof Horde_Text_Diff_Op_Copy) {
151:                 $lcs += count($edit->orig);
152:             }
153:         }
154:         return $lcs;
155:     }
156: 
157:     /**
158:      * Gets the original set of lines.
159:      *
160:      * This reconstructs the $from_lines parameter passed to the constructor.
161:      *
162:      * @return array  The original sequence of strings.
163:      */
164:     public function getOriginal()
165:     {
166:         $lines = array();
167:         foreach ($this->_edits as $edit) {
168:             if ($edit->orig) {
169:                 array_splice($lines, count($lines), 0, $edit->orig);
170:             }
171:         }
172:         return $lines;
173:     }
174: 
175:     /**
176:      * Gets the final set of lines.
177:      *
178:      * This reconstructs the $to_lines parameter passed to the constructor.
179:      *
180:      * @return array  The sequence of strings.
181:      */
182:     public function getFinal()
183:     {
184:         $lines = array();
185:         foreach ($this->_edits as $edit) {
186:             if ($edit->final) {
187:                 array_splice($lines, count($lines), 0, $edit->final);
188:             }
189:         }
190:         return $lines;
191:     }
192: 
193:     /**
194:      * Removes trailing newlines from a line of text. This is meant to be used
195:      * with array_walk().
196:      *
197:      * @param string $line  The line to trim.
198:      * @param integer $key  The index of the line in the array. Not used.
199:      */
200:     static public function trimNewlines(&$line, $key)
201:     {
202:         $line = str_replace(array("\n", "\r"), '', $line);
203:     }
204: 
205:     /**
206:      * Checks a diff for validity.
207:      *
208:      * This is here only for debugging purposes.
209:      */
210:     protected function _check($from_lines, $to_lines)
211:     {
212:         if (serialize($from_lines) != serialize($this->getOriginal())) {
213:             trigger_error("Reconstructed original doesn't match", E_USER_ERROR);
214:         }
215:         if (serialize($to_lines) != serialize($this->getFinal())) {
216:             trigger_error("Reconstructed final doesn't match", E_USER_ERROR);
217:         }
218: 
219:         $rev = $this->reverse();
220:         if (serialize($to_lines) != serialize($rev->getOriginal())) {
221:             trigger_error("Reversed original doesn't match", E_USER_ERROR);
222:         }
223:         if (serialize($from_lines) != serialize($rev->getFinal())) {
224:             trigger_error("Reversed final doesn't match", E_USER_ERROR);
225:         }
226: 
227:         $prevtype = null;
228:         foreach ($this->_edits as $edit) {
229:             if ($prevtype == get_class($edit)) {
230:                 trigger_error("Edit sequence is non-optimal", E_USER_ERROR);
231:             }
232:             $prevtype = get_class($edit);
233:         }
234: 
235:         return true;
236:     }
237: }
238: 
API documentation generated by ApiGen