Test Setup Failed
Pull Request — master (#33)
by Josh
03:27
created

HtmlDiff::diffTables()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 6
Bugs 0 Features 2
Metric Value
c 6
b 0
f 2
dl 0
loc 6
ccs 2
cts 2
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
crap 1
1
<?php
2
3
namespace Caxy\HtmlDiff;
4
5
use Caxy\HtmlDiff\Table\TableDiff;
6
7
/**
8
 * Class HtmlDiff
9
 * @package Caxy\HtmlDiff
10
 */
11
class HtmlDiff extends AbstractDiff
12
{
13
    /**
14
     * @var array
15
     */
16
    protected $wordIndices;
17
    /**
18
     * @var array
19
     */
20
    protected $oldTables;
21
    /**
22
     * @var array
23
     */
24
    protected $newTables;
25
    /**
26
     * @var array
27
     */
28
    protected $newIsolatedDiffTags;
29
    /**
30
     * @var array
31
     */
32
    protected $oldIsolatedDiffTags;
33
34
    /**
35
     * @param string              $oldText
36
     * @param string              $newText
37
     * @param HtmlDiffConfig|null $config
38
     *
39
     * @return self
40
     */
41 7 View Code Duplication
    public static function create($oldText, $newText, HtmlDiffConfig $config = 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...
42
    {
43 7
        $diff = new self($oldText, $newText);
44
45 7
        if (null !== $config) {
46 7
            $diff->setConfig($config);
47 7
        }
48
49 7
        return $diff;
50
    }
51
52
    /**
53
     * @param $bool
54
     *
55
     * @return $this
56
     *
57
     * @deprecated since 0.1.0
58
     */
59
    public function setUseTableDiffing($bool)
60
    {
61
        $this->config->setUseTableDiffing($bool);
62
63
        return $this;
64
    }
65
66
    /**
67
     * @param  boolean  $boolean
68
     * @return HtmlDiff
69
     *
70
     * @deprecated since 0.1.0
71
     */
72
    public function setInsertSpaceInReplace($boolean)
73
    {
74
        $this->config->setInsertSpaceInReplace($boolean);
75
76
        return $this;
77
    }
78
79
    /**
80
     * @return boolean
81
     *
82
     * @deprecated since 0.1.0
83
     */
84
    public function getInsertSpaceInReplace()
85
    {
86
        return $this->config->isInsertSpaceInReplace();
87
    }
88
89
    /**
90
     * @return string
91
     */
92 11
    public function build()
93
    {
94 11
        if ($this->hasDiffCache() && $this->getDiffCache()->contains($this->oldText, $this->newText)) {
95 11
            $this->content = $this->getDiffCache()->fetch($this->oldText, $this->newText);
96 11
97
            return $this->content;
98 11
        }
99 11
100 11
        $this->splitInputsToWords();
101 11
        $this->replaceIsolatedDiffTags();
102
        $this->indexNewWords();
103 11
104
        $operations = $this->operations();
105
        foreach ($operations as $item) {
106 11
            $this->performOperation( $item );
107
        }
108 11
109 11
        if ($this->hasDiffCache()) {
110 11
            $this->getDiffCache()->save($this->oldText, $this->newText, $this->content);
111 8
        }
112 8
113 11
        return $this->content;
114 11
    }
115 11
116 11
    protected function indexNewWords()
117
    {
118 11
        $this->wordIndices = array();
119 11
        foreach ($this->newWords as $i => $word) {
120
            if ( $this->isTag( $word ) ) {
121 11
                $word = $this->stripTagAttributes( $word );
122
            }
123 11
            if ( isset( $this->wordIndices[ $word ] ) ) {
124 11
                $this->wordIndices[ $word ][] = $i;
125 11
            } else {
126
                $this->wordIndices[ $word ] = array( $i );
127
            }
128
        }
129
    }
130
131
    protected function replaceIsolatedDiffTags()
132 11
    {
133
        $this->oldIsolatedDiffTags = $this->createIsolatedDiffTagPlaceholders($this->oldWords);
134 11
        $this->newIsolatedDiffTags = $this->createIsolatedDiffTagPlaceholders($this->newWords);
135 11
    }
136 11
137 11
    /**
138 11
     * @param array $words
139 11
     *
140 11
     * @return array
141 11
     */
142 11
    protected function createIsolatedDiffTagPlaceholders(&$words)
143 11
    {
144 11
        $openIsolatedDiffTags = 0;
145 11
        $isolatedDiffTagIndicies = array();
146 11
        $isolatedDiffTagStart = 0;
147 10
        $currentIsolatedDiffTag = null;
148 10
        foreach ($words as $index => $word) {
149 10
            $openIsolatedDiffTag = $this->isOpeningIsolatedDiffTag($word, $currentIsolatedDiffTag);
150 10
            if ($openIsolatedDiffTag) {
151 10
                if ($openIsolatedDiffTags === 0) {
152 10
                    $isolatedDiffTagStart = $index;
153 11
                }
154 11
                $openIsolatedDiffTags++;
155 11
                $currentIsolatedDiffTag = $openIsolatedDiffTag;
156 11
            } elseif ($openIsolatedDiffTags > 0 && $this->isClosingIsolatedDiffTag($word, $currentIsolatedDiffTag)) {
157 10
                $openIsolatedDiffTags--;
158 10
                if ($openIsolatedDiffTags == 0) {
159 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...
160 10
                    $currentIsolatedDiffTag = null;
161 11
                }
162
            }
163 11
        }
164
        $isolatedDiffTagScript = array();
165
        $offset = 0;
166
        foreach ($isolatedDiffTagIndicies as $isolatedDiffTagIndex) {
167
            $start = $isolatedDiffTagIndex['start'] - $offset;
168
            $placeholderString = $this->config->getIsolatedDiffTagPlaceholder($isolatedDiffTagIndex['tagType']);
169
            $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...
170
            $offset += $isolatedDiffTagIndex['length'] - 1;
171
        }
172
173 11
        return $isolatedDiffTagScript;
174
175
    }
176 11
177 11
    /**
178 11
     * @param string      $item
179 11
     * @param null|string $currentIsolatedDiffTag
180 11
     *
181
     * @return false|string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
182 11
     */
183 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...
184 11
    {
185
        $tagsToMatch = $currentIsolatedDiffTag !== null
186
            ? array($currentIsolatedDiffTag => $this->config->getIsolatedDiffTagPlaceholder($currentIsolatedDiffTag))
187
            : $this->config->getIsolatedDiffTags();
188
        foreach ($tagsToMatch as $key => $value) {
189
            if (preg_match("#<".$key."[^>]*>\\s*#iU", $item)) {
190
                return $key;
191
            }
192
        }
193 11
194
        return false;
195
    }
196 11
197 11
    /**
198 11
     * @param string      $item
199 11
     * @param null|string $currentIsolatedDiffTag
200 10
     *
201
     * @return false|string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
202 11
     */
203 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...
204 11
    {
205
        $tagsToMatch = $currentIsolatedDiffTag !== null
206
            ? array($currentIsolatedDiffTag => $this->config->getIsolatedDiffTagPlaceholder($currentIsolatedDiffTag))
207
            : $this->config->getIsolatedDiffTags();
208
        foreach ($tagsToMatch as $key => $value) {
209
            if (preg_match("#</".$key."[^>]*>\\s*#iU", $item)) {
210 11
                return $key;
211
            }
212 11
        }
213 11
214 11
        return false;
215 11
    }
216 9
217 5
    /**
218 5
     * @param Operation $operation
219 9
     */
220 8
    protected function performOperation($operation)
221 8
    {
222 7
        switch ($operation->action) {
223 7
            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...
224 7
            $this->processEqualOperation( $operation );
225
            break;
226
            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...
227 11
            $this->processDeleteOperation( $operation, "diffdel" );
228 11
            break;
229
            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...
230
            $this->processInsertOperation( $operation, "diffins");
231
            break;
232
            case 'replace':
233 7
            $this->processReplaceOperation( $operation );
234
            break;
235 7
            default:
236 7
            break;
237 7
        }
238
    }
239
240
    /**
241
     * @param Operation $operation
242
     */
243 9
    protected function processReplaceOperation($operation)
244
    {
245 9
        $this->processDeleteOperation( $operation, "diffmod" );
246 9
        $this->processInsertOperation( $operation, "diffmod" );
247 9
    }
248 9
249 4
    /**
250 4
     * @param Operation $operation
251 4
     * @param string    $cssClass
252 4
     */
253 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...
254
    {
255 9
        $text = array();
256 9
        foreach ($this->newWords as $pos => $s) {
257 9
            if ($pos >= $operation->startInNew && $pos < $operation->endInNew) {
258 9
                if ($this->config->isIsolatedDiffTagPlaceholder($s) && isset($this->newIsolatedDiffTags[$pos])) {
259
                    foreach ($this->newIsolatedDiffTags[$pos] as $word) {
260
                        $text[] = $word;
261
                    }
262
                } else {
263
                    $text[] = $s;
264 9
                }
265
            }
266 9
        }
267 9
        $this->insertTag( "ins", $cssClass, $text );
268 9
    }
269 9
270 6
    /**
271 6
     * @param Operation $operation
272 6
     * @param string    $cssClass
273 6
     */
274 8 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...
275
    {
276 9
        $text = array();
277 9
        foreach ($this->oldWords as $pos => $s) {
278 9
            if ($pos >= $operation->startInOld && $pos < $operation->endInOld) {
279 9
                if ($this->config->isIsolatedDiffTagPlaceholder($s) && isset($this->oldIsolatedDiffTags[$pos])) {
280
                    foreach ($this->oldIsolatedDiffTags[$pos] as $word) {
281
                        $text[] = $word;
282
                    }
283
                } else {
284
                    $text[] = $s;
285
                }
286
            }
287
        }
288
        $this->insertTag( "del", $cssClass, $text );
289 7
    }
290
291 7
    /**
292 7
     * @param Operation $operation
293
     * @param int       $pos
294 7
     * @param string    $placeholder
295 4
     * @param bool      $stripWrappingTags
296 5
     *
297
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
298 5
     */
299 1
    protected function diffIsolatedPlaceholder($operation, $pos, $placeholder, $stripWrappingTags = true)
300
    {
301
        $oldText = implode("", $this->findIsolatedDiffTagsInOld($operation, $pos));
302 4
        $newText = implode("", $this->newIsolatedDiffTags[$pos]);
303
304
        if ($this->isListPlaceholder($placeholder)) {
305
            return $this->diffList($oldText, $newText);
306
        } elseif ($this->config->isUseTableDiffing() && $this->isTablePlaceholder($placeholder)) {
307
            return $this->diffTables($oldText, $newText);
308
        } elseif ($this->isLinkPlaceholder($placeholder)) {
309
            return $this->diffLinks($oldText, $newText);
310
        }
311
312 5
        return $this->diffElements($oldText, $newText, $stripWrappingTags);
313
    }
314 5
315 5
    /**
316
     * @param string $oldText
317 5
     * @param string $newText
318 5
     * @param bool   $stripWrappingTags
319 5
     *
320
     * @return string
321 5
     */
322 5
    protected function diffElements($oldText, $newText, $stripWrappingTags = true)
323 5
    {
324 5
        $wrapStart = '';
325 5
        $wrapEnd = '';
326 5
327 5
        if ($stripWrappingTags) {
328
            $pattern = '/(^<[^>]+>)|(<\/[^>]+>$)/i';
329 5
            $matches = array();
330
331 5
            if (preg_match_all($pattern, $newText, $matches)) {
332
                $wrapStart = isset($matches[0][0]) ? $matches[0][0] : '';
333
                $wrapEnd = isset($matches[0][1]) ? $matches[0][1] : '';
334
            }
335
            $oldText = preg_replace($pattern, '', $oldText);
336
            $newText = preg_replace($pattern, '', $newText);
337
        }
338
339
        $diff = HtmlDiff::create($oldText, $newText, $this->config);
340 4
341
        return $wrapStart . $diff->build() . $wrapEnd;
342 4
    }
343
344 4
    /**
345
     * @param string $oldText
346
     * @param string $newText
347
     *
348
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
349
     */
350
    protected function diffList($oldText, $newText)
351
    {
352
        $diff = ListDiffNew::create($oldText, $newText, $this->config);
353
354
        return $diff->build();
355
    }
356
357
    /**
358
     * @param string $oldText
359
     * @param string $newText
360
     *
361
     * @return string
362
     */
363
    protected function diffTables($oldText, $newText)
364
    {
365
        $diff = TableDiff::create($oldText, $newText, $this->config);
366 1
367
        return $diff->build();
368 1
    }
369 1
370
    /**
371 1
     * @param string $oldText
372 1
     * @param string $newText
373 1
     *
374 1
     * @return string
375 1
     */
376 1
    protected function diffLinks($oldText, $newText)
377
    {
378
        $oldHref = $this->getAttributeFromTag($oldText, 'href');
379 1
        $newHref = $this->getAttributeFromTag($newText, 'href');
380
381
        if ($oldHref != $newHref) {
382
            return sprintf(
383
                '%s%s',
384
                $this->wrapText($oldText, 'del', 'diffmod diff-href'),
385 11
                $this->wrapText($newText, 'ins', 'diffmod diff-href')
386
            );
387 11
        }
388 11
389
        return $this->diffElements($oldText, $newText);
390 11
    }
391 11
392
    /**
393 7
     * @param Operation $operation
394 7
     */
395 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...
396
    {
397 11
        $result = array();
398 11
        foreach ($this->newWords as $pos => $s) {
399 11
400 11
            if ($pos >= $operation->startInNew && $pos < $operation->endInNew) {
401
                if ($this->config->isIsolatedDiffTagPlaceholder($s) && isset($this->newIsolatedDiffTags[$pos])) {
402
403
                    $result[] = $this->diffIsolatedPlaceholder($operation, $pos, $s);
404
                } else {
405
                    $result[] = $s;
406
                }
407
            }
408 1
        }
409
        $this->content .= implode( "", $result );
410 1
    }
411 1
412 1
    /**
413
     * @param string $text
414
     * @param string $attribute
415
     *
416
     * @return null|string
417
     */
418
    protected function getAttributeFromTag($text, $attribute)
419
    {
420
        $matches = array();
421
        if (preg_match(sprintf('/<a\s+[^>]*%s=([\'"])(.*)\1[^>]*>/i', $attribute), $text, $matches)) {
422
            return $matches[2];
423 7
        }
424
425 7
        return null;
426
    }
427
428
    /**
429
     * @param string $text
430
     *
431
     * @return bool
432
     */
433 5
    protected function isListPlaceholder($text)
434
    {
435 5
        return $this->isPlaceholderType($text, array('ol', 'dl', 'ul'));
436
    }
437
438
    /**
439
     * @param string $text
440
     *
441
     * @return bool
442
     */
443
    public function isLinkPlaceholder($text)
444
    {
445 7
        return $this->isPlaceholderType($text, 'a');
446
    }
447 7
448 5
    /**
449 5
     * @param string       $text
450
     * @param array|string $types
451 7
     * @param bool         $strict
452 7
     *
453 7
     * @return bool
454 7
     */
455 7
    protected function isPlaceholderType($text, $types, $strict = true)
456
    {
457
        if (!is_array($types)) {
458 7
            $types = array($types);
459
        }
460 7
461
        $criteria = array();
462
        foreach ($types as $type) {
463
            if ($this->config->isIsolatedDiffTag($type)) {
464
                $criteria[] = $this->config->getIsolatedDiffTagPlaceholder($type);
465
            } else {
466
                $criteria[] = $type;
467
            }
468 5
        }
469
470 5
        return in_array($text, $criteria, $strict);
471
    }
472
473
    /**
474
     * @param string $text
475
     *
476
     * @return bool
477
     */
478
    protected function isTablePlaceholder($text)
479 7
    {
480
        return $this->isPlaceholderType($text, 'table');
481 7
    }
482
483 7
    /**
484
     * @param Operation $operation
485
     * @param int       $posInNew
486
     *
487
     * @return array
488
     */
489
    protected function findIsolatedDiffTagsInOld($operation, $posInNew)
490
    {
491 9
        $offset = $posInNew - $operation->startInNew;
492
493 9
        return $this->oldIsolatedDiffTags[$operation->startInOld + $offset];
494 9
    }
495 9
496
    /**
497
     * @param string $tag
498 9
     * @param string $cssClass
499
     * @param array  $words
500 9
     */
501 9
    protected function insertTag($tag, $cssClass, &$words)
502
    {
503 9
        while (true) {
504 9
            if ( count( $words ) == 0 ) {
505 9
                break;
506 9
            }
507 6
508 6
            $nonTags = $this->extractConsecutiveWords( $words, 'noTag' );
509
510
            $specialCaseTagInjection = '';
511
            $specialCaseTagInjectionIsBefore = false;
512
513 6
            if ( count( $nonTags ) != 0 ) {
514 6
                $text = $this->wrapText( implode( "", $nonTags ), $tag, $cssClass );
515
                $this->content .= $text;
516
            } else {
517
                $firstOrDefault = false;
518
                foreach ($this->config->getSpecialCaseOpeningTags() as $x) {
519 6
                    if ( preg_match( $x, $words[ 0 ] ) ) {
520
                        $firstOrDefault = $x;
521
                        break;
522
                    }
523
                }
524
                if ($firstOrDefault) {
525
                    $specialCaseTagInjection = '<ins class="mod">';
526
                    if ($tag == "del") {
527 9
                        unset( $words[ 0 ] );
528
                    }
529
                } elseif ( array_search( $words[ 0 ], $this->config->getSpecialCaseClosingTags()) !== false ) {
530 9
                    $specialCaseTagInjection = "</ins>";
531
                    $specialCaseTagInjectionIsBefore = true;
532
                    if ($tag == "del") {
533 9
                        unset( $words[ 0 ] );
534 9
                    }
535 8
                }
536 4
            }
537 4
            if ( count( $words ) == 0 && count( $specialCaseTagInjection ) == 0 ) {
538 4
                break;
539 8
            }
540
            if ($specialCaseTagInjectionIsBefore) {
541 8
                $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...
542 9
            } else {
543
                $workTag = $this->extractConsecutiveWords( $words, 'tag' );
544 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...
545 9
                    if ( strpos( $workTag[ 0 ], 'class=' ) ) {
546
                        $workTag[ 0 ] = str_replace( 'class="', 'class="diffmod ', $workTag[ 0 ] );
547
                        $workTag[ 0 ] = str_replace( "class='", 'class="diffmod ', $workTag[ 0 ] );
548
                    } else {
549
                        $workTag[ 0 ] = str_replace( ">", ' class="diffmod">', $workTag[ 0 ] );
550
                    }
551
                }
552
                $this->content .= implode( "", $workTag ) . $specialCaseTagInjection;
553 9
            }
554
        }
555 9
    }
556
557
    /**
558
     * @param string $word
559
     * @param string $condition
560
     *
561
     * @return bool
562
     */
563
    protected function checkCondition($word, $condition)
564
    {
565 10
        return $condition == 'tag' ? $this->isTag( $word ) : !$this->isTag( $word );
566
    }
567 10
568
    /**
569
     * @param string $text
570
     * @param string $tagName
571
     * @param string $cssClass
572
     *
573
     * @return string
574
     */
575
    protected function wrapText($text, $tagName, $cssClass)
576 9
    {
577
        return sprintf( '<%1$s class="%2$s">%3$s</%1$s>', $tagName, $cssClass, $text );
578 9
    }
579 9
580 9
    /**
581 9
     * @param array  $words
582 8
     * @param string $condition
583 8
     *
584
     * @return array
585 9
     */
586 9
    protected function extractConsecutiveWords(&$words, $condition)
587 8
    {
588 8
        $indexOfFirstTag = null;
589 8
        $words = array_values($words);
590 8
        foreach ($words as $i => $word) {
591 8
            if ( !$this->checkCondition( $word, $condition ) ) {
592 8
                $indexOfFirstTag = $i;
593 8
                break;
594 8
            }
595 8
        }
596
        if ($indexOfFirstTag !== null) {
597 8
            $items = array();
598 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...
599 9
                if ($pos >= 0 && $pos < $indexOfFirstTag) {
600 9
                    $items[] = $s;
601 9
                }
602 9
            }
603 9
            if ($indexOfFirstTag > 0) {
604 9
                array_splice( $words, 0, $indexOfFirstTag );
605 9
            }
606
607 9
            return $items;
608
        } else {
609
            $items = array();
610 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...
611
                if ( $pos >= 0 && $pos <= count( $words ) ) {
612
                    $items[] = $s;
613
                }
614
            }
615
            array_splice( $words, 0, count( $words ) );
616 11
617
            return $items;
618 11
        }
619
    }
620
621
    /**
622
     * @param string $item
623
     *
624
     * @return bool
625
     */
626 11
    protected function isTag($item)
627
    {
628 11
        return $this->isOpeningTag( $item ) || $this->isClosingTag( $item );
629
    }
630
631
    /**
632
     * @param string $item
633
     *
634
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
635
     */
636 11
    protected function isOpeningTag($item)
637
    {
638 11
        return preg_match( "#<[^>]+>\\s*#iU", $item );
639
    }
640
641
    /**
642
     * @param string $item
643
     *
644 11
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
645
     */
646 11
    protected function isClosingTag($item)
647 11
    {
648 11
        return preg_match( "#</[^>]+>\\s*#iU", $item );
649 11
    }
650 11
651 11
    /**
652 11
     * @return Operation[]
653 11
     */
654 11
    protected function operations()
655
    {
656 11
        $positionInOld = 0;
657 7
        $positionInNew = 0;
658 11
        $operations = array();
659 8
        $matches = $this->matchingBlocks();
660 11
        $matches[] = new Match( count( $this->oldWords ), count( $this->newWords ), 0 );
661 5
        foreach ($matches as $i => $match) {
662 5
            $matchStartsAtCurrentPositionInOld = ( $positionInOld == $match->startInOld );
663 11
            $matchStartsAtCurrentPositionInNew = ( $positionInNew == $match->startInNew );
664
            $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...
665 11
666 9
            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...
667 9
                $action = 'replace';
668 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...
669 11
                $action = 'insert';
670 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...
671 11
                $action = 'delete';
672 11
            } else { // This occurs if the first few words are the same in both versions
673 11
                $action = 'none';
674
            }
675 11
            if ($action != 'none') {
676
                $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...
677
            }
678
            if ( count( $match ) != 0 ) {
679
                $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...
680
            }
681 11
            $positionInOld = $match->endInOld();
682
            $positionInNew = $match->endInNew();
683 11
        }
684 11
685
        return $operations;
686 11
    }
687
688
    /**
689
     * @return Match[]
690
     */
691
    protected function matchingBlocks()
692
    {
693
        $matchingBlocks = array();
694
        $this->findMatchingBlocks( 0, count( $this->oldWords ), 0, count( $this->newWords ), $matchingBlocks );
695
696 11
        return $matchingBlocks;
697
    }
698 11
699 11
    /**
700 11
     * @param int   $startInOld
701 8
     * @param int   $endInOld
702 8
     * @param int   $startInNew
703 11
     * @param int   $endInNew
704 11
     * @param array $matchingBlocks
705 9
     */
706 9
    protected function findMatchingBlocks($startInOld, $endInOld, $startInNew, $endInNew, &$matchingBlocks)
707 11
    {
708 11
        $match = $this->findMatch( $startInOld, $endInOld, $startInNew, $endInNew );
709
        if ($match !== null) {
710
            if ($startInOld < $match->startInOld && $startInNew < $match->startInNew) {
711
                $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...
712
            }
713
            $matchingBlocks[] = $match;
714
            if ( $match->endInOld() < $endInOld && $match->endInNew() < $endInNew ) {
715 8
                $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...
716
            }
717 8
        }
718
    }
719 8
720
    /**
721
     * @param string $word
722
     *
723
     * @return string
724
     */
725
    protected function stripTagAttributes($word)
726
    {
727
        $word = explode( ' ', trim( $word, '<>' ) );
728
729
        return '<' . $word[ 0 ] . '>';
730 11
    }
731
732 11
    /**
733 11
     * @param int $startInOld
734 11
     * @param int $endInOld
735 11
     * @param int $startInNew
736 11
     * @param int $endInNew
737 11
     *
738 11
     * @return Match|null
739 11
     */
740 6
    protected function findMatch($startInOld, $endInOld, $startInNew, $endInNew)
741 6
    {
742 11
        $bestMatchInOld = $startInOld;
743 9
        $bestMatchInNew = $startInNew;
744 9
        $bestMatchSize = 0;
745
        $matchLengthAt = array();
746 11
        for ($indexInOld = $startInOld; $indexInOld < $endInOld; $indexInOld++) {
747 11
            $newMatchLengthAt = array();
748 9
            $index = $this->oldWords[ $indexInOld ];
749
            if ( $this->isTag( $index ) ) {
750 11
                $index = $this->stripTagAttributes( $index );
751 8
            }
752
            if ( !isset( $this->wordIndices[ $index ] ) ) {
753 11
                $matchLengthAt = $newMatchLengthAt;
754 11
                continue;
755 11
            }
756
            foreach ($this->wordIndices[ $index ] as $indexInNew) {
757 11
                if ($indexInNew < $startInNew) {
758 11
                    continue;
759 11
                }
760 11
                if ($indexInNew >= $endInNew) {
761 11
                    break;
762 11
                }
763 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...
764 11
                $newMatchLengthAt[ $indexInNew ] = $newMatchLength;
765 11
                if ($newMatchLength > $bestMatchSize ||
766 11
                    (
767 11
                        $this->isGroupDiffs() &&
0 ignored issues
show
Deprecated Code introduced by
The method Caxy\HtmlDiff\AbstractDiff::isGroupDiffs() has been deprecated with message: since 0.1.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
768 11
                        $bestMatchSize > 0 &&
769 11
                        preg_match(
770 11
                            '/^\s+$/',
771 11
                            implode('', array_slice($this->oldWords, $bestMatchInOld, $bestMatchSize))
772
                        )
773
                    )
774 11
                ) {
775
                    $bestMatchInOld = $indexInOld - $newMatchLength + 1;
776 11
                    $bestMatchInNew = $indexInNew - $newMatchLength + 1;
777 11
                    $bestMatchSize = $newMatchLength;
778 11
                }
779 11
            }
780 11
            $matchLengthAt = $newMatchLengthAt;
781
        }
782
783 7
        // Skip match if none found or match consists only of whitespace
784
        if ($bestMatchSize != 0 &&
785
            (
786
                !$this->isGroupDiffs() ||
0 ignored issues
show
Deprecated Code introduced by
The method Caxy\HtmlDiff\AbstractDiff::isGroupDiffs() has been deprecated with message: since 0.1.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
787
                !preg_match('/^\s+$/', implode('', array_slice($this->oldWords, $bestMatchInOld, $bestMatchSize)))
788
            )
789
        ) {
790
            return new Match($bestMatchInOld, $bestMatchInNew, $bestMatchSize);
791
        }
792
793
        return null;
794
    }
795
}
796