xml.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: xml.php 8264 2009-07-31 01:50:14Z gwoo $ */
00003 /**
00004  * XML handling for Cake.
00005  *
00006  * The methods in these classes enable the datasources that use XML to work.
00007  *
00008  * PHP versions 4 and 5
00009  *
00010  * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
00011  * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00012  *
00013  * Licensed under The MIT License
00014  * Redistributions of files must retain the above copyright notice.
00015  *
00016  * @filesource
00017  * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
00018  * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00019  * @package       cake
00020  * @subpackage    cake.cake.libs
00021  * @since         CakePHP v .0.10.3.1400
00022  * @version       $Revision: 8264 $
00023  * @modifiedby    $LastChangedBy: gwoo $
00024  * @lastmodified  $Date: 2009-07-30 21:50:14 -0400 (Thu, 30 Jul 2009) $
00025  * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
00026  */
00027 App::import('Core', 'Set');
00028 /**
00029  * XML node.
00030  *
00031  * Single XML node in an XML tree.
00032  *
00033  * @package       cake
00034  * @subpackage    cake.cake.libs
00035  * @since         CakePHP v .0.10.3.1400
00036  */
00037 class XmlNode extends Object {
00038 /**
00039  * Name of node
00040  *
00041  * @var string
00042  * @access public
00043  */
00044     var $name = null;
00045 /**
00046  * Node namespace
00047  *
00048  * @var string
00049  * @access public
00050  */
00051     var $namespace = null;
00052 /**
00053  * Namespaces defined for this node and all child nodes
00054  *
00055  * @var array
00056  * @access public
00057  */
00058     var $namespaces = array();
00059 /**
00060  * Value of node
00061  *
00062  * @var string
00063  * @access public
00064  */
00065     var $value;
00066 /**
00067  * Attributes on this node
00068  *
00069  * @var array
00070  * @access public
00071  */
00072     var $attributes = array();
00073 /**
00074  * This node's children
00075  *
00076  * @var array
00077  * @access public
00078  */
00079     var $children = array();
00080 /**
00081  * Reference to parent node.
00082  *
00083  * @var XmlNode
00084  * @access private
00085  */
00086     var $__parent = null;
00087 /**
00088  * Constructor.
00089  *
00090  * @param string $name Node name
00091  * @param array $attributes Node attributes
00092  * @param mixed $value Node contents (text)
00093  * @param array $children Node children
00094  */
00095     function __construct($name = null, $value = null, $namespace = null) {
00096         if (strpos($name, ':') !== false) {
00097             list($prefix, $name) = explode(':', $name);
00098             if (!$namespace) {
00099                 $namespace = $prefix;
00100             }
00101         }
00102         $this->name = $name;
00103         if ($namespace) {
00104             $this->namespace = $namespace;
00105         }
00106 
00107         if (is_array($value) || is_object($value)) {
00108             $this->normalize($value);
00109         } elseif (!empty($value) || $value === 0 || $value === '0') {
00110             $this->createTextNode($value);
00111         }
00112     }
00113 
00114 /**
00115  * Adds a namespace to the current node
00116  *
00117  * @param string $prefix The namespace prefix
00118  * @param string $url The namespace DTD URL
00119  * @return void
00120  */
00121     function addNamespace($prefix, $url) {
00122         if ($ns = Xml::addGlobalNs($prefix, $url)) {
00123             $this->namespaces = array_merge($this->namespaces, $ns);
00124             return true;
00125         }
00126         return false;
00127     }
00128 /**
00129  * Adds a namespace to the current node
00130  *
00131  * @param string $prefix The namespace prefix
00132  * @param string $url The namespace DTD URL
00133  * @return void
00134  */
00135     function removeNamespace($prefix) {
00136         if (Xml::removeGlobalNs($prefix)) {
00137             return true;
00138         }
00139         return false;
00140     }
00141 /**
00142  * Creates an XmlNode object that can be appended to this document or a node in it
00143  *
00144  * @param string $name Node name
00145  * @param string $value Node value
00146  * @param string $namespace Node namespace
00147  * @return object XmlNode
00148  */
00149     function &createNode($name = null, $value = null, $namespace = false) {
00150         $node =& new XmlNode($name, $value, $namespace);
00151         $node->setParent($this);
00152         return $node;
00153     }
00154 /**
00155  * Creates an XmlElement object that can be appended to this document or a node in it
00156  *
00157  * @param string $name Element name
00158  * @param string $value Element value
00159  * @param array $attributes Element attributes
00160  * @param string $namespace Node namespace
00161  * @return object XmlElement
00162  */
00163     function &createElement($name = null, $value = null, $attributes = array(), $namespace = false) {
00164         $element =& new XmlElement($name, $value, $attributes, $namespace);
00165         $element->setParent($this);
00166         return $element;
00167     }
00168 /**
00169  * Creates an XmlTextNode object that can be appended to this document or a node in it
00170  *
00171  * @param string $value Node value
00172  * @return object XmlTextNode
00173  */
00174     function &createTextNode($value = null) {
00175         $node = new XmlTextNode($value);
00176         $node->setParent($this);
00177         return $node;
00178     }
00179 /**
00180  * Gets the XML element properties from an object.
00181  *
00182  * @param object $object Object to get properties from
00183  * @return array Properties from object
00184  * @access public
00185  */
00186     function normalize($object, $keyName = null, $options = array()) {
00187         if (is_a($object, 'XmlNode')) {
00188             return $object;
00189         }
00190         $name = null;
00191         $options += array('format' => 'attributes');
00192 
00193         if ($keyName !== null && !is_numeric($keyName)) {
00194             $name = $keyName;
00195         } elseif (!empty($object->_name_)) {
00196             $name = $object->_name_;
00197         } elseif (isset($object->name)) {
00198             $name = $object->name;
00199         } elseif ($options['format'] == 'attributes') {
00200             $name = get_class($object);
00201         }
00202 
00203         $tagOpts = $this->__tagOptions($name);
00204 
00205         if ($tagOpts === false) {
00206             return;
00207         }
00208 
00209         if (isset($tagOpts['name'])) {
00210             $name = $tagOpts['name'];
00211         } elseif ($name != strtolower($name)) {
00212             $name = Inflector::slug(Inflector::underscore($name));
00213         }
00214 
00215         if (!empty($name)) {
00216             $node =& $this->createElement($name);
00217         } else {
00218             $node =& $this;
00219         }
00220 
00221         $namespace = array();
00222         $attributes = array();
00223         $children = array();
00224         $chldObjs = array();
00225 
00226         if (is_object($object)) {
00227             $chldObjs = get_object_vars($object);
00228         } elseif (is_array($object)) {
00229             $chldObjs = $object;
00230         } elseif (!empty($object) || $object === 0) {
00231             $node->createTextNode($object);
00232         }
00233         $attr = array();
00234 
00235         if (isset($tagOpts['attributes'])) {
00236             $attr = $tagOpts['attributes'];
00237         }
00238         if (isset($tagOpts['value']) && isset($chldObjs[$tagOpts['value']])) {
00239             $node->createTextNode($chldObjs[$tagOpts['value']]);
00240             unset($chldObjs[$tagOpts['value']]);
00241         }
00242 
00243         $n = $name;
00244         if (!empty($chldObjs['_name_'])) {
00245             $n = null;
00246             unset($chldObjs['_name_']);
00247         }
00248         $c = 0;
00249 
00250         foreach ($chldObjs as $key => $val) {
00251             if (in_array($key, $attr) && !is_object($val) && !is_array($val)) {
00252                 $attributes[$key] = $val;
00253             } else {
00254                 if (!isset($tagOpts['children']) || $tagOpts['children'] === array() || (is_array($tagOpts['children']) && in_array($key, $tagOpts['children']))) {
00255                     if (!is_numeric($key)) {
00256                         $n = $key;
00257                     }
00258                     if (is_array($val)) {
00259                         foreach ($val as $n2 => $obj2) {
00260                             if (is_numeric($n2)) {
00261                                 $n2 = $n;
00262                             }
00263                             $node->normalize($obj2, $n2, $options);
00264                         }
00265                     } else {
00266                         if (is_object($val)) {
00267 
00268                             $node->normalize($val, $n, $options);
00269                         } elseif ($options['format'] == 'tags' && $this->__tagOptions($key) !== false) {
00270                             $tmp =& $node->createElement($key);
00271                             if (!empty($val) || $val === 0) {
00272                                 $tmp->createTextNode($val);
00273                             }
00274                         } elseif ($options['format'] == 'attributes') {
00275                             $node->addAttribute($key, $val);
00276                         }
00277                     }
00278                 }
00279             }
00280             $c++;
00281         }
00282         if (!empty($name)) {
00283             return $node;
00284         }
00285         return $children;
00286     }
00287 /**
00288  * Gets the tag-specific options for the given node name
00289  *
00290  * @param string $name XML tag name
00291  * @param string $option The specific option to query.  Omit for all options
00292  * @return mixed A specific option value if $option is specified, otherwise an array of all options
00293  * @access private
00294  */
00295     function __tagOptions($name, $option = null) {
00296         if (isset($this->__tags[$name])) {
00297             $tagOpts = $this->__tags[$name];
00298         } elseif (isset($this->__tags[strtolower($name)])) {
00299             $tagOpts = $this->__tags[strtolower($name)];
00300         } else {
00301             return null;
00302         }
00303         if ($tagOpts === false) {
00304             return false;
00305         }
00306         if (empty($option)) {
00307             return $tagOpts;
00308         }
00309         if (isset($tagOpts[$option])) {
00310             return $tagOpts[$option];
00311         }
00312         return null;
00313     }
00314 /**
00315  * Returns the fully-qualified XML node name, with namespace
00316  *
00317  * @access public
00318  */
00319     function name() {
00320         if (!empty($this->namespace)) {
00321             $_this =& XmlManager::getInstance();
00322             if (!isset($_this->options['verifyNs']) || !$_this->options['verifyNs'] || in_array($this->namespace, array_keys($_this->namespaces))) {
00323                 return $this->namespace . ':' . $this->name;
00324             }
00325         }
00326         return $this->name;
00327     }
00328 /**
00329  * Sets the parent node of this XmlNode.
00330  *
00331  * @access public
00332  */
00333     function setParent(&$parent) {
00334         if (strtolower(get_class($this)) == 'xml') {
00335             return;
00336         }
00337         if (isset($this->__parent) && is_object($this->__parent)) {
00338             if ($this->__parent->compare($parent)) {
00339                 return;
00340             }
00341             foreach ($this->__parent->children as $i => $child) {
00342                 if ($this->compare($child)) {
00343                     array_splice($this->__parent->children, $i, 1);
00344                     break;
00345                 }
00346             }
00347         }
00348         if ($parent == null) {
00349             unset($this->__parent);
00350         } else {
00351             $parent->children[] =& $this;
00352             $this->__parent =& $parent;
00353         }
00354     }
00355 /**
00356  * Returns a copy of self.
00357  *
00358  * @return object Cloned instance
00359  * @access public
00360  */
00361     function cloneNode() {
00362         return clone($this);
00363     }
00364 /**
00365  * Compares $node to this XmlNode object
00366  *
00367  * @param object An XmlNode or subclass instance
00368  * @return boolean True if the nodes match, false otherwise
00369  * @access public
00370  */
00371     function compare($node) {
00372         $keys = array(get_object_vars($this), get_object_vars($node));
00373         return ($keys[0] === $keys[1]);
00374     }
00375 /**
00376  * Append given node as a child.
00377  *
00378  * @param object $child XmlNode with appended child
00379  * @param array $options XML generator options for objects and arrays
00380  * @return object A reference to the appended child node
00381  * @access public
00382  */
00383     function &append(&$child, $options = array()) {
00384         if (empty($child)) {
00385             $return = false;
00386             return $return;
00387         }
00388 
00389         if (is_object($child)) {
00390             if ($this->compare($child)) {
00391                 trigger_error('Cannot append a node to itself.');
00392                 $return = false;
00393                 return $return;
00394             }
00395         } else if (is_array($child)) {
00396             $child = Set::map($child);
00397             if (is_array($child)) {
00398                 if (!is_a(current($child), 'XmlNode')) {
00399                     foreach ($child as $i => $childNode) {
00400                         $child[$i] = $this->normalize($childNode, null, $options);
00401                     }
00402                 } else {
00403                     foreach ($child as $childNode) {
00404                         $this->append($childNode, $options);
00405                     }
00406                 }
00407                 return $child;
00408             }
00409         } else {
00410             $attributes = array();
00411             if (func_num_args() >= 2) {
00412                 $attributes = func_get_arg(1);
00413             }
00414             $child =& $this->createNode($child, null, $attributes);
00415         }
00416 
00417         $child = $this->normalize($child, null, $options);
00418 
00419         if (empty($child->namespace) && !empty($this->namespace)) {
00420             $child->namespace = $this->namespace;
00421         }
00422 
00423         if (is_a($child, 'XmlNode')) {
00424             $child->setParent($this);
00425         }
00426 
00427         return $child;
00428     }
00429 /**
00430  * Returns first child node, or null if empty.
00431  *
00432  * @return object First XmlNode
00433  * @access public
00434  */
00435     function &first() {
00436         if (isset($this->children[0])) {
00437             return $this->children[0];
00438         } else {
00439             $return = null;
00440             return $return;
00441         }
00442     }
00443 /**
00444  * Returns last child node, or null if empty.
00445  *
00446  * @return object Last XmlNode
00447  * @access public
00448  */
00449     function &last() {
00450         if (count($this->children) > 0) {
00451             return $this->children[count($this->children) - 1];
00452         } else {
00453             $return = null;
00454             return $return;
00455         }
00456     }
00457 /**
00458  * Returns child node with given ID.
00459  *
00460  * @param string $id Name of child node
00461  * @return object Child XmlNode
00462  * @access public
00463  */
00464     function &child($id) {
00465         $null = null;
00466 
00467         if (is_int($id)) {
00468             if (isset($this->children[$id])) {
00469                 return $this->children[$id];
00470             } else {
00471                 return null;
00472             }
00473         } elseif (is_string($id)) {
00474             for ($i = 0; $i < count($this->children); $i++) {
00475                 if ($this->children[$i]->name == $id) {
00476                     return $this->children[$i];
00477                 }
00478             }
00479         }
00480         return $null;
00481     }
00482 /**
00483  * Gets a list of childnodes with the given tag name.
00484  *
00485  * @param string $name Tag name of child nodes
00486  * @return array An array of XmlNodes with the given tag name
00487  * @access public
00488  */
00489     function children($name) {
00490         $nodes = array();
00491         $count = count($this->children);
00492         for ($i = 0; $i < $count; $i++) {
00493             if ($this->children[$i]->name == $name) {
00494                 $nodes[] =& $this->children[$i];
00495             }
00496         }
00497         return $nodes;
00498     }
00499 /**
00500  * Gets a reference to the next child node in the list of this node's parent.
00501  *
00502  * @return object A reference to the XmlNode object
00503  * @access public
00504  */
00505     function &nextSibling() {
00506         $null = null;
00507         $count = count($this->__parent->children);
00508         for ($i = 0; $i < $count; $i++) {
00509             if ($this->__parent->children[$i] == $this) {
00510                 if ($i >= $count - 1 || !isset($this->__parent->children[$i + 1])) {
00511                     return $null;
00512                 }
00513                 return $this->__parent->children[$i + 1];
00514             }
00515         }
00516         return $null;
00517     }
00518 /**
00519  * Gets a reference to the previous child node in the list of this node's parent.
00520  *
00521  * @return object A reference to the XmlNode object
00522  * @access public
00523  */
00524     function &previousSibling() {
00525         $null = null;
00526         $count = count($this->__parent->children);
00527         for ($i = 0; $i < $count; $i++) {
00528             if ($this->__parent->children[$i] == $this) {
00529                 if ($i == 0 || !isset($this->__parent->children[$i - 1])) {
00530                     return $null;
00531                 }
00532                 return $this->__parent->children[$i - 1];
00533             }
00534         }
00535         return $null;
00536     }
00537 /**
00538  * Returns parent node.
00539  *
00540  * @return object Parent XmlNode
00541  * @access public
00542  */
00543     function &parent() {
00544         return $this->__parent;
00545     }
00546 /**
00547  * Returns the XML document to which this node belongs
00548  *
00549  * @return object Parent XML object
00550  * @access public
00551  */
00552     function &document() {
00553         $document =& $this;
00554         while (true) {
00555             if (get_class($document) == 'Xml' || $document == null) {
00556                 break;
00557             }
00558             $document =& $document->parent();
00559         }
00560         return $document;
00561     }
00562 /**
00563  * Returns true if this structure has child nodes.
00564  *
00565  * @return bool
00566  * @access public
00567  */
00568     function hasChildren() {
00569         if (is_array($this->children) && count($this->children) > 0) {
00570             return true;
00571         }
00572         return false;
00573     }
00574 /**
00575  * Returns this XML structure as a string.
00576  *
00577  * @return string String representation of the XML structure.
00578  * @access public
00579  */
00580     function toString($options = array(), $depth = 0) {
00581         if (is_int($options)) {
00582             $depth = $options;
00583             $options = array();
00584         }
00585         $defaults = array('cdata' => true, 'whitespace' => false, 'convertEntities' => false, 'showEmpty' => true, 'leaveOpen' => false);
00586         $options = array_merge($defaults, Xml::options(), $options);
00587         $tag = !(strpos($this->name, '#') === 0);
00588         $d = '';
00589 
00590         if ($tag) {
00591             if ($options['whitespace']) {
00592                 $d .= str_repeat("\t", $depth);
00593             }
00594 
00595             $d .= '<' . $this->name();
00596             if (count($this->namespaces) > 0) {
00597                 foreach ($this->namespaces as $key => $val) {
00598                     $val = str_replace('"', '\"', $val);
00599                     $d .= ' xmlns:' . $key . '="' . $val . '"';
00600                 }
00601             }
00602 
00603             $parent =& $this->parent();
00604             if ($parent->name === '#document' && count($parent->namespaces) > 0) {
00605                 foreach ($parent->namespaces as $key => $val) {
00606                     $val = str_replace('"', '\"', $val);
00607                     $d .= ' xmlns:' . $key . '="' . $val . '"';
00608                 }
00609             }
00610 
00611             if (is_array($this->attributes) && count($this->attributes) > 0) {
00612                 foreach ($this->attributes as $key => $val) {
00613                     if (is_bool($val) && $val === false) {
00614                         $val = 0;
00615                     }
00616                     $d .= ' ' . $key . '="' . htmlspecialchars($val, ENT_QUOTES, Configure::read('App.encoding')) . '"';
00617                 }
00618             }
00619         }
00620 
00621         if (!$this->hasChildren() && empty($this->value) && $this->value !== 0 && $tag) {
00622             if (!$options['leaveOpen']) {
00623                 $d .= ' />';
00624             }
00625             if ($options['whitespace']) {
00626                 $d .= "\n";
00627             }
00628         } elseif ($tag || $this->hasChildren()) {
00629             if ($tag) {
00630                 $d .= '>';
00631             }
00632             if ($this->hasChildren()) {
00633                 if ($options['whitespace']) {
00634                     $d .= "\n";
00635                 }
00636                 $count = count($this->children);
00637                 $cDepth = $depth + 1;
00638                 for ($i = 0; $i < $count; $i++) {
00639                     $d .= $this->children[$i]->toString($options, $cDepth);
00640                 }
00641                 if ($tag) {
00642                     if ($options['whitespace'] && $tag) {
00643                         $d .= str_repeat("\t", $depth);
00644                     }
00645                     if (!$options['leaveOpen']) {
00646                         $d .= '</' . $this->name() . '>';
00647                     }
00648                     if ($options['whitespace']) {
00649                         $d .= "\n";
00650                     }
00651                 }
00652             }
00653         }
00654         return $d;
00655     }
00656 /**
00657  * Return array representation of current object.
00658  *
00659  * @param boolean $camelize true will camelize child nodes, false will not alter node names
00660  * @return array Array representation
00661  * @access public
00662  */
00663     function toArray($camelize = true) {
00664         $out = $this->attributes;
00665         $multi = null;
00666 
00667         foreach ($this->children as $child) {
00668             $key = $camelize ? Inflector::camelize($child->name) : $child->name;
00669 
00670             if (is_a($child, 'XmlTextNode')) {
00671                 $out['value'] = $child->value;
00672                 continue;
00673             } elseif (isset($child->children[0]) && is_a($child->children[0], 'XmlTextNode')) {
00674                 $value = $child->children[0]->value;
00675 
00676                 if ($child->attributes) {
00677                     $value = array_merge(array('value' => $value), $child->attributes);
00678                 }
00679 
00680                 if (isset($out[$child->name]) || isset($multi[$key])) {
00681                     if (!isset($multi[$key])) {
00682                         $multi[$key] = array($out[$child->name]);
00683                         unset($out[$child->name]);
00684                     }
00685                     $multi[$key][] = $value;
00686                 } else {
00687                     $out[$child->name] = $value;
00688                 }
00689                 continue;
00690             } elseif (count($child->children) === 0 && $child->value == '') {
00691                 $value = $child->attributes;
00692 
00693                 if (isset($out[$child->name]) || isset($multi[$key])) {
00694                     if (!isset($multi[$key])) {
00695                         $multi[$key] = array($out[$child->name]);
00696                         unset($out[$child->name]);
00697                     }
00698                     $multi[$key][] = $value;
00699                 } else {
00700                     $out[$key] = $value;
00701                 }
00702                 continue;
00703             } else {
00704                 $value = $child->toArray($camelize);
00705             }
00706 
00707             if (!isset($out[$key])) {
00708                 $out[$key] = $value;
00709             } else {
00710                 if (!is_array($out[$key]) || !isset($out[$key][0])) {
00711                     $out[$key] = array($out[$key]);
00712                 }
00713                 $out[$key][] = $value;
00714             }
00715         }
00716 
00717         if (isset($multi)) {
00718             $out = array_merge($out, $multi);
00719         }
00720         return $out;
00721     }
00722 /**
00723  * Returns data from toString when this object is converted to a string.
00724  *
00725  * @return string String representation of this structure.
00726  * @access private
00727  */
00728     function __toString() {
00729         return $this->toString();
00730     }
00731 /**
00732  * Debug method. Deletes the parent. Also deletes this node's children,
00733  * if given the $recursive parameter.
00734  *
00735  * @param boolean $recursive Recursively delete elements.
00736  * @access private
00737  */
00738     function __killParent($recursive = true) {
00739         unset($this->__parent, $this->_log);
00740         if ($recursive && $this->hasChildren()) {
00741             for ($i = 0; $i < count($this->children); $i++) {
00742                 $this->children[$i]->__killParent(true);
00743             }
00744         }
00745     }
00746 }
00747 
00748 /**
00749  * Main XML class.
00750  *
00751  * Parses and stores XML data, representing the root of an XML document
00752  *
00753  * @package       cake
00754  * @subpackage    cake.cake.libs
00755  * @since         CakePHP v .0.10.3.1400
00756  */
00757 class Xml extends XmlNode {
00758 
00759 /**
00760  * Resource handle to XML parser.
00761  *
00762  * @var resource
00763  * @access private
00764  */
00765     var $__parser;
00766 /**
00767  * File handle to XML indata file.
00768  *
00769  * @var resource
00770  * @access private
00771  */
00772     var $__file;
00773 /**
00774  * Raw XML string data (for loading purposes)
00775  *
00776  * @var string
00777  * @access private
00778  */
00779     var $__rawData = null;
00780 
00781 /**
00782  * XML document header
00783  *
00784  * @var string
00785  * @access private
00786  */
00787     var $__header = null;
00788 
00789 /**
00790  * Default array keys/object properties to use as tag names when converting objects or array
00791  * structures to XML. Set by passing $options['tags'] to this object's constructor.
00792  *
00793  * @var array
00794  * @access private
00795  */
00796     var $__tags = array();
00797 
00798 /**
00799  * XML document version
00800  *
00801  * @var string
00802  * @access private
00803  */
00804     var $version = '1.0';
00805 
00806 /**
00807  * XML document encoding
00808  *
00809  * @var string
00810  * @access private
00811  */
00812     var $encoding = 'UTF-8';
00813 
00814 /**
00815  * Constructor.  Sets up the XML parser with options, gives it this object as
00816  * its XML object, and sets some variables.
00817  *
00818  * @param mixed $input The content with which this XML document should be initialized.  Can be a
00819  *                     string, array or object.  If a string is specified, it may be a literal XML
00820  *                     document, or a URL or file path to read from.
00821  * @param array $options Options to set up with, valid options are as follows:
00822  *                      - 'root': The name of the root element, defaults to '#document'
00823  *                      - 'version': The XML version, defaults to '1.0'
00824  *                      - 'encoding': Document encoding, defaults to 'UTF-8'
00825  *                      - 'namespaces': An array of namespaces (as strings) used in this document
00826  *                      - 'format': Specifies the format this document converts to when parsed or
00827  *                         rendered out as text, either 'attributes' or 'tags',
00828  *                         defaults to 'attributes'
00829  *                       - 'tags': An array specifying any tag-specific formatting options, indexed
00830  *                         by tag name.  See XmlNode::normalize().
00831  * @see XmlNode::normalize()
00832  */
00833     function __construct($input = null, $options = array()) {
00834         $defaults = array(
00835             'root' => '#document', 'tags' => array(), 'namespaces' => array(),
00836             'version' => '1.0', 'encoding' => 'UTF-8', 'format' => 'attributes'
00837         );
00838         $options = array_merge($defaults, Xml::options(), $options);
00839 
00840         foreach (array('version', 'encoding', 'namespaces') as $key) {
00841             $this->{$key} = $options[$key];
00842         }
00843         $this->__tags = $options['tags'];
00844         parent::__construct('#document');
00845 
00846         if ($options['root'] !== '#document') {
00847             $Root = $this->createNode($options['root']);
00848         } else {
00849             $Root =& $this;
00850         }
00851 
00852         if (!empty($input)) {
00853             if (is_string($input)) {
00854                 $Root->load($input);
00855             } elseif (is_array($input) || is_object($input)) {
00856                 $Root->append($input, $options);
00857             }
00858         }
00859         // if (Configure::read('App.encoding') !== null) {
00860         //  $this->encoding = Configure::read('App.encoding');
00861         // }
00862     }
00863 /**
00864  * Initialize XML object from a given XML string. Returns false on error.
00865  *
00866  * @param string $input XML string, a path to a file, or an HTTP resource to load
00867  * @return boolean Success
00868  * @access public
00869  */
00870     function load($input) {
00871         if (!is_string($input)) {
00872             return false;
00873         }
00874         $this->__rawData = null;
00875         $this->__header = null;
00876 
00877         if (strstr($input, "<")) {
00878             $this->__rawData = $input;
00879         } elseif (strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) {
00880             App::import('Core', 'HttpSocket');
00881             $socket = new HttpSocket();
00882             $this->__rawData = $socket->get($input);
00883         } elseif (file_exists($input)) {
00884             $this->__rawData = file_get_contents($input);
00885         } else {
00886             trigger_error('XML cannot be read');
00887             return false;
00888         }
00889         return $this->parse();
00890     }
00891 /**
00892  * Parses and creates XML nodes from the __rawData property.
00893  *
00894  * @return boolean Success
00895  * @access public
00896  * @see Xml::load()
00897  * @todo figure out how to link attributes and namespaces
00898  */
00899     function parse() {
00900         $this->__initParser();
00901         $this->__rawData = trim($this->__rawData);
00902         $this->__header = trim(str_replace(
00903             a('<' . '?', '?' . '>'),
00904             a('', ''),
00905             substr($this->__rawData, 0, strpos($this->__rawData, '?' . '>'))
00906         ));
00907 
00908         xml_parse_into_struct($this->__parser, $this->__rawData, $vals);
00909         $xml =& $this;
00910         $count = count($vals);
00911 
00912         for ($i = 0; $i < $count; $i++) {
00913             $data = $vals[$i];
00914             $data += array('tag' => null, 'value' => null, 'attributes' => array());
00915 
00916             switch ($data['type']) {
00917                 case "open" :
00918                     $xml =& $xml->createElement($data['tag'], $data['value'], $data['attributes']);
00919                 break;
00920                 case "close" :
00921                     $xml =& $xml->parent();
00922                 break;
00923                 case "complete" :
00924                     $xml->createElement($data['tag'], $data['value'], $data['attributes']);
00925                 break;
00926                 case 'cdata':
00927                     $xml->createTextNode($data['value']);
00928                 break;
00929             }
00930         }
00931         return true;
00932     }
00933 /**
00934  * Initializes the XML parser resource
00935  *
00936  * @return void
00937  * @access private
00938  */
00939     function __initParser() {
00940         if (empty($this->__parser)) {
00941             $this->__parser = xml_parser_create();
00942             xml_set_object($this->__parser, $this);
00943             xml_parser_set_option($this->__parser, XML_OPTION_CASE_FOLDING, 0);
00944             xml_parser_set_option($this->__parser, XML_OPTION_SKIP_WHITE, 1);
00945         }
00946     }
00947 /**
00948  * Returns a string representation of the XML object
00949  *
00950  * @param mixed $options If boolean: whether to include the XML header with the document
00951  *        (defaults to true); if an array, overrides the default XML generation options
00952  * @return string XML data
00953  * @access public
00954  * @deprecated
00955  * @see Xml::toString()
00956  */
00957     function compose($options = array()) {
00958         return $this->toString($options);
00959     }
00960 /**
00961  * If debug mode is on, this method echoes an error message.
00962  *
00963  * @param string $msg Error message
00964  * @param integer $code Error code
00965  * @param integer $line Line in file
00966  * @access public
00967  */
00968     function error($msg, $code = 0, $line = 0) {
00969         if (Configure::read('debug')) {
00970             echo $msg . " " . $code . " " . $line;
00971         }
00972     }
00973 /**
00974  * Returns a string with a textual description of the error code, or FALSE if no description was found.
00975  *
00976  * @param integer $code Error code
00977  * @return string Error message
00978  * @access public
00979  */
00980     function getError($code) {
00981         $r = @xml_error_string($code);
00982         return $r;
00983     }
00984 
00985 // Overridden functions from superclass
00986 
00987 /**
00988  * Get next element. NOT implemented.
00989  *
00990  * @return object
00991  * @access public
00992  */
00993     function &next() {
00994         $return = null;
00995         return $return;
00996     }
00997 /**
00998  * Get previous element. NOT implemented.
00999  *
01000  * @return object
01001  * @access public
01002  */
01003     function &previous() {
01004         $return = null;
01005         return $return;
01006     }
01007 /**
01008  * Get parent element. NOT implemented.
01009  *
01010  * @return object
01011  * @access public
01012  */
01013     function &parent() {
01014         $return = null;
01015         return $return;
01016     }
01017 /**
01018  * Adds a namespace to the current document
01019  *
01020  * @param string $prefix The namespace prefix
01021  * @param string $url The namespace DTD URL
01022  * @return void
01023  */
01024     function addNamespace($prefix, $url) {
01025         if ($count = count($this->children)) {
01026             for ($i = 0; $i < $count; $i++) {
01027                 $this->children[$i]->addNamespace($prefix, $url);
01028             }
01029             return true;
01030         }
01031         return parent::addNamespace($prefix, $url);
01032     }
01033 /**
01034  * Removes a namespace to the current document
01035  *
01036  * @param string $prefix The namespace prefix
01037  * @return void
01038  */
01039     function removeNamespace($prefix) {
01040         if ($count = count($this->children)) {
01041             for ($i = 0; $i < $count; $i++) {
01042                 $this->children[$i]->removeNamespace($prefix);
01043             }
01044             return true;
01045         }
01046         return parent::removeNamespace($prefix);
01047     }
01048 /**
01049  * Return string representation of current object.
01050  *
01051  * @return string String representation
01052  * @access public
01053  */
01054     function toString($options = array()) {
01055         if (is_bool($options)) {
01056             $options = array('header' => $options);
01057         }
01058 
01059         $defaults = array('header' => false, 'encoding' => $this->encoding);
01060         $options = array_merge($defaults, Xml::options(), $options);
01061         $data = parent::toString($options, 0);
01062 
01063         if ($options['header']) {
01064             if (!empty($this->__header)) {
01065                 return $this->header($this->__header)  . "\n" . $data;
01066             }
01067             return $this->header()  . "\n" . $data;
01068         }
01069 
01070         return $data;
01071     }
01072 /**
01073  * Return a header used on the first line of the xml file
01074  *
01075  * @param  mixed  $attrib attributes of the header element
01076  * @return string formated header
01077  */
01078     function header($attrib = array()) {
01079         $header = 'xml';
01080         if (is_string($attrib)) {
01081             $header = $attrib;
01082         } else {
01083 
01084             $attrib = array_merge(array('version' => $this->version, 'encoding' => $this->encoding), $attrib);
01085             foreach ($attrib as $key=>$val) {
01086                 $header .= ' ' . $key . '="' . $val . '"';
01087             }
01088         }
01089         return '<' . '?' . $header . ' ?' . '>';
01090     }
01091 
01092 /**
01093  * Destructor, used to free resources.
01094  *
01095  * @access private
01096  */
01097     function __destruct() {
01098         if (is_resource($this->__parser)) {
01099             xml_parser_free($this->__parser);
01100         }
01101     }
01102 /**
01103  * Adds a namespace to any XML documents generated or parsed
01104  *
01105  * @param  string  $name The namespace name
01106  * @param  string  $url  The namespace URI; can be empty if in the default namespace map
01107  * @return boolean False if no URL is specified, and the namespace does not exist
01108  *                 default namespace map, otherwise true
01109  * @access public
01110  * @static
01111  */
01112     function addGlobalNs($name, $url = null) {
01113         $_this =& XmlManager::getInstance();
01114         if ($ns = Xml::resolveNamespace($name, $url)) {
01115             $_this->namespaces = array_merge($_this->namespaces, $ns);
01116             return $ns;
01117         }
01118         return false;
01119     }
01120 /**
01121  * Resolves current namespace
01122  *
01123  * @param  string  $name
01124  * @param  string  $url
01125  * @return array
01126  */
01127     function resolveNamespace($name, $url) {
01128         $_this =& XmlManager::getInstance();
01129         if ($url == null && isset($_this->defaultNamespaceMap[$name])) {
01130             $url = $_this->defaultNamespaceMap[$name];
01131         } elseif ($url == null) {
01132             return false;
01133         }
01134 
01135         if (!strpos($url, '://') && isset($_this->defaultNamespaceMap[$name])) {
01136             $_url = $_this->defaultNamespaceMap[$name];
01137             $name = $url;
01138             $url = $_url;
01139         }
01140         return array($name => $url);
01141     }
01142 /**
01143  * Alias to Xml::addNs
01144  *
01145  * @access public
01146  * @static
01147  */
01148     function addGlobalNamespace($name, $url = null) {
01149         return Xml::addGlobalNs($name, $url);
01150     }
01151 /**
01152  * Removes a namespace added in addNs()
01153  *
01154  * @param  string  $name The namespace name or URI
01155  * @access public
01156  * @static
01157  */
01158     function removeGlobalNs($name) {
01159         $_this =& XmlManager::getInstance();
01160         if (isset($_this->namespaces[$name])) {
01161             unset($_this->namespaces[$name]);
01162             unset($this->namespaces[$name]);
01163             return true;
01164         } elseif (in_array($name, $_this->namespaces)) {
01165             $keys = array_keys($_this->namespaces);
01166             $count = count($keys);
01167             for ($i = 0; $i < $count; $i++) {
01168                 if ($_this->namespaces[$keys[$i]] == $name) {
01169                     unset($_this->namespaces[$keys[$i]]);
01170                     unset($this->namespaces[$keys[$i]]);
01171                     return true;
01172                 }
01173             }
01174         }
01175         return false;
01176     }
01177 /**
01178  * Alias to Xml::removeNs
01179  *
01180  * @access public
01181  * @static
01182  */
01183     function removeGlobalNamespace($name) {
01184         return Xml::removeGlobalNs($name);
01185     }
01186 /**
01187  * Sets/gets global XML options
01188  *
01189  * @param array $options
01190  * @return array
01191  * @access public
01192  * @static
01193  */
01194     function options($options = array()) {
01195         $_this =& XmlManager::getInstance();
01196         $_this->options = array_merge($_this->options, $options);
01197         return $_this->options;
01198     }
01199 }
01200 /**
01201  * The XML Element
01202  *
01203  */
01204 class XmlElement extends XmlNode {
01205 /**
01206  * Construct an Xml element
01207  *
01208  * @param  string  $name name of the node
01209  * @param  string  $value value of the node
01210  * @param  array  $attributes
01211  * @param  string  $namespace
01212  * @return string A copy of $data in XML format
01213  */
01214     function __construct($name = null, $value = null, $attributes = array(), $namespace = false) {
01215         parent::__construct($name, $value, $namespace);
01216         $this->addAttribute($attributes);
01217     }
01218 /**
01219  * Get all the attributes for this element
01220  *
01221  * @return array
01222  */
01223     function attributes() {
01224         return $this->attributes;
01225     }
01226 /**
01227  * Add attributes to this element
01228  *
01229  * @param  string  $name name of the node
01230  * @param  string  $value value of the node
01231  * @return boolean
01232  */
01233     function addAttribute($name, $val = null) {
01234         if (is_object($name)) {
01235             $name = get_object_vars($name);
01236         }
01237         if (is_array($name)) {
01238             foreach ($name as $key => $val) {
01239                 $this->addAttribute($key, $val);
01240             }
01241             return true;
01242         }
01243         if (is_numeric($name)) {
01244             $name = $val;
01245             $val = null;
01246         }
01247         if (!empty($name)) {
01248             if (strpos($name, 'xmlns') === 0) {
01249                 if ($name == 'xmlns') {
01250                     $this->namespace = $val;
01251                 } else {
01252                     list($pre, $prefix) = explode(':', $name);
01253                     $this->addNamespace($prefix, $val);
01254                     return true;
01255                 }
01256             }
01257             $this->attributes[$name] = $val;
01258             return true;
01259         }
01260         return false;
01261     }
01262 /**
01263  * Remove attributes to this element
01264  *
01265  * @param  string  $name name of the node
01266  * @return boolean
01267  */
01268     function removeAttribute($attr) {
01269         if (array_key_exists($attr, $this->attributes)) {
01270             unset($this->attributes[$attr]);
01271             return true;
01272         }
01273         return false;
01274     }
01275 }
01276 
01277 /**
01278  * XML text or CDATA node
01279  *
01280  * Stores XML text data according to the encoding of the parent document
01281  *
01282  * @package       cake
01283  * @subpackage    cake.cake.libs
01284  * @since         CakePHP v .1.2.6000
01285  */
01286 class XmlTextNode extends XmlNode {
01287 /**
01288  * Harcoded XML node name, represents this object as a text node
01289  *
01290  * @var string
01291  */
01292     var $name = '#text';
01293 /**
01294  * The text/data value which this node contains
01295  *
01296  * @var string
01297  */
01298     var $value = null;
01299 /**
01300  * Construct text node with the given parent object and data
01301  *
01302  * @param object $parent Parent XmlNode/XmlElement object
01303  * @param mixed $value Node value
01304  */
01305     function __construct($value = null) {
01306         $this->value = $value;
01307     }
01308 /**
01309  * Looks for child nodes in this element
01310  *
01311  * @return boolean False - not supported
01312  */
01313     function hasChildren() {
01314         return false;
01315     }
01316 /**
01317  * Append an XML node: XmlTextNode does not support this operation
01318  *
01319  * @return boolean False - not supported
01320  * @todo make convertEntities work without mb support, convert entities to number entities
01321  */
01322     function append() {
01323         return false;
01324     }
01325 /**
01326  * Return string representation of current text node object.
01327  *
01328  * @return string String representation
01329  * @access public
01330  */
01331     function toString($options = array(), $depth = 0) {
01332         if (is_int($options)) {
01333             $depth = $options;
01334             $options = array();
01335         }
01336 
01337         $defaults = array('cdata' => true, 'whitespace' => false, 'convertEntities' => false);
01338         $options = array_merge($defaults, Xml::options(), $options);
01339         $val = $this->value;
01340 
01341         if ($options['convertEntities'] && function_exists('mb_convert_encoding')) {
01342             $val = mb_convert_encoding($val,'UTF-8', 'HTML-ENTITIES');
01343         }
01344 
01345         if ($options['cdata'] === true && !is_numeric($val)) {
01346             $val = '<![CDATA[' . $val . ']]>';
01347         }
01348 
01349         if ($options['whitespace']) {
01350             return str_repeat("\t", $depth) . $val . "\n";
01351         }
01352         return $val;
01353     }
01354 }
01355 /**
01356  * Manages application-wide namespaces and XML parsing/generation settings.
01357  * Private class, used exclusively within scope of XML class.
01358  *
01359  * @access private
01360  */
01361 class XmlManager {
01362 
01363 /**
01364  * Global XML namespaces.  Used in all XML documents processed by this application
01365  *
01366  * @var array
01367  * @access public
01368  */
01369     var $namespaces = array();
01370 /**
01371  * Global XML document parsing/generation settings.
01372  *
01373  * @var array
01374  * @access public
01375  */
01376     var $options = array();
01377 /**
01378  * Map of common namespace URIs
01379  *
01380  * @access private
01381  * @var array
01382  */
01383     var $defaultNamespaceMap = array(
01384         'dc'     => 'http://purl.org/dc/elements/1.1/',                 // Dublin Core
01385         'dct'    => 'http://purl.org/dc/terms/',                        // Dublin Core Terms
01386         'g'         => 'http://base.google.com/ns/1.0',                 // Google Base
01387         'rc'        => 'http://purl.org/rss/1.0/modules/content/',      // RSS 1.0 Content Module
01388         'wf'        => 'http://wellformedweb.org/CommentAPI/',          // Well-Formed Web Comment API
01389         'fb'        => 'http://rssnamespace.org/feedburner/ext/1.0',    // FeedBurner extensions
01390         'lj'        => 'http://www.livejournal.org/rss/lj/1.0/',        // Live Journal
01391         'itunes'    => 'http://www.itunes.com/dtds/podcast-1.0.dtd',    // iTunes
01392         'xhtml'     => 'http://www.w3.org/1999/xhtml',                  // XHTML,
01393         'atom'      => 'http://www.w3.org/2005/Atom'                    // Atom
01394     );
01395 /**
01396  * Returns a reference to the global XML object that manages app-wide XML settings
01397  *
01398  * @return object
01399  * @access public
01400  */
01401     function &getInstance() {
01402         static $instance = array();
01403 
01404         if (!$instance) {
01405             $instance[0] =& new XmlManager();
01406         }
01407         return $instance[0];
01408     }
01409 }
01410 ?>

Generated on Sun Nov 22 00:30:54 2009 for CakePHP 1.2.x.x (v1.2.4.8284) by doxygen 1.4.7