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

HtmlSaxDiffOutput   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 131
Duplicated Lines 0 %

Test Coverage

Coverage 92.94%

Importance

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

3 Methods

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

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 29
    public function __construct(ContentHandlerInterface $handler)
35
    {
36 29
        $this->handler = $handler;
37 29
    }
38
39
    /**
40
     * @param TagNode $node
41
     */
42 29
    public function generateOutput(TagNode $node): void
43
    {
44 29
        if ('img' !== $node->getQName() && 'body' !== $node->getQName()) {
45 29
            $this->handler->startElement($node->getQName(), $node->getAttributes());
46
        }
47
48 29
        $newStarted = false;
49 29
        $remStarted = false;
50 29
        $changeStarted = false;
51 29
        $conflictStarted = false;
52 29
        $changeText = '';
53
54 29
        foreach ($node as $child) {
55 29
            if ($child instanceof TagNode) {
56 15
                if ($newStarted) {
57
                    $this->handler->endElement('ins');
58
                    $newStarted = false;
59 15
                } elseif ($changeStarted) {
60 1
                    $this->handler->endElement('span');
61 1
                    $changeStarted = false;
62 15
                } elseif ($remStarted) {
63
                    $this->handler->endElement('del');
64
                    $remStarted = false;
65 15
                } elseif ($conflictStarted) {
66
                    $this->handler->endElement('span');
67
                    $conflictStarted = false;
68
                }
69
70 15
                $this->generateOutput($child);
71 29
            } elseif ($child instanceof TextNode) {
72 29
                $mod = $child->getModification();
73
74 29
                if ($newStarted && ($mod->getOutputType() !== ModificationType::ADDED || $mod->isFirstOfId())) {
75 7
                    $this->handler->endElement('ins');
76 7
                    $newStarted = false;
77
                } elseif (
78 29
                    $changeStarted && (
79 2
                        $mod->getOutputType() !== ModificationType::CHANGED ||
80 1
                        $mod->getChanges() !== $changeText ||
81 29
                        $mod->isFirstOfId()
82
                    )
83
                ) {
84 2
                    $this->handler->endElement('span');
85 2
                    $changeStarted = false;
86 29
                } elseif ($remStarted && ($mod->getOutputType() !== ModificationType::REMOVED || $mod->isFirstOfId())) {
87 6
                    $this->handler->endElement('del');
88 6
                    $remStarted = false;
89
                } elseif (
90 29
                    $conflictStarted &&
91 29
                    ($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 29
                if (!$newStarted && $mod->getOutputType() === ModificationType::ADDED) {
99 12
                    $attrs = ['class' => 'diff-html-added'];
100 12
                    $this->handler->startElement('ins', $attrs);
101 12
                    $newStarted = true;
102 28
                } elseif (!$changeStarted && $mod->getOutputType() === ModificationType::CHANGED) {
103 3
                    $attrs = ['class' => 'diff-html-changed'];
104 3
                    $this->handler->startElement('span', $attrs);
105 3
                    $changeStarted = true;
106 3
                    $changeText = $mod->getChanges();
107 27
                } elseif (!$remStarted && $mod->getOutputType() === ModificationType::REMOVED) {
108 12
                    $attrs = ['class' => 'diff-html-removed'];
109 12
                    $this->handler->startElement('del', $attrs);
110 12
                    $remStarted = true;
111 23
                } 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 29
                if ($child instanceof ImageNode) {
118 9
                    $this->writeImage($child);
119
                } else {
120 29
                    $this->handler->characters($child->getText());
121
                }
122
            }
123
        }
124
125 29
        if ($newStarted) {
126 6
            $this->handler->endElement('ins');
127 27
        } elseif ($changeStarted) {
128 3
            $this->handler->endElement('span');
129 27
        } elseif ($remStarted) {
130 7
            $this->handler->endElement('del');
131 24
        } elseif ($conflictStarted) {
132 2
            $this->handler->endElement('span');
133
        }
134
135 29
        if ('img' !== $node->getQName() && 'body' !== $node->getQName()) {
136 29
            $this->handler->endElement($node->getQName());
137
        }
138 29
    }
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