HtmlDiffer::preProcess()   A
last analyzed

Complexity

Conditions 5
Paths 3

Size

Total Lines 43
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 5.3174

Importance

Changes 0
Metric Value
cc 5
eloc 30
nc 3
nop 1
dl 0
loc 43
ccs 23
cts 30
cp 0.7667
crap 5.3174
rs 9.1288
c 0
b 0
f 0
1
<?php
2
/**
3
 * (c) Steve Nebes <[email protected]>
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
declare(strict_types=1);
10
11
namespace SN\DaisyDiff\Html;
12
13
use SN\DaisyDiff\Html\Modification\ModificationType;
14
use SN\DaisyDiff\Output\DiffOutputInterface;
15
use SN\DaisyDiff\RangeDifferencer\Core\LCSSettings;
16
use SN\DaisyDiff\RangeDifferencer\RangeDifference;
17
use SN\DaisyDiff\RangeDifferencer\RangeDifferencer;
18
19
/**
20
 * Takes TextNodeComparator instances, computes the difference between them, marks the changes, and outputs a merged
21
 * tree to a [] instance.
22
 */
23
class HtmlDiffer
24
{
25
    /** @var DiffOutputInterface */
26
    private $output;
27
28
    /**
29
     * @param DiffOutputInterface $output
30
     */
31 48
    public function __construct(DiffOutputInterface $output)
32
    {
33 48
        $this->output = $output;
34 48
    }
35
36
    /**
37
     * Compares two Node Trees.
38
     *
39
     * @param TextNodeComparator $leftComparator  Root of the first tree.
40
     * @param TextNodeComparator $rightComparator Root of the second tree.
41
     */
42 43
    public function diff(TextNodeComparator $leftComparator, TextNodeComparator $rightComparator): void
43
    {
44 43
        $settings = new LCSSettings();
45 43
        $settings->setUseGreedyMethod(false);
46
47
        /** @var RangeDifference[] $differences */
48 43
        $differences = RangeDifferencer::findDifferences($leftComparator, $rightComparator, $settings);
49 43
        $pDifferences = $this->preProcess($differences);
50
51 43
        $currentIndexLeft = 0;
52 43
        $currentIndexRight = 0;
53
54
        /** @var RangeDifference $d */
55 43
        foreach ($pDifferences as $d) {
56 27
            if ($d->getLeftStart() > $currentIndexLeft) {
57 21
                $rightComparator->handlePossibleChangedPart(
58 21
                    $currentIndexLeft, $d->getLeftStart(),
59 21
                    $currentIndexRight, $d->getRightStart(),
60 21
                    $leftComparator);
61
            }
62
63 27
            if ($d->getLeftLength() > 0) {
64 17
                $rightComparator->markAsDeleted(
65 17
                    $d->getLeftStart(), $d->getLeftEnd(),
66 17
                    $leftComparator,
67 17
                    $d->getRightStart());
68
            }
69
70 27
            $rightComparator->markAsNew($d->getRightStart(), $d->getRightEnd());
71
72 27
            $currentIndexLeft = $d->getLeftEnd();
73 27
            $currentIndexRight = $d->getRightEnd();
74
        }
75
76 43
        if ($currentIndexLeft < $leftComparator->getRangeCount()) {
77 36
            $rightComparator->handlePossibleChangedPart(
78 36
                $currentIndexLeft, $leftComparator->getRangeCount(),
79 36
                $currentIndexRight, $rightComparator->getRangeCount(),
80 36
                $leftComparator);
81
        }
82
83 43
        $rightComparator->expandWhiteSpace();
84 43
        $this->output->generateOutput($rightComparator->getBodyNode());
85 43
    }
86
87
    /**
88
     * @param TextNodeComparator $ancestorComparator
89
     * @param TextNodeComparator $leftComparator
90
     * @param TextNodeComparator $rightComparator
91
     */
92 5
    public function diff3(
93
        TextNodeComparator $ancestorComparator,
94
        TextNodeComparator $leftComparator,
95
        TextNodeComparator $rightComparator
96
    ): void {
97 5
        $settings = new LCSSettings();
98 5
        $settings->setUseGreedyMethod(false);
99
100
        /** @var RangeDifference[] $differences */
101 5
        $differences = RangeDifferencer::findDifferences3($ancestorComparator, $leftComparator, $rightComparator);
102 5
        $pDifferences = $this->preProcess($differences);
103
104 5
        $currentIndexAncestor = 0;
105 5
        $currentIndexLeft = 0;
106 5
        $currentIndexRight = 0;
107
108
        /** @var RangeDifference $d */
109 5
        foreach ($pDifferences as $d) {
110 4
            $tempKind = $d->getKind();
111
112 4
            if (RangeDifference::ANCESTOR === $tempKind) {
113
                // Ignore, we won't show pseudo-conflicts currently (left and right have the same change).
114 1
                continue;
115
            }
116
117 3
            if ($d->getLeftStart() > $currentIndexLeft) {
118 3
                $ancestorComparator->handlePossibleChangedPart(
119 3
                    $currentIndexLeft, $d->getLeftStart(),
120 3
                    $currentIndexAncestor, $d->getAncestorStart(),
121 3
                    $leftComparator);
122
            }
123
124 3
            if ($d->getRightStart() > $currentIndexRight) {
125 3
                $ancestorComparator->handlePossibleChangedPart(
126 3
                    $currentIndexRight, $d->getRightStart(),
127 3
                    $currentIndexAncestor, $d->getAncestorStart(),
128 3
                    $rightComparator);
129
            }
130
131 3
            if (RangeDifference::CONFLICT === $tempKind || RangeDifference::LEFT === $tempKind) {
132
                // Conflicts and changes on the left side.
133 3
                if ($d->getLeftLength() > 0) {
134 3
                    $ancestorComparator->markAsDeleted(
135 3
                        $d->getLeftStart(), $d->getLeftEnd(), $leftComparator,
136 3
                        $d->getAncestorStart(), ModificationType::ADDED);
137
                }
138
            }
139
140 3
            if (RangeDifference::CONFLICT === $tempKind || RangeDifference::RIGHT === $tempKind) {
141
                // Conflicts and changes on the right side.
142 3
                if ($d->getRightLength() > 0) {
143 2
                    $ancestorComparator->markAsDeleted(
144 2
                        $d->getRightStart(), $d->getRightEnd(), $rightComparator,
145 2
                        $d->getAncestorStart(), ModificationType::ADDED);
146
                }
147
            }
148
149 3
            $ancestorComparator->markAsNew($d->getAncestorStart(), $d->getAncestorEnd(), ModificationType::REMOVED);
150
151 3
            $currentIndexAncestor = $d->getAncestorEnd();
152 3
            $currentIndexLeft = $d->getLeftEnd();
153 3
            $currentIndexRight = $d->getRightEnd();
154
        }
155
156 5
        if ($currentIndexLeft < $leftComparator->getRangeCount()) {
157 5
            $ancestorComparator->handlePossibleChangedPart(
158 5
                $currentIndexLeft, $leftComparator->getRangeCount(),
159 5
                $currentIndexAncestor, $ancestorComparator->getRangeCount(),
160 5
                $leftComparator);
161
        }
162
163 5
        if ($currentIndexRight < $rightComparator->getRangeCount()) {
164 5
            $ancestorComparator->handlePossibleChangedPart(
165 5
                $currentIndexRight, $rightComparator->getRangeCount(),
166 5
                $currentIndexAncestor, $ancestorComparator->getRangeCount(),
167 5
                $rightComparator);
168
        }
169
170 5
        $ancestorComparator->expandWhiteSpace();
171 5
        $this->output->generateOutput($ancestorComparator->getBodyNode());
172 5
    }
173
174
    /**
175
     * @param RangeDifference[] $differences
176
     * @return RangeDifference[]
177
     */
178 48
    private function preProcess(array $differences): array
179
    {
180
        /** @var RangeDifference[] */
181 48
        $newRanges = [];
182
183 48
        for ($i = 0, $iMax = \count($differences); $i < $iMax; $i++) {
184 31
            $ancestorStart = $differences[$i]->getAncestorStart();
185 31
            $ancestorEnd = $differences[$i]->getAncestorEnd();
186 31
            $leftStart = $differences[$i]->getLeftStart();
187 31
            $leftEnd = $differences[$i]->getLeftEnd();
188 31
            $rightStart = $differences[$i]->getRightStart();
189 31
            $rightEnd = $differences[$i]->getRightEnd();
190 31
            $kind = $differences[$i]->getKind();
191
192 31
            $ancestorLength = $ancestorEnd - $ancestorStart;
193 31
            $leftLength = $leftEnd - $leftStart;
194 31
            $rightLength = $rightEnd - $rightStart;
195
196
            while (
197 31
                $i + 1 < $iMax &&
198 31
                $differences[$i + 1]->getKind() === $kind &&
199 2
                $this->score($leftLength, $differences[$i + 1]->getLeftLength(), $rightLength,
200 31
                    $differences[$i + 1]->getRightLength()) > ($differences[$i + 1]->getLeftStart() - $leftEnd)
201
            ) {
202
                $ancestorEnd = $differences[$i + 1]->getAncestorEnd();
203
                $leftEnd = $differences[$i + 1]->getLeftEnd();
204
                $rightEnd = $differences[$i + 1]->getRightEnd();
205
206
                $ancestorLength = $ancestorEnd - $ancestorStart;
207
                $leftLength = $leftEnd - $leftStart;
208
                $rightLength = $rightEnd - $rightStart;
209
210
                $i++;
211
            }
212
213 31
            $newRanges[] = new RangeDifference(
214 31
                $kind,
215 31
                $rightStart, $rightLength,
216 31
                $leftStart, $leftLength,
217 31
                $ancestorStart, $ancestorLength);
218
        }
219
220 48
        return $newRanges;
221
    }
222
223
    /**
224
     * @param int[] $numbers
225
     * @return float
226
     *
227
     * @throws \OutOfRangeException
228
     */
229 4
    public static function score(int ...$numbers): float
230
    {
231 4
        if (\count($numbers) < 3) {
232 1
            throw new \OutOfRangeException();
233
        }
234
235 3
        if (($numbers[0] === 0 && $numbers[1] === 0) || ($numbers[2] === 0 && $numbers[3] === 0)) {
236 3
            return (float) 0;
237
        }
238
239 2
        $d = 0;
240
241 2
        foreach ($numbers as $number) {
242 2
            while ($number > 3) {
243 1
                $d += 3;
244 1
                $number -= 3;
245 1
                $number *= 0.5;
246
            }
247
248 2
            $d += $number;
249
        }
250
251 2
        return (float) ($d / (1.5 * \count($numbers)));
252
    }
253
}
254