Completed
Push — master ( a4b138...de35d6 )
by Dan Michael O.
02:54
created

QuiteSimpleXMLElement::make()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 6
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
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
52
class QuiteSimpleXMLElement
53
{
54
    public $namespaces;
55
    public $el;
56
57
    #
58
    # See https://github.com/draffter/FollowFunctionPHP/blob/master/_php/SimpleXML.php
59
    # for list of functions with arguments
60
    #
61
    public function __construct($elem, $inherit_from=null)
62
    {
63
        $this->namespaces = array();
64
65
        if (gettype($elem) == 'string') {
66
            try {
67
                $this->el = new \SimpleXMLElement($elem);
68
            } catch (\Exception $e) {
69
                throw new InvalidXMLException("Invalid XML encountered: " . $elem);
70
            }
71
        } elseif (gettype($elem) == 'object') {
72
            if (in_array(get_class($elem), array('Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement', 'SimpleXMLElement'))) {
73
                $this->el = $elem; // assume it's a SimpleXMLElement
74
            } else {
75
                throw new \InvalidArgumentException('Unknown object given to QuiteSimpleXMLElement. Expected SimpleXMLElement or QuiteSimpleXMLElement.');
76
            }
77
        } else {
78
            throw new \InvalidArgumentException('QuiteSimpleXMLElement expects a string or a QuiteSimpleXMLElement/SimpleXMLElement object.');
79
        }
80
81
        if ($inherit_from != null) {
82
            foreach ($inherit_from->namespaces as $prefix => $uri) {
83
                $this->registerXPathNamespace($prefix, $uri);
84
            }
85
        } else {
86
            $this->namespaces = $this->el->getNamespaces(true);
87
        }
88
89
    }
90
91
    public function registerXPathNamespace($prefix, $uri)
92
    {
93
        $this->el->registerXPathNamespace($prefix, $uri);
94
        $this->namespaces[$prefix] = $uri;
95
    }
96
97
    public function registerXPathNamespaces($namespaces)
98
    {
99
        # Convenience method to add multiple namespaces at once
100
        foreach ($namespaces as $prefix => $uri) {
101
            $this->registerXPathNamespace($prefix, $uri);
102
        }
103
    }
104
105
    public function text($path = '.')
106
    {
107
        # Convenience method
108
        $r = $this->el->xpath($path);
109
110
        // in case of an error
111
        if ($r === false) {
112
            return '';
113
        }
114
115
        // no results
116
        if (count($r) === 0) {
117
            return '';
118
        }
119
120
        return trim((string) $r[0]);
121
    }
122
123
    /*
124
     * Convenience method for getting an attribute of a node
125
     */
126
    public function attr($attribute)
127
    {
128
        return trim((string) $this->el->attributes()->{$attribute});
129
    }
130
131
    public function first($path)
132
    {
133
        # Convenience method
134
        $x = $this->xpath($path);
135
136
        return (count($x) === 0) ? false : $x[0];
137
    }
138
139
    /*
140
     * Convenience method for checking if a node exists
141
     */
142
    public function has($path)
143
    {
144
        $x = $this->xpath($path);
145
146
        return (count($x) === 0) ? false : true;
147
    }
148
149
    public function el()
150
    {
151
        return $this->el;
152
    }
153
154
    /**
155
     * @param $path
156
     * @return bool|QuiteSimpleXMLElement[]
157
     */
158
    public function xpath($path)
159
    {
160
        $r = $this->el->xpath($path);
161
        if ($r === false) return false;
162
        $r2 = array();
163
        foreach ($r as $i) {
164
            $r2[] = new QuiteSimpleXMLElement($i, $this);
165
        }
166
167
        return $r2;
168
    }
169
170
    /**
171
     * Wrapper method for xpath() that *always* returns an array.
172
     *
173
     * @param $path
174
     * @return QuiteSimpleXMLElement[]
175
     */
176
    public function all($path)
177
    {
178
        $r = $this->xpath($path);
179
        return (!is_array($r)) ? array() : $r;
180
    }
181
182
    public function __toString()
183
    {
184
        return (string) $this->el;
185
    }
186
187
    /* The original children and count methods are quite flawed. The count() method
188
       only return the count of children _with no namespace_. The children() method
189
       can take namespace prefix as argument, but doesn't use the document's prefixes,
190
       not the registered ones.
191
192
       And it returns a "pseudo array" instead of a real iterator... making it quite hard
193
       to work with, there's no next() method for instance...
194
       We're returning a real array instead, even though that might not be what you want
195
       in _all_ situations.
196
197
       An alternative could be to use xpath('child::node()')
198
    */
199
    public function children($ns = null)
200
    {
201
        $ch = $ns
202
            ? $this->el->children($this->namespaces[$ns])
203
            : $this->el->children();
204
        $o = array();
205
        foreach ($ch as $c) {
206
            $o[] = new QuiteSimpleXMLElement($c, $this);
207
        }
208
209
        return $o;
210
    }
211
    public function count($ns = null)
212
    {
213
        return $ns
214
            ? count($this->el->children($this->namespaces[$ns]))
215
            : count($this->el->children());
216
    }
217
218
    public function attributes() { return $this->el->attributes(); }
219
    public function asXML() { return $this->el->asXML(); }
220
    public function getName() { return $this->el->getName(); }
221
    public function getNamespaces($recursive = false) { return $this->el->getNamespaces($recursive); }
222
223
    /**
224
     * Set the node value
225
     */
226
    public function setValue($value)
227
    {
228
        $r = $this->el[0] = $value;
0 ignored issues
show
Unused Code introduced by
$r is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
229
    }
230
231
    public function asDOMElement()
232
    {
233
        return dom_import_simplexml($this->el);
234
    }
235
236
    /**
237
     * Replaces the current node. Thanks to @hakre
238
     * <http://stackoverflow.com/questions/17661167/how-to-replace-xml-node-with-simplexmlelement-php>
239
     */
240
    public function replace(QuiteSimpleXMLElement $element) {
241
        $oldNode = $this->asDOMElement();
242
        $newNode = $oldNode->ownerDocument->importNode(
243
            $element->asDOMElement(),
244
            true
245
        );
246
        $oldNode->parentNode->replaceChild($newNode, $oldNode);
247
    }
248
249
    public static function make($input, $ns = [])
250
    {
251
        $elem = new QuiteSimpleXMLElement($input);
252
        $elem->registerXPathNamespaces($ns);
253
        return $elem;
254
    }
255
}
256