Passed
Push — master ( 0d07bf...dd4616 )
by Steve
35s
created

HtmlDiffer   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 229
Duplicated Lines 0 %

Test Coverage

Coverage 54.03%

Importance

Changes 0
Metric Value
eloc 119
dl 0
loc 229
ccs 67
cts 124
cp 0.5403
rs 9.84
c 0
b 0
f 0
wmc 32

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
C diff3() 0 80 13
B score() 0 23 8
A preProcess() 0 43 5
A diff() 0 43 5
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 15
    public function __construct(DiffOutputInterface $output)
32
    {
33 15
        $this->output = $output;
34 15
    }
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 15
    public function diff(TextNodeComparator $leftComparator, TextNodeComparator $rightComparator): void
43
    {
44 15
        $settings = new LCSSettings();
45 15
        $settings->setUseGreedyMethod(false);
46
47
        /** @var RangeDifference[] $differences */
48 15
        $differences = RangeDifferencer::findDifferences($leftComparator, $rightComparator, $settings);
49 15
        $pDifferences = $this->preProcess($differences);
50
51 15
        $currentIndexLeft = 0;
52 15
        $currentIndexRight = 0;
53
54
        /** @var RangeDifference $d */
55 15
        foreach ($pDifferences as $d) {
56 11
            if ($d->getLeftStart() > $currentIndexLeft) {
57 5
                $rightComparator->handlePossibleChangedPart(
58 5
                    $currentIndexLeft, $d->getLeftStart(),
59 5
                    $currentIndexRight, $d->getRightStart(),
60 5
                    $leftComparator);
61
            }
62
63 11
            if ($d->getLeftLength() > 0) {
64 7
                $rightComparator->markAsDeleted(
65 7
                    $d->getLeftStart(), $d->getLeftEnd(),
66 7
                    $leftComparator,
67 7
                    $d->getRightStart());
68
            }
69
70 11
            $rightComparator->markAsNew($d->getRightStart(), $d->getRightEnd());
71
72 11
            $currentIndexLeft = $d->getLeftEnd();
73 11
            $currentIndexRight = $d->getRightEnd();
74
        }
75
76 15
        if ($currentIndexLeft < $leftComparator->getRangeCount()) {
77 9
            $rightComparator->handlePossibleChangedPart(
78 9
                $currentIndexLeft, $leftComparator->getRangeCount(),
79 9
                $currentIndexRight, $rightComparator->getRangeCount(),
80 9
                $leftComparator);
81
        }
82
83 15
        $rightComparator->expandWhiteSpace();
84 15
        $this->output->generateOutput($rightComparator->getBodyNode());
85 15
    }
86
87
    /**
88
     * @param TextNodeComparator $ancestorComparator
89
     * @param TextNodeComparator $leftComparator
90
     * @param TextNodeComparator $rightComparator
91
     */
92
    public function diff3(
93
        TextNodeComparator $ancestorComparator,
94
        TextNodeComparator $leftComparator,
95
        TextNodeComparator $rightComparator
96
    ): void {
97
        $settings = new LCSSettings();
98
        $settings->setUseGreedyMethod(false);
99
100
        /** @var RangeDifference[] $differences */
101
        $differences = RangeDifferencer::findDifferences3($ancestorComparator, $leftComparator, $rightComparator);
102
        $pDifferences = $this->preProcess($differences);
103
104
        $currentIndexAncestor = 0;
105
        $currentIndexLeft = 0;
106
        $currentIndexRight = 0;
107
108
        /** @var RangeDifference $d */
109
        foreach ($pDifferences as $d) {
110
            $tempKind = $d->getKind();
111
112
            if (RangeDifference::ANCESTOR === $tempKind) {
113
                // Ignore, we won't show pseudo-conflicts currently (left and right have the same change).
114
                continue;
115
            }
116
117
            if ($d->getLeftStart() > $currentIndexLeft) {
118
                $ancestorComparator->handlePossibleChangedPart(
119
                    $currentIndexLeft, $d->getLeftStart(),
120
                    $currentIndexAncestor, $d->getAncestorStart(),
121
                    $leftComparator);
122
            }
123
124
            if ($d->getRightStart() > $currentIndexRight) {
125
                $ancestorComparator->handlePossibleChangedPart(
126
                    $currentIndexRight, $d->getRightStart(),
127
                    $currentIndexAncestor, $d->getAncestorStart(),
128
                    $rightComparator);
129
            }
130
131
            if (RangeDifference::CONFLICT === $tempKind || RangeDifference::LEFT === $tempKind) {
132
                // Conflicts and changes on the left side.
133
                if ($d->getLeftLength() > 0) {
134
                    $ancestorComparator->markAsDeleted(
135
                        $d->getLeftStart(), $d->getLeftEnd(), $leftComparator,
136
                        $d->getAncestorStart(), ModificationType::ADDED);
137
                }
138
            }
139
140
            if (RangeDifference::CONFLICT === $tempKind || RangeDifference::RIGHT === $tempKind) {
141
                // Conflicts and changes on the right side.
142
                if ($d->getRightLength() > 0) {
143
                    $ancestorComparator->markAsDeleted(
144
                        $d->getRightStart(), $d->getRightEnd(), $rightComparator,
145
                        $d->getAncestorStart(), ModificationType::ADDED);
146
                }
147
            }
148
149
            $ancestorComparator->markAsNew($d->getAncestorStart(), $d->getAncestorEnd(), ModificationType::REMOVED);
150
151
            $currentIndexAncestor = $d->getAncestorEnd();
152
            $currentIndexLeft = $d->getLeftEnd();
153
            $currentIndexRight = $d->getRightEnd();
154
        }
155
156
        if ($currentIndexLeft < $leftComparator->getRangeCount()) {
157
            $ancestorComparator->handlePossibleChangedPart(
158
                $currentIndexLeft, $leftComparator->getRangeCount(),
159
                $currentIndexAncestor, $ancestorComparator->getRangeCount(),
160
                $leftComparator);
161
        }
162
163
        if ($currentIndexRight < $rightComparator->getRangeCount()) {
164
            $ancestorComparator->handlePossibleChangedPart(
165
                $currentIndexRight, $rightComparator->getRangeCount(),
166
                $currentIndexAncestor, $ancestorComparator->getRangeCount(),
167
                $rightComparator);
168
        }
169
170
        $ancestorComparator->expandWhiteSpace();
171
        $this->output->generateOutput($ancestorComparator->getBodyNode());
172
    }
173
174
    /**
175
     * @param RangeDifference[] $differences
176
     * @return RangeDifference[]
177
     */
178 15
    private function preProcess(array $differences): array
179
    {
180
        /** @var RangeDifference[] */
181 15
        $newRanges = [];
182
183 15
        for ($i = 0, $iMax = \count($differences); $i < $iMax; $i++) {
184 11
            $ancestorStart = $differences[$i]->getAncestorStart();
185 11
            $ancestorEnd = $differences[$i]->getAncestorEnd();
186 11
            $leftStart = $differences[$i]->getLeftStart();
187 11
            $leftEnd = $differences[$i]->getLeftEnd();
188 11
            $rightStart = $differences[$i]->getRightStart();
189 11
            $rightEnd = $differences[$i]->getRightEnd();
190 11
            $kind = $differences[$i]->getKind();
191
192 11
            $ancestorLength = $ancestorEnd - $ancestorStart;
193 11
            $leftLength = $leftEnd - $leftStart;
194 11
            $rightLength = $rightEnd - $rightStart;
195
196
            while (
197 11
                $i + 1 < $iMax &&
198 11
                $differences[$i + 1]->getKind() === $kind &&
199
                $this->score($leftLength, $differences[$i + 1]->getLeftLength(), $rightLength,
200 11
                    $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 11
            $newRanges[] = new RangeDifference(
214 11
                $kind,
215 11
                $rightStart, $rightLength,
216 11
                $leftStart, $leftLength,
217 11
                $ancestorStart, $ancestorLength);
218
        }
219
220 15
        return $newRanges;
221
    }
222
223
    /**
224
     * @param int[] $numbers
225
     * @return float
226
     *
227
     * @throws \OutOfRangeException
228
     */
229 2
    public static function score(int ...$numbers): float
230
    {
231 2
        if (\count($numbers) < 3) {
232 1
            throw new \OutOfRangeException();
233
        }
234
235 1
        if (($numbers[0] === 0 && $numbers[1] === 0) || ($numbers[2] === 0 && $numbers[3] === 0)) {
236 1
            return (float) 0;
237
        }
238
239 1
        $d = 0;
240
241 1
        foreach ($numbers as $number) {
242 1
            while ($number > 3) {
243 1
                $d += 3;
244 1
                $number -= 3;
245 1
                $number *= 0.5;
246
            }
247
248 1
            $d += $number;
249
        }
250
251 1
        return (float) ($d / (1.5 * \count($numbers)));
252
    }
253
}
254