Diff.php (8500B)
1 <?php 2 /** 3 * PHPUnit 4 * 5 * Copyright (c) 2001-2014, Sebastian Bergmann <sebastian@phpunit.de>. 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * * Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * * Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in 17 * the documentation and/or other materials provided with the 18 * distribution. 19 * 20 * * Neither the name of Sebastian Bergmann nor the names of his 21 * contributors may be used to endorse or promote products derived 22 * from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. 36 * 37 * @package PHPUnit 38 * @subpackage Util 39 * @author Sebastian Bergmann <sebastian@phpunit.de> 40 * @author Kore Nordmann <mail@kore-nordmann.de> 41 * @copyright 2001-2014 Sebastian Bergmann <sebastian@phpunit.de> 42 * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License 43 * @link http://www.phpunit.de/ 44 * @since File available since Release 3.4.0 45 */ 46 47 /** 48 * Diff implementation. 49 * 50 * @package PHPUnit 51 * @subpackage Util 52 * @author Sebastian Bergmann <sebastian@phpunit.de> 53 * @author Kore Nordmann <mail@kore-nordmann.de> 54 * @copyright 2001-2014 Sebastian Bergmann <sebastian@phpunit.de> 55 * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License 56 * @link http://www.phpunit.de/ 57 * @since Class available since Release 3.4.0 58 */ 59 class PHPUnit_Util_Diff 60 { 61 /** 62 * Returns the diff between two arrays or strings as string. 63 * 64 * @param array|string $from 65 * @param array|string $to 66 * @return string 67 */ 68 public static function diff($from, $to) 69 { 70 $buffer= "--- Expected\n+++ Actual\n"; 71 $diff = self::diffToArray($from,$to); 72 73 $inOld = FALSE; 74 $i = 0; 75 $old = array(); 76 77 foreach ($diff as $line) { 78 if ($line[1] === 0 /* OLD */) { 79 if ($inOld === FALSE) { 80 $inOld = $i; 81 } 82 } 83 84 else if ($inOld !== FALSE) { 85 if (($i - $inOld) > 5) { 86 $old[$inOld] = $i - 1; 87 } 88 89 $inOld = FALSE; 90 } 91 92 ++$i; 93 } 94 95 $start = isset($old[0]) ? $old[0] : 0; 96 $end = count($diff); 97 $i = 0; 98 99 if ($tmp = array_search($end, $old)) { 100 $end = $tmp; 101 } 102 103 $newChunk = TRUE; 104 105 for ($i = $start; $i < $end; $i++) { 106 if (isset($old[$i])) { 107 $buffer .= "\n"; 108 $newChunk = TRUE; 109 $i = $old[$i]; 110 } 111 112 if ($newChunk) { 113 $buffer .= "@@ @@\n"; 114 $newChunk = FALSE; 115 } 116 117 if ($diff[$i][1] === 1 /* ADDED */) { 118 $buffer .= '+' . $diff[$i][0] . "\n"; 119 } 120 121 else if ($diff[$i][1] === 2 /* REMOVED */) { 122 $buffer .= '-' . $diff[$i][0] . "\n"; 123 } 124 125 else { 126 $buffer .= ' ' . $diff[$i][0] . "\n"; 127 } 128 } 129 130 return $buffer; 131 } 132 133 /** 134 * Returns the diff between two arrays or strings as array. 135 * 136 * every array-entry contains two elements: 137 * - [0] => string $token 138 * - [1] => 2|1|0 139 * 140 * - 2: REMOVED: $token was removed from $from 141 * - 1: ADDED: $token was added to $from 142 * - 0: OLD: $token is not changed in $to 143 * 144 * @param array|string $from 145 * @param array|string $to 146 * @return array 147 */ 148 public static function diffToArray($from, $to) 149 { 150 preg_match_all('(\r\n|\r|\n)', $from, $fromMatches); 151 preg_match_all('(\r\n|\r|\n)', $to, $toMatches); 152 153 if (is_string($from)) { 154 $from = preg_split('(\r\n|\r|\n)', $from); 155 } 156 157 if (is_string($to)) { 158 $to = preg_split('(\r\n|\r|\n)', $to); 159 } 160 161 $start = array(); 162 $end = array(); 163 $fromLength = count($from); 164 $toLength = count($to); 165 $length = min($fromLength, $toLength); 166 167 for ($i = 0; $i < $length; ++$i) { 168 if ($from[$i] === $to[$i]) { 169 $start[] = $from[$i]; 170 unset($from[$i], $to[$i]); 171 } else { 172 break; 173 } 174 } 175 176 $length -= $i; 177 178 for ($i = 1; $i < $length; ++$i) { 179 if ($from[$fromLength - $i] === $to[$toLength - $i]) { 180 array_unshift($end, $from[$fromLength - $i]); 181 unset($from[$fromLength - $i], $to[$toLength - $i]); 182 } else { 183 break; 184 } 185 } 186 187 $common = self::longestCommonSubsequence( 188 array_values($from), array_values($to) 189 ); 190 191 $diff = array(); 192 $line = 0; 193 194 if (isset($fromMatches[0]) && $toMatches[0] && 195 count($fromMatches[0]) === count($toMatches[0]) && 196 $fromMatches[0] !== $toMatches[0]) { 197 $diff[] = array( 198 '#Warning: Strings contain different line endings!', 0 199 ); 200 } 201 202 foreach ($start as $token) { 203 $diff[] = array($token, 0 /* OLD */); 204 } 205 206 reset($from); 207 reset($to); 208 209 foreach ($common as $token) { 210 while ((($fromToken = reset($from)) !== $token)) { 211 $diff[] = array(array_shift($from), 2 /* REMOVED */); 212 } 213 214 while ((($toToken = reset($to)) !== $token)) { 215 $diff[] = array(array_shift($to), 1 /* ADDED */); 216 } 217 218 $diff[] = array($token, 0 /* OLD */); 219 220 array_shift($from); 221 array_shift($to); 222 } 223 224 while (($token = array_shift($from)) !== NULL) { 225 $diff[] = array($token, 2 /* REMOVED */); 226 } 227 228 while (($token = array_shift($to)) !== NULL) { 229 $diff[] = array($token, 1 /* ADDED */); 230 } 231 232 foreach ($end as $token) { 233 $diff[] = array($token, 0 /* OLD */); 234 } 235 236 return $diff; 237 } 238 239 /** 240 * Calculates the longest common subsequence of two arrays. 241 * 242 * @param array $from 243 * @param array $to 244 * @return array 245 */ 246 protected static function longestCommonSubsequence(array $from, array $to) 247 { 248 $common = array(); 249 $matrix = array(); 250 $fromLength = count($from); 251 $toLength = count($to); 252 253 for ($i = 0; $i <= $fromLength; ++$i) { 254 $matrix[$i][0] = 0; 255 } 256 257 for ($j = 0; $j <= $toLength; ++$j) { 258 $matrix[0][$j] = 0; 259 } 260 261 for ($i = 1; $i <= $fromLength; ++$i) { 262 for ($j = 1; $j <= $toLength; ++$j) { 263 $matrix[$i][$j] = max( 264 $matrix[$i-1][$j], 265 $matrix[$i][$j-1], 266 $from[$i-1] === $to[$j-1] ? $matrix[$i-1][$j-1] + 1 : 0 267 ); 268 } 269 } 270 271 $i = $fromLength; 272 $j = $toLength; 273 274 while ($i > 0 && $j > 0) { 275 if ($from[$i-1] === $to[$j-1]) { 276 array_unshift($common, $from[$i-1]); 277 --$i; 278 --$j; 279 } 280 281 else if ($matrix[$i][$j-1] > $matrix[$i-1][$j]) { 282 --$j; 283 } 284 285 else { 286 --$i; 287 } 288 } 289 290 return $common; 291 } 292 }