1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
22:
23: App::uses('HttpSocket', 'Network/Http');
24:
25: 26: 27: 28: 29: 30: 31:
32: class Xml {
33:
34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88:
89: public static function build($input, $options = array()) {
90: if (!is_array($options)) {
91: $options = array('return' => (string)$options);
92: }
93: $defaults = array(
94: 'return' => 'simplexml',
95: 'loadEntities' => false,
96: );
97: $options = array_merge($defaults, $options);
98:
99: if (is_array($input) || is_object($input)) {
100: return self::fromArray((array)$input, $options);
101: } elseif (strpos($input, '<') !== false) {
102: return self::_loadXml($input, $options);
103: } elseif (file_exists($input)) {
104: return self::_loadXml(file_get_contents($input), $options);
105: } elseif (strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) {
106: try {
107: $socket = new HttpSocket(array('request' => array('redirect' => 10)));
108: $response = $socket->get($input);
109: if (!$response->isOk()) {
110: throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
111: }
112: return self::_loadXml($response->body, $options);
113: } catch (SocketException $e) {
114: throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
115: }
116: } elseif (!is_string($input)) {
117: throw new XmlException(__d('cake_dev', 'Invalid input.'));
118: }
119: throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
120: }
121:
122: 123: 124: 125: 126: 127: 128: 129:
130: protected static function _loadXml($input, $options) {
131: $hasDisable = function_exists('libxml_disable_entity_loader');
132: $internalErrors = libxml_use_internal_errors(true);
133: if ($hasDisable && !$options['loadEntities']) {
134: libxml_disable_entity_loader(true);
135: }
136: try {
137: if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
138: $xml = new SimpleXMLElement($input, LIBXML_NOCDATA);
139: } else {
140: $xml = new DOMDocument();
141: $xml->loadXML($input);
142: }
143: } catch (Exception $e) {
144: $xml = null;
145: }
146: if ($hasDisable && !$options['loadEntities']) {
147: libxml_disable_entity_loader(false);
148: }
149: libxml_use_internal_errors($internalErrors);
150: if ($xml === null) {
151: throw new XmlException(__d('cake_dev', 'Xml cannot be read.'));
152: }
153: return $xml;
154: }
155:
156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192:
193: public static function fromArray($input, $options = array()) {
194: if (!is_array($input) || count($input) !== 1) {
195: throw new XmlException(__d('cake_dev', 'Invalid input.'));
196: }
197: $key = key($input);
198: if (is_int($key)) {
199: throw new XmlException(__d('cake_dev', 'The key of input must be alphanumeric'));
200: }
201:
202: if (!is_array($options)) {
203: $options = array('format' => (string)$options);
204: }
205: $defaults = array(
206: 'format' => 'tags',
207: 'version' => '1.0',
208: 'encoding' => Configure::read('App.encoding'),
209: 'return' => 'simplexml'
210: );
211: $options = array_merge($defaults, $options);
212:
213: $dom = new DOMDocument($options['version'], $options['encoding']);
214: self::_fromArray($dom, $dom, $input, $options['format']);
215:
216: $options['return'] = strtolower($options['return']);
217: if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
218: return new SimpleXMLElement($dom->saveXML());
219: }
220: return $dom;
221: }
222:
223: 224: 225: 226: 227: 228: 229: 230: 231: 232:
233: protected static function _fromArray($dom, $node, &$data, $format) {
234: if (empty($data) || !is_array($data)) {
235: return;
236: }
237: foreach ($data as $key => $value) {
238: if (is_string($key)) {
239: if (!is_array($value)) {
240: if (is_bool($value)) {
241: $value = (int)$value;
242: } elseif ($value === null) {
243: $value = '';
244: }
245: $isNamespace = strpos($key, 'xmlns:');
246: if ($isNamespace !== false) {
247: $node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, $value);
248: continue;
249: }
250: if ($key[0] !== '@' && $format === 'tags') {
251: $child = null;
252: if (!is_numeric($value)) {
253:
254:
255:
256: $child = $dom->createElement($key, '');
257: $child->appendChild(new DOMText($value));
258: } else {
259: $child = $dom->createElement($key, $value);
260: }
261: $node->appendChild($child);
262: } else {
263: if ($key[0] === '@') {
264: $key = substr($key, 1);
265: }
266: $attribute = $dom->createAttribute($key);
267: $attribute->appendChild($dom->createTextNode($value));
268: $node->appendChild($attribute);
269: }
270: } else {
271: if ($key[0] === '@') {
272: throw new XmlException(__d('cake_dev', 'Invalid array'));
273: }
274: if (is_numeric(implode('', array_keys($value)))) {
275: foreach ($value as $item) {
276: $itemData = compact('dom', 'node', 'key', 'format');
277: $itemData['value'] = $item;
278: self::_createChild($itemData);
279: }
280: } else {
281: self::_createChild(compact('dom', 'node', 'key', 'value', 'format'));
282: }
283: }
284: } else {
285: throw new XmlException(__d('cake_dev', 'Invalid array'));
286: }
287: }
288: }
289:
290: 291: 292: 293: 294: 295:
296: protected static function _createChild($data) {
297: extract($data);
298: $childNS = $childValue = null;
299: if (is_array($value)) {
300: if (isset($value['@'])) {
301: $childValue = (string)$value['@'];
302: unset($value['@']);
303: }
304: if (isset($value['xmlns:'])) {
305: $childNS = $value['xmlns:'];
306: unset($value['xmlns:']);
307: }
308: } elseif (!empty($value) || $value === 0) {
309: $childValue = (string)$value;
310: }
311:
312: $child = $dom->createElement($key);
313: if ($childValue !== null) {
314: $child->appendChild($dom->createTextNode($childValue));
315: }
316: if ($childNS) {
317: $child->setAttribute('xmlns', $childNS);
318: }
319:
320: self::_fromArray($dom, $child, $value, $format);
321: $node->appendChild($child);
322: }
323:
324: 325: 326: 327: 328: 329: 330:
331: public static function toArray($obj) {
332: if ($obj instanceof DOMNode) {
333: $obj = simplexml_import_dom($obj);
334: }
335: if (!($obj instanceof SimpleXMLElement)) {
336: throw new XmlException(__d('cake_dev', 'The input is not instance of SimpleXMLElement, DOMDocument or DOMNode.'));
337: }
338: $result = array();
339: $namespaces = array_merge(array('' => ''), $obj->getNamespaces(true));
340: self::_toArray($obj, $result, '', array_keys($namespaces));
341: return $result;
342: }
343:
344: 345: 346: 347: 348: 349: 350: 351: 352:
353: protected static function _toArray($xml, &$parentData, $ns, $namespaces) {
354: $data = array();
355:
356: foreach ($namespaces as $namespace) {
357: foreach ($xml->attributes($namespace, true) as $key => $value) {
358: if (!empty($namespace)) {
359: $key = $namespace . ':' . $key;
360: }
361: $data['@' . $key] = (string)$value;
362: }
363:
364: foreach ($xml->children($namespace, true) as $child) {
365: self::_toArray($child, $data, $namespace, $namespaces);
366: }
367: }
368:
369: $asString = trim((string)$xml);
370: if (empty($data)) {
371: $data = $asString;
372: } elseif (strlen($asString) > 0) {
373: $data['@'] = $asString;
374: }
375:
376: if (!empty($ns)) {
377: $ns .= ':';
378: }
379: $name = $ns . $xml->getName();
380: if (isset($parentData[$name])) {
381: if (!is_array($parentData[$name]) || !isset($parentData[$name][0])) {
382: $parentData[$name] = array($parentData[$name]);
383: }
384: $parentData[$name][] = $data;
385: } else {
386: $parentData[$name] = $data;
387: }
388: }
389:
390: }
391: