Completed
Push — master ( 5bfc48...6d45fd )
by Dan Michael O.
02:36
created

QuiteSimpleXMLElement   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 19
Bugs 4 Features 9
Metric Value
wmc 34
c 19
b 4
f 9
lcom 1
cbo 1
dl 0
loc 210
rs 9.2

21 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 28 7
A registerXPathNamespace() 0 5 1
A registerXPathNamespaces() 0 7 2
A attr() 0 4 1
A first() 0 7 2
A has() 0 6 2
A el() 0 4 1
A text() 0 6 2
A xpath() 0 6 1
A all() 0 4 1
A __toString() 0 4 1
A children() 0 12 3
A count() 0 6 2
A attributes() 0 4 1
A asXML() 0 4 1
A getName() 0 4 1
A getNamespaces() 0 4 1
A setValue() 0 4 1
A asDOMElement() 0 4 1
A replace() 0 9 1
A make() 0 6 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
40
namespace Danmichaelo\QuiteSimpleXMLElement;
41
42
class InvalidXMLException extends \Exception
43
{
44
45
    public function __construct($message = null, $code = 0, Exception $previous = null)
46
    {
47
        parent::__construct($message, $code, $previous);
48
    }
49
}
50
51
class QuiteSimpleXMLElement
52
{
53
    public $namespaces;
54
    public $el;
55
56
    #
57
    # See https://github.com/draffter/FollowFunctionPHP/blob/master/_php/SimpleXML.php
58
    # for list of functions with arguments
59
    #
60
    public function __construct($elem, $inherit_from=null)
61
    {
62
        $this->namespaces = array();
63
64
        if (gettype($elem) == 'string') {
65
            try {
66
                $this->el = new \SimpleXMLElement($elem);
67
            } catch (\Exception $e) {
68
                throw new InvalidXMLException("Invalid XML encountered: " . $elem);
69
            }
70
        } elseif (gettype($elem) == 'object') {
71
            if (in_array(get_class($elem), array('Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement', 'SimpleXMLElement'))) {
72
                $this->el = $elem; // assume it's a SimpleXMLElement
73
            } else {
74
                throw new \InvalidArgumentException('Unknown object given to QuiteSimpleXMLElement. Expected SimpleXMLElement or QuiteSimpleXMLElement.');
75
            }
76
        } else {
77
            throw new \InvalidArgumentException('QuiteSimpleXMLElement expects a string or a QuiteSimpleXMLElement/SimpleXMLElement object.');
78
        }
79
80
        if ($inherit_from != null) {
81
            foreach ($inherit_from->namespaces as $prefix => $uri) {
82
                $this->registerXPathNamespace($prefix, $uri);
83
            }
84
        } else {
85
            $this->namespaces = $this->el->getNamespaces(true);
86
        }
87
    }
88
89
    public function registerXPathNamespace($prefix, $uri)
90
    {
91
        $this->el->registerXPathNamespace($prefix, $uri);
92
        $this->namespaces[$prefix] = $uri;
93
    }
94
95
    public function registerXPathNamespaces($namespaces)
96
    {
97
        # Convenience method to add multiple namespaces at once
98
        foreach ($namespaces as $prefix => $uri) {
99
            $this->registerXPathNamespace($prefix, $uri);
100
        }
101
    }
102
103
    /*
104
     * Convenience method for getting the text of the first
105
     * node matching an xpath. The text is trimmed by default,
106
     * but setting the second argument to false will return
107
     * the untrimmed text.
108
     */
109
    public function text($path = '.', $trim=true)
110
    {
111
        $text = strval($this->first($path));
112
113
        return $trim ? trim($text) : $text;
114
    }
115
116
    /*
117
     * Convenience method for getting an attribute of a node
118
     */
119
    public function attr($attribute)
120
    {
121
        return trim((string) $this->el->attributes()->{$attribute});
122
    }
123
124
    public function first($path)
125
    {
126
        # Convenience method
127
        $x = $this->xpath($path);
128
129
        return count($x) ? $x[0] : null;
130
    }
131
132
    /*
133
     * Convenience method for checking if a node exists
134
     */
135
    public function has($path)
136
    {
137
        $x = $this->xpath($path);
138
139
        return count($x) ? true : false;
140
    }
141
142
    public function el()
143
    {
144
        return $this->el;
145
    }
146
147
    /**
148
     * Returns an array of QuiteSimpleXMLElement instances.
149
     *
150
     * @param $path
151
     * @return QuiteSimpleXMLElement[]
152
     */
153
    public function xpath($path)
154
    {
155
        return array_map(function($el)  {
156
            return new QuiteSimpleXMLElement($el, $this);
157
        }, $this->el->xpath($path));
158
    }
159
160
    /**
161
     * Alias for xpath().
162
     *
163
     * @param $path
164
     * @return QuiteSimpleXMLElement[]
165
     */
166
    public function all($path)
167
    {
168
        return $this->xpath($path);
169
    }
170
171
    /**
172
     * Returns the *untrimmed* text content of the node
173
     */
174
    public function __toString()
175
    {
176
        return (string) $this->el;
177
    }
178
179
    /* The original children and count methods are quite flawed. The count() method
180
       only return the count of children _with no namespace_. The children() method
181
       can take namespace prefix as argument, but doesn't use the document's prefixes,
182
       not the registered ones.
183
184
       And it returns a "pseudo array" instead of a real iterator... making it quite hard
185
       to work with, there's no next() method for instance...
186
       We're returning a real array instead, even though that might not be what you want
187
       in _all_ situations.
188
189
       An alternative could be to use xpath('child::node()')
190
    */
191
    public function children($ns = null)
192
    {
193
        $ch = $ns
194
            ? $this->el->children($this->namespaces[$ns])
195
            : $this->el->children();
196
        $o = array();
197
        foreach ($ch as $c) {
198
            $o[] = new QuiteSimpleXMLElement($c, $this);
199
        }
200
201
        return $o;
202
    }
203
    public function count($ns = null)
204
    {
205
        return $ns
206
            ? count($this->el->children($this->namespaces[$ns]))
207
            : count($this->el->children());
208
    }
209
210
    public function attributes()
211
    {
212
        return $this->el->attributes();
213
    }
214
    public function asXML()
215
    {
216
        return $this->el->asXML();
217
    }
218
    public function getName()
219
    {
220
        return $this->el->getName();
221
    }
222
    public function getNamespaces($recursive = false)
223
    {
224
        return $this->el->getNamespaces($recursive);
225
    }
226
227
    /**
228
     * Set the node value
229
     */
230
    public function setValue($value)
231
    {
232
        $this->el[0] = $value;
233
    }
234
235
    public function asDOMElement()
236
    {
237
        return dom_import_simplexml($this->el);
238
    }
239
240
    /**
241
     * Replaces the current node. Thanks to @hakre
242
     * <http://stackoverflow.com/questions/17661167/how-to-replace-xml-node-with-simplexmlelement-php>
243
     */
244
    public function replace(QuiteSimpleXMLElement $element)
245
    {
246
        $oldNode = $this->asDOMElement();
247
        $newNode = $oldNode->ownerDocument->importNode(
248
            $element->asDOMElement(),
249
            true
250
        );
251
        $oldNode->parentNode->replaceChild($newNode, $oldNode);
252
    }
253
254
    public static function make($input, $ns = array())
255
    {
256
        $elem = new QuiteSimpleXMLElement($input);
257
        $elem->registerXPathNamespaces($ns);
258
        return $elem;
259
    }
260
}
261