Completed
Push — master ( 528f7d...f4b721 )
by Kacper
04:19
created

XmlParser::_name()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
ccs 0
cts 6
cp 0
crap 6
rs 9.4285
c 2
b 0
f 0
1
<?php
2
/**
3
 * XMPP Library
4
 *
5
 * Copyright (C) 2016, Some right reserved.
6
 *
7
 * @author Kacper "Kadet" Donat <[email protected]>
8
 *
9
 * Contact with author:
10
 * Xmpp: [email protected]
11
 * E-mail: [email protected]
12
 *
13
 * From Kadet with love.
14
 */
15
16
namespace Kadet\Xmpp\Xml;
17
18
use Kadet\Xmpp\Utils\BetterEmitter;
19
use Kadet\Xmpp\Utils\BetterEmitterInterface;
20
21
/**
22
 * Class XmlParser
23
 * @package Kadet\Xmpp\Xml
24
 *
25
 * @event element
26
 */
27
class XmlParser implements BetterEmitterInterface
28
{
29
    use BetterEmitter;
30
31
    /**
32
     * Factory used for XML element creation
33
     *
34
     * @var XmlElementFactory
35
     */
36
    public $factory;
37
38
    /**
39
     * XML element stack.
40
     *
41
     * @var XmlElement[]
42
     */
43
    private $_stack = [];
44
45
    /**
46
     * XML parser resource
47
     *
48
     * @var resource
49
     */
50
    private $_parser;
51
52
    /**
53
     * XmlParser constructor.
54
     *
55
     * @param XmlElementFactory $factory Factory used for XML element creation
56
     */
57
    public function __construct(XmlElementFactory $factory)
58
    {
59
        $this->factory = $factory;
60
61
        $this->reset();
62
    }
63
64
    /**
65
     * Resets state of xml parser.
66
     */
67
    public function reset()
68
    {
69
        $this->_parser = xml_parser_create();
70
71
        xml_parser_set_option($this->_parser, XML_OPTION_SKIP_WHITE, 1);
72
        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, 0);
73
74
        xml_set_element_handler($this->_parser, function ($parser, $name, $attrs) {
75
            $this->handleElementStart($name, $attrs);
76
        }, function ($parser, $name) {
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 $name 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...
77
            $this->handleElementEnd();
78
        });
79
80
        xml_set_character_data_handler($this->_parser, function ($parser, $data) {
81
            $this->handleTextData($data);
82
        });
83
84
        $this->_stack = [];
85
    }
86
87
    public function parse($data)
88
    {
89
        xml_parse($this->_parser, $data);
90
    }
91
92
    private function _attributes($attrs)
93
    {
94
        $attributes = [];
95
        $namespaces = [];
96
97
        foreach ($attrs as $attr => $value) {
98
            if (strpos($attr, 'xmlns') === 0) {
99
                $namespaces[substr($attr, 6) ?: null] = $value;
100
            }
101
102
            $attributes[$attr] = $value;
103
        }
104
105
        return [$attributes, $namespaces];
106
    }
107
108
    private function _lookup($prefix, $namespaces)
109
    {
110
        if ($prefix === 'xmlns') {
111
            return 'http://www.w3.org/2000/xmlns/';
112
        }
113
114
        return isset($namespaces[$prefix]) ? $namespaces[$prefix] : end($this->_stack)->lookupUri($prefix);
115
    }
116
117
    private function _element($name, $attrs)
118
    {
119
        list($attributes, $namespaces) = $this->_attributes($attrs);
120
        list($tag, $prefix)            = XmlElement::resolve($name);
121
122
        $uri   = $this->_lookup($prefix, $namespaces);
123
124
        /** @var XmlElement $element */
125
        $element = $this->factory->create($uri, $tag, [ $name, $uri ]);
126
        foreach ($attributes as $name => $value) {
127
            $element->setAttribute($name, $value);
128
        }
129
130
        return $element;
131
    }
132
133
    private function handleElementStart($name, $attrs)
134
    {
135
        $element = $this->_element($name, $attrs);
136
137
        if (count($this->_stack) > 1) {
138
            end($this->_stack)->append($element);
139
        }
140
        $this->emit('parse.begin', [ $element ]);
141
142
        $this->_stack[] = $element;
143
    }
144
145
    private function handleElementEnd()
146
    {
147
        if (empty($this->_stack) === null) {
148
            return;
149
        }
150
151
        $element = array_pop($this->_stack);
152
        if (count($this->_stack) == 1) {
153
            $this->emit('element', [ $element ]);
154
        }
155
156
        $this->emit('parse.end', [ $element ]);
157
    }
158
159
    private function handleTextData($data)
160
    {
161
        if (trim($data)) {
162
            end($this->_stack)->append($data);
163
        }
164
    }
165
}
166