Wordpress 3.6
[autoinstalls/wordpress.git] / wp-includes / Text / Diff / Engine / string.php
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  * Copyright 2005 Örjan Persson <o@42mm.org>
14  * Copyright 2005-2010 The Horde Project (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://opensource.org/licenses/lgpl-license.php.
18  *
19  * @author  Örjan Persson <o@42mm.org>
20  * @package Text_Diff
21  * @since   0.2.0
22  */
23 class Text_Diff_Engine_string {
24
25     /**
26      * Parses a unified or context diff.
27      *
28      * First param contains the whole diff and the second can be used to force
29      * a specific diff type. If the second parameter is 'autodetect', the
30      * diff will be examined to find out which type of diff this is.
31      *
32      * @param string $diff  The diff content.
33      * @param string $mode  The diff mode of the content in $diff. One of
34      *                      'context', 'unified', or 'autodetect'.
35      *
36      * @return array  List of all diff operations.
37      */
38     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             return PEAR::raiseError('Type of diff is unsupported');
55         }
56
57         if ($mode == 'autodetect') {
58             $context = strpos($diff, '***');
59             $unified = strpos($diff, '---');
60             if ($context === $unified) {
61                 return PEAR::raiseError('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     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 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 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 Text_Diff_Op_delete($diff1);
125                 } else {
126                     $edits[] = new 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     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 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 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 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 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 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 Text_Diff_Op_delete($diff2);
240                     break;
241                 }
242             }
243         }
244
245         return $edits;
246     }
247
248 }