AbstractSaxHandler   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 266
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 84.62%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 1
dl 0
loc 266
ccs 55
cts 65
cp 0.8462
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
onDocumentStart() 0 1 ?
onElementStart() 0 1 ?
onElementData() 0 1 ?
onElementEnd() 0 1 ?
onDocumentEnd() 0 1 ?
onParseError() 0 1 ?
getResult() 0 1 ?
A __construct() 0 11 1
A parse() 0 30 4
A onNamespaceDeclarationStart() 0 4 1
A onNamespaceDeclarationEnd() 0 4 1
A getDeclaredNamespaces() 0 4 1
A process() 0 12 4
A attachHandlers() 0 50 2
A normalize() 0 4 1
1
<?php
2
/*
3
 * This file is part of the runopencode/sax, an RunOpenCode project.
4
 *
5
 * (c) 2017 RunOpenCode
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace RunOpenCode\Sax\Handler;
11
12
use Psr\Http\Message\StreamInterface;
13
use RunOpenCode\Sax\Contract\SaxHandlerInterface;
14
15
/**
16
 * Class AbstractSaxHandler
17
 *
18
 * Sax handler prototype.
19
 *
20
 * @package RunOpenCode\Sax
21
 */
22
abstract class AbstractSaxHandler implements SaxHandlerInterface
23
{
24
    /**
25
     * @var array
26
     */
27
    protected $options;
28
29
    /**
30
     * @var string|null
31
     */
32
    private $currentElement = null;
33
34
    /**
35
     * @var int
36
     */
37
    private $stackSize = 0;
38
39
    /**
40
     * @var string|null
41
     */
42
    private $dataBuffer = null;
43
44
    /**
45
     * @var array
46
     */
47
    private $namespaces = [];
48
49
    /**
50
     * AbstractSaxHandler constructor.
51
     *
52
     * @param array $options
53
     */
54 7
    public function __construct(array $options = array())
55
    {
56 7
        $this->options = array_merge(array(
57 7
            'buffer_size'   => 4096,
58
            'case_folding'  => true,
59
            'separator'     => ':',
60
            'encoding'      => 'UTF-8',
61
            'skip_tagstart' => null,
62
            'skip_white'    => null,
63 7
        ), $options);
64 7
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69 6
    final public function parse(StreamInterface $stream)
70
    {
71 6
        $parser = xml_parser_create_ns($this->options['encoding'], $this->options['separator']);
72
73 6
        if (false === $this->options['case_folding']) {
74
            xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
75
        }
76
77 6
        if (null === $this->options['skip_tagstart']) {
78 6
            xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, $this->options['skip_tagstart']);
79
        }
80
81 6
        if (null === $this->options['skip_white']) {
82 6
            xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $this->options['skip_white']);
83
        }
84
85 6
        $this->onDocumentStart($parser, $stream);
86
87 6
        $this->attachHandlers($parser);
88
89 6
        $this->process($parser, $stream);
90
91 5
        $this->onDocumentEnd($parser, $stream);
92
93 5
        xml_parser_free($parser);
94
95 5
        $stream->close();
96
97 5
        return $this->getResult();
98
    }
99
100
    /**
101
     * Document start handler, executed when parsing process started.
102
     *
103
     * @param resource $parser Parser handler.
104
     * @param StreamInterface $stream XML stream.
105
     */
106
    abstract protected function onDocumentStart($parser, $stream);
107
108
    /**
109
     * Element start handler, executed when XML tag is entered.
110
     *
111
     * @param resource $parser Parser handler.
112
     * @param string $name Tag name.
113
     * @param array $attributes Element attributes.
114
     */
115
    abstract protected function onElementStart($parser, $name, $attributes);
116
117
    /**
118
     * Element CDATA handler, executed when XML tag CDATA is parsed.
119
     *
120
     * @param resource $parser Parser handler.
121
     * @param string $data Element CDATA.
122
     */
123
    abstract protected function onElementData($parser, $data);
124
125
    /**
126
     * Element end handler, executed when XML tag is leaved.
127
     *
128
     * @param resource $parser Parser handler.
129
     * @param string $name Tag name.
130
     */
131
    abstract protected function onElementEnd($parser, $name);
132
133
    /**
134
     * Document end handler, executed when parsing process ended.
135
     *
136
     * @param resource $parser Parser handler.
137
     * @param StreamInterface $stream XML stream.
138
     */
139
    abstract protected function onDocumentEnd($parser, $stream);
140
141
    /**
142
     * Parsing error handler.
143
     *
144
     * @param string $message Parsing error message.
145
     * @param int $code Error code.
146
     * @param int $lineno XML line number which caused error.
147
     */
148
    abstract protected function onParseError($message, $code, $lineno);
149
150
    /**
151
     * Get parsing result.
152
     *
153
     * Considering that your handler processed XML document, this method will collect
154
     * parsing result. This method is called last and it will provide parsing result to invoker.
155
     *
156
     * @return mixed Parsing result
157
     */
158
    abstract protected function getResult();
159
160
    /**
161
     * Start namespace declaration handler, executed when namespace declaration started.
162
     *
163
     * @param resource $parser Parser handler.
164
     * @param string $prefix Namespace reference within an XML object.
165
     * @param string $uri Uniform Resource Identifier (URI) of namespace.
166
     */
167
    protected function onNamespaceDeclarationStart($parser, $prefix, $uri)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $prefix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $uri is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
168
    {
169
        // noop
170
    }
171
172
    /**
173
     * End namespace declaration handler, executed when namespace declaration ended.
174
     *
175
     * @param resource $parser Parser handler.
176
     * @param string $prefix Namespace reference within an XML object.
177
     */
178
    protected function onNamespaceDeclarationEnd($parser, $prefix)
0 ignored issues
show
Unused Code introduced by
The parameter $parser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $prefix is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
179
    {
180
        // noop
181
    }
182
183
    /**
184
     * Get declared namespaces.
185
     *
186
     * Retrieve declared namespaces as associative array where keys are
187
     * used prefixes within XML document. Note that only processed namespace
188
     * declarations will be provided.
189
     *
190
     * @return array
191
     */
192
    final protected function getDeclaredNamespaces()
193
    {
194
        return $this->namespaces;
195
    }
196
197
    /**
198
     * Parse path to XML document/string content.
199
     *
200
     * @param resource $parser Parser.
201
     * @param StreamInterface $stream XML document stream.
202
     * @return AbstractSaxHandler $this Fluent interface.
203
     *
204
     * @throws \RuntimeException
205
     */
206 6
    private function process($parser, StreamInterface $stream)
207
    {
208 6
        if ($stream->eof()) {
209 1
            $stream->rewind();
210
        }
211
212 6
        while ($data = $stream->read($this->options['buffer_size'])) {
213 6
            xml_parse($parser, $data, $stream->eof()) || $this->onParseError(xml_error_string(xml_get_error_code($parser)), xml_get_error_code($parser), xml_get_current_line_number($parser));
214
        }
215
216 5
        return $this;
217
    }
218
219
    /**
220
     * Attach handlers.
221
     *
222
     * @param resource $parser XML parser.
223
     * @return AbstractSaxHandler $this Fluent interface.
224
     */
225
    private function attachHandlers($parser)
226
    {
227 6
        $onElementStart = \Closure::bind(function ($parser, $name, $attributes) {
228 6
            $name                 = $this->normalize($name);
229 6
            $this->currentElement = $name;
230 6
            $this->dataBuffer     = null;
231
232 6
            $this->stackSize++;
233
234 6
            $this->onElementStart($parser, $name, $attributes);
235 6
        }, $this);
236
237 6
        $onElementEnd   = \Closure::bind(function ($parser, $name) {
238 6
            $name                 = $this->normalize($name);
239 6
            $this->currentElement = null;
240
241 6
            $this->stackSize--;
242
243 6
            if (null !== $this->dataBuffer) {
244 6
                $this->onElementData($parser, $this->dataBuffer);
245
            }
246
247 6
            $this->dataBuffer = null;
248
249 6
            $this->onElementEnd($parser, $name);
250 6
        }, $this);
251
252 6
        $onElementData  =  \Closure::bind(function ($parser, $data) {
253 6
            $this->dataBuffer .= $data;
254 6
        }, $this);
255
256 6
        $onNamespaceDeclarationStart = \Closure::bind(function ($parser, $prefix, $uri) {
257
            $this->namespaces[$prefix] = rtrim($uri, '/');
258
            $this->onNamespaceDeclarationStart($parser, $prefix, $uri);
259 6
        }, $this);
260
261 6
        $onNamespaceDeclarationEnd = \Closure::bind(function ($parser, $prefix) {
262
            $this->onNamespaceDeclarationEnd($parser, $prefix);
263 6
        }, $this);
264
265 6
        xml_set_element_handler($parser, $onElementStart, $onElementEnd);
266
267 6
        xml_set_character_data_handler($parser, $onElementData);
268
269 6
        xml_set_start_namespace_decl_handler($parser, $onNamespaceDeclarationStart);
270
271 6
        xml_set_end_namespace_decl_handler($parser, $onNamespaceDeclarationEnd);
272
273 6
        return $this;
274
    }
275
276
    /**
277
     * Normalize namespaced tag name.
278
     *
279
     * @param $name
280
     *
281
     * @return string
282
     */
283 6
    private function normalize($name)
284
    {
285 6
        return str_replace('/:', ':', $name);
286
    }
287
}
288