4
4
5
5
namespace jblond \Diff \Renderer ;
6
6
7
+ use jblond \Diff \SequenceMatcher ;
8
+
7
9
/**
8
10
* Base renderer for rendering diffs for PHP DiffLib.
9
11
*
@@ -135,7 +137,7 @@ protected function renderSequences(): array
135
137
136
138
if (($ tag == 'replace ' ) && ($ blockSizeOld == $ blockSizeNew )) {
137
139
// Inline differences between old and new block.
138
- $ this ->markInlineChange ($ oldText , $ newText , $ startOld , $ endOld , $ startNew );
140
+ $ this ->markInlineChanges ($ oldText , $ newText , $ startOld , $ endOld , $ startNew );
139
141
}
140
142
141
143
$ lastBlock = $ this ->appendChangesArray ($ blocks , $ tag , $ startOld , $ startNew );
@@ -167,6 +169,118 @@ protected function renderSequences(): array
167
169
return $ changes ;
168
170
}
169
171
172
+ /**
173
+ * Surround inline changes with markers.
174
+ *
175
+ * @param array $oldText Collection of lines of old text.
176
+ * @param array $newText Collection of lines of new text.
177
+ * @param int $startOld First line of the block in old to replace.
178
+ * @param int $endOld last line of the block in old to replace.
179
+ * @param int $startNew First line of the block in new to replace.
180
+ */
181
+ private function markInlineChanges (
182
+ array &$ oldText ,
183
+ array &$ newText ,
184
+ int $ startOld ,
185
+ int $ endOld ,
186
+ int $ startNew
187
+ ): void {
188
+ if ($ this ->options ['inlineMarking ' ] < self ::CHANGE_LEVEL_LINE ) {
189
+ $ this ->markInnerChange ($ oldText , $ newText , $ startOld , $ endOld , $ startNew );
190
+
191
+ return ;
192
+ }
193
+
194
+ if ($ this ->options ['inlineMarking ' ] == self ::CHANGE_LEVEL_LINE ) {
195
+ $ this ->markOuterChange ($ oldText , $ newText , $ startOld , $ endOld , $ startNew );
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Add markers around inline changes between old and new text.
201
+ *
202
+ * Each line of the old and new text is evaluated.
203
+ * When a line of old differs from the same line of new, a marker is inserted into both lines, just before the first
204
+ * different character/word. A second marker is added just before the following character/word which matches again.
205
+ *
206
+ * Setting parameter changeType to self::CHANGE_LEVEL_CHAR will mark differences at character level.
207
+ * Other values will mark differences at word level.
208
+ *
209
+ * E.g. Character level.
210
+ * <pre>
211
+ * 1234567890
212
+ * Old => "aa bbc cdd" Start marker inserted at position 4
213
+ * New => "aa 12c cdd" End marker inserted at position 6
214
+ * </pre>
215
+ * E.g. Word level.
216
+ * <pre>
217
+ * 1234567890
218
+ * Old => "aa bbc cdd" Start marker inserted at position 4
219
+ * New => "aa 12c cdd" End marker inserted at position 7
220
+ * </pre>
221
+ *
222
+ * @param array $oldText Collection of lines of old text.
223
+ * @param array $newText Collection of lines of new text.
224
+ * @param int $startOld First line of the block in old to replace.
225
+ * @param int $endOld last line of the block in old to replace.
226
+ * @param int $startNew First line of the block in new to replace.
227
+ */
228
+ private function markInnerChange (array &$ oldText , array &$ newText , int $ startOld , int $ endOld , int $ startNew ): void
229
+ {
230
+ for ($ iterator = 0 ; $ iterator < ($ endOld - $ startOld ); ++$ iterator ) {
231
+ // ChangeType 0: Character Level.
232
+ // ChangeType 1: Word Level.
233
+ $ regex = $ this ->options ['inlineMarking ' ] ? '/\w+|[^\w\s]|\s/u ' : '/.?/u ' ;
234
+
235
+ // Deconstruct the lines into arrays, including new empty element to the end in case a marker needs to be
236
+ // placed as last.
237
+ $ oldLine = $ this ->sequenceToArray ($ regex , $ oldText [$ startOld + $ iterator ]);
238
+ $ newLine = $ this ->sequenceToArray ($ regex , $ newText [$ startNew + $ iterator ]);
239
+ $ oldLine [] = '' ;
240
+ $ newLine [] = '' ;
241
+
242
+ $ sequenceMatcher = new SequenceMatcher ($ oldLine , $ newLine );
243
+ $ opCodes = $ sequenceMatcher ->getGroupedOpCodes ();
244
+
245
+ foreach ($ opCodes as $ group ) {
246
+ foreach ($ group as [$ tag , $ changeStartOld , $ changeEndOld , $ changeStartNew , $ changeEndNew ]) {
247
+ if ($ tag == 'equal ' ) {
248
+ continue ;
249
+ }
250
+ if ($ tag == 'replace ' || $ tag == 'delete ' ) {
251
+ $ oldLine [$ changeStartOld ] = "\0" . $ oldLine [$ changeStartOld ];
252
+ $ oldLine [$ changeEndOld ] = "\1" . $ oldLine [$ changeEndOld ];
253
+ }
254
+ if ($ tag == 'replace ' || $ tag == 'insert ' ) {
255
+ $ newLine [$ changeStartNew ] = "\0" . $ newLine [$ changeStartNew ];
256
+ $ newLine [$ changeEndNew ] = "\1" . $ newLine [$ changeEndNew ];
257
+ }
258
+ }
259
+ }
260
+
261
+ // Reconstruct the lines and overwrite originals.
262
+ $ oldText [$ startOld + $ iterator ] = implode ('' , $ oldLine );
263
+ $ newText [$ startNew + $ iterator ] = implode ('' , $ newLine );
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Split a sequence of characters into an array.
269
+ *
270
+ * Each element of the returned array contains a full pattern match of the regex pattern.
271
+ *
272
+ * @param string $pattern Regex pattern to split by.
273
+ * @param string $sequence The sequence to split.
274
+ *
275
+ * @return array The split sequence.
276
+ */
277
+ public function sequenceToArray (string $ pattern , string $ sequence ): array
278
+ {
279
+ preg_match_all ($ pattern , $ sequence , $ matches );
280
+
281
+ return $ matches [0 ];
282
+ }
283
+
170
284
/**
171
285
* Add markers around inline changes between old and new text.
172
286
*
@@ -187,15 +301,15 @@ protected function renderSequences(): array
187
301
* @param int $endOld last line of the block in old to replace.
188
302
* @param int $startNew First line of the block in new to replace.
189
303
*/
190
- private function markInlineChange (array &$ oldText , array &$ newText , int $ startOld , int $ endOld , int $ startNew )
304
+ private function markOuterChange (array &$ oldText , array &$ newText , int $ startOld , int $ endOld , int $ startNew ): void
191
305
{
192
306
for ($ iterator = 0 ; $ iterator < ($ endOld - $ startOld ); ++$ iterator ) {
193
307
// Check each line in the block for differences.
194
308
$ oldString = $ oldText [$ startOld + $ iterator ];
195
309
$ newString = $ newText [$ startNew + $ iterator ];
196
310
197
311
// Determine the start and end position of the line difference.
198
- [$ start , $ end ] = $ this ->getInlineChange ($ oldString , $ newString );
312
+ [$ start , $ end ] = $ this ->getOuterChange ($ oldString , $ newString );
199
313
if ($ start != 0 || $ end != 0 ) {
200
314
// Changes between the lines exist.
201
315
// Add markers around the changed character sequence in the old string.
@@ -233,7 +347,7 @@ private function markInlineChange(array &$oldText, array &$newText, int $startOl
233
347
*
234
348
* @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
235
349
*/
236
- private function getInlineChange (string $ oldString , string $ newString ): array
350
+ private function getOuterChange (string $ oldString , string $ newString ): array
237
351
{
238
352
$ start = 0 ;
239
353
$ limit = min (mb_strlen ($ oldString ), mb_strlen ($ newString ));
0 commit comments