XmlParser::_element()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
nc 4
nop 2
dl 0
loc 19
ccs 9
cts 10
cp 0.9
crap 3.009
rs 9.4285
c 2
b 0
f 0
1
<?php
2
/**
3
 * Nucleus - XMPP Library for PHP
4
 *
5
 * Copyright (C) 2016, Some rights 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
    public static $predefined = [
32
        'xmlns' => XmlElement::XMLNS,
33
        'xml'   => XmlElement::XML
34
    ];
35
36
    /**
37
     * Factory used for XML element creation
38
     *
39
     * @var XmlElementFactory
40
     */
41
    public $factory;
42
43
    /**
44
     * XML element stack.
45
     *
46
     * @var XmlElement[]
47
     */
48
    private $_stack = [];
49
50
    /**
51
     * XML parser resource
52
     *
53
     * @var resource
54
     */
55
    private $_parser;
56
57
    /**
58
     * XmlParser constructor.
59
     *
60
     * @param XmlElementFactory $factory Factory used for XML element creation
61
     */
62 12
    public function __construct(XmlElementFactory $factory)
63
    {
64 12
        $this->factory = $factory;
65
66 12
        $this->reset();
67 12
    }
68
69
    /**
70
     * Resets state of xml parser.
71
     */
72 12
    public function reset()
73
    {
74 12
        $this->_parser = xml_parser_create();
75
76 12
        xml_parser_set_option($this->_parser, XML_OPTION_SKIP_WHITE, 1);
77 12
        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, 0);
78
79
        xml_set_element_handler($this->_parser, function ($parser, $name, $attrs) {
80 3
            $this->handleElementStart($name, $attrs);
81
        }, function () {
82 3
            $this->handleElementEnd();
83 12
        });
84
85
        xml_set_character_data_handler($this->_parser, function ($parser, $data) {
86
            $this->handleTextData($data);
87 12
        });
88
89 12
        $this->_stack = [];
90 12
    }
91
92 3
    public function parse($data)
93
    {
94 3
        xml_parse($this->_parser, $data);
95 3
    }
96
97 3
    private function _attributes($attrs)
98
    {
99 3
        $attributes = [];
100 3
        $namespaces = [];
101
102 3
        foreach ($attrs as $attr => $value) {
103 3
            if (strpos($attr, 'xmlns') === 0) {
104 3
                $namespaces[substr($attr, 6) ?: null] = $value;
105
            } else {
106 3
                $attributes[$attr] = $value;
107
            }
108
        }
109
110 3
        return [$attributes, $namespaces];
111
    }
112
113 3
    private function _lookup($prefix, $namespaces)
114
    {
115 3
        if(isset(static::$predefined[$prefix])) {
116
            return static::$predefined[$prefix];
117
        }
118
119 3
        if (isset($namespaces[ $prefix ])) {
120 2
            return $namespaces[ $prefix ];
121
        }
122
123 3
        return !empty($this->_stack) ? end($this->_stack)->lookupUri($prefix) : null;
124
    }
125
126 3
    private function _element($name, $attrs)
127
    {
128 3
        list($attributes, $namespaces) = $this->_attributes($attrs);
129 3
        list($tag, $prefix)            = XmlElement::resolve($name);
130
131 3
        $uri   = $this->_lookup($prefix, $namespaces);
132
133
        /** @var XmlElement $element */
134 3
        $element = $this->factory->create($uri, $tag, [ $name, $uri ], $this->_getCollocations());
135
136 3
        foreach ($namespaces as $prefix => $uri) {
137 3
            $element->setNamespace($uri, $prefix);
138
        }
139 3
        foreach ($attributes as $name => $value) {
140
            $element->setAttribute($name, $value);
141
        }
142
143 3
        return $element;
144
    }
145
146 3
    private function _getCollocations()
147
    {
148 3
        if(empty($this->_stack)) {
149 3
            return [];
150
        }
151
152 1
        $top = end($this->_stack);
153 1
        return array_reduce($top->parents, function($additional, $current) {
154 1
            return $current instanceof XmlFactoryCollocations
155
                ? array_merge($additional, $current->getXmlCollocations())
156 1
                : $additional;
157 1
        }, $top instanceof XmlFactoryCollocations ? $top->getXmlCollocations() : []);
158
    }
159
160 3
    private function handleElementStart($name, $attrs)
161
    {
162 3
        $element = $this->_element($name, $attrs);
163
164 3
        if (count($this->_stack) > 0) {
165 1
            end($this->_stack)->append($element);
166
        }
167 3
        $this->emit('parse.begin', [ $element, count($this->_stack) ]);
168
169 3
        $this->_stack[] = $element;
170 3
    }
171
172 3
    private function handleElementEnd()
173
    {
174 3
        if (empty($this->_stack) === null) {
175
            return;
176
        }
177
178 3
        $element = array_pop($this->_stack);
179 3
        if (count($this->_stack) == 1) {
180 1
            $this->emit('element', [ $element, count($this->_stack) ]);
181
        }
182
183 3
        $this->emit('parse.end', [ $element, count($this->_stack) ]);
184 3
    }
185
186
    private function handleTextData($data)
187
    {
188
        if (trim($data)) {
189
            end($this->_stack)->append($data);
190
        }
191
    }
192
}
193