Completed
Push — master ( a509ec...f043ba )
by Dan Michael O.
01:03
created

QuiteSimpleXMLElement::first()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 3
nc 2
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
    use HelperMethods;
52
53
    /**
54
     * @var array
55
     */
56
    public $namespaces;
57
58
    /**
59
     * @var SimpleXMLElement
60
     */
61
    public $el;
62
63
    /**
64
     * QuiteSimpleXMLElement constructor.
65
     *
66
     * @param string|SimpleXMLElement|QuiteSimpleXMLElement    $elem
67
     * @param QuiteSimpleXMLElement                            $inherit_from
68
     * @throws InvalidXMLException
69
     * @throws InvalidArgumentException
70
     */
71
    public function __construct($elem, QuiteSimpleXMLElement $inherit_from = null)
72
    {
73
        $this->namespaces = [];
74
        $this->el = $this->getSimpleXMLElement($elem);
75
76
        if (is_null($this->el)) {
77
            throw new InvalidArgumentException('QuiteSimpleXMLElement expects a string or a QuiteSimpleXMLElement/SimpleXMLElement object.');
78
        }
79
80
        if (is_null($inherit_from)) {
81
            $this->namespaces = $this->el->getNamespaces(true);
82
        } else {
83
            foreach ($inherit_from->namespaces as $prefix => $uri) {
84
                $this->registerXPathNamespace($prefix, $uri);
85
            }
86
        }
87
    }
88
89
    /**
90
     * Internal helper method to get a SimpleXMLElement from either a string
91
     * or a SimpleXMLElement/QuiteSimpleXMLElement object.
92
     *
93
     * @param string|SimpleXMLElement|QuiteSimpleXMLElement $elem
94
     * @return SimpleXMLElement
95
     * @throws InvalidXMLException
96
     * @throws InvalidArgumentException
97
     */
98
    private function getSimpleXMLElement($elem)
99
    {
100
        if (gettype($elem) == 'string') {
101
            return $this->initFromString($elem);
102
        }
103
104
        if (gettype($elem) == 'object') {
105
            return $this->initFromObject($elem);
106
        }
107
    }
108
109
    /**
110
     * Internal helper method to parse content from string.
111
     *
112
     * @param string $content
113
     * @return SimpleXMLElement
114
     */
115
    private function initFromString($content)
116
    {
117
        try {
118
            return new SimpleXMLElement($content);
119
        } catch (Exception $e) {
120
            throw new InvalidXMLException('Invalid XML encountered: ' . $content);
121
        }
122
    }
123
124
    /**
125
     * Internal helper method to parse content from string.
126
     *
127
     * @param $elem
128
     * @return SimpleXMLElement
129
     */
130
    private function initFromObject($elem)
131
    {
132
        switch (get_class($elem)) {
133
            case 'Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement':
134
                return $elem->el();
135
            case 'SimpleXMLElement':
136
                return $elem;
137
        }
138
    }
139
140
    /**
141
     * Register a new xpath namespace.
142
     *
143
     * @param string $prefix
144
     * @param string $uri
145
     */
146
    public function registerXPathNamespace($prefix, $uri)
147
    {
148
        $this->el->registerXPathNamespace($prefix, $uri);
149
        $this->namespaces[$prefix] = $uri;
150
    }
151
152
    /**
153
     * Register an array of new xpath namespaces.
154
     *
155
     * @param array $namespaces
156
     */
157
    public function registerXPathNamespaces($namespaces)
158
    {
159
        // Convenience method to add multiple namespaces at once
160
        foreach ($namespaces as $prefix => $uri) {
161
            $this->registerXPathNamespace($prefix, $uri);
162
        }
163
    }
164
165
    /**
166
     * Get the text of the first node matching an XPath query. By default,
167
     * the text will be trimmed, but if you want the untrimmed text, set
168
     * the second paramter to False.
169
     *
170
     * @param string $path
171
     * @param bool   $trim
172
     * @return string
173
     */
174
    public function text($path='.', $trim=true)
175
    {
176
        $text = strval($this->first($path));
177
178
        return $trim ? trim($text) : $text;
179
    }
180
181
    /**
182
     * Get the wrapped SimpleXMLElement object.
183
     *
184
     * @return SimpleXMLElement
185
     */
186
    public function el()
187
    {
188
        return $this->el;
189
    }
190
191
    /**
192
     * Returns the *untrimmed* text content of the node.
193
     * @return string
194
     */
195
    public function __toString()
196
    {
197
        return (string) $this->el;
198
    }
199
200
    /**
201
     * Returns the child elements.
202
     *
203
     * Note: By default, only children without namespace will be returned. You can
204
     * specify a namespace prefix to get children with that namespace prefix.
205
     *
206
     * Tip: You could also use `xpath('child::node()')`.
207
     *
208
     * @param null $ns
209
     * @return QuiteSimpleXMLElement[]
210
     */
211
    public function children($ns=null)
212
    {
213
        $ch = is_null($ns)
214
            ? $this->el->children()
215
            : $this->el->children($this->namespaces[$ns]);
216
217
        $o = [];
218
        foreach ($ch as $c) {
219
            $o[] = new self($c, $this);
220
        }
221
222
        return $o;
223
    }
224
225
    /**
226
     * Returns the number of child elements.
227
     *
228
     * Note: By default, only children without namespace will be counted. You can
229
     * specify a namespace prefix to count children with that namespace prefix.
230
     *
231
     * @param null $ns
232
     * @return int
233
     */
234
    public function count($ns = null)
235
    {
236
        return count($this->children($ns));
237
    }
238
239
    /**
240
     * Returns and element's attributes.
241
     *
242
     * @return SimpleXMLElement a `SimpleXMLElement` object that can be
243
     * iterated over to loop through the attributes on the tag.
244
     */
245
    public function attributes()
246
    {
247
        return $this->el->attributes();
248
    }
249
250
    /**
251
     * Returns the XML as text.
252
     *
253
     * @return string
254
     */
255
    public function asXML()
256
    {
257
        return $this->el->asXML();
258
    }
259
260
    /**
261
     * Get the element name.
262
     *
263
     * @return string
264
     */
265
    public function getName()
266
    {
267
        return $this->el->getName();
268
    }
269
270
    /**
271
     * Return a namespace array.
272
     *
273
     * @return array
274
     */
275
    public function getNamespaces()
276
    {
277
        return $this->namespaces;
278
    }
279
280
    /**
281
     * Set the node value.
282
     *
283
     * @param string $value
284
     */
285
    public function setValue($value)
286
    {
287
        $this->el[0] = $value;
288
    }
289
290
    /**
291
     * Return the current object as DOMElement.
292
     *
293
     * @return \DOMElement
294
     */
295
    public function asDOMElement()
296
    {
297
        return dom_import_simplexml($this->el);
298
    }
299
300
    /**
301
     * Replaces the current node. Thanks to @hakre
302
     * <http://stackoverflow.com/questions/17661167/how-to-replace-xml-node-with-simplexmlelement-php>.
303
     *
304
     * @param QuiteSimpleXMLElement $element
305
     */
306
    public function replace(QuiteSimpleXMLElement $element)
307
    {
308
        $oldNode = $this->asDOMElement();
309
        $newNode = $oldNode->ownerDocument->importNode(
310
            $element->asDOMElement(),
311
            true
312
        );
313
        $oldNode->parentNode->replaceChild($newNode, $oldNode);
314
    }
315
316
    /**
317
     * Static helper method to make initialization easier.
318
     *
319
     * @param       $input
320
     * @param array $ns
321
     * @return QuiteSimpleXMLElement
322
     */
323
    public static function make($input, $ns = [])
324
    {
325
        $elem = new self($input);
326
        $elem->registerXPathNamespaces($ns);
327
328
        return $elem;
329
    }
330
}
331