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.5 2008/09/10 08:31:58 jan Exp $ 14 * 15 * Copyright 2005 �rjan Persson <o@42mm.org> 16 * Copyright 2005-2008 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 if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { 43 return PEAR::raiseError('Type of diff is unsupported'); 44 } 45 46 if ($mode == 'autodetect') { 47 $context = strpos($diff, '***'); 48 $unified = strpos($diff, '---'); 49 if ($context === $unified) { 50 return PEAR::raiseError('Type of diff could not be detected'); 51 } elseif ($context === false || $unified === false) { 52 $mode = $context !== false ? 'context' : 'unified'; 53 } else { 54 $mode = $context < $unified ? 'context' : 'unified'; 55 } 56 } 57 58 // Split by new line and remove the diff header, if there is one. 59 $diff = explode("\n", $diff); 60 if (($mode == 'context' && strpos($diff[0], '***') === 0) || 61 ($mode == 'unified' && strpos($diff[0], '---') === 0)) { 62 array_shift($diff); 63 array_shift($diff); 64 } 65 66 if ($mode == 'context') { 67 return $this->parseContextDiff($diff); 68 } else { 69 return $this->parseUnifiedDiff($diff); 70 } 71 } 72 73 /** 74 * Parses an array containing the unified diff. 75 * 76 * @param array $diff Array of lines. 77 * 78 * @return array List of all diff operations. 79 */ 80 function parseUnifiedDiff($diff) 81 { 82 $edits = array(); 83 $end = count($diff) - 1; 84 for ($i = 0; $i < $end;) { 85 $diff1 = array(); 86 switch (substr($diff[$i], 0, 1)) { 87 case ' ': 88 do { 89 $diff1[] = substr($diff[$i], 1); 90 } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); 91 $edits[] = new Text_Diff_Op_copy($diff1); 92 break; 93 94 case '+': 95 // get all new lines 96 do { 97 $diff1[] = substr($diff[$i], 1); 98 } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); 99 $edits[] = new Text_Diff_Op_add($diff1); 100 break; 101 102 case '-': 103 // get changed or removed lines 104 $diff2 = array(); 105 do { 106 $diff1[] = substr($diff[$i], 1); 107 } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); 108 109 while ($i < $end && substr($diff[$i], 0, 1) == '+') { 110 $diff2[] = substr($diff[$i++], 1); 111 } 112 if (count($diff2) == 0) { 113 $edits[] = new Text_Diff_Op_delete($diff1); 114 } else { 115 $edits[] = new Text_Diff_Op_change($diff1, $diff2); 116 } 117 break; 118 119 default: 120 $i++; 121 break; 122 } 123 } 124 125 return $edits; 126 } 127 128 /** 129 * Parses an array containing the context diff. 130 * 131 * @param array $diff Array of lines. 132 * 133 * @return array List of all diff operations. 134 */ 135 function parseContextDiff(&$diff) 136 { 137 $edits = array(); 138 $i = $max_i = $j = $max_j = 0; 139 $end = count($diff) - 1; 140 while ($i < $end && $j < $end) { 141 while ($i >= $max_i && $j >= $max_j) { 142 // Find the boundaries of the diff output of the two files 143 for ($i = $j; 144 $i < $end && substr($diff[$i], 0, 3) == '***'; 145 $i++); 146 for ($max_i = $i; 147 $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; 148 $max_i++); 149 for ($j = $max_i; 150 $j < $end && substr($diff[$j], 0, 3) == '---'; 151 $j++); 152 for ($max_j = $j; 153 $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; 154 $max_j++); 155 } 156 157 // find what hasn't been changed 158 $array = array(); 159 while ($i < $max_i && 160 $j < $max_j && 161 strcmp($diff[$i], $diff[$j]) == 0) { 162 $array[] = substr($diff[$i], 2); 163 $i++; 164 $j++; 165 } 166 167 while ($i < $max_i && ($max_j-$j) <= 1) { 168 if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { 169 break; 170 } 171 $array[] = substr($diff[$i++], 2); 172 } 173 174 while ($j < $max_j && ($max_i-$i) <= 1) { 175 if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { 176 break; 177 } 178 $array[] = substr($diff[$j++], 2); 179 } 180 if (count($array) > 0) { 181 $edits[] = new Text_Diff_Op_copy($array); 182 } 183 184 if ($i < $max_i) { 185 $diff1 = array(); 186 switch (substr($diff[$i], 0, 1)) { 187 case '!': 188 $diff2 = array(); 189 do { 190 $diff1[] = substr($diff[$i], 2); 191 if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { 192 $diff2[] = substr($diff[$j++], 2); 193 } 194 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); 195 $edits[] = new Text_Diff_Op_change($diff1, $diff2); 196 break; 197 198 case '+': 199 do { 200 $diff1[] = substr($diff[$i], 2); 201 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); 202 $edits[] = new Text_Diff_Op_add($diff1); 203 break; 204 205 case '-': 206 do { 207 $diff1[] = substr($diff[$i], 2); 208 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); 209 $edits[] = new Text_Diff_Op_delete($diff1); 210 break; 211 } 212 } 213 214 if ($j < $max_j) { 215 $diff2 = array(); 216 switch (substr($diff[$j], 0, 1)) { 217 case '+': 218 do { 219 $diff2[] = substr($diff[$j++], 2); 220 } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); 221 $edits[] = new Text_Diff_Op_add($diff2); 222 break; 223 224 case '-': 225 do { 226 $diff2[] = substr($diff[$j++], 2); 227 } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); 228 $edits[] = new Text_Diff_Op_delete($diff2); 229 break; 230 } 231 } 232 } 233 234 return $edits; 235 } 236 237} 238