# 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 (, ) :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)