##############################################################################
#
# Copyright (c) 2001 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
"""

string_types = (str, unicode)

__metaclass__ = type

# 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, sts=string_types):
        """
        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) in sts:
                n=TextNode(n)
            r.append(n.__of__(self))

        return NodeList(r)

    def getFirstChild(self, type=type, sts=string_types):
        """
        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) in sts:
            n = TextNode(n)

        return n.__of__(self)

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


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):
        """
        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) in string_types:
                n=TextNode(n)
            n._DOMIndex=index
            return n.__of__(self)


    def getNextSibling(self):
        """
        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) in string_types:
                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()

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):
        """
        The node immediately preceding this node.  If
        there is no such node, this returns None.
        """

    def getNextSibling(self):
        """
        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())

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

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):
        r=[]
        for c in self.getChildren():
            if type(c) not in string_types:
                c=c.getNodeValue()
            r.append(c)
        return ''.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 not hasattr(child, 'getNodeType'): continue
            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)

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, sts=string_types):
        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
        """
        return self._data.get(index, None)

    def getLength(self):
        """The length of the NodeList
        """
        return len(self._data)

    __len__ = 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
        """
        return self._data.values().get(index, None)
        

    def __getitem__(self, key):
        if isinstance(key, int):
            return self.item(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.
        """
        return self._data.get(name, None)

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
