Completed
Push — master ( 5b0b87...b75b1a )
by Dan Michael O.
01:12
created

QuiteSimpleXMLElement::initFromObject()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
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
73
        $this->el = $this->getSimpleXMLElement($elem);
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 get SimpleXMLElement object.
124
     *
125
     * @param object $elem
126
     * @return mixed
127
     */
128
    private function initFromObject($elem)
129
    {
130
        switch (get_class($elem)) {
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