Completed
Push — master ( 1c890a...232a3b )
by Nikola
02:13
created

AbstractSaxHandler   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 76%

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 2
dl 0
loc 210
ccs 38
cts 50
cp 0.76
rs 10
c 0
b 0
f 0

13 Methods

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