add Horde_Xml_Element to git
authorChuck Hagenbuch <chuck@horde.org>
Sat, 15 Nov 2008 14:55:15 +0000 (09:55 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Sat, 15 Nov 2008 14:55:15 +0000 (09:55 -0500)
50 files changed:
framework/Xml_Element/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/CVS/Root [new file with mode: 0644]
framework/Xml_Element/CVS/Template [new file with mode: 0644]
framework/Xml_Element/lib/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/lib/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/lib/CVS/Root [new file with mode: 0644]
framework/Xml_Element/lib/CVS/Template [new file with mode: 0644]
framework/Xml_Element/lib/Horde/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/lib/Horde/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/lib/Horde/CVS/Root [new file with mode: 0644]
framework/Xml_Element/lib/Horde/CVS/Template [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/CVS/Root [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/CVS/Template [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/Element.php [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/Element/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/Element/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/Element/CVS/Root [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/Element/CVS/Template [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/Element/Exception.php [new file with mode: 0644]
framework/Xml_Element/lib/Horde/Xml/Element/List.php [new file with mode: 0644]
framework/Xml_Element/package.xml [new file with mode: 0644]
framework/Xml_Element/test/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/test/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/test/CVS/Root [new file with mode: 0644]
framework/Xml_Element/test/CVS/Template [new file with mode: 0644]
framework/Xml_Element/test/Horde/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/test/Horde/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/test/Horde/CVS/Root [new file with mode: 0644]
framework/Xml_Element/test/Horde/CVS/Template [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/CVS/Root [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/CVS/Template [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/AllTests.php [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/CVS/Root [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/CVS/Template [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/CountTest.php [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/ElementTest.php [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/IteratorTest.php [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Entries [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Repository [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Root [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Template [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/fixtures/NamespacedSample.xml [new file with mode: 0644]
framework/Xml_Element/test/Horde/Xml/Element/fixtures/Sample.xml [new file with mode: 0644]

diff --git a/framework/Xml_Element/CVS/Entries b/framework/Xml_Element/CVS/Entries
new file mode 100644 (file)
index 0000000..506dc15
--- /dev/null
@@ -0,0 +1,3 @@
+D/lib////
+D/test////
+/package.xml/1.15/Fri Sep 26 12:37:16 2008//
diff --git a/framework/Xml_Element/CVS/Repository b/framework/Xml_Element/CVS/Repository
new file mode 100644 (file)
index 0000000..0f9672a
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element
diff --git a/framework/Xml_Element/CVS/Root b/framework/Xml_Element/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/CVS/Template b/framework/Xml_Element/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/lib/CVS/Entries b/framework/Xml_Element/lib/CVS/Entries
new file mode 100644 (file)
index 0000000..ae2779e
--- /dev/null
@@ -0,0 +1 @@
+D/Horde////
diff --git a/framework/Xml_Element/lib/CVS/Repository b/framework/Xml_Element/lib/CVS/Repository
new file mode 100644 (file)
index 0000000..1f30f77
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element/lib
diff --git a/framework/Xml_Element/lib/CVS/Root b/framework/Xml_Element/lib/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/lib/CVS/Template b/framework/Xml_Element/lib/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/lib/Horde/CVS/Entries b/framework/Xml_Element/lib/Horde/CVS/Entries
new file mode 100644 (file)
index 0000000..02b1fd1
--- /dev/null
@@ -0,0 +1 @@
+D/Xml////
diff --git a/framework/Xml_Element/lib/Horde/CVS/Repository b/framework/Xml_Element/lib/Horde/CVS/Repository
new file mode 100644 (file)
index 0000000..d297557
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element/lib/Horde
diff --git a/framework/Xml_Element/lib/Horde/CVS/Root b/framework/Xml_Element/lib/Horde/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/lib/Horde/CVS/Template b/framework/Xml_Element/lib/Horde/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/lib/Horde/Xml/CVS/Entries b/framework/Xml_Element/lib/Horde/Xml/CVS/Entries
new file mode 100644 (file)
index 0000000..22b59a1
--- /dev/null
@@ -0,0 +1,2 @@
+D/Element////
+/Element.php/1.14/Sat Sep 27 14:56:17 2008//
diff --git a/framework/Xml_Element/lib/Horde/Xml/CVS/Repository b/framework/Xml_Element/lib/Horde/Xml/CVS/Repository
new file mode 100644 (file)
index 0000000..639123f
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element/lib/Horde/Xml
diff --git a/framework/Xml_Element/lib/Horde/Xml/CVS/Root b/framework/Xml_Element/lib/Horde/Xml/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/lib/Horde/Xml/CVS/Template b/framework/Xml_Element/lib/Horde/Xml/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/lib/Horde/Xml/Element.php b/framework/Xml_Element/lib/Horde/Xml/Element.php
new file mode 100644 (file)
index 0000000..8f89c82
--- /dev/null
@@ -0,0 +1,590 @@
+<?php
+/**
+ * $Horde: framework/Xml_Element/lib/Horde/Xml/Element.php,v 1.14 2008/09/27 02:57:42 chuck Exp $
+ *
+ * Portions Copyright 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package  Horde_Xml_Element
+ */
+
+/**
+ * Wraps a DOMElement allowing for SimpleXML-like access to attributes.
+ *
+ * @method mixed TAGNAME() To get the un-wrapped value of a node, use
+ * method syntax ($xml_element->tagname()). This will return the
+ * string value of the tag if it is a single tag, an array of
+ * Horde_Xml_Element objects if there are multiple tags, or null if
+ * the tag does not exist.
+ *
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package  Horde_Xml_Element
+ */
+class Horde_Xml_Element implements ArrayAccess
+{
+    /**
+     * @var array
+     */
+    protected static $_namespaces = array(
+        'opensearch' => 'http://a9.com/-/spec/opensearchrss/1.0/',
+        'atom' => 'http://www.w3.org/2005/Atom',
+        'rss' => 'http://blogs.law.harvard.edu/tech/rss',
+        'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns',
+    );
+
+    /**
+     * Get the full version of a namespace prefix
+     *
+     * Looks up a prefix (atom:, etc.) in the list of registered
+     * namespaces and returns the full namespace URI if
+     * available. Returns the prefix, unmodified, if it's not
+     * registered.
+     *
+     * @return string
+     */
+    public static function lookupNamespace($prefix)
+    {
+        return isset(self::$_namespaces[$prefix]) ?
+            self::$_namespaces[$prefix] :
+            $prefix;
+    }
+
+    /**
+     * Add a namespace and prefix to the registered list
+     *
+     * Takes a prefix and a full namespace URI and adds them to the
+     * list of registered namespaces for use by
+     * Horde_Xml_Element::lookupNamespace().
+     *
+     * @param string $prefix The namespace prefix
+     * @param string $namespaceURI The full namespace URI
+     */
+    public static function registerNamespace($prefix, $namespaceURI)
+    {
+        self::$_namespaces[$prefix] = $namespaceURI;
+    }
+
+    /**
+     * @var DOMElement
+     */
+    protected $_element;
+
+    /**
+     * A string representation of the element, used when
+     * serializing/unserializing.
+     *
+     * @var string
+     */
+    protected $_serialized;
+
+    /**
+     * @var Horde_Xml_Element
+     */
+    protected $_parentElement;
+
+    /**
+     * @var array
+     */
+    protected $_children = null;
+
+    /**
+     * @var boolean
+     */
+    protected $_appended = true;
+
+    /**
+     * Horde_Xml_Element constructor.
+     *
+     * @param DOMElement | Horde_Xml_Element | string $element  The DOM element,
+     * pre-existing Horde_Xml_Element, or XML string that we're encapsulating.
+     */
+    public function __construct($element)
+    {
+        $this->_element = $element;
+        $this->__wakeup();
+    }
+
+    /**
+     * Get a DOM representation of the element
+     *
+     * Returns the underlying DOM object, which can then be
+     * manipulated with full DOM methods.
+     *
+     * @return DOMDocument
+     */
+    public function getDom()
+    {
+        return $this->_element;
+    }
+
+    /**
+     * Update the object from a DOM element
+     *
+     * Take a DOMElement object, which may be originally from a call
+     * to getDom() or may be custom created, and use it as the
+     * DOM tree for this Horde_Xml_Element.
+     *
+     * @param DOMElement $element
+     */
+    public function setDom(DOMElement $element)
+    {
+        $this->_element = $this->_element->ownerDocument->importNode($element, true);
+    }
+
+    /**
+     * Add child elements and attributes to this element from a simple
+     * key => value hash. Keys can be:
+     *
+     *   ElementName               -> <$ElementName> will be appended with
+     *                                a value of $value
+     *   #AttributeName            -> An attribute $AttributeName will be
+     *                                added to this element with a value
+     *                                of $value
+     *   ElementName#AttributeName -> <$ElementName> will be appended to this
+     *                                element if it doesn't already exist,
+     *                                and have its attribute $AttributeName
+     *                                set to $value
+     *
+     * @param $array Hash to import into this element.
+     */
+    public function fromArray($array)
+    {
+        foreach ($array as $key => $value) {
+            $element = null;
+            $attribute = null;
+
+            $hash_position = strpos($key, '#');
+            if ($hash_position === false) {
+                $element = $key;
+            } elseif ($hash_position === 0) {
+                $attribute = substr($key, 1);
+            } else {
+                list($element, $attribute) = explode('#', $key, 2);
+            }
+
+            if (!is_null($element)) {
+                if (!is_null($attribute)) {
+                    $this->{$element}[$attribute] = $value;
+                } else {
+                    if (is_array($value)) {
+                        $this->$element->fromArray($value);
+                    } else {
+                        $this->$element = $value;
+                    }
+                }
+            } elseif (!is_null($attribute)) {
+                $this[$attribute] = $value;
+            }
+        }
+    }
+
+    /**
+     * Append a child node to this element.
+     *
+     * @param Horde_Xml_Element $element The element to append.
+     */
+    public function appendChild(Horde_Xml_Element $element)
+    {
+        $element->setParent($this);
+        $element->_ensureAppended();
+        $this->_expireCachedChildren();
+    }
+
+    /**
+     * Get an XML string representation of this element
+     *
+     * Returns a string of this element's XML, including the XML
+     * prologue.
+     *
+     * @return string
+     */
+    public function saveXml()
+    {
+        // Return a complete document including XML prologue.
+        $doc = new DOMDocument($this->_element->ownerDocument->version,
+                               $this->_element->ownerDocument->actualEncoding);
+        $doc->appendChild($doc->importNode($this->_element, true));
+        return $doc->saveXML();
+    }
+
+    /**
+     * Get the XML for only this element
+     *
+     * Returns a string of this element's XML without prologue.
+     *
+     * @return string
+     */
+    public function saveXmlFragment()
+    {
+        return $this->_element->ownerDocument->saveXML($this->_element);
+    }
+
+    /**
+     * Unserialization handler; handles $this->_element being an instance of
+     * DOMElement or Horde_Xml_Element, or parses it as an XML string.
+     */
+    public function __wakeup()
+    {
+        if ($this->_element instanceof DOMElement) {
+            return true;
+        }
+
+        if ($this->_element instanceof Horde_Xml_Element) {
+            $this->_element = $this->_element->getDom();
+            return true;
+        }
+
+        if ($this->_serialized) {
+            $this->_element = $this->_serialized;
+            $this->_serialized = null;
+        }
+
+        if (is_string($this->_element)) {
+            $doc = new DOMDocument;
+            $doc->preserveWhiteSpace = false;
+            $e = error_reporting(0);
+            $loaded = $doc->loadXML($this->_element);
+            error_reporting($e);
+            if (!$loaded) {
+                throw new Horde_Xml_Element_Exception('DOMDocument cannot parse XML: ', error_get_last());
+            }
+
+            $this->_element = $doc->documentElement;
+            return true;
+        }
+
+        throw new InvalidArgumentException('Horde_Xml_Element initialization value must be a DOMElement, a Horde_Xml_Element, or a non-empty string; '
+                                           . (gettype($this->_element) == 'object' ? get_class($this->_element) : gettype($this->_element))
+                                           . ' given');
+    }
+
+    /**
+     * Prepare for serialization
+     *
+     * @return array
+     */
+    public function __sleep()
+    {
+        $this->_serialized = $this->saveXml();
+        return array('_serialized');
+    }
+
+    /**
+     * Map variable access onto the underlying entry representation.
+     *
+     * Get-style access returns a Horde_Xml_Element representing the
+     * child element accessed. To get string values, use method syntax
+     * with the __call() overriding.
+     *
+     * @param string $var The property to access.
+     * @return mixed
+     */
+    public function __get($var)
+    {
+        $nodes = $this->_children($var);
+        $length = count($nodes);
+
+        if ($length == 1) {
+            if ($nodes[0] instanceof Horde_Xml_Element) {
+                return $nodes[0];
+            }
+            return new Horde_Xml_Element($nodes[0]);
+        } elseif ($length > 1) {
+            if ($nodes[0] instanceof Horde_Xml_Element) {
+                return $nodes;
+            }
+            return array_map(create_function('$e', 'return new Horde_Xml_Element($e);'), $nodes);
+        } else {
+            // When creating anonymous nodes for __set chaining, don't
+            // call appendChild() on them. Instead we pass the current
+            // element to them as an extra reference; the child is
+            // then responsible for appending itself when it is
+            // actually set. This way "if ($foo->bar)" doesn't create
+            // a phantom "bar" element in our tree.
+            if (strpos($var, ':') !== false) {
+                list($ns, $elt) = explode(':', $var, 2);
+                $node = $this->_element->ownerDocument->createElementNS(Horde_Xml_Element::lookupNamespace($ns), $elt);
+            } else {
+                $node = $this->_element->ownerDocument->createElement($var);
+            }
+            $node = new Horde_Xml_Element($node);
+            $node->setParent($this);
+            return $node;
+        }
+    }
+
+    /**
+     * Map variable sets onto the underlying entry representation.
+     *
+     * @param string $var The property to change.
+     * @param string $val The property's new value.
+     */
+    public function __set($var, $val)
+    {
+        if (!is_scalar($val)) {
+            throw new InvalidArgumentException('Element values must be scalars, ' . gettype($val) . ' given');
+        }
+
+        $this->_ensureAppended();
+
+        $nodes = $this->_children($var);
+        if (!$nodes) {
+            if (strpos($var, ':') !== false) {
+                list($ns, $elt) = explode(':', $var, 2);
+                $node = $this->_element->ownerDocument->createElementNS(Horde_Xml_Element::lookupNamespace($ns), $var, $val);
+                $this->_element->appendChild($node);
+            } else {
+                $node = $this->_element->ownerDocument->createElement($var, $val);
+                $this->_element->appendChild($node);
+            }
+
+            $this->_expireCachedChildren();
+        } elseif (count($nodes) > 1) {
+            throw new Horde_Xml_Element_Exception('Cannot set the value of multiple nodes simultaneously.');
+        } else {
+            $nodes[0]->nodeValue = $val;
+        }
+    }
+
+    /**
+     * Map isset calls onto the underlying entry representation.
+     */
+    public function __isset($var)
+    {
+        return (boolean)$this->_children($var);
+    }
+
+    /**
+     * Get the value of an element with method syntax.
+     *
+     * Map method calls to get the string value of the requested
+     * element. If there are multiple elements that match, this will
+     * return an array of those objects.
+     *
+     * @param string $var The element to get the string value of.
+     *
+     * @return mixed The node's value, null, or an array of nodes.
+     */
+    public function __call($var, $unused)
+    {
+        $nodes = $this->_children($var);
+
+        if (!$nodes) {
+            return null;
+        } elseif (count($nodes) > 1) {
+            if ($nodes[0] instanceof Horde_Xml_Element) {
+                return $nodes;
+            }
+            return array_map(create_function('$e', 'return new Horde_Xml_Element($e);'), $nodes);
+        } else {
+            if ($nodes[0] instanceof Horde_Xml_Element) {
+                return (string)$nodes[0];
+            } else {
+                return $nodes[0]->nodeValue;
+            }
+        }
+    }
+
+    /**
+     * Remove all children matching $var.
+     */
+    public function __unset($var)
+    {
+        $nodes = $this->_children($var);
+        foreach ($nodes as $node) {
+            $parent = $node->parentNode;
+            $parent->removeChild($node);
+        }
+
+        $this->_expireCachedChildren();
+    }
+
+    /**
+     * Returns the nodeValue of this element when this object is used
+     * in a string context.
+     *
+     * @internal
+     */
+    public function __toString()
+    {
+        return $this->_element->nodeValue;
+    }
+
+    /**
+     * Set the parent element of this object to another
+     * Horde_Xml_Element.
+     *
+     * @internal
+     */
+    public function setParent(Horde_Xml_Element $element)
+    {
+        $this->_parentElement = $element;
+        $this->_appended = false;
+    }
+
+    /**
+     * Appends this element to its parent if necessary.
+     *
+     * @internal
+     */
+    protected function _ensureAppended()
+    {
+        if (!$this->_appended) {
+            $parentDom = $this->_parentElement->getDom();
+            if (!$parentDom->ownerDocument->isSameNode($this->_element->ownerDocument)) {
+                $this->_element = $parentDom->ownerDocument->importNode($this->_element, true);
+            }
+
+            $parentDom->appendChild($this->_element);
+            $this->_appended = true;
+            $this->_parentElement->_ensureAppended();
+        }
+    }
+
+    /**
+     * Finds children with tagnames matching $var
+     *
+     * Similar to SimpleXML's children() method.
+     *
+     * @param string Tagname to match, can be either namespace:tagName or just tagName.
+     * @return array
+     */
+    protected function _children($var)
+    {
+        if (is_null($this->_children))
+            $this->_cacheChildren();
+
+        // Honor any explicit getters. Because Horde_Xml_Element has a __call()
+        // method, is_callable returns true on every method name. Use
+        // method_exists instead.
+        $varMethod = 'get' . ucfirst($var);
+        if (method_exists($this, $varMethod)) {
+            $children = call_user_func(array($this, $varMethod));
+            if (is_null($children)) {
+                $this->_children[$var] = array();
+            } elseif (!is_array($children)) {
+                $this->_children[$var] = array($children);
+            } else {
+                $this->_children[$var] = $children;
+            }
+        }
+
+        if (!isset($this->_children[$var])) {
+            $this->_children[$var] = array();
+        }
+
+        return $this->_children[$var];
+    }
+
+    /**
+     * Build a cache of child nodes.
+     */
+    protected function _cacheChildren()
+    {
+        foreach ($this->_element->childNodes as $child) {
+            if (!isset($this->_children[$child->localName]))
+                $this->_children[$child->localName] = array();
+            $this->_children[$child->localName][] = $child;
+
+            if ($child->prefix) {
+                if (!isset($this->_children[$child->prefix . ':' . $child->localName]))
+                    $this->_children[$child->prefix . ':' . $child->localName] = array();
+                $this->_children[$child->prefix . ':' . $child->localName][] = $child;
+            }
+        }
+    }
+
+    /**
+     * Expire cached children.
+     */
+    protected function _expireCachedChildren()
+    {
+        $this->_children = null;
+    }
+
+    /**
+     * Required by the ArrayAccess interface.
+     *
+     * @internal
+     */
+    public function offsetExists($offset)
+    {
+        if (strpos($offset, ':') !== false) {
+            list($ns, $attr) = explode(':', $offset, 2);
+            return $this->_element->hasAttributeNS(Horde_Xml_Element::lookupNamespace($ns), $attr);
+        } else {
+            return $this->_element->hasAttribute($offset);
+        }
+    }
+
+    /**
+     * Required by the ArrayAccess interface.
+     *
+     * @internal
+     */
+    public function offsetGet($offset)
+    {
+        if (strpos($offset, ':') !== false) {
+            list($ns, $attr) = explode(':', $offset, 2);
+            return $this->_element->getAttributeNS(Horde_Xml_Element::lookupNamespace($ns), $attr);
+        } else {
+            return $this->_element->getAttribute($offset);
+        }
+    }
+
+    /**
+     * Required by the ArrayAccess interface.
+     *
+     * @internal
+     */
+    public function offsetSet($offset, $value)
+    {
+        if (!is_scalar($value)) {
+            throw new InvalidArgumentException('Element values must be scalars, ' . gettype($value) . ' given');
+        }
+
+        $this->_ensureAppended();
+
+        if (strpos($offset, ':') !== false) {
+            list($ns, $attr) = explode(':', $offset, 2);
+            $result = $this->_element->setAttributeNS(Horde_Xml_Element::lookupNamespace($ns), $attr, $value);
+        } else {
+            $result = $this->_element->setAttribute($offset, $value);
+        }
+
+        if ($result) {
+            $this->_expireCachedChildren();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Required by the ArrayAccess interface.
+     *
+     * @internal
+     */
+    public function offsetUnset($offset)
+    {
+        if (strpos($offset, ':') !== false) {
+            list($ns, $attr) = explode(':', $offset, 2);
+            $result = $this->_element->removeAttributeNS(Horde_Xml_Element::lookupNamespace($ns), $attr);
+        } else {
+            $result = $this->_element->removeAttribute($offset);
+        }
+
+        if ($result) {
+            $this->_expireCachedChildren();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+}
diff --git a/framework/Xml_Element/lib/Horde/Xml/Element/CVS/Entries b/framework/Xml_Element/lib/Horde/Xml/Element/CVS/Entries
new file mode 100644 (file)
index 0000000..bd4a9dc
--- /dev/null
@@ -0,0 +1,3 @@
+/Exception.php/1.3/Tue Jul 29 15:52:47 2008//
+/List.php/1.3/Tue Jul 29 15:52:47 2008//
+D
diff --git a/framework/Xml_Element/lib/Horde/Xml/Element/CVS/Repository b/framework/Xml_Element/lib/Horde/Xml/Element/CVS/Repository
new file mode 100644 (file)
index 0000000..39c827d
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element/lib/Horde/Xml/Element
diff --git a/framework/Xml_Element/lib/Horde/Xml/Element/CVS/Root b/framework/Xml_Element/lib/Horde/Xml/Element/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/lib/Horde/Xml/Element/CVS/Template b/framework/Xml_Element/lib/Horde/Xml/Element/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/lib/Horde/Xml/Element/Exception.php b/framework/Xml_Element/lib/Horde/Xml/Element/Exception.php
new file mode 100644 (file)
index 0000000..500679f
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package  Horde_Xml_Element
+ */
+
+/**
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package  Horde_Xml_Element
+ */
+class Horde_Xml_Element_Exception extends Exception
+{
+    /**
+     * Create an exception, filling in some details from the result of
+     * error_get_last if they are passed in $code_or_lasterror.
+     */
+    public function __construct($message = null, $code_or_lasterror = 0)
+    {
+        if (is_array($code_or_lasterror)) {
+            if ($message) {
+                $message .= $code_or_lasterror['message'];
+            } else {
+                $message = $code_or_lasterror['message'];
+            }
+
+            $this->file = $code_or_lasterror['file'];
+            $this->line = $code_or_lasterror['line'];
+            $code = $code_or_lasterror['type'];
+        } else {
+            $code = $code_or_lasterror;
+        }
+
+        parent::__construct($message, $code);
+    }
+
+}
diff --git a/framework/Xml_Element/lib/Horde/Xml/Element/List.php b/framework/Xml_Element/lib/Horde/Xml/Element/List.php
new file mode 100644 (file)
index 0000000..95ca636
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+/**
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package  Horde_Xml_Element
+ */
+
+/**
+ * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @license  http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package  Horde_Xml_Element
+ */
+abstract class Horde_Xml_Element_List extends Horde_Xml_Element implements Countable, Iterator
+{
+    /**
+     * Cache of list items.
+     * @var array
+     */
+    protected $_listItems;
+
+    /**
+     * Current index on the collection of list items for the Iterator
+     * implementation.
+     * @var integer
+     */
+    protected $_listItemIndex = 0;
+
+    /**
+     * The classname for individual list items.
+     * @var string
+     */
+    protected $_listItemClassName = 'Horde_Xml_Element';
+
+    /**
+     * Ensure that $_listItems is populated by calling the concrete implementation's
+     * _buildItemsCache() method.
+     */
+    public function __wakeup()
+    {
+        parent::__wakeup();
+
+        $this->_listItems = $this->_buildListItemCache();
+    }
+
+    /**
+     * Called by __wakeup to cache list items. Must be implemented in the
+     * extending class to return the array of list items.
+     * @return array
+     */
+    abstract protected function _buildListItemCache();
+
+    /**
+     * Get the number of items in this list.
+     *
+     * @return integer Item count.
+     */
+    public function count()
+    {
+        return count($this->_listItems);
+    }
+
+    /**
+     * Required by the Iterator interface.
+     *
+     * @internal
+     */
+    public function rewind()
+    {
+        $this->_listItemIndex = 0;
+    }
+
+    /**
+     * Required by the Iterator interface.
+     *
+     * @internal
+     *
+     * @return mixed The current row, or null if no rows.
+     */
+    public function current()
+    {
+        return new $this->_listItemClassName(
+            $this->_listItems[$this->_listItemIndex]);
+    }
+
+    /**
+     * Required by the Iterator interface.
+     *
+     * @internal
+     *
+     * @return mixed The current row number (starts at 0), or null if no rows
+     */
+    public function key()
+    {
+        return $this->_listItemIndex;
+    }
+
+    /**
+     * Required by the Iterator interface.
+     *
+     * @internal
+     *
+     * @return mixed The next row, or null if no more rows.
+     */
+    public function next()
+    {
+        ++$this->_listItemIndex;
+    }
+
+    /**
+     * Required by the Iterator interface.
+     *
+     * @internal
+     *
+     * @return boolean Whether the iteration is valid
+     */
+    public function valid()
+    {
+        return (0 <= $this->_listItemIndex && $this->_listItemIndex < count($this->_listItems));
+    }
+
+}
diff --git a/framework/Xml_Element/package.xml b/framework/Xml_Element/package.xml
new file mode 100644 (file)
index 0000000..b97142e
--- /dev/null
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Xml_Element</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Xml Element object</summary>
+ <description>This package provides an element object that can be used
+ to provide SimpleXML-like functionality over a DOM object. The main
+ advantage over using SimpleXML is the ability to add multiple levels
+ of new elements in a single call, without introducing &quot;ghost&quot; objects.
+ </description>
+ <lead>
+  <name>Chuck Hagenbuch</name>
+  <user>chuck</user>
+  <email>chuck@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Jan Schneider</name>
+  <user>jan</user>
+  <email>jan@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2008-09-25</date>
+ <version>
+  <release>0.4.2</release>
+  <api>0.4.0</api>
+ </version>
+ <stability>
+  <release>beta</release>
+  <api>beta</api>
+ </stability>
+ <license uri="http://opensource.org/licenses/bsd-license.php">BSD</license>
+ <notes>
+* Avoid errors when a child node doesn't exist.
+</notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <dir name="Xml">
+      <dir name="Element">
+       <file name="Exception.php" role="php" />
+       <file name="List.php" role="php" />
+      </dir> <!-- /lib/Horde/Xml/Element -->
+      <file name="Element.php" role="php" />
+     </dir> <!-- /lib/Horde/Xml -->
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.5.0</min>
+   </pearinstaller>
+  </required>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Xml/Element/Exception.php" as="Horde/Xml/Element/Exception.php" />
+   <install name="lib/Horde/Xml/Element/List.php" as="Horde/Xml/Element/List.php" />
+   <install name="lib/Horde/Xml/Element.php" as="Horde/Xml/Element.php" />
+  </filelist>
+ </phprelease>
+</package>
diff --git a/framework/Xml_Element/test/CVS/Entries b/framework/Xml_Element/test/CVS/Entries
new file mode 100644 (file)
index 0000000..ae2779e
--- /dev/null
@@ -0,0 +1 @@
+D/Horde////
diff --git a/framework/Xml_Element/test/CVS/Repository b/framework/Xml_Element/test/CVS/Repository
new file mode 100644 (file)
index 0000000..d273845
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element/test
diff --git a/framework/Xml_Element/test/CVS/Root b/framework/Xml_Element/test/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/test/CVS/Template b/framework/Xml_Element/test/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/test/Horde/CVS/Entries b/framework/Xml_Element/test/Horde/CVS/Entries
new file mode 100644 (file)
index 0000000..02b1fd1
--- /dev/null
@@ -0,0 +1 @@
+D/Xml////
diff --git a/framework/Xml_Element/test/Horde/CVS/Repository b/framework/Xml_Element/test/Horde/CVS/Repository
new file mode 100644 (file)
index 0000000..518894b
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element/test/Horde
diff --git a/framework/Xml_Element/test/Horde/CVS/Root b/framework/Xml_Element/test/Horde/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/test/Horde/CVS/Template b/framework/Xml_Element/test/Horde/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/test/Horde/Xml/CVS/Entries b/framework/Xml_Element/test/Horde/Xml/CVS/Entries
new file mode 100644 (file)
index 0000000..71227d4
--- /dev/null
@@ -0,0 +1 @@
+D/Element////
diff --git a/framework/Xml_Element/test/Horde/Xml/CVS/Repository b/framework/Xml_Element/test/Horde/Xml/CVS/Repository
new file mode 100644 (file)
index 0000000..16e6152
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element/test/Horde/Xml
diff --git a/framework/Xml_Element/test/Horde/Xml/CVS/Root b/framework/Xml_Element/test/Horde/Xml/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/test/Horde/Xml/CVS/Template b/framework/Xml_Element/test/Horde/Xml/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/AllTests.php b/framework/Xml_Element/test/Horde/Xml/Element/AllTests.php
new file mode 100644 (file)
index 0000000..b80b1c3
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php BSD
+ * @category   Horde
+ * @package    Horde_Xml_Element
+ * @subpackage UnitTests
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Horde_Xml_Element_AllTests::main');
+}
+
+require_once 'PHPUnit/Framework/TestSuite.php';
+require_once 'PHPUnit/TextUI/TestRunner.php';
+
+class Horde_Xml_Element_AllTests {
+
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Xml_Element');
+
+        $basedir = dirname(__FILE__);
+        $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
+
+        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
+            if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
+                $pathname = $file->getPathname();
+                require $pathname;
+
+                $class = str_replace(DIRECTORY_SEPARATOR, '_',
+                                     preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
+                $suite->addTestSuite('Horde_Xml_Element_' . $class);
+            }
+        }
+
+        return $suite;
+    }
+
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Xml_Element_AllTests::main') {
+    Horde_Xml_Element_AllTests::main();
+}
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/CVS/Entries b/framework/Xml_Element/test/Horde/Xml/Element/CVS/Entries
new file mode 100644 (file)
index 0000000..4cce634
--- /dev/null
@@ -0,0 +1,5 @@
+/AllTests.php/1.2/Tue Jul 29 15:52:48 2008//
+/CountTest.php/1.2/Tue Jul 29 15:52:48 2008//
+/IteratorTest.php/1.2/Tue Jul 29 15:52:48 2008//
+D/fixtures////
+/ElementTest.php/1.5/Fri Sep 26 12:37:17 2008//
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/CVS/Repository b/framework/Xml_Element/test/Horde/Xml/Element/CVS/Repository
new file mode 100644 (file)
index 0000000..53c55b2
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element/test/Horde/Xml/Element
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/CVS/Root b/framework/Xml_Element/test/Horde/Xml/Element/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/CVS/Template b/framework/Xml_Element/test/Horde/Xml/Element/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/CountTest.php b/framework/Xml_Element/test/Horde/Xml/Element/CountTest.php
new file mode 100644 (file)
index 0000000..c35c6c5
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php BSD
+ * @category   Horde
+ * @package    Horde_Xml_Element
+ * @subpackage UnitTests
+ */
+
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/lib/Horde/Xml/Element.php';
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/lib/Horde/Xml/Element/Exception.php';
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/lib/Horde/Xml/Element/List.php';
+
+class Horde_Xml_Element_CountTest extends PHPUnit_Framework_TestCase
+{
+    public function testCount()
+    {
+        $l = new Horde_Xml_Element_CountTest_List(
+            '<?xml version="1.0" encoding="utf-8"?><list><item>1</item><item>2</item></list>'
+        );
+        $this->assertEquals(2, $l->count(), 'List count should be 2');
+    }
+
+}
+
+class Horde_Xml_Element_CountTest_List extends Horde_Xml_Element_List
+{
+    protected function _buildListItemCache()
+    {
+        $results = array();
+        foreach ($this->_element->childNodes as $child) {
+            if ($child->localName == 'item') {
+                $results[] = $child;
+            }
+        }
+
+        return $results;
+    }
+
+}
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/ElementTest.php b/framework/Xml_Element/test/Horde/Xml/Element/ElementTest.php
new file mode 100644 (file)
index 0000000..72928fc
--- /dev/null
@@ -0,0 +1,249 @@
+<?php
+/**
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php BSD
+ * @category   Horde
+ * @package    Horde_Xml_Element
+ * @subpackage UnitTests
+ */
+
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/lib/Horde/Xml/Element.php';
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/lib/Horde/Xml/Element/Exception.php';
+
+/**
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php BSD
+ * @category   Horde
+ * @package    Horde_Xml_Element
+ * @subpackage UnitTests
+ */
+class Horde_Xml_Element_ElementTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        $this->element = new Horde_Xml_Element(file_get_contents(dirname(__FILE__) . '/fixtures/Sample.xml'));
+        $this->namespacedElement = new Horde_Xml_Element(file_get_contents(dirname(__FILE__) . '/fixtures/NamespacedSample.xml'));
+    }
+
+    public function testXml()
+    {
+        $el = new Horde_Xml_Element('<root href="#">Root</root>');
+        $this->assertEquals((string)$el, 'Root');
+        $this->assertEquals($el['href'], '#');
+    }
+
+    public function testInvalidXml()
+    {
+        $failed = false;
+        try {
+            new Horde_Xml_Element('<root');
+        } catch (Horde_Xml_Element_Exception $e) {
+            $failed = true;
+        }
+        $this->assertTrue($failed, 'Invalid XML should result in an exception');
+    }
+
+    public function testSerialization()
+    {
+        $elt = new Horde_Xml_Element('<entry><title>Test</title></entry>');
+        $this->assertEquals($elt->title(), 'Test', 'Value before serialization/unserialization');
+        $serialized = serialize($elt);
+        $unserialized = unserialize($serialized);
+        $this->assertEquals($unserialized->title(), 'Test', 'Value after serialization/unserialization');
+    }
+
+    public function testExists()
+    {
+        $this->assertFalse(isset($this->element[-1]), 'Negative array access should fail');
+        $this->assertTrue(isset($this->element['version']), 'Version should be set');
+
+        $this->assertFalse(isset($this->namespacedElement[-1]), 'Negative array access should fail');
+        $this->assertTrue(isset($this->namespacedElement['version']), 'Version should be set');
+    }
+
+    public function testGet()
+    {
+        $this->assertEquals($this->element['version'], '1.0', 'Version should be 1.0');
+        $this->assertEquals($this->namespacedElement['version'], '1.0', 'Version should be 1.0');
+    }
+
+    public function testArrayGet()
+    {
+        $this->assertTrue(is_array($this->element->entry));
+        $this->assertTrue(is_array($this->namespacedElement->entry));
+
+        foreach ($this->element->entry as $entry) {
+            $this->assertTrue($entry instanceof Horde_Xml_Element);
+        }
+
+        foreach ($this->namespacedElement->entry as $entry) {
+            $this->assertTrue($entry instanceof Horde_Xml_Element);
+        }
+    }
+
+    public function testSet()
+    {
+        $this->element['category'] = 'tests';
+        $this->assertTrue(isset($this->element['category']), 'Category should be set');
+        $this->assertEquals($this->element['category'], 'tests', 'Category should be tests');
+
+        $this->namespacedElement['atom:category'] = 'tests';
+        $this->assertTrue(isset($this->namespacedElement['atom:category']), 'Category should be set');
+        $this->assertEquals($this->namespacedElement['atom:category'], 'tests', 'Category should be tests');
+
+        // Changing an existing index.
+        $oldEntry = $this->element['version'];
+        $this->element['version'] = '1.1';
+        $this->assertTrue($oldEntry != $this->element['version'], 'Version should have changed');
+    }
+
+    public function testOffsetUnset()
+    {
+        $element = new Horde_Xml_Element(file_get_contents(dirname(__FILE__) . '/fixtures/Sample.xml'));
+        $namespacedElement = new Horde_Xml_Element(file_get_contents(dirname(__FILE__) . '/fixtures/NamespacedSample.xml'));
+
+        $this->assertTrue(isset($element['version']));
+        unset($element['version']);
+        $this->assertFalse(isset($element['version']), 'Version should be unset');
+        $this->assertEquals('', $element['version'], 'Version should be equal to the empty string');
+
+        $this->assertTrue(isset($namespacedElement['version']));
+        unset($namespacedElement['version']);
+        $this->assertFalse(isset($namespacedElement['version']), 'Version should be unset');
+        $this->assertEquals('', $namespacedElement['version'], 'Version should be equal to the empty string');
+    }
+
+    public function testUnset()
+    {
+        $element = new Horde_Xml_Element(file_get_contents(dirname(__FILE__) . '/fixtures/Sample.xml'));
+        $namespacedElement = new Horde_Xml_Element(file_get_contents(dirname(__FILE__) . '/fixtures/NamespacedSample.xml'));
+
+        $this->assertTrue(isset($element->title));
+        unset($element->title);
+        $this->assertFalse(isset($element->title));
+
+        $this->assertTrue(isset($namespacedElement->title));
+        unset($namespacedElement->title);
+        $this->assertFalse(isset($namespacedElement->title));
+    }
+
+    public function testIsInitialized()
+    {
+        $e = new Horde_Xml_Element('<element />');
+
+        $e->author->name['last'] = 'Lastname';
+        $e->author->name['first'] = 'Firstname';
+        $e->author->name->{'Firstname:url'} = 'www.example.com';
+
+        $e->author->title['foo'] = 'bar';
+        if ($e->pants()) {
+            $this->fail('<pants> does not exist, it should not have a true value');
+            // This should not create an element in the actual tree.
+        }
+        if ($e->pants()) {
+            $this->fail('<pants> should not have been created by testing for it');
+            // This should not create an element in the actual tree.
+        }
+
+        $xml = $e->saveXML();
+
+        $this->assertFalse(strpos($xml, 'pants'), '<pants> should not be in the xml output');
+        $this->assertTrue(strpos($xml, 'www.example.com') !== false, 'the url attribute should be set');
+    }
+
+    public function testStrings()
+    {
+        $xml = "<entry>
+    <title> Using C++ Intrinsic Functions for Pipelined Text Processing</title>
+    <id>http://www.oreillynet.com/pub/wlg/8356</id>
+    <link rel='alternate' href='http://www.oreillynet.com/pub/wlg/8356'/>
+    <summary type='xhtml'>
+    <div xmlns='http://www.w3.org/1999/xhtml'>
+    A good C++ programming technique that has almost no published material available on the WWW relates to using the special pipeline instructions in modern CPUs for faster text processing. Here's example code using C++ intrinsic functions to give a fourfold speed increase for a UTF-8 to UTF-16 converter compared to the original C/C++ code.
+    </div>
+    </summary>
+    <author><name>Rick Jelliffe</name></author>
+    <updated>2005-11-07T08:15:57-08:00</updated>
+</entry>";
+        $element = new Horde_Xml_Element($xml);
+
+        $this->assertTrue($element->summary instanceof Horde_Xml_Element, '__get access should return a Horde_Xml_Element instance');
+        $this->assertFalse($element->summary() instanceof Horde_Xml_Element, 'method access should not return a Horde_Xml_Element instance');
+        $this->assertTrue(is_string($element->summary()), 'method access should return a string');
+        $this->assertFalse(is_string($element->summary), '__get access should not return a string');
+    }
+
+    public function testAppendChild()
+    {
+        $e = new Horde_Xml_Element('<element />');
+        $e2 = new Horde_Xml_Element('<child />');
+
+        $e->appendChild($e2);
+        $this->assertEquals($e->saveXmlFragment(),
+                            '<element><child/></element>');
+    }
+
+    public function testFromArray()
+    {
+        $e = new Horde_Xml_Element('<element />');
+
+        $e->fromArray(array('user' => 'Joe Schmoe',
+                            'atom:title' => 'Test Title',
+                            'child#href' => 'http://www.example.com/',
+                            '#title' => 'Test Element'));
+
+        $this->assertEquals($e->saveXmlFragment(),
+                            '<element title="Test Element"><user>Joe Schmoe</user><atom:title xmlns:atom="http://www.w3.org/2005/Atom">Test Title</atom:title><child href="http://www.example.com/"/></element>');
+
+        $e = new Horde_Xml_Element('<element />');
+        $e->fromArray(array('author' => array('name' => 'Joe', 'email' => 'joe@example.com')));
+
+        $this->assertEquals($e->saveXmlFragment(),
+                            '<element><author><name>Joe</name><email>joe@example.com</email></author></element>');
+    }
+
+    public function testIllegalFromArray()
+    {
+        $failed = false;
+        $e = new Horde_Xml_Element('<element />');
+        try {
+            $e->fromArray(array('#name' => array('foo' => 'bar')));
+        } catch (InvalidArgumentException $e) {
+            $failed = true;
+        }
+        $this->assertTrue($failed);
+    }
+
+    public function testCustomGetterGet()
+    {
+        $xml = '<element><author><name>Joe</name><email>joe@example.com</email></author></element>';
+        $e = new Horde_Xml_Element_CustomGetter($xml);
+
+        $this->assertEquals($e->author, $e->writer);
+        $this->assertEquals($e->author->name, $e->psuedonym);
+    }
+
+    public function testCustomGetterCall()
+    {
+        $xml = '<element><author><name>Joe</name><email>joe@example.com</email></author></element>';
+        $e = new Horde_Xml_Element_CustomGetter($xml);
+
+        $this->assertEquals($e->author(), $e->writer());
+        $this->assertEquals($e->author->name(), $e->psuedonym());
+    }
+
+}
+
+class Horde_Xml_Element_CustomGetter extends Horde_Xml_Element
+{
+    public function getWriter()
+    {
+        return $this->author;
+    }
+
+    public function getPsuedonym()
+    {
+        return $this->author->name;
+    }
+
+}
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/IteratorTest.php b/framework/Xml_Element/test/Horde/Xml/Element/IteratorTest.php
new file mode 100644 (file)
index 0000000..1309036
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php BSD
+ * @category   Horde
+ * @package    Horde_Xml_Element
+ * @subpackage UnitTests
+ */
+
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/lib/Horde/Xml/Element.php';
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/lib/Horde/Xml/Element/Exception.php';
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . '/lib/Horde/Xml/Element/List.php';
+
+class Horde_Xml_Element_IteratorTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        $this->list = new Horde_Xml_Element_IteratorTest_List(
+            '<?xml version="1.0" encoding="utf-8"?><list><item>1</item><item>2</item></list>'
+        );
+    }
+
+    public function testRewind()
+    {
+        $times = 0;
+        foreach ($this->list as $i) {
+            ++$times;
+        }
+
+        $times2 = 0;
+        foreach ($this->list as $i) {
+            ++$times2;
+        }
+
+        $this->assertEquals($times, $times2, 'List should have the same number of iterations multiple times through');
+    }
+
+    public function testCurrent()
+    {
+        foreach ($this->list as $i) {
+            $this->assertType('Horde_Xml_Element', $i, 'Each list item should be an instance of Horde_Xml_Element');
+            break;
+        }
+    }
+
+    public function testKey()
+    {
+        $keys = array();
+        foreach ($this->list as $k => $i) {
+            $keys[] = $k;
+        }
+        $this->assertEquals($keys, array(0, 1), 'List should have keys 0 and 1');
+    }
+
+    public function testNext()
+    {
+        $last = null;
+        foreach ($this->list as $current) {
+            $this->assertFalse($last === $current, 'Iteration should produce a new object each item');
+            $last = $current;
+        }
+    }
+
+}
+
+class Horde_Xml_Element_IteratorTest_List extends Horde_Xml_Element_List
+{
+    protected function _buildListItemCache()
+    {
+        $results = array();
+        foreach ($this->_element->childNodes as $child) {
+            if ($child->localName == 'item') {
+                $results[] = $child;
+            }
+        }
+
+        return $results;
+    }
+
+}
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Entries b/framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Entries
new file mode 100644 (file)
index 0000000..6f402d6
--- /dev/null
@@ -0,0 +1,3 @@
+/NamespacedSample.xml/1.1/Wed Mar  5 20:37:56 2008//
+/Sample.xml/1.1/Wed Mar  5 20:37:56 2008//
+D
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Repository b/framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Repository
new file mode 100644 (file)
index 0000000..4ac2119
--- /dev/null
@@ -0,0 +1 @@
+framework/Xml_Element/test/Horde/Xml/Element/fixtures
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Root b/framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Root
new file mode 100644 (file)
index 0000000..5d63612
--- /dev/null
@@ -0,0 +1 @@
+chuck@cvs.horde.org:/repository
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Template b/framework/Xml_Element/test/Horde/Xml/Element/fixtures/CVS/Template
new file mode 100644 (file)
index 0000000..3971591
--- /dev/null
@@ -0,0 +1,8 @@
+
+Bug: 
+Submitted by: 
+Merge after:
+CVS: ----------------------------------------------------------------------
+CVS: Bug:           Fill this in if a listed bug is affected by the change.
+CVS: Submitted by:  Fill this in if someone else sent in the change.
+CVS: Merge after:   N [day[s]|week[s]|month[s]] (days assumed by default)
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/fixtures/NamespacedSample.xml b/framework/Xml_Element/test/Horde/Xml/Element/fixtures/NamespacedSample.xml
new file mode 100644 (file)
index 0000000..02d0ee2
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<atom:feed xmlns:atom="http://www.w3.org/2005/Atom" xmlns:opensearch="http://a9.com/-/spec/opensearchrss/1.0/" version="1.0">
+ <title>Atom Example</title>
+ <tagline>This is a simple Atom Feed made by XML_Feed_Writer.</tagline>
+ <atom:link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="./TestAtomFeedNamespaced.xml" />
+ <atom:link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" href="./TestAtomFeedNamespaced.xml" />
+ <atom:link rel="self" type="application/atom+xml" href="./TestAtomFeedNamespaced.xml" />
+ <opensearch:totalResults>4</opensearch:totalResults>
+ <opensearch:startIndex>3</opensearch:startIndex>
+ <opensearch:itemsPerPage>2</opensearch:itemsPerPage>
+ <modified>2005-04-25T00:00:00+02:00</modified>
+ <atom:entry>
+  <atom:id>1</atom:id>
+  <atom:link rel="edit" href="./TestAtomFeedNamespaced.xml/1/1" />
+  <title>The Item Title</title>
+  <issued>2004-09-25T16:03:00+02:00</issued>
+  <modified>2005-12-25T16:03:00+01:00</modified>
+  <author>
+   <name>David Coallier</name>
+  </author>
+  <summary>Testing something before releasing</summary>
+ </atom:entry>
+ <atom:entry>
+  <atom:id>2</atom:id>
+  <atom:link rel="edit" href="./TestAtomFeedNamespaced.xml/2/1" />
+  <title>Second item added to the builder/feeded..</title>
+  <issued>2004-01-04T00:00:00+01:00</issued>
+  <modified>1970-01-01T01:00:00+01:00</modified>
+  <author>
+   <name>David Coallier</name>
+  </author>
+  <summary>Jaws project, visit the website for infos...</summary>
+ </atom:entry>
+</atom:feed>
diff --git a/framework/Xml_Element/test/Horde/Xml/Element/fixtures/Sample.xml b/framework/Xml_Element/test/Horde/Xml/Element/fixtures/Sample.xml
new file mode 100644 (file)
index 0000000..69afce7
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xmlns:opensearch="http://a9.com/-/spec/opensearchrss/1.0/" version="1.0">
+ <title>Atom Example</title>
+ <tagline>This is a simple Atom Feed made by XML_Feed_Writer.</tagline>
+ <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="./TestAtomFeed.xml" />
+ <link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" href="./TestAtomFeed.xml" />
+ <link rel="self" type="application/atom+xml" href="./TestAtomFeed.xml" />
+ <opensearch:totalResults>4</opensearch:totalResults>
+ <opensearch:startIndex>3</opensearch:startIndex>
+ <opensearch:itemsPerPage>2</opensearch:itemsPerPage>
+ <modified>2005-04-25T00:00:00+02:00</modified>
+ <entry>
+  <id>1</id>
+  <link rel="edit" href="./TestAtomFeed.xml/1/1" />
+  <title>The Item Title</title>
+  <issued>2004-09-25T16:03:00+02:00</issued>
+  <modified>2005-12-25T16:03:00+01:00</modified>
+  <author>
+   <name>David Coallier</name>
+  </author>
+  <summary>Testing something before releasing</summary>
+ </entry>
+ <entry>
+  <id>2</id>
+  <link rel="edit" href="./TestAtomFeed.xml/2/1" />
+  <title>Second item added to the builder/feeded..</title>
+  <issued>2004-01-04T00:00:00+01:00</issued>
+  <modified>1970-01-01T01:00:00+01:00</modified>
+  <author>
+   <name>David Coallier</name>
+  </author>
+  <summary>Jaws project, visit the website for infos...</summary>
+ </entry>
+</feed>