# Copyright (c) 2004-2005 DoCoMo Euro-Labs GmbH (Munich, Germany). # http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com # # Copyright (c) 2001-2005 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 """base classes for narval memory elements :version: $Revision:$ :author: Logilab :copyright: 2001-2005 LOGILAB S.A. (Paris, FRANCE) 2004-2005 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: tags.py,v 1.4 2001/12/21 13:52:51 syt Exp $' from copy import copy, deepcopy from sets import Set from narval import AL_NS, TYPE_NS, NO_NS from narval.serialutils import yn_value, yn_rev_value class NSAttribute(object): """namespace attribute descriptor""" def __init__(self, namespace = NO_NS, default = None, loader = str, serializer = str): self.attrname = None self.namespace = namespace self.default = default self.serializer = serializer self.loader = loader self.key = None self.prefix = None def __repr__(self): return '<%s %s:%s>' % (self.__class__.__name__, self.namespace, self.attrname) def set_name(self, attrname): """should only be called by the meta class""" self.attrname = attrname self.key = (self.namespace, attrname) def set_prefix(self, prefix): """set the serialization prefix if this attribute is in a xml namespace """ self.prefix = prefix def as_xml(self, value, encoding='UTF-8'): """return the xml attribute definition for this attribute""" #value = self.__get__(obj, obj.__class__) value = self.serializer(value) if self.namespace is None: return '%s="%s"' % (self.attrname, value) else: return '%s:%s="%s"' % (self.prefix, self.attrname, value) def from_string(self, value): """deserialize the attribute value from a string""" return self.loader(value) def __get__(self, obj, objtype): if obj is None: return self # what should we do here ? if self.key is None: raise AttributeError('accessing unnamed attribute on %s' % obj) # return self._value ? try: return obj.getattr(self.key) except KeyError: return self.default def __set__(self, obj, value): obj.setattr(self.key, value) # self._value = value ? class MetaElement(type): """metaclass for narval elements, preprocessing classes according to the value of their __namespaces__ attribute and to their 'namespace attributes' (i.e. attributes handled by a NSAttribute descriptor) """ def __new__(mcs, name, bases, classdict): namespaces = classdict.get('__namespaces__', {}) ns_strings = Set() effective_ns = {} # this will hold the final __namespaces__ dict # Update final dict with each __namespaces__ definition for baseclass in bases: base_namespaces = getattr(baseclass, '__namespaces__', {}) effective_ns.update(base_namespaces) ns_strings |= getattr(baseclass, 'ns_strings', Set()) classdict['__namespaces__'] = effective_ns effective_ns.update(namespaces) # prepare descriptors for attrname, descr in classdict.items(): if isinstance(descr, NSAttribute): descr.set_name(attrname) if descr.namespace: try: descr.set_prefix(effective_ns[descr.namespace]) except KeyError: raise Exception('No prefix defined for namespace %s' % descr.namespace) # set the namespaces string used for serialization # FIXME: add only effectively used namespaces for namespace, prefix in effective_ns.items(): ns_strings.add('xmlns:%s="%s"' % (prefix, namespace)) classdict['ns_strings'] = ns_strings return type.__new__(mcs, name, bases, classdict) class NSAttributesElement(object): """base class for element supporting attributes in different namespaces notice this class or derived should always be the left most in the inheritance list """ __metaclass__ = MetaElement # xml element definition, REQUIRE OVERRIDING # (namespace, tagname) __xml_element__ = (None, None) # mapping of defined prefixes / namespaces # {prefix: namespace} __namespaces__ = {AL_NS : 'al', TYPE_NS : 'type', } # defined by the metaclass, but here it's ok either and please pylint :) ns_strings = Set() def __init__(self, **kwargs): """kwargs is a attrname/attrvalue dict /!\ raises a AttributeError if a non existing attrname is given """ super(NSAttributesElement, self).__init__() ## # this attribute should be defined before the "super" call self._ns_attrs = {} existing_attrs = [descr.attrname for descr in self.ns_attributes()] for attrname, attrvalue in kwargs.items(): if attrname not in existing_attrs: raise TypeError('Unknown attribute %r' % attrname) setattr(self, attrname, attrvalue) def ns_attributes(self): """return NSAttribute descriptors""" for attrname in dir(self.__class__): descr = getattr(self.__class__, attrname) if isinstance(descr, NSAttribute): yield descr def init_attrs(self, attrs): """init element's attributes according to the given dictionary. All values in the dictionary are given as unicode strings (actually as returned by the xml parser). :type attrs: dict( tuple(str, str) : unicode) :param attrs: dictionary of attributes to define """ for descr in self.ns_attributes(): key = descr.key try: value = attrs[key] except KeyError: continue self.setattr(key, descr.from_string(value)) def clone_attrs(self, attrs, deep=False): """init element's attributes according to the given dictionary of preprocessed attribute (i.e. no need to deserialize) :type attrs: dict( tuple(str, str) : unicode) :param attrs: dictionary of attributes to define """ self._ns_attrs = {} for attr_key, value in attrs.items(): if attr_key == (AL_NS, 'eid'): continue if deep: self.setattr(attr_key, deepcopy(value)) else: self.setattr(attr_key, value) def setattr(self, attr_key, value): """set an attribute to a particular name space :type attr_key: tuple(str, str) :param attr_key: a 2-uple with the the attribute's namespace as first element and the attribute's name as second element :return: the attribute's value """ self._ns_attrs[attr_key] = value def getattr(self, attr_key): """get an attribute from a particular name space :type attr_key: tuple(str, str) :param attr_key: a 2-uple with the the attribute's namespace as first element and the attribute's name as second element :return: the attribute's value """ return self._ns_attrs[attr_key] def as_xml(self, encoding='UTF-8', namespaces_def=True): """return the XML representation of the element. The default behavior handle any non container element. :type encoding: str :param encoding: the encoding to use in the returned string :rtype: str :return: XML string representing the element """ if namespaces_def: ns_defs = ' %s' % self.namespaces_as_xml() else: ns_defs = '' attrs = self.attributes_as_xml() ns, tag = self.__xml_element__ if not ns is None: tag = '%s:%s' % (self.__namespaces__[ns], tag) child_data = self.children_as_xml(encoding) if child_data: return '<%s%s %s>\n%s\n' % (tag, ns_defs, attrs, child_data, tag) return '<%s%s %s/>' % (tag, ns_defs, attrs) def namespaces_as_xml(self): """return the xml representation of namespaces/prefix definition. The value returned by this method is computed only once per class. :rtype: str :return: XML string representing the element's attributes """ return ' '.join(self.ns_strings) def attributes_as_xml(self, encoding='UTF-8'): """return the xml representation of element's attributes :rtype: str :return: XML string representing the element's attributes """ result = [] # sort attributes for test... attrs = [(descr.key, descr) for descr in self.ns_attributes()] attrs.sort() for key, descr in attrs: # catch key error to serialize only defined attributes, not # default values try: value = self.getattr(descr.key) except KeyError: continue result.append(descr.as_xml(value, encoding)) return ' '.join(result) def children_as_xml(self, encoding='UTF-8'): """return the XML representation of children in this element. :type encoding: str :param encoding: the encoding to use in the returned string :rtype: str :return: XML string representing the element """ return '' class DescriptionableMixin(object): """a mixin for elements supporting a description in multiple languages. WARNING: class using that mixin MUST add the "descriptions" field to the list of internal attributes :type descriptions: dict :ivar descriptions: dictionary of available descriptions, indexed by the language code (two letters) """ def __init__(self, cloned=None, **kwargs): super(DescriptionableMixin, self).__init__(**kwargs) # dictionary of description indexed by language if cloned is not None: self.descriptions = cloned.descriptions.copy() else: self.descriptions = {} def description_as_xml(self, encoding='UTF-8'): """return a xml string for descriptions :type encoding: str :param encoding: the encoding to use in the returned string :rtype: str :return: a XML snippet string containing all available descriptions """ result = [] for lang, descr in self.descriptions.items(): result.append('%s' % (lang.encode(), descr.encode(encoding))) return '\n'.join(result) class ALElement(NSAttributesElement): """base class for all elements used in narval memory handle attributes set dynamically by narval :type eid: int :ivar eid: unique identifier of the element :type persist: bool :ivar persist: indicates whether the element should be removed from memory when it's not anymore referenced :type outdated: bool :ivar outdated: indicates whether the element has been outdated and should not be further considered :type input_id: str or None :ivar input_id: identifier of the input which has introduced this element into a step :type output_id: str or None :ivar output_id: identifier of the output which has produced this element in a step :type from_plan: int or None :ivar from_plan: eid of the plan which has produced this element :type from_step: int or None :ivar from_step: eid of the plan's step which has produced this element :type timestamp: float or None :ivar timestamp: time stamp set when the element is added to the memory """ # implemented interfaces __implements__ = () eid = NSAttribute(AL_NS, None, int, str) persist = NSAttribute(AL_NS, None, yn_value, yn_rev_value) outdated = NSAttribute(AL_NS, None, yn_value, yn_rev_value) input_id = NSAttribute(AL_NS, None, str, str) output_id = NSAttribute(AL_NS, None, str, str) from_plan = NSAttribute(AL_NS, None, int, str) from_step = NSAttribute(AL_NS, None, str, str) timestamp = NSAttribute(AL_NS, None, float, str) name = NSAttribute(TYPE_NS, None, str, str) # handler used to handle child elements when deserializing # should implements necessary methods of IXMLHandler __child_handler__ = None def __repr__(self): return '<%s eid=%s at %s>' % (self.__class__.__name__, self.eid, hex(abs(id(self)))) def reset_runtime_attributes(self): """reset all runtime attributes (ie attributes in the AL_NS namespace) """ for descr in self.ns_attributes(): if descr.namespace == AL_NS: self.setattr(descr.key, None) def clone(self): """return a clone of this element""" elmt = copy(self) elmt._ns_attrs = self._ns_attrs.copy() elmt.eid = None return elmt def display_type(self): """return a string used to display this element's type""" return self.__class__.__xml_element__[1] def display_group(self): """return a string used to display this element's group""" return getattr(self, 'group', 'element') def display_name(self): """return a string used to display this element's name""" return getattr(self, 'name', '') def display_data(self): return (self.eid, self.display_type(), self.display_group(), self.display_name())