##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""
DOM implementation in StructuredText : Read-Only methods

All standard Zope objects support DOM to a limited extent.
"""
import string


# Node type codes
# ---------------

ELEMENT_NODE                  = 1
ATTRIBUTE_NODE                = 2
TEXT_NODE                     = 3
CDATA_SECTION_NODE            = 4
ENTITY_REFERENCE_NODE         = 5
ENTITY_NODE                   = 6
PROCESSING_INSTRUCTION_NODE   = 7
COMMENT_NODE                  = 8
DOCUMENT_NODE                 = 9
DOCUMENT_TYPE_NODE            = 10
DOCUMENT_FRAGMENT_NODE        = 11
NOTATION_NODE                 = 12

# Exception codes
# ---------------

INDEX_SIZE_ERR                = 1
DOMSTRING_SIZE_ERR            = 2
HIERARCHY_REQUEST_ERR         = 3
WRONG_DOCUMENT_ERR            = 4
INVALID_CHARACTER_ERR         = 5
NO_DATA_ALLOWED_ERR           = 6
NO_MODIFICATION_ALLOWED_ERR   = 7
NOT_FOUND_ERR                 = 8
NOT_SUPPORTED_ERR             = 9
INUSE_ATTRIBUTE_ERR           = 10

# Exceptions
# ----------

class DOMException(Exception):
    pass
class IndexSizeException(DOMException):
    code = INDEX_SIZE_ERR
class DOMStringSizeException(DOMException):
    code = DOMSTRING_SIZE_ERR
class HierarchyRequestException(DOMException):
    code = HIERARCHY_REQUEST_ERR
class WrongDocumentException(DOMException):
    code = WRONG_DOCUMENT_ERR
class InvalidCharacterException(DOMException):
    code = INVALID_CHARACTER_ERR
class NoDataAllowedException(DOMException):
    code = NO_DATA_ALLOWED_ERR
class NoModificationAllowedException(DOMException):
    code = NO_MODIFICATION_ALLOWED_ERR
class NotFoundException(DOMException):
    code = NOT_FOUND_ERR
class NotSupportedException(DOMException):
    code = NOT_SUPPORTED_ERR
class InUseAttributeException(DOMException):
    code = INUSE_ATTRIBUTE_ERR

# Node classes
# ------------

class ParentNode:
   """
   A node that can have children, or, more precisely, that implements
   the child access methods of the DOM.
   """

   def getChildNodes(self, type=type, st=type('')):
      """
      Returns a NodeList that contains all children of this node.
      If there are no children, this is a empty NodeList
      """
      
      r=[]
      for n in self.getChildren():
         if type(n) is st: n=TextNode(n)
         r.append(n.__of__(self))
      
      return  NodeList(r)
   
   def getFirstChild(self, type=type, st=type('')):
      """
      The first child of this node. If there is no such node
      this returns None
      """
      children = self.getChildren()

      if not children:
         return None
         
      n=children[0]

      if type(n) is st:
         n=TextNode(n)
         
      return n.__of__(self)

   def getLastChild(self, type=type, st=type('')):
      """
      The last child of this node.  If there is no such node
      this returns None.
      """
      children = self.getChildren()
      if not children: return None
      n=chidren[-1]
      if type(n) is st: n=TextNode(n)
      return n.__of__(self)

   """
   create aliases for all above functions in the pythony way.
   """
   
   def _get_ChildNodes(self, type=type, st=type('')):
      return self.getChildNodes(type,st)
   
   def _get_FirstChild(self, type=type, st=type('')):
      return self.getFirstChild(type,st)

   def _get_LastChild(self, type=type, st=type('')):
      return self.getLastChild(type,st)
  
class NodeWrapper(ParentNode):
   """
   This is an acquisition-like wrapper that provides parent access for 
   DOM sans circular references!
   """

   def __init__(self, aq_self, aq_parent):
      self.aq_self=aq_self
      self.aq_parent=aq_parent

   def __getattr__(self, name):
      return getattr(self.aq_self, name)
    
   def getParentNode(self):
      """
      The parent of this node.  All nodes except Document
      DocumentFragment and Attr may have a parent
      """
      return self.aq_parent

   def _getDOMIndex(self, children, getattr=getattr):
      i=0
      self=self.aq_self
      for child in children:
         if getattr(child, 'aq_self', child) is self:
            self._DOMIndex=i
            return i
         i=i+1
      return None

   def getPreviousSibling(self,
                          type=type,
                          st=type(''),
                          getattr=getattr,
                          none=None):

      """
      The node immediately preceding this node.  If
      there is no such node, this returns None.
      """
      
      children = self.aq_parent.getChildren()
      if not children:
         return None

      index=getattr(self, '_DOMIndex', none)
      if index is none:
         index=self._getDOMIndex(children)
         if index is none: return None

      index=index-1
      if index < 0: return None
      try: n=children[index]
      except IndexError: return None
      else:
         if type(n) is st:
            n=TextNode(n)
         n._DOMIndex=index
         return n.__of__(self)


   def getNextSibling(self, type=type, st=type('')):
      """
      The node immediately preceding this node.  If
      there is no such node, this returns None.
      """
      children = self.aq_parent.getChildren()
      if not children:
         return None

      index=getattr(self, '_DOMIndex', None)
      if index is None:
         index=self._getDOMIndex(children)
         if index is None:
            return None

      index=index+1
      try: n=children[index]
      except IndexError:
         return None
      else:
         if type(n) is st:
            n=TextNode(n)
         n._DOMIndex=index
         return n.__of__(self)

   def getOwnerDocument(self):
      """
      The Document object associated with this node, if any.
      """
      return self.aq_parent.getOwnerDocument()

   """
   create aliases for all above functions in the pythony way.   
   """
   
   def _get_ParentNode(self):
      return self.getParentNode()

   def _get_DOMIndex(self, children, getattr=getattr):
      return self._getDOMIndex(children,getattr)
      
   def _get_PreviousSibling(self,
                          type=type,
                          st=type(''),
                          getattr=getattr,
                          none=None):

      return self.getPreviousSibling(type,st,getattr,None)
      
   def _get_NextSibling(self, type=type, st=type('')):
      return self.getNextSibling(type,st)
      
   def _get_OwnerDocument(self):
      return self.getOwnerDocument()
      
class Node(ParentNode):
   """
   Node Interface
   """

   # Get a DOM wrapper with a parent link
   def __of__(self, parent):
      return NodeWrapper(self, parent)

   # DOM attributes    
   # --------------
    
   def getNodeName(self):
      """
      The name of this node, depending on its type
      """

   def getNodeValue(self):
      """
      The value of this node, depending on its type
      """
      return None

   def getParentNode(self):
      """
      The parent of this node.  All nodes except Document
      DocumentFragment and Attr may have a parent
      """

   def getChildren(self):
      """
      Get a Python sequence of children
      """
      return ()

   def getPreviousSibling(self,
                          type=type,
                          st=type(''),
                          getattr=getattr,
                          none=None):
      """
      The node immediately preceding this node.  If
      there is no such node, this returns None.
      """

   def getNextSibling(self, type=type, st=type('')):
      """
      The node immediately preceding this node.  If
      there is no such node, this returns None.
      """

   def getAttributes(self):
      """
      Returns a NamedNodeMap containing the attributes
      of this node (if it is an element) or None otherwise.
      """
      return None

   def getOwnerDocument(self):
      """
      The Document object associated with this node, if any.
      """
        
    # DOM Methods    
    # -----------
    
   def hasChildNodes(self):
      """
      Returns true if the node has any children, false
      if it doesn't.
      """
      return len(self.getChildren())

   """
   create aliases for all above functions in the pythony way.
   """

   def _get_NodeName(self):
      return self.getNodeName()
      
   def _get_NodeValue(self):
      return self.getNodeValue()
      
   def _get_ParentNode(self):
      return self.getParentNode()

   def _get_Children(self):
      return self.getChildren()

   def _get_PreviousSibling(self,
                          type=type,
                          st=type(''),
                          getattr=getattr,
                          none=None):
      
      return self.getPreviousSibling(type,st,getattr,None)
      
   def _get_NextSibling(self, type=type, st=type('')):
      return self.getNextSibling()

   def _get_Attributes(self):
      return self.getAttributes()

   def _get_OwnerDocument(self):
      return self.getOwnerDocument()
    
   def _has_ChildNodes(self):
      return self.hasChildNodes()
      
         
class TextNode(Node):

   def __init__(self, str): self._value=str
      
   def getNodeType(self):
      return TEXT_NODE
      
   def getNodeName(self):
      return '#text'

   def getNodeValue(self):
      return self._value
   
   """
   create aliases for all above functions in the pythony way.
   """
   
   def _get_NodeType(self):
      return self.getNodeType()
      
   def _get_NodeName(self):
      return self.getNodeName()

   def _get_NodeValue(self):
      return self.getNodeValue()

class Element(Node):
   """
   Element interface
   """
    
   # Element Attributes
   # ------------------
    
   def getTagName(self):
      """The name of the element"""
      return self.__class__.__name__
    
   def getNodeName(self):
      """The name of this node, depending on its type"""
      return self.__class__.__name__
      
   def getNodeType(self):
      """A code representing the type of the node."""
      return ELEMENT_NODE

   def getNodeValue(self, type=type, st=type('')):
      r=[]
      for c in self.getChildren():
         if type(c) is not st:
            c=c.getNodeValue()
         r.append(c)
      return string.join(r,'')
    
   def getParentNode(self):
      """
      The parent of this node.  All nodes except Document
      DocumentFragment and Attr may have a parent
      """
      
   # Element Methods
   # ---------------
    
   _attributes=()

   def getAttribute(self, name): return getattr(self, name, None)
   def getAttributeNode(self, name):
      if hasattr(self, name):
         return Attr(name, getattr(self, name))

   def getAttributes(self):
      d={}
      for a in self._attributes:
         d[a]=getattr(self, a, '')
      return NamedNodeMap(d)

   def getAttribute(self, name):
      """Retrieves an attribute value by name."""
      return None

   def getAttributeNode(self, name):
      """ Retrieves an Attr node by name or None if
      there is no such attribute. """
      return None

   def getElementsByTagName(self, tagname):
      """
      Returns a NodeList of all the Elements with a given tag
      name in the order in which they would be encountered in a
      preorder traversal of the Document tree.  Parameter: tagname
      The name of the tag to match (* = all tags). Return Value: A new
      NodeList object containing all the matched Elements.
      """
      nodeList = []
      for child in self.getChildren():
         if (child.getNodeType()==ELEMENT_NODE and \
             child.getTagName()==tagname or tagname== '*'):
                
            nodeList.append(child)
               
         if hasattr(child, 'getElementsByTagName'):
            n1       = child.getElementsByTagName(tagname)
            nodeList = nodeList + n1._data
      return NodeList(nodeList)
   
   """
   create aliases for all above functions in the pythony way.
   """

   def _get_TagName(self):
      return self.getTagName()
      
   def _get_NodeName(self):
      return self.getNodeName()
      
   def _get_NodeType(self):
      return self.getNodeType()
      
   def _get_NodeValue(self, type=type, st=type('')):
      return self.getNodeValue(type,st)
      
   def _get_ParentNode(self):
      return self.getParentNode()
      
   def _get_Attribute(self, name):
      return self.getAttribute(name)
   
   def _get_AttributeNode(self, name):
      return self.getAttributeNode(name)
      
   def _get_Attributes(self):
      return self.getAttributes()
      
   def _get_Attribute(self, name):
      return self.getAttribute(name)
      
   def _get_AttributeNode(self, name):
      return self.getAttributeNode(name)
      
   def _get_ElementsByTagName(self, tagname):
      return self.getElementsByTagName(tagname)
      
         
class NodeList:
   """
   NodeList interface - Provides the abstraction of an ordered
   collection of nodes.
    
   Python extensions: can use sequence-style 'len', 'getitem', and
   'for..in' constructs.
   """
    
   def __init__(self,list=None):
      self._data = list or []
    
   def __getitem__(self, index, type=type, st=type('')):
      return self._data[index]

   def __getslice__(self, i, j):
      return self._data[i:j]
    
   def item(self, index):
      """
      Returns the index-th item in the collection
      """
      try:  return self._data[index]    
      except IndexError: return None
         
   def getLength(self):
      """
      The length of the NodeList
      """
      return len(self._data)
    
   __len__=getLength
   
   """
   create aliases for all above functions in the pythony way.
   """
   
   def _get_Length(self):
      return self.getLength()
 
class NamedNodeMap:
   """
   NamedNodeMap interface - Is used to represent collections
   of nodes that can be accessed by name.  NamedNodeMaps are not
   maintained in any particular order.
    
   Python extensions: can use sequence-style 'len', 'getitem', and
   'for..in' constructs, and mapping-style 'getitem'.
   """
    
   def __init__(self, data=None):
      if data is None:
         data = {}
      self._data = data

   def item(self, index):
      """
      Returns the index-th item in the map
      """
      try: return self._data.values()[index]
      except IndexError: return None
        
   def __getitem__(self, key):
      if type(key)==type(1):
         return self._data.values()[key]
      else:
         return self._data[key]
            
   def getLength(self):
      """
      The length of the NodeList
      """
      return len(self._data)
    
   __len__ = getLength
    
   def getNamedItem(self, name):
      """
      Retrieves a node specified by name. Parameters:
      name Name of a node to retrieve. Return Value A Node (of any
      type) with the specified name, or None if the specified name
      did not identify any node in the map.
      """
      if self._data.has_key(name): 
         return self._data[name]
      return None

   """
   create aliases for all above functions in the pythony way.
   """
   def _get_Length(self):
      return self.getLength()
      
   def _get_NamedItem(self, name):
      return self.getNamedItem(name)
      
class Attr(Node):
   """
   Attr interface - The Attr interface represents an attriubte in an
   Element object. Attr objects inherit the Node Interface
   """

   def __init__(self, name, value, specified=1):
      self.name = name
      self.value = value
      self.specified = specified
        
   def getNodeName(self):
      """
      The name of this node, depending on its type
      """
      return self.name

   def getName(self):
      """
      Returns the name of this attribute.
      """
      return self.name
    
   def getNodeValue(self):
      """
      The value of this node, depending on its type
      """
      return self.value

   def getNodeType(self):
      """
      A code representing the type of the node.
      """
      return ATTRIBUTE_NODE

   def getSpecified(self):
      """
      If this attribute was explicitly given a value in the
      original document, this is true; otherwise, it is false.
      """
      return self.specified
        
   """
   create aliases for all above functions in the pythony way.
   """
   
   def _get_NodeName(self):
      return self.getNodeName()

   def _get_Name(self):
      return self.getName()
    
   def _get_NodeValue(self):
      return self.getNodeValue()

   def _get_NodeType(self):
      return self.getNodeType()

   def _get_Specified(self):
      return self.getSpecified()
