/mvc/helpers/xml
[return to app]1 
<?php
2 /**
3  * XML handling for Cake.
4  *
5  * This is CakePHP 2.0 XmlHelper class updated to PHP5 syntax for Vork
6  *
7  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8  * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
9  *
10  * Licensed under The MIT License
11  * Redistributions of files must retain the above copyright notice.
12  *
13  * @copyright     Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
14  * @link          http://cakephp.org CakePHP(tm) Project
15  * @package       cake.libs
16  * @since         CakePHP v .0.10.3.1400
17  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
18  */
19 
20 class xmlHelper {
21 
22 /**
23  * Initialize SimpleXMLElement or DOMDocument from a given XML string, file path, URL or array.
24  *
25  * ### Usage:
26  *
27  * Building XML from a string:
28  *
29  * `$xml = Xml::build('<example>text</example>');`
30  *
31  * Building XML from string (output DOMDocument):
32  *
33  * `$xml = Xml::build('<example>text</example>', array('return' => 'domdocument'));`
34  *
35  * Building XML from a file path:
36  *
37  * `$xml = Xml::build('/path/to/an/xml/file.xml');`
38  *
39  * Building from a remote URL:
40  *
41  * `$xml = Xml::build('http://example.com/example.xml');`
42  *
43  * Building from an array:
44  *
45  * {{{
46  *     $value = array(
47  *         'tags' => array(
48  *             'tag' => array(
49  *                 array(
50  *                     'id' => '1',
51  *                     'name' => 'defect'
52  *                 ),
53  *                 array(
54  *                     'id' => '2',
55  *                     'name' => 'enhancement'
56  *                )
57  *             )
58  *         )
59  *     );
60  * $xml = Xml::build($value);
61  * }}}
62  * 
63  * When building XML from an array ensure that there is only one top level element.
64  *
65  * ### Options
66  *
67  * - `return` Can be 'simplexml' to return object of SimpleXMLElement or 'domdocument' to return DOMDocument.
68  * - If using array as input, you can pass `options` from Xml::fromArray.
69  *
70  * @param mixed $input XML string, a path to a file, an URL or an array
71  * @param array $options The options to use
72  * @return object SimpleXMLElement or DOMDocument
73  * @throws XmlException
74  */
75     public static function build($input, $options = array()) {
76         if (!is_array($options)) {
77             $options = array('return' => (string)$options);
78         }
79         $defaults = array(
80             'return' => 'simplexml'
81         );
82         $options = array_merge($defaults, $options);
83 
84         if (is_array($input) || is_object($input)) {
85             return self::fromArray((array)$input, $options);
86         } elseif (strpos($input, '<') !== false) {
87             if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
88                 return new SimpleXMLElement($input, LIBXML_NOCDATA);
89             }
90             $dom = new DOMDocument();
91             $dom->loadXML($input);
92             return $dom;
93         } elseif (file_exists($input) || strpos($input, 'http://') === 0 
94         || strpos($input, 'https://') === 0) {
95             if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
96                 return new SimpleXMLElement($input, LIBXML_NOCDATA, true);
97             }
98             $dom = new DOMDocument();
99             $dom->load($input);
100             return $dom;
101         } elseif (!is_string($input)) {
102             throw new XmlException(_d('cake_dev', 'Invalid input.'));
103         }
104         throw new XmlException(_d('cake_dev', 'XML cannot be read.'));
105     }
106 
107 /**
108  * Transform an array into a SimpleXMLElement
109  *
110  * ### Options
111  *
112  * - `format` If create childs ('tags') or attributes ('attribute').
113  * - `version` Version of XML document. Default is 1.0.
114  * - `encoding` Encoding of XML document. If null remove from XML header. Default is the some of application.
115  * - `return` If return object of SimpleXMLElement ('simplexml') or DOMDocument ('domdocument'). 
116  * Default is SimpleXMLElement.
117  *
118  * Using the following data:
119  * 
120  * {{{
121  * $value = array(
122  *    'root' => array(
123  *        'tag' => array(
124  *            'id' => 1,
125  *            'value' => 'defect',
126  *            '@' => 'description'
127  *         )
128  *     )
129  * );
130  * }}}
131  *
132  * Calling `Xml::fromArray($value, 'tags');`  Will generate:
133  *
134  * `<root><tag><id>1</id><value>defect</value>description</tag></root>`
135  *
136  * And calling `Xml::fromArray($value, 'attribute');` Will generate:
137  *
138  * `<root><tag id="1" value="defect">description</tag></root>`
139  *
140  * @param array $input Array with data
141  * @param array $options The options to use
142  * @return object SimpleXMLElement or DOMDocument
143  * @throws XmlException
144  */
145     public static function fromArray($input, $options = array()) {
146         if (!is_array($input) || count($input) !== 1) {
147             echo 'Invalid input.';
148         }
149         $key = key($input);
150         if (is_integer($key)) {
151             echo 'The key of input must be alphanumeric';
152         }
153 
154         if (!is_array($options)) {
155             $options = array('format' => (string)$options);
156         }
157         $defaults = array(
158             'format' => 'tags',
159             'version' => '1.0',
160             'encoding' => 'UTF-8',
161             'return' => 'simplexml'
162         );
163         $options = array_merge($defaults, $options);
164 
165         $dom = new DOMDocument($options['version'], $options['encoding']);
166         self::_fromArray($dom, $dom, $input, $options['format']);
167 
168         $options['return'] = strtolower($options['return']);
169         if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
170             return new SimpleXMLElement($dom->saveXML());
171         }
172         return $dom;
173     }
174 
175 /**
176  * Recursive method to create childs from array
177  *
178  * @param object $dom Handler to DOMDocument
179  * @param object $node Handler to DOMElement (child)
180  * @param array $data Array of data to append to the $node.
181  * @param string $format Either 'attribute' or 'tags'.  This determines where nested keys go.
182  * @return void
183  */
184     protected static function _fromArray($dom, $node, &$data, $format) {
185         if (empty($data) || !is_array($data)) {
186             return;
187         }
188         foreach ($data as $key => $value) {
189             if (is_string($key)) {
190                 if (!is_array($value)) {
191                     if (is_bool($value)) {
192                         $value = (int)$value;
193                     } elseif ($value === null) {
194                         $value = '';
195                     }
196                     $isNamespace = strpos($key, 'xmlns:');
197                     if ($isNamespace !== false) {
198                         $node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, $value);
199                         continue;
200                     }
201                     if ($key[0] !== '@' && $format === 'tags') {
202                         $child = $dom->createElement($key, $value);
203                         $node->appendChild($child);
204                     } else {
205                         if ($key[0] === '@') {
206                             $key = substr($key, 1);
207                         }
208                         $attribute = $dom->createAttribute($key);
209                         $attribute->appendChild($dom->createTextNode($value));
210                         $node->appendChild($attribute);
211                     }
212                 } else {
213                     if ($key[0] === '@') {
214                         throw new XmlException(_d('cake_dev', 'Invalid array'));
215                     }
216                     if (array_keys($value) === range(0, count($value) - 1)) { // List
217                         foreach ($value as $item) {
218                             $data = compact('dom', 'node', 'key', 'format');
219                             $data['value'] = $item;
220                             self::_createChild($data);
221                         }
222                     } else { // Struct
223                         self::_createChild(compact('dom', 'node', 'key', 'value', 'format'));
224                     }
225                 }
226             } else {
227                 throw new XmlException(_d('cake_dev', 'Invalid array'));
228             }
229         }
230     }
231 
232 /**
233  * Helper to _fromArray(). It will create childs of arrays
234  *
235  * @param array $data Array with informations to create childs
236  * @return void
237  */
238     private static function _createChild($data) {
239         extract($data);
240         $childNS = $childValue = null;
241         if (is_array($value)) {
242             if (isset($value['@'])) {
243                 $childValue = (string)$value['@'];
244                 unset($value['@']);
245             }
246             if (isset($value['xmlns:'])) {
247                 $childNS = $value['xmlns:'];
248                 unset($value['xmlns:']);
249             }
250         } elseif (!empty($value) || $value === 0) {
251             $childValue = (string)$value;
252         }
253 
254         if ($childValue) {
255             $child = $dom->createElement($key, $childValue);
256         } else {
257             $child = $dom->createElement($key);
258         }
259         if ($childNS) {
260             $child->setAttribute('xmlns', $childNS);
261         }
262 
263         self::_fromArray($dom, $child, $value, $format);
264         $node->appendChild($child);
265     }
266 
267 /**
268  * Returns this XML structure as a array.
269  *
270  * @param object $obj SimpleXMLElement, DOMDocument or DOMNode instance
271  * @return array Array representation of the XML structure.
272  * @throws XmlException
273  */
274     public static function toArray($obj) {
275         if ($obj instanceof DOMNode) {
276             $obj = simplexml_import_dom($obj);
277         }
278         if (!($obj instanceof SimpleXMLElement)) {
279             throw new XmlException(_d('cake_dev', 'The input is not instance of SimpleXMLElement, 
280             DOMDocument or DOMNode.'));
281         }
282         $result = array();
283         $namespaces = array_merge(array('' => ''), $obj->getNamespaces(true));
284         self::_toArray($obj, $result, '', array_keys($namespaces));
285         return $result;
286     }
287 
288 /**
289  * Recursive method to toArray
290  *
291  * @param object $xml SimpleXMLElement object
292  * @param array $parentData Parent array with data
293  * @param string $ns Namespace of current child
294  * @param array $namespaces List of namespaces in XML
295  * @return void
296  */
297     protected static function _toArray($xml, &$parentData, $ns, $namespaces) {
298         $data = array();
299 
300         foreach ($namespaces as $namespace) {
301             foreach ($xml->attributes($namespace, true) as $key => $value) {
302                 if (!empty($namespace)) {
303                     $key = $namespace . ':' . $key;
304                 }
305                 $data['@' . $key] = (string)$value;
306             }
307 
308             foreach ($xml->children($namespace, true) as $child) {
309                 self::_toArray($child, $data, $namespace, $namespaces);
310             }
311         }
312 
313         $asString = trim((string)$xml);
314         if (empty($data)) {
315             $data = $asString;
316         } elseif (!empty($asString)) {
317             $data['@'] = $asString;
318         }
319 
320         if (!empty($ns)) {
321             $ns .= ':';
322         }
323         $name = $ns . $xml->getName();
324         if (isset($parentData[$name])) {
325             if (!is_array($parentData[$name]) || !isset($parentData[$name][0])) {
326                 $parentData[$name] = array($parentData[$name]);
327             }
328             $parentData[$name][] = $data;
329         } else {
330             $parentData[$name] = $data;
331         }
332     }
333 
334 }



