Issues (2)

QuiteSimpleXMLElement/SimpleXMLElementWrapper.php (2 issues)

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
abstract class SimpleXMLElementWrapper
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(
76
                'QuiteSimpleXMLElement expects a string or a QuiteSimpleXMLElement/SimpleXMLElement object.'
77
            );
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 QuiteSimpleXMLElement|SimpleXMLElement $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();
0 ignored issues
show
The method el() does not exist on SimpleXMLElement. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

134
                return $elem->/** @scrutinizer ignore-call */ el();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
135
            case 'SimpleXMLElement':
136
                return $elem;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $elem also could return the type Danmichaelo\QuiteSimpleX...t\QuiteSimpleXMLElement which is incompatible with the documented return type SimpleXMLElement.
Loading history...
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 wrapped SimpleXMLElement object.
167
     *
168
     * @return SimpleXMLElement
169
     */
170
    public function el()
171
    {
172
        return $this->el;
173
    }
174
175
    /**
176
     * Returns the *untrimmed* text content of the node.
177
     * @return string
178
     */
179
    public function __toString()
180
    {
181
        return (string) $this->el;
182
    }
183
184
    /**
185
     * Returns child elements.
186
     *
187
     * Note: By default, only children without namespace will be returned! You can
188
     * specify a namespace prefix to get children with that namespace prefix.
189
     *
190
     * If you want all child elements, namespaced or not, use
191
     * `$record->all('child::*')` instead.
192
     *
193
     * @param string $ns
194
     * @return QuiteSimpleXMLElement[]
195
     */
196
    public function children($ns = null)
197
    {
198
        $ch = is_null($ns)
199
            ? $this->el->children()
200
            : $this->el->children($this->namespaces[$ns]);
201
202
        $o = [];
203
        foreach ($ch as $c) {
204
            $o[] = new static($c, $this);
205
        }
206
207
        return $o;
208
    }
209
210
    /**
211
     * Returns the number of child elements.
212
     *
213
     * Note: By default, only children without namespace will be counted! You can
214
     * specify a namespace prefix to count children with that namespace prefix.
215
     *
216
     * If you want all child elements, namespaced or not, use
217
     * `count($record->all('child::*'))` instead.
218
     *
219
     * @param string $ns
220
     * @return int
221
     */
222
    public function count($ns = null)
223
    {
224
        return count($this->children($ns));
225
    }
226
227
    /**
228
     * Returns and element's attributes.
229
     *
230
     * @param string $ns
231
     * @param bool $is_prefix
232
     * @return SimpleXMLElement a `SimpleXMLElement` object that can be
233
     * iterated over to loop through the attributes on the tag.
234
     */
235
    public function attributes($ns = null, $is_prefix = false)
236
    {
237
        return $this->el->attributes($ns, $is_prefix);
238
    }
239
240
    /**
241
     * Returns the XML as text.
242
     *
243
     * @return string
244
     */
245
    public function asXML()
246
    {
247
        return $this->el->asXML();
248
    }
249
250
    /**
251
     * Get the element name.
252
     *
253
     * @return string
254
     */
255
    public function getName()
256
    {
257
        return $this->el->getName();
258
    }
259
260
    /**
261
     * Return a namespace array.
262
     *
263
     * @return array
264
     */
265
    public function getNamespaces()
266
    {
267
        return $this->namespaces;
268
    }
269
270
    /**
271
     * Set the node value.
272
     *
273
     * @param string $value
274
     */
275
    public function setValue($value)
276
    {
277
        $this->el[0] = $value;
278
    }
279
280
    /**
281
     * Return the current object as DOMElement.
282
     *
283
     * @return \DOMElement
284
     */
285
    public function asDOMElement()
286
    {
287
        return dom_import_simplexml($this->el);
288
    }
289
290
    /**
291
     * Replaces the current node. Thanks to @hakre
292
     * <http://stackoverflow.com/questions/17661167/how-to-replace-xml-node-with-simplexmlelement-php>.
293
     *
294
     * @param QuiteSimpleXMLElement $element
295
     */
296
    public function replace(QuiteSimpleXMLElement $element)
297
    {
298
        $oldNode = $this->asDOMElement();
299
        $newNode = $oldNode->ownerDocument->importNode(
300
            $element->asDOMElement(),
301
            true
302
        );
303
        $oldNode->parentNode->replaceChild($newNode, $oldNode);
304
    }
305
306
    /**
307
     * Static helper method to make initialization easier.
308
     *
309
     * @param       $input
310
     * @param array $ns
311
     * @return QuiteSimpleXMLElement
312
     */
313
    public static function make($input, $ns = [])
314
    {
315
        $elem = new static($input);
316
        $elem->registerXPathNamespaces($ns);
317
318
        return $elem;
319
    }
320
}
321