1<?php 2/** 3 * Parses unified or context diffs output from eg. the diff utility. 4 * 5 * Example: 6 * <code> 7 * $patch = file_get_contents('example.patch'); 8 * $diff = new Text_Diff('string', array($patch)); 9 * $renderer = new Text_Diff_Renderer_inline(); 10 * echo $renderer->render($diff); 11 * </code> 12 * 13 * $Horde: framework/Text_Diff/Diff/Engine/string.php,v 1.5.2.7 2009/07/24 13:04:43 jan Exp $ 14 * 15 * Copyright 2005 �rjan Persson <o@42mm.org> 16 * Copyright 2005-2009 The Horde Project (http://www.horde.org/) 17 * 18 * See the enclosed file COPYING for license information (LGPL). If you did 19 * not receive this file, see http://opensource.org/licenses/lgpl-license.php. 20 * 21 * @author �rjan Persson <o@42mm.org> 22 * @package Text_Diff 23 * @since 0.2.0 24 */ 25class Text_Diff_Engine_string { 26 27 /** 28 * Parses a unified or context diff. 29 * 30 * First param contains the whole diff and the second can be used to force 31 * a specific diff type. If the second parameter is 'autodetect', the 32 * diff will be examined to find out which type of diff this is. 33 * 34 * @param string $diff The diff content. 35 * @param string $mode The diff mode of the content in $diff. One of 36 * 'context', 'unified', or 'autodetect'. 37 * 38 * @return array List of all diff operations. 39 */ 40 function diff($diff, $mode = 'autodetect') 41 { 42 // Detect line breaks. 43 $lnbr = "\n"; 44 if (strpos($diff, "\r\n") !== false) { 45 $lnbr = "\r\n"; 46 } elseif (strpos($diff, "\r") !== false) { 47 $lnbr = "\r"; 48 } 49 50 // Make sure we have a line break at the EOF. 51 if (substr($diff, -strlen($lnbr)) != $lnbr) { 52 $diff .= $lnbr; 53 } 54 55 if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { 56 return PEAR::raiseError('Type of diff is unsupported'); 57 } 58 59 if ($mode == 'autodetect') { 60 $context = strpos($diff, '***'); 61 $unified = strpos($diff, '---'); 62 if ($context === $unified) { 63 return PEAR::raiseError('Type of diff could not be detected'); 64 } elseif ($context === false || $unified === false) { 65 $mode = $context !== false ? 'context' : 'unified'; 66 } else { 67 $mode = $context < $unified ? 'context' : 'unified'; 68 } 69 } 70 71 // Split by new line and remove the diff header, if there is one. 72 $diff = explode($lnbr, $diff); 73 if (($mode == 'context' && strpos($diff[0], '***') === 0) || 74 ($mode == 'unified' && strpos($diff[0], '---') === 0)) { 75 array_shift($diff); 76 array_shift($diff); 77 } 78 79 if ($mode == 'context') { 80 return $this->parseContextDiff($diff); 81 } else { 82 return $this->parseUnifiedDiff($diff); 83 } 84 } 85 86 /** 87 * Parses an array containing the unified diff. 88 * 89 * @param array $diff Array of lines. 90 * 91 * @return array List of all diff operations. 92 */ 93 function parseUnifiedDiff($diff) 94 { 95 $edits = array(); 96 $end = count($diff) - 1; 97 for ($i = 0; $i < $end;) { 98 $diff1 = array(); 99 switch (substr($diff[$i], 0, 1)) { 100 case ' ': 101 do { 102 $diff1[] = substr($diff[$i], 1); 103 } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); 104 $edits[] = new Text_Diff_Op_copy($diff1); 105 break; 106 107 case '+': 108 // get all new lines 109 do { 110 $diff1[] = substr($diff[$i], 1); 111 } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); 112 $edits[] = new Text_Diff_Op_add($diff1); 113 break; 114 115 case '-': 116 // get changed or removed lines 117 $diff2 = array(); 118 do { 119 $diff1[] = substr($diff[$i], 1); 120 } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); 121 122 while ($i < $end && substr($diff[$i], 0, 1) == '+') { 123 $diff2[] = substr($diff[$i++], 1); 124 } 125 if (count($diff2) == 0) { 126 $edits[] = new Text_Diff_Op_delete($diff1); 127 } else { 128 $edits[] = new Text_Diff_Op_change($diff1, $diff2); 129 } 130 break; 131 132 default: 133 $i++; 134 break; 135 } 136 } 137 138 return $edits; 139 } 140 141 /** 142 * Parses an array containing the context diff. 143 * 144 * @param array $diff Array of lines. 145 * 146 * @return array List of all diff operations. 147 */ 148 function parseContextDiff(&$diff) 149 { 150 $edits = array(); 151 $i = $max_i = $j = $max_j = 0; 152 $end = count($diff) - 1; 153 while ($i < $end && $j < $end) { 154 while ($i >= $max_i && $j >= $max_j) { 155 // Find the boundaries of the diff output of the two files 156 for ($i = $j; 157 $i < $end && substr($diff[$i], 0, 3) == '***'; 158 $i++); 159 for ($max_i = $i; 160 $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; 161 $max_i++); 162 for ($j = $max_i; 163 $j < $end && substr($diff[$j], 0, 3) == '---'; 164 $j++); 165 for ($max_j = $j; 166 $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; 167 $max_j++); 168 } 169 170 // find what hasn't been changed 171 $array = array(); 172 while ($i < $max_i && 173 $j < $max_j && 174 strcmp($diff[$i], $diff[$j]) == 0) { 175 $array[] = substr($diff[$i], 2); 176 $i++; 177 $j++; 178 } 179 180 while ($i < $max_i && ($max_j-$j) <= 1) { 181 if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { 182 break; 183 } 184 $array[] = substr($diff[$i++], 2); 185 } 186 187 while ($j < $max_j && ($max_i-$i) <= 1) { 188 if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { 189 break; 190 } 191 $array[] = substr($diff[$j++], 2); 192 } 193 if (count($array) > 0) { 194 $edits[] = new Text_Diff_Op_copy($array); 195 } 196 197 if ($i < $max_i) { 198 $diff1 = array(); 199 switch (substr($diff[$i], 0, 1)) { 200 case '!': 201 $diff2 = array(); 202 do { 203 $diff1[] = substr($diff[$i], 2); 204 if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { 205 $diff2[] = substr($diff[$j++], 2); 206 } 207 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); 208 $edits[] = new Text_Diff_Op_change($diff1, $diff2); 209 break; 210 211 case '+': 212 do { 213 $diff1[] = substr($diff[$i], 2); 214 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); 215 $edits[] = new Text_Diff_Op_add($diff1); 216 break; 217 218 case '-': 219 do { 220 $diff1[] = substr($diff[$i], 2); 221 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); 222 $edits[] = new Text_Diff_Op_delete($diff1); 223 break; 224 } 225 } 226 227 if ($j < $max_j) { 228 $diff2 = array(); 229 switch (substr($diff[$j], 0, 1)) { 230 case '+': 231 do { 232 $diff2[] = substr($diff[$j++], 2); 233 } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); 234 $edits[] = new Text_Diff_Op_add($diff2); 235 break; 236 237 case '-': 238 do { 239 $diff2[] = substr($diff[$j++], 2); 240 } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); 241 $edits[] = new Text_Diff_Op_delete($diff2); 242 break; 243 } 244 } 245 } 246 247 return $edits; 248 } 249 250} 251