pictcode / lib / Cake / Utility / Xml.php @ 40928d1c
履歴 | 表示 | アノテート | ダウンロード (11.811 KB)
| 1 | 635eef61 | spyder1211 | <?php
|
|---|---|---|---|
| 2 | /**
|
||
| 3 | * XML handling for Cake.
|
||
| 4 | *
|
||
| 5 | * The methods in these classes enable the datasources that use XML to work.
|
||
| 6 | *
|
||
| 7 | * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||
| 8 | * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||
| 9 | *
|
||
| 10 | * Licensed under The MIT License
|
||
| 11 | * For full copyright and license information, please see the LICENSE.txt
|
||
| 12 | * Redistributions of files must retain the above copyright notice.
|
||
| 13 | *
|
||
| 14 | * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||
| 15 | * @link http://cakephp.org CakePHP(tm) Project
|
||
| 16 | * @package Cake.Utility
|
||
| 17 | * @since CakePHP v .0.10.3.1400
|
||
| 18 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||
| 19 | */
|
||
| 20 | |||
| 21 | App::uses('HttpSocket', 'Network/Http'); |
||
| 22 | |||
| 23 | /**
|
||
| 24 | * XML handling for CakePHP.
|
||
| 25 | *
|
||
| 26 | * The methods in these classes enable the datasources that use XML to work.
|
||
| 27 | *
|
||
| 28 | * @package Cake.Utility
|
||
| 29 | */
|
||
| 30 | class Xml { |
||
| 31 | |||
| 32 | /**
|
||
| 33 | * Initialize SimpleXMLElement or DOMDocument from a given XML string, file path, URL or array.
|
||
| 34 | *
|
||
| 35 | * ### Usage:
|
||
| 36 | *
|
||
| 37 | * Building XML from a string:
|
||
| 38 | *
|
||
| 39 | * `$xml = Xml::build('<example>text</example>');`
|
||
| 40 | *
|
||
| 41 | * Building XML from string (output DOMDocument):
|
||
| 42 | *
|
||
| 43 | * `$xml = Xml::build('<example>text</example>', array('return' => 'domdocument'));`
|
||
| 44 | *
|
||
| 45 | * Building XML from a file path:
|
||
| 46 | *
|
||
| 47 | * `$xml = Xml::build('/path/to/an/xml/file.xml');`
|
||
| 48 | *
|
||
| 49 | * Building from a remote URL:
|
||
| 50 | *
|
||
| 51 | * `$xml = Xml::build('http://example.com/example.xml');`
|
||
| 52 | *
|
||
| 53 | * Building from an array:
|
||
| 54 | *
|
||
| 55 | * ```
|
||
| 56 | * $value = array(
|
||
| 57 | * 'tags' => array(
|
||
| 58 | * 'tag' => array(
|
||
| 59 | * array(
|
||
| 60 | * 'id' => '1',
|
||
| 61 | * 'name' => 'defect'
|
||
| 62 | * ),
|
||
| 63 | * array(
|
||
| 64 | * 'id' => '2',
|
||
| 65 | * 'name' => 'enhancement'
|
||
| 66 | * )
|
||
| 67 | * )
|
||
| 68 | * )
|
||
| 69 | * );
|
||
| 70 | * $xml = Xml::build($value);
|
||
| 71 | * ```
|
||
| 72 | *
|
||
| 73 | * When building XML from an array ensure that there is only one top level element.
|
||
| 74 | *
|
||
| 75 | * ### Options
|
||
| 76 | *
|
||
| 77 | * - `return` Can be 'simplexml' to return object of SimpleXMLElement or 'domdocument' to return DOMDocument.
|
||
| 78 | * - `loadEntities` Defaults to false. Set to true to enable loading of `<!ENTITY` definitions. This
|
||
| 79 | * is disabled by default for security reasons.
|
||
| 80 | * - `readFile` Set to false to disable file reading. This is important to disable when
|
||
| 81 | * putting user data into Xml::build(). If enabled local & remote files will be read if they exist.
|
||
| 82 | * Defaults to true for backwards compatibility reasons.
|
||
| 83 | * - If using array as input, you can pass `options` from Xml::fromArray.
|
||
| 84 | *
|
||
| 85 | * @param string|array $input XML string, a path to a file, a URL or an array
|
||
| 86 | * @param array $options The options to use
|
||
| 87 | * @return SimpleXMLElement|DOMDocument SimpleXMLElement or DOMDocument
|
||
| 88 | * @throws XmlException
|
||
| 89 | */
|
||
| 90 | public static function build($input, $options = array()) { |
||
| 91 | if (!is_array($options)) { |
||
| 92 | $options = array('return' => (string)$options); |
||
| 93 | } |
||
| 94 | $defaults = array( |
||
| 95 | 'return' => 'simplexml', |
||
| 96 | 'loadEntities' => false, |
||
| 97 | 'readFile' => true |
||
| 98 | ); |
||
| 99 | $options += $defaults; |
||
| 100 | |||
| 101 | if (is_array($input) || is_object($input)) { |
||
| 102 | return static::fromArray((array)$input, $options); |
||
| 103 | } elseif (strpos($input, '<') !== false) { |
||
| 104 | return static::_loadXml($input, $options); |
||
| 105 | } elseif ($options['readFile'] && file_exists($input)) { |
||
| 106 | return static::_loadXml(file_get_contents($input), $options); |
||
| 107 | } elseif ($options['readFile'] && strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) { |
||
| 108 | try {
|
||
| 109 | $socket = new HttpSocket(array('request' => array('redirect' => 10))); |
||
| 110 | $response = $socket->get($input); |
||
| 111 | if (!$response->isOk()) { |
||
| 112 | throw new XmlException(__d('cake_dev', 'XML cannot be read.')); |
||
| 113 | } |
||
| 114 | return static::_loadXml($response->body, $options); |
||
| 115 | } catch (SocketException $e) { |
||
| 116 | throw new XmlException(__d('cake_dev', 'XML cannot be read.')); |
||
| 117 | } |
||
| 118 | } elseif (!is_string($input)) { |
||
| 119 | throw new XmlException(__d('cake_dev', 'Invalid input.')); |
||
| 120 | } |
||
| 121 | throw new XmlException(__d('cake_dev', 'XML cannot be read.')); |
||
| 122 | } |
||
| 123 | |||
| 124 | /**
|
||
| 125 | * Parse the input data and create either a SimpleXmlElement object or a DOMDocument.
|
||
| 126 | *
|
||
| 127 | * @param string $input The input to load.
|
||
| 128 | * @param array $options The options to use. See Xml::build()
|
||
| 129 | * @return SimpleXmlElement|DOMDocument
|
||
| 130 | * @throws XmlException
|
||
| 131 | */
|
||
| 132 | protected static function _loadXml($input, $options) { |
||
| 133 | $hasDisable = function_exists('libxml_disable_entity_loader'); |
||
| 134 | $internalErrors = libxml_use_internal_errors(true); |
||
| 135 | if ($hasDisable && !$options['loadEntities']) { |
||
| 136 | libxml_disable_entity_loader(true);
|
||
| 137 | } |
||
| 138 | try {
|
||
| 139 | if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { |
||
| 140 | $xml = new SimpleXMLElement($input, LIBXML_NOCDATA); |
||
| 141 | } else {
|
||
| 142 | $xml = new DOMDocument(); |
||
| 143 | $xml->loadXML($input); |
||
| 144 | } |
||
| 145 | } catch (Exception $e) { |
||
| 146 | $xml = null; |
||
| 147 | } |
||
| 148 | if ($hasDisable && !$options['loadEntities']) { |
||
| 149 | libxml_disable_entity_loader(false);
|
||
| 150 | } |
||
| 151 | libxml_use_internal_errors($internalErrors);
|
||
| 152 | if ($xml === null) { |
||
| 153 | throw new XmlException(__d('cake_dev', 'Xml cannot be read.')); |
||
| 154 | } |
||
| 155 | return $xml; |
||
| 156 | } |
||
| 157 | |||
| 158 | /**
|
||
| 159 | * Transform an array into a SimpleXMLElement
|
||
| 160 | *
|
||
| 161 | * ### Options
|
||
| 162 | *
|
||
| 163 | * - `format` If create childs ('tags') or attributes ('attributes').
|
||
| 164 | * - `pretty` Returns formatted Xml when set to `true`. Defaults to `false`
|
||
| 165 | * - `version` Version of XML document. Default is 1.0.
|
||
| 166 | * - `encoding` Encoding of XML document. If null remove from XML header. Default is the some of application.
|
||
| 167 | * - `return` If return object of SimpleXMLElement ('simplexml') or DOMDocument ('domdocument'). Default is SimpleXMLElement.
|
||
| 168 | *
|
||
| 169 | * Using the following data:
|
||
| 170 | *
|
||
| 171 | * ```
|
||
| 172 | * $value = array(
|
||
| 173 | * 'root' => array(
|
||
| 174 | * 'tag' => array(
|
||
| 175 | * 'id' => 1,
|
||
| 176 | * 'value' => 'defect',
|
||
| 177 | * '@' => 'description'
|
||
| 178 | * )
|
||
| 179 | * )
|
||
| 180 | * );
|
||
| 181 | * ```
|
||
| 182 | *
|
||
| 183 | * Calling `Xml::fromArray($value, 'tags');` Will generate:
|
||
| 184 | *
|
||
| 185 | * `<root><tag><id>1</id><value>defect</value>description</tag></root>`
|
||
| 186 | *
|
||
| 187 | * And calling `Xml::fromArray($value, 'attributes');` Will generate:
|
||
| 188 | *
|
||
| 189 | * `<root><tag id="1" value="defect">description</tag></root>`
|
||
| 190 | *
|
||
| 191 | * @param array $input Array with data
|
||
| 192 | * @param array $options The options to use
|
||
| 193 | * @return SimpleXMLElement|DOMDocument SimpleXMLElement or DOMDocument
|
||
| 194 | * @throws XmlException
|
||
| 195 | */
|
||
| 196 | public static function fromArray($input, $options = array()) { |
||
| 197 | if (!is_array($input) || count($input) !== 1) { |
||
| 198 | throw new XmlException(__d('cake_dev', 'Invalid input.')); |
||
| 199 | } |
||
| 200 | $key = key($input); |
||
| 201 | if (is_int($key)) { |
||
| 202 | throw new XmlException(__d('cake_dev', 'The key of input must be alphanumeric')); |
||
| 203 | } |
||
| 204 | |||
| 205 | if (!is_array($options)) { |
||
| 206 | $options = array('format' => (string)$options); |
||
| 207 | } |
||
| 208 | $defaults = array( |
||
| 209 | 'format' => 'tags', |
||
| 210 | 'version' => '1.0', |
||
| 211 | 'encoding' => Configure::read('App.encoding'), |
||
| 212 | 'return' => 'simplexml', |
||
| 213 | 'pretty' => false |
||
| 214 | ); |
||
| 215 | $options += $defaults; |
||
| 216 | |||
| 217 | $dom = new DOMDocument($options['version'], $options['encoding']); |
||
| 218 | if ($options['pretty']) { |
||
| 219 | $dom->formatOutput = true; |
||
| 220 | } |
||
| 221 | static::_fromArray($dom, $dom, $input, $options['format']); |
||
| 222 | |||
| 223 | $options['return'] = strtolower($options['return']); |
||
| 224 | if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { |
||
| 225 | return new SimpleXMLElement($dom->saveXML()); |
||
| 226 | } |
||
| 227 | return $dom; |
||
| 228 | } |
||
| 229 | |||
| 230 | /**
|
||
| 231 | * Recursive method to create childs from array
|
||
| 232 | *
|
||
| 233 | * @param DOMDocument $dom Handler to DOMDocument
|
||
| 234 | * @param DOMElement $node Handler to DOMElement (child)
|
||
| 235 | * @param array &$data Array of data to append to the $node.
|
||
| 236 | * @param string $format Either 'attributes' or 'tags'. This determines where nested keys go.
|
||
| 237 | * @return void
|
||
| 238 | * @throws XmlException
|
||
| 239 | */
|
||
| 240 | protected static function _fromArray($dom, $node, &$data, $format) { |
||
| 241 | if (empty($data) || !is_array($data)) { |
||
| 242 | return;
|
||
| 243 | } |
||
| 244 | foreach ($data as $key => $value) { |
||
| 245 | if (is_string($key)) { |
||
| 246 | if (!is_array($value)) { |
||
| 247 | if (is_bool($value)) { |
||
| 248 | $value = (int)$value; |
||
| 249 | } elseif ($value === null) { |
||
| 250 | $value = ''; |
||
| 251 | } |
||
| 252 | $isNamespace = strpos($key, 'xmlns:'); |
||
| 253 | if ($isNamespace !== false) { |
||
| 254 | $node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, $value); |
||
| 255 | continue;
|
||
| 256 | } |
||
| 257 | if ($key[0] !== '@' && $format === 'tags') { |
||
| 258 | $child = null; |
||
| 259 | if (!is_numeric($value)) { |
||
| 260 | // Escape special characters
|
||
| 261 | // http://www.w3.org/TR/REC-xml/#syntax
|
||
| 262 | // https://bugs.php.net/bug.php?id=36795
|
||
| 263 | $child = $dom->createElement($key, ''); |
||
| 264 | $child->appendChild(new DOMText($value)); |
||
| 265 | } else {
|
||
| 266 | $child = $dom->createElement($key, $value); |
||
| 267 | } |
||
| 268 | $node->appendChild($child); |
||
| 269 | } else {
|
||
| 270 | if ($key[0] === '@') { |
||
| 271 | $key = substr($key, 1); |
||
| 272 | } |
||
| 273 | $attribute = $dom->createAttribute($key); |
||
| 274 | $attribute->appendChild($dom->createTextNode($value)); |
||
| 275 | $node->appendChild($attribute); |
||
| 276 | } |
||
| 277 | } else {
|
||
| 278 | if ($key[0] === '@') { |
||
| 279 | throw new XmlException(__d('cake_dev', 'Invalid array')); |
||
| 280 | } |
||
| 281 | if (is_numeric(implode('', array_keys($value)))) { // List |
||
| 282 | foreach ($value as $item) { |
||
| 283 | $itemData = compact('dom', 'node', 'key', 'format'); |
||
| 284 | $itemData['value'] = $item; |
||
| 285 | static::_createChild($itemData); |
||
| 286 | } |
||
| 287 | } else { // Struct |
||
| 288 | static::_createChild(compact('dom', 'node', 'key', 'value', 'format')); |
||
| 289 | } |
||
| 290 | } |
||
| 291 | } else {
|
||
| 292 | throw new XmlException(__d('cake_dev', 'Invalid array')); |
||
| 293 | } |
||
| 294 | } |
||
| 295 | } |
||
| 296 | |||
| 297 | /**
|
||
| 298 | * Helper to _fromArray(). It will create childs of arrays
|
||
| 299 | *
|
||
| 300 | * @param array $data Array with informations to create childs
|
||
| 301 | * @return void
|
||
| 302 | */
|
||
| 303 | protected static function _createChild($data) { |
||
| 304 | extract($data); |
||
| 305 | $childNS = $childValue = null; |
||
| 306 | if (is_array($value)) { |
||
| 307 | if (isset($value['@'])) { |
||
| 308 | $childValue = (string)$value['@']; |
||
| 309 | unset($value['@']); |
||
| 310 | } |
||
| 311 | if (isset($value['xmlns:'])) { |
||
| 312 | $childNS = $value['xmlns:']; |
||
| 313 | unset($value['xmlns:']); |
||
| 314 | } |
||
| 315 | } elseif (!empty($value) || $value === 0) { |
||
| 316 | $childValue = (string)$value; |
||
| 317 | } |
||
| 318 | |||
| 319 | $child = $dom->createElement($key); |
||
| 320 | if ($childValue !== null) { |
||
| 321 | $child->appendChild($dom->createTextNode($childValue)); |
||
| 322 | } |
||
| 323 | if ($childNS) { |
||
| 324 | $child->setAttribute('xmlns', $childNS); |
||
| 325 | } |
||
| 326 | |||
| 327 | static::_fromArray($dom, $child, $value, $format); |
||
| 328 | $node->appendChild($child); |
||
| 329 | } |
||
| 330 | |||
| 331 | /**
|
||
| 332 | * Returns this XML structure as an array.
|
||
| 333 | *
|
||
| 334 | * @param SimpleXMLElement|DOMDocument|DOMNode $obj SimpleXMLElement, DOMDocument or DOMNode instance
|
||
| 335 | * @return array Array representation of the XML structure.
|
||
| 336 | * @throws XmlException
|
||
| 337 | */
|
||
| 338 | public static function toArray($obj) { |
||
| 339 | if ($obj instanceof DOMNode) { |
||
| 340 | $obj = simplexml_import_dom($obj); |
||
| 341 | } |
||
| 342 | if (!($obj instanceof SimpleXMLElement)) { |
||
| 343 | throw new XmlException(__d('cake_dev', 'The input is not instance of SimpleXMLElement, DOMDocument or DOMNode.')); |
||
| 344 | } |
||
| 345 | $result = array(); |
||
| 346 | $namespaces = array_merge(array('' => ''), $obj->getNamespaces(true)); |
||
| 347 | static::_toArray($obj, $result, '', array_keys($namespaces)); |
||
| 348 | return $result; |
||
| 349 | } |
||
| 350 | |||
| 351 | /**
|
||
| 352 | * Recursive method to toArray
|
||
| 353 | *
|
||
| 354 | * @param SimpleXMLElement $xml SimpleXMLElement object
|
||
| 355 | * @param array &$parentData Parent array with data
|
||
| 356 | * @param string $ns Namespace of current child
|
||
| 357 | * @param array $namespaces List of namespaces in XML
|
||
| 358 | * @return void
|
||
| 359 | */
|
||
| 360 | protected static function _toArray($xml, &$parentData, $ns, $namespaces) { |
||
| 361 | $data = array(); |
||
| 362 | |||
| 363 | foreach ($namespaces as $namespace) { |
||
| 364 | foreach ($xml->attributes($namespace, true) as $key => $value) { |
||
| 365 | if (!empty($namespace)) { |
||
| 366 | $key = $namespace . ':' . $key; |
||
| 367 | } |
||
| 368 | $data['@' . $key] = (string)$value; |
||
| 369 | } |
||
| 370 | |||
| 371 | foreach ($xml->children($namespace, true) as $child) { |
||
| 372 | static::_toArray($child, $data, $namespace, $namespaces); |
||
| 373 | } |
||
| 374 | } |
||
| 375 | |||
| 376 | $asString = trim((string)$xml); |
||
| 377 | if (empty($data)) { |
||
| 378 | $data = $asString; |
||
| 379 | } elseif (strlen($asString) > 0) { |
||
| 380 | $data['@'] = $asString; |
||
| 381 | } |
||
| 382 | |||
| 383 | if (!empty($ns)) { |
||
| 384 | $ns .= ':'; |
||
| 385 | } |
||
| 386 | $name = $ns . $xml->getName(); |
||
| 387 | if (isset($parentData[$name])) { |
||
| 388 | if (!is_array($parentData[$name]) || !isset($parentData[$name][0])) { |
||
| 389 | $parentData[$name] = array($parentData[$name]); |
||
| 390 | } |
||
| 391 | $parentData[$name][] = $data; |
||
| 392 | } else {
|
||
| 393 | $parentData[$name] = $data; |
||
| 394 | } |
||
| 395 | } |
||
| 396 | |||
| 397 | } |