Passed
Branch master (31b9e5)
by Tomáš
01:44
created

DefaultReader::nextNode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Inspirum\XML\Reader;
6
7
use Inspirum\XML\Builder\Document;
8
use Inspirum\XML\Builder\Node;
9
use Inspirum\XML\Exception\Handler;
10
use Inspirum\XML\Formatter\Formatter;
11
use XMLReader;
12
13
final class DefaultReader implements Reader
14
{
15 14
    public function __construct(
16
        private XMLReader $reader,
17
        private Document $document,
18
    ) {
19
    }
20
21 14
    public function __destruct()
22
    {
23 14
        $this->reader->close();
24
    }
25
26
    /**
27
     * @inheritDoc
28
     */
29 7
    public function iterateNode(string $nodeName): iterable
30
    {
31 7
        $found = $this->moveToNode($nodeName);
32
33 6
        if ($found === false) {
34 1
            return yield from [];
35
        }
36
37
        do {
38 5
            $item = $this->readNode();
39
40 5
            if ($item !== null) {
41 5
                yield $item;
42
            }
43 5
        } while ($this->moveToNextNode($nodeName));
44
    }
45
46 8
    public function nextNode(string $nodeName): ?Node
47
    {
48 8
        $found = $this->moveToNode($nodeName);
49
50 7
        if ($found === false) {
51 2
            return null;
52
        }
53
54 6
        return $this->readNode();
55
    }
56
57 14
    private function moveToNode(string $nodeName): bool
58
    {
59 14
        while ($this->read()) {
60 12
            if ($this->isNodeElementType() && $this->getNodeName() === $nodeName) {
61 10
                return true;
62
            }
63
        }
64
65 3
        return false;
66
    }
67
68 5
    private function moveToNextNode(string $nodeName): bool
69
    {
70 5
        $localName = Formatter::getLocalName($nodeName);
71
72 5
        while ($this->reader->next($localName)) {
73 5
            if ($this->getNodeName() === $nodeName) {
74 4
                return true;
75
            }
76
        }
77
78 5
        return false;
79
    }
80
81 10
    private function readNode(): ?Node
82
    {
83 10
        $nodeName   = $this->getNodeName();
84 10
        $attributes = $this->getNodeAttributes();
85
86 10
        if ($this->isNodeEmptyElementType()) {
87 1
            return $this->document->createElement($nodeName, $attributes);
88
        }
89
90 9
        $node     = null;
91 9
        $text     = null;
92 9
        $elements = [];
93
94 9
        while ($this->read()) {
95 9
            if ($this->isNodeElementEndType() && $this->getNodeName() === $nodeName) {
96 9
                $node = $this->document->createTextElement($nodeName, $text, $attributes);
97
98 9
                foreach ($elements as $element) {
99 6
                    $node->append($element);
100
                }
101
102 9
                break;
103
            }
104
105 8
            if ($this->isNodeTextType()) {
106 8
                $text = $this->getNodeValue();
107 6
            } elseif ($this->isNodeElementType()) {
108 6
                if ($this->isNodeEmptyElementType()) {
109 1
                    $elements[] = $this->document->createElement($this->getNodeName());
110 1
                    continue;
111
                }
112
113 6
                $element = $this->readNode();
114 6
                if ($element !== null) {
115 6
                    $elements[] = $element;
116
                }
117
            }
118
        }
119
120 9
        return $node;
121
    }
122
123 14
    private function read(): bool
124
    {
125 14
        return Handler::withErrorHandlerForXMLReader(fn(): bool => $this->reader->read());
126
    }
127
128 12
    private function getNodeName(): string
129
    {
130 12
        return $this->reader->name;
131
    }
132
133 12
    private function getNodeType(): int
134
    {
135 12
        return $this->reader->nodeType;
136
    }
137
138 10
    private function getNodeValue(): string
139
    {
140 10
        return $this->reader->value;
141
    }
142
143 12
    private function isNodeElementType(): bool
144
    {
145 12
        return $this->isNodeType(XMLReader::ELEMENT);
146
    }
147
148 10
    private function isNodeEmptyElementType(): bool
149
    {
150 10
        return $this->reader->isEmptyElement;
151
    }
152
153 9
    private function isNodeElementEndType(): bool
154
    {
155 9
        return $this->isNodeType(XMLReader::END_ELEMENT);
156
    }
157
158 8
    private function isNodeTextType(): bool
159
    {
160 8
        return $this->isNodeType(XMLReader::TEXT) || $this->isNodeType(XMLReader::CDATA);
161
    }
162
163 12
    private function isNodeType(int $type): bool
164
    {
165 12
        return $this->getNodeType() === $type;
166
    }
167
168
    /**
169
     * @return array<string,string>
170
     */
171 10
    private function getNodeAttributes(): array
172
    {
173 10
        $attributes = [];
174
175 10
        if ($this->reader->hasAttributes) {
176 6
            while ($this->reader->moveToNextAttribute()) {
177 6
                $attributes[$this->getNodeName()] = $this->getNodeValue();
178
            }
179
180 6
            $this->reader->moveToElement();
181
        }
182
183 10
        return $attributes;
184
    }
185
}
186