Completed
Push — master ( b78812...1c890a )
by Nikola
04:35 queued 03:15
created

AbstractSaxHandler::attachHandlers()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 35
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 0
cts 18
cp 0
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 22
nc 2
nop 1
crap 6
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 2
    public function __construct(array $options = array())
36
    {
37 2
        $this->options = array_merge(array(
38 2
            'buffer_size'   => 4096,
39
            'case_folding'  => true,
40
            'namespaces'    => false,
41
            'separator'     => ':',
42
            'encoding'      => null,
43
            'skip_tagstart' => null,
44
            'skip_white'    => null,
45 2
        ), $options);
46 2
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51 1
    final public function parse(StreamInterface $stream)
52
    {
53 1
        $parser = (true === $this->options['namespaces'])
54
            ?
55
            xml_parser_create_ns($this->options['encoding'], $this->options['separator'])
56
            :
57 1
            xml_parser_create($this->options['encoding']);
58
59 1
        if (false === $this->options['case_folding']) {
60
            xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
61
        }
62
63 1
        if (null === $this->options['skip_tagstart']) {
64 1
            xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, $this->options['skip_tagstart']);
65
        }
66
67 1
        if (null === $this->options['skip_white']) {
68 1
            xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $this->options['skip_white']);
69
        }
70
71
        $this->onDocumentStart($parser, $stream);
72
73
        $this->attachHandlers($parser);
74
75
        $this->process($parser, $stream);
76
77
        $this->onDocumentEnd($parser, $stream);
78
79
        xml_parser_free($parser);
80
81
        $stream->close();
82
83
        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
    private function process($parser, StreamInterface $stream)
179
    {
180
        if ($stream->eof()) {
181
            $stream->rewind();
182
        }
183
184
        while ($data = $stream->read($this->options['buffer_size'])) {
185
            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
        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
        $onElementStart = \Closure::bind(function ($parser, $name, $attributes) {
200
            $this->onElementStart($parser, $name, $attributes);
201
        }, $this);
202
203
        $onElementEnd   = \Closure::bind(function ($parser, $name) {
204
            $this->onElementEnd($parser, $name);
205
        }, $this);
206
207
        $onElementData  =  \Closure::bind(function ($parser, $data) {
208
            $this->onElementData($parser, $data);
209
        }, $this);
210
211
        xml_set_element_handler($parser, $onElementStart, $onElementEnd);
212
213
        xml_set_character_data_handler($parser, $onElementData);
214
215
        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