Completed
Push — master ( 5f3a6c...fcd09b )
by Kacper
05:50
created

XmlParser::handleElementStart()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
nc 2
nop 2
dl 0
loc 11
ccs 7
cts 7
cp 1
crap 2
rs 9.4285
c 5
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
    /**
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 3
    public function __construct(XmlElementFactory $factory)
58
    {
59 3
        $this->factory = $factory;
60
61 3
        $this->reset();
62 3
    }
63
64
    /**
65
     * Resets state of xml parser.
66
     */
67 3
    public function reset()
68
    {
69 3
        $this->_parser = xml_parser_create();
70
71 3
        xml_parser_set_option($this->_parser, XML_OPTION_SKIP_WHITE, 1);
72 3
        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, 0);
73
74
        xml_set_element_handler($this->_parser, function ($parser, $name, $attrs) {
75 3
            $this->handleElementStart($name, $attrs);
76
        }, function () {
77 3
            $this->handleElementEnd();
78 3
        });
79
80
        xml_set_character_data_handler($this->_parser, function ($parser, $data) {
81
            $this->handleTextData($data);
82 3
        });
83
84 3
        $this->_stack = [];
85 3
    }
86
87 3
    public function parse($data)
88
    {
89 3
        xml_parse($this->_parser, $data);
90 3
    }
91
92 3
    private function _attributes($attrs)
93
    {
94 3
        $attributes = [];
95 3
        $namespaces = [];
96
97 3
        foreach ($attrs as $attr => $value) {
98 3
            if (strpos($attr, 'xmlns') === 0) {
99 3
                $namespaces[substr($attr, 6) ?: null] = $value;
100
            } else {
101 3
                $attributes[$attr] = $value;
102
            }
103
        }
104
105 3
        return [$attributes, $namespaces];
106
    }
107
108 3
    private function _lookup($prefix, $namespaces)
109
    {
110 3
        if ($prefix === 'xmlns') {
111
            return 'http://www.w3.org/2000/xmlns/';
112
        }
113
114 3
        if (isset($namespaces[ $prefix ])) {
115 2
            return $namespaces[ $prefix ];
116
        }
117
118 3
        return !empty($this->_stack) ? end($this->_stack)->lookupUri($prefix) : null;
119
    }
120
121 3
    private function _element($name, $attrs)
122
    {
123 3
        list($attributes, $namespaces) = $this->_attributes($attrs);
124 3
        list($tag, $prefix)            = XmlElement::resolve($name);
125
126 3
        $uri   = $this->_lookup($prefix, $namespaces);
127
128
        /** @var XmlElement $element */
129 3
        $element = $this->factory->create($uri, $tag, [ $name, $uri ], $this->_getCollocations());
130
131 3
        foreach ($namespaces as $prefix => $uri) {
132 3
            $element->setNamespace($uri, $prefix);
133
        }
134 3
        foreach ($attributes as $name => $value) {
135
            $element->setAttribute($name, $value);
136
        }
137
138 3
        return $element;
139
    }
140
141 3
    private function _getCollocations()
142
    {
143 3
        if(empty($this->_stack)) {
144 3
            return [];
145
        }
146
147 1
        $top = end($this->_stack);
148 1
        return array_reduce($top->parents, function($additional, $current) {
149
            return $current instanceof XmlFactoryCollocations
150
                ? array_merge($additional, $current->getXmlCollocations())
151
                : $additional;
152 1
        }, $top instanceof XmlFactoryCollocations ? $top->getXmlCollocations() : []);
153
    }
154
155 3
    private function handleElementStart($name, $attrs)
156
    {
157 3
        $element = $this->_element($name, $attrs);
158
159 3
        if (count($this->_stack) > 1) {
160 1
            end($this->_stack)->append($element);
161
        }
162 3
        $this->emit('parse.begin', [ $element ]);
163
164 3
        $this->_stack[] = $element;
165 3
    }
166
167 3
    private function handleElementEnd()
168
    {
169 3
        if (empty($this->_stack) === null) {
170
            return;
171
        }
172
173 3
        $element = array_pop($this->_stack);
174 3
        if (count($this->_stack) == 1) {
175 1
            $this->emit('element', [ $element ]);
176
        }
177
178 3
        $this->emit('parse.end', [ $element ]);
179 3
    }
180
181
    private function handleTextData($data)
182
    {
183
        if (trim($data)) {
184
            end($this->_stack)->append($data);
185
        }
186
    }
187
}
188