HtmlSaxDiffOutput   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 131
Duplicated Lines 0 %

Test Coverage

Coverage 97.65%

Importance

Changes 0
Metric Value
eloc 85
dl 0
loc 131
ccs 83
cts 85
cp 0.9765
rs 8.96
c 0
b 0
f 0
wmc 43

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A writeImage() 0 14 4
F generateOutput() 0 95 38

How to fix   Complexity   

Complex Class

Complex classes like HtmlSaxDiffOutput often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HtmlSaxDiffOutput, and based on these observations, apply Extract Interface, too.

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\Dom\ImageNode;
14
use SN\DaisyDiff\Html\Dom\TagNode;
15
use SN\DaisyDiff\Html\Dom\TextNode;
16
use SN\DaisyDiff\Html\Modification\Modification;
17
use SN\DaisyDiff\Html\Modification\ModificationType;
18
use SN\DaisyDiff\Output\DiffOutputInterface;
19
use SN\DaisyDiff\Xml\ContentHandlerInterface;
20
21
/**
22
 * Takes a branch root and creates an HTML file for it.
23
 *
24
 * @internal
25
 */
26
class HtmlSaxDiffOutput implements DiffOutputInterface
27
{
28
    /** @var ContentHandlerInterface */
29
    private $handler;
30
31
    /**
32
     * @param ContentHandlerInterface $handler
33
     */
34 62
    public function __construct(ContentHandlerInterface $handler)
35
    {
36 62
        $this->handler = $handler;
37 62
    }
38
39
    /**
40
     * @param TagNode $node
41
     */
42 62
    public function generateOutput(TagNode $node): void
43
    {
44 62
        if ('img' !== $node->getQName() && 'body' !== $node->getQName()) {
45 62
            $this->handler->startElement($node->getQName(), $node->getAttributes());
46
        }
47
48 62
        $newStarted = false;
49 62
        $remStarted = false;
50 62
        $changeStarted = false;
51 62
        $conflictStarted = false;
52 62
        $changeText = '';
53
54 62
        foreach ($node as $child) {
55 62
            if ($child instanceof TagNode) {
56 48
                if ($newStarted) {
57 3
                    $this->handler->endElement('ins');
58 3
                    $newStarted = false;
59 48
                } elseif ($changeStarted) {
60 5
                    $this->handler->endElement('span');
61 5
                    $changeStarted = false;
62 47
                } elseif ($remStarted) {
63 1
                    $this->handler->endElement('del');
64 1
                    $remStarted = false;
65 47
                } elseif ($conflictStarted) {
66
                    $this->handler->endElement('span');
67
                    $conflictStarted = false;
68
                }
69
70 48
                $this->generateOutput($child);
71 62
            } elseif ($child instanceof TextNode) {
72 62
                $mod = $child->getModification();
73
74 62
                if ($newStarted && ($mod->getOutputType() !== ModificationType::ADDED || $mod->isFirstOfId())) {
75 17
                    $this->handler->endElement('ins');
76 17
                    $newStarted = false;
77
                } elseif (
78 62
                    $changeStarted && (
79 17
                        $mod->getOutputType() !== ModificationType::CHANGED ||
80 16
                        $mod->getChanges() !== $changeText ||
81 62
                        $mod->isFirstOfId()
82
                    )
83
                ) {
84 16
                    $this->handler->endElement('span');
85 16
                    $changeStarted = false;
86 62
                } elseif ($remStarted && ($mod->getOutputType() !== ModificationType::REMOVED || $mod->isFirstOfId())) {
87 17
                    $this->handler->endElement('del');
88 17
                    $remStarted = false;
89
                } elseif (
90 62
                    $conflictStarted &&
91 62
                    ($mod->getOutputType() !== ModificationType::CONFLICT || $mod->isFirstOfId())
92
                ) {
93 2
                    $this->handler->endElement('span');
94 2
                    $conflictStarted = false;
95
                }
96
97
                // No else because a removed part can just be closed and a new part can start.
98 62
                if (!$newStarted && $mod->getOutputType() === ModificationType::ADDED) {
99 25
                    $attrs = ['class' => 'diff-html-added'];
100 25
                    $this->handler->startElement('ins', $attrs);
101 25
                    $newStarted = true;
102 61
                } elseif (!$changeStarted && $mod->getOutputType() === ModificationType::CHANGED) {
103 19
                    $attrs = ['class' => 'diff-html-changed'];
104 19
                    $this->handler->startElement('span', $attrs);
105 19
                    $changeStarted = true;
106 19
                    $changeText = $mod->getChanges();
107 60
                } elseif (!$remStarted && $mod->getOutputType() === ModificationType::REMOVED) {
108 24
                    $attrs = ['class' => 'diff-html-removed'];
109 24
                    $this->handler->startElement('del', $attrs);
110 24
                    $remStarted = true;
111 56
                } elseif (!$conflictStarted && $mod->getOutputType() === ModificationType::CONFLICT) {
112 4
                    $attrs = ['class' => 'diff-html-conflict'];
113 4
                    $this->handler->startElement('span', $attrs);
114 4
                    $conflictStarted = true;
115
                }
116
117 62
                if ($child instanceof ImageNode) {
118 9
                    $this->writeImage($child);
119
                } else {
120 62
                    $this->handler->characters($child->getText());
121
                }
122
            }
123
        }
124
125 62
        if ($newStarted) {
126 11
            $this->handler->endElement('ins');
127 60
        } elseif ($changeStarted) {
128 16
            $this->handler->endElement('span');
129 59
        } elseif ($remStarted) {
130 10
            $this->handler->endElement('del');
131 56
        } elseif ($conflictStarted) {
132 2
            $this->handler->endElement('span');
133
        }
134
135 62
        if ('img' !== $node->getQName() && 'body' !== $node->getQName()) {
136 62
            $this->handler->endElement($node->getQName());
137
        }
138 62
    }
139
140
    /**
141
     * @param ImageNode $imageNode
142
     */
143 9
    private function writeImage(ImageNode $imageNode): void
144
    {
145 9
        $attrs = $imageNode->getAttributes();
146
147 9
        if ($imageNode->getModification()->getOutputType() === ModificationType::REMOVED) {
148 2
            $attrs['changeType'] = 'diff-removed-image';
149 7
        } elseif ($imageNode->getModification()->getOutputType() === ModificationType::ADDED) {
150 3
            $attrs['changeType'] = 'diff-added-image';
151 4
        } elseif ($imageNode->getModification()->getOutputType() === ModificationType::CONFLICT) {
152 2
            $attrs['changeType'] = 'diff-conflict-image';
153
        }
154
155 9
        $this->handler->startElement('img', $attrs);
156 9
        $this->handler->endElement('img');
157 9
    }
158
}
159