16 * @since 0.2.0 |
16 * @since 0.2.0 |
17 * @access private |
17 * @access private |
18 */ |
18 */ |
19 class Text_Diff_Engine_string { |
19 class Text_Diff_Engine_string { |
20 |
20 |
21 /** |
21 /** |
22 * Parses a unified or context diff. |
22 * Parses a unified or context diff. |
23 * |
23 * |
24 * First param contains the whole diff and the second can be used to force |
24 * First param contains the whole diff and the second can be used to force |
25 * a specific diff type. If the second parameter is 'autodetect', the |
25 * a specific diff type. If the second parameter is 'autodetect', the |
26 * diff will be examined to find out which type of diff this is. |
26 * diff will be examined to find out which type of diff this is. |
27 * |
27 * |
28 * @param string $diff The diff content. |
28 * @param string $diff The diff content. |
29 * @param string $mode The diff mode of the content in $diff. One of |
29 * @param string $mode The diff mode of the content in $diff. One of |
30 * 'context', 'unified', or 'autodetect'. |
30 * 'context', 'unified', or 'autodetect'. |
31 * |
31 * |
32 * @return array List of all diff operations. |
32 * @return array List of all diff operations. |
33 */ |
33 */ |
34 function diff($diff, $mode = 'autodetect') |
34 function diff($diff, $mode = 'autodetect') |
35 { |
35 { |
36 if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { |
36 if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { |
37 die_friendly('Text_Diff', '<p>Type of diff is unsupported</p>'); |
37 die_friendly('Text_Diff', '<p>Type of diff is unsupported</p>'); |
38 } |
38 } |
39 |
39 |
40 if ($mode == 'autodetect') { |
40 if ($mode == 'autodetect') { |
41 $context = strpos($diff, '***'); |
41 $context = strpos($diff, '***'); |
42 $unified = strpos($diff, '---'); |
42 $unified = strpos($diff, '---'); |
43 if ($context === $unified) { |
43 if ($context === $unified) { |
44 die_friendly('Text_Diff', '<p>Type of diff could not be detected</p>'); |
44 die_friendly('Text_Diff', '<p>Type of diff could not be detected</p>'); |
45 } elseif ($context === false || $context === false) { |
45 } elseif ($context === false || $context === false) { |
46 $mode = $context !== false ? 'context' : 'unified'; |
46 $mode = $context !== false ? 'context' : 'unified'; |
47 } else { |
47 } else { |
48 $mode = $context < $unified ? 'context' : 'unified'; |
48 $mode = $context < $unified ? 'context' : 'unified'; |
49 } |
49 } |
50 } |
50 } |
51 |
51 |
52 // split by new line and remove the diff header |
52 // split by new line and remove the diff header |
53 $diff = explode("\n", $diff); |
53 $diff = explode("\n", $diff); |
54 array_shift($diff); |
54 array_shift($diff); |
55 array_shift($diff); |
55 array_shift($diff); |
56 |
56 |
57 if ($mode == 'context') { |
57 if ($mode == 'context') { |
58 return $this->parseContextDiff($diff); |
58 return $this->parseContextDiff($diff); |
59 } else { |
59 } else { |
60 return $this->parseUnifiedDiff($diff); |
60 return $this->parseUnifiedDiff($diff); |
61 } |
61 } |
62 } |
62 } |
63 |
63 |
64 /** |
64 /** |
65 * Parses an array containing the unified diff. |
65 * Parses an array containing the unified diff. |
66 * |
66 * |
67 * @param array $diff Array of lines. |
67 * @param array $diff Array of lines. |
68 * |
68 * |
69 * @return array List of all diff operations. |
69 * @return array List of all diff operations. |
70 */ |
70 */ |
71 function parseUnifiedDiff($diff) |
71 function parseUnifiedDiff($diff) |
72 { |
72 { |
73 $edits = array(); |
73 $edits = array(); |
74 $end = count($diff) - 1; |
74 $end = count($diff) - 1; |
75 for ($i = 0; $i < $end;) { |
75 for ($i = 0; $i < $end;) { |
76 $diff1 = array(); |
76 $diff1 = array(); |
77 switch (substr($diff[$i], 0, 1)) { |
77 switch (substr($diff[$i], 0, 1)) { |
78 case ' ': |
78 case ' ': |
79 do { |
79 do { |
80 $diff1[] = substr($diff[$i], 1); |
80 $diff1[] = substr($diff[$i], 1); |
81 } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); |
81 } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); |
82 $edits[] = &new Text_Diff_Op_copy($diff1); |
82 $edits[] = &new Text_Diff_Op_copy($diff1); |
83 break; |
83 break; |
84 case '+': |
84 case '+': |
85 // get all new lines |
85 // get all new lines |
86 do { |
86 do { |
87 $diff1[] = substr($diff[$i], 1); |
87 $diff1[] = substr($diff[$i], 1); |
88 } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); |
88 } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); |
89 $edits[] = &new Text_Diff_Op_add($diff1); |
89 $edits[] = &new Text_Diff_Op_add($diff1); |
90 break; |
90 break; |
91 case '-': |
91 case '-': |
92 // get changed or removed lines |
92 // get changed or removed lines |
93 $diff2 = array(); |
93 $diff2 = array(); |
94 do { |
94 do { |
95 $diff1[] = substr($diff[$i], 1); |
95 $diff1[] = substr($diff[$i], 1); |
96 } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); |
96 } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); |
97 while ($i < $end && substr($diff[$i], 0, 1) == '+') { |
97 while ($i < $end && substr($diff[$i], 0, 1) == '+') { |
98 $diff2[] = substr($diff[$i++], 1); |
98 $diff2[] = substr($diff[$i++], 1); |
99 } |
99 } |
100 if (count($diff2) == 0) { |
100 if (count($diff2) == 0) { |
101 $edits[] = &new Text_Diff_Op_delete($diff1); |
101 $edits[] = &new Text_Diff_Op_delete($diff1); |
102 } else { |
102 } else { |
103 $edits[] = &new Text_Diff_Op_change($diff1, $diff2); |
103 $edits[] = &new Text_Diff_Op_change($diff1, $diff2); |
104 } |
104 } |
105 break; |
105 break; |
106 default: |
106 default: |
107 $i++; |
107 $i++; |
108 break; |
108 break; |
109 } |
109 } |
110 } |
110 } |
111 return $edits; |
111 return $edits; |
112 } |
112 } |
113 |
113 |
114 /** |
114 /** |
115 * Parses an array containing the context diff. |
115 * Parses an array containing the context diff. |
116 * |
116 * |
117 * @param array $diff Array of lines. |
117 * @param array $diff Array of lines. |
118 * |
118 * |
119 * @return array List of all diff operations. |
119 * @return array List of all diff operations. |
120 */ |
120 */ |
121 function parseContextDiff(&$diff) |
121 function parseContextDiff(&$diff) |
122 { |
122 { |
123 $edits = array(); |
123 $edits = array(); |
124 $i = $max_i = $j = $max_j = 0; |
124 $i = $max_i = $j = $max_j = 0; |
125 $end = count($diff) - 1; |
125 $end = count($diff) - 1; |
126 while ($i < $end && $j < $end) { |
126 while ($i < $end && $j < $end) { |
127 while ($i >= $max_i && $j >= $max_j) { |
127 while ($i >= $max_i && $j >= $max_j) { |
128 // find the boundaries of the diff output of the two files |
128 // find the boundaries of the diff output of the two files |
129 for ($i = $j; |
129 for ($i = $j; |
130 $i < $end && substr($diff[$i], 0, 3) == '***'; |
130 $i < $end && substr($diff[$i], 0, 3) == '***'; |
131 $i++); |
131 $i++); |
132 for ($max_i = $i; |
132 for ($max_i = $i; |
133 $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; |
133 $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; |
134 $max_i++); |
134 $max_i++); |
135 for ($j = $max_i; |
135 for ($j = $max_i; |
136 $j < $end && substr($diff[$j], 0, 3) == '---'; |
136 $j < $end && substr($diff[$j], 0, 3) == '---'; |
137 $j++); |
137 $j++); |
138 for ($max_j = $j; |
138 for ($max_j = $j; |
139 $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; |
139 $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; |
140 $max_j++); |
140 $max_j++); |
141 } |
141 } |
142 |
142 |
143 // find what hasn't been changed |
143 // find what hasn't been changed |
144 $array = array(); |
144 $array = array(); |
145 while ($i < $max_i && |
145 while ($i < $max_i && |
146 $j < $max_j && |
146 $j < $max_j && |
147 strcmp($diff[$i], $diff[$j]) == 0) { |
147 strcmp($diff[$i], $diff[$j]) == 0) { |
148 $array[] = substr($diff[$i], 2); |
148 $array[] = substr($diff[$i], 2); |
149 $i++; |
149 $i++; |
150 $j++; |
150 $j++; |
151 } |
151 } |
152 while ($i < $max_i && ($max_j-$j) <= 1) { |
152 while ($i < $max_i && ($max_j-$j) <= 1) { |
153 if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { |
153 if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { |
154 break; |
154 break; |
155 } |
155 } |
156 $array[] = substr($diff[$i++], 2); |
156 $array[] = substr($diff[$i++], 2); |
157 } |
157 } |
158 while ($j < $max_j && ($max_i-$i) <= 1) { |
158 while ($j < $max_j && ($max_i-$i) <= 1) { |
159 if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { |
159 if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { |
160 break; |
160 break; |
161 } |
161 } |
162 $array[] = substr($diff[$j++], 2); |
162 $array[] = substr($diff[$j++], 2); |
163 } |
163 } |
164 if (count($array) > 0) { |
164 if (count($array) > 0) { |
165 $edits[] = &new Text_Diff_Op_copy($array); |
165 $edits[] = &new Text_Diff_Op_copy($array); |
166 } |
166 } |
167 |
167 |
168 if ($i < $max_i) { |
168 if ($i < $max_i) { |
169 $diff1 = array(); |
169 $diff1 = array(); |
170 switch (substr($diff[$i], 0, 1)) { |
170 switch (substr($diff[$i], 0, 1)) { |
171 case '!': |
171 case '!': |
172 $diff2 = array(); |
172 $diff2 = array(); |
173 do { |
173 do { |
174 $diff1[] = substr($diff[$i], 2); |
174 $diff1[] = substr($diff[$i], 2); |
175 if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { |
175 if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { |
176 $diff2[] = substr($diff[$j++], 2); |
176 $diff2[] = substr($diff[$j++], 2); |
177 } |
177 } |
178 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); |
178 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); |
179 $edits[] = &new Text_Diff_Op_change($diff1, $diff2); |
179 $edits[] = &new Text_Diff_Op_change($diff1, $diff2); |
180 break; |
180 break; |
181 case '+': |
181 case '+': |
182 do { |
182 do { |
183 $diff1[] = substr($diff[$i], 2); |
183 $diff1[] = substr($diff[$i], 2); |
184 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); |
184 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); |
185 $edits[] = &new Text_Diff_Op_add($diff1); |
185 $edits[] = &new Text_Diff_Op_add($diff1); |
186 break; |
186 break; |
187 case '-': |
187 case '-': |
188 do { |
188 do { |
189 $diff1[] = substr($diff[$i], 2); |
189 $diff1[] = substr($diff[$i], 2); |
190 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); |
190 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); |
191 $edits[] = &new Text_Diff_Op_delete($diff1); |
191 $edits[] = &new Text_Diff_Op_delete($diff1); |
192 break; |
192 break; |
193 } |
193 } |
194 } |
194 } |
195 |
195 |
196 if ($j < $max_j) { |
196 if ($j < $max_j) { |
197 $diff2 = array(); |
197 $diff2 = array(); |
198 switch (substr($diff[$j], 0, 1)) { |
198 switch (substr($diff[$j], 0, 1)) { |
199 case '+': |
199 case '+': |
200 do { |
200 do { |
201 $diff2[] = substr($diff[$j++], 2); |
201 $diff2[] = substr($diff[$j++], 2); |
202 } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); |
202 } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); |
203 $edits[] = &new Text_Diff_Op_add($diff2); |
203 $edits[] = &new Text_Diff_Op_add($diff2); |
204 break; |
204 break; |
205 case '-': |
205 case '-': |
206 do { |
206 do { |
207 $diff2[] = substr($diff[$j++], 2); |
207 $diff2[] = substr($diff[$j++], 2); |
208 } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); |
208 } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); |
209 $edits[] = &new Text_Diff_Op_delete($diff2); |
209 $edits[] = &new Text_Diff_Op_delete($diff2); |
210 break; |
210 break; |
211 } |
211 } |
212 } |
212 } |
213 } |
213 } |
214 return $edits; |
214 return $edits; |
215 } |
215 } |
216 } |
216 } |