 Added: svn:keywords
    + Id
 
-Index: skeleton/xml/meta/Marshaller.class.php
-===================================================================
---- skeleton/xml/meta/Marshaller.class.php	(revision 14616)
-+++ skeleton/xml/meta/Marshaller.class.php	(working copy)
-@@ -63,7 +63,7 @@
-         
-         // Node content
-         if ('.' == $element) {
--          $node->setContent($method->invoke($instance));
-+          $node->addChild(new Text($method->invoke($instance)));
-           continue;
-         }
-         
 Index: skeleton/xml/PCData.class.php
 ===================================================================
 --- skeleton/xml/PCData.class.php	(revision 14616)

           ));
         }
       }
Index: skeleton/xml/Node.class.php
===================================================================
--- skeleton/xml/Node.class.php	(revision 14616)
+++ skeleton/xml/Node.class.php	(working copy)
@@ -6,8 +6,10 @@
  */
 
   uses(
+    'xml.Element',
     'xml.PCData',
     'xml.CData',
+    'xml.Text',
     'xml.XMLFormatException'
   );
   
@@ -23,14 +25,13 @@
    * @see   xp://xml.Tree#addChild
    * @test  xp://net.xp_framework.unittest.xml.NodeTest
    */
-  class Node extends Object {
+  class Node extends Object implements Element {
     const
       XML_ILLEGAL_CHARS   = XML_ILLEGAL_CHARS;
 
     public 
       $name         = '',
       $attribute    = array(),
-      $content      = NULL,
       $children     = array();
 
     /**
@@ -45,12 +46,18 @@
      * @param   string name
      * @param   string content default NULL
      * @param   array<string, string> attribute default array() attributes
-     * @throws  lang.IllegalArgumentException
+     * @throws  xml.XMLFormatException
      */
     public function __construct($name, $content= NULL, $attribute= array()) {
       $this->name= $name;
       $this->attribute= $attribute;
-      $this->setContent($content);
+      if (NULL !== $content) {
+        if ($content instanceof Element) {
+          $this->children= array($content);
+        } else if ('' !== $content) {
+          $this->children= array(new Text($content));
+        }
+      }
     }
 
     /**
@@ -131,31 +138,40 @@
     /**
      * Set content
      *
+     * @deprecated  Use addChild(new Text(...)) instead
      * @param   string content
      * @throws  xml.XMLFormatException in case content contains illegal characters
      */
     public function setContent($content) {
-
-      // Scan the given string for illegal characters.
-      if (is_string($content)) {  
-        if (strlen($content) > ($p= strcspn($content, XML_ILLEGAL_CHARS))) {
-          throw new XMLFormatException(
-            'Content contains illegal character at position '.$p. ' / chr('.ord($content{$p}).')'
-          );
-        }
-      }
-      
-      $this->content= $content;
+      $this->children[]= new Text($content);
     }
     
     /**
      * Get content (all CDATA)
      *
+     * @deprecated  Use textContent() instead
      * @return  string content
      */
     public function getContent() {
-      return $this->content;
+      return $this->textContent();
     }
+    
+    /**
+     * Get content
+     *
+     * @return  string content
+     */
+    public function textContent() {
+      $c= '';
+      foreach ($this->children as $child) {
+        if ($child instanceof Text) {
+          $c.= $child->getContent();
+        } else if ($child instanceof self) {
+          $c.= $child->textContent();
+        }
+      }
+      return $c;
+    }
 
     /**
      * Set an attribute
@@ -234,31 +250,16 @@
     public function getSource($indent= INDENT_WRAPPED, $encoding= 'iso-8859-1', $inset= '') {
       $xml= $inset.'<'.$this->name;
       $conv= 'iso-8859-1' != $encoding;
-      
-      if ('string' == ($type= gettype($this->content))) {
+
+      if (1 === sizeof($this->children) && $this->children[0] instanceof Text) {
+        $children= array();
         $content= $conv
-          ? iconv('iso-8859-1', $encoding, htmlspecialchars($this->content))
-          : htmlspecialchars($this->content)
+          ? iconv('iso-8859-1', $encoding, htmlspecialchars($this->children[0]->getContent()))
+          : htmlspecialchars($this->children[0]->getContent())
         ;
-      } else if ('float' == $type) {
-        $content= ($this->content - floor($this->content) == 0)
-          ? number_format($this->content, 0, NULL, NULL)
-          : $this->content
-        ;
-      } else if ($this->content instanceof PCData) {
-        $content= $conv
-          ? iconv('iso-8859-1', $encoding, $this->content->pcdata)
-          : $this->content->pcdata
-        ;
-      } else if ($this->content instanceof CData) {
-        $content= '<![CDATA['.str_replace(']]>', ']]]]><![CDATA[>', $conv
-          ? iconv('iso-8859-1', $encoding, $this->content->cdata)
-          : $this->content->cdata
-        ).']]>';
-      } else if ($this->content instanceof String) {
-        $content= htmlspecialchars($this->content->getBytes($encoding));
       } else {
-        $content= $this->content; 
+        $children= $this->children;
+        $content= NULL; 
       }
       
       if (INDENT_NONE === $indent) {
@@ -268,8 +269,12 @@
             : $value
           ).'"';
         }
+
+        // No content and no children => close tag
+        if (NULL === $content && !$children) return $xml.'/>';
+        
         $xml.= '>'.$content;
-        foreach ($this->children as $child) {
+        foreach ($children as $child) {
           $xml.= $child->getSource($indent, $encoding, $inset);
         }
         return $xml.'</'.$this->name.'>';
@@ -286,16 +291,16 @@
         }
 
         // No content and no children => close tag
-        if (0 == strlen($content)) {
-          if (!$this->children) return $xml."/>\n";
+        if (NULL === $content) {
+          if (!$children) return $xml."/>\n";
           $xml.= '>';
         } else {
           $xml.= '>'.($indent ? "\n  ".$inset.$content : trim($content));
         }
 
-        if ($this->children) {
+        if ($children) {
           $xml.= ($indent ? '' : $inset)."\n";
-          foreach ($this->children as $child) {
+          foreach ($children as $child) {
             $xml.= $child->getSource($indent, $encoding, $inset.'  ');
           }
           $xml= ($indent ? substr($xml, 0, -1) : $xml).$inset;
@@ -307,25 +312,52 @@
     /**
      * Add a child node
      *
-     * @param   xml.Node child
-     * @return  xml.Node added child
+     * @param   xml.Element child
+     * @return  xml.Element added child
      * @throws  lang.IllegalArgumentException in case the given argument is not a Node
      */
-    public function addChild(Node $child) {
+    public function addChild(Element $child) {
       $this->children[]= $child;
       return $child;
     }
+    
+    /**
+     * Returns whether another object is equal to this node
+     *
+     * @param   lang.Generic cmp
+     * @return  bool
+     */
+    public function equals($cmp) {
+      return $cmp instanceof self && $this->getSource(INDENT_NONE) === $cmp->getSource(INDENT_NONE);
+    }
 
     /**
      * Add a child node and return this node
      *
-     * @param   xml.Node child
+     * @param   xml.Element child
      * @return  xml.Node this
      * @throws  lang.IllegalArgumentException in case the given argument is not a Node
      */
-    public function withChild(Node $child) {
+    public function withChild(Element $child) {
       $this->addChild($child);
       return $this;
     }
+
+    /**
+     * Creates a string representation of this object
+     *
+     * @return  string
+     */
+    public function toString() {
+      $a= '';
+      foreach ($this->attribute as $name => $value) {
+        $a.= ' @'.$name.'= '.xp::stringOf($value);
+      }
+      $s= $this->getClassName().'('.$this->name.$a.") {\n";
+      foreach ($this->children as $child) {
+        $s.= '  '.str_replace("\n", "\n  ", xp::stringOf($child))."\n";
+      }
+      return $s.'}';
+    }
   }
 ?>
Index: skeleton/xml/Element.class.php
===================================================================
--- skeleton/xml/Element.class.php	(revision 0)

Index: skeleton/xml/Node.class.php
===================================================================
--- skeleton/xml/Node.class.php	(revision 14616)
+++ skeleton/xml/Node.class.php	(working copy)
@@ -6,8 +6,10 @@
  */
 
   uses(
+    'xml.Element',
     'xml.PCData',
     'xml.CData',
+    'xml.Text',
     'xml.XMLFormatException'
   );
   
@@ -23,14 +25,13 @@
    * @see   xp://xml.Tree#addChild
    * @test  xp://net.xp_framework.unittest.xml.NodeTest
    */
-  class Node extends Object {
+  class Node extends Object implements Element {
     const
       XML_ILLEGAL_CHARS   = XML_ILLEGAL_CHARS;
 
     public 
       $name         = '',
       $attribute    = array(),
-      $content      = NULL,
       $children     = array();
 
     /**
@@ -45,12 +46,18 @@
      * @param   string name
      * @param   string content default NULL
      * @param   array<string, string> attribute default array() attributes
-     * @throws  lang.IllegalArgumentException
+     * @throws  xml.XMLFormatException
      */
     public function __construct($name, $content= NULL, $attribute= array()) {
       $this->name= $name;
       $this->attribute= $attribute;
-      $this->setContent($content);
+      if (NULL !== $content) {
+        if ($content instanceof Element) {
+          $this->children= array($content);
+        } else if ('' !== $content) {
+          $this->children= array(new Text($content));
+        }
+      }
     }
 
     /**
@@ -131,31 +138,40 @@
     /**
      * Set content
      *
+     * @deprecated  Use addChild(new Text(...)) instead
      * @param   string content
      * @throws  xml.XMLFormatException in case content contains illegal characters
      */
     public function setContent($content) {
-
-      // Scan the given string for illegal characters.
-      if (is_string($content)) {  
-        if (strlen($content) > ($p= strcspn($content, XML_ILLEGAL_CHARS))) {
-          throw new XMLFormatException(
-            'Content contains illegal character at position '.$p. ' / chr('.ord($content{$p}).')'
-          );
-        }
-      }
-      
-      $this->content= $content;
+      $this->children[]= new Text($content);
     }
     
     /**
      * Get content (all CDATA)
      *
+     * @deprecated  Use textContent() instead
      * @return  string content
      */
     public function getContent() {
-      return $this->content;
+      return $this->textContent();
     }
+    
+    /**
+     * Get content
+     *
+     * @return  string content
+     */
+    public function textContent() {
+      $c= '';
+      foreach ($this->children as $child) {
+        if ($child instanceof Text) {
+          $c.= $child->getContent();
+        } else if ($child instanceof self) {
+          $c.= $child->textContent();
+        }
+      }
+      return $c;
+    }
 
     /**
      * Set an attribute
@@ -234,31 +250,16 @@
     public function getSource($indent= INDENT_WRAPPED, $encoding= 'iso-8859-1', $inset= '') {
       $xml= $inset.'<'.$this->name;
       $conv= 'iso-8859-1' != $encoding;
-      
-      if ('string' == ($type= gettype($this->content))) {
+
+      if (1 === sizeof($this->children) && $this->children[0] instanceof Text) {
+        $children= array();
         $content= $conv
-          ? iconv('iso-8859-1', $encoding, htmlspecialchars($this->content))
-          : htmlspecialchars($this->content)
+          ? iconv('iso-8859-1', $encoding, htmlspecialchars($this->children[0]->getContent()))
+          : htmlspecialchars($this->children[0]->getContent())
         ;
-      } else if ('float' == $type) {
-        $content= ($this->content - floor($this->content) == 0)
-          ? number_format($this->content, 0, NULL, NULL)
-          : $this->content
-        ;
-      } else if ($this->content instanceof PCData) {
-        $content= $conv
-          ? iconv('iso-8859-1', $encoding, $this->content->pcdata)
-          : $this->content->pcdata
-        ;
-      } else if ($this->content instanceof CData) {
-        $content= '<![CDATA['.str_replace(']]>', ']]]]><![CDATA[>', $conv
-          ? iconv('iso-8859-1', $encoding, $this->content->cdata)
-          : $this->content->cdata
-        ).']]>';
-      } else if ($this->content instanceof String) {
-        $content= htmlspecialchars($this->content->getBytes($encoding));
       } else {
-        $content= $this->content; 
+        $children= $this->children;
+        $content= NULL; 
       }
       
       if (INDENT_NONE === $indent) {
@@ -268,8 +269,12 @@
             : $value
           ).'"';
         }
+
+        // No content and no children => close tag
+        if (NULL === $content && !$children) return $xml.'/>';
+        
         $xml.= '>'.$content;
-        foreach ($this->children as $child) {
+        foreach ($children as $child) {
           $xml.= $child->getSource($indent, $encoding, $inset);
         }
         return $xml.'</'.$this->name.'>';
@@ -286,16 +291,16 @@
         }
 
         // No content and no children => close tag
-        if (0 == strlen($content)) {
-          if (!$this->children) return $xml."/>\n";
+        if (NULL === $content) {
+          if (!$children) return $xml."/>\n";
           $xml.= '>';
         } else {
           $xml.= '>'.($indent ? "\n  ".$inset.$content : trim($content));
         }
 
-        if ($this->children) {
+        if ($children) {
           $xml.= ($indent ? '' : $inset)."\n";
-          foreach ($this->children as $child) {
+          foreach ($children as $child) {
             $xml.= $child->getSource($indent, $encoding, $inset.'  ');
           }
           $xml= ($indent ? substr($xml, 0, -1) : $xml).$inset;
@@ -307,25 +312,52 @@
     /**
      * Add a child node
      *
-     * @param   xml.Node child
-     * @return  xml.Node added child
+     * @param   xml.Element child
+     * @return  xml.Element added child
      * @throws  lang.IllegalArgumentException in case the given argument is not a Node
      */
-    public function addChild(Node $child) {
+    public function addChild(Element $child) {
       $this->children[]= $child;
       return $child;
     }
+    
+    /**
+     * Returns whether another object is equal to this node
+     *
+     * @param   lang.Generic cmp
+     * @return  bool
+     */
+    public function equals($cmp) {
+      return $cmp instanceof self && $this->getSource(INDENT_NONE) === $cmp->getSource(INDENT_NONE);
+    }
 
     /**
      * Add a child node and return this node
      *
-     * @param   xml.Node child
+     * @param   xml.Element child
      * @return  xml.Node this
      * @throws  lang.IllegalArgumentException in case the given argument is not a Node
      */
-    public function withChild(Node $child) {
+    public function withChild(Element $child) {
       $this->addChild($child);
       return $this;
     }
+
+    /**
+     * Creates a string representation of this object
+     *
+     * @return  string
+     */
+    public function toString() {
+      $a= '';
+      foreach ($this->attribute as $name => $value) {
+        $a.= ' @'.$name.'= '.xp::stringOf($value);
+      }
+      $s= $this->getClassName().'('.$this->name.$a.") {\n";
+      foreach ($this->children as $child) {
+        $s.= '  '.str_replace("\n", "\n  ", xp::stringOf($child))."\n";
+      }
+      return $s.'}';
+    }
   }
 ?>
Index: skeleton/xml/dom/Document.class.php
===================================================================
--- skeleton/xml/dom/Document.class.php	(revision 14616)
