Test Failed
Pull Request — master (#31)
by Josh
04:18
created

HtmlDiff::diffIsolatedPlaceholder()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 15
ccs 9
cts 9
cp 1
rs 8.8571
cc 5
eloc 10
nc 4
nop 4
crap 5
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 10
     * @return HtmlDiff
40
     */
41 10
    public function setInsertSpaceInReplace($boolean)
42 10
    {
43 10
        $this->insertSpaceInReplace = $boolean;
44
45 10
        return $this;
46 10
    }
47 10
48 10
    /**
49
     * @return boolean
50 10
     */
51
    public function getInsertSpaceInReplace()
52
    {
53 10
        return $this->insertSpaceInReplace;
54
    }
55 10
56 10
    public function build()
57 10
    {
58 9
        $this->splitInputsToWords();
59 9
        $this->replaceIsolatedDiffTags();
60 10
        $this->indexNewWords();
61 10
62 10
        $operations = $this->operations();
63 10
        foreach ($operations as $item) {
64
            $this->performOperation( $item );
65 10
        }
66 10
67
        return $this->content;
68 10
    }
69
70 10
    protected function indexNewWords()
71 10
    {
72
        $this->wordIndices = array();
73 10
        foreach ($this->newWords as $i => $word) {
74
            if ( $this->isTag( $word ) ) {
75 10
                $word = $this->stripTagAttributes( $word );
76
            }
77 10
            if ( isset( $this->wordIndices[ $word ] ) ) {
78 10
                $this->wordIndices[ $word ][] = $i;
79 10
            } else {
80 10
                $this->wordIndices[ $word ] = array( $i );
81 10
            }
82 10
        }
83 10
    }
84 9
85 9
    protected function replaceIsolatedDiffTags()
86 9
    {
87 9
        $this->oldIsolatedDiffTags = $this->createIsolatedDiffTagPlaceholders($this->oldWords);
88 9
        $this->newIsolatedDiffTags = $this->createIsolatedDiffTagPlaceholders($this->newWords);
89 10
    }
90 9
91 9
    protected function createIsolatedDiffTagPlaceholders(&$words)
92 9
    {
93 9
        $openIsolatedDiffTags = 0;
94 9
        $isolatedDiffTagIndicies = array();
95 9
        $isolatedDiffTagStart = 0;
96 10
        $currentIsolatedDiffTag = null;
97 10
        foreach ($words as $index => $word) {
98 10
            $openIsolatedDiffTag = $this->isOpeningIsolatedDiffTag($word, $currentIsolatedDiffTag);
99 10
            if ($openIsolatedDiffTag) {
100 9
                if ($openIsolatedDiffTags === 0) {
101 9
                    $isolatedDiffTagStart = $index;
102 9
                }
103 9
                $openIsolatedDiffTags++;
104 10
                $currentIsolatedDiffTag = $openIsolatedDiffTag;
105
            } elseif ($openIsolatedDiffTags > 0 && $this->isClosingIsolatedDiffTag($word, $currentIsolatedDiffTag)) {
106 10
                $openIsolatedDiffTags--;
107
                if ($openIsolatedDiffTags == 0) {
108
                    $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
                    $currentIsolatedDiffTag = null;
110 10
                }
111
            }
112 10
        }
113 10
        $isolatedDiffTagScript = array();
114 10
        $offset = 0;
115 9
        foreach ($isolatedDiffTagIndicies as $isolatedDiffTagIndex) {
116
            $start = $isolatedDiffTagIndex['start'] - $offset;
117 10
            $placeholderString = $this->isolatedDiffTags[$isolatedDiffTagIndex['tagType']];
118
            $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
        }
121
122 9
        return $isolatedDiffTagScript;
123
124 9
    }
125 9
126 9 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 9
    {
128
        $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 9
        foreach ($tagsToMatch as $key => $value) {
130
            if (preg_match("#<".$key."[^>]*>\\s*#iU", $item)) {
131 9
                return $key;
132
            }
133
        }
134 10
135
        return false;
136 10
    }
137 10
138 10 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 10
    {
140 9
        $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 6
        foreach ($tagsToMatch as $key => $value) {
142 6
            if (preg_match("#</".$key."[^>]*>\\s*#iU", $item)) {
143 9
                return $key;
144 9
            }
145 9
        }
146 7
147 7
        return false;
148 7
    }
149
150
    protected function performOperation($operation)
151 10
    {
152 10
        switch ($operation->action) {
153
            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 7
            $this->processEqualOperation( $operation );
155
            break;
156 7
            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 7
            $this->processDeleteOperation( $operation, "diffdel" );
158 7
            break;
159
            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 9
            $this->processInsertOperation( $operation, "diffins");
161
            break;
162 9
            case 'replace':
163 9
            $this->processReplaceOperation( $operation );
164 9
            break;
165 9
            default:
166 2
            break;
167 2
        }
168 2
    }
169 2
170 9
    protected function processReplaceOperation($operation)
171
    {
172 9
        $this->processDeleteOperation( $operation, "diffmod" );
173 9
        $this->processInsertOperation( $operation, "diffmod" );
174 9
    }
175 9
176 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 9
    {
178
        $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 9
                    foreach ($this->newIsolatedDiffTags[$pos] as $word) {
183 5
                        $text[] = $word;
184 5
                    }
185 5
                } else {
186 5
                    $text[] = $s;
187 9
                }
188
            }
189 9
        }
190 9
        $this->insertTag( "ins", $cssClass, $text );
191 9
    }
192 9
193 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 1
    {
195
        $text = array();
196 1
        foreach ($this->oldWords as $pos => $s) {
197 1
            if ($pos >= $operation->startInOld && $pos < $operation->endInOld) {
198
                if (in_array($s, $this->isolatedDiffTags) && isset($this->oldIsolatedDiffTags[$pos])) {
199 1
                    foreach ($this->oldIsolatedDiffTags[$pos] as $word) {
200 1
                        $text[] = $word;
201 1
                    }
202
                } else {
203 1
                    $text[] = $s;
204 1
                }
205 1
            }
206 1
        }
207 1
        $this->insertTag( "del", $cssClass, $text );
208 1
    }
209 1
210
    /**
211 1
     * @param Operation $operation
212
     * @param int       $pos
213 1
     * @param string    $placeholder
214
     * @param bool      $stripWrappingTags
215
     *
216 7
     * @return string
217
     */
218 7
    protected function diffIsolatedPlaceholder($operation, $pos, $placeholder, $stripWrappingTags = true)
219 7
    {
220
        $oldText = implode("", $this->findIsolatedDiffTagsInOld($operation, $pos));
221 7
        $newText = implode("", $this->newIsolatedDiffTags[$pos]);
222
223
        if ($this->isListPlaceholder($placeholder)) {
224 10
            return $this->diffList($oldText, $newText);
225
        } elseif ($this->useTableDiffing && $this->isTablePlaceholder($placeholder)) {
226 10
            return $this->diffTables($oldText, $newText);
227 10
        } elseif ($this->isLinkPlaceholder($placeholder)) {
228
            return $this->diffLinks($oldText, $newText);
229 10
        }
230 10
231
        return $this->diffElements($oldText, $newText, $stripWrappingTags);
232 7
    }
233 7
234
    protected function diffElements($oldText, $newText, $stripWrappingTags = true)
235 7
    {
236 7
        $wrapStart = '';
237 7
        $wrapEnd = '';
238 1
239
        if ($stripWrappingTags) {
240 7
            $pattern = '/(^<[^>]+>)|(<\/[^>]+>$)/i';
241 10
            $matches = array();
242
243 10
            if (preg_match_all($pattern, $newText, $matches)) {
244 10
                $wrapStart = isset($matches[0][0]) ? $matches[0][0] : '';
245 10
                $wrapEnd = isset($matches[0][1]) ? $matches[0][1] : '';
246 10
            }
247
            $oldText = preg_replace($pattern, '', $oldText);
248 7
            $newText = preg_replace($pattern, '', $newText);
249
        }
250 7
251 7
        $diff = new HtmlDiff($oldText, $newText, $this->encoding, $this->specialCaseTags, $this->groupDiffs);
252 7
253 7
        return $wrapStart . $diff->build() . $wrapEnd;
254 7
    }
255 7
256
    protected function diffList($oldText, $newText)
257
    {
258 1
        $diff = new ListDiffNew($oldText, $newText, $this->encoding, $this->specialCaseTags, $this->groupDiffs);
259
        $diff->setMatchThreshold($this->matchThreshold);
260
261 7
        return $diff->build();
262
    }
263 7
264
    protected function diffTables($oldText, $newText)
265 7
    {
266
        $diff = new TableDiff($oldText, $newText, $this->encoding, $this->specialCaseTags, $this->groupDiffs);
267
        $diff->setMatchThreshold($this->matchThreshold);
268 9
        $diff->setStrategy($this->strategy);
269
270 9
        return $diff->build();
271 9
    }
272 9
273
    /**
274
     * @param string $oldText
275 9
     * @param string $newText
276
     *
277 9
     * @return string
278 9
     */
279
    protected function diffLinks($oldText, $newText)
280 9
    {
281 9
        $oldHref = $this->getAttributeFromTag($oldText, 'href');
282 9
        $newHref = $this->getAttributeFromTag($newText, 'href');
283 9
284 6
        if ($oldHref != $newHref) {
285 6
            return sprintf(
286
                '%s%s',
287
                $this->wrapText($oldText, 'del', 'diffmod diff-href'),
288
                $this->wrapText($newText, 'ins', 'diffmod diff-href')
289
            );
290 6
        }
291 6
292
        return $this->diffElements($oldText, $newText);
293
    }
294
295 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...
296 6
    {
297
        $result = array();
298
        foreach ($this->newWords as $pos => $s) {
299
300
            if ($pos >= $operation->startInNew && $pos < $operation->endInNew) {
301
                if (in_array($s, $this->isolatedDiffTags) && isset($this->newIsolatedDiffTags[$pos])) {
302
303
                    $result[] = $this->diffIsolatedPlaceholder($operation, $pos, $s);
304 9
                } else {
305
                    $result[] = $s;
306
                }
307 9
            }
308
        }
309
        $this->content .= implode( "", $result );
310 9
    }
311 9
312 8
    /**
313 2
     * @param string $text
314 2
     * @param string $attribute
315 2
     *
316 8
     * @return null|string
317
     */
318 8
    protected function getAttributeFromTag($text, $attribute)
319 9
    {
320
        $matches = array();
321 9
        if (preg_match(sprintf('/<a\s+[^>]*%s=([\'"])(.*)\1[^>]*>/i', $attribute), $text, $matches)) {
322 9
            return $matches[2];
323
        }
324 9
325
        return null;
326 9
    }
327
328
    protected function isListPlaceholder($text)
329 9
    {
330
        return $this->isPlaceholderType($text, array('ol', 'dl', 'ul'));
331 9
    }
332
333
    /**
334 9
     * @param string $text
335
     *
336 9
     * @return bool
337 9
     */
338 9
    public function isLinkPlaceholder($text)
339 8
    {
340 8
        return $this->isPlaceholderType($text, 'a');
341
    }
342 9
343 9
    /**
344 8
     * @param string       $text
345 8
     * @param array|string $types
346 8
     * @param bool         $strict
347 8
     *
348 8
     * @return bool
349 8
     */
350 8
    protected function isPlaceholderType($text, $types, $strict = true)
351 8
    {
352 8
        if (!is_array($types)) {
353
            $types = array($types);
354 8
        }
355
356 9
        $criteria = array();
357 9
        foreach ($types as $type) {
358 9
            if (isset($this->isolatedDiffTags[$type])) {
359 9
                $criteria[] = $this->isolatedDiffTags[$type];
360 9
            } else {
361 9
                $criteria[] = $type;
362 9
            }
363
        }
364 9
365
        return in_array($text, $criteria, $strict);
366
    }
367
368 10
    protected function isTablePlaceholder($text)
369
    {
370 10
        return in_array($text, array(
371
            $this->isolatedDiffTags['table'],
372
        ), true);
373 10
    }
374
375 10
    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...
376
    {
377
        $offset = $posInNew - $operation->startInNew;
378 10
379
        return $this->oldIsolatedDiffTags[$operation->startInOld + $offset];
380 10
    }
381
382
    protected function insertTag($tag, $cssClass, &$words)
383 10
    {
384
        while (true) {
385 10
            if ( count( $words ) == 0 ) {
386 10
                break;
387 10
            }
388 10
389 10
            $nonTags = $this->extractConsecutiveWords( $words, 'noTag' );
390 10
391 10
            $specialCaseTagInjection = '';
392 10
            $specialCaseTagInjectionIsBefore = false;
393 10
394
            if ( count( $nonTags ) != 0 ) {
395 10
                $text = $this->wrapText( implode( "", $nonTags ), $tag, $cssClass );
396 7
                $this->content .= $text;
397 10
            } else {
398 9
                $firstOrDefault = false;
399 10
                foreach ($this->specialCaseOpeningTags as $x) {
400 6
                    if ( preg_match( $x, $words[ 0 ] ) ) {
401 6
                        $firstOrDefault = $x;
402 10
                        break;
403
                    }
404 10
                }
405 9
                if ($firstOrDefault) {
406 9
                    $specialCaseTagInjection = '<ins class="mod">';
407 10
                    if ($tag == "del") {
408 10
                        unset( $words[ 0 ] );
409 10
                    }
410 10
                } elseif ( array_search( $words[ 0 ], $this->specialCaseClosingTags ) !== false ) {
411 10
                    $specialCaseTagInjection = "</ins>";
412 10
                    $specialCaseTagInjectionIsBefore = true;
413
                    if ($tag == "del") {
414 10
                        unset( $words[ 0 ] );
415
                    }
416
                }
417 10
            }
418
            if ( count( $words ) == 0 && count( $specialCaseTagInjection ) == 0 ) {
419 10
                break;
420 10
            }
421
            if ($specialCaseTagInjectionIsBefore) {
422 10
                $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...
423
            } else {
424
                $workTag = $this->extractConsecutiveWords( $words, 'tag' );
425 10
                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...
426
                    if ( strpos( $workTag[ 0 ], 'class=' ) ) {
427 10
                        $workTag[ 0 ] = str_replace( 'class="', 'class="diffmod ', $workTag[ 0 ] );
428 10
                        $workTag[ 0 ] = str_replace( "class='", 'class="diffmod ', $workTag[ 0 ] );
429 10
                    } else {
430 8
                        $workTag[ 0 ] = str_replace( ">", ' class="diffmod">', $workTag[ 0 ] );
431 8
                    }
432 10
                }
433 10
                $this->content .= implode( "", $workTag ) . $specialCaseTagInjection;
434 9
            }
435 9
        }
436 10
    }
437 10
438
    protected function checkCondition($word, $condition)
439 9
    {
440
        return $condition == 'tag' ? $this->isTag( $word ) : !$this->isTag( $word );
441 9
    }
442
443 9
    protected function wrapText($text, $tagName, $cssClass)
444
    {
445
        return sprintf( '<%1$s class="%2$s">%3$s</%1$s>', $tagName, $cssClass, $text );
446 10
    }
447
448 10
    protected function extractConsecutiveWords(&$words, $condition)
449 10
    {
450 10
        $indexOfFirstTag = null;
451 10
        $words = array_values($words);
452 10
        foreach ($words as $i => $word) {
453 10
            if ( !$this->checkCondition( $word, $condition ) ) {
454 10
                $indexOfFirstTag = $i;
455 10
                break;
456 9
            }
457 9
        }
458 10
        if ($indexOfFirstTag !== null) {
459 9
            $items = array();
460 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...
461
                if ($pos >= 0 && $pos < $indexOfFirstTag) {
462 10
                    $items[] = $s;
463 10
                }
464 9
            }
465
            if ($indexOfFirstTag > 0) {
466 10
                array_splice( $words, 0, $indexOfFirstTag );
467 8
            }
468
469 10
            return $items;
470 10
        } else {
471 10
            $items = array();
472 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...
473 10
                if ( $pos >= 0 && $pos <= count( $words ) ) {
474 10
                    $items[] = $s;
475 10
                }
476 10
            }
477 10
            array_splice( $words, 0, count( $words ) );
478 10
479 10
            return $items;
480 10
        }
481 10
    }
482 10
483 10
    protected function isTag($item)
484 10
    {
485 10
        return $this->isOpeningTag( $item ) || $this->isClosingTag( $item );
486 10
    }
487 10
488
    protected function isOpeningTag($item)
489
    {
490 10
        return preg_match( "#<[^>]+>\\s*#iU", $item );
491
    }
492 10
493 10
    protected function isClosingTag($item)
494 10
    {
495 10
        return preg_match( "#</[^>]+>\\s*#iU", $item );
496 10
    }
497
498
    protected function operations()
499 7
    {
500
        $positionInOld = 0;
501
        $positionInNew = 0;
502
        $operations = array();
503
        $matches = $this->matchingBlocks();
504
        $matches[] = new Match( count( $this->oldWords ), count( $this->newWords ), 0 );
505
        foreach ($matches as $i => $match) {
506
            $matchStartsAtCurrentPositionInOld = ( $positionInOld == $match->startInOld );
507
            $matchStartsAtCurrentPositionInNew = ( $positionInNew == $match->startInNew );
508
            $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...
509
510
            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...
511
                $action = 'replace';
512
            } 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...
513
                $action = 'insert';
514
            } 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...
515
                $action = 'delete';
516
            } else { // This occurs if the first few words are the same in both versions
517
                $action = 'none';
518
            }
519
            if ($action != 'none') {
520
                $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...
521
            }
522
            if ( count( $match ) != 0 ) {
523
                $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...
524
            }
525
            $positionInOld = $match->endInOld();
526
            $positionInNew = $match->endInNew();
527
        }
528
529
        return $operations;
530
    }
531
532
    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...
533
    {
534
        $matchingBlocks = array();
535
        $this->findMatchingBlocks( 0, count( $this->oldWords ), 0, count( $this->newWords ), $matchingBlocks );
536
537
        return $matchingBlocks;
538
    }
539
540
    protected function findMatchingBlocks($startInOld, $endInOld, $startInNew, $endInNew, &$matchingBlocks)
541
    {
542
        $match = $this->findMatch( $startInOld, $endInOld, $startInNew, $endInNew );
543
        if ($match !== null) {
544
            if ($startInOld < $match->startInOld && $startInNew < $match->startInNew) {
545
                $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...
546
            }
547
            $matchingBlocks[] = $match;
548
            if ( $match->endInOld() < $endInOld && $match->endInNew() < $endInNew ) {
549
                $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...
550
            }
551
        }
552
    }
553
554
    protected function stripTagAttributes($word)
555
    {
556
        $word = explode( ' ', trim( $word, '<>' ) );
557
558
        return '<' . $word[ 0 ] . '>';
559
    }
560
561
    protected function findMatch($startInOld, $endInOld, $startInNew, $endInNew)
562
    {
563
        $bestMatchInOld = $startInOld;
564
        $bestMatchInNew = $startInNew;
565
        $bestMatchSize = 0;
566
        $matchLengthAt = array();
567
        for ($indexInOld = $startInOld; $indexInOld < $endInOld; $indexInOld++) {
568
            $newMatchLengthAt = array();
569
            $index = $this->oldWords[ $indexInOld ];
570
            if ( $this->isTag( $index ) ) {
571
                $index = $this->stripTagAttributes( $index );
572
            }
573
            if ( !isset( $this->wordIndices[ $index ] ) ) {
574
                $matchLengthAt = $newMatchLengthAt;
575
                continue;
576
            }
577
            foreach ($this->wordIndices[ $index ] as $indexInNew) {
578
                if ($indexInNew < $startInNew) {
579
                    continue;
580
                }
581
                if ($indexInNew >= $endInNew) {
582
                    break;
583
                }
584
                $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...
585
                $newMatchLengthAt[ $indexInNew ] = $newMatchLength;
586
                if ($newMatchLength > $bestMatchSize ||
587
                    (
588
                        $this->isGroupDiffs() &&
589
                        $bestMatchSize > 0 &&
590
                        preg_match(
591
                            '/^\s+$/',
592
                            implode('', array_slice($this->oldWords, $bestMatchInOld, $bestMatchSize))
593
                        )
594
                    )
595
                ) {
596
                    $bestMatchInOld = $indexInOld - $newMatchLength + 1;
597
                    $bestMatchInNew = $indexInNew - $newMatchLength + 1;
598
                    $bestMatchSize = $newMatchLength;
599
                }
600
            }
601
            $matchLengthAt = $newMatchLengthAt;
602
        }
603
604
        // Skip match if none found or match consists only of whitespace
605
        if ($bestMatchSize != 0 &&
606
            (
607
                !$this->isGroupDiffs() ||
608
                !preg_match('/^\s+$/', implode('', array_slice($this->oldWords, $bestMatchInOld, $bestMatchSize)))
609
            )
610
        ) {
611
            return new Match($bestMatchInOld, $bestMatchInNew, $bestMatchSize);
612
        }
613
614
        return null;
615
    }
616
}
617