Test Failed
Pull Request — master (#96)
by Sven
03:18
created

TableDiff::diffTableRowsWithMatches()   C

Complexity

Conditions 14
Paths 54

Size

Total Lines 72

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 22.246

Importance

Changes 0
Metric Value
cc 14
nc 54
nop 3
dl 0
loc 72
rs 5.6242
c 0
b 0
f 0
ccs 30
cts 46
cp 0.6522
crap 22.246

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Caxy\HtmlDiff\Table;
4
5
use Caxy\HtmlDiff\AbstractDiff;
6
use Caxy\HtmlDiff\HtmlDiff;
7
use Caxy\HtmlDiff\HtmlDiffConfig;
8
use Caxy\HtmlDiff\Operation;
9
10
/**
11
 * Class TableDiff.
12
 */
13
class TableDiff extends AbstractDiff
14
{
15
    /**
16
     * @var null|Table
17
     */
18
    protected $oldTable = null;
19
20
    /**
21
     * @var null|Table
22
     */
23
    protected $newTable = null;
24
25
    /**
26
     * @var null|\DOMElement
27
     */
28
    protected $diffTable = null;
29
30
    /**
31
     * @var null|\DOMDocument
32
     */
33
    protected $diffDom = null;
34
35
    /**
36
     * @var int
37
     */
38
    protected $newRowOffsets = 0;
39
40
    /**
41
     * @var int
42
     */
43
    protected $oldRowOffsets = 0;
44
45
    /**
46
     * @var array
47
     */
48
    protected $cellValues = array();
49
50
    /**
51
     * @param string              $oldText
52
     * @param string              $newText
53
     * @param HtmlDiffConfig|null $config
54
     *
55
     * @return self
56
     */
57 1
    public static function create($oldText, $newText, HtmlDiffConfig $config = null)
58
    {
59 1
        $diff = new self($oldText, $newText);
60
61 1
        if (null !== $config) {
62 1
            $diff->setConfig($config);
63
        }
64
65 1
        return $diff;
66
    }
67
68
    /**
69
     * TableDiff constructor.
70
     *
71
     * @param string     $oldText
72
     * @param string     $newText
73
     * @param string     $encoding
74
     * @param array|null $specialCaseTags
75
     * @param bool|null  $groupDiffs
76
     */
77 1
    public function __construct(
78
        $oldText,
79
        $newText,
80
        $encoding = 'UTF-8',
81
        $specialCaseTags = null,
82
        $groupDiffs = null
83
    ) {
84 1
        parent::__construct($oldText, $newText, $encoding, $specialCaseTags, $groupDiffs);
85 1
    }
86
87
    /**
88
     * @return string
89
     */
90 1
    public function build()
91
    {
92 1
        $this->prepare();
93
94 1
        if ($this->hasDiffCache() && $this->getDiffCache()->contains($this->oldText, $this->newText)) {
95
            $this->content = $this->getDiffCache()->fetch($this->oldText, $this->newText);
96
97
            return $this->content;
98
        }
99
100 1
        $this->buildTableDoms();
101
102 1
        $this->diffDom = new \DOMDocument();
103
104 1
        $this->indexCellValues($this->newTable);
105
106 1
        $this->diffTableContent();
107
108 1
        if ($this->hasDiffCache()) {
109
            $this->getDiffCache()->save($this->oldText, $this->newText, $this->content);
110
        }
111
112 1
        return $this->content;
113
    }
114
115 1
    protected function diffTableContent()
116
    {
117 1
        $this->diffDom = new \DOMDocument();
118 1
        $this->diffTable = $this->newTable->cloneNode($this->diffDom);
119 1
        $this->diffDom->appendChild($this->diffTable);
120
121 1
        $oldRows = $this->oldTable->getRows();
122 1
        $newRows = $this->newTable->getRows();
123
124 1
        $oldMatchData = array();
125 1
        $newMatchData = array();
126
127
        /* @var $oldRow TableRow */
128 1
        foreach ($oldRows as $oldIndex => $oldRow) {
129 1
            $oldMatchData[$oldIndex] = array();
130
131
            // Get match percentages
132
            /* @var $newRow TableRow */
133 1
            foreach ($newRows as $newIndex => $newRow) {
134 1
                if (!array_key_exists($newIndex, $newMatchData)) {
135 1
                    $newMatchData[$newIndex] = array();
136
                }
137
138
                // similar_text
139 1
                $percentage = $this->getMatchPercentage($oldRow, $newRow, $oldIndex, $newIndex);
140
141 1
                $oldMatchData[$oldIndex][$newIndex] = $percentage;
142 1
                $newMatchData[$newIndex][$oldIndex] = $percentage;
143
            }
144
        }
145
146 1
        $matches = $this->getRowMatches($oldMatchData, $newMatchData);
147 1
        $this->diffTableRowsWithMatches($oldRows, $newRows, $matches);
148
149 1
        $this->content = $this->htmlFromNode($this->diffTable);
150 1
    }
151
152
    /**
153
     * @param TableRow[] $oldRows
154
     * @param TableRow[] $newRows
155
     * @param RowMatch[] $matches
156
     */
157 1
    protected function diffTableRowsWithMatches($oldRows, $newRows, $matches)
158
    {
159 1
        $operations = array();
160
161 1
        $indexInOld = 0;
162 1
        $indexInNew = 0;
163
164 1
        $oldRowCount = count($oldRows);
165 1
        $newRowCount = count($newRows);
166
167 1
        $matches[] = new RowMatch($newRowCount, $oldRowCount, $newRowCount, $oldRowCount);
168
169
        // build operations
170 1
        foreach ($matches as $match) {
171 1
            $matchAtIndexInOld = $indexInOld === $match->getStartInOld();
172 1
            $matchAtIndexInNew = $indexInNew === $match->getStartInNew();
173
174 1
            $action = 'equal';
175
176 1
            if (!$matchAtIndexInOld && !$matchAtIndexInNew) {
177
                $action = 'replace';
178 1
            } elseif ($matchAtIndexInOld && !$matchAtIndexInNew) {
179
                $action = 'insert';
180 1
            } elseif (!$matchAtIndexInOld && $matchAtIndexInNew) {
181
                $action = 'delete';
182
            }
183
184 1
            if ($action !== 'equal') {
185
                $operations[] = new Operation(
186
                    $action,
187
                    $indexInOld,
188
                    $match->getStartInOld(),
189
                    $indexInNew,
190
                    $match->getStartInNew()
191
                );
192
            }
193
194 1
            $operations[] = new Operation(
195 1
                'equal',
196 1
                $match->getStartInOld(),
197 1
                $match->getEndInOld(),
198 1
                $match->getStartInNew(),
199 1
                $match->getEndInNew()
200
            );
201
202 1
            $indexInOld = $match->getEndInOld();
203 1
            $indexInNew = $match->getEndInNew();
204
        }
205
206 1
        $appliedRowSpans = array();
207
208
        // process operations
209 1
        foreach ($operations as $operation) {
210 1
            switch ($operation->action) {
211 1
                case 'equal':
212 1
                    $this->processEqualOperation($operation, $oldRows, $newRows, $appliedRowSpans);
213 1
                    break;
214
215
                case 'delete':
216
                    $this->processDeleteOperation($operation, $oldRows, $appliedRowSpans);
217
                    break;
218
219
                case 'insert':
220
                    $this->processInsertOperation($operation, $newRows, $appliedRowSpans);
221
                    break;
222
223
                case 'replace':
224
                    $this->processReplaceOperation($operation, $oldRows, $newRows, $appliedRowSpans);
225
                    break;
226
            }
227
        }
228 1
    }
229
230
    /**
231
     * @param Operation $operation
232
     * @param array     $newRows
233
     * @param array     $appliedRowSpans
234
     * @param bool      $forceExpansion
235
     */
236
    protected function processInsertOperation(
237
        Operation $operation,
238
        $newRows,
239
        &$appliedRowSpans,
240
        $forceExpansion = false
241
    ) {
242
        $targetRows = array_slice($newRows, $operation->startInNew, $operation->endInNew - $operation->startInNew);
243
        foreach ($targetRows as $row) {
244
            $this->diffAndAppendRows(null, $row, $appliedRowSpans, $forceExpansion);
245
        }
246
    }
247
248
    /**
249
     * @param Operation $operation
250
     * @param array     $oldRows
251
     * @param array     $appliedRowSpans
252
     * @param bool      $forceExpansion
253
     */
254
    protected function processDeleteOperation(
255
        Operation $operation,
256
        $oldRows,
257
        &$appliedRowSpans,
258
        $forceExpansion = false
259
    ) {
260
        $targetRows = array_slice($oldRows, $operation->startInOld, $operation->endInOld - $operation->startInOld);
261
        foreach ($targetRows as $row) {
262
            $this->diffAndAppendRows($row, null, $appliedRowSpans, $forceExpansion);
263
        }
264
    }
265
266
    /**
267
     * @param Operation $operation
268
     * @param array     $oldRows
269
     * @param array     $newRows
270
     * @param array     $appliedRowSpans
271
     */
272 1
    protected function processEqualOperation(Operation $operation, $oldRows, $newRows, &$appliedRowSpans)
273
    {
274 1
        $targetOldRows = array_values(
275 1
            array_slice($oldRows, $operation->startInOld, $operation->endInOld - $operation->startInOld)
276
        );
277 1
        $targetNewRows = array_values(
278 1
            array_slice($newRows, $operation->startInNew, $operation->endInNew - $operation->startInNew)
279
        );
280
281 1
        foreach ($targetNewRows as $index => $newRow) {
282 1
            if (!isset($targetOldRows[$index])) {
283
                continue;
284
            }
285
286 1
            $this->diffAndAppendRows($targetOldRows[$index], $newRow, $appliedRowSpans);
287
        }
288 1
    }
289
290
    /**
291
     * @param Operation $operation
292
     * @param array     $oldRows
293
     * @param array     $newRows
294
     * @param array     $appliedRowSpans
295
     */
296
    protected function processReplaceOperation(Operation $operation, $oldRows, $newRows, &$appliedRowSpans)
297
    {
298
        $this->processDeleteOperation($operation, $oldRows, $appliedRowSpans, true);
299
        $this->processInsertOperation($operation, $newRows, $appliedRowSpans, true);
300
    }
301
302
    /**
303
     * @param array $oldMatchData
304
     * @param array $newMatchData
305
     *
306
     * @return array
307
     */
308 1
    protected function getRowMatches($oldMatchData, $newMatchData)
309
    {
310 1
        $matches = array();
311
312 1
        $startInOld = 0;
313 1
        $startInNew = 0;
314 1
        $endInOld = count($oldMatchData);
315 1
        $endInNew = count($newMatchData);
316
317 1
        $this->findRowMatches($newMatchData, $startInOld, $endInOld, $startInNew, $endInNew, $matches);
318
319 1
        return $matches;
320
    }
321
322
    /**
323
     * @param array $newMatchData
324
     * @param int   $startInOld
325
     * @param int   $endInOld
326
     * @param int   $startInNew
327
     * @param int   $endInNew
328
     * @param array $matches
329
     */
330 1
    protected function findRowMatches($newMatchData, $startInOld, $endInOld, $startInNew, $endInNew, &$matches)
331
    {
332 1
        $match = $this->findRowMatch($newMatchData, $startInOld, $endInOld, $startInNew, $endInNew);
333 1
        if ($match !== null) {
334 1
            if ($startInOld < $match->getStartInOld() &&
335 1
                $startInNew < $match->getStartInNew()
336
            ) {
337
                $this->findRowMatches(
338
                    $newMatchData,
339
                    $startInOld,
340
                    $match->getStartInOld(),
341
                    $startInNew,
342
                    $match->getStartInNew(),
343
                    $matches
344
                );
345
            }
346
347 1
            $matches[] = $match;
348
349 1
            if ($match->getEndInOld() < $endInOld &&
350 1
                $match->getEndInNew() < $endInNew
351
            ) {
352
                $this->findRowMatches(
353
                    $newMatchData,
354
                    $match->getEndInOld(),
355
                    $endInOld,
356
                    $match->getEndInNew(),
357
                    $endInNew,
358
                    $matches
359
                );
360
            }
361
        }
362 1
    }
363
364
    /**
365
     * @param array $newMatchData
366
     * @param int   $startInOld
367
     * @param int   $endInOld
368
     * @param int   $startInNew
369
     * @param int   $endInNew
370
     *
371
     * @return RowMatch|null
372
     */
373 1
    protected function findRowMatch($newMatchData, $startInOld, $endInOld, $startInNew, $endInNew)
374
    {
375 1
        $bestMatch = null;
376 1
        $bestPercentage = 0;
377
378 1
        foreach ($newMatchData as $newIndex => $oldMatches) {
379 1
            if ($newIndex < $startInNew) {
380
                continue;
381
            }
382
383 1
            if ($newIndex >= $endInNew) {
384
                break;
385
            }
386 1
            foreach ($oldMatches as $oldIndex => $percentage) {
387 1
                if ($oldIndex < $startInOld) {
388
                    continue;
389
                }
390
391 1
                if ($oldIndex >= $endInOld) {
392
                    break;
393
                }
394
395 1
                if ($percentage > $bestPercentage) {
396 1
                    $bestPercentage = $percentage;
397
                    $bestMatch = array(
398 1
                        'oldIndex' => $oldIndex,
399 1
                        'newIndex' => $newIndex,
400 1
                        'percentage' => $percentage,
401
                    );
402
                }
403
            }
404
        }
405
406 1
        if ($bestMatch !== null) {
407 1
            return new RowMatch(
408 1
                $bestMatch['newIndex'],
409 1
                $bestMatch['oldIndex'],
410 1
                $bestMatch['newIndex'] + 1,
411 1
                $bestMatch['oldIndex'] + 1,
412 1
                $bestMatch['percentage']
413
            );
414
        }
415
416
        return;
417
    }
418
419
    /**
420
     * @param TableRow|null $oldRow
421
     * @param TableRow|null $newRow
422
     * @param array         $appliedRowSpans
423
     * @param bool          $forceExpansion
424
     *
425
     * @return array
426
     */
427 1
    protected function diffRows($oldRow, $newRow, array &$appliedRowSpans, $forceExpansion = false)
428
    {
429
        // create tr dom element
430 1
        $rowToClone = $newRow ?: $oldRow;
431
        /* @var $diffRow \DOMElement */
432 1
        $diffRow = $this->diffDom->importNode($rowToClone->getDomNode()->cloneNode(false), false);
433
434 1
        $oldCells = $oldRow ? $oldRow->getCells() : array();
435 1
        $newCells = $newRow ? $newRow->getCells() : array();
436
437 1
        $position = new DiffRowPosition();
438
439 1
        $extraRow = null;
440
441
        /* @var $expandCells \DOMElement[] */
442 1
        $expandCells = array();
443
        /* @var $cellsWithMultipleRows \DOMElement[] */
444 1
        $cellsWithMultipleRows = array();
445
446 1
        $newCellCount = count($newCells);
447 1
        while ($position->getIndexInNew() < $newCellCount) {
448 1
            if (!$position->areColumnsEqual()) {
449
                $type = $position->getLesserColumnType();
450
                if ($type === 'new') {
451
                    $row = $newRow;
452
                    $targetRow = $extraRow;
453
                } else {
454
                    $row = $oldRow;
455
                    $targetRow = $diffRow;
456
                }
457
                if ($row && $targetRow && (!$type === 'old' || isset($oldCells[$position->getIndexInOld()]))) {
458
                    $this->syncVirtualColumns($row, $position, $cellsWithMultipleRows, $targetRow, $type, true);
459
460
                    continue;
461
                }
462
            }
463
464
            /* @var $newCell TableCell */
465 1
            $newCell = $newCells[$position->getIndexInNew()];
466
            /* @var $oldCell TableCell */
467 1
            $oldCell = isset($oldCells[$position->getIndexInOld()]) ? $oldCells[$position->getIndexInOld()] : null;
468
469 1
            if ($oldCell && $newCell->getColspan() != $oldCell->getColspan()) {
470
                if (null === $extraRow) {
471
                    /* @var $extraRow \DOMElement */
472
                    $extraRow = $this->diffDom->importNode($rowToClone->getDomNode()->cloneNode(false), false);
473
                }
474
475
                if ($oldCell->getColspan() > $newCell->getColspan()) {
476
                    $this->diffCellsAndIncrementCounters(
477
                        $oldCell,
478
                        null,
479
                        $cellsWithMultipleRows,
480
                        $diffRow,
481
                        $position,
482
                        true
483
                    );
484
                    $this->syncVirtualColumns($newRow, $position, $cellsWithMultipleRows, $extraRow, 'new', true);
485
                } else {
486
                    $this->diffCellsAndIncrementCounters(
487
                        null,
488
                        $newCell,
489
                        $cellsWithMultipleRows,
490
                        $extraRow,
491
                        $position,
492
                        true
493
                    );
494
                    $this->syncVirtualColumns($oldRow, $position, $cellsWithMultipleRows, $diffRow, 'old', true);
495
                }
496
            } else {
497 1
                $diffCell = $this->diffCellsAndIncrementCounters(
498 1
                    $oldCell,
499
                    $newCell,
500
                    $cellsWithMultipleRows,
501
                    $diffRow,
502
                    $position
503
                );
504 1
                $expandCells[] = $diffCell;
505
            }
506
        }
507
508 1
        $oldCellCount = count($oldCells);
509 1
        while ($position->getIndexInOld() < $oldCellCount) {
510
            $diffCell = $this->diffCellsAndIncrementCounters(
511
                $oldCells[$position->getIndexInOld()],
512
                null,
513
                $cellsWithMultipleRows,
514
                $diffRow,
515
                $position
516
            );
517
            $expandCells[] = $diffCell;
518
        }
519
520 1
        if ($extraRow) {
521
            foreach ($expandCells as $expandCell) {
522
                $rowspan = $expandCell->getAttribute('rowspan') ?: 1;
523
                $expandCell->setAttribute('rowspan', 1 + $rowspan);
524
            }
525
        }
526
527 1
        if ($extraRow || $forceExpansion) {
528
            foreach ($appliedRowSpans as $rowSpanCells) {
529
                /* @var $rowSpanCells \DOMElement[] */
530
                foreach ($rowSpanCells as $extendCell) {
531
                    $rowspan = $extendCell->getAttribute('rowspan') ?: 1;
532
                    $extendCell->setAttribute('rowspan', 1 + $rowspan);
533
                }
534
            }
535
        }
536
537 1
        if (!$forceExpansion) {
538 1
            array_shift($appliedRowSpans);
539 1
            $appliedRowSpans = array_values($appliedRowSpans);
540
        }
541 1
        $appliedRowSpans = array_merge($appliedRowSpans, array_values($cellsWithMultipleRows));
542
543 1
        return array($diffRow, $extraRow);
544
    }
545
546
    /**
547
     * @param TableCell|null $oldCell
548
     * @param TableCell|null $newCell
549
     *
550
     * @return \DOMElement
551
     */
552 1
    protected function getNewCellNode(TableCell $oldCell = null, TableCell $newCell = null)
553
    {
554
        // If only one cell exists, use it
555 1
        if (!$oldCell || !$newCell) {
556
            $clone = $newCell
557
                ? $newCell->getDomNode()->cloneNode(false)
558
                : $oldCell->getDomNode()->cloneNode(false);
559
        } else {
560 1
            $oldNode = $oldCell->getDomNode();
561 1
            $newNode = $newCell->getDomNode();
562
563
            /* @var $clone \DOMElement */
564 1
            $clone = $newNode->cloneNode(false);
565
566 1
            $oldRowspan = $oldNode->getAttribute('rowspan') ?: 1;
567 1
            $oldColspan = $oldNode->getAttribute('colspan') ?: 1;
568 1
            $newRowspan = $newNode->getAttribute('rowspan') ?: 1;
569 1
            $newColspan = $newNode->getAttribute('colspan') ?: 1;
570
571 1
            $clone->setAttribute('rowspan', max($oldRowspan, $newRowspan));
572 1
            $clone->setAttribute('colspan', max($oldColspan, $newColspan));
573
        }
574
575 1
        return $this->diffDom->importNode($clone);
576
    }
577
578
    /**
579
     * @param TableCell|null $oldCell
580
     * @param TableCell|null $newCell
581
     * @param bool           $usingExtraRow
582
     *
583
     * @return \DOMElement
584
     */
585 1
    protected function diffCells($oldCell, $newCell, $usingExtraRow = false)
586
    {
587 1
        $diffCell = $this->getNewCellNode($oldCell, $newCell);
588
589 1
        $oldContent = $oldCell ? $this->getInnerHtml($oldCell->getDomNode()) : '';
590 1
        $newContent = $newCell ? $this->getInnerHtml($newCell->getDomNode()) : '';
591
592 1
        $htmlDiff = HtmlDiff::create(
593 1
            mb_convert_encoding($oldContent, 'UTF-8', 'HTML-ENTITIES'),
594 1
            mb_convert_encoding($newContent, 'UTF-8', 'HTML-ENTITIES'),
595 1
            $this->config
596
        );
597 1
        $diff = $htmlDiff->build();
598
599 1
        $this->setInnerHtml($diffCell, $diff);
600
601 1
        if (null === $newCell) {
602
            $diffCell->setAttribute('class', trim($diffCell->getAttribute('class').' del'));
603
        }
604
605 1
        if (null === $oldCell) {
606
            $diffCell->setAttribute('class', trim($diffCell->getAttribute('class').' ins'));
607
        }
608
609 1
        if ($usingExtraRow) {
610
            $diffCell->setAttribute('class', trim($diffCell->getAttribute('class').' extra-row'));
611
        }
612
613 1
        return $diffCell;
614
    }
615
616 1
    protected function buildTableDoms()
617
    {
618 1
        $this->oldTable = $this->parseTableStructure($this->oldText);
619 1
        $this->newTable = $this->parseTableStructure($this->newText);
620 1
    }
621
622
    /**
623
     * @param string $text
624
     *
625
     * @return \DOMDocument
626
     */
627 1
    protected function createDocumentWithHtml($text)
628
    {
629 1
        $dom = new \DOMDocument();
630 1
        $dom->loadHTML(mb_convert_encoding(
631 1
            $this->purifyHtml(mb_convert_encoding($text, $this->config->getEncoding(), mb_detect_encoding($text))),
632 1
            'HTML-ENTITIES',
633 1
            $this->config->getEncoding()
634
        ));
635
636 1
        return $dom;
637
    }
638
639
    /**
640
     * @param string $text
641
     *
642
     * @return Table
643
     */
644 1
    protected function parseTableStructure($text)
645
    {
646 1
        $dom = $this->createDocumentWithHtml($text);
647
648 1
        $tableNode = $dom->getElementsByTagName('table')->item(0);
649
650 1
        $table = new Table($tableNode);
651
652 1
        $this->parseTable($table);
653
654 1
        return $table;
655
    }
656
657
    /**
658
     * @param Table         $table
659
     * @param \DOMNode|null $node
660
     */
661 1
    protected function parseTable(Table $table, \DOMNode $node = null)
662
    {
663 1
        if ($node === null) {
664 1
            $node = $table->getDomNode();
665
        }
666
667 1
        if (!$node->childNodes) {
668
            return;
669
        }
670
671 1
        foreach ($node->childNodes as $child) {
672 1
            if ($child->nodeName === 'tr') {
673 1
                $row = new TableRow($child);
674 1
                $table->addRow($row);
675
676 1
                $this->parseTableRow($row);
677
            } else {
678 1
                $this->parseTable($table, $child);
679
            }
680
        }
681 1
    }
682
683
    /**
684
     * @param TableRow $row
685
     */
686 1
    protected function parseTableRow(TableRow $row)
687
    {
688 1
        $node = $row->getDomNode();
689
690 1
        foreach ($node->childNodes as $child) {
691 1
            if (in_array($child->nodeName, array('td', 'th'))) {
692 1
                $cell = new TableCell($child);
693 1
                $row->addCell($cell);
694
            }
695
        }
696 1
    }
697
698
    /**
699
     * @param \DOMNode $node
700
     *
701
     * @return string
702
     */
703 1
    protected function getInnerHtml($node)
704
    {
705 1
        $innerHtml = '';
706 1
        $children = $node->childNodes;
707
708 1
        foreach ($children as $child) {
709 1
            $innerHtml .= $this->htmlFromNode($child);
710
        }
711
712 1
        return $;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected ';', expecting T_VARIABLE or '{' or '$'
Loading history...
713
    }
714
715
    /**
716
     * @param \DOMNode $node
717
     *
718
     * @return string
719
     */
720 1
    protected function htmlFromNode($node)
721
    {
722 1
        $domDocument = new \DOMDocument();
723 1
        $newNode = $domDocument->importNode($node, true);
724 1
        $domDocument->appendChild($newNode);
725
726 1
        return $domDocument->saveHTML();
727
    }
728
729
    /**
730
     * @param \DOMNode $node
731
     * @param string   $html
732
     */
733 1
    protected function setInnerHtml($node, $html)
734
    {
735
        // DOMDocument::loadHTML does not allow empty strings.
736 1
        if ($this->stringUtil->strlen(trim($html)) === 0) {
737 1
            $html = '<span class="empty"></span>';
738
        }
739
740 1
        $doc = $this->createDocumentWithHtml($html);
741 1
        $fragment = $node->ownerDocument->createDocumentFragment();
742 1
        $root = $doc->getElementsByTagName('body')->item(0);
743 1
        foreach ($root->childNodes as $child) {
744 1
            $fragment->appendChild($node->ownerDocument->importNode($child, true));
745
        }
746
747 1
        $node->appendChild($fragment);
748 1
    }
749
750
    /**
751
     * @param Table $table
752
     */
753 1
    protected function indexCellValues(Table $table)
754
    {
755 1
        foreach ($table->getRows() as $rowIndex => $row) {
756 1
            foreach ($row->getCells() as $cellIndex => $cell) {
757 1
                $value = trim($cell->getDomNode()->textContent);
758
759 1
                if (!isset($this->cellValues[$value])) {
760 1
                    $this->cellValues[$value] = array();
761
                }
762
763 1
                $this->cellValues[$value][] = new TablePosition($rowIndex, $cellIndex);
764
            }
765
        }
766 1
    }
767
768
    /**
769
     * @param TableRow        $tableRow
770
     * @param DiffRowPosition $position
771
     * @param array           $cellsWithMultipleRows
772
     * @param \DOMNode        $diffRow
773
     * @param string          $diffType
774
     * @param bool            $usingExtraRow
775
     */
776
    protected function syncVirtualColumns(
777
        $tableRow,
778
        DiffRowPosition $position,
779
        &$cellsWithMultipleRows,
780
        $diffRow,
781
        $diffType,
782
        $usingExtraRow = false
783
    ) {
784
        $currentCell = $tableRow->getCell($position->getIndex($diffType));
785
        while ($position->isColumnLessThanOther($diffType) && $currentCell) {
786
            $diffCell = $diffType === 'new' ? $this->diffCells(null, $currentCell, $usingExtraRow) : $this->diffCells(
787
                $currentCell,
788
                null,
789
                $usingExtraRow
790
            );
791
            // Store cell in appliedRowSpans if spans multiple rows
792
            if ($diffCell->getAttribute('rowspan') > 1) {
793
                $cellsWithMultipleRows[$diffCell->getAttribute('rowspan')][] = $diffCell;
794
            }
795
            $diffRow->appendChild($diffCell);
796
            $position->incrementColumn($diffType, $currentCell->getColspan());
797
            $currentCell = $tableRow->getCell($position->incrementIndex($diffType));
798
        }
799
    }
800
801
    /**
802
     * @param null|TableCell  $oldCell
803
     * @param null|TableCell  $newCell
804
     * @param array           $cellsWithMultipleRows
805
     * @param \DOMElement     $diffRow
806
     * @param DiffRowPosition $position
807
     * @param bool            $usingExtraRow
808
     *
809
     * @return \DOMElement
810
     */
811 1
    protected function diffCellsAndIncrementCounters(
812
        $oldCell,
813
        $newCell,
814
        &$cellsWithMultipleRows,
815
        $diffRow,
816
        DiffRowPosition $position,
817
        $usingExtraRow = false
818
    ) {
819 1
        $diffCell = $this->diffCells($oldCell, $newCell, $usingExtraRow);
820
        // Store cell in appliedRowSpans if spans multiple rows
821 1
        if ($diffCell->getAttribute('rowspan') > 1) {
822
            $cellsWithMultipleRows[$diffCell->getAttribute('rowspan')][] = $diffCell;
823
        }
824 1
        $diffRow->appendChild($diffCell);
825
826 1
        if ($newCell !== null) {
827 1
            $position->incrementIndexInNew();
828 1
            $position->incrementColumnInNew($newCell->getColspan());
829
        }
830
831 1
        if ($oldCell !== null) {
832 1
            $position->incrementIndexInOld();
833 1
            $position->incrementColumnInOld($oldCell->getColspan());
834
        }
835
836 1
        return $diffCell;
837
    }
838
839
    /**
840
     * @param TableRow|null $oldRow
841
     * @param TableRow|null $newRow
842
     * @param array         $appliedRowSpans
843
     * @param bool          $forceExpansion
844
     */
845 1
    protected function diffAndAppendRows($oldRow, $newRow, &$appliedRowSpans, $forceExpansion = false)
846
    {
847 1
        list($rowDom, $extraRow) = $this->diffRows(
848 1
            $oldRow,
849
            $newRow,
850
            $appliedRowSpans,
851
            $forceExpansion
852
        );
853
854 1
        $this->diffTable->appendChild($rowDom);
855
856 1
        if ($extraRow) {
857
            $this->diffTable->appendChild($extraRow);
858
        }
859 1
    }
860
861
    /**
862
     * @param TableRow $oldRow
863
     * @param TableRow $newRow
864
     * @param int      $oldIndex
865
     * @param int      $newIndex
866
     *
867
     * @return float|int
868
     */
869 1
    protected function getMatchPercentage(TableRow $oldRow, TableRow $newRow, $oldIndex, $newIndex)
870
    {
871 1
        $firstCellWeight = 1.5;
872 1
        $indexDeltaWeight = 0.25 * (abs($oldIndex - $newIndex));
873 1
        $thresholdCount = 0;
874 1
        $minCells = min(count($newRow->getCells()), count($oldRow->getCells()));
875 1
        $totalCount = ($minCells + $firstCellWeight + $indexDeltaWeight) * 100;
876 1
        foreach ($newRow->getCells() as $newIndex => $newCell) {
877 1
            $oldCell = $oldRow->getCell($newIndex);
878
879 1
            if ($oldCell) {
880 1
                $percentage = null;
881 1
                similar_text($oldCell->getInnerHtml(), $newCell->getInnerHtml(), $percentage);
882
883 1
                if ($percentage > ($this->config->getMatchThreshold() * 0.50)) {
884 1
                    $increment = $percentage;
885 1
                    if ($newIndex === 0 && $percentage > 95) {
886
                        $increment = $increment * $firstCellWeight;
887
                    }
888 1
                    $thresholdCount += $increment;
889
                }
890
            }
891
        }
892
893 1
        return ($totalCount > 0) ? ($thresholdCount / $totalCount) : 0;
894
    }
895
}
896