aboutsummaryrefslogtreecommitdiffstats
path: root/sleekxmpp/xmlstream/stanzabase.py
diff options
context:
space:
mode:
authorThibaut Horel <thibaut.horel@gmail.com>2010-12-31 19:19:25 +0100
committerThibaut Horel <thibaut.horel@gmail.com>2010-12-31 19:19:25 +0100
commitd90aec17e2201f256783a531c548dcc9857c889d (patch)
tree56b6d0580ee1993c73e67c63d4a452a81bbaaf1e /sleekxmpp/xmlstream/stanzabase.py
parentaf76bcdf7a947702eaa19d39f5b9ecfcd7ec6fd2 (diff)
downloadalias-d90aec17e2201f256783a531c548dcc9857c889d.tar.gz
Cleanup of repository. Bases of webclient.
* remove sleekxmpp (install guideline in server/README) * move server code to server directory * webclient directory with basic strophejs example
Diffstat (limited to 'sleekxmpp/xmlstream/stanzabase.py')
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py1165
1 files changed, 0 insertions, 1165 deletions
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
deleted file mode 100644
index aabd386..0000000
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ /dev/null
@@ -1,1165 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-import copy
-import logging
-import sys
-import weakref
-from xml.etree import cElementTree as ET
-
-from sleekxmpp.xmlstream import JID
-from sleekxmpp.xmlstream.tostring import tostring
-
-
-log = logging.getLogger(__name__)
-
-
-# Used to check if an argument is an XML object.
-XML_TYPE = type(ET.Element('xml'))
-
-
-def register_stanza_plugin(stanza, plugin):
- """
- Associate a stanza object as a plugin for another stanza.
-
- Arguments:
- stanza -- The class of the parent stanza.
- plugin -- The class of the plugin stanza.
- """
- tag = "{%s}%s" % (plugin.namespace, plugin.name)
- stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
- stanza.plugin_tag_map[tag] = plugin
-
-
-# To maintain backwards compatibility for now, preserve the camel case name.
-registerStanzaPlugin = register_stanza_plugin
-
-
-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 accessed
- directly from the parent stanza, as so:
-
- >>> message['custom'] = 'bar' # Same as using message['custom']['custom']
-
- Class Attributes:
- name -- The name of the stanza's main element.
- namespace -- The namespace of the stanza's main element.
- interfaces -- A set of attribute and element names that may
- be accessed using dictionary syntax.
- sub_interfaces -- A subset of the set of interfaces which map
- to subelements instead of attributes.
- subitem -- A set of stanza classes which are allowed to
- be added as substanzas.
- types -- A set of generic type attribute values.
- plugin_attrib -- The interface name that the stanza uses to be
- accessed as a plugin from another stanza.
- plugin_attrib_map -- A mapping of plugin attribute names with the
- associated plugin stanza classes.
- plugin_tag_map -- A mapping of plugin stanza tag names with
- the associated plugin stanza classes.
-
- Instance Attributes:
- xml -- The stanza's XML contents.
- parent -- The parent stanza of this stanza.
- plugins -- A map of enabled plugin names with the
- initialized plugin stanza objects.
- values -- A dictionary of the stanza's interfaces
- and interface values, including plugins.
-
- Methods:
- setup -- Initialize the stanza's XML contents.
- enable -- Instantiate a stanza plugin.
- Alias for init_plugin.
- init_plugin -- Instantiate a stanza plugin.
- _get_stanza_values -- Return a dictionary of stanza interfaces and
- their values.
- _set_stanza_values -- Set stanza interface values given a dictionary
- of interfaces and values.
- __getitem__ -- Return the value of a stanza interface.
- __setitem__ -- Set the value of a stanza interface.
- __delitem__ -- Remove the value of a stanza interface.
- _set_attr -- Set an attribute value of the main
- stanza element.
- _del_attr -- Remove an attribute from the main
- stanza element.
- _get_attr -- Return an attribute's value from the main
- stanza element.
- _get_sub_text -- Return the text contents of a subelement.
- _set_sub_ext -- Set the text contents of a subelement.
- _del_sub -- Remove a subelement.
- match -- Compare the stanza against an XPath expression.
- find -- Return subelement matching an XPath expression.
- findall -- Return subelements matching an XPath expression.
- get -- Return the value of a stanza interface, with an
- optional default value.
- keys -- Return the set of interface names accepted by
- the stanza.
- append -- Add XML content or a substanza to the stanza.
- appendxml -- Add XML content to the stanza.
- pop -- Remove a substanza.
- next -- Return the next iterable substanza.
- _fix_ns -- Apply the stanza's namespace to non-namespaced
- elements in an XPath expression.
- """
-
- name = 'stanza'
- plugin_attrib = 'plugin'
- namespace = 'jabber:client'
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
- types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
- sub_interfaces = tuple()
- plugin_attrib_map = {}
- plugin_tag_map = {}
- subitem = None
-
- def __init__(self, xml=None, parent=None):
- """
- Create a new stanza object.
-
- Arguments:
- xml -- Initialize the stanza with optional existing XML.
- parent -- Optional stanza object that contains this stanza.
- """
- # To comply with PEP8, method names now use underscores.
- # Deprecated method names are re-mapped for backwards compatibility.
- self.initPlugin = self.init_plugin
- self._getAttr = self._get_attr
- self._setAttr = self._set_attr
- self._delAttr = self._del_attr
- self._getSubText = self._get_sub_text
- self._setSubText = self._set_sub_text
- self._delSub = self._del_sub
- self.getStanzaValues = self._get_stanza_values
- self.setStanzaValues = self._set_stanza_values
-
- self.xml = xml
- self.plugins = {}
- self.iterables = []
- self._index = 0
- if parent is None:
- self.parent = None
- else:
- self.parent = weakref.ref(parent)
-
- ElementBase.values = property(ElementBase._get_stanza_values,
- ElementBase._set_stanza_values)
-
- 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.getchildren():
- if child.tag in self.plugin_tag_map:
- plugin = self.plugin_tag_map[child.tag]
- self.plugins[plugin.plugin_attrib] = plugin(child, self)
- if self.subitem is not None:
- for sub in self.subitem:
- if child.tag == "{%s}%s" % (sub.namespace, sub.name):
- self.iterables.append(sub(child, self))
- break
-
- def setup(self, xml=None):
- """
- Initialize the stanza's XML contents.
-
- Will return True if XML was generated according to the stanza's
- definition.
-
- Arguments:
- xml -- Optional XML object to use for the stanza's content
- instead of generating XML.
- """
- if self.xml is None:
- self.xml = 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):
- """
- Enable and initialize a stanza plugin.
-
- Alias for init_plugin.
-
- Arguments:
- attrib -- The stanza interface for the plugin.
- """
- return self.init_plugin(attrib)
-
- def init_plugin(self, attrib):
- """
- Enable and initialize a stanza plugin.
-
- Arguments:
- attrib -- The stanza interface for the plugin.
- """
- if attrib not in self.plugins:
- plugin_class = self.plugin_attrib_map[attrib]
- self.plugins[attrib] = plugin_class(parent=self)
- return self
-
- def _get_stanza_values(self):
- """
- Return a dictionary of the stanza's interface values.
-
- Stanza plugin values are included as nested dictionaries.
- """
- values = {}
- for interface in self.interfaces:
- values[interface] = self[interface]
- for plugin, stanza in self.plugins.items():
- values[plugin] = stanza._get_stanza_values()
- if self.iterables:
- iterables = []
- for stanza in self.iterables:
- iterables.append(stanza._get_stanza_values())
- iterables[-1].update({
- '__childtag__': "{%s}%s" % (stanza.namespace,
- stanza.name)})
- 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.
-
- Arguments:
- values -- A dictionary mapping stanza interface with values.
- Plugin interfaces may accept a nested dictionary that
- will be used recursively.
- """
- for interface, value in values.items():
- if interface == 'substanzas':
- for subdict in value:
- if '__childtag__' in subdict:
- for subclass in self.subitem:
- child_tag = "{%s}%s" % (subclass.namespace,
- subclass.name)
- if subdict['__childtag__'] == child_tag:
- sub = subclass(parent=self)
- sub._set_stanza_values(subdict)
- self.iterables.append(sub)
- break
- elif interface in self.interfaces:
- self[interface] = value
- elif interface in self.plugin_attrib_map:
- if interface not in self.plugins:
- self.init_plugin(interface)
- self.plugins[interface]._set_stanza_values(value)
- return self
-
- def __getitem__(self, attrib):
- """
- Return the value of a stanza interface using dictionary-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.
- 2. The result of calling get_foo.
- 3. The result of calling getFoo.
- 4. The contents of the foo subelement, if foo is a sub interface.
- 5. The value of the foo attribute of the XML object.
- 6. The plugin named 'foo'
- 7. An empty string.
-
- Arguments:
- attrib -- The name of the requested stanza interface.
- """
- if attrib == 'substanzas':
- return self.iterables
- elif attrib in self.interfaces:
- get_method = "get_%s" % attrib.lower()
- get_method2 = "get%s" % attrib.title()
- if hasattr(self, get_method):
- return getattr(self, get_method)()
- elif hasattr(self, get_method2):
- return getattr(self, get_method2)()
- else:
- if attrib in self.sub_interfaces:
- return self._get_sub_text(attrib)
- else:
- return self._get_attr(attrib)
- elif attrib in self.plugin_attrib_map:
- if attrib not in self.plugins:
- self.init_plugin(attrib)
- return self.plugins[attrib]
- 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 set_foo, if it exists.
- 3. Call setFoo, if it exists.
- 4. Set the text of a foo element, if foo is in sub_interfaces.
- 5. Set the value of a top level XML attribute name foo.
- 6. Attempt to pass value to a plugin named foo using the plugin's
- foo interface.
- 7. Do nothing.
-
- Arguments:
- attrib -- The name of the stanza interface to modify.
- value -- The new value of the stanza interface.
- """
- if attrib in self.interfaces:
- if value is not None:
- set_method = "set_%s" % attrib.lower()
- set_method2 = "set%s" % attrib.title()
- if hasattr(self, set_method):
- getattr(self, set_method)(value,)
- elif hasattr(self, set_method2):
- getattr(self, set_method2)(value,)
- else:
- if attrib in self.sub_interfaces:
- return self._set_sub_text(attrib, text=value)
- else:
- self._set_attr(attrib, value)
- else:
- self.__delitem__(attrib)
- elif attrib in self.plugin_attrib_map:
- if attrib not in self.plugins:
- self.init_plugin(attrib)
- self.plugins[attrib][attrib] = value
- return self
-
- def __delitem__(self, attrib):
- """
- Delete the value of a stanza interface using dictionary-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, if it exists.
- 2. Call delFoo, if it exists.
- 3. Delete foo element, if foo is in sub_interfaces.
- 4. Delete top level XML attribute named foo.
- 5. Remove the foo plugin, if it was loaded.
- 6. Do nothing.
-
- Arguments:
- attrib -- The name of the affected stanza interface.
- """
- if attrib in self.interfaces:
- del_method = "del_%s" % attrib.lower()
- del_method2 = "del%s" % attrib.title()
- if hasattr(self, del_method):
- getattr(self, del_method)()
- elif hasattr(self, del_method2):
- getattr(self, del_method2)()
- else:
- if attrib in self.sub_interfaces:
- return self._del_sub(attrib)
- else:
- self._del_attr(attrib)
- elif attrib in self.plugin_attrib_map:
- if attrib in self.plugins:
- xml = self.plugins[attrib].xml
- del self.plugins[attrib]
- self.xml.remove(xml)
- return self
-
- def _set_attr(self, name, value):
- """
- Set the value of a top level attribute of the underlying XML object.
-
- If the new value is None or an empty string, then the attribute will
- be removed.
-
- Arguments:
- name -- The name of the attribute.
- 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 underlying XML object.
-
- Arguments:
- 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 underlying
- 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.
-
- Arguments:
- name -- The name of the attribute.
- 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=''):
- """
- 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.
-
- Arguments:
- name -- The name or XPath expression of the element.
- default -- Optional default to return if the element does
- not exists. An empty string is returned otherwise.
- """
- name = self._fix_ns(name)
- stanza = self.xml.find(name)
- if stanza is None or stanza.text is None:
- return default
- else:
- return stanza.text
-
- def _set_sub_text(self, name, text=None, keep=False):
- """
- 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.
-
- Arguments:
- name -- The name or XPath expression of the element.
- 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.
- keep -- Indicates if the element should be kept if its text is
- removed. Defaults to False.
- """
- path = self._fix_ns(name, split=True)
- element = self.xml.find(name)
-
- if not text and not keep:
- return self._del_sub(name)
-
- if element is None:
- # We need to add the element. If the provided name was
- # an XPath expression, some of the intermediate elements
- # may already exist. If so, we want to use those instead
- # of generating new elements.
- last_xml = self.xml
- walked = []
- for ename in path:
- walked.append(ename)
- element = self.xml.find("/".join(walked))
- if element is None:
- element = ET.Element(ename)
- last_xml.append(element)
- last_xml = element
- element = last_xml
-
- element.text = text
- return element
-
- def _del_sub(self, name, all=False):
- """
- 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.
-
- Arguments:
- name -- The name or XPath expression for the element(s) to remove.
- 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]
-
- 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 element.getchildren():
- # Only delete the originally requested elements, and
- # any parent elements that have become empty.
- 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 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.
-
- Arguments:
- xpath -- The XPath expression to check against. It may be either a
- string or a list of element names with attribute checks.
- """
- if isinstance(xpath, str):
- 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.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]
- if next_tag in self.plugins:
- return self.plugins[next_tag].match(xpath[1:])
- else:
- 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 that matching on attribute values is not supported in Python 2.6
- or Python 3.1
-
- Arguments:
- 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 that matching on attribute values is not supported in Python 2.6
- or Python 3.1.
-
- Arguments:
- 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.
-
- Arguments:
- key -- The name of the stanza interface to check.
- 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.plugins]
- 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.
-
- Arguments:
- 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)
- 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.
-
- Arguments:
- 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.
-
- Arguments:
- 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__()
-
- @property
- def attrib(self):
- """
- DEPRECATED
-
- For backwards compatibility, stanza.attrib returns the stanza 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.
- """
- return self
-
- def _fix_ns(self, xpath, split=False, propagate_ns=True):
- """
- Apply the stanza's namespace to elements in an XPath expression.
-
- Arguments:
- xpath -- The XPath expression to fix with namespaces.
- 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.
- 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 = self.namespace
- elements = ns_block.split('/')
-
- for element in elements:
- if element:
- # Skip empty entry artifacts from splitting.
- if propagate_ns:
- tag = '{%s}%s' % (namespace, element)
- else:
- tag = element
- fixed.append(tag)
- if split:
- return fixed
- return '/'.join(fixed)
-
- 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.
-
- Arguments:
- 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._get_stanza_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._get_stanza_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.
-
- Arguments:
- 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 contained in this stanza.
- """
- return len(self.iterables)
-
- def __iter__(self):
- """
- Return an iterator object for iterating over 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):
- """
- Return a string serialization of the underlying XML object.
- """
- return tostring(self.xml, xmlns='', stanza_ns=self.namespace)
-
- 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 Interface:
- from -- A JID object representing the sender's JID.
- id -- An optional id value that can be used to associate stanzas
- with their replies.
- payload -- The XML contents of the stanza.
- to -- A JID object representing the recipient's JID.
- type -- The type of stanza, typically will be 'normal', 'error',
- 'get', or 'set', etc.
-
- Attributes:
- stream -- The XMLStream instance that will handle sending this stanza.
- tag -- The namespaced version of the stanza's name.
-
- Methods:
- set_type -- Set the type of the stanza.
- get_to -- Return the stanza recipients JID.
- set_to -- Set the stanza recipient's JID.
- get_from -- Return the stanza sender's JID.
- set_from -- Set the stanza sender's JID.
- get_payload -- Return the stanza's XML contents.
- set_payload -- Append to the stanza's XML contents.
- del_payload -- Remove the stanza's XML contents.
- clear -- Reset the stanza's XML contents.
- reply -- Reset the stanza and modify the 'to' and 'from'
- attributes to prepare for sending a reply.
- error -- Set the stanza's type to 'error'.
- unhandled -- Callback for when the stanza is not handled by a
- stream handler.
- exception -- Callback for if an exception is raised while
- handling the stanza.
- send -- Send the stanza using the stanza's stream.
- """
-
- name = 'stanza'
- namespace = 'jabber:client'
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
- types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
- sub_interfaces = tuple()
-
- def __init__(self, stream=None, xml=None, stype=None,
- sto=None, sfrom=None, sid=None):
- """
- Create a new stanza.
-
- Arguments:
- stream -- Optional XMLStream responsible for sending this stanza.
- xml -- Optional XML contents to initialize stanza values.
- stype -- Optional stanza type value.
- sto -- Optional string or JID object of the recipient's JID.
- sfrom -- Optional string or JID object of the sender's JID.
- sid -- Optional ID value for the stanza.
- """
- # To comply with PEP8, method names now use underscores.
- # Deprecated method names are re-mapped for backwards compatibility.
- self.setType = self.set_type
- self.getTo = self.get_to
- self.setTo = self.set_to
- self.getFrom = self.get_from
- self.setFrom = self.set_from
- self.getPayload = self.get_payload
- self.setPayload = self.set_payload
- self.delPayload = self.del_payload
-
- self.stream = stream
- if stream is not None:
- self.namespace = stream.default_ns
- ElementBase.__init__(self, xml)
- if stype is not None:
- self['type'] = stype
- if sto is not None:
- self['to'] = sto
- if sfrom is not None:
- self['from'] = sfrom
- self.tag = "{%s}%s" % (self.namespace, self.name)
-
- def set_type(self, value):
- """
- Set the stanza's 'type' attribute.
-
- Only type values contained in StanzaBase.types are accepted.
-
- Arguments:
- value -- One of the values contained in StanzaBase.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.
-
- Arguments:
- value -- A string or 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 self.xml.getchildren()
-
- def set_payload(self, value):
- """
- Add XML content to the stanza.
-
- Arguments:
- 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 clear(self):
- """
- Remove all XML element contents and plugins.
-
- Any attribute values will be preserved.
- """
- for child in self.xml.getchildren():
- self.xml.remove(child)
- for plugin in list(self.plugins.keys()):
- del self.plugins[plugin]
- return self
-
- def reply(self):
- """
- Reset the stanza and swap its 'from' and 'to' attributes to prepare
- for sending a reply stanza.
-
- For client streams, the 'from' attribute is removed.
- """
- # 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']
- self.clear()
- return self
-
- def error(self):
- """Set the stanza's type to 'error'."""
- self['type'] = 'error'
- return self
-
- def unhandled(self):
- """
- Called when 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):
- """Queue the stanza to be sent on the XML stream."""
- self.stream.sendRaw(self.__str__())
-
- 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):
- """Serialize the stanza's XML to a string."""
- return tostring(self.xml, xmlns='',
- stanza_ns=self.namespace,
- stream=self.stream)