# Copyright (c) 2004 DoCoMo Euro-Labs GmbH (Munich, Germany).
# http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
#
# Copyright (c) 2001-2004 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""XML handler interface plus some generic XML handlers


:version: $Revision:$  
:author: Logilab

:copyright:
  2001-2004 LOGILAB S.A. (Paris, FRANCE)
  
  2004 DoCoMo Euro-Labs GmbH (Munich, Germany)

:contact:
  http://www.logilab.fr/ -- mailto:contact@logilab.fr
  
  http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
"""

__revision__ = "$Id: public.py $"
__docformat__ = "restructuredtext en"

from twisted.python.components import Interface

# xml handler interface #######################################################

class IXMLHandler(Interface):
    """interface for XML handlers registerable to the narval registry

    :type MATCH_ELEMENT: tuple
    :cvar MATCH_ELEMENT:
      defines the element matched by the handler with a tuple (<uri>, <tagname>)

    :type ELEMENT_CLASS: classobj
    :cvar ELEMENT_CLASS: class of objects generated by this handler
    """
    
    def __init__(self, elmt, context, locator):
        """initialize the handler with context (prefix mapping) and
        document locator

        :type locator: xml.sax.saxlib.Locator
        :param locator:
          SAX locator object, allowing to locate parsing error in the original
          XML document

        :type context: dict
        :param context:
          the current namespaces / prefix mapping in the document when the
          node is introduced
        """
        self.elmt = elmt
    
    def start(self, attrs):
        """SAX like callback: start the base node for this handler

        :type attrs: dict
        :param attrs:
          the node's attribute values, indexed by attribute's name as a tuple
          (uri, name)
        """
    def end(self):
        """SAX like callback: close the base node for this handler"""
        
    def start_prefix_mapping(self, prefix, uri):
        """SAX callback: start a new namespace prefix declaration

        :type prefix: unicode
        :param prefix: the prefix'string

        :type uri: unicode
        :param uri: the uri'string
        """
        
    def end_prefix_mapping(self, prefix):
        """SAX callback: end namespace prefix declaration scope

        :type prefix: unicode
        :param prefix: the prefix'string
        """

    def start_element(self, name, attrs):
        """SAX callback: start a new xml node

        By default set attributes according to the xml node attributes and to
        the self.__attributes__ dictionary
        
        :type name: tuple
        :param name: the tag name as a tuple (uri, name)
        
        :type attrs: dict
        :param attrs:
          the node's attribute values, indexed by attribute's name as a tuple
          (uri, name)
        """
                
    def end_element(self, name):
        """SAX callback: close a xml node

        :type name: tuple
        :param name: the tag name as a tuple (uri, name)
        """
            
    def characters(self, content):
        """SAX callback: get some (non empty) string

        :type content: unicode
        :param content: the non empty string to hold
        """

class BaseXMLHandler(object):
    """base class for registry XML handlers
    
    methods **requiring** overloading are start_element and characters, an
    empty default implementation is provided for other callbacks.

    :type elmt: narval.public.ALElement
    :ivar elmt: the generated element
    """
    __implements__ = (IXMLHandler,)
    
    def __init__(self, elmt, context, locator):
        self.elmt = elmt
        self._ns_context = context
        self._locator = locator
        
    def start(self, attrs):
        """SAX like callback: start the node for which the handler has been
        registered
        """
    def end(self):
        """SAX like callback: close the node for which the handler has been
        registered
        """
        
    def start_prefix_mapping(self, prefix, uri):
        """SAX callback: start namespace prefix declaration

        :type prefix: unicode
        :param prefix: the prefix'string

        :type uri: unicode
        :param uri: the uri'string
        """
    def end_prefix_mapping(self, prefix):
        """SAX callback: end namespace prefix declaration scope

        :type prefix: unicode
        :param prefix: the prefix'string
        """
        
        
    def start_element(self, name, attr):
        """SAX callback: open a xml node

        :type name: tuple
        :param name: the tag name as a tuple (uri, name)
        """
        
    def end_element(self, name):
        """SAX callback: close a xml node

        :type name: tuple
        :param name: the tag name as a tuple (uri, name)
        """

    def characters(self, content):
        """SAX callback: read characters in a node

        :type content: unicode
        :param content: the non empty string to hold
        """
        
# generic handlers ############################################################

class DescriptionHandler:
    """a partial handler dedicated to multilanguages descriptions

    :type descriptionable: object
    :ivar descriptionable: the object holding the description
          
    :type lang: str
    :ivar lang: the current description's language code
    """
    
    def __init__(self, descriptionable, lang):
        """
        :type descriptionable: object
        :param descriptionable: the object holding the description
    
        :type lang: str
        :param lang: the current description's language code
        """
        self.descriptionable = descriptionable
        self.lang = lang
        
    def characters(self, content):
        """add string to the current language's description

        :type content: unicode
        :param content: the non empty string to hold
        """
        try:
            self.descriptionable.descriptions[self.lang] += content
        except KeyError:
            self.descriptionable.descriptions[self.lang] = content


def data_handler(data_attr):
    """return a parametrized data handler (an handler used to accumulate
    textual data to a specific attribute
    """
    class DataXMLHandler(BaseXMLHandler):
        """XML handler for elements with only text as children"""
        
        def characters(self, string, data_attr=data_attr):
            """SAX callback: get some (non empty) string
            
            :type content: unicode
            :param content: the non empty string to hold
            """
            value = getattr(self.elmt, data_attr, '')
            setattr(self.elmt, data_attr, '%s%s' % (value, string))
            
    return DataXMLHandler


class DictHandler(BaseXMLHandler):
    """SAX handler with a usual reader interface to parse file / stream
    and return an Entity instance
    """

    def start_element(self, name, attrs):
        self._current_attr = name[1].encode()
            
    def end_element(self, name):
        self._current_attr = None
        
    def characters(self, value):
        value = value.strip()
        if not value or self._current_attr is None:
            return
        try:
            self.elmt._dict[self._current_attr] += value
        except KeyError:
            self.elmt._dict[self._current_attr] = value

            
class ListHandler(BaseXMLHandler):
    """SAX handler with a usual reader interface to parse file / stream
    and return an Entity instance
    """

    def start(self, attrs):
        self._in_element = False
##        try:
        self.accu = []#getattr(self.elmt, self.elmt.list_attr)
##        except AttributeError:
        setattr(self.elmt, self.elmt.list_attr, self.accu)
##             self.accu = getattr(self.elmt, self.elmt.list_attr)
        
    def start_element(self, name, attrs):
        self._in_element = True
        self.accu.append('')
        
    def end_element(self, name):
        self._in_element = False
        
    def characters(self, value):
        if not self._in_element:
            return
        self.accu[-1] += value


# not used anymore, commented out for now (OneLevelHandler is much more flexible)
## class AttrDictHandler(BaseXMLHandler):
##     """SAX handler with a usual reader interface to parse file / stream
##     and return an Entity instance
##     """

##     def start(self, attrs):
##         self._in_element = None
##         self._attr = self.elmt.dict_attr
##         self._accu = {}
##         setattr(self.elmt, self._attr, self._accu)
        
##     def start_element(self, name, attrs):
##         self._in_element = attrs[(NO_NS, self._attr)]
##         self._accu[self._in_element] = ''
        
##     def end_element(self, name):
##         self._in_element = None
        
##     def characters(self, value):
##         value = value.strip()
##         if value and self._in_element:
##             self._accu[self._in_element] += value


class OneLevelHandler(BaseXMLHandler):
    """SAX handler which expected a subelement_classes attribute on self.elmt,
    containing a mapping giving which class should be instantiated to handle
    encountered element. Each created instance is appended to an attribute
    on self.elmt, designated by the special subelements_attribute attribute
    (still on self.elmt).

    Classes are instanciated with the attrs mapping as single argument.
    """

    def start(self, attrs):
        self._in_element = None
        self._map = self.elmt.subelements_classes
        self._accu = []
        setattr(self.elmt, self.elmt.subelements_attribute, self._accu)
        
    def start_element(self, name, attrs):
        instance = self._map[name](attrs)
        self._accu.append(instance)
        self._in_element = True
        
    def end_element(self, name):
        self._in_element = None
        
    def characters(self, value):
        if self._in_element is None:
            return
        if value.strip():
            self._accu[-1].characters(value)
