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