Open-Source PHP Framework - Designed for rapid development of performance-oriented scalable applications

/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($inputLIBXML_NOCDATA);
89             }
90             
$dom = new DOMDocument();
91             
$dom->loadXML($input);
92             return 
$dom;
93         } elseif (
file_exists($input) || strpos($input'http://') === 
94         
|| strpos($input'https://') === 0) {
95             if (
$options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
96                 return new 
SimpleXMLElement($inputLIBXML_NOCDATAtrue);
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($key1);
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(0count($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($namespacetrue) as $key => $value) {
302                 if (!empty(
$namespace)) {
303                     
$key $namespace ':' $key;
304                 }
305                 
$data['@' $key] = (string)$value;
306             }
307
308             foreach (
$xml->children($namespacetrue) 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 }