Completed
Push — master ( db23b0...c2084f )
by Dan Michael O.
01:06
created

QuiteSimpleXMLElement::initFromObject()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 3
eloc 6
nc 3
nop 1
1
<?php
2
/*
3
 * (c) Dan Michael O. Heggø (2013)
4
 *
5
 * QuiteSimpleXMLElement is a wrapper around SimpleXMLElement to add a quite simple
6
 * feature not present in SimpleXMLElement: inheritance of namespaces.
7
 *
8
 * My first attempt was to extend the original SimpleXMLElement class, but
9
 * unfortunately the constructor is static and cannot be overriden!
10
 *
11
 * It's easier to understand with a simple example:
12
 *
13
 *     $xml = '<root xmlns:dc="http://purl.org/dc/elements/1.1/">
14
 *         <dc:a>
15
 *           <dc:b >
16
 *             1.9
17
 *           </dc:a>
18
 *         </dc:b>
19
 *       </root>';
20
 *
21
 *     $root = new SimpleXMLElement($xml);
22
 *     $root->registerXPathNamespace('d', 'http://purl.org/dc/elements/1.1/');
23
 *     $a = $root->xpath('d:a');
24
 *     $a[0]->registerXPathNamespace('d', 'http://purl.org/dc/elements/1.1/');
25
 *     $b = $a[0]->xpath('d:b');
26
 *     echo trim((string)$b[0]);
27
 *
28
 * Since namespaces are not inherited, we have to register them over and over again.
29
 * Using QuiteSimpleXMLElement instead;
30
 *
31
 *     $root = new QuiteSimpleXMLElement($xml);
32
 *     $root->registerXPathNamespace('d', 'http://purl.org/dc/elements/1.1/');
33
 *     $a = $root->xpath('d:a');
34
 *     $b = $a->xpath('d:b');
35
 *     echo trim((string)$b[0]);
36
 *
37
 * And while we're at it, we can add a few convenience methods.
38
 *
39
 * SimpleXmlElement reference:
40
 * https://github.com/draffter/FollowFunctionPHP/blob/master/_php/SimpleXML.php
41
 */
42
43
namespace Danmichaelo\QuiteSimpleXMLElement;
44
45
use Exception;
46
use InvalidArgumentException;
47
use SimpleXMLElement;
48
49
class QuiteSimpleXMLElement
50
{
51
    /**
52
     * @var array
53
     */
54
    public $namespaces;
55
56
    /**
57
     * @var SimpleXMLElement
58
     */
59
    public $el;
60
61
    /**
62
     * QuiteSimpleXMLElement constructor.
63
     *
64
     * @param string|SimpleXMLElement|QuiteSimpleXMLElement    $elem
65
     * @param QuiteSimpleXMLElement                            $inherit_from
66
     * @throws InvalidXMLException
67
     * @throws InvalidArgumentException
68
     */
69
    public function __construct($elem, QuiteSimpleXMLElement $inherit_from = null)
70
    {
71
        $this->namespaces = [];
72
        $this->el = $this->getSimpleXMLElement($elem);
73
74
        if (is_null($this->el)) {
75
            throw new InvalidArgumentException('QuiteSimpleXMLElement expects a string or a QuiteSimpleXMLElement/SimpleXMLElement object.');
76
        }
77
78
        if (is_null($inherit_from)) {
79
            $this->namespaces = $this->el->getNamespaces(true);
80
        } else {
81
            foreach ($inherit_from->namespaces as $prefix => $uri) {
82
                $this->registerXPathNamespace($prefix, $uri);
83
            }
84
        }
85
    }
86
87
    /**
88
     * Internal helper method to get a SimpleXMLElement from either a string
89
     * or a SimpleXMLElement/QuiteSimpleXMLElement object.
90
     *
91
     * @param string|SimpleXMLElement|QuiteSimpleXMLElement $elem
92
     * @return SimpleXMLElement
93
     * @throws InvalidXMLException
94
     * @throws InvalidArgumentException
95
     */
96
    private function getSimpleXMLElement($elem)
97
    {
98
        if (gettype($elem) == 'string') {
99
            return $this->initFromString($elem);
100
        }
101
102
        if (gettype($elem) == 'object') {
103
            return $this->initFromObject($elem);
104
        }
105
    }
106
107
    /**
108
     * Internal helper method to parse content from string.
109
     *
110
     * @param string $content
111
     * @return SimpleXMLElement
112
     */
113
    private function initFromString($content)
114
    {
115
        try {
116
            return new SimpleXMLElement($content);
117
        } catch (Exception $e) {
118
            throw new InvalidXMLException('Invalid XML encountered: ' . $content);
119
        }
120
    }
121
122
    /**
123
     * Internal helper method to parse content from string.
124
     *
125
     * @param object $content
126
     * @return SimpleXMLElement
127
     */
128
    private function initFromObject($content)
0 ignored issues
show
Unused Code introduced by
The parameter $content 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...
129
    {
130
        switch (get_class($elem)) {
0 ignored issues
show
Bug introduced by
The variable $elem does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
131
            case 'Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement':
132
                return $elem->el();
133
            case 'SimpleXMLElement':
134
                return $elem;
135
        }
136
    }
137
138
    /**
139
     * Register a new xpath namespace.
140
     *
141
     * @param string $prefix
142
     * @param string $uri
143
     */
144
    public function registerXPathNamespace($prefix, $uri)
145
    {
146
        $this->el->registerXPathNamespace($prefix, $uri);
147
        $this->namespaces[$prefix] = $uri;
148
    }
149
150
    /**
151
     * Register an array of new xpath namespaces.
152
     *
153
     * @param array $namespaces
154
     */
155
    public function registerXPathNamespaces($namespaces)
156
    {
157
        // Convenience method to add multiple namespaces at once
158
        foreach ($namespaces as $prefix => $uri) {
159
            $this->registerXPathNamespace($prefix, $uri);
160
        }
161
    }
162
163
    /**
164
     * Get the text of the first node matching an XPath query. By default,
165
     * the text will be trimmed, but if you want the untrimmed text, set
166
     * the second paramter to False.
167
     *
168
     * @param string $path
169
     * @param bool   $trim
170
     * @return string
171
     */
172
    public function text($path='.', $trim=true)
173
    {
174
        $text = strval($this->first($path));
175
176
        return $trim ? trim($text) : $text;
177
    }
178
179
    /**
180
     * Get a node attribute value.
181
     *
182
     * @param string $attribute
183
     * @return string
184
     */
185
    public function attr($attribute)
186
    {
187
        return trim((string) $this->el->attributes()->{$attribute});
188
    }
189
190
    /**
191
     * Get the first node matching an XPath query, or null if no match.
192
     *
193
     * @param string $path
194
     * @return QuiteSimpleXMLElement
195
     */
196
    public function first($path)
197
    {
198
        // Convenience method
199
        $x = $this->xpath($path);
200
201
        return count($x) ? $x[0] : null;
202
    }
203
204
    /**
205
     * Check if the document has at least one node matching an XPath query.
206
     *
207
     * @param string $path
208
     * @return bool
209
     */
210
    public function has($path)
211
    {
212
        $x = $this->xpath($path);
213
214
        return count($x) ? true : false;
215
    }
216
217
    /**
218
     * Get the wrapped SimpleXMLElement object.
219
     *
220
     * @return SimpleXMLElement
221
     */
222
    public function el()
223
    {
224
        return $this->el;
225
    }
226
227
    /**
228
     * Get all nodes matching an XPath query.
229
     *
230
     * @param string $path
231
     * @return QuiteSimpleXMLElement[]
232
     */
233
    public function xpath($path)
234
    {
235
        return array_map(function ($el) {
236
            return new QuiteSimpleXMLElement($el, $this);
237
        }, $this->el->xpath($path));
238
    }
239
240
    /**
241
     * Alias for `xpath()`.
242
     *
243
     * @param $path
244
     * @return QuiteSimpleXMLElement[]
245
     */
246
    public function all($path)
247
    {
248
        return $this->xpath($path);
249
    }
250
251
    /**
252
     * Returns the *untrimmed* text content of the node.
253
     * @return string
254
     */
255
    public function __toString()
256
    {
257
        return (string) $this->el;
258
    }
259
260
    /**
261
     * Returns the child elements.
262
     *
263
     * Note: By default, only children without namespace will be returned. You can
264
     * specify a namespace prefix to get children with that namespace prefix.
265
     *
266
     * Tip: You could also use `xpath('child::node()')`.
267
     *
268
     * @param null $ns
269
     * @return QuiteSimpleXMLElement[]
270
     */
271
    public function children($ns=null)
272
    {
273
        $ch = is_null($ns)
274
            ? $this->el->children()
275
            : $this->el->children($this->namespaces[$ns]);
276
277
        $o = [];
278
        foreach ($ch as $c) {
279
            $o[] = new self($c, $this);
280
        }
281
282
        return $o;
283
    }
284
285
    /**
286
     * Returns the number of child elements.
287
     *
288
     * Note: By default, only children without namespace will be counted. You can
289
     * specify a namespace prefix to count children with that namespace prefix.
290
     *
291
     * @param null $ns
292
     * @return int
293
     */
294
    public function count($ns = null)
295
    {
296
        return count($this->children($ns));
297
    }
298
299
    /**
300
     * Returns and element's attributes.
301
     *
302
     * @return SimpleXMLElement a `SimpleXMLElement` object that can be
303
     * iterated over to loop through the attributes on the tag.
304
     */
305
    public function attributes()
306
    {
307
        return $this->el->attributes();
308
    }
309
310
    /**
311
     * Returns the XML as text.
312
     *
313
     * @return string
314
     */
315
    public function asXML()
316
    {
317
        return $this->el->asXML();
318
    }
319
320
    /**
321
     * Get the element name.
322
     *
323
     * @return string
324
     */
325
    public function getName()
326
    {
327
        return $this->el->getName();
328
    }
329
330
    /**
331
     * Return a namespace array.
332
     *
333
     * @return array
334
     */
335
    public function getNamespaces()
336
    {
337
        return $this->namespaces;
338
    }
339
340
    /**
341
     * Set the node value.
342
     *
343
     * @param string $value
344
     */
345
    public function setValue($value)
346
    {
347
        $this->el[0] = $value;
348
    }
349
350
    /**
351
     * Return the current object as DOMElement.
352
     *
353
     * @return \DOMElement
354
     */
355
    public function asDOMElement()
356
    {
357
        return dom_import_simplexml($this->el);
358
    }
359
360
    /**
361
     * Replaces the current node. Thanks to @hakre
362
     * <http://stackoverflow.com/questions/17661167/how-to-replace-xml-node-with-simplexmlelement-php>.
363
     *
364
     * @param QuiteSimpleXMLElement $element
365
     */
366
    public function replace(QuiteSimpleXMLElement $element)
367
    {
368
        $oldNode = $this->asDOMElement();
369
        $newNode = $oldNode->ownerDocument->importNode(
370
            $element->asDOMElement(),
371
            true
372
        );
373
        $oldNode->parentNode->replaceChild($newNode, $oldNode);
374
    }
375
376
    /**
377
     * Static helper method to make initialization easier.
378
     *
379
     * @param       $input
380
     * @param array $ns
381
     * @return QuiteSimpleXMLElement
382
     */
383
    public static function make($input, $ns = [])
384
    {
385
        $elem = new self($input);
386
        $elem->registerXPathNamespaces($ns);
387
388
        return $elem;
389
    }
390
}
391