@@ -37,6 +37,18 @@ class ListDiff extends HtmlDiff
37
37
* @var string
38
38
*/
39
39
protected $ listType ;
40
+
41
+ /**
42
+ * Used to hold what type of list the old list is.
43
+ * @var string
44
+ */
45
+ protected $ oldListType ;
46
+
47
+ /**
48
+ * Used to hold what type of list the new list is.
49
+ * @var string
50
+ */
51
+ protected $ newListType ;
40
52
41
53
/**
42
54
* Hold the old/new content of the content of the list.
@@ -61,6 +73,18 @@ class ListDiff extends HtmlDiff
61
73
* @var array
62
74
*/
63
75
protected $ listsIndex ;
76
+
77
+ /**
78
+ * Array that holds the index of all content outside of the array. Format is array(index => content).
79
+ * @var array
80
+ */
81
+ protected $ contentIndex = array ();
82
+
83
+ /**
84
+ * Holds the order and data on each list/content block within this list.
85
+ * @var array
86
+ */
87
+ protected $ diffOrderIndex = array ();
64
88
65
89
/**
66
90
* We're using the same functions as the parent in build() to get us to the point of
@@ -90,20 +114,86 @@ protected function diffListContent()
90
114
* Format this to only have the list contents, outside of the array.
91
115
*/
92
116
$ this ->formatThisListContent ();
117
+
118
+ /* Build an index of content outside of list tags.
119
+ */
120
+ $ this ->indexContent ();
121
+
93
122
/* In cases where we're dealing with nested lists,
94
123
* make sure we use placeholders to replace the nested lists
95
124
*/
96
125
$ this ->replaceListIsolatedDiffTags ();
126
+
97
127
/* Build a list of matches we can reference when we diff the contents of the lists.
98
128
* This is needed so that we each NEW list node is matched against the best possible OLD list node/
99
129
* It helps us determine whether the list was added, removed, or changed.
100
130
*/
101
131
$ this ->matchAndCompareLists ();
102
- /* Go through the list of matches, and diff the contents of each.
132
+
133
+ /* Go through the list of matches, content, and diff each.
103
134
* Any nested lists would be sent to parent's diffList function, which creates a new listDiff class.
104
135
*/
105
136
$ this ->diff ();
106
137
}
138
+
139
+ /**
140
+ * This function is used to populate both contentIndex and diffOrderIndex arrays for use in the diff function.
141
+ */
142
+ protected function indexContent ()
143
+ {
144
+ $ this ->contentIndex = array ();
145
+ $ this ->diffOrderIndex = array ('new ' => array (), 'old ' => array ());
146
+ foreach ($ this ->list as $ type => $ list ) {
147
+
148
+ $ this ->contentIndex [$ type ] = array ();
149
+ $ depth = 0 ;
150
+ $ parentList = 0 ;
151
+ $ position = 0 ;
152
+ $ newBlock = true ;
153
+ $ listCount = 0 ;
154
+ $ contentCount = 0 ;
155
+ foreach ($ list as $ key => $ word ) {
156
+ if (!$ parentList && $ this ->isOpeningListTag ($ word )) {
157
+ $ depth ++;
158
+
159
+ $ this ->diffOrderIndex [$ type ][] = array ('type ' => 'list ' , 'position ' => $ listCount , 'index ' => $ key );
160
+ $ listCount ++;
161
+ continue ;
162
+ }
163
+
164
+ if (!$ parentList && $ this ->isClosingListTag ($ word )) {
165
+ $ depth --;
166
+
167
+ if ($ depth == 0 ) {
168
+ $ newBlock = true ;
169
+ }
170
+ continue ;
171
+ }
172
+
173
+ if ($ this ->isOpeningIsolatedDiffTag ($ word )) {
174
+ $ parentList ++;
175
+ }
176
+
177
+ if ($ this ->isClosingIsolatedDiffTag ($ word )) {
178
+ $ parentList --;
179
+ }
180
+
181
+ if ($ depth == 0 ) {
182
+ if ($ newBlock && !array_key_exists ($ contentCount , $ this ->contentIndex [$ type ])) {
183
+ $ this ->diffOrderIndex [$ type ][] = array ('type ' => 'content ' , 'position ' => $ contentCount , 'index ' => $ key );
184
+
185
+ $ position = $ contentCount ;
186
+ $ this ->contentIndex [$ type ][$ position ] = '' ;
187
+ $ contentCount ++;
188
+ }
189
+
190
+ $ this ->contentIndex [$ type ][$ position ] .= $ word ;
191
+ }
192
+
193
+ $ newBlock = false ;
194
+ }
195
+ }
196
+ }
107
197
108
198
/*
109
199
* This function is used to remove the wrapped ul, ol, or dl characters from this list
@@ -123,6 +213,8 @@ protected function formatThisListContent()
123
213
? $ this ->formatList ($ values [0 ], $ item ['type ' ])
124
214
: array ();
125
215
}
216
+
217
+ $ this ->listType = $ this ->newListType ?: $ this ->oldListType ;
126
218
}
127
219
128
220
/**
@@ -139,8 +231,12 @@ protected function formatList(array $arrayData, $index = 'old')
139
231
if (array_key_exists ($ openingTag , $ this ->isolatedDiffTags ) &&
140
232
array_key_exists ($ closingTag , $ this ->isolatedDiffTags )
141
233
) {
142
- if ($ index == 'old ' ) {
143
- $ this ->listType = $ this ->getAndStripTag ($ arrayData [0 ]);
234
+ if ($ index == 'new ' && $ this ->isOpeningTag ($ arrayData [0 ])) {
235
+ $ this ->newListType = $ this ->getAndStripTag ($ arrayData [0 ]);
236
+ }
237
+
238
+ if ($ index == 'old ' && $ this ->isOpeningTag ($ arrayData [0 ])) {
239
+ $ this ->oldListType = $ this ->getAndStripTag ($ arrayData [0 ]);
144
240
}
145
241
146
242
array_shift ($ arrayData );
@@ -181,27 +277,48 @@ protected function matchAndCompareLists()
181
277
*/
182
278
$ this ->compareChildLists ();
183
279
}
184
-
280
+
281
+ /**
282
+ * Creates matches for lists.
283
+ */
185
284
protected function compareChildLists ()
285
+ {
286
+ $ this ->createNewOldMatches ($ this ->childLists , $ this ->textMatches , 'content ' );
287
+ }
288
+
289
+ /**
290
+ * Abstracted function used to match items in an array.
291
+ * This is used primarily for populating lists matches.
292
+ *
293
+ * @param array $listArray
294
+ * @param array $resultArray
295
+ * @param string|null $column
296
+ */
297
+ protected function createNewOldMatches (&$ listArray , &$ resultArray , $ column = null )
186
298
{
187
299
// Always compare the new against the old.
188
300
// Compare each new string against each old string.
189
301
$ bestMatchPercentages = array ();
190
-
191
- foreach ($ this -> childLists ['new ' ] as $ thisKey => $ thisList ) {
302
+
303
+ foreach ($ listArray ['new ' ] as $ thisKey => $ thisList ) {
192
304
$ bestMatchPercentages [$ thisKey ] = array ();
193
- foreach ($ this -> childLists ['old ' ] as $ thatKey => $ thatList ) {
305
+ foreach ($ listArray ['old ' ] as $ thatKey => $ thatList ) {
194
306
// Save the percent amount each new list content compares against the old list content.
195
- similar_text ($ thisList ['content ' ], $ thatList ['content ' ], $ percentage );
307
+ similar_text (
308
+ $ column ? $ thisList [$ column ] : $ thisList ,
309
+ $ column ? $ thatList [$ column ] : $ thatList ,
310
+ $ percentage
311
+ );
312
+
196
313
$ bestMatchPercentages [$ thisKey ][] = $ percentage ;
197
314
}
198
315
}
199
-
316
+
200
317
// Sort each array by value, highest percent to lowest percent.
201
318
foreach ($ bestMatchPercentages as &$ thisMatch ) {
202
319
arsort ($ thisMatch );
203
320
}
204
-
321
+
205
322
// Build matches.
206
323
$ matches = array ();
207
324
$ taken = array ();
@@ -258,28 +375,30 @@ function ($v) use ($percent) {
258
375
}
259
376
}
260
377
}
261
-
378
+
262
379
$ matches [] = array ('new ' => $ item , 'old ' => $ highestMatchKey > -1 ? $ highestMatchKey : null );
263
380
if ($ highestMatchKey > -1 ) {
264
381
$ taken [] = $ highestMatchKey ;
265
382
$ takenItems [] = $ takenItemKey ;
266
383
}
267
384
}
385
+
386
+
268
387
269
388
/* Checking for removed items. Basically, if a list item from the old lists is removed
270
389
* it will not be accounted for, and will disappear in the results altogether.
271
390
* Loop through all the old lists, any that has not been added, will be added as:
272
391
* array( new => null, old => oldItemId )
273
392
*/
274
393
$ matchColumns = $ this ->getArrayColumn ($ matches , 'old ' );
275
- foreach ($ this -> childLists ['old ' ] as $ thisKey => $ thisList ) {
394
+ foreach ($ listArray ['old ' ] as $ thisKey => $ thisList ) {
276
395
if (!in_array ($ thisKey , $ matchColumns )) {
277
396
$ matches [] = array ('new ' => null , 'old ' => $ thisKey );
278
397
}
279
398
}
280
-
399
+
281
400
// Save the matches.
282
- $ this -> textMatches = $ matches ;
401
+ $ resultArray = $ matches ;
283
402
}
284
403
285
404
/**
@@ -314,32 +433,80 @@ protected function buildChildLists()
314
433
* Build the content of the class.
315
434
*/
316
435
protected function diff ()
317
- {
436
+ {
318
437
// Add the opening parent node from listType. So if ol, <ol>, etc.
319
438
$ this ->content = $ this ->addListTypeWrapper ();
320
- foreach ($ this ->textMatches as $ key => $ matches ) {
321
-
322
- $ oldText = $ matches ['old ' ] !== null ? $ this ->childLists ['old ' ][$ matches ['old ' ]] : '' ;
323
- $ newText = $ matches ['new ' ] !== null ? $ this ->childLists ['new ' ][$ matches ['new ' ]] : '' ;
324
-
325
- // Add the opened and closed the list
326
- $ this ->content .= "<li> " ;
327
- // Process any placeholders, if they exist.
328
- // Placeholders would be nested lists (a nested ol, ul, dl for example).
329
- $ this ->content .= $ this ->processPlaceholders (
330
- $ this ->diffElements (
331
- $ this ->convertListContentArrayToString ($ oldText ),
332
- $ this ->convertListContentArrayToString ($ newText ),
333
- false
334
- ),
335
- $ matches
336
- );
337
- $ this ->content .= "</li> " ;
439
+
440
+ $ oldIndexCount = 0 ;
441
+ foreach ($ this ->diffOrderIndex ['new ' ] as $ key => $ index ) {
442
+
443
+ if ($ index ['type ' ] == "list " ) {
444
+ $ match = $ this ->getArrayByColumnValue ($ this ->textMatches , 'new ' , $ index ['position ' ]);
445
+ $ newList = $ this ->childLists ['new ' ][$ match ['new ' ]];
446
+ $ oldList = array_key_exists ($ match ['old ' ], $ this ->childLists ['old ' ])
447
+ ? $ this ->childLists ['old ' ][$ match ['old ' ]]
448
+ : '' ;
449
+
450
+ $ content = "<li> " ;
451
+ $ content .= $ this ->processPlaceholders (
452
+ $ this ->diffElements (
453
+ $ this ->convertListContentArrayToString ($ oldList ),
454
+ $ this ->convertListContentArrayToString ($ newList ),
455
+ false
456
+ ),
457
+ $ match
458
+ );
459
+ $ content .= "</li> " ;
460
+ $ this ->content .= $ content ;
461
+ }
462
+
463
+ if ($ index ['type ' ] == 'content ' ) {
464
+ $ newContent = $ this ->contentIndex ['new ' ][$ index ['position ' ]];
465
+
466
+ $ oldDiffOrderIndexMatch = array_key_exists ($ oldIndexCount , $ this ->diffOrderIndex ['old ' ])
467
+ ? $ this ->diffOrderIndex ['old ' ][$ oldIndexCount ]
468
+ : '' ;
469
+
470
+ $ oldContent = $ oldDiffOrderIndexMatch && array_key_exists ($ oldDiffOrderIndexMatch ['position ' ], $ this ->contentIndex ['old ' ])
471
+ ? $ this ->contentIndex ['old ' ][$ oldDiffOrderIndexMatch ['position ' ]]
472
+ : '' ;
473
+
474
+ $ diffObject = new HtmlDiff ($ oldContent , $ newContent );
475
+ $ content = $ diffObject ->build ();
476
+ $ this ->content .= $ content ;
477
+ }
478
+
479
+ $ oldIndexCount ++;
338
480
}
339
481
340
482
// Add the closing parent node from listType. So if ol, </ol>, etc.
341
483
$ this ->content .= $ this ->addListTypeWrapper (false );
342
484
}
485
+
486
+ /**
487
+ * This function replaces array_column function in PHP for older versions of php.
488
+ *
489
+ * @param array $parentArray
490
+ * @param string $column
491
+ * @param mixed $value
492
+ * @param boolean $allMatches
493
+ * @return array|boolean
494
+ */
495
+ protected function getArrayByColumnValue ($ parentArray , $ column , $ value , $ allMatches = false )
496
+ {
497
+ $ returnArray = array ();
498
+ foreach ($ parentArray as $ array ) {
499
+ if (array_key_exists ($ column , $ array ) && $ array [$ column ] == $ value ) {
500
+ if ($ allMatches ) {
501
+ $ returnArray [] = $ array ;
502
+ } else {
503
+ return $ array ;
504
+ }
505
+ }
506
+ }
507
+
508
+ return $ allMatches ? $ returnArray : false ;
509
+ }
343
510
344
511
/**
345
512
* Converts the list (li) content arrays to string.
@@ -564,7 +731,7 @@ protected function getListsContent(array $contentArray, $stripTags = true)
564
731
$ arrayDepth = 0 ;
565
732
$ nestedCount = array ();
566
733
foreach ($ contentArray as $ index => $ word ) {
567
-
734
+
568
735
if ($ this ->isOpeningListTag ($ word )) {
569
736
$ arrayDepth ++;
570
737
if (!array_key_exists ($ arrayDepth , $ nestedCount )) {
0 commit comments