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