Passed
Pull Request — master (#31)
by Josh
06:09
created

HtmlDiff::isTablePlaceholder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Caxy\HtmlDiff;
4
5
use Caxy\HtmlDiff\Table\TableDiff;
6
7
class HtmlDiff extends AbstractDiff
8
{
9
    protected $wordIndices;
10
    protected $oldTables;
11
    protected $newTables;
12
    protected $insertSpaceInReplace = false;
13
    protected $newIsolatedDiffTags;
14
    protected $oldIsolatedDiffTags;
15
    protected $isolatedDiffTags = array (
16
        'ol' => '[[REPLACE_ORDERED_LIST]]',
17
        'ul' => '[[REPLACE_UNORDERED_LIST]]',
18
        'sub' => '[[REPLACE_SUB_SCRIPT]]',
19
        'sup' => '[[REPLACE_SUPER_SCRIPT]]',
20
        'dl' => '[[REPLACE_DEFINITION_LIST]]',
21
        'table' => '[[REPLACE_TABLE]]',
22
        'strong' => '[[REPLACE_STRONG]]',
23
        'b' => '[[REPLACE_B]]',
24
        'em' => '[[REPLACE_EM]]',
25
        'i' => '[[REPLACE_I]]',
26
        'a' => '[[REPLACE_A]]',
27
    );
28
    protected $useTableDiffing = true;
29
30
    public function setUseTableDiffing($bool)
31
    {
32
        $this->useTableDiffing = $bool;
33
34
        return $this;
35
    }
36
37
    /**
38
     * @param  boolean  $boolean
39
     * @return HtmlDiff
40
     */
41
    public function setInsertSpaceInReplace($boolean)
42
    {
43
        $this->insertSpaceInReplace = $boolean;
44
45
        return $this;
46
    }
47
48
    /**
49
     * @return boolean
50
     */
51
    public function getInsertSpaceInReplace()
52
    {
53
        return $this->insertSpaceInReplace;
54
    }
55
56 11
    public function build()
57
    {
58 11
        $this->splitInputsToWords();
59 11
        $this->replaceIsolatedDiffTags();
60 11
        $this->indexNewWords();
61
62 11
        $operations = $this->operations();
63 11
        foreach ($operations as $item) {
64 11
            $this->performOperation( $item );
65 11
        }
66
67 11
        return $this->content;
68
    }
69
70 11
    protected function indexNewWords()
71
    {
72 11
        $this->wordIndices = array();
73 11
        foreach ($this->newWords as $i => $word) {
74 11
            if ( $this->isTag( $word ) ) {
75 8
                $word = $this->stripTagAttributes( $word );
76 8
            }
77 11
            if ( isset( $this->wordIndices[ $word ] ) ) {
78 11
                $this->wordIndices[ $word ][] = $i;
79 11
            } else {
80 11
                $this->wordIndices[ $word ] = array( $i );
81
            }
82 11
        }
83 11
    }
84
85 11
    protected function replaceIsolatedDiffTags()
86
    {
87 11
        $this->oldIsolatedDiffTags = $this->createIsolatedDiffTagPlaceholders($this->oldWords);
88 11
        $this->newIsolatedDiffTags = $this->createIsolatedDiffTagPlaceholders($this->newWords);
89 11
    }
90
91 11
    protected function createIsolatedDiffTagPlaceholders(&$words)
92
    {
93 11
        $openIsolatedDiffTags = 0;
94 11
        $isolatedDiffTagIndicies = array();
95 11
        $isolatedDiffTagStart = 0;
96 11
        $currentIsolatedDiffTag = null;
97 11
        foreach ($words as $index => $word) {
98 11
            $openIsolatedDiffTag = $this->isOpeningIsolatedDiffTag($word, $currentIsolatedDiffTag);
99 11
            if ($openIsolatedDiffTag) {
100 11
                if ($openIsolatedDiffTags === 0) {
101 11
                    $isolatedDiffTagStart = $index;
102 11
                }
103 11
                $openIsolatedDiffTags++;
104 11
                $currentIsolatedDiffTag = $openIsolatedDiffTag;
105 11
            } elseif ($openIsolatedDiffTags > 0 && $this->isClosingIsolatedDiffTag($word, $currentIsolatedDiffTag)) {
106 10
                $openIsolatedDiffTags--;
107 10
                if ($openIsolatedDiffTags == 0) {
108 10
                    $isolatedDiffTagIndicies[] = array ('start' => $isolatedDiffTagStart, 'length' => $index - $isolatedDiffTagStart + 1, 'tagType' => $currentIsolatedDiffTag);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 176 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
109 10
                    $currentIsolatedDiffTag = null;
110 10
                }
111 10
            }
112 11
        }
113 11
        $isolatedDiffTagScript = array();
114 11
        $offset = 0;
115 11
        foreach ($isolatedDiffTagIndicies as $isolatedDiffTagIndex) {
116 10
            $start = $isolatedDiffTagIndex['start'] - $offset;
117 10
            $placeholderString = $this->isolatedDiffTags[$isolatedDiffTagIndex['tagType']];
118 10
            $isolatedDiffTagScript[$start] = array_splice($words, $start, $isolatedDiffTagIndex['length'], $placeholderString);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 127 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
119 10
            $offset += $isolatedDiffTagIndex['length'] - 1;
120 11
        }
121
122 11
        return $isolatedDiffTagScript;
123
124
    }
125
126 11 View Code Duplication
    protected function isOpeningIsolatedDiffTag($item, $currentIsolatedDiffTag = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
127
    {
128 11
        $tagsToMatch = $currentIsolatedDiffTag !== null ? array($currentIsolatedDiffTag => $this->isolatedDiffTags[$currentIsolatedDiffTag]) : $this->isolatedDiffTags;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 167 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
129 11
        foreach ($tagsToMatch as $key => $value) {
130 11
            if (preg_match("#<".$key."[^>]*>\\s*#iU", $item)) {
131 11
                return $key;
132
            }
133 11
        }
134
135 11
        return false;
136
    }
137
138 11 View Code Duplication
    protected function isClosingIsolatedDiffTag($item, $currentIsolatedDiffTag = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139
    {
140 11
        $tagsToMatch = $currentIsolatedDiffTag !== null ? array($currentIsolatedDiffTag => $this->isolatedDiffTags[$currentIsolatedDiffTag]) : $this->isolatedDiffTags;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 167 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
141 11
        foreach ($tagsToMatch as $key => $value) {
142 11
            if (preg_match("#</".$key."[^>]*>\\s*#iU", $item)) {
143 10
                return $key;
144
            }
145 11
        }
146
147 11
        return false;
148
    }
149
150 11
    protected function performOperation($operation)
151
    {
152 11
        switch ($operation->action) {
153 11
            case 'equal' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
154 11
            $this->processEqualOperation( $operation );
155 11
            break;
156 9
            case 'delete' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
157 5
            $this->processDeleteOperation( $operation, "diffdel" );
158 5
            break;
159 9
            case 'insert' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
160 8
            $this->processInsertOperation( $operation, "diffins");
161 8
            break;
162 7
            case 'replace':
163 7
            $this->processReplaceOperation( $operation );
164 7
            break;
165
            default:
166
            break;
167 11
        }
168 11
    }
169
170 7
    protected function processReplaceOperation($operation)
171
    {
172 7
        $this->processDeleteOperation( $operation, "diffmod" );
173 7
        $this->processInsertOperation( $operation, "diffmod" );
174 7
    }
175
176 9 View Code Duplication
    protected function processInsertOperation($operation, $cssClass)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
177
    {
178 9
        $text = array();
179 9
        foreach ($this->newWords as $pos => $s) {
180 9
            if ($pos >= $operation->startInNew && $pos < $operation->endInNew) {
181 9
                if (in_array($s, $this->isolatedDiffTags) && isset($this->newIsolatedDiffTags[$pos])) {
182 4
                    foreach ($this->newIsolatedDiffTags[$pos] as $word) {
183 4
                        $text[] = $word;
184 4
                    }
185 4
                } else {
186 9
                    $text[] = $s;
187
                }
188 9
            }
189 9
        }
190 9
        $this->insertTag( "ins", $cssClass, $text );
191 9
    }
192
193 9 View Code Duplication
    protected function processDeleteOperation($operation, $cssClass)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194
    {
195 9
        $text = array();
196 9
        foreach ($this->oldWords as $pos => $s) {
197 9
            if ($pos >= $operation->startInOld && $pos < $operation->endInOld) {
198 9
                if (in_array($s, $this->isolatedDiffTags) && isset($this->oldIsolatedDiffTags[$pos])) {
199 6
                    foreach ($this->oldIsolatedDiffTags[$pos] as $word) {
200 6
                        $text[] = $word;
201 6
                    }
202 6
                } else {
203 8
                    $text[] = $s;
204
                }
205 9
            }
206 9
        }
207 9
        $this->insertTag( "del", $cssClass, $text );
208 9
    }
209
210
    /**
211
     * @param Operation $operation
212
     * @param int       $pos
213
     * @param string    $placeholder
214
     * @param bool      $stripWrappingTags
215
     *
216
     * @return string
217
     */
218 7
    protected function diffIsolatedPlaceholder($operation, $pos, $placeholder, $stripWrappingTags = true)
219
    {
220 7
        $oldText = implode("", $this->findIsolatedDiffTagsInOld($operation, $pos));
221 7
        $newText = implode("", $this->newIsolatedDiffTags[$pos]);
222
223 7
        if ($this->isListPlaceholder($placeholder)) {
224 4
            return $this->diffList($oldText, $newText);
225 5
        } elseif ($this->useTableDiffing && $this->isTablePlaceholder($placeholder)) {
226
            return $this->diffTables($oldText, $newText);
227 5
        } elseif ($this->isLinkPlaceholder($placeholder)) {
228 1
            return $this->diffLinks($oldText, $newText);
229
        }
230
231 4
        return $this->diffElements($oldText, $newText, $stripWrappingTags);
232
    }
233
234 5
    protected function diffElements($oldText, $newText, $stripWrappingTags = true)
235
    {
236 5
        $wrapStart = '';
237 5
        $wrapEnd = '';
238
239 5
        if ($stripWrappingTags) {
240 5
            $pattern = '/(^<[^>]+>)|(<\/[^>]+>$)/i';
241 5
            $matches = array();
242
243 5
            if (preg_match_all($pattern, $newText, $matches)) {
244 5
                $wrapStart = isset($matches[0][0]) ? $matches[0][0] : '';
245 5
                $wrapEnd = isset($matches[0][1]) ? $matches[0][1] : '';
246 5
            }
247 5
            $oldText = preg_replace($pattern, '', $oldText);
248 5
            $newText = preg_replace($pattern, '', $newText);
249 5
        }
250
251 5
        $diff = new HtmlDiff($oldText, $newText, $this->encoding, $this->specialCaseTags, $this->groupDiffs);
252
253 5
        return $wrapStart . $diff->build() . $wrapEnd;
254
    }
255
256 4 View Code Duplication
    protected function diffList($oldText, $newText)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
257
    {
258 4
        $diff = new ListDiffNew($oldText, $newText, $this->encoding, $this->specialCaseTags, $this->groupDiffs);
259 4
        $diff->setMatchThreshold($this->matchThreshold);
260
261 4
        return $diff->build();
262
    }
263
264 View Code Duplication
    protected function diffTables($oldText, $newText)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
265
    {
266
        $diff = new TableDiff($oldText, $newText, $this->encoding, $this->specialCaseTags, $this->groupDiffs);
267
        $diff->setMatchThreshold($this->matchThreshold);
268
269
        return $diff->build();
270
    }
271
272
    /**
273
     * @param string $oldText
274
     * @param string $newText
275
     *
276
     * @return string
277
     */
278 1
    protected function diffLinks($oldText, $newText)
279
    {
280 1
        $oldHref = $this->getAttributeFromTag($oldText, 'href');
281 1
        $newHref = $this->getAttributeFromTag($newText, 'href');
282
283 1
        if ($oldHref != $newHref) {
284 1
            return sprintf(
285 1
                '%s%s',
286 1
                $this->wrapText($oldText, 'del', 'diffmod diff-href'),
287 1
                $this->wrapText($newText, 'ins', 'diffmod diff-href')
288 1
            );
289
        }
290
291 1
        return $this->diffElements($oldText, $newText);
292
    }
293
294 11 View Code Duplication
    protected function processEqualOperation($operation)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
295
    {
296 11
        $result = array();
297 11
        foreach ($this->newWords as $pos => $s) {
298
299 11
            if ($pos >= $operation->startInNew && $pos < $operation->endInNew) {
300 11
                if (in_array($s, $this->isolatedDiffTags) && isset($this->newIsolatedDiffTags[$pos])) {
301
302 7
                    $result[] = $this->diffIsolatedPlaceholder($operation, $pos, $s);
303 7
                } else {
304 11
                    $result[] = $s;
305
                }
306 11
            }
307 11
        }
308 11
        $this->content .= implode( "", $result );
309 11
    }
310
311
    /**
312
     * @param string $text
313
     * @param string $attribute
314
     *
315
     * @return null|string
316
     */
317 1
    protected function getAttributeFromTag($text, $attribute)
318
    {
319 1
        $matches = array();
320 1
        if (preg_match(sprintf('/<a\s+[^>]*%s=([\'"])(.*)\1[^>]*>/i', $attribute), $text, $matches)) {
321 1
            return $matches[2];
322
        }
323
324
        return null;
325
    }
326
327 7
    protected function isListPlaceholder($text)
328
    {
329 7
        return $this->isPlaceholderType($text, array('ol', 'dl', 'ul'));
330
    }
331
332
    /**
333
     * @param string $text
334
     *
335
     * @return bool
336
     */
337 5
    public function isLinkPlaceholder($text)
338
    {
339 5
        return $this->isPlaceholderType($text, 'a');
340
    }
341
342
    /**
343
     * @param string       $text
344
     * @param array|string $types
345
     * @param bool         $strict
346
     *
347
     * @return bool
348
     */
349 7
    protected function isPlaceholderType($text, $types, $strict = true)
350
    {
351 7
        if (!is_array($types)) {
352 5
            $types = array($types);
353 5
        }
354
355 7
        $criteria = array();
356 7
        foreach ($types as $type) {
357 7
            if (isset($this->isolatedDiffTags[$type])) {
358 7
                $criteria[] = $this->isolatedDiffTags[$type];
359 7
            } else {
360
                $criteria[] = $type;
361
            }
362 7
        }
363
364 7
        return in_array($text, $criteria, $strict);
365
    }
366
367 5
    protected function isTablePlaceholder($text)
368
    {
369 5
        return in_array($text, array(
370 5
            $this->isolatedDiffTags['table'],
371 5
        ), true);
372
    }
373
374 7
    protected function findIsolatedDiffTagsInOld($operation, $posInNew)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
375
    {
376 7
        $offset = $posInNew - $operation->startInNew;
377
378 7
        return $this->oldIsolatedDiffTags[$operation->startInOld + $offset];
379
    }
380
381 9
    protected function insertTag($tag, $cssClass, &$words)
382
    {
383 9
        while (true) {
384 9
            if ( count( $words ) == 0 ) {
385 9
                break;
386
            }
387
388 9
            $nonTags = $this->extractConsecutiveWords( $words, 'noTag' );
389
390 9
            $specialCaseTagInjection = '';
391 9
            $specialCaseTagInjectionIsBefore = false;
392
393 9
            if ( count( $nonTags ) != 0 ) {
394 9
                $text = $this->wrapText( implode( "", $nonTags ), $tag, $cssClass );
395 9
                $this->content .= $text;
396 9
            } else {
397 6
                $firstOrDefault = false;
398 6
                foreach ($this->specialCaseOpeningTags as $x) {
399
                    if ( preg_match( $x, $words[ 0 ] ) ) {
400
                        $firstOrDefault = $x;
401
                        break;
402
                    }
403 6
                }
404 6
                if ($firstOrDefault) {
405
                    $specialCaseTagInjection = '<ins class="mod">';
406
                    if ($tag == "del") {
407
                        unset( $words[ 0 ] );
408
                    }
409 6
                } elseif ( array_search( $words[ 0 ], $this->specialCaseClosingTags ) !== false ) {
410
                    $specialCaseTagInjection = "</ins>";
411
                    $specialCaseTagInjectionIsBefore = true;
412
                    if ($tag == "del") {
413
                        unset( $words[ 0 ] );
414
                    }
415
                }
416
            }
417 9
            if ( count( $words ) == 0 && count( $specialCaseTagInjection ) == 0 ) {
418
                break;
419
            }
420 9
            if ($specialCaseTagInjectionIsBefore) {
421
                $this->content .= $specialCaseTagInjection . implode( "", $this->extractConsecutiveWords( $words, 'tag' ) );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 124 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
422
            } else {
423 9
                $workTag = $this->extractConsecutiveWords( $words, 'tag' );
424 9
                if ( isset( $workTag[ 0 ] ) && $this->isOpeningTag( $workTag[ 0 ] ) && !$this->isClosingTag( $workTag[ 0 ] ) ) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
425 8
                    if ( strpos( $workTag[ 0 ], 'class=' ) ) {
426 4
                        $workTag[ 0 ] = str_replace( 'class="', 'class="diffmod ', $workTag[ 0 ] );
427 4
                        $workTag[ 0 ] = str_replace( "class='", 'class="diffmod ', $workTag[ 0 ] );
428 4
                    } else {
429 8
                        $workTag[ 0 ] = str_replace( ">", ' class="diffmod">', $workTag[ 0 ] );
430
                    }
431 8
                }
432 9
                $this->content .= implode( "", $workTag ) . $specialCaseTagInjection;
433
            }
434 9
        }
435 9
    }
436
437 9
    protected function checkCondition($word, $condition)
438
    {
439 9
        return $condition == 'tag' ? $this->isTag( $word ) : !$this->isTag( $word );
440
    }
441
442 10
    protected function wrapText($text, $tagName, $cssClass)
443
    {
444 10
        return sprintf( '<%1$s class="%2$s">%3$s</%1$s>', $tagName, $cssClass, $text );
445
    }
446
447 9
    protected function extractConsecutiveWords(&$words, $condition)
448
    {
449 9
        $indexOfFirstTag = null;
450 9
        $words = array_values($words);
451 9
        foreach ($words as $i => $word) {
452 9
            if ( !$this->checkCondition( $word, $condition ) ) {
453 8
                $indexOfFirstTag = $i;
454 8
                break;
455
            }
456 9
        }
457 9
        if ($indexOfFirstTag !== null) {
458 8
            $items = array();
459 8 View Code Duplication
            foreach ($words as $pos => $s) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
460 8
                if ($pos >= 0 && $pos < $indexOfFirstTag) {
461 8
                    $items[] = $s;
462 8
                }
463 8
            }
464 8
            if ($indexOfFirstTag > 0) {
465 8
                array_splice( $words, 0, $indexOfFirstTag );
466 8
            }
467
468 8
            return $items;
469
        } else {
470 9
            $items = array();
471 9 View Code Duplication
            foreach ($words as $pos => $s) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
472 9
                if ( $pos >= 0 && $pos <= count( $words ) ) {
473 9
                    $items[] = $s;
474 9
                }
475 9
            }
476 9
            array_splice( $words, 0, count( $words ) );
477
478 9
            return $items;
479
        }
480
    }
481
482 11
    protected function isTag($item)
483
    {
484 11
        return $this->isOpeningTag( $item ) || $this->isClosingTag( $item );
485
    }
486
487 11
    protected function isOpeningTag($item)
488
    {
489 11
        return preg_match( "#<[^>]+>\\s*#iU", $item );
490
    }
491
492 11
    protected function isClosingTag($item)
493
    {
494 11
        return preg_match( "#</[^>]+>\\s*#iU", $item );
495
    }
496
497 11
    protected function operations()
498
    {
499 11
        $positionInOld = 0;
500 11
        $positionInNew = 0;
501 11
        $operations = array();
502 11
        $matches = $this->matchingBlocks();
503 11
        $matches[] = new Match( count( $this->oldWords ), count( $this->newWords ), 0 );
504 11
        foreach ($matches as $i => $match) {
505 11
            $matchStartsAtCurrentPositionInOld = ( $positionInOld == $match->startInOld );
506 11
            $matchStartsAtCurrentPositionInNew = ( $positionInNew == $match->startInNew );
507 11
            $action = 'none';
0 ignored issues
show
Unused Code introduced by
$action is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
508
509 11
            if ($matchStartsAtCurrentPositionInOld == false && $matchStartsAtCurrentPositionInNew == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
510 7
                $action = 'replace';
511 11
            } elseif ($matchStartsAtCurrentPositionInOld == true && $matchStartsAtCurrentPositionInNew == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
512 8
                $action = 'insert';
513 11
            } elseif ($matchStartsAtCurrentPositionInOld == false && $matchStartsAtCurrentPositionInNew == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
514 5
                $action = 'delete';
515 5
            } else { // This occurs if the first few words are the same in both versions
516 11
                $action = 'none';
517
            }
518 11
            if ($action != 'none') {
519 9
                $operations[] = new Operation( $action, $positionInOld, $match->startInOld, $positionInNew, $match->startInNew );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 129 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
520 9
            }
521 11
            if ( count( $match ) != 0 ) {
522 11
                $operations[] = new Operation( 'equal', $match->startInOld, $match->endInOld(), $match->startInNew, $match->endInNew() );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 137 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
523 11
            }
524 11
            $positionInOld = $match->endInOld();
525 11
            $positionInNew = $match->endInNew();
526 11
        }
527
528 11
        return $operations;
529
    }
530
531 11
    protected function matchingBlocks()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
532
    {
533 11
        $matchingBlocks = array();
534 11
        $this->findMatchingBlocks( 0, count( $this->oldWords ), 0, count( $this->newWords ), $matchingBlocks );
535
536 11
        return $matchingBlocks;
537
    }
538
539 11
    protected function findMatchingBlocks($startInOld, $endInOld, $startInNew, $endInNew, &$matchingBlocks)
540
    {
541 11
        $match = $this->findMatch( $startInOld, $endInOld, $startInNew, $endInNew );
542 11
        if ($match !== null) {
543 11
            if ($startInOld < $match->startInOld && $startInNew < $match->startInNew) {
544 8
                $this->findMatchingBlocks( $startInOld, $match->startInOld, $startInNew, $match->startInNew, $matchingBlocks );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 127 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
545 8
            }
546 11
            $matchingBlocks[] = $match;
547 11
            if ( $match->endInOld() < $endInOld && $match->endInNew() < $endInNew ) {
548 9
                $this->findMatchingBlocks( $match->endInOld(), $endInOld, $match->endInNew(), $endInNew, $matchingBlocks );
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 123 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
549 9
            }
550 11
        }
551 11
    }
552
553 8
    protected function stripTagAttributes($word)
554
    {
555 8
        $word = explode( ' ', trim( $word, '<>' ) );
556
557 8
        return '<' . $word[ 0 ] . '>';
558
    }
559
560 11
    protected function findMatch($startInOld, $endInOld, $startInNew, $endInNew)
561
    {
562 11
        $bestMatchInOld = $startInOld;
563 11
        $bestMatchInNew = $startInNew;
564 11
        $bestMatchSize = 0;
565 11
        $matchLengthAt = array();
566 11
        for ($indexInOld = $startInOld; $indexInOld < $endInOld; $indexInOld++) {
567 11
            $newMatchLengthAt = array();
568 11
            $index = $this->oldWords[ $indexInOld ];
569 11
            if ( $this->isTag( $index ) ) {
570 6
                $index = $this->stripTagAttributes( $index );
571 6
            }
572 11
            if ( !isset( $this->wordIndices[ $index ] ) ) {
573 9
                $matchLengthAt = $newMatchLengthAt;
574 9
                continue;
575
            }
576 11
            foreach ($this->wordIndices[ $index ] as $indexInNew) {
577 11
                if ($indexInNew < $startInNew) {
578 9
                    continue;
579
                }
580 11
                if ($indexInNew >= $endInNew) {
581 8
                    break;
582
                }
583 11
                $newMatchLength = ( isset( $matchLengthAt[ $indexInNew - 1 ] ) ? $matchLengthAt[ $indexInNew - 1 ] : 0 ) + 1;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 125 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
584 11
                $newMatchLengthAt[ $indexInNew ] = $newMatchLength;
585 11
                if ($newMatchLength > $bestMatchSize ||
586
                    (
587 11
                        $this->isGroupDiffs() &&
588 11
                        $bestMatchSize > 0 &&
589 11
                        preg_match(
590 11
                            '/^\s+$/',
591 11
                            implode('', array_slice($this->oldWords, $bestMatchInOld, $bestMatchSize))
592 11
                        )
593 11
                    )
594 11
                ) {
595 11
                    $bestMatchInOld = $indexInOld - $newMatchLength + 1;
596 11
                    $bestMatchInNew = $indexInNew - $newMatchLength + 1;
597 11
                    $bestMatchSize = $newMatchLength;
598 11
                }
599 11
            }
600 11
            $matchLengthAt = $newMatchLengthAt;
601 11
        }
602
603
        // Skip match if none found or match consists only of whitespace
604 11
        if ($bestMatchSize != 0 &&
605
            (
606 11
                !$this->isGroupDiffs() ||
607 11
                !preg_match('/^\s+$/', implode('', array_slice($this->oldWords, $bestMatchInOld, $bestMatchSize)))
608 11
            )
609 11
        ) {
610 11
            return new Match($bestMatchInOld, $bestMatchInNew, $bestMatchSize);
611
        }
612
613 7
        return null;
614
    }
615
}
616