Completed
Pull Request — master (#10)
by Steve
02:41
created

HtmlDiffer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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