Completed
Push — master ( 232a3b...f7d1b9 )
by Nikola
04:46
created

AbstractSaxHandler::attachHandlers()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 48
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 3.1105

Importance

Changes 0
Metric Value
dl 0
loc 48
ccs 20
cts 26
cp 0.7692
rs 9.125
c 0
b 0
f 0
cc 3
eloc 30
nc 2
nop 1
crap 3.1105
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
use RunOpenCode\Sax\Exception\RuntimeException;
15
16
/**
17
 * Class AbstractSaxHandler
18
 *
19
 * Sax handler prototype.
20
 *
21
 * @package RunOpenCode\Sax
22
 */
23
abstract class AbstractSaxHandler implements SaxHandlerInterface
24
{
25
    /**
26
     * @var array
27
     */
28
    protected $options;
29
30
    /**
31
     * @var string|null
32
     */
33
    private $currentElement = null;
34
35
    /**
36
     * @var int
37
     */
38
    private $stackSize = 0;
39
40
    /**
41
     * @var string|null
42
     */
43
    private $dataBuffer = null;
44
45
    /**
46
     * AbstractSaxHandler constructor.
47
     *
48
     * @param array $options
49
     */
50 7
    public function __construct(array $options = array())
51
    {
52 7
        $this->options = array_merge(array(
53 7
            'buffer_size'   => 4096,
54
            'case_folding'  => true,
55
            'namespaces'    => false,
56
            'separator'     => ':',
57
            'encoding'      => 'UTF-8',
58
            'skip_tagstart' => null,
59
            'skip_white'    => null,
60 7
        ), $options);
61 7
    }
62
63
    /**
64
     * {@inheritdoc}
65
     */
66 6
    final public function parse(StreamInterface $stream)
67
    {
68 6
        $parser = (true === $this->options['namespaces'])
69
            ?
70
            xml_parser_create_ns($this->options['encoding'], $this->options['separator'])
71
            :
72 6
            xml_parser_create($this->options['encoding']);
73
74 6
        if (false === $this->options['case_folding']) {
75
            xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
76
        }
77
78 6
        if (null === $this->options['skip_tagstart']) {
79 6
            xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, $this->options['skip_tagstart']);
80
        }
81
82 6
        if (null === $this->options['skip_white']) {
83 6
            xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $this->options['skip_white']);
84
        }
85
86 6
        $this->onDocumentStart($parser, $stream);
87
88 6
        $this->attachHandlers($parser);
89
90 6
        $this->process($parser, $stream);
91
92 5
        $this->onDocumentEnd($parser, $stream);
93
94 5
        xml_parser_free($parser);
95
96 5
        $stream->close();
97
98 5
        return $this->getResult();
99
    }
100
101
    /**
102
     * Document start handler, executed when parsing process started.
103
     *
104
     * @param resource $parser Parser handler.
105
     * @param StreamInterface $stream XML stream.
106
     */
107
    abstract protected function onDocumentStart($parser, $stream);
108
109
    /**
110
     * Element start handler, executed when XML tag is entered.
111
     *
112
     * @param resource $parser Parser handler.
113
     * @param string $name Tag name.
114
     * @param array $attributes Element attributes.
115
     */
116
    abstract protected function onElementStart($parser, $name, $attributes);
117
118
    /**
119
     * Element CDATA handler, executed when XML tag CDATA is parsed.
120
     *
121
     * @param resource $parser Parser handler.
122
     * @param string $data Element CDATA.
123
     */
124
    abstract protected function onElementData($parser, $data);
125
126
    /**
127
     * Element end handler, executed when XML tag is leaved.
128
     *
129
     * @param resource $parser Parser handler.
130
     * @param string $name Tag name.
131
     */
132
    abstract protected function onElementEnd($parser, $name);
133
134
    /**
135
     * Document end handler, executed when parsing process ended.
136
     *
137
     * @param resource $parser Parser handler.
138
     * @param StreamInterface $stream XML stream.
139
     */
140
    abstract protected function onDocumentEnd($parser, $stream);
141
142
    /**
143
     * Start namespace declaration handler, executed when namespace declaration started.
144
     *
145
     * @param resource $parser Parser handler.
146
     * @param string $prefix Namespace reference within an XML object.
147
     * @param string $uri Uniform Resource Identifier (URI) of namespace.
148
     */
149
    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...
150
    {
151
        throw new RuntimeException(sprintf('When namespace support is on, method "%s" must be overridden.', __METHOD__));
152
    }
153
154
    /**
155
     * End namespace declaration handler, executed when namespace declaration ended.
156
     *
157
     * @param resource $parser Parser handler.
158
     * @param string $prefix Namespace reference within an XML object.
159
     */
160
    protected function onNamespaceDeclarationEnd($parser , string $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...
161
    {
162
        throw new RuntimeException(sprintf('When namespace support is on, method "%s" must be overridden.', __METHOD__));
163
    }
164
165
    /**
166
     * Parsing error handler.
167
     *
168
     * @param string $message Parsing error message.
169
     * @param int $code Error code.
170
     * @param int $lineno XML line number which caused error.
171
     */
172
    abstract protected function onParseError($message, $code, $lineno);
173
174
    /**
175
     * Get parsing result.
176
     *
177
     * Considering that your handler processed XML document, this method will collect
178
     * parsing result. This method is called last and it will provide parsing result to invoker.
179
     *
180
     * @return mixed Parsing result
181
     */
182
    abstract protected function getResult();
183
184
    /**
185
     * Parse path to XML document/string content.
186
     *
187
     * @param resource $parser Parser.
188
     * @param StreamInterface $stream XML document stream.
189
     * @return AbstractSaxHandler $this Fluent interface.
190
     *
191
     * @throws \RuntimeException
192
     */
193 6
    private function process($parser, StreamInterface $stream)
194
    {
195 6
        if ($stream->eof()) {
196 1
            $stream->rewind();
197
        }
198
199 6
        while ($data = $stream->read($this->options['buffer_size'])) {
200 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));
201
        }
202
203 5
        return $this;
204
    }
205
206
    /**
207
     * Attach handlers.
208
     *
209
     * @param resource $parser XML parser.
210
     * @return AbstractSaxHandler $this Fluent interface.
211
     */
212
    private function attachHandlers($parser)
213
    {
214 6
        $onElementStart = \Closure::bind(function ($parser, $name, $attributes) {
215 6
            $this->currentElement = $name;
216 6
            $this->stackSize++;
217 6
            $this->dataBuffer = null;
218
219 6
            $this->onElementStart($parser, $name, $attributes);
220 6
        }, $this);
221
222 6
        $onElementEnd   = \Closure::bind(function ($parser, $name) {
223 6
            $this->currentElement = null;
224 6
            $this->stackSize--;
225
226 6
            if (null !== $this->dataBuffer) {
227 6
                $this->onElementData($parser, $this->dataBuffer);
228
            }
229
230 6
            $this->dataBuffer = null;
231
232 6
            $this->onElementEnd($parser, $name);
233 6
        }, $this);
234
235 6
        $onElementData  =  \Closure::bind(function ($parser, $data) {
236 6
            $this->dataBuffer .= $data;
237 6
        }, $this);
238
239 6
        xml_set_element_handler($parser, $onElementStart, $onElementEnd);
240
241 6
        xml_set_character_data_handler($parser, $onElementData);
242
243 6
        if ($this->options['namespaces']) {
244
245
            $onNamespaceDeclarationStart = \Closure::bind(function ($parser, $prefix, $uri) {
246
                $this->onNamespaceDeclarationStart($parser, $prefix, $uri);
247
            }, $this);
248
249
            $onNamespaceDeclarationEnd   = \Closure::bind(function ($parser, $prefix) {
250
                $this->onNamespaceDeclarationEnd($parser, $prefix);
251
            }, $this);
252
253
            xml_set_start_namespace_decl_handler($parser, $onNamespaceDeclarationStart);
254
255
            xml_set_end_namespace_decl_handler($parser, $onNamespaceDeclarationEnd);
256
        }
257
258
        return $this;
259
    }
260
}
261