diff options
Diffstat (limited to 'sleekxmpp/xmlstream/stanzabase.py')
-rw-r--r-- | sleekxmpp/xmlstream/stanzabase.py | 187 |
1 files changed, 163 insertions, 24 deletions
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 8814df78..f8242005 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -586,15 +586,16 @@ class ElementBase(object): string or a list of element names with attribute checks. """ if isinstance(xpath, str): - xpath = xpath.split('/') + 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), - self.plugins, self.plugin_attrib): + 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 @@ -613,6 +614,12 @@ class ElementBase(object): 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 @@ -754,30 +761,45 @@ class ElementBase(object): """ return self - def _fix_ns(self, xpath, split=False): + 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. + 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: - fixed.append('{%s}%s' % (namespace, - 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) @@ -886,6 +908,48 @@ class ElementBase(object): 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: + setType -- Set the type of the stanza. + getTo -- Return the stanza recipients JID. + setTo -- Set the stanza recipient's JID. + getFrom -- Return the stanza sender's JID. + setFrom -- Set the stanza sender's JID. + getPayload -- Return the stanza's XML contents. + setPayload -- Append to the stanza's XML contents. + delPayload -- 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')) @@ -894,6 +958,17 @@ class StanzaBase(ElementBase): 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. + """ self.stream = stream if stream is not None: self.namespace = stream.default_ns @@ -907,22 +982,73 @@ class StanzaBase(ElementBase): self.tag = "{%s}%s" % (self.namespace, self.name) def setType(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 getTo(self): + """Return the value of the stanza's 'to' attribute.""" + return JID(self._getAttr('to')) + + def setTo(self, value): + """ + Set the 'to' attribute of the stanza. + + Arguments: + value -- A string or JID object representing the recipient's JID. + """ + return self._setAttr('to', str(value)) + + def getFrom(self): + """Return the value of the stanza's 'from' attribute.""" + return JID(self._getAttr('from')) + + def setFrom(self, value): + """ + Set the 'from' attribute of the stanza. + + Arguments: + from -- A string or JID object representing the sender's JID. + """ + return self._setAttr('from', str(value)) + def getPayload(self): + """Return a list of XML objects contained in the stanza.""" return self.xml.getchildren() def setPayload(self, value): - self.xml.append(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 delPayload(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()): @@ -930,6 +1056,12 @@ class StanzaBase(ElementBase): 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: @@ -941,35 +1073,42 @@ class StanzaBase(ElementBase): return self def error(self): + """Set the stanza's type to 'error'.""" self['type'] = 'error' return self - def getTo(self): - return JID(self._getAttr('to')) - - def setTo(self, value): - return self._setAttr('to', str(value)) - - def getFrom(self): - return JID(self._getAttr('from')) - - def setFrom(self, value): - return self._setAttr('from', str(value)) - 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. + """ logging.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 self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream) + """ + 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) |