/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 }