Completed
Push — master ( f4b721...596cf5 )
by Kacper
02:42
created

XmlElement::attributes()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 1
nop 0
dl 0
loc 12
ccs 8
cts 8
cp 1
crap 2
rs 9.4285
c 0
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\Exception\InvalidArgumentException;
19
use Kadet\Xmpp\Utils\Accessors;
20
21
use \Kadet\Xmpp\Utils\helper;
22
use \Kadet\Xmpp\Utils\filter;
23
24
/**
25
 * Class XmlElement
26
 * @package Kadet\Xmpp\Xml
27
 *
28
 * @property string $localName Tag name without prefix.
29
 * @property string $namespace
30
 * @property string $prefix
31
 * @property string $name
32
 * @property string $innerXml
33
 * @property XmlElement $parent
34
 * @property XmlElement[] $children
35
 * @property array $attributes
36
 * @property array $namespaces
37
 */
38
class XmlElement
39
{
40
    use Accessors;
41
42
    public static $tidy = [
43
        'indent'           => true,
44
        'input-xml'        => true,
45
        'output-xml'       => true,
46
        'drop-empty-paras' => false,
47
        'wrap'             => 0
48
    ];
49
50
    private $_localName;
51
    private $_prefix = null;
52
53
    private $_namespaces = [
54
    ];
55
    private $_attributes = [];
56
57
    /**
58
     * @var XmlElement
59
     */
60
    private $_parent;
61
62
    /**
63
     * @var XmlElement[]
64
     */
65
    private $_children = [];
66
67
    /**
68
     * XmlElement constructor.
69
     * @param string $name
70
     * @param string $uri
71
     */
72 15
    public function __construct(string $name, string $uri = null)
73
    {
74 15
        list($name, $prefix) = self::resolve($name);
75
76 15
        $this->_localName = $name;
77 15
        $this->_prefix    = $prefix;
78
79 15
        if ($uri !== null) {
80 5
            $this->namespace = $uri;
81
        }
82 15
    }
83
84 1
    public function __toString()
85
    {
86 1
        return trim($this->xml(true));
87
    }
88
89 2
    public function xml($clean = true): string
90
    {
91 2
        if($this->namespace && $this->_prefix === null) {
92 1
            $this->_prefix = $this->lookupPrefix($this->namespace);
93
        }
94
95 2
        $attributes = $this->attributes();
96
97 2
        $result = "<{$this->name}";
98
        $result .= ' '.implode(' ', array_map(function($key, $value) {
99 1
            return $key.'="'.htmlspecialchars($value, ENT_QUOTES).'"';
100 2
        }, array_keys($attributes), array_values($attributes)));
101
102 2
        if(!empty($this->_children)) {
103 1
            $result .= ">{$this->innerXml}</{$this->name}>";
104
        } else {
105 2
            $result .= "/>";
106
        }
107
108 2
        return $clean && function_exists('tidy_repair_string') ? tidy_repair_string($result, self::$tidy) : $result;
109
    }
110
111 2
    public function getInnerXml()
112
    {
113
        return implode('', array_map(function($element) {
114 2
            if(is_string($element)) {
115 1
                return htmlspecialchars($element);
116
            } elseif ($element instanceof XmlElement) {
117 1
                return $element->xml(false);
118
            }
119
120
            return (string)$element;
121 2
        }, $this->_children));
122
    }
123
124 5
    public function setAttribute(string $attribute, $value, string $uri = null)
125
    {
126 5
        if($uri === 'http://www.w3.org/2000/xmlns/') {
127 1
            $this->setNamespace($value, $attribute);
128 1
            return;
129
        }
130
131 4
        if($uri !== null) {
132 3
            $attribute = $this->_prefix($attribute, $uri);
133
        }
134
135 3
        $this->_attributes[$attribute] = $value;
136 3
    }
137
138 2
    public function getAttribute(string $attribute, string $uri = null)
139
    {
140 2
        if($uri !== null) {
141 1
            $attribute = $this->_prefix($attribute, $uri);
142
        }
143
144 2
        return $this->_attributes[$attribute] ?? false;
145
    }
146
147 1
    public function getParent()
148
    {
149 1
        return $this->_parent;
150
    }
151
152
    /**
153
     * @return array
154
     */
155 16
    public function getNamespaces($parent = true): array
156
    {
157 16
        if(!$this->_parent) {
158 16
            return $this->_namespaces;
159
        }
160
161 8
        if($parent) {
162 8
            return array_merge($this->_namespaces, $this->_parent->getNamespaces());
163
        } else {
164 1
            return array_diff_assoc($this->_namespaces, $this->_parent->getNamespaces());
165
        }
166
    }
167
168 2
    private function attributes(): array
169
    {
170 2
        $namespaces = $this->getNamespaces(false);
171 2
        $namespaces = array_map(function($prefix, $uri) {
172 1
            return [ $prefix ? "xmlns:{$prefix}" : 'xmlns', $uri ];
173 2
        }, array_values($namespaces), array_keys($namespaces));
174
175 2
        return array_merge(
176 2
            $this->_attributes,
177 2
            array_combine(array_column($namespaces, 0), array_column($namespaces, 1))
178
        );
179
    }
180
181 13
    public function lookupUri(string $prefix = null)
182
    {
183 13
        return array_search($prefix, $this->getNamespaces()) ?: false;
184
    }
185
186 9
    public function lookupPrefix(string $uri = null)
187
    {
188 9
        return $this->getNamespaces()[$uri] ?? false;
189
    }
190
191
    /**
192
     * @param string       $uri
193
     * @param string|false $prefix
194
     */
195 13
    public function setNamespace(string $uri, $prefix = false)
196
    {
197 13
        if($prefix === false) {
198 10
            $prefix = $this->_prefix;
199
        }
200
201 13
        $this->_namespaces[$uri] = $prefix;
202 13
    }
203
204 13
    public function getNamespace()
205
    {
206 13
        return $this->lookupUri($this->prefix);
207
    }
208
209 5
    public function getChildren()
210
    {
211 5
        return $this->_children;
212
    }
213
214 10
    public function append($element)
215
    {
216 10
        if(!is_string($element) && !$element instanceof XmlElement) {
217 1
            throw new InvalidArgumentException(helper\format('$element should be either string or object of {class} class, {type} given', [
218 1
                'class' => XmlElement::class,
219 1
                'type'  => helper\typeof($element)
220
            ]));
221
        }
222
223 9
        if($element instanceof XmlElement) {
224 8
            $element->parent  = $this;
225
        }
226
227 9
        return $this->_children[] = $element;
228
    }
229
230 6
    public function getName()
231
    {
232 6
        return ($this->_prefix ? $this->prefix.':' : null).$this->localName;
233
    }
234
235 13
    public function getPrefix()
236
    {
237 13
        return $this->_prefix;
238
    }
239
240 8
    public function getLocalName()
241
    {
242 8
        return $this->_localName;
243
    }
244
245 8
    protected function setParent(XmlElement $parent)
246
    {
247 8
        if(!$this->_prefix && ($prefix = $parent->lookupPrefix($this->namespace)) !== false) {
248 1
            $this->_namespaces[$this->namespace] = $prefix;
249 1
            $this->_prefix = $prefix;
250
        }
251
252 8
        $this->_parent = $parent;
253 8
        if($this->namespace === false) {
254 3
            $this->namespace = $parent->namespace;
255
        }
256
257 8
        if(!$this->_prefix) {
258 6
            $this->_prefix = $this->lookupPrefix($this->namespace);
259
        }
260 8
    }
261
262
    /**
263
     * Retrieves array of matching elements
264
     *
265
     * @param string $name  Requested element tag name
266
     * @param null   $uri   Requested element namespace
267
     *
268
     * @return XmlElement[] Found Elements
269
     */
270 2
    public function elements($name, $uri = null) : array
271
    {
272 2
        $predicate = filter\tag($name);
273 2
        if($uri !== null) {
274 1
            $predicate = filter\all($predicate, filter\xmlns($uri));
275
        }
276
277 2
        return $this->all($predicate);
278
    }
279
280 6
    public function getAttributes()
281
    {
282 6
        return $this->_attributes;
283
    }
284
285
    /**
286
     * Returns one element at specified index (for default the first one).
287
     *
288
     * @param string $name  Requested element tag name
289
     * @param string $uri   Requested element namespace
290
     * @param int    $index Index of element to retrieve
291
     *
292
     * @return XmlElement|false Retrieved element
293
     */
294 1
    public function element(string $name, string $uri = null, int $index = 0)
295
    {
296 1
        return array_values($this->elements($name, $uri))[$index] ?? false;
0 ignored issues
show
Bug introduced by
It seems like $uri defined by parameter $uri on line 294 can also be of type string; however, Kadet\Xmpp\Xml\XmlElement::elements() does only seem to accept null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
297
    }
298
299 2
    public function all($predicate) {
300 2
        $predicate = filter\predicate($predicate);
301 2
        return array_filter($this->_children, $predicate);
302
    }
303
304
    /**
305
     * @param string|null $query
306
     * @return XPathQuery
307
     */
308 1
    public function query(string $query = null)
309
    {
310 1
        return new XPathQuery($query, $this);
311
    }
312
313
    protected function init() { }
314
315 4
    public static function plain(string $name, string $uri = null)
316
    {
317 4
        $reflection = new \ReflectionClass(static::class);
318
319 4
        list($name, $prefix) = static::resolve($name);
320
321
        /** @var XmlElement $element */
322 4
        $element = $reflection->newInstanceWithoutConstructor();
323 4
        $element->_localName = $name;
324 4
        $element->_prefix    = $prefix;
325
326 4
        if ($uri !== null) {
327 3
            $element->namespace = $uri;
328
        }
329
330 4
        $element->init();
331 4
        return $element;
332
    }
333
334
    /**
335
     * @param string $name
336
     * @param string $uri
337
     * @return string
338
     */
339 3
    protected function _prefix(string $name, string $uri): string
340
    {
341 3
        if (($prefix = $this->lookupPrefix($uri)) === false) {
342 1
            throw new InvalidArgumentException(helper\format('URI "{uri}" is not a registered namespace', ['uri' => $uri]));
343
        }
344
345 2
        return "{$prefix}:{$name}";
346
    }
347
348 19
    public static function resolve($name)
349
    {
350 19
        $prefix = null;
351 19
        if (($pos = strpos($name, ':')) !== false) {
352 2
            $prefix = substr($name, 0, $pos);
353 2
            $name   = substr($name, $pos + 1);
354
        }
355
356 19
        return [$name, $prefix];
357
    }
358
}
359