From 294bef6c466725518e0498f805022510dd546392 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 26 Jul 2010 21:31:39 -0300 Subject: [PATCH 01/37] Refactory of Xml class. Now uses SimpleXMLElement to manipulate XML data. --- cake/libs/xml.php | 1478 ++------------------------- cake/tests/cases/libs/xml.test.php | 1534 ++++------------------------ 2 files changed, 289 insertions(+), 2723 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index a2c14c8b3..22b7bd184 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -4,7 +4,7 @@ * * The methods in these classes enable the datasources that use XML to work. * - * PHP versions 4 and 5 + * PHP 5 * * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) @@ -19,1421 +19,117 @@ * @since CakePHP v .0.10.3.1400 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -App::import('Core', 'Set'); -/** - * XML node. - * - * Single XML node in an XML tree. - * - * @package cake - * @subpackage cake.cake.libs - * @since CakePHP v .0.10.3.1400 - */ -class XmlNode extends Object { - -/** - * Name of node - * - * @var string - * @access public - */ - public $name = null; - -/** - * Node namespace - * - * @var string - * @access public - */ - public $namespace = null; - -/** - * Namespaces defined for this node and all child nodes - * - * @var array - * @access public - */ - public $namespaces = array(); - -/** - * Value of node - * - * @var string - * @access public - */ - public $value; - -/** - * Attributes on this node - * - * @var array - * @access public - */ - public $attributes = array(); - -/** - * This node's children - * - * @var array - * @access public - */ - public $children = array(); - -/** - * Reference to parent node. - * - * @var XmlNode - * @access private - */ - private $__parent = null; - -/** - * Constructor. - * - * @param string $name Node name - * @param array $attributes Node attributes - * @param mixed $value Node contents (text) - * @param array $children Node children - */ - function __construct($name = null, $value = null, $namespace = null) { - if (strpos($name, ':') !== false) { - list($prefix, $name) = explode(':', $name); - if (!$namespace) { - $namespace = $prefix; - } - } - $this->name = $name; - if ($namespace) { - $this->namespace = $namespace; - } - - if (is_array($value) || is_object($value)) { - $this->normalize($value); - } elseif (!empty($value) || $value === 0 || $value === '0') { - $this->createTextNode($value); - } - } -/** - * Adds a namespace to the current node - * - * @param string $prefix The namespace prefix - * @param string $url The namespace DTD URL - * @return void - */ - function addNamespace($prefix, $url) { - if ($ns = Xml::addGlobalNs($prefix, $url)) { - $this->namespaces = array_merge($this->namespaces, $ns); - return true; - } - return false; - } - -/** - * Adds a namespace to the current node - * - * @param string $prefix The namespace prefix - * @param string $url The namespace DTD URL - * @return void - */ - function removeNamespace($prefix) { - if (Xml::removeGlobalNs($prefix)) { - return true; - } - return false; - } - -/** - * Creates an XmlNode object that can be appended to this document or a node in it - * - * @param string $name Node name - * @param string $value Node value - * @param string $namespace Node namespace - * @return object XmlNode - */ - function &createNode($name = null, $value = null, $namespace = false) { - $node =& new XmlNode($name, $value, $namespace); - $node->setParent($this); - return $node; - } - -/** - * Creates an XmlElement object that can be appended to this document or a node in it - * - * @param string $name Element name - * @param string $value Element value - * @param array $attributes Element attributes - * @param string $namespace Node namespace - * @return object XmlElement - */ - function &createElement($name = null, $value = null, $attributes = array(), $namespace = false) { - $element =& new XmlElement($name, $value, $attributes, $namespace); - $element->setParent($this); - return $element; - } - -/** - * Creates an XmlTextNode object that can be appended to this document or a node in it - * - * @param string $value Node value - * @return object XmlTextNode - */ - function &createTextNode($value = null) { - $node = new XmlTextNode($value); - $node->setParent($this); - return $node; - } - -/** - * Gets the XML element properties from an object. - * - * @param object $object Object to get properties from - * @return array Properties from object - */ - public function normalize($object, $keyName = null, $options = array()) { - if (is_a($object, 'XmlNode')) { - return $object; - } - $name = null; - $options += array('format' => 'attributes'); - - if ($keyName !== null && !is_numeric($keyName)) { - $name = $keyName; - } elseif (!empty($object->_name_)) { - $name = $object->_name_; - } elseif (isset($object->name)) { - $name = $object->name; - } elseif ($options['format'] == 'attributes') { - $name = get_class($object); - } - - $tagOpts = $this->__tagOptions($name); - - if ($tagOpts === false) { - return; - } - - if (isset($tagOpts['name'])) { - $name = $tagOpts['name']; - } elseif ($name != strtolower($name)) { - $name = Inflector::slug(Inflector::underscore($name)); - } - - if (!empty($name)) { - $node =& $this->createElement($name); - } else { - $node =& $this; - } - - $namespace = array(); - $attributes = array(); - $children = array(); - $chldObjs = array(); - - if (is_object($object)) { - $chldObjs = get_object_vars($object); - } elseif (is_array($object)) { - $chldObjs = $object; - } elseif (!empty($object) || $object === 0 || $object === '0') { - $node->createTextNode($object); - } - $attr = array(); - - if (isset($tagOpts['attributes'])) { - $attr = $tagOpts['attributes']; - } - if (isset($tagOpts['value']) && isset($chldObjs[$tagOpts['value']])) { - $node->createTextNode($chldObjs[$tagOpts['value']]); - unset($chldObjs[$tagOpts['value']]); - } - - $n = $name; - if (isset($chldObjs['_name_'])) { - $n = null; - unset($chldObjs['_name_']); - } - $c = 0; - - foreach ($chldObjs as $key => $val) { - if (in_array($key, $attr) && !is_object($val) && !is_array($val)) { - $attributes[$key] = $val; - } else { - if (!isset($tagOpts['children']) || $tagOpts['children'] === array() || (is_array($tagOpts['children']) && in_array($key, $tagOpts['children']))) { - if (!is_numeric($key)) { - $n = $key; - } - if (is_array($val)) { - foreach ($val as $n2 => $obj2) { - if (is_numeric($n2)) { - $n2 = $n; - } - $node->normalize($obj2, $n2, $options); - } - } else { - if (is_object($val)) { - - $node->normalize($val, $n, $options); - } elseif ($options['format'] == 'tags' && $this->__tagOptions($key) !== false) { - $tmp =& $node->createElement($key); - if (!empty($val) || $val === 0 || $val === '0') { - $tmp->createTextNode($val); - } - } elseif ($options['format'] == 'attributes') { - $node->addAttribute($key, $val); - } - } - } - } - $c++; - } - if (!empty($name)) { - return $node; - } - return $children; - } - -/** - * Gets the tag-specific options for the given node name - * - * @param string $name XML tag name - * @param string $option The specific option to query. Omit for all options - * @return mixed A specific option value if $option is specified, otherwise an array of all options - * @access private - */ - function __tagOptions($name, $option = null) { - if (isset($this->_tags[$name])) { - $tagOpts = $this->_tags[$name]; - } elseif (isset($this->_tags[strtolower($name)])) { - $tagOpts = $this->_tags[strtolower($name)]; - } else { - return null; - } - if ($tagOpts === false) { - return false; - } - if (empty($option)) { - return $tagOpts; - } - if (isset($tagOpts[$option])) { - return $tagOpts[$option]; - } - return null; - } - -/** - * Returns the fully-qualified XML node name, with namespace - * - */ - public function name() { - if (!empty($this->namespace)) { - $_this =& XmlManager::getInstance(); - if (!isset($_this->options['verifyNs']) || !$_this->options['verifyNs'] || in_array($this->namespace, array_keys($_this->namespaces))) { - return $this->namespace . ':' . $this->name; - } - } - return $this->name; - } - -/** - * Sets the parent node of this XmlNode. - * - */ - public function setParent(&$parent) { - if (strtolower(get_class($this)) == 'xml') { - return; - } - if (isset($this->__parent) && is_object($this->__parent)) { - if ($this->__parent->compare($parent)) { - return; - } - foreach ($this->__parent->children as $i => $child) { - if ($this->compare($child)) { - array_splice($this->__parent->children, $i, 1); - break; - } - } - } - if ($parent == null) { - unset($this->__parent); - } else { - $parent->children[] =& $this; - $this->__parent =& $parent; - } - } - -/** - * Returns a copy of self. - * - * @return object Cloned instance - */ - public function cloneNode() { - return clone($this); - } - -/** - * Compares $node to this XmlNode object - * - * @param object An XmlNode or subclass instance - * @return boolean True if the nodes match, false otherwise - */ - public function compare($node) { - $keys = array(get_object_vars($this), get_object_vars($node)); - return ($keys[0] === $keys[1]); - } - -/** - * Append given node as a child. - * - * @param object $child XmlNode with appended child - * @param array $options XML generator options for objects and arrays - * @return object A reference to the appended child node - */ - public function &append(&$child, $options = array()) { - if (empty($child)) { - $return = false; - return $return; - } - - if (is_object($child)) { - if ($this->compare($child)) { - trigger_error(__('Cannot append a node to itself.')); - $return = false; - return $return; - } - } else if (is_array($child)) { - $child = Set::map($child); - if (is_array($child)) { - if (!is_a(current($child), 'XmlNode')) { - foreach ($child as $i => $childNode) { - $child[$i] = $this->normalize($childNode, null, $options); - } - } else { - foreach ($child as $childNode) { - $this->append($childNode, $options); - } - } - return $child; - } - } else { - $attributes = array(); - if (func_num_args() >= 2) { - $attributes = func_get_arg(1); - } - $child =& $this->createNode($child, null, $attributes); - } - - $child = $this->normalize($child, null, $options); - - if (empty($child->namespace) && !empty($this->namespace)) { - $child->namespace = $this->namespace; - } - - if (is_a($child, 'XmlNode')) { - $child->setParent($this); - } - - return $child; - } - -/** - * Returns first child node, or null if empty. - * - * @return object First XmlNode - */ - public function &first() { - if (isset($this->children[0])) { - return $this->children[0]; - } else { - $return = null; - return $return; - } - } - -/** - * Returns last child node, or null if empty. - * - * @return object Last XmlNode - */ - public function &last() { - if (count($this->children) > 0) { - return $this->children[count($this->children) - 1]; - } else { - $return = null; - return $return; - } - } - -/** - * Returns child node with given ID. - * - * @param string $id Name of child node - * @return object Child XmlNode - */ - public function &child($id) { - $null = null; - - if (is_int($id)) { - if (isset($this->children[$id])) { - return $this->children[$id]; - } else { - return null; - } - } elseif (is_string($id)) { - for ($i = 0; $i < count($this->children); $i++) { - if ($this->children[$i]->name == $id) { - return $this->children[$i]; - } - } - } - return $null; - } - -/** - * Gets a list of childnodes with the given tag name. - * - * @param string $name Tag name of child nodes - * @return array An array of XmlNodes with the given tag name - */ - public function children($name) { - $nodes = array(); - $count = count($this->children); - for ($i = 0; $i < $count; $i++) { - if ($this->children[$i]->name == $name) { - $nodes[] =& $this->children[$i]; - } - } - return $nodes; - } - -/** - * Gets a reference to the next child node in the list of this node's parent. - * - * @return object A reference to the XmlNode object - */ - public function &nextSibling() { - $null = null; - $count = count($this->__parent->children); - for ($i = 0; $i < $count; $i++) { - if ($this->__parent->children[$i] == $this) { - if ($i >= $count - 1 || !isset($this->__parent->children[$i + 1])) { - return $null; - } - return $this->__parent->children[$i + 1]; - } - } - return $null; - } - -/** - * Gets a reference to the previous child node in the list of this node's parent. - * - * @return object A reference to the XmlNode object - */ - public function &previousSibling() { - $null = null; - $count = count($this->__parent->children); - for ($i = 0; $i < $count; $i++) { - if ($this->__parent->children[$i] == $this) { - if ($i == 0 || !isset($this->__parent->children[$i - 1])) { - return $null; - } - return $this->__parent->children[$i - 1]; - } - } - return $null; - } - -/** - * Returns parent node. - * - * @return object Parent XmlNode - */ - public function &parent() { - return $this->__parent; - } - -/** - * Returns the XML document to which this node belongs - * - * @return object Parent XML object - */ - public function &document() { - $document =& $this; - while (true) { - if (get_class($document) == 'Xml' || $document == null) { - break; - } - $document =& $document->parent(); - } - return $document; - } - -/** - * Returns true if this structure has child nodes. - * - * @return bool - */ - public function hasChildren() { - if (is_array($this->children) && !empty($this->children)) { - return true; - } - return false; - } - -/** - * Returns this XML structure as a string. - * - * @return string String representation of the XML structure. - */ - public function toString($options = array(), $depth = 0) { - if (is_int($options)) { - $depth = $options; - $options = array(); - } - $defaults = array('cdata' => true, 'whitespace' => false, 'convertEntities' => false, 'showEmpty' => true, 'leaveOpen' => false); - $options = array_merge($defaults, Xml::options(), $options); - $tag = !(strpos($this->name, '#') === 0); - $d = ''; - - if ($tag) { - if ($options['whitespace']) { - $d .= str_repeat("\t", $depth); - } - - $d .= '<' . $this->name(); - if (!empty($this->namespaces) > 0) { - foreach ($this->namespaces as $key => $val) { - $val = str_replace('"', '\"', $val); - $d .= ' xmlns:' . $key . '="' . $val . '"'; - } - } - - $parent =& $this->parent(); - if ($parent->name === '#document' && !empty($parent->namespaces)) { - foreach ($parent->namespaces as $key => $val) { - $val = str_replace('"', '\"', $val); - $d .= ' xmlns:' . $key . '="' . $val . '"'; - } - } - - if (is_array($this->attributes) && !empty($this->attributes)) { - foreach ($this->attributes as $key => $val) { - if (is_bool($val) && $val === false) { - $val = 0; - } - $d .= ' ' . $key . '="' . htmlspecialchars($val, ENT_QUOTES, Configure::read('App.encoding')) . '"'; - } - } - } - - if (!$this->hasChildren() && empty($this->value) && $this->value !== 0 && $tag) { - if (!$options['leaveOpen']) { - $d .= ' />'; - } - if ($options['whitespace']) { - $d .= "\n"; - } - } elseif ($tag || $this->hasChildren()) { - if ($tag) { - $d .= '>'; - } - if ($this->hasChildren()) { - if ($options['whitespace']) { - $d .= "\n"; - } - $count = count($this->children); - $cDepth = $depth + 1; - for ($i = 0; $i < $count; $i++) { - $d .= $this->children[$i]->toString($options, $cDepth); - } - if ($tag) { - if ($options['whitespace'] && $tag) { - $d .= str_repeat("\t", $depth); - } - if (!$options['leaveOpen']) { - $d .= 'name() . '>'; - } - if ($options['whitespace']) { - $d .= "\n"; - } - } - } - } - return $d; - } - -/** - * Return array representation of current object. - * - * @param boolean $camelize true will camelize child nodes, false will not alter node names - * @return array Array representation - */ - public function toArray($camelize = true) { - $out = $this->attributes; - $multi = null; - - foreach ($this->children as $child) { - $key = $camelize ? Inflector::camelize($child->name) : $child->name; - - if (is_a($child, 'XmlTextNode')) { - $out['value'] = $child->value; - continue; - } elseif (isset($child->children[0]) && is_a($child->children[0], 'XmlTextNode')) { - $value = $child->children[0]->value; - if ($child->attributes) { - $value = array_merge(array('value' => $value), $child->attributes); - } - if (isset($out[$child->name]) || isset($multi[$key])) { - if (!isset($multi[$key])) { - $multi[$key] = array($out[$child->name]); - unset($out[$child->name]); - } - $multi[$key][] = $value; - } else { - $out[$child->name] = $value; - } - continue; - } elseif (count($child->children) === 0 && $child->value == '') { - $value = $child->attributes; - if (isset($out[$key]) || isset($multi[$key])) { - if (!isset($multi[$key])) { - $multi[$key] = array($out[$key]); - //unset($out[$key]); - } - $multi[$key][] = $value; - } elseif (!empty($value)) { - $out[$key] = $value; - } else { - $out[$child->name] = $value; - } - continue; - } else { - $value = $child->toArray($camelize); - } - - if (!isset($out[$key])) { - $out[$key] = $value; - } else { - if (!is_array($out[$key]) || !isset($out[$key][0])) { - $out[$key] = array($out[$key]); - } - $out[$key][] = $value; - } - } - - if (isset($multi)) { - $out = array_merge($out, $multi); - } - return $out; - } - -/** - * Returns data from toString when this object is converted to a string. - * - * @return string String representation of this structure. - * @access private - */ - function __toString() { - return $this->toString(); - } - -/** - * Debug method. Deletes the parent. Also deletes this node's children, - * if given the $recursive parameter. - * - * @param boolean $recursive Recursively delete elements. - */ - protected function _killParent($recursive = true) { - unset($this->__parent, $this->_log); - if ($recursive && $this->hasChildren()) { - for ($i = 0; $i < count($this->children); $i++) { - $this->children[$i]->_killParent(true); - } - } - } -} - -/** - * Main XML class. - * - * Parses and stores XML data, representing the root of an XML document - * - * @package cake - * @subpackage cake.cake.libs - * @since CakePHP v .0.10.3.1400 - */ -class Xml extends XmlNode { - -/** - * Resource handle to XML parser. - * - * @var resource - * @access private - */ - private $__parser; - -/** - * File handle to XML indata file. - * - * @var resource - * @access private - */ - private $__file; - -/** - * Raw XML string data (for loading purposes) - * - * @var string - * @access private - */ - private $__rawData = null; - -/** - * XML document header - * - * @var string - * @access protected - */ - protected $_header = null; - -/** - * Default array keys/object properties to use as tag names when converting objects or array - * structures to XML. Set by passing $options['tags'] to this object's constructor. - * - * @var array - * @access protected - */ - protected $_tags = array(); - -/** - * XML document version - * - * @var string - * @access private - */ - public $version = '1.0'; - -/** - * XML document encoding - * - * @var string - * @access private - */ - public $encoding = 'UTF-8'; - -/** - * Constructor. Sets up the XML parser with options, gives it this object as - * its XML object, and sets some variables. - * - * ### Options - * - 'root': The name of the root element, defaults to '#document' - * - 'version': The XML version, defaults to '1.0' - * - 'encoding': Document encoding, defaults to 'UTF-8' - * - 'namespaces': An array of namespaces (as strings) used in this document - * - 'format': Specifies the format this document converts to when parsed or - * rendered out as text, either 'attributes' or 'tags', defaults to 'attributes' - * - 'tags': An array specifying any tag-specific formatting options, indexed - * by tag name. See XmlNode::normalize(). - * @param mixed $input The content with which this XML document should be initialized. Can be a - * string, array or object. If a string is specified, it may be a literal XML - * document, or a URL or file path to read from. - * @param array $options Options to set up with, for valid options see above: - * @see XmlNode::normalize() - */ - function __construct($input = null, $options = array()) { - $defaults = array( - 'root' => '#document', 'tags' => array(), 'namespaces' => array(), - 'version' => '1.0', 'encoding' => 'UTF-8', 'format' => 'attributes' - ); - $options = array_merge($defaults, Xml::options(), $options); - - foreach (array('version', 'encoding', 'namespaces') as $key) { - $this->{$key} = $options[$key]; - } - $this->_tags = $options['tags']; - parent::__construct('#document'); - - if ($options['root'] !== '#document') { - $Root =& $this->createNode($options['root']); - } else { - $Root =& $this; - } - - if (!empty($input)) { - if (is_string($input)) { - $Root->load($input); - } elseif (is_array($input) || is_object($input)) { - $Root->append($input, $options); - } - } - } +class Xml { /** * Initialize XML object from a given XML string. Returns false on error. * * @param string $input XML string, a path to a file, or an HTTP resource to load - * @return boolean Success + * @return object SimpleXMLElement */ - public function load($input) { - if (!is_string($input)) { - return false; + public static function build($input) { + if (is_array($input) || is_object($input)) { + return self::fromArray((array)$input); + } elseif (strstr($input, "<")) { + return new SimpleXMLElement($input); + } elseif (file_exists($input) || strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0 ) { + return new SimpleXMLElement($input, null, true); + } elseif (!is_string($input)) { + throw new Exception(__('Invalid input.')); } - $this->__rawData = null; - $this->_header = null; + throw new Exception(__('XML cannot be read.')); + } - if (strstr($input, "<")) { - $this->__rawData = $input; - } elseif (strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) { - App::import('Core', 'HttpSocket'); - $socket = new HttpSocket(); - $this->__rawData = $socket->get($input); - } elseif (file_exists($input)) { - $this->__rawData = file_get_contents($input); + public static function fromArray($input, $format = 'attribute') { + if (!is_array($input) || count($input) !== 1) { + throw new Exception(__('Invalid input.')); + } + $key = key($input); + if (is_integer($key)) { + throw new Exception(__('The key of input must be alphanumeric')); + } + if (is_array($input[$key])) { + $simpleXml = new SimpleXMLElement('<' . '?xml version="1.0"?' . '><' . $key . ' />'); + self::_fromArrayRecursive($simpleXml, $input[$key], $format); } else { - trigger_error(__('XML cannot be read')); - return false; + $simpleXml = new SimpleXMLElement('<' . '?xml version="1.0"?' . '><' . $key . '>' . $input[$key] . ''); } - return $this->parse(); + return $simpleXml; } -/** - * Parses and creates XML nodes from the __rawData property. - * - * @return boolean Success - * @access public - * @see Xml::load() - * @todo figure out how to link attributes and namespaces - */ - function parse() { - $this->__initParser(); - $this->__rawData = trim($this->__rawData); - $this->_header = trim(str_replace( - array('<' . '?', '?' . '>'), - array('', ''), - substr($this->__rawData, 0, strpos($this->__rawData, '?' . '>')) - )); - - xml_parse_into_struct($this->__parser, $this->__rawData, $vals); - $xml =& $this; - $count = count($vals); - - for ($i = 0; $i < $count; $i++) { - $data = $vals[$i]; - $data += array('tag' => null, 'value' => null, 'attributes' => array()); - switch ($data['type']) { - case "open" : - $xml =& $xml->createElement($data['tag'], $data['value'], $data['attributes']); - break; - case "close" : - $xml =& $xml->parent(); - break; - case "complete" : - $xml->createElement($data['tag'], $data['value'], $data['attributes']); - break; - case 'cdata': - $xml->createTextNode($data['value']); - break; - } + protected static function _fromArrayRecursive(&$node, &$array, $format = 'attribute') { + if (empty($array) || !is_array($array)) { + return; } - xml_parser_free($this->__parser); - $this->__parser = null; - return true; - } - -/** - * Initializes the XML parser resource - * - * @return void - * @access private - */ - function __initParser() { - if (empty($this->__parser)) { - $this->__parser = xml_parser_create(); - xml_set_object($this->__parser, $this); - xml_parser_set_option($this->__parser, XML_OPTION_CASE_FOLDING, 0); - xml_parser_set_option($this->__parser, XML_OPTION_SKIP_WHITE, 1); - } - } - -/** - * Returns a string representation of the XML object - * - * @param mixed $options If boolean: whether to include the XML header with the document - * (defaults to true); if an array, overrides the default XML generation options - * @return string XML data - * @access public - * @deprecated - * @see Xml::toString() - */ - function compose($options = array()) { - return $this->toString($options); - } - -/** - * If debug mode is on, this method echoes an error message. - * - * @param string $msg Error message - * @param integer $code Error code - * @param integer $line Line in file - */ - public function error($msg, $code = 0, $line = 0) { - if (Configure::read('debug')) { - echo $msg . " " . $code . " " . $line; - } - } - -/** - * Returns a string with a textual description of the error code, or FALSE if no description was found. - * - * @param integer $code Error code - * @return string Error message - */ - public function getError($code) { - $r = @xml_error_string($code); - return $r; - } - -// Overridden functions from superclass - -/** - * Get next element. NOT implemented. - * - * @return object - */ - public function &next() { - $return = null; - return $return; - } - -/** - * Get previous element. NOT implemented. - * - * @return object - */ - public function &previous() { - $return = null; - return $return; - } - -/** - * Get parent element. NOT implemented. - * - * @return object - */ - public function &parent() { - $return = null; - return $return; - } - -/** - * Adds a namespace to the current document - * - * @param string $prefix The namespace prefix - * @param string $url The namespace DTD URL - * @return void - */ - function addNamespace($prefix, $url) { - if ($count = count($this->children)) { - for ($i = 0; $i < $count; $i++) { - $this->children[$i]->addNamespace($prefix, $url); - } - return true; - } - return parent::addNamespace($prefix, $url); - } - -/** - * Removes a namespace to the current document - * - * @param string $prefix The namespace prefix - * @return void - */ - function removeNamespace($prefix) { - if ($count = count($this->children)) { - for ($i = 0; $i < $count; $i++) { - $this->children[$i]->removeNamespace($prefix); - } - return true; - } - return parent::removeNamespace($prefix); - } - -/** - * Return string representation of current object. - * - * @return string String representation - */ - public function toString($options = array()) { - if (is_bool($options)) { - $options = array('header' => $options); - } - - $defaults = array('header' => false, 'encoding' => $this->encoding); - $options = array_merge($defaults, Xml::options(), $options); - $data = parent::toString($options, 0); - - if ($options['header']) { - if (!empty($this->_header)) { - return $this->header($this->_header) . "\n" . $data; - } - return $this->header() . "\n" . $data; - } - - return $data; - } - -/** - * Return a header used on the first line of the xml file - * - * @param mixed $attrib attributes of the header element - * @return string formated header - */ - function header($attrib = array()) { - $header = 'xml'; - if (is_string($attrib)) { - $header = $attrib; - } else { - - $attrib = array_merge(array('version' => $this->version, 'encoding' => $this->encoding), $attrib); - foreach ($attrib as $key=>$val) { - $header .= ' ' . $key . '="' . $val . '"'; - } - } - return '<' . '?' . $header . ' ?' . '>'; - } - -/** - * Destructor, used to free resources. - * - * @access private - */ - function __destruct() { - $this->_killParent(true); - } - -/** - * Adds a namespace to any XML documents generated or parsed - * - * @param string $name The namespace name - * @param string $url The namespace URI; can be empty if in the default namespace map - * @return boolean False if no URL is specified, and the namespace does not exist - * default namespace map, otherwise true - * @access public - * @static - */ - function addGlobalNs($name, $url = null) { - $_this =& XmlManager::getInstance(); - if ($ns = Xml::resolveNamespace($name, $url)) { - $_this->namespaces = array_merge($_this->namespaces, $ns); - return $ns; - } - return false; - } - -/** - * Resolves current namespace - * - * @param string $name - * @param string $url - * @return array - */ - function resolveNamespace($name, $url) { - $_this =& XmlManager::getInstance(); - if ($url == null && isset($_this->defaultNamespaceMap[$name])) { - $url = $_this->defaultNamespaceMap[$name]; - } elseif ($url == null) { - return false; - } - - if (!strpos($url, '://') && isset($_this->defaultNamespaceMap[$name])) { - $_url = $_this->defaultNamespaceMap[$name]; - $name = $url; - $url = $_url; - } - return array($name => $url); - } - -/** - * Alias to Xml::addNs - * - * @access public - * @static - */ - function addGlobalNamespace($name, $url = null) { - return Xml::addGlobalNs($name, $url); - } - -/** - * Removes a namespace added in addNs() - * - * @param string $name The namespace name or URI - * @access public - * @static - */ - function removeGlobalNs($name) { - $_this =& XmlManager::getInstance(); - if (isset($_this->namespaces[$name])) { - unset($_this->namespaces[$name]); - unset($this->namespaces[$name]); - return true; - } elseif (in_array($name, $_this->namespaces)) { - $keys = array_keys($_this->namespaces); - $count = count($keys); - for ($i = 0; $i < $count; $i++) { - if ($_this->namespaces[$keys[$i]] == $name) { - unset($_this->namespaces[$keys[$i]]); - unset($this->namespaces[$keys[$i]]); - return true; - } - } - } - return false; - } - -/** - * Alias to Xml::removeNs - * - * @access public - * @static - */ - function removeGlobalNamespace($name) { - return Xml::removeGlobalNs($name); - } - -/** - * Sets/gets global XML options - * - * @param array $options - * @return array - * @access public - * @static - */ - function options($options = array()) { - $_this =& XmlManager::getInstance(); - $_this->options = array_merge($_this->options, $options); - return $_this->options; - } -} - -/** - * The XML Element - * - */ -class XmlElement extends XmlNode { - -/** - * Construct an Xml element - * - * @param string $name name of the node - * @param string $value value of the node - * @param array $attributes - * @param string $namespace - * @return string A copy of $data in XML format - */ - function __construct($name = null, $value = null, $attributes = array(), $namespace = false) { - parent::__construct($name, $value, $namespace); - $this->addAttribute($attributes); - } - -/** - * Get all the attributes for this element - * - * @return array - */ - function attributes() { - return $this->attributes; - } - -/** - * Add attributes to this element - * - * @param string $name name of the node - * @param string $value value of the node - * @return boolean - */ - function addAttribute($name, $val = null) { - if (is_object($name)) { - $name = get_object_vars($name); - } - if (is_array($name)) { - foreach ($name as $key => $val) { - $this->addAttribute($key, $val); - } - return true; - } - if (is_numeric($name)) { - $name = $val; - $val = null; - } - if (!empty($name)) { - if (strpos($name, 'xmlns') === 0) { - if ($name == 'xmlns') { - $this->namespace = $val; + foreach ($array as $key => $value) { + if (is_string($key)) { + if (!is_array($value)) { + if (is_bool($value)) { + $value = (int)$value; + } elseif ($value === null) { + $value = ''; + } + if ($format === 'tags') { + $node->addChild($key, $value); + } else { + $node->addAttribute($key, $value); + } } else { - list($pre, $prefix) = explode(':', $name); - $this->addNamespace($prefix, $val); - return true; + if (array_keys($value) === range(0, count($value) - 1)) { // List + foreach ($value as $item) { + $child = $node->addChild($key); + self::_fromArrayRecursive($child, $item, $format); + } + } else { // Struct + $child = $node->addChild($key); + self::_fromArrayRecursive($child, $value, $format); + } } + } else { + throw new Exception(__('Invalid array')); } - $this->attributes[$name] = $val; - return true; } - return false; } /** - * Remove attributes to this element + * Returns this XML structure as a array. * - * @param string $name name of the node - * @return boolean + * @return array Array representation of the XML structure. */ - function removeAttribute($attr) { - if (array_key_exists($attr, $this->attributes)) { - unset($this->attributes[$attr]); - return true; + public static function toArray($simpleXML) { + if (!($simpleXML instanceof SimpleXMLElement)) { + throw new Exception(__('The input is not instance of SimpleXMLElement.')); } - return false; - } -} - -/** - * XML text or CDATA node - * - * Stores XML text data according to the encoding of the parent document - * - * @package cake - * @subpackage cake.cake.libs - * @since CakePHP v .1.2.6000 - */ -class XmlTextNode extends XmlNode { - -/** - * Harcoded XML node name, represents this object as a text node - * - * @var string - */ - public $name = '#text'; - -/** - * The text/data value which this node contains - * - * @var string - */ - public $value = null; - -/** - * Construct text node with the given parent object and data - * - * @param object $parent Parent XmlNode/XmlElement object - * @param mixed $value Node value - */ - function __construct($value = null) { - $this->value = $value; + $result = array(); + self::_toArray($simpleXML, $result); + return $result; } -/** - * Looks for child nodes in this element - * - * @return boolean False - not supported - */ - function hasChildren() { - return false; + protected static function _toArray($xml, &$parentData) { + $data = array(); + + foreach ($xml->attributes() as $key => $value) { + $data[$key] = (string)$value; + } + + foreach ($xml->children() as $child) { + self::_toArray($child, $data); + } + + if (empty($data)) { + $data = (string)$xml; + } + + $name = $xml->getName(); + if (isset($parentData[$name])) { + if (!is_array($parentData[$name]) || !isset($parentData[$name][0])) { + $parentData[$name] = array($parentData[$name]); + } + $parentData[$name][] = $data; + } else { + $parentData[$name] = $data; + } } -/** - * Append an XML node: XmlTextNode does not support this operation - * - * @return boolean False - not supported - * @todo make convertEntities work without mb support, convert entities to number entities - */ - function append() { - return false; - } - -/** - * Return string representation of current text node object. - * - * @return string String representation - */ - public function toString($options = array(), $depth = 0) { - if (is_int($options)) { - $depth = $options; - $options = array(); - } - - $defaults = array('cdata' => true, 'whitespace' => false, 'convertEntities' => false); - $options = array_merge($defaults, Xml::options(), $options); - $val = $this->value; - - if ($options['convertEntities'] && function_exists('mb_convert_encoding')) { - $val = mb_convert_encoding($val,'UTF-8', 'HTML-ENTITIES'); - } - - if ($options['cdata'] === true && !is_numeric($val)) { - $val = ''; - } - - if ($options['whitespace']) { - return str_repeat("\t", $depth) . $val . "\n"; - } - return $val; - } -} - -/** - * Manages application-wide namespaces and XML parsing/generation settings. - * Private class, used exclusively within scope of XML class. - * - * @access private - */ -class XmlManager { - -/** - * Global XML namespaces. Used in all XML documents processed by this application - * - * @var array - * @access public - */ - public $namespaces = array(); - -/** - * Global XML document parsing/generation settings. - * - * @var array - * @access public - */ - public $options = array(); - -/** - * Map of common namespace URIs - * - * @access private - * @var array - */ - public $defaultNamespaceMap = array( - 'dc' => 'http://purl.org/dc/elements/1.1/', // Dublin Core - 'dct' => 'http://purl.org/dc/terms/', // Dublin Core Terms - 'g' => 'http://base.google.com/ns/1.0', // Google Base - 'rc' => 'http://purl.org/rss/1.0/modules/content/', // RSS 1.0 Content Module - 'wf' => 'http://wellformedweb.org/CommentAPI/', // Well-Formed Web Comment API - 'fb' => 'http://rssnamespace.org/feedburner/ext/1.0', // FeedBurner extensions - 'lj' => 'http://www.livejournal.org/rss/lj/1.0/', // Live Journal - 'itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd', // iTunes - 'xhtml' => 'http://www.w3.org/1999/xhtml', // XHTML, - 'atom' => 'http://www.w3.org/2005/Atom' // Atom - ); - -/** - * Returns a reference to the global XML object that manages app-wide XML settings - * - * @return object - */ - public function &getInstance() { - static $instance = array(); - - if (!$instance) { - $instance[0] =& new XmlManager(); - } - return $instance[0]; - } -} +} \ No newline at end of file diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 5a9cb87ed..94cc987ae 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -2,7 +2,7 @@ /** * XmlTest file * - * PHP versions 4 and 5 + * PHP 5 * * CakePHP(tm) Tests * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) @@ -19,24 +19,6 @@ */ App::import('Core', 'Xml'); -/** - * Test XML Class - * - * @package cake - * @subpackage cake.tests.cases.libs - */ -class TestXml extends Xml { - -/** - * Return the protected _header instance variable - * - * @return string Header - */ - public function getHeader() { - return $this->_header; - } -} - /** * XmlTest class * @@ -46,942 +28,196 @@ class TestXml extends Xml { class XmlTest extends CakeTestCase { /** - * setUp method + * testBuild method * * @access public * @return void */ - function setUp() { - $manager =& new XmlManager(); - $manager->namespaces = array(); + function testBuild() { + $xml = 'value'; + $obj = Xml::build($xml); + $this->assertTrue($obj instanceof SimpleXMLElement); + $this->assertEqual((string)$obj->getName(), 'tag'); + $this->assertEqual((string)$obj, 'value'); + + $xml = 'value'; + $this->assertEqual($obj, Xml::build($xml)); + + $xml = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'webroot' . DS . 'sample.xml'; + $obj = Xml::build($xml); + $this->assertEqual($obj->getName(), 'tags'); + $this->assertEqual(count($obj), 2); + + $this->assertEqual(Xml::build($xml), Xml::build(file_get_contents($xml))); + + $xml = array('tag' => 'value'); + $obj = Xml::build($xml); + $this->assertEqual($obj->getName(), 'tag'); + $this->assertEqual((string)$obj, 'value'); } /** - * testRootTagParsing method + * testBuildException method * * @access public * @return void */ - function testRootTagParsing() { - $input = '<' . '?xml version="1.0" encoding="UTF-8" ?' . '>' . "\n" . - '' - .'' - .'' - .''; - $xml = new Xml($input); - $this->assertEqual($xml->children[0]->name, 'plugin'); - $this->assertEqual($xml->children[0]->children[0]->name, 'current'); - $this->assertEqual($xml->toString(true), $input); + function testBuildException() { + try { + Xml::build(false); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } + + try { + Xml::build(''); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } + + try { + Xml::build(''); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } } /** - * testSerialization method + * testFromArray method * * @access public * @return void */ - function testSerialization() { - $input = array( - array( - 'Project' => array('id' => 1, 'title' => null, 'client_id' => 1, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 1, 'industry_id' => 1, 'modified' => null, 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 1, 'name' => 'Touch Screen Kiosk'), - 'Industry' => array('id' => 1, 'name' => 'Financial') - ), - array( - 'Project' => array('id' => 2, 'title' => null, 'client_id' => 2, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 2, 'industry_id' => 2, 'modified' => '2007-11-26 14:48:36', 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 2, 'name' => 'Awareness Campaign'), - 'Industry' => array('id' => 2, 'name' => 'Education') - ) - ); + function testFromArray() { + $xml = array('tag' => 'value'); + $obj = Xml::fromArray($xml); + $this->assertEqual($obj->getName(), 'tag'); + $this->assertEqual((string)$obj, 'value'); - $xml = new Xml($input); - $result = preg_replace("/\n/",'', $xml->toString(false)); - $expected = '1Touch Screen Kiosk1Financial2<client_id>2</client_id><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>2</job_type_id><industry_id>2</industry_id><modified>2007-11-26 14:48:36</modified><created /><style><id /><name /></style><job_type><id>2</id><name>Awareness Campaign</name></job_type><industry><id>2</id><name>Education</name></industry></project>'; - - $xml = new Xml($input, array('format' => 'tags')); - $result = $xml->toString(array('header' => false, 'cdata' => false)); - $this->assertEqual($expected, $result); - } - -/** - * testNestedArraySerialization method - * - * @access public - * @return void - */ - function testSerializationNestedArray() { - $input = array( - array( - 'Project' => array('id' => 1, 'title' => null, 'client_id' => 1, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 1, 'industry_id' => 1, 'modified' => null, 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 1, 'name' => 'Touch Screen Kiosk'), - 'Industry' => array('id' => 1, 'name' => 'Financial'), - 'BusinessSolution' => array(array('id' => 6, 'name' => 'Convert Sales')), - 'MediaType' => array( - array('id' => 15, 'name' => 'Print'), - array('id' => 7, 'name' => 'Web Demo'), - array('id' => 6, 'name' => 'CD-ROM') - ) - ), - array( - 'Project' => array('id' => 2, 'title' => null, 'client_id' => 2, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 2, 'industry_id' => 2, 'modified' => '2007-11-26 14:48:36', 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 2, 'name' => 'Awareness Campaign'), - 'Industry' => array('id' => 2, 'name' => 'Education'), - 'BusinessSolution' => array( - array('id' => 4, 'name' => 'Build Relationship'), - array('id' => 6, 'name' => 'Convert Sales') - ), - 'MediaType' => array( - array('id' => 17, 'name' => 'Web'), - array('id' => 6, 'name' => 'CD-ROM') + $xml = array( + 'tags' => array( + 'tag' => array( + array( + 'id' => '1', + 'name' => 'defect' + ), + array( + 'id' => '2', + 'name' => 'enhancement' + ) ) ) ); - $expected = '<project><id>1</id><title /><client_id>1</client_id><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>1</job_type_id><industry_id>1</industry_id><modified /><created /><style><id /><name /></style><job_type><id>1</id><name>Touch Screen Kiosk</name></job_type><industry><id>1</id><name>Financial</name></industry><business_solution><id>6</id><name>Convert Sales</name></business_solution><media_type><id>15</id><name>Print</name></media_type><media_type><id>7</id><name>Web Demo</name></media_type><media_type><id>6</id><name>CD-ROM</name></media_type></project><project><id>2</id><title /><client_id>2</client_id><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>2</job_type_id><industry_id>2</industry_id><modified>2007-11-26 14:48:36</modified><created /><style><id /><name /></style><job_type><id>2</id><name>Awareness Campaign</name></job_type><industry><id>2</id><name>Education</name></industry><business_solution><id>4</id><name>Build Relationship</name></business_solution><business_solution><id>6</id><name>Convert Sales</name></business_solution><media_type><id>17</id><name>Web</name></media_type><media_type><id>6</id><name>CD-ROM</name></media_type></project>'; + $obj = Xml::fromArray($xml); + $this->assertTrue($obj instanceof SimpleXMLElement); + $this->assertEqual($obj->getName(), 'tags'); + $this->assertEqual(count($obj), 2); + $xmlText = '<' . '?xml version="1.0"?><tags><tag id="1" name="defect"/><tag id="2" name="enhancement"/></tags>'; + $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); - $xml = new Xml($input, array('format' => 'tags')); - $result = $xml->toString(array('header' => false, 'cdata' => false)); - $this->assertEqual($expected, $result); - } + $obj = Xml::fromArray($xml, 'tags'); + $this->assertTrue($obj instanceof SimpleXMLElement); + $this->assertEqual($obj->getName(), 'tags'); + $this->assertEqual(count($obj), 2); + $xmlText = '<' . '?xml version="1.0"?><tags><tag><id>1</id><name>defect</name></tag><tag><id>2</id><name>enhancement</name></tag></tags>'; + $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); -/** - * Prove that serialization with a given root node works - * as expected. - * - * @access public - * @return void - * @link https://trac.cakephp.org/ticket/6294 - */ - function testArraySerializationWithRoot() { - $input = array( - array('Shirt' => array('id' => 1, 'color' => 'green')), - array('Shirt' => array('id' => 2, 'color' => 'blue')), - ); - $expected = '<collection><shirt id="1" color="green" />'; - $expected .= '<shirt id="2" color="blue" /></collection>'; - - $Xml = new Xml($input, array('root' => 'collection')); - $result = $Xml->toString(array('header' => false)); - $this->assertEqual($expected, $result); - } - -/** - * testCloneNode - * - * @access public - * @return void - */ - function testCloneNode() { - $node =& new XmlNode('element', 'myValue'); - $twin =& $node->cloneNode(); - $this->assertEqual($node, $twin); - } - -/** - * testNextSibling - * - * @access public - * @return void - */ - function testNextSibling() { - $input = array( - array( - 'Project' => array('id' => 1, 'title' => null, 'client_id' => 1, 'show' => '1', 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => '1.89', 'industry_id' => '1.56', 'modified' => null, 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 1, 'name' => 'Touch Screen Kiosk'), - 'Industry' => array('id' => 1, 'name' => 'Financial') - ), - array( - 'Project' => array('id' => 2, 'title' => null, 'client_id' => 2, 'show' => '1', 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => '2.2', 'industry_id' => 2.2, 'modified' => '2007-11-26 14:48:36', 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 2, 'name' => 'Awareness Campaign'), - 'Industry' => array('id' => 2, 'name' => 'Education'), + $xml = array( + 'tags' => array( ) ); - $xml =& new Xml($input, array('format' => 'tags')); - $node =& $xml->children[0]->children[0]; + $obj = Xml::fromArray($xml); + $this->assertEqual($obj->getName(), 'tags'); + $this->assertEqual((string)$obj, ''); - $nextSibling =& $node->nextSibling(); - $this->assertEqual($nextSibling, $xml->children[0]->children[1]); - - $nextSibling2 =& $nextSibling->nextSibling(); - $this->assertEqual($nextSibling2, $xml->children[0]->children[2]); - - $noFriends =& $xml->children[0]->children[12]; - $this->assertNull($noFriends->nextSibling()); - } - -/** - * testPreviousSibling - * - * @access public - * @return void - */ - function testPreviousSibling() { - $input = array( - array( - 'Project' => array('id' => 1, 'title' => null, 'client_id' => 1, 'show' => '1', 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => '1.89', 'industry_id' => '1.56', 'modified' => null, 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 1, 'name' => 'Touch Screen Kiosk'), - 'Industry' => array('id' => 1, 'name' => 'Financial') - ), - array( - 'Project' => array('id' => 2, 'title' => null, 'client_id' => 2, 'show' => '1', 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => '2.2', 'industry_id' => 2.2, 'modified' => '2007-11-26 14:48:36', 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 2, 'name' => 'Awareness Campaign'), - 'Industry' => array('id' => 2, 'name' => 'Education'), + $xml = array( + 'tags' => array( + 'bool' => true, + 'int' => 1, + 'float' => 10.2, + 'string' => 'ok', + 'null' => null, + 'array' => array() ) ); - $xml =& new Xml($input, array('format' => 'tags')); - $node =& $xml->children[0]->children[1]; - - $prevSibling =& $node->previousSibling(); - $this->assertEqual($prevSibling, $xml->children[0]->children[0]); - - $this->assertNull($prevSibling->previousSibling()); + $obj = Xml::fromArray($xml, 'tags'); + $this->assertEqual(count($obj), 6); + $this->assertIdentical((string)$obj->bool, '1'); + $this->assertIdentical((string)$obj->int, '1'); + $this->assertIdentical((string)$obj->float, '10.2'); + $this->assertIdentical((string)$obj->string, 'ok'); + $this->assertIdentical((string)$obj->null, ''); + $this->assertIdentical((string)$obj->array, ''); } /** - * testAddAndRemoveAttributes + * testFromArrayFail method * * @access public * @return void */ - function testAddAndRemoveAttributes() { - $node =& new XmlElement('myElement', 'superValue'); - $this->assertTrue(empty($node->attributes)); + function testFromArrayFail() { + try { + Xml::fromArray(false); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } - $attrs = array( - 'id' => 'test', - 'show' => 1, - 'is_spotlight' => 1, - ); - $node->addAttribute($attrs); - $this->assertEqual($node->attributes, $attrs); + try { + Xml::fromArray(array()); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } - $node =& new XmlElement('myElement', 'superValue'); - $node->addAttribute('test', 'value'); - $this->assertTrue(isset($node->attributes['test'])); + try { + Xml::fromArray(array('numeric key as root')); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } - $node =& new XmlElement('myElement', 'superValue'); - $obj =& new StdClass(); - $obj->class = 'info'; - $obj->id = 'primaryInfoBox'; - $node->addAttribute($obj); - $expected = array( - 'class' => 'info', - 'id' => 'primaryInfoBox', - ); - $this->assertEqual($node->attributes, $expected); + try { + Xml::fromArray(array('item1' => '', 'item2' => '')); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } - $result = $node->removeAttribute('class'); - $this->assertTrue($result); - $this->assertFalse(isset($node->attributes['class'])); + try { + Xml::fromArray(array('items' => array('item1', 'item2'))); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } - $result = $node->removeAttribute('missing'); - $this->assertFalse($result); - } - - /** - * Tests that XML documents with non-standard spacing (i.e. leading whitespace, whole document - * on one line) still parse properly. - * - * @return void - */ - function testParsingWithNonStandardWhitespace() { - $raw = '<?xml version="1.0" encoding="ISO-8859-1" ?><prices><price>1.0</price></prices>'; - $array = array('Prices' => array('price' => 1.0)); - - $xml = new TestXml($raw); - $this->assertEqual($xml->toArray(), $array); - $this->assertEqual($xml->getHeader(), 'xml version="1.0" encoding="ISO-8859-1"'); - - $xml = new TestXml(' ' . $raw); - $this->assertEqual($xml->toArray(), $array); - $this->assertEqual($xml->getHeader(), 'xml version="1.0" encoding="ISO-8859-1"'); - - $xml = new TestXml("\n" . $raw); - $this->assertEqual($xml->toArray(), $array); - $this->assertEqual($xml->getHeader(), 'xml version="1.0" encoding="ISO-8859-1"'); - } - - /* Not implemented yet */ - /* function testChildFilter() { - $input = array( - array( - 'Project' => array('id' => 1, 'title' => null, 'client_id' => 1, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 1, 'industry_id' => 1, 'modified' => null, 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 1, 'name' => 'Touch Screen Kiosk'), - 'Industry' => array('id' => 1, 'name' => 'Financial'), - 'BusinessSolution' => array(array('id' => 6, 'name' => 'Convert Sales')), - 'MediaType' => array( - array('id' => 15, 'name' => 'Print'), - array('id' => 7, 'name' => 'Web Demo'), - array('id' => 6, 'name' => 'CD-ROM') - ) - ), - array( - 'Project' => array('id' => 2, 'title' => null, 'client_id' => 2, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 2, 'industry_id' => 2, 'modified' => '2007-11-26 14:48:36', 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 2, 'name' => 'Awareness Campaign'), - 'Industry' => array('id' => 2, 'name' => 'Education'), - 'BusinessSolution' => array( - array('id' => 4, 'name' => 'Build Relationship'), - array('id' => 6, 'name' => 'Convert Sales') - ), - 'MediaType' => array( - array('id' => 17, 'name' => 'Web'), - array('id' => 6, 'name' => 'CD-ROM') - ) - ) - ); - - $xml = new Xml($input, array('format' => 'tags', 'tags' => array( - 'MediaType' => array('value' => 'id', 'children' => false), - 'JobType' => array('children' => array()), - 'Industry' => array('children' => array('name')), - 'show' => false - ))); - - $result = $xml->toString(array('header' => false, 'cdata' => false)); - $expected = '<project><id>1</id><title /><client_id>1</client_id><is_spotlight /><style_id>0</style_id><job_type_id>1</job_type_id><industry_id>1</industry_id><modified /><created /><style><id /><name /></style><job_type><id>1</id><name>Touch Screen Kiosk</name></job_type><industry><name>Financial</name></industry><business_solution><id>6</id><name>Convert Sales</name></business_solution><media_type>15</media_type><media_type>7</media_type><media_type>6</media_type></project><project><id>2</id><title /><client_id>2</client_id><is_spotlight /><style_id>0</style_id><job_type_id>2</job_type_id><industry_id>2</industry_id><modified>2007-11-26 14:48:36</modified><created /><style><id /><name /></style><job_type><id>2</id><name>Awareness Campaign</name></job_type><industry><name>Education</name></industry><business_solution><id>4</id><name>Build Relationship</name></business_solution><business_solution><id>6</id><name>Convert Sales</name></business_solution><media_type>17</media_type><media_type>6</media_type></project>'; - $this->assertEqual($expected, $result); - } */ - - /* Broken due to a Set class issue */ - /* function testMixedArray() { - $input = array('OptionGroup' => array( - array('name' => 'OptA', 'id' => 12, 'OptA 1', 'OptA 2', 'OptA 3', 'OptA 4', 'OptA 5', 'OptA 6'), - array('name' => 'OptB', 'id' => 12, 'OptB 1', 'OptB 2', 'OptB 3', 'OptB 4', 'OptB 5', 'OptB 6') - )); - $expected = '<option_group><name>OptA</name><id>12</id><option_group>OptA 1</option_group><option_group>OptA 2</option_group><option_group>OptA 3</option_group><option_group>OptA 4</option_group><option_group>OptA 5</option_group><option_group>OptA 6</option_group></option_group><option_group><name>OptB</name><id>12</id><option_group>OptB 1</option_group><option_group>OptB 2</option_group><option_group>OptB 3</option_group><option_group>OptB 4</option_group><option_group>OptB 5</option_group><option_group>OptB 6</option_group></option_group>'; - $xml = new Xml($input, array('format' => 'tags')); - $result = $xml->toString(array('header' => false, 'cdata' => false)); - $this->assertEqual($expected, $result); - } */ - - /* function testMixedNestedArray() { - $input = array( - 'OptionA' => array( - 'name' => 'OptA', - 'id' => 12, - 'opt' => array('OptA 1', 'OptA 2', 'OptA 3', 'OptA 4', 'OptA 5', 'OptA 6') - ), - 'OptionB' => array( - 'name' => 'OptB', - 'id' => 12, - 'opt' => array('OptB 1', 'OptB 2', 'OptB 3', 'OptB 4', 'OptB 5', 'OptB 6') - ) - ); - $expected = '<option_a><name>OptA</name><id>12</id><opt>OptA 1</opt><opt>OptA 2</opt><opt>OptA 3</opt><opt>OptA 4</opt><opt>OptA 5</opt><opt>OptA 6</opt></option_a><option_b><name>OptB</name><id>12</id><opt>OptB 1</opt><opt>OptB 2</opt><opt>OptB 3</opt><opt>OptB 4</opt><opt>OptB 5</opt><opt>OptB 6</opt></option_b>'; - $xml = new Xml($input, array('format' => 'tags')); - $result = $xml->toString(array('header' => false, 'cdata' => false)); - $this->assertEqual($expected, $result); - } */ - - /* function testMixedArrayAttributes() { - $input = array('OptionGroup' => array( - array( - 'name' => 'OptA', - 'id' => 12, - array('opt' => 'OptA 1'), - array('opt' => 'OptA 2'), - array('opt' => 'OptA 3'), - array('opt' => 'OptA 4'), - array('opt' => 'OptA 5'), - array('opt' => 'OptA 6') - ), - array( - 'name' => 'OptB', - 'id' => 12, - array('opt' => 'OptB 1'), - array('opt' => 'OptB 2'), - array('opt' => 'OptB 3'), - array('opt' => 'OptB 4'), - array('opt' => 'OptB 5'), - array('opt' => 'OptB 6') - ) - )); - $expected = '<option_group name="OptA" id="12"><opt>OptA 1</opt><opt>OptA 2</opt><opt>OptA 3</opt><opt>OptA 4</opt><opt>OptA 5</opt><opt>OptA 6</opt></option_group><option_group name="OptB" id="12"><opt>OptB 1</opt><opt>OptB 2</opt><opt>OptB 3</opt><opt>OptB 4</opt><opt>OptB 5</opt><opt>OptB 6</opt></option_group>'; - - $options = array('tags' => array('option_group' => array('attributes' => array('id', 'name')))); - $xml = new Xml($input, $options); - $result = $xml->toString(false); - - $this->assertEqual($expected, $result); - } */ - - /* Not implemented yet */ - /* function testTagMap() { - $input = array( - array( - 'Project' => array('id' => 1, 'title' => null, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 1, 'industry_id' => 1, 'modified' => null, 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 1, 'name' => 'Touch Screen Kiosk'), - 'Industry' => array('id' => 1, 'name' => 'Financial') - ), - array( - 'Project' => array('id' => 2, 'title' => null, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 2, 'industry_id' => 2, 'modified' => '2007-11-26 14:48:36', 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 2, 'name' => 'Awareness Campaign'), - 'Industry' => array('id' => 2, 'name' => 'Education'), - ) - ); - $expected = '<project id="1"><title /><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>1</job_type_id><industry_id>1</industry_id><modified /><created /><style id=""><name /></style><jobtype id="1">Touch Screen Kiosk</jobtype><industry id="1"><name>Financial</name></industry></project><project id="2"><title /><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>2</job_type_id><industry_id>2</industry_id><modified>2007-11-26 14:48:36</modified><created /><style id=""><name /></style><jobtype id="2">Awareness Campaign</jobtype><industry id="2"><name>Education</name></industry></project>'; - - $xml = new Xml($input, array('tags' => array( - 'Project' => array('attributes' => array('id')), - 'style' => array('attributes' => array('id')), - 'JobType' => array('name' => 'jobtype', 'attributes' => array('id'), 'value' => 'name'), - 'Industry' => array('attributes' => array('id')) - ))); - $result = $xml->toString(array('header' => false, 'cdata' => false)); - $this->assertEqual($expected, $result); - } */ - -/** - * testAllCData method - * - * @access public - * @return void - */ - function testAllCData() { - $input = array( - array( - 'Project' => array('id' => 1, 'title' => null, 'client_id' => 1, 'show' => '1', 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => '1.89', 'industry_id' => '1.56', 'modified' => null, 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 1, 'name' => 'Touch Screen Kiosk'), - 'Industry' => array('id' => 1, 'name' => 'Financial') - ), - array( - 'Project' => array('id' => 2, 'title' => null, 'client_id' => 2, 'show' => '1', 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => '2.2', 'industry_id' => 2.2, 'modified' => '2007-11-26 14:48:36', 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 2, 'name' => 'Awareness Campaign'), - 'Industry' => array('id' => 2, 'name' => 'Education'), - ) - ); - $expected = '<project><id>1</id><title /><client_id>1</client_id><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>1.89</job_type_id><industry_id>1.56</industry_id><modified /><created /><style><id /><name /></style><job_type><id>1</id><name><![CDATA[Touch Screen Kiosk]]></name></job_type><industry><id>1</id><name><![CDATA[Financial]]></name></industry></project><project><id>2</id><title /><client_id>2</client_id><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>2.2</job_type_id><industry_id>2.2</industry_id><modified><![CDATA[2007-11-26 14:48:36]]></modified><created /><style><id /><name /></style><job_type><id>2</id><name><![CDATA[Awareness Campaign]]></name></job_type><industry><id>2</id><name><![CDATA[Education]]></name></industry></project>'; - $xml = new Xml($input, array('format' => 'tags')); - $result = $xml->toString(array('header' => false, 'cdata' => true)); - $this->assertEqual($expected, $result); - } - /* PHP-native Unicode support pending */ - /* function testConvertEntities() { - $input = array('project' => 'écît'); - $xml = new Xml($input); - - $result = $xml->toString(array('header' => false, 'cdata' => false, 'convertEntities' => true)); - $expected = '<project>écît</project>'; - $this->assertEqual($result, $expected); - } */ - -/** - * testWhitespace method - * - * @access public - * @return void - */ - function testWhitespace() { - $input = array( - array( - 'Project' => array('id' => 1, 'title' => null, 'client_id' => 1, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 1, 'industry_id' => 1, 'modified' => null, 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 1, 'name' => 'Touch Screen Kiosk'), - 'Industry' => array('id' => 1, 'name' => 'Financial') - ), - array( - 'Project' => array('id' => 2, 'title' => null, 'client_id' => 2, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 2, 'industry_id' => 2, 'modified' => '2007-11-26 14:48:36', 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 2, 'name' => 'Awareness Campaign'), - 'Industry' => array('id' => 2, 'name' => 'Education'), - ) - ); - $expected = "\n\t<project>\n\t\t<id>\n\t\t\t1\n\t\t</id>\n\t\t<title />\n\t\t<client_id>\n\t\t\t1\n\t\t</client_id>\n\t\t<show>\n\t\t\t1\n\t\t</show>\n\t\t<is_spotlight />\n\t\t<style_id>\n\t\t\t0\n\t\t</style_id>\n\t\t<job_type_id>\n\t\t\t1\n\t\t</job_type_id>\n\t\t<industry_id>\n\t\t\t1\n\t\t</industry_id>\n\t\t<modified />\n\t\t<created />\n\t\t<style>\n\t\t\t<id />\n\t\t\t<name />\n\t\t</style>\n\t\t<job_type>\n\t\t\t<id>\n\t\t\t\t1\n\t\t\t</id>\n\t\t\t<name>\n\t\t\t\tTouch Screen Kiosk\n\t\t\t</name>\n\t\t</job_type>\n\t\t<industry>\n\t\t\t<id>\n\t\t\t\t1\n\t\t\t</id>\n\t\t\t<name>\n\t\t\t\tFinancial\n\t\t\t</name>\n\t\t</industry>\n\t</project>\n\t<project>\n\t\t<id>\n\t\t\t2\n\t\t</id>\n\t\t<title />\n\t\t<client_id>\n\t\t\t2\n\t\t</client_id>\n\t\t<show>\n\t\t\t1\n\t\t</show>\n\t\t<is_spotlight />\n\t\t<style_id>\n\t\t\t0\n\t\t</style_id>\n\t\t<job_type_id>\n\t\t\t2\n\t\t</job_type_id>\n\t\t<industry_id>\n\t\t\t2\n\t\t</industry_id>\n\t\t<modified>\n\t\t\t2007-11-26 14:48:36\n\t\t</modified>\n\t\t<created />\n\t\t<style>\n\t\t\t<id />\n\t\t\t<name />\n\t\t</style>\n\t\t<job_type>\n\t\t\t<id>\n\t\t\t\t2\n\t\t\t</id>\n\t\t\t<name>\n\t\t\t\tAwareness Campaign\n\t\t\t</name>\n\t\t</job_type>\n\t\t<industry>\n\t\t\t<id>\n\t\t\t\t2\n\t\t\t</id>\n\t\t\t<name>\n\t\t\t\tEducation\n\t\t\t</name>\n\t\t</industry>\n\t</project>\n"; - - $xml = new Xml($input, array('format' => 'tags')); - $result = $xml->toString(array('header' => false, 'cdata' => false, 'whitespace' => true)); - $this->assertEqual($expected, $result); - } - -/** - * testSetSerialization method - * - * @access public - * @return void - */ - function testSetSerialization() { - $input = array( - array( - 'Project' => array('id' => 1, 'title' => null, 'client_id' => 1, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 1, 'industry_id' => 1, 'modified' => null, 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 1, 'name' => 'Touch Screen Kiosk'), - 'Industry' => array('id' => 1, 'name' => 'Financial') - ), - array( - 'Project' => array('id' => 2, 'title' => null, 'client_id' => 2, 'show' => 1, 'is_spotlight' => null, 'style_id' => 0, 'job_type_id' => 2, 'industry_id' => 2, 'modified' => '2007-11-26 14:48:36', 'created' => null), - 'Style' => array('id' => null, 'name' => null), - 'JobType' => array('id' => 2, 'name' => 'Awareness Campaign'), - 'Industry' => array('id' => 2, 'name' => 'Education'), - ) - ); - $expected = '<project><id>1</id><title /><client_id>1</client_id><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>1</job_type_id><industry_id>1</industry_id><modified /><created /><style><id /><name /></style><job_type><id>1</id><name>Touch Screen Kiosk</name></job_type><industry><id>1</id><name>Financial</name></industry></project><project><id>2</id><title /><client_id>2</client_id><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>2</job_type_id><industry_id>2</industry_id><modified>2007-11-26 14:48:36</modified><created /><style><id /><name /></style><job_type><id>2</id><name>Awareness Campaign</name></job_type><industry><id>2</id><name>Education</name></industry></project>'; - - $xml = new Xml(Set::map($input), array('format' => 'tags')); - $result = $xml->toString(array('header' => false, 'cdata' => false)); - $this->assertEqual($expected, $result); - } - -/** - * ensure that normalize does not add _name_ elements that come from Set::map sometimes. - * - * @return void - */ - function testNormalizeNotAdding_name_Element() { - $input = array( - 'output' => array( - 'Vouchers' => array( - array('Voucher' => array('id' => 1)), - array('Voucher' => array('id' => 2)), - ), - ) - ); - $xml = new Xml($input, array('attributes' => false, 'format' => 'tags')); - $this->assertFalse(isset($xml->children[0]->children[0]->children[1]), 'Too many children %s'); - $this->assertEqual($xml->children[0]->children[0]->children[0]->name, 'voucher'); - } -/** - * testSimpleParsing method - * - * @access public - * @return void - */ - function testSimpleParsing() { - $source = '<response><hello><![CDATA[happy world]]></hello><goodbye><![CDATA[cruel world]]></goodbye></response>'; - $xml = new Xml($source); - $result = $xml->toString(); - $this->assertEqual($source, $result); - } - -/** - * test that elements with empty tag values do not collapse and corrupt data structures - * - * @access public - * @return void - */ - function testElementCollapsing() { - $xmlDataThatFails = '<resultpackage> - <result qid="46b1c46ed6208"><![CDATA[46b1c46ed3af9]]></result> - <result qid="46b1c46ed332a"><![CDATA[]]></result> - <result qid="46b1c46ed90e6"><![CDATA[46b1c46ed69d8]]></result> - <result qid="46b1c46ed71a7"><![CDATA[46b1c46ed5a38]]></result> - <result qid="46b1c46ed8146"><![CDATA[46b1c46ed98b6]]></result> - <result qid="46b1c46ed7978"><![CDATA[]]></result> - <result qid="46b1c46ed4a98"><![CDATA[]]></result> - <result qid="46b1c46ed42c8"><![CDATA[]]></result> - <result qid="46b1c46ed5268"><![CDATA[46b1c46ed8917]]></result> - </resultpackage>'; - - $Xml = new Xml(); - $Xml->load('<?xml version="1.0" encoding="UTF-8" ?>' . $xmlDataThatFails); - $result = $Xml->toArray(false); - - $this->assertTrue(is_array($result)); - $expected = array( - 'resultpackage' => array( - 'result' => array( - 0 => array( - 'value' => '46b1c46ed3af9', - 'qid' => '46b1c46ed6208'), - 1 => array( - 'qid' => '46b1c46ed332a'), - 2 => array( - 'value' => '46b1c46ed69d8', - 'qid' => '46b1c46ed90e6'), - 3 => array( - 'value' => '46b1c46ed5a38', - 'qid' => '46b1c46ed71a7'), - 4 => array( - 'value' => '46b1c46ed98b6', - 'qid' => '46b1c46ed8146'), - 5 => array( - 'qid' => '46b1c46ed7978'), - 6 => array( - 'qid' => '46b1c46ed4a98'), - 7 => array( - 'qid' => '46b1c46ed42c8'), - 8 => array( - 'value' => '46b1c46ed8917', - 'qid' => '46b1c46ed5268'), + try { + $xml = array( + 'tags' => array( + 'tag' => array( + array( + array( + 'string' + ) + ) + ) ) - )); - $this->assertEqual( - count($result['resultpackage']['result']), count($expected['resultpackage']['result']), - 'Incorrect array length %s'); + ); + Xml::fromArray($xml); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertEqual($e->getMessage(), __('Invalid array')); + } - $this->assertFalse( - isset($result['resultpackage']['result'][0][0]['qid']), 'Nested array exists, data is corrupt. %s'); - - $this->assertEqual($result, $expected); - } - -/** - * test that empty values do not casefold collapse - * - * @see http://code.cakephp.org/tickets/view/8 - * @return void - */ - function testCaseFoldingWithEmptyValues() { - $filledValue = '<method name="set_user_settings"> - <title>update user information - 1 - - 1 - varchar(45) - - '; - $xml =& new XML($filledValue); - $expected = array( - 'Method' => array( - 'name' => 'set_user_settings', - 'title' => 'update user information', - 'user' => '1', - 'User' => array( - 'id' => 1, - 'name' => 'varchar(45)', - ), - ) - ); - $result = $xml->toArray(); - $this->assertEqual($result, $expected); - - $emptyValue =' - update user information - - - 1 - varchar(45) - - '; - - $xml =& new XML($emptyValue); - $expected = array( - 'Method' => array( - 'name' => 'set_user_settings', - 'title' => 'update user information', - 'user' => array(), - 'User' => array( - 'id' => 1, - 'name' => 'varchar(45)', - ), - ) - ); - $result = $xml->toArray(); - $this->assertEqual($result, $expected); - } -/** - * testMixedParsing method - * - * @access public - * @return void - */ - function testMixedParsing() { - $source = ''; - $xml = new Xml($source); - $result = $xml->toString(); - $this->assertEqual($source, $result); - } - -/** - * testComplexParsing method - * - * @access public - * @return void - */ - function testComplexParsing() { - $source = '1<client_id>1</client_id><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>1</job_type_id><industry_id>1</industry_id><modified /><created /><style><id /><name /></style><job_type><id>1</id><name>Touch Screen Kiosk</name></job_type><industry><id>1</id><name>Financial</name></industry></project><project><id>2</id><title /><client_id>2</client_id><show>1</show><is_spotlight /><style_id>0</style_id><job_type_id>2</job_type_id><industry_id>2</industry_id><modified>2007-11-26 14:48:36</modified><created /><style><id /><name /></style><job_type><id>2</id><name>Awareness Campaign</name></job_type><industry><id>2</id><name>Education</name></industry></project></projects>'; - $xml = new Xml($source); - $result = $xml->toString(array('cdata' => false)); - $this->assertEqual($source, $result); - } - -/** - * testNamespaceParsing method - * - * @access public - * @return void - */ - function testNamespaceParsing() { - $source = '<a:container xmlns:a="http://example.com/a" xmlns:b="http://example.com/b" xmlns:c="http://example.com/c" xmlns:d="http://example.com/d" xmlns:e="http://example.com/e"><b:rule test=""><c:result>value</c:result></b:rule><d:rule test=""><e:result>value</e:result></d:rule></a:container>'; - $xml = new Xml($source); - - $result = $xml->toString(array('cdata' => false)); - $this->assertEqual($source, $result); - - $children = $xml->children('container'); - $this->assertEqual($children[0]->namespace, 'a'); - - $children = $children[0]->children('rule'); - $this->assertEqual($children[0]->namespace, 'b'); - } - -/** - * testNamespaces method - * - * @access public - * @return void - */ - function testNamespaces() { - $source = '<a:container xmlns:a="http://example.com/a" xmlns:b="http://example.com/b" xmlns:c="http://example.com/c" xmlns:d="http://example.com/d" xmlns:e="http://example.com/e"><b:rule test=""><c:result>value</c:result></b:rule><d:rule test=""><e:result>value</e:result></d:rule></a:container>'; - $xml = new Xml($source); - - $expects = '<a:container xmlns:a="http://example.com/a" xmlns:b="http://example.com/b" xmlns:c="http://example.com/c" xmlns:d="http://example.com/d" xmlns:e="http://example.com/e" xmlns:f="http://example.com/f"><b:rule test=""><c:result>value</c:result></b:rule><d:rule test=""><e:result>value</e:result></d:rule></a:container>'; - - $_xml =& XmlManager::getInstance(); - $xml->addNamespace('f', 'http://example.com/f'); - $result = $xml->toString(array('cdata' => false)); - $this->assertEqual($expects, $result); - } - -/** - * testEscapeCharSerialization method - * - * @access public - * @return void - */ - function testEscapeCharSerialization() { - $xml = new Xml(array('text' => 'JavaScript & DHTML'), array('attributes' => false, 'format' => 'attributes')); - - $result = $xml->toString(false); - $expected = '<std_class text="JavaScript & DHTML" />'; - $this->assertEqual($expected, $result); - } - -/** - * testCompleteEscapeCharSerialization method - * - * @access public - * @return void - */ - function testCompleteEscapeCharSerialization() { - $xml = new Xml(array('text' => '<>&"\''), array('attributes' => false, 'format' => 'attributes')); - - $result = $xml->toString(false); - $expected = '<std_class text="<>&"'" />'; - $this->assertEqual($expected, $result); } /** @@ -991,446 +227,80 @@ class XmlTest extends CakeTestCase { * @return void */ function testToArray() { - App::import('Set'); - $string = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?> - <rss version="2.0"> - <channel> - <title>Cake PHP Google Group - http://groups.google.com/group/cake-php - Search this group before posting anything. There are over 20,000 posts and it&#39;s very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database. - en - - constructng result array when using findall - http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f - i'm using cakephp to construct a logical data model array that will be <br> passed to a flex app. I have the following model association: <br> ServiceDay-&gt;(hasMany)ServiceTi me-&gt;(hasMany)ServiceTimePrice. So what <br> the current output from my findall is something like this example: <br> <p>Array( <br> [0] =&gt; Array( - http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f - bmil...@gmail.com(bpscrugs) - Fri, 28 Dec 2007 00:44:14 UT - - - Re: share views between actions? - http://groups.google.com/group/cake-php/msg/8b350d898707dad8 - Then perhaps you might do us all a favour and refrain from replying to <br> things you do not understand. That goes especially for asinine comments. <br> Indeed. <br> To sum up: <br> No comment. <br> In my day, a simple &quot;RTFM&quot; would suffice. I'll keep in mind to ignore any <br> further responses from you. <br> You (and I) were referring to the *online documentation*, not other - http://groups.google.com/group/cake-php/msg/8b350d898707dad8 - subtropolis.z...@gmail.com(subtropolis zijn) - Fri, 28 Dec 2007 00:45:01 UT - - - '; - $xml = new Xml($string); - $result = $xml->toArray(); - $expected = array('Rss' => array( - 'version' => '2.0', - 'Channel' => array( - 'title' => 'Cake PHP Google Group', - 'link' => 'http://groups.google.com/group/cake-php', - 'description' => 'Search this group before posting anything. There are over 20,000 posts and it's very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database.', - 'language' => 'en', - 'Item' => array( + $obj = new DateTime(); + try { + Xml::toArray($obj); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } + + try { + Xml::toArray(array()); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertTrue(true); + } + + $xml = 'name'; + $obj = Xml::build($xml); + $this->assertEqual(Xml::toArray($obj), array('tag' => 'name')); + + $xml = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'webroot' . DS . 'sample.xml'; + $obj = Xml::build($xml); + $expected = array( + 'tags' => array( + 'tag' => array( array( - 'title' => 'constructng result array when using findall', - 'link' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f', - 'description' => "i'm using cakephp to construct a logical data model array that will be
passed to a flex app. I have the following model association:
ServiceDay->(hasMany)ServiceTi me->(hasMany)ServiceTimePrice. So what
the current output from my findall is something like this example:

Array(
[0] => Array(", - 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f'), - 'author' => 'bmil...@gmail.com(bpscrugs)', - 'pubDate' => 'Fri, 28 Dec 2007 00:44:14 UT', + 'id' => '1', + 'name' => 'defect' ), array( - 'title' => 'Re: share views between actions?', - 'link' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8', - 'description' => 'Then perhaps you might do us all a favour and refrain from replying to
things you do not understand. That goes especially for asinine comments.
Indeed.
To sum up:
No comment.
In my day, a simple "RTFM" would suffice. I\'ll keep in mind to ignore any
further responses from you.
You (and I) were referring to the *online documentation*, not other', - 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8'), - 'author' => 'subtropolis.z...@gmail.com(subtropolis zijn)', - 'pubDate' => 'Fri, 28 Dec 2007 00:45:01 UT' - ) - ) - ) - )); - $this->assertEqual($result, $expected); - - $string =''; - $xml = new Xml($string); - $result = $xml->toArray(); - $expected = array('Data' => array('Post' => array('title' => 'Title of this post', 'description' => 'cool'))); - $this->assertEqual($result, $expected); - - $xml = new Xml('An example of a correctly reversed XMLNode'); - $result = Set::reverse($xml); - $expected = array( - 'Example' => array( - 'Item' => array( - 'title' => 'An example of a correctly reversed XMLNode', - 'desc' => array(), - ) - ) - ); - $this->assertIdentical($result, $expected); - - $xml = new Xml('title1title2'); - $result = $xml->toArray(); - $expected = array( - 'Example' => array( - 'Item' => array( - 'attr' => '123', - 'Titles' => array( - 'Title' => array('title1', 'title2') + 'id' => '2', + 'name' => 'enhancement' ) ) ) ); - $this->assertIdentical($result, $expected); - - $xml = new Xml('listtextforitems'); - $result = $xml->toArray(); - $expected = array( - 'Example' => array( - 'attr' => 'ex_attr', - 'Item' => array( - 'attr' => '123', - 'titles' => 'list', - 'value' => 'textforitems' - ) - ) - ); - $this->assertIdentical($result, $expected); - - $xml = new Xml('listtextforitems'); - $example = $xml->child('example'); - $item = $example->child('item'); - $result = $item->toArray(); + $this->assertEqual(Xml::toArray($obj), $expected); + $this->assertEqual(Xml::toArray(Xml::fromArray($expected)), $expected); + $this->assertEqual(Xml::toArray(Xml::fromArray($expected, 'tags')), $expected); $expected = array( - 'attr' => '123', - 'titles' => 'list', - 'value' => 'textforitems' - ); - $this->assertIdentical($result, $expected); - - $string = ' - - - Cake PHP Google Group - http://groups.google.com/group/cake-php - Search this group before posting anything. There are over 20,000 posts and it&#39;s very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database. - en - - constructng result array when using findall - http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f - i'm using cakephp to construct a logical data model array that will be <br> passed to a flex app. I have the following model association: <br> ServiceDay-&gt;(hasMany)ServiceTi me-&gt;(hasMany)ServiceTimePrice. So what <br> the current output from my findall is something like this example: <br> <p>Array( <br> [0] =&gt; Array( - cakephp - - - http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f - bmil...@gmail.com(bpscrugs) - Fri, 28 Dec 2007 00:44:14 UT - - - Re: share views between actions? - http://groups.google.com/group/cake-php/msg/8b350d898707dad8 - Then perhaps you might do us all a favour and refrain from replying to <br> things you do not understand. That goes especially for asinine comments. <br> Indeed. <br> To sum up: <br> No comment. <br> In my day, a simple &quot;RTFM&quot; would suffice. I'll keep in mind to ignore any <br> further responses from you. <br> You (and I) were referring to the *online documentation*, not other - cakephp - - - http://groups.google.com/group/cake-php/msg/8b350d898707dad8 - subtropolis.z...@gmail.com(subtropolis zijn) - Fri, 28 Dec 2007 00:45:01 UT - - - '; - - $xml = new Xml($string); - $result = $xml->toArray(); - - $expected = array('Rss' => array( - 'version' => '2.0', - 'Channel' => array( - 'title' => 'Cake PHP Google Group', - 'link' => 'http://groups.google.com/group/cake-php', - 'description' => 'Search this group before posting anything. There are over 20,000 posts and it's very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database.', - 'language' => 'en', - 'Item' => array( - array( - 'title' => 'constructng result array when using findall', - 'link' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f', - 'description' => "i'm using cakephp to construct a logical data model array that will be
passed to a flex app. I have the following model association:
ServiceDay->(hasMany)ServiceTi me->(hasMany)ServiceTimePrice. So what
the current output from my findall is something like this example:

Array(
[0] => Array(", - 'creator' => 'cakephp', - 'Category' => array('cakephp', 'model'), - 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f'), - 'author' => 'bmil...@gmail.com(bpscrugs)', - 'pubDate' => 'Fri, 28 Dec 2007 00:44:14 UT', - ), - array( - 'title' => 'Re: share views between actions?', - 'link' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8', - 'description' => 'Then perhaps you might do us all a favour and refrain from replying to
things you do not understand. That goes especially for asinine comments.
Indeed.
To sum up:
No comment.
In my day, a simple "RTFM" would suffice. I\'ll keep in mind to ignore any
further responses from you.
You (and I) were referring to the *online documentation*, not other', - 'creator' => 'cakephp', - 'Category' => array('cakephp', 'model'), - 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8'), - 'author' => 'subtropolis.z...@gmail.com(subtropolis zijn)', - 'pubDate' => 'Fri, 28 Dec 2007 00:45:01 UT' - ) - ) - ) - )); - $this->assertEqual($result, $expected); - - $text = " - - - 1 - 2 - 3 - 4 - - "; - $xml = new Xml($text); - $result = $xml->toArray(); - - $expected = array('Course' => array( - 'Comps' => array( - 'Comp' => array( - 1, 2, 3, 4 - ) - ) - )); - - $this->assertEqual($result, $expected); - - $text = ' - - - xri://$xrds*simple - 2008-04-13T07:34:58Z - - http://oauth.net/core/1.0/endpoint/authorize - http://oauth.net/core/1.0/parameters/auth-header - http://oauth.net/core/1.0/parameters/uri-query - https://ma.gnolia.com/oauth/authorize - http://ma.gnolia.com/oauth/authorize - - - - xri://$xrds*simple - - http://oauth.net/discovery/1.0 - #oauth - - - '; - - $xml = new Xml($text); - $result = $xml->toArray(); - - $expected = array('XRDS' => array( - 'xmlns' => 'xri://$xrds', - 'XRD' => array( - array( - 'xml:id' => 'oauth', - 'xmlns' => 'xri://$XRD*($v*2.0)', - 'version' => '2.0', - 'Type' => 'xri://$xrds*simple', - 'Expires' => '2008-04-13T07:34:58Z', - 'Service' => array( - 'Type' => array( - 'http://oauth.net/core/1.0/endpoint/authorize', - 'http://oauth.net/core/1.0/parameters/auth-header', - 'http://oauth.net/core/1.0/parameters/uri-query' - ), - 'URI' => array( - array( - 'value' => 'https://ma.gnolia.com/oauth/authorize', - 'priority' => '10', - ), - array( - 'value' => 'http://ma.gnolia.com/oauth/authorize', - 'priority' => '20' - ) - ) + 'tags' => array( + 'tag' => array( + 'id' => '1', + 'posts' => array( + array('id' => '1'), + array('id' => '2') ) ), - array( - 'xmlns' => 'xri://$XRD*($v*2.0)', - 'version' => '2.0', - 'Type' => 'xri://$xrds*simple', - 'Service' => array( - 'priority' => '10', - 'Type' => 'http://oauth.net/discovery/1.0', - 'URI' => '#oauth' + 'tagOther' => array( + 'subtag' => array( + 'id' => '1' ) ) ) - )); - $this->assertEqual($result, $expected); + ); + $this->assertEqual(Xml::toArray(Xml::fromArray($expected)), $expected); + + $xml = ''; + $xml .= '
ApplesBananas
'; + $xml .= 'CakePHPMIT
'; + $xml .= 'The book is on the table.
'; + $xml .= '
'; + $obj = Xml::build($xml); - $text = ' - - - - - - - '; - $xml = new Xml($text); - $result = $xml->toArray(); $expected = array( - 'Root' => array( - 'Child' => array( - array('id' => 1, 'other' => 1), - array('id' => 2, 'other' => 1), - array('id' => 3, 'other' => 1), - array('id' => 4, 'other' => 1), - array('id' => 5, 'other' => 1) + 'root' => array( + 'table' => array( + array('tr' => array('td' => array('Apples', 'Bananas'))), + array('name' => 'CakePHP', 'license' => 'MIT'), + 'The book is on the table.' ) ) ); - $this->assertEqual($result, $expected); - - $text = '

'; - $xml = new Xml($text); - $result = $xml->toArray(); - $expected = array( - 'Main' => array( - 'First' => array( - array('label' => 'first type node 1'), - array('label' => 'first type node 2') - ), - 'Second' => array('label'=>'second type node') - ) - ); - $this->assertIdentical($result,$expected); - - $text = '
'; - $xml = new Xml($text); - $result = $xml->toArray(); - $expected = array( - 'Main' => array( - 'First' => array( - array('label' => 'first type node 1'), - array('label' => 'first type node 2') - ), - 'Second' => array('label'=>'second type node'), - 'Collection' => array( - 'Fifth' => array('label' => 'fifth type node'), - 'Third' => array( - array('label' => 'third type node 1'), - array('label' => 'third type node 2'), - array('label' => 'third type node 3'), - ), - 'Fourth' => array('label' => 'fourth type node'), - ) - ) - ); - $this->assertIdentical($result,$expected); + $this->assertEqual(Xml::toArray($obj), $expected); } -/** - * testAppend method - * - * @access public - * @return void - */ - function testAppend() { - $parentNode = new XmlNode('ourParentNode'); - $parentNode->append( new XmlNode('ourChildNode')); - $first =& $parentNode->first(); - $this->assertEqual($first->name, 'ourChildNode'); - - $string = 'ourChildNode'; - $parentNode = new XmlNode('ourParentNode'); - $parentNode->append($string); - $last =& $parentNode->last(); - $this->assertEqual($last->name, 'ourChildNode'); - $this->expectError(); - $parentNode->append($parentNode); - } - -/** - * testNamespacing method - * - * @access public - * @return void - */ - function testNamespacing() { - $node = new Xml(''); - $node->addNamespace('cake', 'http://cakephp.org'); - $this->assertEqual($node->toString(), ''); - - $this->assertTrue($node->removeNamespace('cake')); - $this->assertEqual($node->toString(), ''); - - - $node = new Xml(''); - $this->assertTrue($node->removeNamespace('cake')); - $this->assertEqual($node->toString(), ''); - - $node->addNamespace('cake', 'http://cakephp.org'); - $this->assertEqual($node->toString(), ''); - } - -/** - * testCamelize method - * - * @access public - * @return void - */ - function testCamelize() { - $xmlString = 'examples.getStateName' . - '41'; - - $Xml = new Xml($xmlString); - $expected = array( - 'methodCall' => array( - 'methodName' => 'examples.getStateName', - 'params' => array( - 'param' => array('value' => array('i4' => 41))))); - $this->assertEqual($expected, $Xml->toArray(false)); - - $Xml = new Xml($xmlString); - $expected = array( - 'MethodCall' => array( - 'methodName' => 'examples.getStateName', - 'Params' => array( - 'Param' => array('Value' => array('i4' => 41))))); - $this->assertEqual($expected, $Xml->toArray()); - } - -/** - * testNumericDataHandling method - * - * @access public - * @return void - */ - function testNumericDataHandling() { - $data = '012345'; - - $node = new Xml(); - $node->load($data); - $node->parse(); - - $result = $node->first(); - $result = $result->children("data"); - - $result = $result[0]->first(); - $this->assertEqual($result->value, '012345'); - } - -/** - * test that creating an xml object does not leak memory - * - * @return void - */ - function testMemoryLeakInConstructor() { - if ($this->skipIf(!function_exists('memory_get_usage'), 'Cannot test memory leaks without memory_get_usage')) { - return; - } - $data = 'TEST'; - $start = memory_get_usage(); - for ($i = 0; $i <= 300; $i++) { - $test =& new XML($data); - $test->__destruct(); - unset($test); - } - $end = memory_get_usage(); - $this->assertWithinMargin($start, $end, 3600, 'Memory leaked %s'); - } } From 43d49095aefbbca3fb46238b5d1d4bbcdf7ae49a Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 26 Jul 2010 21:39:45 -0300 Subject: [PATCH 02/37] Removing xml helper. Use SimpleXML instead. --- cake/libs/view/helpers/xml.php | 172 ----------- cake/tests/cases/libs/all_xml.test.php | 1 - .../cases/libs/view/helpers/xml.test.php | 287 ------------------ 3 files changed, 460 deletions(-) delete mode 100644 cake/libs/view/helpers/xml.php delete mode 100644 cake/tests/cases/libs/view/helpers/xml.test.php diff --git a/cake/libs/view/helpers/xml.php b/cake/libs/view/helpers/xml.php deleted file mode 100644 index 4f0798398..000000000 --- a/cake/libs/view/helpers/xml.php +++ /dev/null @@ -1,172 +0,0 @@ -Xml =& new Xml(); - $this->Xml->options(array('verifyNs' => false)); - } - -/** - * Returns an XML document header - * - * @param array $attrib Header tag attributes - * @return string XML header - * @access public - * @link http://book.cakephp.org/view/1476/header - */ - public function header($attrib = array()) { - if (Configure::read('App.encoding') !== null) { - $this->encoding = Configure::read('App.encoding'); - } - - if (is_array($attrib)) { - $attrib = array_merge(array('encoding' => $this->encoding), $attrib); - } - if (is_string($attrib) && strpos($attrib, 'xml') !== 0) { - $attrib = 'xml ' . $attrib; - } - - return $this->Xml->header($attrib); - } - -/** - * Adds a namespace to any documents generated - * - * @param string $name The namespace name - * @param string $url The namespace URI; can be empty if in the default namespace map - * @return boolean False if no URL is specified, and the namespace does not exist - * default namespace map, otherwise true - * @deprecated - * @see Xml::addNs() - */ - function addNs($name, $url = null) { - return $this->Xml->addNamespace($name, $url); - } - -/** - * Removes a namespace added in addNs() - * - * @param string $name The namespace name or URI - * @deprecated - * @see Xml::removeNs() - */ - public function removeNs($name) { - return $this->Xml->removeGlobalNamespace($name); - } - -/** - * Generates an XML element - * - * @param string $name The name of the XML element - * @param array $attrib The attributes of the XML element - * @param mixed $content XML element content - * @param boolean $endTag Whether the end tag of the element should be printed - * @return string XML - * @access public - * @link http://book.cakephp.org/view/1475/elem - */ - public function elem($name, $attrib = array(), $content = null, $endTag = true) { - $namespace = null; - if (isset($attrib['namespace'])) { - $namespace = $attrib['namespace']; - unset($attrib['namespace']); - } - $cdata = false; - if (is_array($content) && isset($content['cdata'])) { - $cdata = true; - unset($content['cdata']); - } - if (is_array($content) && array_key_exists('value', $content)) { - $content = $content['value']; - } - $children = array(); - if (is_array($content)) { - $children = $content; - $content = null; - } - - $elem =& $this->Xml->createElement($name, $content, $attrib, $namespace); - foreach ($children as $child) { - $elem->createElement($child); - } - $out = $elem->toString(array('cdata' => $cdata, 'leaveOpen' => !$endTag)); - - if (!$endTag) { - $this->Xml =& $elem; - } - return $out; - } - -/** - * Create closing tag for current element - * - * @return string - */ - public function closeElem() { - $name = $this->Xml->name(); - if ($parent =& $this->Xml->parent()) { - $this->Xml =& $parent; - } - return ''; - } - -/** - * Serializes a model resultset into XML - * - * @param mixed $data The content to be converted to XML - * @param array $options The data formatting options. For a list of valid options, see - * Xml::__construct(). - * @return string A copy of $data in XML format - * @see Xml::__construct() - * @access public - * @link http://book.cakephp.org/view/1474/serialize - */ - public function serialize($data, $options = array()) { - $options += array('attributes' => false, 'format' => 'attributes'); - $data =& new Xml($data, $options); - return $data->toString($options + array('header' => false)); - } -} diff --git a/cake/tests/cases/libs/all_xml.test.php b/cake/tests/cases/libs/all_xml.test.php index 70625ae31..aed5b21d1 100644 --- a/cake/tests/cases/libs/all_xml.test.php +++ b/cake/tests/cases/libs/all_xml.test.php @@ -38,7 +38,6 @@ class AllXmlTest extends PHPUnit_Framework_TestSuite { $suite->addTestFile(CORE_TEST_CASES . DS . 'libs' . DS . 'xml.test.php'); $suite->addTestFile(CORE_TEST_CASES . DS . 'libs' . DS . 'view' . DS . 'helpers' . DS . 'rss.test.php'); - $suite->addTestFile(CORE_TEST_CASES . DS . 'libs' . DS . 'view' . DS . 'helpers' . DS . 'xml.test.php'); return $suite; } } \ No newline at end of file diff --git a/cake/tests/cases/libs/view/helpers/xml.test.php b/cake/tests/cases/libs/view/helpers/xml.test.php deleted file mode 100644 index cb3e35b55..000000000 --- a/cake/tests/cases/libs/view/helpers/xml.test.php +++ /dev/null @@ -1,287 +0,0 @@ - - * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The Open Group Test Suite License - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) - * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests - * @package cake - * @subpackage cake.tests.cases.libs.view.helpers - * @since CakePHP(tm) v 1.2.0.4206 - * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License - */ -if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { - define('CAKEPHP_UNIT_TEST_EXECUTION', 1); -} -App::import('Helper', 'Xml'); - -/** - * TestXml class - * - * @package cake - * @subpackage cake.tests.cases.libs.view.helpers - */ -class TestXmlHelper extends Object { - -/** - * content property - * - * @var string '' - * @access public - */ - public $content = ''; - -/** - * construct method - * - * @param mixed $content - * @access private - * @return void - */ - function __construct($content) { - $this->content = $content; - } - -/** - * toString method - * - * @access public - * @return void - */ - function toString() { - return $this->content; - } -} - -/** - * XmlHelperTest class - * - * @package cake - * @subpackage cake.tests.cases.libs.view.helpers - */ -class XmlHelperTest extends CakeTestCase { - -/** - * setUp method - * - * @access public - * @return void - */ - function setUp() { - $this->Xml =& new XmlHelper(); - $this->Xml->beforeRender(); - $manager =& XmlManager::getInstance(); - $manager->namespaces = array(); - } - -/** - * tearDown method - * - * @access public - * @return void - */ - function tearDown() { - unset($this->Xml); - } - -/** - * testAddNamespace method - * - * @access public - * @return void - */ - function testAddNamespace() { - $this->Xml->addNs('custom', 'http://example.com/dtd.xml'); - $manager =& XmlManager::getInstance(); - - $expected = array('custom' => 'http://example.com/dtd.xml'); - $this->assertEqual($manager->namespaces, $expected); - } - -/** - * testRemoveNamespace method - * - * @access public - * @return void - */ - function testRemoveNamespace() { - $this->Xml->addNs('custom', 'http://example.com/dtd.xml'); - $this->Xml->addNs('custom2', 'http://example.com/dtd2.xml'); - $manager =& XmlManager::getInstance(); - - $expected = array('custom' => 'http://example.com/dtd.xml', 'custom2' => 'http://example.com/dtd2.xml'); - $this->assertEqual($manager->namespaces, $expected); - - $this->Xml->removeNs('custom'); - $expected = array('custom2' => 'http://example.com/dtd2.xml'); - $this->assertEqual($manager->namespaces, $expected); - } - -/** - * testRenderZeroElement method - * - * @access public - * @return void - */ - function testRenderZeroElement() { - $result = $this->Xml->elem('count', null, 0); - $expected = '0'; - $this->assertEqual($result, $expected); - - $result = $this->Xml->elem('count', null, array('cdata' => true, 'value' => null)); - $expected = ''; - $this->assertEqual($result, $expected); - } - -/** - * testRenderElementWithNamespace method - * - * @access public - * @return void - */ - function testRenderElementWithNamespace() { - $result = $this->Xml->elem('count', array('namespace' => 'myNameSpace'), 'content'); - $expected = 'content'; - $this->assertEqual($result, $expected); - - $result = $this->Xml->elem('count', array('namespace' => 'myNameSpace'), 'content', false); - $expected = 'content'; - $this->assertEqual($result, $expected); - - $expected .= ''; - $result .= $this->Xml->closeElem(); - $this->assertEqual($result, $expected); - } - /** - * testRenderElementWithComplexContent method - * - * @access public - * @return void - */ - function testRenderElementWithComplexContent() { - $result = $this->Xml->elem('count', array('namespace' => 'myNameSpace'), array('contrived' => 'content')); - $expected = ''; - $this->assertEqual($result, $expected); - - $result = $this->Xml->elem('count', array('namespace' => 'myNameSpace'), array('cdata' => true, 'value' => 'content')); - $expected = ''; - $this->assertEqual($result, $expected); - } - -/** - * testSerialize method - * - * @access public - * @return void - */ - function testSerialize() { - $data = array( - 'test1' => 'test with no quotes', - 'test2' => 'test with "double quotes"' - ); - $result = $this->Xml->serialize($data); - $expected = ''; - $this->assertIdentical($result, $expected); - - $data = array( - 'test1' => 'test with no quotes', - 'test2' => 'test without double quotes' - ); - $result = $this->Xml->serialize($data); - $expected = ''; - $this->assertIdentical($result, $expected); - - $data = array( - 'ServiceDay' => array('ServiceTime' => array('ServiceTimePrice' => array('dollar' => 1, 'cents' => '2'))) - ); - $result = $this->Xml->serialize($data); - $expected = ''; - $this->assertIdentical($result, $expected); - - $data = array( - 'ServiceDay' => array('ServiceTime' => array('ServiceTimePrice' => array('dollar' => 1, 'cents' => '2'))) - ); - $result = $this->Xml->serialize($data, array('format' => 'tags')); - $expected = '12'; - $this->assertIdentical($result, $expected); - - $data = array( - 'Pages' => array('id' => 2, 'url' => 'http://www.url.com/rb/153/?id=bbbb&t=access') - ); - $result = $this->Xml->serialize($data); - $expected = ''; - $this->assertIdentical($result, $expected); - - $test = array( - 'Test' => array('test' => true) - ); - $expected = ''; - $result = $this->Xml->serialize($test); - $this->assertidentical($expected, $result); - } - -/** - * testSerializeOnMultiDimensionalArray method - * - * @access public - * @return void - */ - function testSerializeOnMultiDimensionalArray() { - $data = array( - 'Statuses' => array( - array('Status' => array('id' => 1)), - array('Status' => array('id' => 2)) - ) - ); - $result = $this->Xml->serialize($data, array('format' => 'tags')); - $expected = '12'; - $this->assertIdentical($result, $expected); - - } - -/** - * testHeader method - * - * @access public - * @return void - */ - function testHeader() { - $expectedDefaultEncoding = Configure::read('App.encoding'); - if (empty($expectedDefaultEncoding)) { - $expectedDefaultEncoding = 'UTF-8'; - } - $attrib = array(); - $result = $this->Xml->header($attrib); - $expected = ''; - $this->assertIdentical($result, $expected); - - $attrib = array( - 'encoding' => 'UTF-8', - 'version' => '1.1' - ); - $result = $this->Xml->header($attrib); - $expected = ''; - $this->assertIdentical($result, $expected); - - $attrib = array( - 'encoding' => 'UTF-8', - 'version' => '1.2', - 'additional' => 'attribute' - ); - $result = $this->Xml->header($attrib); - $expected = ''; - $this->assertIdentical($result, $expected); - - $attrib = 'encoding="UTF-8" someOther="value"'; - $result = $this->Xml->header($attrib); - $expected = ''; - $this->assertIdentical($result, $expected); - } -} From 9725d12ccb7772965b04662fd826f7f9715ea205 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 26 Jul 2010 23:16:23 -0300 Subject: [PATCH 03/37] Adding sample.xml and changing the path in tests. --- cake/tests/cases/libs/xml.test.php | 4 ++-- cake/tests/fixtures/sample.xml | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 cake/tests/fixtures/sample.xml diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 94cc987ae..56a7951d4 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -43,7 +43,7 @@ class XmlTest extends CakeTestCase { $xml = 'value'; $this->assertEqual($obj, Xml::build($xml)); - $xml = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'webroot' . DS . 'sample.xml'; + $xml = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'sample.xml'; $obj = Xml::build($xml); $this->assertEqual($obj->getName(), 'tags'); $this->assertEqual(count($obj), 2); @@ -246,7 +246,7 @@ class XmlTest extends CakeTestCase { $obj = Xml::build($xml); $this->assertEqual(Xml::toArray($obj), array('tag' => 'name')); - $xml = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'webroot' . DS . 'sample.xml'; + $xml = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'sample.xml'; $obj = Xml::build($xml); $expected = array( 'tags' => array( diff --git a/cake/tests/fixtures/sample.xml b/cake/tests/fixtures/sample.xml new file mode 100644 index 000000000..0ab267da0 --- /dev/null +++ b/cake/tests/fixtures/sample.xml @@ -0,0 +1,9 @@ + + + + defect + + + enhancement + + \ No newline at end of file From 921dfb259f1a7fb3facff888b7888ea1aaf6ab92 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 26 Jul 2010 23:25:55 -0300 Subject: [PATCH 04/37] Split of exception tests by tests using @expectedException --- cake/tests/cases/libs/xml.test.php | 81 ++++++++++++++++-------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 56a7951d4..e78105cf4 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -57,32 +57,33 @@ class XmlTest extends CakeTestCase { } /** - * testBuildException method + * testBuildInvalidData * - * @access public - * @return void + * @expectedException Exception + * return void */ - function testBuildException() { - try { - Xml::build(false); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } + function testBuildInvalidData() { + Xml::build(false); + } - try { - Xml::build(''); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } +/** + * testBuildEmptyData + * + * @expectedException Exception + * return void + */ + function testBuildEmptyData() { + Xml::build(''); + } - try { - Xml::build(''); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } +/** + * testBuildInvalidTag + * + * @expectedException Exception + * return void + */ + function testBuildInvalidTag() { + Xml::build(''); } /** @@ -220,6 +221,27 @@ class XmlTest extends CakeTestCase { } +/** + * testToArrayInvalidObject + * + * @expectedException Exception + * @return void + */ + function testToArrayInvalidObject() { + $obj = new DateTime(); + Xml::toArray($obj); + } + +/** + * testToArrayNoObject + * + * @expectedException Exception + * @return void + */ + function testToArrayNoObject() { + Xml::toArray(array()); + } + /** * testToArray method * @@ -227,21 +249,6 @@ class XmlTest extends CakeTestCase { * @return void */ function testToArray() { - $obj = new DateTime(); - try { - Xml::toArray($obj); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } - - try { - Xml::toArray(array()); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } - $xml = 'name'; $obj = Xml::build($xml); $this->assertEqual(Xml::toArray($obj), array('tag' => 'name')); From 5de7fa5930fb9837cf3f23ab85a500aa484e6702 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 26 Jul 2010 23:42:05 -0300 Subject: [PATCH 05/37] Adding PHP Doc to XML class. --- cake/libs/xml.php | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 22b7bd184..e56b1fc3c 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -23,10 +23,12 @@ class Xml { /** - * Initialize XML object from a given XML string. Returns false on error. + * Initialize SimpleXMLElement from a given XML string, file path, URL or array. * - * @param string $input XML string, a path to a file, or an HTTP resource to load + * @param mixed $input XML string, a path to a file, an URL or an array * @return object SimpleXMLElement + * @static + * @access public */ public static function build($input) { if (is_array($input) || is_object($input)) { @@ -41,6 +43,15 @@ class Xml { throw new Exception(__('XML cannot be read.')); } +/** + * Transform an array in a SimpleXMLElement + * + * @param array $input Array with data + * @param string $format If create childs ('tags') or attributes ('attribute') + * @return object SimpleXMLElement + * @static + * @access public + */ public static function fromArray($input, $format = 'attribute') { if (!is_array($input) || count($input) !== 1) { throw new Exception(__('Invalid input.')); @@ -58,6 +69,16 @@ class Xml { return $simpleXml; } +/** + * Recursive method to create SimpleXMLElement from array + * + * @param object $node Handler to SimpleXMLElement + * @param array $array + * @param string $format + * @return void + * @static + * @access protected + */ protected static function _fromArrayRecursive(&$node, &$array, $format = 'attribute') { if (empty($array) || !is_array($array)) { return; @@ -95,7 +116,10 @@ class Xml { /** * Returns this XML structure as a array. * + * @param object $simpleXML SimpleXMLElement instance * @return array Array representation of the XML structure. + * @static + * @access public */ public static function toArray($simpleXML) { if (!($simpleXML instanceof SimpleXMLElement)) { @@ -106,6 +130,15 @@ class Xml { return $result; } +/** + * Recursive method to toArray + * + * @param object $xml SimpleXMLElement object + * @param array $parentData Parent array with data + * @return void + * @static + * @access public + */ protected static function _toArray($xml, &$parentData) { $data = array(); From df12e5b4ab2b0077f6823d631dd9c801fe6dbb85 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 26 Jul 2010 23:44:31 -0300 Subject: [PATCH 06/37] Removing @static and @access from Xml class. --- cake/libs/xml.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index e56b1fc3c..980cf2dee 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -27,8 +27,6 @@ class Xml { * * @param mixed $input XML string, a path to a file, an URL or an array * @return object SimpleXMLElement - * @static - * @access public */ public static function build($input) { if (is_array($input) || is_object($input)) { @@ -49,8 +47,6 @@ class Xml { * @param array $input Array with data * @param string $format If create childs ('tags') or attributes ('attribute') * @return object SimpleXMLElement - * @static - * @access public */ public static function fromArray($input, $format = 'attribute') { if (!is_array($input) || count($input) !== 1) { @@ -76,8 +72,6 @@ class Xml { * @param array $array * @param string $format * @return void - * @static - * @access protected */ protected static function _fromArrayRecursive(&$node, &$array, $format = 'attribute') { if (empty($array) || !is_array($array)) { @@ -118,8 +112,6 @@ class Xml { * * @param object $simpleXML SimpleXMLElement instance * @return array Array representation of the XML structure. - * @static - * @access public */ public static function toArray($simpleXML) { if (!($simpleXML instanceof SimpleXMLElement)) { @@ -136,8 +128,6 @@ class Xml { * @param object $xml SimpleXMLElement object * @param array $parentData Parent array with data * @return void - * @static - * @access public */ protected static function _toArray($xml, &$parentData) { $data = array(); From 8f1bdd5378f268a93f466d3430045cc638958324 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 28 Jul 2010 17:47:02 -0300 Subject: [PATCH 07/37] Change to RssHelper use Xml class instead XmlHelper. --- cake/libs/view/helpers/rss.php | 68 +++++++++++++++++-- .../cases/libs/view/helpers/rss.test.php | 66 +----------------- 2 files changed, 63 insertions(+), 71 deletions(-) diff --git a/cake/libs/view/helpers/rss.php b/cake/libs/view/helpers/rss.php index 31effa21a..a9c893556 100644 --- a/cake/libs/view/helpers/rss.php +++ b/cake/libs/view/helpers/rss.php @@ -17,18 +17,16 @@ * @since CakePHP(tm) v 1.2 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -App::import('Helper', 'Xml'); +App::import('Core', 'Xml'); /** - * XML Helper class for easy output of XML structures. - * - * XmlHelper encloses all methods needed while working with XML documents. + * RSS Helper class for easy output RSS structures. * * @package cake * @subpackage cake.cake.libs.view.helpers * @link http://book.cakephp.org/view/1460/RSS */ -class RssHelper extends XmlHelper { +class RssHelper extends AppHelper { /** * Helpers used by RSS Helper @@ -270,7 +268,7 @@ class RssHelper extends XmlHelper { if (!empty($elements)) { $content = implode('', $elements); } - return $this->elem('item', $att, $content, !($content === null)); + return $this->elem('item', (array)$att, $content, !($content === null)); } /** @@ -283,4 +281,62 @@ class RssHelper extends XmlHelper { function time($time) { return $this->Time->toRSS($time); } + +/** + * Generates an XML element + * + * @param string $name The name of the XML element + * @param array $attrib The attributes of the XML element + * @param mixed $content XML element content + * @param boolean $endTag Whether the end tag of the element should be printed + * @return string XML + */ + function elem($name, $attrib = array(), $content = null, $endTag = true) { + $namespace = null; + if (isset($attrib['namespace'])) { + $namespace = $attrib['namespace']; + unset($attrib['namespace']); + } + $cdata = false; + if (is_array($content) && isset($content['cdata'])) { + $cdata = true; + unset($content['cdata']); + } + if (is_array($content) && array_key_exists('value', $content)) { + $content = $content['value']; + } + $children = array(); + if (is_array($content)) { + $children = $content; + $content = null; + } + + $xml = '<' . $name; + if (!empty($namespace)) { + $xml .= ' xmlns:"' . $namespace . '"'; + } + if (strpos($name, ':') !== false) { + list($prefix, ) = explode(':', $name, 2); + switch ($prefix) { + case 'atom': + $xml .= ' xmlns:atom="http://www.w3.org/2005/Atom"'; + break; + } + } + if ($cdata && !empty($content)) { + $content = ''; + } + $xml .= '>' . $content . ''; + $elem = Xml::build($xml); + foreach ($attrib as $key => $value) { + $elem->addAttribute($key, $value); + } + foreach ($children as $child) { + $elem->addChild($child); + } + + $xml = $elem->asXML(); + $xml = trim(substr($xml, strpos($xml, '?>') + 2)); + return $xml; + } } diff --git a/cake/tests/cases/libs/view/helpers/rss.test.php b/cake/tests/cases/libs/view/helpers/rss.test.php index 2b083c5ab..ed554db8e 100644 --- a/cake/tests/cases/libs/view/helpers/rss.test.php +++ b/cake/tests/cases/libs/view/helpers/rss.test.php @@ -37,9 +37,6 @@ class RssHelperTest extends CakeTestCase { $this->Rss =& new RssHelper(); $this->Rss->Time =& new TimeHelper(); $this->Rss->beforeRender(); - - $manager =& XmlManager::getInstance(); - $manager->namespaces = array(); } /** @@ -53,52 +50,6 @@ class RssHelperTest extends CakeTestCase { } /** - * testAddNamespace method - * - * @access public - * @return void - */ - function testAddNamespace() { - $this->Rss->addNs('custom', 'http://example.com/dtd.xml'); - $manager =& XmlManager::getInstance(); - - $expected = array('custom' => 'http://example.com/dtd.xml'); - $this->assertEqual($manager->namespaces, $expected); - - $this->Rss->removeNs('custom'); - - $this->Rss->addNs('dummy', 'http://dummy.com/1.0/'); - $result = $this->Rss->document(); - $expected = array( - 'rss' => array( - 'xmlns:dummy' => 'http://dummy.com/1.0/', - 'version' => '2.0' - ) - ); - $this->assertTags($result, $expected); - - $this->Rss->removeNs('dummy'); - } - -/** - * testRemoveNamespace method - * - * @access public - * @return void - */ - function testRemoveNamespace() { - $this->Rss->addNs('custom', 'http://example.com/dtd.xml'); - $this->Rss->addNs('custom2', 'http://example.com/dtd2.xml'); - $manager =& XmlManager::getInstance(); - - $expected = array('custom' => 'http://example.com/dtd.xml', 'custom2' => 'http://example.com/dtd2.xml'); - $this->assertEqual($manager->namespaces, $expected); - - $this->Rss->removeNs('custom'); - $expected = array('custom2' => 'http://example.com/dtd2.xml'); - $this->assertEqual($manager->namespaces, $expected); - } - /** * testDocument method * * @access public @@ -252,6 +203,7 @@ class RssHelperTest extends CakeTestCase { ' array( + 'xmlns:atom' => 'http://www.w3.org/2005/Atom', 'href' => "http://www.example.com/rss.xml", 'rel' => "self", 'type' =>"application/rss+xml" @@ -386,22 +338,6 @@ class RssHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); - $item = array( - 'title' => array( - 'value' => 'My Title & more', - 'convertEntities' => false - ) - ); - $result = $this->Rss->item(null, $item); - $expected = array( - 'assertTags($result, $expected); - $item = array( 'title' => array( 'value' => 'My Title & more', From f4d5230dfa33c541de6817778ef793404e1fe7f3 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 28 Jul 2010 18:53:36 -0300 Subject: [PATCH 08/37] Fixing the read for toArray of content when have child or attribute in xml. --- cake/libs/xml.php | 5 ++++- cake/tests/cases/libs/xml.test.php | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 980cf2dee..2530c2b93 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -140,8 +140,11 @@ class Xml { self::_toArray($child, $data); } + $asString = trim((string)$xml); if (empty($data)) { - $data = (string)$xml; + $data = $asString; + } elseif (!empty($asString)) { + $data['value'] = $asString; } $name = $xml->getName(); diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index e78105cf4..23b6b604e 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -291,6 +291,21 @@ class XmlTest extends CakeTestCase { ); $this->assertEqual(Xml::toArray(Xml::fromArray($expected)), $expected); + $xml = ''; + $xml .= 'defect'; + $xml .= ''; + $obj = Xml::build($xml); + + $expected = array( + 'root' => array( + 'tag' => array( + 'id' => 1, + 'value' => 'defect' + ) + ) + ); + $this->assertEqual(Xml::toArray($obj), $expected); + $xml = ''; $xml .= '
ApplesBananas
'; $xml .= 'CakePHPMIT
'; From b862d6801680b1d35d700df2e49fbe62e28324f8 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 28 Jul 2010 19:46:35 -0300 Subject: [PATCH 09/37] Support to namespaces in Xml::toArray. --- cake/libs/xml.php | 18 +++++++++++------- cake/tests/cases/libs/xml.test.php | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 2530c2b93..5aecdc845 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -118,7 +118,8 @@ class Xml { throw new Exception(__('The input is not instance of SimpleXMLElement.')); } $result = array(); - self::_toArray($simpleXML, $result); + $namespaces = array_merge(array('' => ''), $simpleXML->getNamespaces(true)); + self::_toArray($simpleXML, $result, array_keys($namespaces)); return $result; } @@ -127,17 +128,20 @@ class Xml { * * @param object $xml SimpleXMLElement object * @param array $parentData Parent array with data + * @param array $namespaces List of namespaces in XML * @return void */ - protected static function _toArray($xml, &$parentData) { + protected static function _toArray($xml, &$parentData, $namespaces) { $data = array(); - foreach ($xml->attributes() as $key => $value) { - $data[$key] = (string)$value; - } + foreach ($namespaces as $namespace) { + foreach ($xml->attributes($namespace, true) as $key => $value) { + $data[$key] = (string)$value; + } - foreach ($xml->children() as $child) { - self::_toArray($child, $data); + foreach ($xml->children($namespace, true) as $child) { + self::_toArray($child, $data, $namespaces); + } } $asString = trim((string)$xml); diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 23b6b604e..3a460154e 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -323,6 +323,20 @@ class XmlTest extends CakeTestCase { ) ); $this->assertEqual(Xml::toArray($obj), $expected); + + $xml = ''; + $xml .= 'defect'; + $xml .= '1'; + $xml .= ''; + $obj = Xml::build($xml); + + $expected = array( + 'root' => array( + 'tag' => 'defect', + 'bug' => 1 + ) + ); + $this->assertEqual(Xml::toArray($obj), $expected); } } From 53f8353f92e0f5273f3d8c12fdc61899ba63caea Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 28 Jul 2010 19:53:52 -0300 Subject: [PATCH 10/37] Updating Set to use new Xml class. --- cake/libs/set.php | 5 +-- cake/tests/cases/libs/set.test.php | 63 ++++++++++++++---------------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/cake/libs/set.php b/cake/libs/set.php index 917c20f56..1205eb409 100644 --- a/cake/libs/set.php +++ b/cake/libs/set.php @@ -941,9 +941,8 @@ class Set { */ public static function reverse($object) { $out = array(); - if (is_a($object, 'XmlNode')) { - $out = $object->toArray(); - return $out; + if ($object instanceof SimpleXMLElement) { + return Xml::toArray($object); } else if (is_object($object)) { $keys = get_object_vars($object); if (isset($keys['_name_'])) { diff --git a/cake/tests/cases/libs/set.test.php b/cake/tests/cases/libs/set.test.php index 5bb6ddcad..1a6573474 100644 --- a/cake/tests/cases/libs/set.test.php +++ b/cake/tests/cases/libs/set.test.php @@ -2662,20 +2662,20 @@ class SetTest extends CakeTestCase { '; - $xml = new Xml($string); + $xml = Xml::build($string); $result = Set::reverse($xml); - $expected = array('Rss' => array( + $expected = array('rss' => array( 'version' => '2.0', - 'Channel' => array( + 'channel' => array( 'title' => 'Cake PHP Google Group', 'link' => 'http://groups.google.com/group/cake-php', 'description' => 'Search this group before posting anything. There are over 20,000 posts and it's very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database.', 'language' => 'en', - 'Item' => array( + 'item' => array( array( 'title' => 'constructng result array when using findall', 'link' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f', - 'description' => "i'm using cakephp to construct a logical data model array that will be
passed to a flex app. I have the following model association:
ServiceDay->(hasMany)ServiceTi me->(hasMany)ServiceTimePrice. So what
the current output from my findall is something like this example:

Array(
[0] => Array(", + 'description' => "i'm using cakephp to construct a logical data model array that will be
passed to a flex app. I have the following model association:
ServiceDay->(hasMany)ServiceTi me->(hasMany)ServiceTimePrice. So what
the current output from my findall is something like this example:

Array(
[0] => Array(", 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f'), 'author' => 'bmil...@gmail.com(bpscrugs)', 'pubDate' => 'Fri, 28 Dec 2007 00:44:14 UT', @@ -2694,43 +2694,43 @@ class SetTest extends CakeTestCase { $this->assertEqual($result, $expected); $string =''; - $xml = new Xml($string); + $xml = Xml::build($string); $result = Set::reverse($xml); - $expected = array('Data' => array('Post' => array('title' => 'Title of this post', 'description' => 'cool'))); + $expected = array('data' => array('post' => array('title' => 'Title of this post', 'description' => 'cool'))); $this->assertEqual($result, $expected); - $xml = new Xml('An example of a correctly reversed XMLNode'); + $xml = Xml::build('An example of a correctly reversed SimpleXMLElement'); $result = Set::reverse($xml); - $expected = array('Example' => + $expected = array('example' => array( - 'Item' => array( - 'title' => 'An example of a correctly reversed XMLNode', - 'desc' => array(), + 'item' => array( + 'title' => 'An example of a correctly reversed SimpleXMLElement', + 'desc' => '', ) ) ); $this->assertEquals($result, $expected); - $xml = new Xml('title1title2'); + $xml = Xml::build('title1title2'); $result = Set::reverse($xml); $expected = - array('Example' => array( - 'Item' => array( + array('example' => array( + 'item' => array( 'attr' => '123', - 'Titles' => array( - 'Title' => array('title1', 'title2') + 'titles' => array( + 'title' => array('title1', 'title2') ) ) ) ); $this->assertEquals($result, $expected); - $xml = new Xml('listtextforitems'); + $xml = Xml::build('listtextforitems'); $result = Set::reverse($xml); $expected = - array('Example' => array( + array('example' => array( 'attr' => 'ex_attr', - 'Item' => array( + 'item' => array( 'attr' => '123', 'titles' => 'list', 'value' => 'textforitems' @@ -2740,7 +2740,7 @@ class SetTest extends CakeTestCase { $this->assertEquals($result, $expected); $string = ' - + Cake PHP Google Group http://groups.google.com/group/cake-php @@ -2771,23 +2771,23 @@ class SetTest extends CakeTestCase { '; - $xml = new Xml($string); + $xml = Xml::build($string); $result = Set::reverse($xml); - $expected = array('Rss' => array( + $expected = array('rss' => array( 'version' => '2.0', - 'Channel' => array( + 'channel' => array( 'title' => 'Cake PHP Google Group', 'link' => 'http://groups.google.com/group/cake-php', 'description' => 'Search this group before posting anything. There are over 20,000 posts and it's very likely your question was answered before. Visit the IRC channel #cakephp at irc.freenode.net for live chat with users and developers of Cake. If you post, tell us the version of Cake, PHP, and database.', 'language' => 'en', - 'Item' => array( + 'item' => array( array( 'title' => 'constructng result array when using findall', 'link' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f', - 'description' => "i'm using cakephp to construct a logical data model array that will be
passed to a flex app. I have the following model association:
ServiceDay->(hasMany)ServiceTi me->(hasMany)ServiceTimePrice. So what
the current output from my findall is something like this example:

Array(
[0] => Array(", + 'description' => "i'm using cakephp to construct a logical data model array that will be
passed to a flex app. I have the following model association:
ServiceDay->(hasMany)ServiceTi me->(hasMany)ServiceTimePrice. So what
the current output from my findall is something like this example:

Array(
[0] => Array(", 'creator' => 'cakephp', - 'Category' => array('cakephp', 'model'), + 'category' => array('cakephp', 'model'), 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/49bc00f3bc651b4f'), 'author' => 'bmil...@gmail.com(bpscrugs)', 'pubDate' => 'Fri, 28 Dec 2007 00:44:14 UT', @@ -2797,7 +2797,7 @@ class SetTest extends CakeTestCase { 'link' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8', 'description' => 'Then perhaps you might do us all a favour and refrain from replying to
things you do not understand. That goes especially for asinine comments.
Indeed.
To sum up:
No comment.
In my day, a simple "RTFM" would suffice. I\'ll keep in mind to ignore any
further responses from you.
You (and I) were referring to the *online documentation*, not other', 'creator' => 'cakephp', - 'Category' => array('cakephp', 'model'), + 'category' => array('cakephp', 'model'), 'guid' => array('isPermaLink' => 'true', 'value' => 'http://groups.google.com/group/cake-php/msg/8b350d898707dad8'), 'author' => 'subtropolis.z...@gmail.com(subtropolis zijn)', 'pubDate' => 'Fri, 28 Dec 2007 00:45:01 UT' @@ -2829,15 +2829,13 @@ class SetTest extends CakeTestCase { '; - $xml = new Xml($text); + $xml = Xml::build($text); $result = Set::reverse($xml); $expected = array('XRDS' => array( - 'xmlns' => 'xri://$xrds', 'XRD' => array( array( - 'xml:id' => 'oauth', - 'xmlns' => 'xri://$XRD*($v*2.0)', + 'id' => 'oauth', 'version' => '2.0', 'Type' => 'xri://$xrds*simple', 'Expires' => '2008-04-13T07:34:58Z', @@ -2860,7 +2858,6 @@ class SetTest extends CakeTestCase { ) ), array( - 'xmlns' => 'xri://$XRD*($v*2.0)', 'version' => '2.0', 'Type' => 'xri://$xrds*simple', 'Service' => array( From 527446a3d1e716769cfc5969bfa2858bf9dee50f Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 28 Jul 2010 21:29:12 -0300 Subject: [PATCH 11/37] Changing the RequestHandler to use new Xml class. --- cake/libs/controller/components/request_handler.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cake/libs/controller/components/request_handler.php b/cake/libs/controller/components/request_handler.php index 2a9e42ac5..50e7ffc9d 100644 --- a/cake/libs/controller/components/request_handler.php +++ b/cake/libs/controller/components/request_handler.php @@ -240,15 +240,15 @@ class RequestHandlerComponent extends Object { } if ($this->requestedWith('xml')) { - if (!class_exists('XmlNode')) { + if (!class_exists('Xml')) { App::import('Core', 'Xml'); } - $xml = new Xml(trim(file_get_contents('php://input'))); + $xml = Xml::build(trim(file_get_contents('php://input'))); - if (count($xml->children) == 1 && is_object($dataNode = $xml->child('data'))) { - $controller->data = $dataNode->toArray(); + if (isset($xml->data)) { + $controller->data = Xml::toArray($xml->data); } else { - $controller->data = $xml->toArray(); + $controller->data = Xml::toArray($xml); } } } From b1c41e5e18ad773dcee5364bc9c6889e81d92b17 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 22 Aug 2010 23:19:54 -0400 Subject: [PATCH 12/37] Making tests use dataProviders instead of repeated test methods. --- cake/tests/cases/libs/xml.test.php | 119 +++++++++-------------------- 1 file changed, 35 insertions(+), 84 deletions(-) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 3a460154e..1eb23d847 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -56,34 +56,29 @@ class XmlTest extends CakeTestCase { $this->assertEqual((string)$obj, 'value'); } +/** + * data provider function for testBuildInvalidData + * + * @return void + */ + public static function invalidDataProvider() { + return array( + array(null), + array(false), + array(''), + array('') + ); + } + /** * testBuildInvalidData * + * @dataProvider invalidDataProvider * @expectedException Exception * return void */ - function testBuildInvalidData() { - Xml::build(false); - } - -/** - * testBuildEmptyData - * - * @expectedException Exception - * return void - */ - function testBuildEmptyData() { - Xml::build(''); - } - -/** - * testBuildInvalidTag - * - * @expectedException Exception - * return void - */ - function testBuildInvalidTag() { - Xml::build(''); + function testBuildInvalidData($value) { + Xml::build($value); } /** @@ -160,49 +155,20 @@ class XmlTest extends CakeTestCase { } /** - * testFromArrayFail method + * data provider for fromArray() failures * - * @access public * @return void */ - function testFromArrayFail() { - try { - Xml::fromArray(false); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } - - try { - Xml::fromArray(array()); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } - - try { - Xml::fromArray(array('numeric key as root')); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } - - try { - Xml::fromArray(array('item1' => '', 'item2' => '')); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } - - try { - Xml::fromArray(array('items' => array('item1', 'item2'))); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertTrue(true); - } - - try { - $xml = array( + public static function invalidArrayDataProvider() { + return array( + array(''), + array(null), + array(false), + array(array()), + array('numeric key as root'), + array('item1' => '', 'item2' => ''), + array('items' => array('item1', 'item2')), + array(array( 'tags' => array( 'tag' => array( array( @@ -212,34 +178,19 @@ class XmlTest extends CakeTestCase { ) ) ) - ); - Xml::fromArray($xml); - $this->fail('No exception thrown'); - } catch (Exception $e) { - $this->assertEqual($e->getMessage(), __('Invalid array')); - } - + )), + array(new DateTime()) + ); } /** - * testToArrayInvalidObject + * testFromArrayFail method * + * @dataProvider invalidArrayDataProvider * @expectedException Exception - * @return void */ - function testToArrayInvalidObject() { - $obj = new DateTime(); - Xml::toArray($obj); - } - -/** - * testToArrayNoObject - * - * @expectedException Exception - * @return void - */ - function testToArrayNoObject() { - Xml::toArray(array()); + function testFromArrayFail($value) { + Xml::fromArray($value); } /** From 9611ab10279b10b76071bf59d1360bd65265a1f2 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 23 Aug 2010 00:24:56 -0300 Subject: [PATCH 13/37] Changing to Xml::toArray() return @ after attributes to dont conflict with tags with same name. --- cake/libs/xml.php | 2 +- cake/tests/cases/libs/xml.test.php | 56 +++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 5aecdc845..c82cd65e7 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -136,7 +136,7 @@ class Xml { foreach ($namespaces as $namespace) { foreach ($xml->attributes($namespace, true) as $key => $value) { - $data[$key] = (string)$value; + $data['@' . $key] = (string)$value; } foreach ($xml->children($namespace, true) as $child) { diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 3a460154e..1f2553cb5 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -256,6 +256,22 @@ class XmlTest extends CakeTestCase { $xml = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'sample.xml'; $obj = Xml::build($xml); $expected = array( + 'tags' => array( + 'tag' => array( + array( + '@id' => '1', + 'name' => 'defect' + ), + array( + '@id' => '2', + 'name' => 'enhancement' + ) + ) + ) + ); + $this->assertEqual(Xml::toArray($obj), $expected); + + $array = array( 'tags' => array( 'tag' => array( array( @@ -269,11 +285,25 @@ class XmlTest extends CakeTestCase { ) ) ); - $this->assertEqual(Xml::toArray($obj), $expected); - $this->assertEqual(Xml::toArray(Xml::fromArray($expected)), $expected); - $this->assertEqual(Xml::toArray(Xml::fromArray($expected, 'tags')), $expected); + $this->assertEqual(Xml::toArray(Xml::fromArray($array, 'tags')), $array); $expected = array( + 'tags' => array( + 'tag' => array( + array( + '@id' => '1', + '@name' => 'defect' + ), + array( + '@id' => '2', + '@name' => 'enhancement' + ) + ) + ) + ); + $this->assertEqual(Xml::toArray(Xml::fromArray($array)), $expected); + + $array = array( 'tags' => array( 'tag' => array( 'id' => '1', @@ -289,7 +319,23 @@ class XmlTest extends CakeTestCase { ) ) ); - $this->assertEqual(Xml::toArray(Xml::fromArray($expected)), $expected); + $expected = array( + 'tags' => array( + 'tag' => array( + '@id' => '1', + 'posts' => array( + array('@id' => '1'), + array('@id' => '2') + ) + ), + 'tagOther' => array( + 'subtag' => array( + '@id' => '1' + ) + ) + ) + ); + $this->assertEqual(Xml::toArray(Xml::fromArray($array)), $expected); $xml = ''; $xml .= 'defect'; @@ -299,7 +345,7 @@ class XmlTest extends CakeTestCase { $expected = array( 'root' => array( 'tag' => array( - 'id' => 1, + '@id' => 1, 'value' => 'defect' ) ) From f777bd983f0f12d3e9b5ca020439a0c3cf3a29df Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 23 Aug 2010 00:48:34 -0300 Subject: [PATCH 14/37] Making Xml::fromArray() parse @ in attibutes keys. --- cake/libs/xml.php | 8 +++++- cake/tests/cases/libs/xml.test.php | 39 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index c82cd65e7..fa783ffd1 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -85,12 +85,18 @@ class Xml { } elseif ($value === null) { $value = ''; } - if ($format === 'tags') { + if ($key[0] !== '@' && $format === 'tags') { $node->addChild($key, $value); } else { + if ($key[0] === '@') { + $key = substr($key, 1); + } $node->addAttribute($key, $value); } } else { + if ($key[0] === '@') { + throw new Exception(__('Invalid array')); + } if (array_keys($value) === range(0, count($value) - 1)) { // List foreach ($value as $item) { $child = $node->addChild($key); diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 1f2553cb5..1b5d77d0e 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -157,6 +157,24 @@ class XmlTest extends CakeTestCase { $this->assertIdentical((string)$obj->string, 'ok'); $this->assertIdentical((string)$obj->null, ''); $this->assertIdentical((string)$obj->array, ''); + + $xml = array( + 'tags' => array( + 'tag' => array( + array( + '@id' => '1', + 'name' => 'defect' + ), + array( + '@id' => '2', + 'name' => 'enhancement' + ) + ) + ) + ); + $obj = Xml::fromArray($xml, 'tags'); + $xmlText = '<' . '?xml version="1.0"?>defectenhancement'; + $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); } /** @@ -219,6 +237,26 @@ class XmlTest extends CakeTestCase { $this->assertEqual($e->getMessage(), __('Invalid array')); } + try { + $xml = array( + 'tags' => array( + '@tag' => array( + array( + '@id' => '1', + 'name' => 'defect' + ), + array( + '@id' => '2', + 'name' => 'enhancement' + ) + ) + ) + ); + $obj = Xml::fromArray($xml); + $this->fail('No exception thrown'); + } catch (Exception $e) { + $this->assertEqual($e->getMessage(), __('Invalid array')); + } } /** @@ -302,6 +340,7 @@ class XmlTest extends CakeTestCase { ) ); $this->assertEqual(Xml::toArray(Xml::fromArray($array)), $expected); + $this->assertEqual(Xml::toArray(Xml::fromArray($array, 'tags')), $array); $array = array( 'tags' => array( From ac36bc5894c22f394724a647e2226b24a2243896 Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 23 Aug 2010 00:01:55 -0400 Subject: [PATCH 15/37] Adding some docblocks to Xml. --- cake/libs/xml.php | 66 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 5aecdc845..68f8e3868 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -25,8 +25,45 @@ class Xml { /** * Initialize SimpleXMLElement from a given XML string, file path, URL or array. * + * ### Usage: + * + * Building XML from a string: + * + * `$xml = Xml::build('text');` + * + * Building XML from a file path: + * + * `$xml = Xml::build('/path/to/an/xml/file.xml');` + * + * Building from a remote URL: + * + * `$xml = Xml::build('http://example.com/example.xml');` + * + * Building from an array: + * + * {{{ + * $value = array( + * 'tags' => array( + * 'tag' => array( + * array( + * 'id' => '1', + * 'name' => 'defect' + * ), + * array( + * 'id' => '2', + * 'name' => 'enhancement' + * ) + * ) + * ) + * ); + * $xml = Xml::build($value); + * }}} + * + * When building XML from an array ensure that there is only one top level element. + * * @param mixed $input XML string, a path to a file, an URL or an array * @return object SimpleXMLElement + * @throws Exception */ public static function build($input) { if (is_array($input) || is_object($input)) { @@ -42,10 +79,31 @@ class Xml { } /** - * Transform an array in a SimpleXMLElement + * Transform an array into a SimpleXMLElement + * + * Using the following data: + * + * {{{ + * $value = array( + * 'root' => array( + * 'tag' => array( + * 'id' => 1, + * 'value' => 'defect' + * ) + * ) + * ); + * }}} + * + * Calling `Xml::fromArray($value, 'tags');` Will generate: + * + * `1defect` + * + * And calling `Xml::fromArray($value, 'attribute');` Will generate: + * + * `defect` * * @param array $input Array with data - * @param string $format If create childs ('tags') or attributes ('attribute') + * @param string $format If create childs ('tags') or attributes ('attribute'). * @return object SimpleXMLElement */ public static function fromArray($input, $format = 'attribute') { @@ -69,8 +127,8 @@ class Xml { * Recursive method to create SimpleXMLElement from array * * @param object $node Handler to SimpleXMLElement - * @param array $array - * @param string $format + * @param array $array Array of data to append to the $node. + * @param string $format Either 'attribute' or 'tags'. This determines where nested keys go. * @return void */ protected static function _fromArrayRecursive(&$node, &$array, $format = 'attribute') { From f8b4d92a3406393e7adcd2fca9a29274a3122c66 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 23 Aug 2010 01:33:06 -0300 Subject: [PATCH 16/37] Arrays with @ as key now is the value of tag in Xml. --- cake/libs/xml.php | 23 +++++++++++++++--- cake/tests/cases/libs/xml.test.php | 39 +++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index fa783ffd1..dfffdf904 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -57,7 +57,12 @@ class Xml { throw new Exception(__('The key of input must be alphanumeric')); } if (is_array($input[$key])) { - $simpleXml = new SimpleXMLElement('<' . '?xml version="1.0"?' . '><' . $key . ' />'); + if (array_key_exists('@', $input[$key])) { + $simpleXml = new SimpleXMLElement('<' . '?xml version="1.0"?' . '><' . $key . '>' . $input[$key]['@'] . ''); + unset($input[$key]['@']); + } else { + $simpleXml = new SimpleXMLElement('<' . '?xml version="1.0"?' . '><' . $key . ' />'); + } self::_fromArrayRecursive($simpleXml, $input[$key], $format); } else { $simpleXml = new SimpleXMLElement('<' . '?xml version="1.0"?' . '><' . $key . '>' . $input[$key] . ''); @@ -99,11 +104,21 @@ class Xml { } if (array_keys($value) === range(0, count($value) - 1)) { // List foreach ($value as $item) { - $child = $node->addChild($key); + if (array_key_exists('@', $item)) { + $child = $node->addChild($key, (string)$item['@']); + unset($item['@']); + } else { + $child = $node->addChild($key); + } self::_fromArrayRecursive($child, $item, $format); } } else { // Struct - $child = $node->addChild($key); + if (array_key_exists('@', $value)) { + $child = $node->addChild($key, (string)$value['@']); + unset($value['@']); + } else { + $child = $node->addChild($key); + } self::_fromArrayRecursive($child, $value, $format); } } @@ -154,7 +169,7 @@ class Xml { if (empty($data)) { $data = $asString; } elseif (!empty($asString)) { - $data['value'] = $asString; + $data['@'] = $asString; } $name = $xml->getName(); diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 1b5d77d0e..5545775a6 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -103,6 +103,11 @@ class XmlTest extends CakeTestCase { $this->assertEqual($obj->getName(), 'tag'); $this->assertEqual((string)$obj, ''); + $xml = array('tag' => array('@' => 'value')); + $obj = Xml::fromArray($xml); + $this->assertEqual($obj->getName(), 'tag'); + $this->assertEqual((string)$obj, 'value'); + $xml = array( 'tags' => array( 'tag' => array( @@ -175,6 +180,38 @@ class XmlTest extends CakeTestCase { $obj = Xml::fromArray($xml, 'tags'); $xmlText = '<' . '?xml version="1.0"?>defectenhancement'; $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); + + $xml = array( + 'tags' => array( + 'tag' => array( + array( + '@id' => '1', + 'name' => 'defect', + '@' => 'Tag 1' + ), + array( + '@id' => '2', + 'name' => 'enhancement' + ), + ), + '@' => 'All tags' + ) + ); + $obj = Xml::fromArray($xml, 'tags'); + $xmlText = '<' . '?xml version="1.0"?>All tagsTag 1defectenhancement'; + $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); + + $xml = array( + 'tags' => array( + 'tag' => array( + 'id' => 1, + '@' => 'defect' + ) + ) + ); + $obj = Xml::fromArray($xml); + $xmlText = '<' . '?xml version="1.0"?>defect'; + $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); } /** @@ -385,7 +422,7 @@ class XmlTest extends CakeTestCase { 'root' => array( 'tag' => array( '@id' => 1, - 'value' => 'defect' + '@' => 'defect' ) ) ); From 84cf5d599cf7dae7eba93376c7c5297c070d961d Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 23 Aug 2010 18:23:53 -0300 Subject: [PATCH 17/37] Changing to use array in tests. --- cake/tests/cases/libs/xml.test.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 77af1919e..2b0dbc3ae 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -59,7 +59,7 @@ class XmlTest extends CakeTestCase { /** * data provider function for testBuildInvalidData * - * @return void + * @return array */ public static function invalidDataProvider() { return array( @@ -212,7 +212,7 @@ class XmlTest extends CakeTestCase { /** * data provider for fromArray() failures * - * @return void + * @return array */ public static function invalidArrayDataProvider() { return array( @@ -220,9 +220,9 @@ class XmlTest extends CakeTestCase { array(null), array(false), array(array()), - array('numeric key as root'), - array('item1' => '', 'item2' => ''), - array('items' => array('item1', 'item2')), + array(array('numeric key as root')), + array(array('item1' => '', 'item2' => '')), + array(array('items' => array('item1', 'item2'))), array(array( 'tags' => array( 'tag' => array( From 52b09188f78702dba07d31c432779bb982e947e5 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 23 Aug 2010 18:30:03 -0300 Subject: [PATCH 18/37] Adding fail tests to Xml::toArray(). --- cake/tests/cases/libs/xml.test.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 2b0dbc3ae..a423012b2 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -406,4 +406,26 @@ class XmlTest extends CakeTestCase { $this->assertEqual(Xml::toArray($obj), $expected); } +/** + * data provider for toArray() failures + * + * @return array + */ + public static function invalidToArrayDataProvider() { + return array( + array(new DateTime()), + array(array()) + ); + } + +/** + * testToArrayFail method + * + * @dataProvider invalidToArrayDataProvider + * @expectedException Exception + */ + function testToArrayFail($value) { + Xml::toArray($value); + } + } From edcb5e4370443ef42fbe351334f65a82422942f7 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 1 Sep 2010 20:06:56 -0300 Subject: [PATCH 19/37] Added tests to use Xml with RSS. --- cake/tests/cases/libs/xml.test.php | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index a423012b2..a04a38b3c 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -406,6 +406,61 @@ class XmlTest extends CakeTestCase { $this->assertEqual(Xml::toArray($obj), $expected); } +/** + * testToArrayRss + * + * @return void + */ + function testToArrayRss() { + $rss = << + + + + The Bakery: + http://bakery.cakephp.org/ + Recent Articles at The Bakery. + en-us + Wed, 01 Sep 2010 12:09:25 -0500 + http://validator.w3.org/feed/docs/rss2.html + CakePHP Bakery + mariano@cricava.com (Mariano Iglesias) + gwoo@cakephp.org (Garrett Woodworth) + + EpisodeCMS + http://bakery.cakephp.org/articles/view/episodecms + EpisodeCMS is CakePHP based content management system. +Features: control panel, events API, module management, multilanguage and translations, themes +http://episodecms.com/ + +Please help me to improve it. Thanks. + Tue, 31 Aug 2010 02:07:02 -0500 + http://bakery.cakephp.org/articles/view/episodecms + + + Alertpay automated sales via IPN + http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn + I'm going to show you how I implemented a payment module via the Alertpay payment processor. + Tue, 31 Aug 2010 01:42:00 -0500 + http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn + + + +EOF; + $rssAsArray = Xml::toArray(Xml::build($rss)); + $this->assertEqual($rssAsArray['rss']['@version'], '2.0'); + $this->assertEqual(count($rssAsArray['rss']['channel']['item']), 2); + + $expected = array( + 'title' => 'Alertpay automated sales via IPN', + 'link' => 'http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn', + 'description' => 'I\'m going to show you how I implemented a payment module via the Alertpay payment processor.', + 'pubDate' => 'Tue, 31 Aug 2010 01:42:00 -0500', + 'guid' => 'http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn' + ); + $this->assertIdentical($rssAsArray['rss']['channel']['item'][1], $expected); + } + /** * data provider for toArray() failures * From 26b514e2cd90dde4e4535fac9da2c903dcfe96c5 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 1 Sep 2010 20:09:03 -0300 Subject: [PATCH 20/37] Chaning tests of Xml to public. --- cake/tests/cases/libs/xml.test.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index a04a38b3c..77c735333 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -30,10 +30,9 @@ class XmlTest extends CakeTestCase { /** * testBuild method * - * @access public * @return void */ - function testBuild() { + public function testBuild() { $xml = 'value'; $obj = Xml::build($xml); $this->assertTrue($obj instanceof SimpleXMLElement); @@ -77,17 +76,16 @@ class XmlTest extends CakeTestCase { * @expectedException Exception * return void */ - function testBuildInvalidData($value) { + public function testBuildInvalidData($value) { Xml::build($value); } /** * testFromArray method * - * @access public * @return void */ - function testFromArray() { + public function testFromArray() { $xml = array('tag' => 'value'); $obj = Xml::fromArray($xml); $this->assertEqual($obj->getName(), 'tag'); @@ -258,17 +256,16 @@ class XmlTest extends CakeTestCase { * @dataProvider invalidArrayDataProvider * @expectedException Exception */ - function testFromArrayFail($value) { + public function testFromArrayFail($value) { Xml::fromArray($value); } /** * testToArray method * - * @access public * @return void */ - function testToArray() { + public function testToArray() { $xml = 'name'; $obj = Xml::build($xml); $this->assertEqual(Xml::toArray($obj), array('tag' => 'name')); @@ -411,7 +408,7 @@ class XmlTest extends CakeTestCase { * * @return void */ - function testToArrayRss() { + public function testToArrayRss() { $rss = << @@ -479,7 +476,7 @@ EOF; * @dataProvider invalidToArrayDataProvider * @expectedException Exception */ - function testToArrayFail($value) { + public function testToArrayFail($value) { Xml::toArray($value); } From ff29897badedd8adf40ea693a61bc887f2b3df16 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 1 Sep 2010 20:22:39 -0300 Subject: [PATCH 21/37] Moving the rss content to a file. --- cake/tests/cases/libs/xml.test.php | 36 +----------------------------- cake/tests/fixtures/rss.xml | 33 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 35 deletions(-) create mode 100644 cake/tests/fixtures/rss.xml diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 77c735333..ea44547e4 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -409,41 +409,7 @@ class XmlTest extends CakeTestCase { * @return void */ public function testToArrayRss() { - $rss = << - - - - The Bakery: - http://bakery.cakephp.org/ - Recent Articles at The Bakery. - en-us - Wed, 01 Sep 2010 12:09:25 -0500 - http://validator.w3.org/feed/docs/rss2.html - CakePHP Bakery - mariano@cricava.com (Mariano Iglesias) - gwoo@cakephp.org (Garrett Woodworth) - - EpisodeCMS - http://bakery.cakephp.org/articles/view/episodecms - EpisodeCMS is CakePHP based content management system. -Features: control panel, events API, module management, multilanguage and translations, themes -http://episodecms.com/ - -Please help me to improve it. Thanks. - Tue, 31 Aug 2010 02:07:02 -0500 - http://bakery.cakephp.org/articles/view/episodecms - - - Alertpay automated sales via IPN - http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn - I'm going to show you how I implemented a payment module via the Alertpay payment processor. - Tue, 31 Aug 2010 01:42:00 -0500 - http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn - - - -EOF; + $rss = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'rss.xml'); $rssAsArray = Xml::toArray(Xml::build($rss)); $this->assertEqual($rssAsArray['rss']['@version'], '2.0'); $this->assertEqual(count($rssAsArray['rss']['channel']['item']), 2); diff --git a/cake/tests/fixtures/rss.xml b/cake/tests/fixtures/rss.xml new file mode 100644 index 000000000..172649d40 --- /dev/null +++ b/cake/tests/fixtures/rss.xml @@ -0,0 +1,33 @@ + + + + + The Bakery: + http://bakery.cakephp.org/ + Recent Articles at The Bakery. + en-us + Wed, 01 Sep 2010 12:09:25 -0500 + http://validator.w3.org/feed/docs/rss2.html + CakePHP Bakery + mariano@cricava.com (Mariano Iglesias) + gwoo@cakephp.org (Garrett Woodworth) + + EpisodeCMS + http://bakery.cakephp.org/articles/view/episodecms + EpisodeCMS is CakePHP based content management system. +Features: control panel, events API, module management, multilanguage and translations, themes +http://episodecms.com/ + +Please help me to improve it. Thanks. + Tue, 31 Aug 2010 02:07:02 -0500 + http://bakery.cakephp.org/articles/view/episodecms + + + Alertpay automated sales via IPN + http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn + I'm going to show you how I implemented a payment module via the Alertpay payment processor. + Tue, 31 Aug 2010 01:42:00 -0500 + http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn + + + \ No newline at end of file From a1eeb03e36417e77be0b09f613e168755b7e2991 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 1 Sep 2010 21:04:53 -0300 Subject: [PATCH 22/37] Added tests to use Xml with XML-RPC. --- cake/tests/cases/libs/xml.test.php | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index ea44547e4..d67a10917 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -424,6 +424,71 @@ class XmlTest extends CakeTestCase { $this->assertIdentical($rssAsArray['rss']['channel']['item'][1], $expected); } +/** + * testXmlRpc + * + * @return void + */ + public function testXmlRpc() { + $xml = Xml::build('test'); + $expected = array( + 'methodCall' => array( + 'methodName' => 'test', + 'params' => '' + ) + ); + $this->assertIdentical(Xml::toArray($xml), $expected); + + $xml = Xml::build('test12Egypt0-31'); + $expected = array( + 'methodCall' => array( + 'methodName' => 'test', + 'params' => array( + 'param' => array( + 'value' => array( + 'array' => array( + 'data' => array( + 'value' => array( + array('int' => '12'), + array('string' => 'Egypt'), + array('boolean' => '0'), + array('int' => '-31') + ) + ) + ) + ) + ) + ) + ) + ); + $this->assertIdentical(Xml::toArray($xml), $expected); + + $xmlText = '1testing'; + $xml = Xml::build($xmlText); + $expected = array( + 'methodResponse' => array( + 'params' => array( + 'param' => array( + 'value' => array( + 'array' => array( + 'data' => array( + 'value' => array( + array('int' => '1'), + array('string' => 'testing') + ) + ) + ) + ) + ) + ) + ) + ); + $this->assertIdentical(Xml::toArray($xml), $expected); + + $xml = Xml::fromArray($expected, 'tags'); + $this->assertEqual(str_replace(array("\r", "\n"), '', $xml->asXML()), $xmlText); + } + /** * data provider for toArray() failures * From 9239caa5893b4f54d0c155ea0a7617f650dc731c Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 1 Sep 2010 21:37:32 -0300 Subject: [PATCH 23/37] Added tests to use Xml with SOAP. --- cake/tests/cases/libs/xml.test.php | 32 +++++++++++++++++++++++++++ cake/tests/fixtures/soap_request.xml | 12 ++++++++++ cake/tests/fixtures/soap_response.xml | 12 ++++++++++ 3 files changed, 56 insertions(+) create mode 100644 cake/tests/fixtures/soap_request.xml create mode 100644 cake/tests/fixtures/soap_response.xml diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index d67a10917..05c9f165b 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -489,6 +489,38 @@ class XmlTest extends CakeTestCase { $this->assertEqual(str_replace(array("\r", "\n"), '', $xml->asXML()), $xmlText); } +/** + * testSoap + * + * @return void + */ + public function testSoap() { + $xmlRequest = Xml::build(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'soap_request.xml'); + $expected = array( + 'Envelope' => array( + '@encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding', + 'Body' => array( + 'GetStockPrice' => array( + 'StockName' => 'IBM' + ) + ) + ) + ); + $this->assertEqual(Xml::toArray($xmlRequest), $expected); + + $xmlResponse = Xml::build(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'soap_response.xml'); + $expected = array( + 'Envelope' => array( + '@encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding', + 'Body' => array( + 'GetStockPriceResponse' => array( + 'Price' => '34.5' + ) + ) + ) + ); + } + /** * data provider for toArray() failures * diff --git a/cake/tests/fixtures/soap_request.xml b/cake/tests/fixtures/soap_request.xml new file mode 100644 index 000000000..cd3a238c4 --- /dev/null +++ b/cake/tests/fixtures/soap_request.xml @@ -0,0 +1,12 @@ + + + + + + IBM + + + + \ No newline at end of file diff --git a/cake/tests/fixtures/soap_response.xml b/cake/tests/fixtures/soap_response.xml new file mode 100644 index 000000000..c9e9de538 --- /dev/null +++ b/cake/tests/fixtures/soap_response.xml @@ -0,0 +1,12 @@ + + + + + + 34.5 + + + + \ No newline at end of file From 87604f92c7cbc821e7394fb8a9a672dd52c826c9 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 6 Sep 2010 20:17:30 -0300 Subject: [PATCH 24/37] Adding a missing assert to tests. --- cake/tests/cases/libs/xml.test.php | 1 + 1 file changed, 1 insertion(+) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 05c9f165b..74dcb464c 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -519,6 +519,7 @@ class XmlTest extends CakeTestCase { ) ) ); + $this->assertEqual(Xml::toArray($xmlResponse), $expected); } /** From 5f902d42e0dfe3b231d48f00e1ed142bb7531914 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 6 Sep 2010 23:11:45 -0300 Subject: [PATCH 25/37] Changing the default format of fromArray from attributes to tags. --- cake/libs/xml.php | 2 +- cake/tests/cases/libs/xml.test.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index c55fddfae..3f6dd1e1c 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -106,7 +106,7 @@ class Xml { * @param string $format If create childs ('tags') or attributes ('attribute'). * @return object SimpleXMLElement */ - public static function fromArray($input, $format = 'attribute') { + public static function fromArray($input, $format = 'tags') { if (!is_array($input) || count($input) !== 1) { throw new Exception(__('Invalid input.')); } diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 74dcb464c..80c06a7d8 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -115,14 +115,14 @@ class XmlTest extends CakeTestCase { ) ) ); - $obj = Xml::fromArray($xml); + $obj = Xml::fromArray($xml, 'attributes'); $this->assertTrue($obj instanceof SimpleXMLElement); $this->assertEqual($obj->getName(), 'tags'); $this->assertEqual(count($obj), 2); $xmlText = '<' . '?xml version="1.0"?>'; $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); - $obj = Xml::fromArray($xml, 'tags'); + $obj = Xml::fromArray($xml); $this->assertTrue($obj instanceof SimpleXMLElement); $this->assertEqual($obj->getName(), 'tags'); $this->assertEqual(count($obj), 2); @@ -202,7 +202,7 @@ class XmlTest extends CakeTestCase { ) ) ); - $obj = Xml::fromArray($xml); + $obj = Xml::fromArray($xml, 'attributes'); $xmlText = '<' . '?xml version="1.0"?>defect'; $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); } @@ -318,8 +318,8 @@ class XmlTest extends CakeTestCase { ) ) ); - $this->assertEqual(Xml::toArray(Xml::fromArray($array)), $expected); - $this->assertEqual(Xml::toArray(Xml::fromArray($array, 'tags')), $array); + $this->assertEqual(Xml::toArray(Xml::fromArray($array, 'attributes')), $expected); + $this->assertEqual(Xml::toArray(Xml::fromArray($array)), $array); $array = array( 'tags' => array( @@ -353,7 +353,7 @@ class XmlTest extends CakeTestCase { ) ) ); - $this->assertEqual(Xml::toArray(Xml::fromArray($array)), $expected); + $this->assertEqual(Xml::toArray(Xml::fromArray($array, 'attributes')), $expected); $xml = ''; $xml .= 'defect'; From c1a75cbc4708d18a6f05f762cca95941ee661675 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Tue, 7 Sep 2010 00:31:50 -0300 Subject: [PATCH 26/37] Support to namespaces in Xml::toArray(). --- cake/libs/xml.php | 15 +++++-- cake/tests/cases/libs/xml.test.php | 66 ++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 3f6dd1e1c..4b5699e68 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -198,7 +198,7 @@ class Xml { } $result = array(); $namespaces = array_merge(array('' => ''), $simpleXML->getNamespaces(true)); - self::_toArray($simpleXML, $result, array_keys($namespaces)); + self::_toArray($simpleXML, $result, '', array_keys($namespaces)); return $result; } @@ -207,19 +207,23 @@ class Xml { * * @param object $xml SimpleXMLElement object * @param array $parentData Parent array with data + * @param string $ns Namespace of current child * @param array $namespaces List of namespaces in XML * @return void */ - protected static function _toArray($xml, &$parentData, $namespaces) { + protected static function _toArray($xml, &$parentData, $ns, $namespaces) { $data = array(); foreach ($namespaces as $namespace) { foreach ($xml->attributes($namespace, true) as $key => $value) { + if (!empty($namespace)) { + $key = $namespace . ':' . $key; + } $data['@' . $key] = (string)$value; } foreach ($xml->children($namespace, true) as $child) { - self::_toArray($child, $data, $namespaces); + self::_toArray($child, $data, $namespace, $namespaces); } } @@ -230,7 +234,10 @@ class Xml { $data['@'] = $asString; } - $name = $xml->getName(); + if (!empty($ns)) { + $ns .= ':'; + } + $name = $ns . $xml->getName(); if (isset($parentData[$name])) { if (!is_array($parentData[$name]) || !isset($parentData[$name][0])) { $parentData[$name] = array($parentData[$name]); diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 80c06a7d8..61c88e233 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -397,7 +397,7 @@ class XmlTest extends CakeTestCase { $expected = array( 'root' => array( 'tag' => 'defect', - 'bug' => 1 + 'cake:bug' => 1 ) ); $this->assertEqual(Xml::toArray($obj), $expected); @@ -414,6 +414,10 @@ class XmlTest extends CakeTestCase { $this->assertEqual($rssAsArray['rss']['@version'], '2.0'); $this->assertEqual(count($rssAsArray['rss']['channel']['item']), 2); + $atomLink = array('@href' => 'http://bakery.cakephp.org/articles/rss', '@rel' => 'self', '@type' => 'application/rss+xml'); + $this->assertEqual($rssAsArray['rss']['channel']['atom:link'], $atomLink); + $this->assertEqual($rssAsArray['rss']['channel']['link'], 'http://bakery.cakephp.org/'); + $expected = array( 'title' => 'Alertpay automated sales via IPN', 'link' => 'http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn', @@ -498,10 +502,10 @@ class XmlTest extends CakeTestCase { $xmlRequest = Xml::build(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'soap_request.xml'); $expected = array( 'Envelope' => array( - '@encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding', - 'Body' => array( - 'GetStockPrice' => array( - 'StockName' => 'IBM' + '@soap:encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding', + 'soap:Body' => array( + 'm:GetStockPrice' => array( + 'm:StockName' => 'IBM' ) ) ) @@ -511,10 +515,10 @@ class XmlTest extends CakeTestCase { $xmlResponse = Xml::build(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'soap_response.xml'); $expected = array( 'Envelope' => array( - '@encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding', - 'Body' => array( - 'GetStockPriceResponse' => array( - 'Price' => '34.5' + '@soap:encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding', + 'soap:Body' => array( + 'm:GetStockPriceResponse' => array( + 'm:Price' => '34.5' ) ) ) @@ -522,6 +526,50 @@ class XmlTest extends CakeTestCase { $this->assertEqual(Xml::toArray($xmlResponse), $expected); } +/** + * testNamespace + * + * @retun void + */ + public function testNamespace() { + $xmlResponse = Xml::build('goodbadTag without ns'); + $expected = array( + 'root' => array( + 'ns:tag' => array( + '@id' => '1', + 'child' => 'good', + 'otherchild' => 'bad' + ), + 'tag' => 'Tag without ns' + ) + ); + $this->assertEqual(Xml::toArray($xmlResponse), $expected); + + $xmlResponse = Xml::build('1'); + $expected = array( + 'root' => array( + 'ns:tag' => array( + '@id' => '1' + ), + 'tag' => array( + 'id' => '1' + ) + ) + ); + $this->assertEqual(Xml::toArray($xmlResponse), $expected); + + $xmlResponse = Xml::build('1'); + $expected = array( + 'root' => array( + 'ns:attr' => '1' + ) + ); + $this->assertEqual(Xml::toArray($xmlResponse), $expected); + + $xmlResponse = Xml::build('1'); + $this->assertEqual(Xml::toArray($xmlResponse), $expected); + } + /** * data provider for toArray() failures * From ca7e327af182d4674c58d53e9e841e54d6f493a7 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Tue, 7 Sep 2010 18:12:35 -0300 Subject: [PATCH 27/37] Fixing the documentation. --- cake/libs/xml.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 4b5699e68..cf5044af8 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -88,7 +88,8 @@ class Xml { * 'root' => array( * 'tag' => array( * 'id' => 1, - * 'value' => 'defect' + * 'value' => 'defect', + * '@' => 'description' * ) * ) * ); @@ -96,11 +97,11 @@ class Xml { * * Calling `Xml::fromArray($value, 'tags');` Will generate: * - * `1defect` + * `1defectdescription` * * And calling `Xml::fromArray($value, 'attribute');` Will generate: * - * `defect` + * `description` * * @param array $input Array with data * @param string $format If create childs ('tags') or attributes ('attribute'). From a8b6182d43c3690ddeb02c934ed7982268a695b4 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 8 Sep 2010 19:18:58 -0300 Subject: [PATCH 28/37] Changing Xml::fromArray to use DOMDocument unless SimpleXMLElement. The return continue as SimpleXMLElement. --- cake/libs/xml.php | 49 ++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index cf5044af8..5884b2815 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -115,33 +115,25 @@ class Xml { if (is_integer($key)) { throw new Exception(__('The key of input must be alphanumeric')); } - if (is_array($input[$key])) { - if (array_key_exists('@', $input[$key])) { - $simpleXml = new SimpleXMLElement('<' . '?xml version="1.0"?' . '><' . $key . '>' . $input[$key]['@'] . ''); - unset($input[$key]['@']); - } else { - $simpleXml = new SimpleXMLElement('<' . '?xml version="1.0"?' . '><' . $key . ' />'); - } - self::_fromArrayRecursive($simpleXml, $input[$key], $format); - } else { - $simpleXml = new SimpleXMLElement('<' . '?xml version="1.0"?' . '><' . $key . '>' . $input[$key] . ''); - } - return $simpleXml; + $dom = new DOMDocument('1.0'); + self::_fromArray($dom, $dom, $input, $format); + return new SimpleXMLElement($dom->saveXML()); } /** - * Recursive method to create SimpleXMLElement from array + * Recursive method to create childs from array * - * @param object $node Handler to SimpleXMLElement - * @param array $array Array of data to append to the $node. + * @param object $dom Handler to DOMDocument + * @param object $node Handler to DOMElement (child) + * @param array $data Array of data to append to the $node. * @param string $format Either 'attribute' or 'tags'. This determines where nested keys go. * @return void */ - protected static function _fromArrayRecursive(&$node, &$array, $format = 'attribute') { - if (empty($array) || !is_array($array)) { + protected function _fromArray(&$dom, &$node, &$data, $format) { + if (empty($data) || !is_array($data)) { return; } - foreach ($array as $key => $value) { + foreach ($data as $key => $value) { if (is_string($key)) { if (!is_array($value)) { if (is_bool($value)) { @@ -150,12 +142,15 @@ class Xml { $value = ''; } if ($key[0] !== '@' && $format === 'tags') { - $node->addChild($key, $value); + $child = $dom->createElement($key, $value); + $node->appendChild($child); } else { if ($key[0] === '@') { $key = substr($key, 1); } - $node->addAttribute($key, $value); + $attribute = $dom->createAttribute($key); + $attribute->appendChild($dom->createTextNode($value)); + $node->appendChild($attribute); } } else { if ($key[0] === '@') { @@ -164,21 +159,23 @@ class Xml { if (array_keys($value) === range(0, count($value) - 1)) { // List foreach ($value as $item) { if (array_key_exists('@', $item)) { - $child = $node->addChild($key, (string)$item['@']); + $child = $dom->createElement($key, (string)$item['@']); unset($item['@']); } else { - $child = $node->addChild($key); + $child = $dom->createElement($key); } - self::_fromArrayRecursive($child, $item, $format); + self::_fromArray($dom, $child, $item, $format); + $node->appendChild($child); } } else { // Struct if (array_key_exists('@', $value)) { - $child = $node->addChild($key, (string)$value['@']); + $child = $dom->createElement($key, (string)$value['@']); unset($value['@']); } else { - $child = $node->addChild($key); + $child = $dom->createElement($key); } - self::_fromArrayRecursive($child, $value, $format); + self::_fromArray($dom, $child, $value, $format); + $node->appendChild($child); } } } else { From 071ff04556bade31f8644482c57f13b567f503d5 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 8 Sep 2010 19:40:33 -0300 Subject: [PATCH 29/37] Xml::fromArray now receives a list of options unless simple format. --- cake/libs/xml.php | 36 ++++++++++++++++++++++++----- cake/tests/cases/libs/xml.test.php | 37 ++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 5884b2815..73a5aa62a 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -81,6 +81,13 @@ class Xml { /** * Transform an array into a SimpleXMLElement * + * ### Options + * + * - `format` If create childs ('tags') or attributes ('attribute'). + * - `version` Version of XML document. Default is 1.0. + * - `encoding` Encoding of XML document. Default is the some of application. + * - `return` If return object of SimpleXMLElement ('simplexml') or DOMDocument ('domdocument'). Default is SimpleXMLElement. + * * Using the following data: * * {{{ @@ -104,10 +111,10 @@ class Xml { * `description` * * @param array $input Array with data - * @param string $format If create childs ('tags') or attributes ('attribute'). - * @return object SimpleXMLElement + * @param array $options The options to use + * @return object SimpleXMLElement or DOMDocument */ - public static function fromArray($input, $format = 'tags') { + public static function fromArray($input, $options = array()) { if (!is_array($input) || count($input) !== 1) { throw new Exception(__('Invalid input.')); } @@ -115,9 +122,26 @@ class Xml { if (is_integer($key)) { throw new Exception(__('The key of input must be alphanumeric')); } - $dom = new DOMDocument('1.0'); - self::_fromArray($dom, $dom, $input, $format); - return new SimpleXMLElement($dom->saveXML()); + + if (is_string($options)) { + $options = array('format' => $options); + } + $defaults = array( + 'format' => 'tags', + 'version' => '1.0', + 'encoding' => Configure::read('App.encoding'), + 'return' => 'simplexml' + ); + $options = array_merge($defaults, $options); + + $dom = new DOMDocument($options['version'], $options['encoding']); + self::_fromArray($dom, $dom, $input, $options['format']); + + $options['return'] = strtolower($options['return']); + if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { + return new SimpleXMLElement($dom->saveXML()); + } + return $dom; } /** diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 61c88e233..b682b943b 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -27,6 +27,29 @@ App::import('Core', 'Xml'); */ class XmlTest extends CakeTestCase { +/** + * setup method + * + * @access public + * @return void + */ + function setUp() { + parent::setup(); + $this->_appEncoding = Configure::read('App.encoding'); + Configure::write('App.encoding', 'UTF-8'); + } + +/** + * tearDown method + * + * @access public + * @return void + */ + function tearDown() { + parent::tearDown(); + Configure::write('App.encoding', $this->_appEncoding); + } + /** * testBuild method * @@ -39,7 +62,7 @@ class XmlTest extends CakeTestCase { $this->assertEqual((string)$obj->getName(), 'tag'); $this->assertEqual((string)$obj, 'value'); - $xml = 'value'; + $xml = 'value'; $this->assertEqual($obj, Xml::build($xml)); $xml = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'sample.xml'; @@ -119,14 +142,14 @@ class XmlTest extends CakeTestCase { $this->assertTrue($obj instanceof SimpleXMLElement); $this->assertEqual($obj->getName(), 'tags'); $this->assertEqual(count($obj), 2); - $xmlText = '<' . '?xml version="1.0"?>'; + $xmlText = '<' . '?xml version="1.0" encoding="UTF-8"?>'; $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); $obj = Xml::fromArray($xml); $this->assertTrue($obj instanceof SimpleXMLElement); $this->assertEqual($obj->getName(), 'tags'); $this->assertEqual(count($obj), 2); - $xmlText = '<' . '?xml version="1.0"?>1defect2enhancement'; + $xmlText = '<' . '?xml version="1.0" encoding="UTF-8"?>1defect2enhancement'; $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); $xml = array( @@ -171,7 +194,7 @@ class XmlTest extends CakeTestCase { ) ); $obj = Xml::fromArray($xml, 'tags'); - $xmlText = '<' . '?xml version="1.0"?>defectenhancement'; + $xmlText = '<' . '?xml version="1.0" encoding="UTF-8"?>defectenhancement'; $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); $xml = array( @@ -191,7 +214,7 @@ class XmlTest extends CakeTestCase { ) ); $obj = Xml::fromArray($xml, 'tags'); - $xmlText = '<' . '?xml version="1.0"?>All tagsTag 1defectenhancement'; + $xmlText = '<' . '?xml version="1.0" encoding="UTF-8"?>All tagsTag 1defectenhancement'; $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); $xml = array( @@ -203,7 +226,7 @@ class XmlTest extends CakeTestCase { ) ); $obj = Xml::fromArray($xml, 'attributes'); - $xmlText = '<' . '?xml version="1.0"?>defect'; + $xmlText = '<' . '?xml version="1.0" encoding="UTF-8"?>defect'; $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $xmlText); } @@ -467,7 +490,7 @@ class XmlTest extends CakeTestCase { ); $this->assertIdentical(Xml::toArray($xml), $expected); - $xmlText = '1testing'; + $xmlText = '1testing'; $xml = Xml::build($xmlText); $expected = array( 'methodResponse' => array( From c8c20eafdcdfe0e5666240ca54644430216121e2 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Wed, 8 Sep 2010 20:30:04 -0300 Subject: [PATCH 30/37] Xml::build now supports options and can return DOMDocument. --- cake/libs/xml.php | 34 +++++++++++++++++++++++------- cake/tests/cases/libs/xml.test.php | 15 +++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 73a5aa62a..16abceed5 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -65,13 +65,31 @@ class Xml { * @return object SimpleXMLElement * @throws Exception */ - public static function build($input) { + public static function build($input, $options = array()) { + if (!is_array($options)) { + $options = array('return' => (string)$options); + } + $defaults = array( + 'return' => 'simplexml' + ); + $options = array_merge($defaults, $options); + if (is_array($input) || is_object($input)) { - return self::fromArray((array)$input); - } elseif (strstr($input, "<")) { - return new SimpleXMLElement($input); - } elseif (file_exists($input) || strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0 ) { - return new SimpleXMLElement($input, null, true); + return self::fromArray((array)$input, $options); + } elseif (strpos($input, '<') !== false) { + if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { + return new SimpleXMLElement($input); + } + $dom = new DOMDocument(); + $dom->loadXML($input); + return $dom; + } elseif (file_exists($input) || strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) { + if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { + return new SimpleXMLElement($input, null, true); + } + $dom = new DOMDocument(); + $dom->load($input); + return $dom; } elseif (!is_string($input)) { throw new Exception(__('Invalid input.')); } @@ -123,8 +141,8 @@ class Xml { throw new Exception(__('The key of input must be alphanumeric')); } - if (is_string($options)) { - $options = array('format' => $options); + if (!is_array($options)) { + $options = array('format' => (string)$options); } $defaults = array( 'format' => 'tags', diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index b682b943b..64876a9f1 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -65,6 +65,11 @@ class XmlTest extends CakeTestCase { $xml = 'value'; $this->assertEqual($obj, Xml::build($xml)); + $obj = Xml::build($xml, array('return' => 'domdocument')); + $this->assertTrue($obj instanceof DOMDocument); + $this->assertEqual($obj->firstChild->nodeName, 'tag'); + $this->assertEqual($obj->firstChild->nodeValue, 'value'); + $xml = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'sample.xml'; $obj = Xml::build($xml); $this->assertEqual($obj->getName(), 'tags'); @@ -72,10 +77,20 @@ class XmlTest extends CakeTestCase { $this->assertEqual(Xml::build($xml), Xml::build(file_get_contents($xml))); + $obj = Xml::build($xml, array('return' => 'domdocument')); + $this->assertEqual($obj->firstChild->nodeName, 'tags'); + + $this->assertEqual(Xml::build($xml, array('return' => 'domdocument')), Xml::build(file_get_contents($xml), array('return' => 'domdocument'))); + $this->assertEqual(Xml::build($xml, array('return' => 'simplexml')), Xml::build($xml, 'simplexml')); + $xml = array('tag' => 'value'); $obj = Xml::build($xml); $this->assertEqual($obj->getName(), 'tag'); $this->assertEqual((string)$obj, 'value'); + + $obj = Xml::build($xml, array('return' => 'domdocument')); + $this->assertEqual($obj->firstChild->nodeName, 'tag'); + $this->assertEqual($obj->firstChild->nodeValue, 'value'); } /** From 2e7851c2be5258b76cb9ac685be52d1c19795330 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sat, 11 Sep 2010 12:36:43 -0300 Subject: [PATCH 31/37] Support to namespaces in Xml::fromArray(). --- cake/libs/xml.php | 61 ++++++++++++++++++++++-------- cake/tests/cases/libs/xml.test.php | 56 +++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 16 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 16abceed5..7c71a23dd 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -183,6 +183,12 @@ class Xml { } elseif ($value === null) { $value = ''; } + $isNamespace = strpos($key, 'xmlns:'); + $nsUri = null; + if ($isNamespace !== false) { + $node->setAttributeNS('http://www.w3.org/2000/xmlns/', $key, $value); + continue; + } if ($key[0] !== '@' && $format === 'tags') { $child = $dom->createElement($key, $value); $node->appendChild($child); @@ -200,24 +206,12 @@ class Xml { } if (array_keys($value) === range(0, count($value) - 1)) { // List foreach ($value as $item) { - if (array_key_exists('@', $item)) { - $child = $dom->createElement($key, (string)$item['@']); - unset($item['@']); - } else { - $child = $dom->createElement($key); - } - self::_fromArray($dom, $child, $item, $format); - $node->appendChild($child); + $data = compact('dom', 'node', 'key', 'format'); + $data['value'] = $item; + self::__createChild($data); } } else { // Struct - if (array_key_exists('@', $value)) { - $child = $dom->createElement($key, (string)$value['@']); - unset($value['@']); - } else { - $child = $dom->createElement($key); - } - self::_fromArray($dom, $child, $value, $format); - $node->appendChild($child); + self::__createChild(compact('dom', 'node', 'key', 'value', 'format')); } } } else { @@ -226,6 +220,41 @@ class Xml { } } +/** + * Helper to _fromArray(). It will create childs of arrays + * + * @param array $data Array with informations to create childs + * @return void + */ + private function __createChild($data) { + extract($data); + $childNS = $childValue = null; + if (is_array($value)) { + if (isset($value['@'])) { + $childValue = (string)$value['@']; + unset($value['@']); + } + if (isset($value['xmlns:'])) { + $childNS = $value['xmlns:']; + unset($value['xmlns:']); + } + } elseif (!empty($value) || $value === 0) { + $childValue = (string)$value; + } + + if ($childValue) { + $child = $dom->createElement($key, $childValue); + } else { + $child = $dom->createElement($key); + } + if ($childNS) { + $child->setAttribute('xmlns', $childNS); + } + + self::_fromArray($dom, $child, $value, $format); + $node->appendChild($child); + } + /** * Returns this XML structure as a array. * diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 64876a9f1..8abfb2539 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -606,6 +606,62 @@ class XmlTest extends CakeTestCase { $xmlResponse = Xml::build('1'); $this->assertEqual(Xml::toArray($xmlResponse), $expected); + + $xml = array( + 'root' => array( + 'ns:attr' => array( + 'xmlns:ns' => 'http://cakephp.org', + '@' => 1 + ) + ) + ); + $expected = '<' . '?xml version="1.0" encoding="UTF-8"?>1'; + $xmlResponse = Xml::fromArray($xml); + $this->assertEqual(str_replace(array("\r", "\n"), '', $xmlResponse->asXML()), $expected); + + $xml = array( + 'root' => array( + 'tag' => array( + 'xmlns:pref' => 'http://cakephp.org', + 'pref:item' => array( + 'item 1', + 'item 2' + ) + ) + ) + ); + $expected = '<' . '?xml version="1.0" encoding="UTF-8"?>item 1item 2'; + $xmlResponse = Xml::fromArray($xml); + $this->assertEqual(str_replace(array("\r", "\n"), '', $xmlResponse->asXML()), $expected); + + $xml = array( + 'root' => array( + 'tag' => array( + 'xmlns:' => 'http://cakephp.org' + ) + ) + ); + $expected = '<' . '?xml version="1.0" encoding="UTF-8"?>'; + $xmlResponse = Xml::fromArray($xml); + $this->assertEqual(str_replace(array("\r", "\n"), '', $xmlResponse->asXML()), $expected); + + $xml = array( + 'root' => array( + 'xmlns:' => 'http://cakephp.org' + ) + ); + $expected = '<' . '?xml version="1.0" encoding="UTF-8"?>'; + $xmlResponse = Xml::fromArray($xml); + $this->assertEqual(str_replace(array("\r", "\n"), '', $xmlResponse->asXML()), $expected); + + $xml = array( + 'root' => array( + 'xmlns:ns' => 'http://cakephp.org' + ) + ); + $expected = '<' . '?xml version="1.0" encoding="UTF-8"?>'; + $xmlResponse = Xml::fromArray($xml); + $this->assertEqual(str_replace(array("\r", "\n"), '', $xmlResponse->asXML()), $expected); } /** From 7237713a30bfc6494fd38b2e64068d932a9f624c Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sat, 11 Sep 2010 22:12:54 -0300 Subject: [PATCH 32/37] Added test to dont use encoding in XML. --- cake/libs/xml.php | 2 +- cake/tests/cases/libs/xml.test.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 7c71a23dd..570a8ab4c 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -103,7 +103,7 @@ class Xml { * * - `format` If create childs ('tags') or attributes ('attribute'). * - `version` Version of XML document. Default is 1.0. - * - `encoding` Encoding of XML document. Default is the some of application. + * - `encoding` Encoding of XML document. If null remove from XML header. Default is the some of application. * - `return` If return object of SimpleXMLElement ('simplexml') or DOMDocument ('domdocument'). Default is SimpleXMLElement. * * Using the following data: diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 8abfb2539..4f71e0b35 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -91,6 +91,9 @@ class XmlTest extends CakeTestCase { $obj = Xml::build($xml, array('return' => 'domdocument')); $this->assertEqual($obj->firstChild->nodeName, 'tag'); $this->assertEqual($obj->firstChild->nodeValue, 'value'); + + $obj = Xml::build($xml, array('return' => 'domdocument', 'encoding' => null)); + $this->assertNoPattern('/encoding/', $obj->saveXML()); } /** From a006ee0019a86b13be4007bbe4971e461ff53d8d Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sat, 11 Sep 2010 22:20:21 -0300 Subject: [PATCH 33/37] Updating docs. --- cake/libs/xml.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index 570a8ab4c..f3153b2e4 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -23,7 +23,7 @@ class Xml { /** - * Initialize SimpleXMLElement from a given XML string, file path, URL or array. + * Initialize SimpleXMLElement or DOMDocument from a given XML string, file path, URL or array. * * ### Usage: * @@ -31,6 +31,10 @@ class Xml { * * `$xml = Xml::build('text');` * + * Building XML from string (output DOMDocument): + * + * `$xml = Xml::build('text', array('return' => 'domdocument'));` + * * Building XML from a file path: * * `$xml = Xml::build('/path/to/an/xml/file.xml');` @@ -61,8 +65,14 @@ class Xml { * * When building XML from an array ensure that there is only one top level element. * + * ### Options + * + * - `return` Can be 'simplexml' to return object of SimpleXMLElement or 'domdocument' to return DOMDocument. + * - If using array as input, you can pass `options` from Xml::fromArray. + * * @param mixed $input XML string, a path to a file, an URL or an array - * @return object SimpleXMLElement + * @param array $options The options to use + * @return object SimpleXMLElement or DOMDocument * @throws Exception */ public static function build($input, $options = array()) { From 46f784f95642487ed54e2e6a69305e1d6558ef98 Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sun, 12 Sep 2010 11:25:37 -0300 Subject: [PATCH 34/37] Support to DOMDocument/DOMNode in Xml::toArray(). --- cake/libs/xml.php | 15 +++++++++------ cake/tests/cases/libs/xml.test.php | 3 +++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cake/libs/xml.php b/cake/libs/xml.php index f3153b2e4..9b1905b82 100644 --- a/cake/libs/xml.php +++ b/cake/libs/xml.php @@ -268,16 +268,19 @@ class Xml { /** * Returns this XML structure as a array. * - * @param object $simpleXML SimpleXMLElement instance + * @param object $obj SimpleXMLElement, DOMDocument or DOMNode instance * @return array Array representation of the XML structure. */ - public static function toArray($simpleXML) { - if (!($simpleXML instanceof SimpleXMLElement)) { - throw new Exception(__('The input is not instance of SimpleXMLElement.')); + public static function toArray($obj) { + if ($obj instanceof DOMNode) { + $obj = simplexml_import_dom($obj); + } + if (!($obj instanceof SimpleXMLElement)) { + throw new Exception(__('The input is not instance of SimpleXMLElement, DOMDocument or DOMNode.')); } $result = array(); - $namespaces = array_merge(array('' => ''), $simpleXML->getNamespaces(true)); - self::_toArray($simpleXML, $result, '', array_keys($namespaces)); + $namespaces = array_merge(array('' => ''), $obj->getNamespaces(true)); + self::_toArray($obj, $result, '', array_keys($namespaces)); return $result; } diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 4f71e0b35..6635d0662 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -360,7 +360,9 @@ class XmlTest extends CakeTestCase { ) ); $this->assertEqual(Xml::toArray(Xml::fromArray($array, 'attributes')), $expected); + $this->assertEqual(Xml::toArray(Xml::fromArray($array, array('return' => 'domdocument', 'format' => 'attributes'))), $expected); $this->assertEqual(Xml::toArray(Xml::fromArray($array)), $array); + $this->assertEqual(Xml::toArray(Xml::fromArray($array, array('return' => 'domdocument'))), $array); $array = array( 'tags' => array( @@ -395,6 +397,7 @@ class XmlTest extends CakeTestCase { ) ); $this->assertEqual(Xml::toArray(Xml::fromArray($array, 'attributes')), $expected); + $this->assertEqual(Xml::toArray(Xml::fromArray($array, array('format' => 'attributes', 'return' => 'domdocument'))), $expected); $xml = ''; $xml .= 'defect'; From 89be9b0986272ac29e79ab789433791438d889ec Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sun, 12 Sep 2010 11:54:11 -0300 Subject: [PATCH 35/37] Added test to generate Rss from array using namespaces. --- cake/tests/cases/libs/xml.test.php | 45 ++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 6635d0662..b1696b7fb 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -448,11 +448,11 @@ class XmlTest extends CakeTestCase { } /** - * testToArrayRss + * testRss * * @return void */ - public function testToArrayRss() { + public function testRss() { $rss = file_get_contents(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'fixtures' . DS . 'rss.xml'); $rssAsArray = Xml::toArray(Xml::build($rss)); $this->assertEqual($rssAsArray['rss']['@version'], '2.0'); @@ -470,6 +470,47 @@ class XmlTest extends CakeTestCase { 'guid' => 'http://bakery.cakephp.org/articles/view/alertpay-automated-sales-via-ipn' ); $this->assertIdentical($rssAsArray['rss']['channel']['item'][1], $expected); + + $rss = array( + 'rss' => array( + 'xmlns:atom' => 'http://www.w3.org/2005/Atom', + '@version' => '2.0', + 'channel' => array( + 'atom:link' => array( + '@href' => 'http://bakery.cakephp.org/articles/rss', + '@rel' => 'self', + '@type' => 'application/rss+xml' + ), + 'title' => 'The Bakery: ', + 'link' => 'http://bakery.cakephp.org/', + 'description' => 'Recent Articles at The Bakery.', + 'pubDate' => 'Sun, 12 Sep 2010 04:18:26 -0500', + 'item' => array( + array( + 'title' => 'CakePHP 1.3.4 released', + 'link' => 'http://bakery.cakephp.org/articles/view/cakephp-1-3-4-released' + ), + array( + 'title' => 'Wizard Component 1.2 Tutorial', + 'link' => 'http://bakery.cakephp.org/articles/view/wizard-component-1-2-tutorial' + ) + ) + ) + ) + ); + $rssAsSimpleXML = Xml::fromArray($rss); + $xmlText = '<' . '?xml version="1.0" encoding="UTF-8"?>'; + $xmlText .= ''; + $xmlText .= ''; + $xmlText .= ''; + $xmlText .= 'The Bakery: '; + $xmlText .= 'http://bakery.cakephp.org/'; + $xmlText .= 'Recent Articles at The Bakery.'; + $xmlText .= 'Sun, 12 Sep 2010 04:18:26 -0500'; + $xmlText .= 'CakePHP 1.3.4 releasedhttp://bakery.cakephp.org/articles/view/cakephp-1-3-4-released'; + $xmlText .= 'Wizard Component 1.2 Tutorialhttp://bakery.cakephp.org/articles/view/wizard-component-1-2-tutorial'; + $xmlText .= ''; + $this->assertEqual(str_replace(array("\r", "\n"), '', $rssAsSimpleXML->asXML()), $xmlText); } /** From 213c83401f11ea82244cb1b5dfdb5ea85cee971b Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Sun, 12 Sep 2010 12:06:59 -0300 Subject: [PATCH 36/37] Added test to generate Soap Request from array using namespaces. --- cake/tests/cases/libs/xml.test.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index b1696b7fb..860fb9237 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -609,6 +609,26 @@ class XmlTest extends CakeTestCase { ) ); $this->assertEqual(Xml::toArray($xmlResponse), $expected); + + $xml = array( + 'soap:Envelope' => array( + 'xmlns:soap' => 'http://www.w3.org/2001/12/soap-envelope', + '@soap:encodingStyle' => 'http://www.w3.org/2001/12/soap-encoding', + 'soap:Body' => array( + 'xmlns:m' => 'http://www.example.org/stock', + 'm:GetStockPrice' => array( + 'm:StockName' => 'IBM' + ) + ) + ) + ); + $xmlRequest = Xml::fromArray($xml, array('encoding' => null)); + $xmlText = '<' . '?xml version="1.0"?>'; + $xmlText .= ''; + $xmlText .= ''; + $xmlText .= 'IBM'; + $xmlText .= ''; + $this->assertEqual(str_replace(array("\r", "\n"), '', $xmlRequest->asXML()), $xmlText); } /** From ef147275d78974ec6d0fa5de66ab7e9f7cc3608f Mon Sep 17 00:00:00 2001 From: Juan Basso Date: Mon, 13 Sep 2010 00:43:11 -0300 Subject: [PATCH 37/37] Added test to use Xml with Model. --- cake/tests/cases/libs/xml.test.php | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index 860fb9237..7320dea7f 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -19,6 +19,52 @@ */ App::import('Core', 'Xml'); +/** + * Article class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class Article extends CakeTestModel { + +/** + * name property + * + * @var string 'Article' + */ + public $name = 'Article'; + +/** + * belongsTo property + * + * @var array + */ + public $belongsTo = array('User'); +} + +/** + * User class + * + * @package cake + * @subpackage cake.tests.cases.libs + */ +class User extends CakeTestModel { + +/** + * name property + * + * @var string 'User' + */ + public $name = 'User'; + +/** + * hasMany property + * + * @var array + */ + public $hasMany = array('Article'); +} + /** * XmlTest class * @@ -27,6 +73,21 @@ App::import('Core', 'Xml'); */ class XmlTest extends CakeTestCase { +/** + * autoFixtures property + * + * @var bool false + */ + public $autoFixtures = false; + +/** + * fixtures property + * @var array + */ + public $fixtures = array( + 'core.article', 'core.user' + ); + /** * setup method * @@ -753,4 +814,27 @@ class XmlTest extends CakeTestCase { Xml::toArray($value); } +/** + * testWithModel method + * + * @return void + */ + public function testWithModel() { + $this->loadFixtures('User', 'Article'); + + $user = new User(); + $data = $user->read(null, 1); + + $obj = Xml::build(compact('data')); + $expected = '<' . '?xml version="1.0" encoding="UTF-8"?>'; + $expected .= '1mariano5f4dcc3b5aa765d61d8327deb882cf99'; + $expected .= '2007-03-17 01:16:232007-03-17 01:18:31'; + $expected .= '

11First ArticleFirst Article Body'; + $expected .= 'Y2007-03-18 10:39:232007-03-18 10:41:31
'; + $expected .= '
31Third ArticleThird Article Body'; + $expected .= 'Y2007-03-18 10:43:232007-03-18 10:45:31
'; + $expected .= ''; + $this->assertEqual(str_replace(array("\r", "\n"), '', $obj->asXML()), $expected); + } + }