Skip to content

Commit 0a3b65f

Browse files
committed
Merge pull request #19 from caxy/feature-list_diffing-new
Feature list diffing new
2 parents 8db1657 + cd4a8ec commit 0a3b65f

File tree

3 files changed

+207
-37
lines changed

3 files changed

+207
-37
lines changed

demo/demo.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@ demo.controller('diffCtrl', ['$scope', '$http', '$sce', '$timeout', function ($s
3737
$scope.waiting = false;
3838
$scope.loading = true;
3939
$http.post('index.php', { oldText: $scope.oldText, newText: $scope.newText })
40-
.success(function (data) {
41-
$scope.diff = data.diff;
40+
.then(function (response) {
41+
$scope.diff = response.data.hasOwnProperty('diff') ? response.data.diff : response.data;
4242
$scope.loading = false;
43+
})
44+
.catch(function (response) {
45+
console.error('Gists error', response.status, response.data);
4346
});
4447
};
4548

demo/index.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
'Caxy/HtmlDiff/HtmlDiff',
1111
'Caxy/HtmlDiff/Match',
1212
'Caxy/HtmlDiff/Operation',
13-
'Caxy/HtmlDiff/ListDiff'
13+
'Caxy/HtmlDiff/ListDiff',
1414
);
1515

1616
foreach ($classes as $class) {

lib/Caxy/HtmlDiff/ListDiff.php

Lines changed: 201 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ class ListDiff extends HtmlDiff
3737
* @var string
3838
*/
3939
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;
4052

4153
/**
4254
* Hold the old/new content of the content of the list.
@@ -61,6 +73,18 @@ class ListDiff extends HtmlDiff
6173
* @var array
6274
*/
6375
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();
6488

6589
/**
6690
* 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()
90114
* Format this to only have the list contents, outside of the array.
91115
*/
92116
$this->formatThisListContent();
117+
118+
/* Build an index of content outside of list tags.
119+
*/
120+
$this->indexContent();
121+
93122
/* In cases where we're dealing with nested lists,
94123
* make sure we use placeholders to replace the nested lists
95124
*/
96125
$this->replaceListIsolatedDiffTags();
126+
97127
/* Build a list of matches we can reference when we diff the contents of the lists.
98128
* This is needed so that we each NEW list node is matched against the best possible OLD list node/
99129
* It helps us determine whether the list was added, removed, or changed.
100130
*/
101131
$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.
103134
* Any nested lists would be sent to parent's diffList function, which creates a new listDiff class.
104135
*/
105136
$this->diff();
106137
}
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+
}
107197

108198
/*
109199
* This function is used to remove the wrapped ul, ol, or dl characters from this list
@@ -123,6 +213,8 @@ protected function formatThisListContent()
123213
? $this->formatList($values[0], $item['type'])
124214
: array();
125215
}
216+
217+
$this->listType = $this->newListType ?: $this->oldListType;
126218
}
127219

128220
/**
@@ -139,8 +231,12 @@ protected function formatList(array $arrayData, $index = 'old')
139231
if (array_key_exists($openingTag, $this->isolatedDiffTags) &&
140232
array_key_exists($closingTag, $this->isolatedDiffTags)
141233
) {
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]);
144240
}
145241

146242
array_shift($arrayData);
@@ -181,27 +277,48 @@ protected function matchAndCompareLists()
181277
*/
182278
$this->compareChildLists();
183279
}
184-
280+
281+
/**
282+
* Creates matches for lists.
283+
*/
185284
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)
186298
{
187299
// Always compare the new against the old.
188300
// Compare each new string against each old string.
189301
$bestMatchPercentages = array();
190-
191-
foreach ($this->childLists['new'] as $thisKey => $thisList) {
302+
303+
foreach ($listArray['new'] as $thisKey => $thisList) {
192304
$bestMatchPercentages[$thisKey] = array();
193-
foreach ($this->childLists['old'] as $thatKey => $thatList) {
305+
foreach ($listArray['old'] as $thatKey => $thatList) {
194306
// 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+
196313
$bestMatchPercentages[$thisKey][] = $percentage;
197314
}
198315
}
199-
316+
200317
// Sort each array by value, highest percent to lowest percent.
201318
foreach ($bestMatchPercentages as &$thisMatch) {
202319
arsort($thisMatch);
203320
}
204-
321+
205322
// Build matches.
206323
$matches = array();
207324
$taken = array();
@@ -258,28 +375,30 @@ function ($v) use ($percent) {
258375
}
259376
}
260377
}
261-
378+
262379
$matches[] = array('new' => $item, 'old' => $highestMatchKey > -1 ? $highestMatchKey : null);
263380
if ($highestMatchKey > -1) {
264381
$taken[] = $highestMatchKey;
265382
$takenItems[] = $takenItemKey;
266383
}
267384
}
385+
386+
268387

269388
/* Checking for removed items. Basically, if a list item from the old lists is removed
270389
* it will not be accounted for, and will disappear in the results altogether.
271390
* Loop through all the old lists, any that has not been added, will be added as:
272391
* array( new => null, old => oldItemId )
273392
*/
274393
$matchColumns = $this->getArrayColumn($matches, 'old');
275-
foreach ($this->childLists['old'] as $thisKey => $thisList) {
394+
foreach ($listArray['old'] as $thisKey => $thisList) {
276395
if (!in_array($thisKey, $matchColumns)) {
277396
$matches[] = array('new' => null, 'old' => $thisKey);
278397
}
279398
}
280-
399+
281400
// Save the matches.
282-
$this->textMatches = $matches;
401+
$resultArray = $matches;
283402
}
284403

285404
/**
@@ -314,32 +433,80 @@ protected function buildChildLists()
314433
* Build the content of the class.
315434
*/
316435
protected function diff()
317-
{
436+
{
318437
// Add the opening parent node from listType. So if ol, <ol>, etc.
319438
$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++;
338480
}
339481

340482
// Add the closing parent node from listType. So if ol, </ol>, etc.
341483
$this->content .= $this->addListTypeWrapper(false);
342484
}
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+
}
343510

344511
/**
345512
* Converts the list (li) content arrays to string.
@@ -564,7 +731,7 @@ protected function getListsContent(array $contentArray, $stripTags = true)
564731
$arrayDepth = 0;
565732
$nestedCount = array();
566733
foreach ($contentArray as $index => $word) {
567-
734+
568735
if ($this->isOpeningListTag($word)) {
569736
$arrayDepth++;
570737
if (!array_key_exists($arrayDepth, $nestedCount)) {

0 commit comments

Comments
 (0)