summaryrefslogtreecommitdiff
path: root/sleekxmpp/xmlstream/stanzabase.py
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp/xmlstream/stanzabase.py')
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py1654
1 files changed, 0 insertions, 1654 deletions
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
deleted file mode 100644
index c2e0f718..00000000
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ /dev/null
@@ -1,1654 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- sleekxmpp.xmlstream.stanzabase
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- module implements a wrapper layer for XML objects
- that allows them to be treated like dictionaries.
-
- Part of SleekXMPP: The Sleek XMPP Library
-
- :copyright: (c) 2011 Nathanael C. Fritz
- :license: MIT, see LICENSE for more details
-"""
-
-from __future__ import with_statement, unicode_literals
-
-import copy
-import logging
-import weakref
-from xml.etree import cElementTree as ET
-
-from sleekxmpp.util import safedict
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.xmlstream.tostring import tostring
-from sleekxmpp.thirdparty import OrderedDict
-
-
-log = logging.getLogger(__name__)
-
-
-# Used to check if an argument is an XML object.
-XML_TYPE = type(ET.Element('xml'))
-
-
-XML_NS = 'http://www.w3.org/XML/1998/namespace'
-
-
-def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
- """
- Associate a stanza object as a plugin for another stanza.
-
- >>> from sleekxmpp.xmlstream import register_stanza_plugin
- >>> register_stanza_plugin(Iq, CustomStanza)
-
- Plugin stanzas marked as iterable will be included in the list of
- substanzas for the parent, using ``parent['substanzas']``. If the
- attribute ``plugin_multi_attrib`` was defined for the plugin, then
- the substanza set can be filtered to only instances of the plugin
- class. For example, given a plugin class ``Foo`` with
- ``plugin_multi_attrib = 'foos'`` then::
-
- parent['foos']
-
- would return a collection of all ``Foo`` substanzas.
-
- :param class stanza: The class of the parent stanza.
- :param class plugin: The class of the plugin stanza.
- :param bool iterable: Indicates if the plugin stanza should be
- included in the parent stanza's iterable
- ``'substanzas'`` interface results.
- :param bool overrides: Indicates if the plugin should be allowed
- to override the interface handlers for
- the parent stanza, based on the plugin's
- ``overrides`` field.
-
- .. versionadded:: 1.0-Beta1
- Made ``register_stanza_plugin`` the default name. The prior
- ``registerStanzaPlugin`` function name remains as an alias.
- """
- tag = "{%s}%s" % (plugin.namespace, plugin.name)
-
- # Prevent weird memory reference gotchas by ensuring
- # that the parent stanza class has its own set of
- # plugin info maps and is not using the mappings from
- # an ancestor class (like ElementBase).
- plugin_info = ('plugin_attrib_map', 'plugin_tag_map',
- 'plugin_iterables', 'plugin_overrides')
- for attr in plugin_info:
- info = getattr(stanza, attr)
- setattr(stanza, attr, info.copy())
-
- stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
- stanza.plugin_tag_map[tag] = plugin
-
- if iterable:
- stanza.plugin_iterables.add(plugin)
- if plugin.plugin_multi_attrib:
- multiplugin = multifactory(plugin, plugin.plugin_multi_attrib)
- register_stanza_plugin(stanza, multiplugin)
- if overrides:
- for interface in plugin.overrides:
- stanza.plugin_overrides[interface] = plugin.plugin_attrib
-
-
-# To maintain backwards compatibility for now, preserve the camel case name.
-registerStanzaPlugin = register_stanza_plugin
-
-
-def multifactory(stanza, plugin_attrib):
- """
- Returns a ElementBase class for handling reoccuring child stanzas
- """
-
- def plugin_filter(self):
- return lambda x: isinstance(x, self._multistanza)
-
- def plugin_lang_filter(self, lang):
- return lambda x: isinstance(x, self._multistanza) and \
- x['lang'] == lang
-
- class Multi(ElementBase):
- """
- Template class for multifactory
- """
- def setup(self, xml=None):
- self.xml = ET.Element('')
-
- def get_multi(self, lang=None):
- parent = self.parent()
- if not lang or lang == '*':
- res = filter(plugin_filter(self), parent)
- else:
- res = filter(plugin_filter(self, lang), parent)
- return list(res)
-
- def set_multi(self, val, lang=None):
- parent = self.parent()
- del_multi = getattr(self, 'del_%s' % plugin_attrib)
- del_multi(lang)
- for sub in val:
- parent.append(sub)
-
- def del_multi(self, lang=None):
- parent = self.parent()
- if not lang or lang == '*':
- res = filter(plugin_filter(self), parent)
- else:
- res = filter(plugin_filter(self, lang), parent)
- res = list(res)
- if not res:
- del parent.plugins[(plugin_attrib, None)]
- parent.loaded_plugins.remove(plugin_attrib)
- try:
- parent.xml.remove(self.xml)
- except ValueError:
- pass
- else:
- for stanza in list(res):
- parent.iterables.remove(stanza)
- parent.xml.remove(stanza.xml)
-
- Multi.is_extension = True
- Multi.plugin_attrib = plugin_attrib
- Multi._multistanza = stanza
- Multi.interfaces = set([plugin_attrib])
- Multi.lang_interfaces = set([plugin_attrib])
- setattr(Multi, "get_%s" % plugin_attrib, get_multi)
- setattr(Multi, "set_%s" % plugin_attrib, set_multi)
- setattr(Multi, "del_%s" % plugin_attrib, del_multi)
- return Multi
-
-
-def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
- """Apply the stanza's namespace to elements in an XPath expression.
-
- :param string xpath: The XPath expression to fix with namespaces.
- :param bool split: Indicates if the fixed XPath should be left as a
- list of element names with namespaces. Defaults to
- False, which returns a flat string path.
- :param bool propagate_ns: Overrides propagating parent element
- namespaces to child elements. Useful if
- you wish to simply split an XPath that has
- non-specified namespaces, and child and
- parent namespaces are known not to always
- match. Defaults to True.
- """
- fixed = []
- # Split the XPath into a series of blocks, where a block
- # is started by an element with a namespace.
- ns_blocks = xpath.split('{')
- for ns_block in ns_blocks:
- if '}' in ns_block:
- # Apply the found namespace to following elements
- # that do not have namespaces.
- namespace = ns_block.split('}')[0]
- elements = ns_block.split('}')[1].split('/')
- else:
- # Apply the stanza's namespace to the following
- # elements since no namespace was provided.
- namespace = default_ns
- elements = ns_block.split('/')
-
- for element in elements:
- if element:
- # Skip empty entry artifacts from splitting.
- if propagate_ns and element[0] != '*':
- tag = '{%s}%s' % (namespace, element)
- else:
- tag = element
- fixed.append(tag)
- if split:
- return fixed
- return '/'.join(fixed)
-
-
-class ElementBase(object):
-
- """
- The core of SleekXMPP's stanza XML manipulation and handling is provided
- by ElementBase. ElementBase wraps XML cElementTree objects and enables
- access to the XML contents through dictionary syntax, similar in style
- to the Ruby XMPP library Blather's stanza implementation.
-
- Stanzas are defined by their name, namespace, and interfaces. For
- example, a simplistic Message stanza could be defined as::
-
- >>> class Message(ElementBase):
- ... name = "message"
- ... namespace = "jabber:client"
- ... interfaces = set(('to', 'from', 'type', 'body'))
- ... sub_interfaces = set(('body',))
-
- The resulting Message stanza's contents may be accessed as so::
-
- >>> message['to'] = "user@example.com"
- >>> message['body'] = "Hi!"
- >>> message['body']
- "Hi!"
- >>> del message['body']
- >>> message['body']
- ""
-
- The interface values map to either custom access methods, stanza
- XML attributes, or (if the interface is also in sub_interfaces) the
- text contents of a stanza's subelement.
-
- Custom access methods may be created by adding methods of the
- form "getInterface", "setInterface", or "delInterface", where
- "Interface" is the titlecase version of the interface name.
-
- Stanzas may be extended through the use of plugins. A plugin
- is simply a stanza that has a plugin_attrib value. For example::
-
- >>> class MessagePlugin(ElementBase):
- ... name = "custom_plugin"
- ... namespace = "custom"
- ... interfaces = set(('useful_thing', 'custom'))
- ... plugin_attrib = "custom"
-
- The plugin stanza class must be associated with its intended
- container stanza by using register_stanza_plugin as so::
-
- >>> register_stanza_plugin(Message, MessagePlugin)
-
- The plugin may then be accessed as if it were built-in to the parent
- stanza::
-
- >>> message['custom']['useful_thing'] = 'foo'
-
- If a plugin provides an interface that is the same as the plugin's
- plugin_attrib value, then the plugin's interface may be assigned
- directly from the parent stanza, as shown below, but retrieving
- information will require all interfaces to be used, as so::
-
- >>> # Same as using message['custom']['custom']
- >>> message['custom'] = 'bar'
- >>> # Must use all interfaces
- >>> message['custom']['custom']
- 'bar'
-
- If the plugin sets :attr:`is_extension` to ``True``, then both setting
- and getting an interface value that is the same as the plugin's
- plugin_attrib value will work, as so::
-
- >>> message['custom'] = 'bar' # Using is_extension=True
- >>> message['custom']
- 'bar'
-
-
- :param xml: Initialize the stanza object with an existing XML object.
- :param parent: Optionally specify a parent stanza object will
- contain this substanza.
- """
-
- #: The XML tag name of the element, not including any namespace
- #: prefixes. For example, an :class:`ElementBase` object for
- #: ``<message />`` would use ``name = 'message'``.
- name = 'stanza'
-
- #: The XML namespace for the element. Given ``<foo xmlns="bar" />``,
- #: then ``namespace = "bar"`` should be used. The default namespace
- #: is ``jabber:client`` since this is being used in an XMPP library.
- namespace = 'jabber:client'
-
- #: For :class:`ElementBase` subclasses which are intended to be used
- #: as plugins, the ``plugin_attrib`` value defines the plugin name.
- #: Plugins may be accessed by using the ``plugin_attrib`` value as
- #: the interface. An example using ``plugin_attrib = 'foo'``::
- #:
- #: register_stanza_plugin(Message, FooPlugin)
- #: msg = Message()
- #: msg['foo']['an_interface_from_the_foo_plugin']
- plugin_attrib = 'plugin'
-
- #: For :class:`ElementBase` subclasses that are intended to be an
- #: iterable group of items, the ``plugin_multi_attrib`` value defines
- #: an interface for the parent stanza which returns the entire group
- #: of matching substanzas. So the following are equivalent::
- #:
- #: # Given stanza class Foo, with plugin_multi_attrib = 'foos'
- #: parent['foos']
- #: filter(isinstance(item, Foo), parent['substanzas'])
- plugin_multi_attrib = ''
-
- #: The set of keys that the stanza provides for accessing and
- #: manipulating the underlying XML object. This set may be augmented
- #: with the :attr:`plugin_attrib` value of any registered
- #: stanza plugins.
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
-
- #: A subset of :attr:`interfaces` which maps interfaces to direct
- #: subelements of the underlying XML object. Using this set, the text
- #: of these subelements may be set, retrieved, or removed without
- #: needing to define custom methods.
- sub_interfaces = set()
-
- #: A subset of :attr:`interfaces` which maps the presence of
- #: subelements to boolean values. Using this set allows for quickly
- #: checking for the existence of empty subelements like ``<required />``.
- #:
- #: .. versionadded:: 1.1
- bool_interfaces = set()
-
- #: .. versionadded:: 1.1.2
- lang_interfaces = set()
-
- #: In some cases you may wish to override the behaviour of one of the
- #: parent stanza's interfaces. The ``overrides`` list specifies the
- #: interface name and access method to be overridden. For example,
- #: to override setting the parent's ``'condition'`` interface you
- #: would use::
- #:
- #: overrides = ['set_condition']
- #:
- #: Getting and deleting the ``'condition'`` interface would not
- #: be affected.
- #:
- #: .. versionadded:: 1.0-Beta5
- overrides = []
-
- #: If you need to add a new interface to an existing stanza, you
- #: can create a plugin and set ``is_extension = True``. Be sure
- #: to set the :attr:`plugin_attrib` value to the desired interface
- #: name, and that it is the only interface listed in
- #: :attr:`interfaces`. Requests for the new interface from the
- #: parent stanza will be passed to the plugin directly.
- #:
- #: .. versionadded:: 1.0-Beta5
- is_extension = False
-
- #: A map of interface operations to the overriding functions.
- #: For example, after overriding the ``set`` operation for
- #: the interface ``body``, :attr:`plugin_overrides` would be::
- #:
- #: {'set_body': <some function>}
- #:
- #: .. versionadded: 1.0-Beta5
- plugin_overrides = {}
-
- #: A mapping of the :attr:`plugin_attrib` values of registered
- #: plugins to their respective classes.
- plugin_attrib_map = {}
-
- #: A mapping of root element tag names (in ``'{namespace}elementname'``
- #: format) to the plugin classes responsible for them.
- plugin_tag_map = {}
-
- #: The set of stanza classes that can be iterated over using
- #: the 'substanzas' interface. Classes are added to this set
- #: when registering a plugin with ``iterable=True``::
- #:
- #: register_stanza_plugin(DiscoInfo, DiscoItem, iterable=True)
- #:
- #: .. versionadded:: 1.0-Beta5
- plugin_iterables = set()
-
- #: A deprecated version of :attr:`plugin_iterables` that remains
- #: for backward compatibility. It required a parent stanza to
- #: know beforehand what stanza classes would be iterable::
- #:
- #: class DiscoItem(ElementBase):
- #: ...
- #:
- #: class DiscoInfo(ElementBase):
- #: subitem = (DiscoItem, )
- #: ...
- #:
- #: .. deprecated:: 1.0-Beta5
- subitem = set()
-
- #: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
- xml_ns = XML_NS
-
- def __init__(self, xml=None, parent=None):
- self._index = 0
-
- #: The underlying XML object for the stanza. It is a standard
- #: :class:`xml.etree.cElementTree` object.
- self.xml = xml
-
- #: An ordered dictionary of plugin stanzas, mapped by their
- #: :attr:`plugin_attrib` value.
- self.plugins = OrderedDict()
- self.loaded_plugins = set()
-
- #: A list of child stanzas whose class is included in
- #: :attr:`plugin_iterables`.
- self.iterables = []
-
- #: The name of the tag for the stanza's root element. It is the
- #: same as calling :meth:`tag_name()` and is formatted as
- #: ``'{namespace}elementname'``.
- self.tag = self.tag_name()
-
- #: A :class:`weakref.weakref` to the parent stanza, if there is one.
- #: If not, then :attr:`parent` is ``None``.
- self.parent = None
- if parent is not None:
- if not isinstance(parent, weakref.ReferenceType):
- self.parent = weakref.ref(parent)
- else:
- self.parent = parent
-
- if self.subitem is not None:
- for sub in self.subitem:
- self.plugin_iterables.add(sub)
-
- if self.setup(xml):
- # If we generated our own XML, then everything is ready.
- return
-
- # Initialize values using provided XML
- for child in self.xml:
- if child.tag in self.plugin_tag_map:
- plugin_class = self.plugin_tag_map[child.tag]
- self.init_plugin(plugin_class.plugin_attrib,
- existing_xml=child,
- reuse=False)
-
- def setup(self, xml=None):
- """Initialize the stanza's XML contents.
-
- Will return ``True`` if XML was generated according to the stanza's
- definition instead of building a stanza object from an existing
- XML object.
-
- :param xml: An existing XML object to use for the stanza's content
- instead of generating new XML.
- """
- if self.xml is None:
- self.xml = xml
-
- last_xml = self.xml
- if self.xml is None:
- # Generate XML from the stanza definition
- for ename in self.name.split('/'):
- new = ET.Element("{%s}%s" % (self.namespace, ename))
- if self.xml is None:
- self.xml = new
- else:
- last_xml.append(new)
- last_xml = new
- if self.parent is not None:
- self.parent().xml.append(self.xml)
-
- # We had to generate XML
- return True
- else:
- # We did not generate XML
- return False
-
- def enable(self, attrib, lang=None):
- """Enable and initialize a stanza plugin.
-
- Alias for :meth:`init_plugin`.
-
- :param string attrib: The :attr:`plugin_attrib` value of the
- plugin to enable.
- """
- return self.init_plugin(attrib, lang)
-
- def _get_plugin(self, name, lang=None, check=False):
- if lang is None:
- lang = self.get_lang()
-
- if name not in self.plugin_attrib_map:
- return None
-
- plugin_class = self.plugin_attrib_map[name]
-
- if plugin_class.is_extension:
- if (name, None) in self.plugins:
- return self.plugins[(name, None)]
- else:
- return None if check else self.init_plugin(name, lang)
- else:
- if (name, lang) in self.plugins:
- return self.plugins[(name, lang)]
- else:
- return None if check else self.init_plugin(name, lang)
-
- def init_plugin(self, attrib, lang=None, existing_xml=None, reuse=True):
- """Enable and initialize a stanza plugin.
-
- :param string attrib: The :attr:`plugin_attrib` value of the
- plugin to enable.
- """
- default_lang = self.get_lang()
- if not lang:
- lang = default_lang
-
- plugin_class = self.plugin_attrib_map[attrib]
-
- if plugin_class.is_extension and (attrib, None) in self.plugins:
- return self.plugins[(attrib, None)]
- if reuse and (attrib, lang) in self.plugins:
- return self.plugins[(attrib, lang)]
-
- plugin = plugin_class(parent=self, xml=existing_xml)
-
- if plugin.is_extension:
- self.plugins[(attrib, None)] = plugin
- else:
- if lang != default_lang:
- plugin['lang'] = lang
- self.plugins[(attrib, lang)] = plugin
-
- if plugin_class in self.plugin_iterables:
- self.iterables.append(plugin)
- if plugin_class.plugin_multi_attrib:
- self.init_plugin(plugin_class.plugin_multi_attrib)
-
- self.loaded_plugins.add(attrib)
-
- return plugin
-
- def _get_stanza_values(self):
- """Return A JSON/dictionary version of the XML content
- exposed through the stanza's interfaces::
-
- >>> msg = Message()
- >>> msg.values
- {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
- 'to': , 'type': 'normal', 'id': '', 'subject': ''}
-
- Likewise, assigning to :attr:`values` will change the XML
- content::
-
- >>> msg = Message()
- >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
- >>> msg
- '<message to="user@example.com"><body>Hi!</body></message>'
-
- .. versionadded:: 1.0-Beta1
- """
- values = OrderedDict()
- values['lang'] = self['lang']
- for interface in self.interfaces:
- if isinstance(self[interface], JID):
- values[interface] = self[interface].jid
- else:
- values[interface] = self[interface]
- if interface in self.lang_interfaces:
- values['%s|*' % interface] = self['%s|*' % interface]
- for plugin, stanza in self.plugins.items():
- lang = stanza['lang']
- if lang:
- values['%s|%s' % (plugin[0], lang)] = stanza.values
- else:
- values[plugin[0]] = stanza.values
- if self.iterables:
- iterables = []
- for stanza in self.iterables:
- iterables.append(stanza.values)
- iterables[-1]['__childtag__'] = stanza.tag
- values['substanzas'] = iterables
- return values
-
- def _set_stanza_values(self, values):
- """Set multiple stanza interface values using a dictionary.
-
- Stanza plugin values may be set using nested dictionaries.
-
- :param values: A dictionary mapping stanza interface with values.
- Plugin interfaces may accept a nested dictionary that
- will be used recursively.
-
- .. versionadded:: 1.0-Beta1
- """
- iterable_interfaces = [p.plugin_attrib for \
- p in self.plugin_iterables]
-
- if 'lang' in values:
- self['lang'] = values['lang']
-
- if 'substanzas' in values:
- # Remove existing substanzas
- for stanza in self.iterables:
- try:
- self.xml.remove(stanza.xml)
- except ValueError:
- pass
- self.iterables = []
-
- # Add new substanzas
- for subdict in values['substanzas']:
- if '__childtag__' in subdict:
- for subclass in self.plugin_iterables:
- child_tag = "{%s}%s" % (subclass.namespace,
- subclass.name)
- if subdict['__childtag__'] == child_tag:
- sub = subclass(parent=self)
- sub.values = subdict
- self.iterables.append(sub)
-
- for interface, value in values.items():
- full_interface = interface
- interface_lang = ('%s|' % interface).split('|')
- interface = interface_lang[0]
- lang = interface_lang[1] or self.get_lang()
-
- if interface == 'lang':
- continue
- elif interface == 'substanzas':
- continue
- elif interface in self.interfaces:
- self[full_interface] = value
- elif interface in self.plugin_attrib_map:
- if interface not in iterable_interfaces:
- plugin = self._get_plugin(interface, lang)
- if plugin:
- plugin.values = value
- return self
-
- def __getitem__(self, attrib):
- """Return the value of a stanza interface using dict-like syntax.
-
- Example::
-
- >>> msg['body']
- 'Message contents'
-
- Stanza interfaces are typically mapped directly to the underlying XML
- object, but can be overridden by the presence of a ``get_attrib``
- method (or ``get_foo`` where the interface is named ``'foo'``, etc).
-
- The search order for interface value retrieval for an interface
- named ``'foo'`` is:
-
- 1. The list of substanzas (``'substanzas'``)
- 2. The result of calling the ``get_foo`` override handler.
- 3. The result of calling ``get_foo``.
- 4. The result of calling ``getFoo``.
- 5. The contents of the ``foo`` subelement, if ``foo`` is listed
- in :attr:`sub_interfaces`.
- 6. True or False depending on the existence of a ``foo``
- subelement and ``foo`` is in :attr:`bool_interfaces`.
- 7. The value of the ``foo`` attribute of the XML object.
- 8. The plugin named ``'foo'``
- 9. An empty string.
-
- :param string attrib: The name of the requested stanza interface.
- """
- full_attrib = attrib
- attrib_lang = ('%s|' % attrib).split('|')
- attrib = attrib_lang[0]
- lang = attrib_lang[1] or None
-
- kwargs = {}
- if lang and attrib in self.lang_interfaces:
- kwargs['lang'] = lang
-
- kwargs = safedict(kwargs)
-
- if attrib == 'substanzas':
- return self.iterables
- elif attrib in self.interfaces or attrib == 'lang':
- get_method = "get_%s" % attrib.lower()
- get_method2 = "get%s" % attrib.title()
-
- if self.plugin_overrides:
- name = self.plugin_overrides.get(get_method, None)
- if name:
- plugin = self._get_plugin(name, lang)
- if plugin:
- handler = getattr(plugin, get_method, None)
- if handler:
- return handler(**kwargs)
-
- if hasattr(self, get_method):
- return getattr(self, get_method)(**kwargs)
- elif hasattr(self, get_method2):
- return getattr(self, get_method2)(**kwargs)
- else:
- if attrib in self.sub_interfaces:
- return self._get_sub_text(attrib, lang=lang)
- elif attrib in self.bool_interfaces:
- elem = self.xml.find('{%s}%s' % (self.namespace, attrib))
- return elem is not None
- else:
- return self._get_attr(attrib)
- elif attrib in self.plugin_attrib_map:
- plugin = self._get_plugin(attrib, lang)
- if plugin and plugin.is_extension:
- return plugin[full_attrib]
- return plugin
- else:
- return ''
-
- def __setitem__(self, attrib, value):
- """Set the value of a stanza interface using dictionary-like syntax.
-
- Example::
-
- >>> msg['body'] = "Hi!"
- >>> msg['body']
- 'Hi!'
-
- Stanza interfaces are typically mapped directly to the underlying XML
- object, but can be overridden by the presence of a ``set_attrib``
- method (or ``set_foo`` where the interface is named ``'foo'``, etc).
-
- The effect of interface value assignment for an interface
- named ``'foo'`` will be one of:
-
- 1. Delete the interface's contents if the value is None.
- 2. Call the ``set_foo`` override handler, if it exists.
- 3. Call ``set_foo``, if it exists.
- 4. Call ``setFoo``, if it exists.
- 5. Set the text of a ``foo`` element, if ``'foo'`` is
- in :attr:`sub_interfaces`.
- 6. Add or remove an empty subelement ``foo``
- if ``foo`` is in :attr:`bool_interfaces`.
- 7. Set the value of a top level XML attribute named ``foo``.
- 8. Attempt to pass the value to a plugin named ``'foo'`` using
- the plugin's ``'foo'`` interface.
- 9. Do nothing.
-
- :param string attrib: The name of the stanza interface to modify.
- :param value: The new value of the stanza interface.
- """
- full_attrib = attrib
- attrib_lang = ('%s|' % attrib).split('|')
- attrib = attrib_lang[0]
- lang = attrib_lang[1] or None
-
- kwargs = {}
- if lang and attrib in self.lang_interfaces:
- kwargs['lang'] = lang
-
- kwargs = safedict(kwargs)
-
- if attrib in self.interfaces or attrib == 'lang':
- if value is not None:
- set_method = "set_%s" % attrib.lower()
- set_method2 = "set%s" % attrib.title()
-
- if self.plugin_overrides:
- name = self.plugin_overrides.get(set_method, None)
- if name:
- plugin = self._get_plugin(name, lang)
- if plugin:
- handler = getattr(plugin, set_method, None)
- if handler:
- return handler(value, **kwargs)
-
- if hasattr(self, set_method):
- getattr(self, set_method)(value, **kwargs)
- elif hasattr(self, set_method2):
- getattr(self, set_method2)(value, **kwargs)
- else:
- if attrib in self.sub_interfaces:
- if lang == '*':
- return self._set_all_sub_text(attrib,
- value,
- lang='*')
- return self._set_sub_text(attrib, text=value,
- lang=lang)
- elif attrib in self.bool_interfaces:
- if value:
- return self._set_sub_text(attrib, '',
- keep=True,
- lang=lang)
- else:
- return self._set_sub_text(attrib, '',
- keep=False,
- lang=lang)
- else:
- self._set_attr(attrib, value)
- else:
- self.__delitem__(attrib)
- elif attrib in self.plugin_attrib_map:
- plugin = self._get_plugin(attrib, lang)
- if plugin:
- plugin[full_attrib] = value
- return self
-
- def __delitem__(self, attrib):
- """Delete the value of a stanza interface using dict-like syntax.
-
- Example::
-
- >>> msg['body'] = "Hi!"
- >>> msg['body']
- 'Hi!'
- >>> del msg['body']
- >>> msg['body']
- ''
-
- Stanza interfaces are typically mapped directly to the underlyig XML
- object, but can be overridden by the presence of a ``del_attrib``
- method (or ``del_foo`` where the interface is named ``'foo'``, etc).
-
- The effect of deleting a stanza interface value named ``foo`` will be
- one of:
-
- 1. Call ``del_foo`` override handler, if it exists.
- 2. Call ``del_foo``, if it exists.
- 3. Call ``delFoo``, if it exists.
- 4. Delete ``foo`` element, if ``'foo'`` is in
- :attr:`sub_interfaces`.
- 5. Remove ``foo`` element if ``'foo'`` is in
- :attr:`bool_interfaces`.
- 6. Delete top level XML attribute named ``foo``.
- 7. Remove the ``foo`` plugin, if it was loaded.
- 8. Do nothing.
-
- :param attrib: The name of the affected stanza interface.
- """
- full_attrib = attrib
- attrib_lang = ('%s|' % attrib).split('|')
- attrib = attrib_lang[0]
- lang = attrib_lang[1] or None
-
- kwargs = {}
- if lang and attrib in self.lang_interfaces:
- kwargs['lang'] = lang
-
- kwargs = safedict(kwargs)
-
- if attrib in self.interfaces or attrib == 'lang':
- del_method = "del_%s" % attrib.lower()
- del_method2 = "del%s" % attrib.title()
-
- if self.plugin_overrides:
- name = self.plugin_overrides.get(del_method, None)
- if name:
- plugin = self._get_plugin(attrib, lang)
- if plugin:
- handler = getattr(plugin, del_method, None)
- if handler:
- return handler(**kwargs)
-
- if hasattr(self, del_method):
- getattr(self, del_method)(**kwargs)
- elif hasattr(self, del_method2):
- getattr(self, del_method2)(**kwargs)
- else:
- if attrib in self.sub_interfaces:
- return self._del_sub(attrib, lang=lang)
- elif attrib in self.bool_interfaces:
- return self._del_sub(attrib, lang=lang)
- else:
- self._del_attr(attrib)
- elif attrib in self.plugin_attrib_map:
- plugin = self._get_plugin(attrib, lang, check=True)
- if not plugin:
- return self
- if plugin.is_extension:
- del plugin[full_attrib]
- del self.plugins[(attrib, None)]
- else:
- del self.plugins[(attrib, plugin['lang'])]
- self.loaded_plugins.remove(attrib)
- try:
- self.xml.remove(plugin.xml)
- except ValueError:
- pass
- return self
-
- def _set_attr(self, name, value):
- """Set the value of a top level attribute of the XML object.
-
- If the new value is None or an empty string, then the attribute will
- be removed.
-
- :param name: The name of the attribute.
- :param value: The new value of the attribute, or None or '' to
- remove it.
- """
- if value is None or value == '':
- self.__delitem__(name)
- else:
- self.xml.attrib[name] = value
-
- def _del_attr(self, name):
- """Remove a top level attribute of the XML object.
-
- :param name: The name of the attribute.
- """
- if name in self.xml.attrib:
- del self.xml.attrib[name]
-
- def _get_attr(self, name, default=''):
- """Return the value of a top level attribute of the XML object.
-
- In case the attribute has not been set, a default value can be
- returned instead. An empty string is returned if no other default
- is supplied.
-
- :param name: The name of the attribute.
- :param default: Optional value to return if the attribute has not
- been set. An empty string is returned otherwise.
- """
- return self.xml.attrib.get(name, default)
-
- def _get_sub_text(self, name, default='', lang=None):
- """Return the text contents of a sub element.
-
- In case the element does not exist, or it has no textual content,
- a default value can be returned instead. An empty string is returned
- if no other default is supplied.
-
- :param name: The name or XPath expression of the element.
- :param default: Optional default to return if the element does
- not exists. An empty string is returned otherwise.
- """
- name = self._fix_ns(name)
- if lang == '*':
- return self._get_all_sub_text(name, default, None)
-
- default_lang = self.get_lang()
- if not lang:
- lang = default_lang
-
- stanzas = self.xml.findall(name)
- if not stanzas:
- return default
- for stanza in stanzas:
- if stanza.attrib.get('{%s}lang' % XML_NS, default_lang) == lang:
- if stanza.text is None:
- return default
- return stanza.text
- return default
-
- def _get_all_sub_text(self, name, default='', lang=None):
- name = self._fix_ns(name)
-
- default_lang = self.get_lang()
- results = OrderedDict()
- stanzas = self.xml.findall(name)
- if stanzas:
- for stanza in stanzas:
- stanza_lang = stanza.attrib.get('{%s}lang' % XML_NS,
- default_lang)
- if not lang or lang == '*' or stanza_lang == lang:
- results[stanza_lang] = stanza.text
- return results
-
- def _set_sub_text(self, name, text=None, keep=False, lang=None):
- """Set the text contents of a sub element.
-
- In case the element does not exist, a element will be created,
- and its text contents will be set.
-
- If the text is set to an empty string, or None, then the
- element will be removed, unless keep is set to True.
-
- :param name: The name or XPath expression of the element.
- :param text: The new textual content of the element. If the text
- is an empty string or None, the element will be removed
- unless the parameter keep is True.
- :param keep: Indicates if the element should be kept if its text is
- removed. Defaults to False.
- """
- default_lang = self.get_lang()
- if lang is None:
- lang = default_lang
-
- if not text and not keep:
- return self._del_sub(name, lang=lang)
-
- path = self._fix_ns(name, split=True)
- name = path[-1]
- parent = self.xml
-
- # The first goal is to find the parent of the subelement, or, if
- # we can't find that, the closest grandparent element.
- missing_path = []
- search_order = path[:-1]
- while search_order:
- parent = self.xml.find('/'.join(search_order))
- ename = search_order.pop()
- if parent is not None:
- break
- else:
- missing_path.append(ename)
- missing_path.reverse()
-
- # Find all existing elements that match the desired
- # element path (there may be multiples due to different
- # languages values).
- if parent is not None:
- elements = self.xml.findall('/'.join(path))
- else:
- parent = self.xml
- elements = []
-
- # Insert the remaining grandparent elements that don't exist yet.
- for ename in missing_path:
- element = ET.Element(ename)
- parent.append(element)
- parent = element
-
- # Re-use an existing element with the proper language, if one exists.
- for element in elements:
- elang = element.attrib.get('{%s}lang' % XML_NS, default_lang)
- if not lang and elang == default_lang or lang and lang == elang:
- element.text = text
- return element
-
- # No useable element exists, so create a new one.
- element = ET.Element(name)
- element.text = text
- if lang and lang != default_lang:
- element.attrib['{%s}lang' % XML_NS] = lang
- parent.append(element)
- return element
-
- def _set_all_sub_text(self, name, values, keep=False, lang=None):
- self._del_sub(name, lang)
- for value_lang, value in values.items():
- if not lang or lang == '*' or value_lang == lang:
- self._set_sub_text(name, text=value,
- keep=keep,
- lang=value_lang)
-
- def _del_sub(self, name, all=False, lang=None):
- """Remove sub elements that match the given name or XPath.
-
- If the element is in a path, then any parent elements that become
- empty after deleting the element may also be deleted if requested
- by setting all=True.
-
- :param name: The name or XPath expression for the element(s) to remove.
- :param bool all: If True, remove all empty elements in the path to the
- deleted element. Defaults to False.
- """
- path = self._fix_ns(name, split=True)
- original_target = path[-1]
-
- default_lang = self.get_lang()
- if not lang:
- lang = default_lang
-
- for level, _ in enumerate(path):
- # Generate the paths to the target elements and their parent.
- element_path = "/".join(path[:len(path) - level])
- parent_path = "/".join(path[:len(path) - level - 1])
-
- elements = self.xml.findall(element_path)
- parent = self.xml.find(parent_path)
-
- if elements:
- if parent is None:
- parent = self.xml
- for element in elements:
- if element.tag == original_target or not list(element):
- # Only delete the originally requested elements, and
- # any parent elements that have become empty.
- elem_lang = element.attrib.get('{%s}lang' % XML_NS,
- default_lang)
- if lang == '*' or elem_lang == lang:
- parent.remove(element)
- if not all:
- # If we don't want to delete elements up the tree, stop
- # after deleting the first level of elements.
- return
-
- def match(self, xpath):
- """Compare a stanza object with an XPath-like expression.
-
- If the XPath matches the contents of the stanza object, the match
- is successful.
-
- The XPath expression may include checks for stanza attributes.
- For example::
-
- 'presence@show=xa@priority=2/status'
-
- Would match a presence stanza whose show value is set to ``'xa'``,
- has a priority value of ``'2'``, and has a status element.
-
- :param string xpath: The XPath expression to check against. It
- may be either a string or a list of element
- names with attribute checks.
- """
- if not isinstance(xpath, list):
- xpath = self._fix_ns(xpath, split=True, propagate_ns=False)
-
- # Extract the tag name and attribute checks for the first XPath node.
- components = xpath[0].split('@')
- tag = components[0]
- attributes = components[1:]
-
- if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \
- tag not in self.loaded_plugins and tag not in self.plugin_attrib:
- # The requested tag is not in this stanza, so no match.
- return False
-
- # Check the rest of the XPath against any substanzas.
- matched_substanzas = False
- for substanza in self.iterables:
- if xpath[1:] == []:
- break
- matched_substanzas = substanza.match(xpath[1:])
- if matched_substanzas:
- break
-
- # Check attribute values.
- for attribute in attributes:
- name, value = attribute.split('=')
- if self[name] != value:
- return False
-
- # Check sub interfaces.
- if len(xpath) > 1:
- next_tag = xpath[1]
- if next_tag in self.sub_interfaces and self[next_tag]:
- return True
-
- # Attempt to continue matching the XPath using the stanza's plugins.
- if not matched_substanzas and len(xpath) > 1:
- # Convert {namespace}tag@attribs to just tag
- next_tag = xpath[1].split('@')[0].split('}')[-1]
- langs = [name[1] for name in self.plugins if name[0] == next_tag]
- for lang in langs:
- plugin = self._get_plugin(next_tag, lang)
- if plugin and plugin.match(xpath[1:]):
- return True
- return False
-
- # Everything matched.
- return True
-
- def find(self, xpath):
- """Find an XML object in this stanza given an XPath expression.
-
- Exposes ElementTree interface for backwards compatibility.
-
- .. note::
-
- Matching on attribute values is not supported in Python 2.6
- or Python 3.1
-
- :param string xpath: An XPath expression matching a single
- desired element.
- """
- return self.xml.find(xpath)
-
- def findall(self, xpath):
- """Find multiple XML objects in this stanza given an XPath expression.
-
- Exposes ElementTree interface for backwards compatibility.
-
- .. note::
-
- Matching on attribute values is not supported in Python 2.6
- or Python 3.1.
-
- :param string xpath: An XPath expression matching multiple
- desired elements.
- """
- return self.xml.findall(xpath)
-
- def get(self, key, default=None):
- """Return the value of a stanza interface.
-
- If the found value is None or an empty string, return the supplied
- default value.
-
- Allows stanza objects to be used like dictionaries.
-
- :param string key: The name of the stanza interface to check.
- :param default: Value to return if the stanza interface has a value
- of ``None`` or ``""``. Will default to returning None.
- """
- value = self[key]
- if value is None or value == '':
- return default
- return value
-
- def keys(self):
- """Return the names of all stanza interfaces provided by the
- stanza object.
-
- Allows stanza objects to be used like dictionaries.
- """
- out = []
- out += [x for x in self.interfaces]
- out += [x for x in self.loaded_plugins]
- out.append('lang')
- if self.iterables:
- out.append('substanzas')
- return out
-
- def append(self, item):
- """Append either an XML object or a substanza to this stanza object.
-
- If a substanza object is appended, it will be added to the list
- of iterable stanzas.
-
- Allows stanza objects to be used like lists.
-
- :param item: Either an XML object or a stanza object to add to
- this stanza's contents.
- """
- if not isinstance(item, ElementBase):
- if type(item) == XML_TYPE:
- return self.appendxml(item)
- else:
- raise TypeError
- self.xml.append(item.xml)
- self.iterables.append(item)
- if item.__class__ in self.plugin_iterables:
- if item.__class__.plugin_multi_attrib:
- self.init_plugin(item.__class__.plugin_multi_attrib)
- elif item.__class__ == self.plugin_tag_map.get(item.tag_name(), None):
- self.init_plugin(item.plugin_attrib,
- existing_xml=item.xml,
- reuse=False)
- return self
-
- def appendxml(self, xml):
- """Append an XML object to the stanza's XML.
-
- The added XML will not be included in the list of
- iterable substanzas.
-
- :param XML xml: The XML object to add to the stanza.
- """
- self.xml.append(xml)
- return self
-
- def pop(self, index=0):
- """Remove and return the last substanza in the list of
- iterable substanzas.
-
- Allows stanza objects to be used like lists.
-
- :param int index: The index of the substanza to remove.
- """
- substanza = self.iterables.pop(index)
- self.xml.remove(substanza.xml)
- return substanza
-
- def next(self):
- """Return the next iterable substanza."""
- return self.__next__()
-
- def clear(self):
- """Remove all XML element contents and plugins.
-
- Any attribute values will be preserved.
- """
- for child in list(self.xml):
- self.xml.remove(child)
-
- for plugin in list(self.plugins.keys()):
- del self.plugins[plugin]
- return self
-
- @classmethod
- def tag_name(cls):
- """Return the namespaced name of the stanza's root element.
-
- The format for the tag name is::
-
- '{namespace}elementname'
-
- For example, for the stanza ``<foo xmlns="bar" />``,
- ``stanza.tag_name()`` would return ``"{bar}foo"``.
- """
- return "{%s}%s" % (cls.namespace, cls.name)
-
- def get_lang(self, lang=None):
- result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
- if not result and self.parent and self.parent():
- return self.parent()['lang']
- return result
-
- def set_lang(self, lang):
- self.del_lang()
- attr = '{%s}lang' % XML_NS
- if lang:
- self.xml.attrib[attr] = lang
-
- def del_lang(self):
- attr = '{%s}lang' % XML_NS
- if attr in self.xml.attrib:
- del self.xml.attrib[attr]
-
- @property
- def attrib(self):
- """Return the stanza object itself.
-
- Older implementations of stanza objects used XML objects directly,
- requiring the use of ``.attrib`` to access attribute values.
-
- Use of the dictionary syntax with the stanza object itself for
- accessing stanza interfaces is preferred.
-
- .. deprecated:: 1.0
- """
- return self
-
- def _fix_ns(self, xpath, split=False, propagate_ns=True):
- return fix_ns(xpath, split=split,
- propagate_ns=propagate_ns,
- default_ns=self.namespace)
-
- def __eq__(self, other):
- """Compare the stanza object with another to test for equality.
-
- Stanzas are equal if their interfaces return the same values,
- and if they are both instances of ElementBase.
-
- :param ElementBase other: The stanza object to compare against.
- """
- if not isinstance(other, ElementBase):
- return False
-
- # Check that this stanza is a superset of the other stanza.
- values = self.values
- for key in other.keys():
- if key not in values or values[key] != other[key]:
- return False
-
- # Check that the other stanza is a superset of this stanza.
- values = other.values
- for key in self.keys():
- if key not in values or values[key] != self[key]:
- return False
-
- # Both stanzas are supersets of each other, therefore they
- # must be equal.
- return True
-
- def __ne__(self, other):
- """Compare the stanza object with another to test for inequality.
-
- Stanzas are not equal if their interfaces return different values,
- or if they are not both instances of ElementBase.
-
- :param ElementBase other: The stanza object to compare against.
- """
- return not self.__eq__(other)
-
- def __bool__(self):
- """Stanza objects should be treated as True in boolean contexts.
-
- Python 3.x version.
- """
- return True
-
- def __nonzero__(self):
- """Stanza objects should be treated as True in boolean contexts.
-
- Python 2.x version.
- """
- return True
-
- def __len__(self):
- """Return the number of iterable substanzas in this stanza."""
- return len(self.iterables)
-
- def __iter__(self):
- """Return an iterator object for the stanza's substanzas.
-
- The iterator is the stanza object itself. Attempting to use two
- iterators on the same stanza at the same time is discouraged.
- """
- self._index = 0
- return self
-
- def __next__(self):
- """Return the next iterable substanza."""
- self._index += 1
- if self._index > len(self.iterables):
- self._index = 0
- raise StopIteration
- return self.iterables[self._index - 1]
-
- def __copy__(self):
- """Return a copy of the stanza object that does not share the same
- underlying XML object.
- """
- return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent)
-
- def __str__(self, top_level_ns=True):
- """Return a string serialization of the underlying XML object.
-
- .. seealso:: :ref:`tostring`
-
- :param bool top_level_ns: Display the top-most namespace.
- Defaults to True.
- """
- return tostring(self.xml, xmlns='',
- top_level=True)
-
- def __repr__(self):
- """Use the stanza's serialized XML as its representation."""
- return self.__str__()
-
-
-class StanzaBase(ElementBase):
-
- """
- StanzaBase provides the foundation for all other stanza objects used
- by SleekXMPP, and defines a basic set of interfaces common to nearly
- all stanzas. These interfaces are the ``'id'``, ``'type'``, ``'to'``,
- and ``'from'`` attributes. An additional interface, ``'payload'``, is
- available to access the XML contents of the stanza. Most stanza objects
- will provided more specific interfaces, however.
-
- **Stanza Interfaces:**
-
- :id: An optional id value that can be used to associate stanzas
- :to: A JID object representing the recipient's JID.
- :from: A JID object representing the sender's JID.
- with their replies.
- :type: The type of stanza, typically will be ``'normal'``,
- ``'error'``, ``'get'``, or ``'set'``, etc.
- :payload: The XML contents of the stanza.
-
- :param XMLStream stream: Optional :class:`sleekxmpp.xmlstream.XMLStream`
- object responsible for sending this stanza.
- :param XML xml: Optional XML contents to initialize stanza values.
- :param string stype: Optional stanza type value.
- :param sto: Optional string or :class:`sleekxmpp.xmlstream.JID`
- object of the recipient's JID.
- :param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID`
- object of the sender's JID.
- :param string sid: Optional ID value for the stanza.
- :param parent: Optionally specify a parent stanza object will
- contain this substanza.
- """
-
- #: The default XMPP client namespace
- namespace = 'jabber:client'
-
- #: There is a small set of attributes which apply to all XMPP stanzas:
- #: the stanza type, the to and from JIDs, the stanza ID, and, especially
- #: in the case of an Iq stanza, a payload.
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
-
- #: A basic set of allowed values for the ``'type'`` interface.
- types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
-
- def __init__(self, stream=None, xml=None, stype=None,
- sto=None, sfrom=None, sid=None, parent=None):
- self.stream = stream
- if stream is not None:
- self.namespace = stream.default_ns
- ElementBase.__init__(self, xml, parent)
- if stype is not None:
- self['type'] = stype
- if sto is not None:
- self['to'] = sto
- if sfrom is not None:
- self['from'] = sfrom
- if sid is not None:
- self['id'] = sid
- self.tag = "{%s}%s" % (self.namespace, self.name)
-
- def set_type(self, value):
- """Set the stanza's ``'type'`` attribute.
-
- Only type values contained in :attr:`types` are accepted.
-
- :param string value: One of the values contained in :attr:`types`
- """
- if value in self.types:
- self.xml.attrib['type'] = value
- return self
-
- def get_to(self):
- """Return the value of the stanza's ``'to'`` attribute."""
- return JID(self._get_attr('to'))
-
- def set_to(self, value):
- """Set the ``'to'`` attribute of the stanza.
-
- :param value: A string or :class:`sleekxmpp.xmlstream.JID` object
- representing the recipient's JID.
- """
- return self._set_attr('to', str(value))
-
- def get_from(self):
- """Return the value of the stanza's ``'from'`` attribute."""
- return JID(self._get_attr('from'))
-
- def set_from(self, value):
- """Set the 'from' attribute of the stanza.
-
- Arguments:
- from -- A string or JID object representing the sender's JID.
- """
- return self._set_attr('from', str(value))
-
- def get_payload(self):
- """Return a list of XML objects contained in the stanza."""
- return list(self.xml)
-
- def set_payload(self, value):
- """Add XML content to the stanza.
-
- :param value: Either an XML or a stanza object, or a list
- of XML or stanza objects.
- """
- if not isinstance(value, list):
- value = [value]
- for val in value:
- self.append(val)
- return self
-
- def del_payload(self):
- """Remove the XML contents of the stanza."""
- self.clear()
- return self
-
- def reply(self, clear=True):
- """Prepare the stanza for sending a reply.
-
- Swaps the ``'from'`` and ``'to'`` attributes.
-
- If ``clear=True``, then also remove the stanza's
- contents to make room for the reply content.
-
- For client streams, the ``'from'`` attribute is removed.
-
- :param bool clear: Indicates if the stanza's contents should be
- removed. Defaults to ``True``.
- """
- # if it's a component, use from
- if self.stream and hasattr(self.stream, "is_component") and \
- self.stream.is_component:
- self['from'], self['to'] = self['to'], self['from']
- else:
- self['to'] = self['from']
- del self['from']
- if clear:
- self.clear()
- return self
-
- def error(self):
- """Set the stanza's type to ``'error'``."""
- self['type'] = 'error'
- return self
-
- def unhandled(self):
- """Called if no handlers have been registered to process this stanza.
-
- Meant to be overridden.
- """
- pass
-
- def exception(self, e):
- """Handle exceptions raised during stanza processing.
-
- Meant to be overridden.
- """
- log.exception('Error handling {%s}%s stanza', self.namespace,
- self.name)
-
- def send(self, now=False):
- """Queue the stanza to be sent on the XML stream.
-
- :param bool now: Indicates if the queue should be skipped and the
- stanza sent immediately. Useful for stream
- initialization. Defaults to ``False``.
- """
- self.stream.send(self, now=now)
-
- def __copy__(self):
- """Return a copy of the stanza object that does not share the
- same underlying XML object, but does share the same XML stream.
- """
- return self.__class__(xml=copy.deepcopy(self.xml),
- stream=self.stream)
-
- def __str__(self, top_level_ns=False):
- """Serialize the stanza's XML to a string.
-
- :param bool top_level_ns: Display the top-most namespace.
- Defaults to ``False``.
- """
- xmlns = self.stream.default_ns if self.stream else ''
- return tostring(self.xml, xmlns=xmlns,
- stream=self.stream,
- top_level=(self.stream is None))
-
-
-#: A JSON/dictionary version of the XML content exposed through
-#: the stanza interfaces::
-#:
-#: >>> msg = Message()
-#: >>> msg.values
-#: {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
-#: 'to': , 'type': 'normal', 'id': '', 'subject': ''}
-#:
-#: Likewise, assigning to the :attr:`values` will change the XML
-#: content::
-#:
-#: >>> msg = Message()
-#: >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
-#: >>> msg
-#: '<message to="user@example.com"><body>Hi!</body></message>'
-#:
-#: Child stanzas are exposed as nested dictionaries.
-ElementBase.values = property(ElementBase._get_stanza_values,
- ElementBase._set_stanza_values)
-
-
-# To comply with PEP8, method names now use underscores.
-# Deprecated method names are re-mapped for backwards compatibility.
-ElementBase.initPlugin = ElementBase.init_plugin
-ElementBase._getAttr = ElementBase._get_attr
-ElementBase._setAttr = ElementBase._set_attr
-ElementBase._delAttr = ElementBase._del_attr
-ElementBase._getSubText = ElementBase._get_sub_text
-ElementBase._setSubText = ElementBase._set_sub_text
-ElementBase._delSub = ElementBase._del_sub
-ElementBase.getStanzaValues = ElementBase._get_stanza_values
-ElementBase.setStanzaValues = ElementBase._set_stanza_values
-
-StanzaBase.setType = StanzaBase.set_type
-StanzaBase.getTo = StanzaBase.get_to
-StanzaBase.setTo = StanzaBase.set_to
-StanzaBase.getFrom = StanzaBase.get_from
-StanzaBase.setFrom = StanzaBase.set_from
-StanzaBase.getPayload = StanzaBase.get_payload
-StanzaBase.setPayload = StanzaBase.set_payload
-StanzaBase.delPayload = StanzaBase.del_payload