pyxmpp.stanza

Package pyxmpp :: Module stanza |
[hide private]

# Source Code for Module pyxmpp.stanza

      1  #
      2  # (C) Copyright 2003-2006 Jacek Konieczny <jajcus@jajcus.net>
      3  #
      4  # This program is free software; you can redistribute it and/or modify
      5  # it under the terms of the GNU Lesser General Public License Version
      6  # 2.1 as published by the Free Software Foundation.
      7  #
      8  # This program is distributed in the hope that it will be useful,
      9  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11  # GNU Lesser General Public License for more details.
     12  #
     13  # You should have received a copy of the GNU Lesser General Public
     14  # License along with this program; if not, write to the Free Software
     15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     16  #
     17
     18  """General XMPP Stanza handling.
     19
     20  Normative reference:
     21    - `RFC 3920 <http://www.ietf.org/rfc/rfc3920.txt>`__
     22  """
     23
     24  __revision__="$Id: stanza.py 684 2009-01-17 17:53:01Z jajcus $"
     25  __docformat__="restructuredtext en"
     26
     27  import libxml2
     28  import random
     29
     30  from pyxmpp import xmlextra
     31  from pyxmpp.utils import from_utf8,to_utf8
     32  from pyxmpp.jid import JID
     33  from pyxmpp.xmlextra import common_doc, common_ns, COMMON_NS
     34  from pyxmpp.exceptions import ProtocolError, JIDMalformedProtocolError
     35
     36  random.seed()
     37  last_id=random.randrange(1000000)
     38
     39 -def gen_id():
     40      """Generate stanza id unique for the session.
     41
     42      :return: the new id."""
     43      global last_id
     44      last_id+=1
     45      return str(last_id)
     46
     47 -class Stanza:
     48      """Base class for all XMPP stanzas.
     49
     50      :Ivariables:
     51          - `xmlnode`: stanza XML node.
     52          - `_error`: `pyxmpp.error.StanzaErrorNode` describing the error associated with
     53            the stanza of type "error".
     54          - `stream`: stream on which the stanza was received or `None`. May be
     55            used to send replies or get some session-related parameters.
     56      :Types:
     57          - `xmlnode`: `libxml2.xmlNode`
     58          - `_error`: `pyxmpp.error.StanzaErrorNode`"""
     59      stanza_type="Unknown"
     60
     61 -    def __init__(self, name_or_xmlnode, from_jid=None, to_jid=None,
     62              stanza_type=None, stanza_id=None, error=None, error_cond=None,
     63              stream = None):
     64          """Initialize a Stanza object.
     65
     66          :Parameters:
     67              - `name_or_xmlnode`: XML node to be wrapped into the Stanza object
     68                or other Presence object to be copied. If not given then new
     69                presence stanza is created using following parameters.
     70              - `from_jid`: sender JID.
     71              - `to_jid`: recipient JID.
     72              - `stanza_type`: staza type: one of: "get", "set", "result" or "error".
     73              - `stanza_id`: stanza id -- value of stanza's "id" attribute. If
     74                not given, then unique for the session value is generated.
     75              - `error`: error object. Ignored if `stanza_type` is not "error".
     76              - `error_cond`: error condition name. Ignored if `stanza_type` is not
     77                "error" or `error` is not None.
     78          :Types:
     79              - `name_or_xmlnode`: `unicode` or `libxml2.xmlNode` or `Stanza`
     80              - `from_jid`: `JID`
     81              - `to_jid`: `JID`
     82              - `stanza_type`: `unicode`
     83              - `stanza_id`: `unicode`
     84              - `error`: `pyxmpp.error.StanzaErrorNode`
     85              - `error_cond`: `unicode`"""
     86          self._error=None
     87          self.xmlnode=None
     88          if isinstance(name_or_xmlnode,Stanza):
     89              self.xmlnode=name_or_xmlnode.xmlnode.docCopyNode(common_doc, True)
     90              common_doc.addChild(self.xmlnode)
     91              self.xmlnode.reconciliateNs(common_doc)
     92          elif isinstance(name_or_xmlnode,libxml2.xmlNode):
     93              self.xmlnode=name_or_xmlnode.docCopyNode(common_doc,1)
     94              common_doc.addChild(self.xmlnode)
     95              try:
     96                  ns = self.xmlnode.ns()
     97              except libxml2.treeError:
     98                  ns = None
     99              if not ns or not ns.name:
    100                  xmlextra.replace_ns(self.xmlnode, ns, common_ns)
    101              self.xmlnode.reconciliateNs(common_doc)
    102          else:
    103              self.xmlnode=common_doc.newChild(common_ns,name_or_xmlnode,None)
    104
    105          if from_jid is not None:
    106              if not isinstance(from_jid,JID):
    107                  from_jid=JID(from_jid)
    108              self.xmlnode.setProp("from",from_jid.as_utf8())
    109
    110          if to_jid is not None:
    111              if not isinstance(to_jid,JID):
    112                  to_jid=JID(to_jid)
    113              self.xmlnode.setProp("to",to_jid.as_utf8())
    114
    115          if stanza_type:
    116              self.xmlnode.setProp("type",stanza_type)
    117
    118          if stanza_id:
    119              self.xmlnode.setProp("id",stanza_id)
    120
    121          if self.get_type()=="error":
    122              from pyxmpp.error import StanzaErrorNode
    123              if error:
    124                  self._error=StanzaErrorNode(error,parent=self.xmlnode,copy=1)
    125              elif error_cond:
    126                  self._error=StanzaErrorNode(error_cond,parent=self.xmlnode)
    127          self.stream = stream
    128
    129 -    def __del__(self):
    130          if self.xmlnode:
    131              self.free()
    132
    133 -    def free(self):
    134          """Free the node associated with this `Stanza` object."""
    135          if self._error:
    136              self._error.free_borrowed()
    137          self.xmlnode.unlinkNode()
    138          self.xmlnode.freeNode()
    139          self.xmlnode=None
    140
    141 -    def copy(self):
    142          """Create a deep copy of the stanza.
    143
    144          :returntype: `Stanza`"""
    145          return Stanza(self)
    146
    147 -    def serialize(self):
    148          """Serialize the stanza into an UTF-8 encoded XML string.
    149
    150          :return: serialized stanza.
    151          :returntype: `str`"""
    152          return self.xmlnode.serialize(encoding="utf-8")
    153
    154 -    def get_node(self):
    155          """Return the XML node wrapped into `self`.
    156
    157          :returntype: `libxml2.xmlNode`"""
    158          return self.xmlnode
    159
    160 -    def get_from(self):
    161          """Get "from" attribute of the stanza.
    162
    163          :return: value of the "from" attribute (sender JID) or None.
    164          :returntype: `JID`"""
    165          if self.xmlnode.hasProp("from"):
    166              try:
    167                  return JID(from_utf8(self.xmlnode.prop("from")))
    168              except JIDError:
    169                  raise JIDMalformedProtocolError, "Bad JID in the 'from' attribute"
    170          else:
    171              return None
    172
    173      get_from_jid=get_from
    174
    175 -    def get_to(self):
    176          """Get "to" attribute of the stanza.
    177
    178          :return: value of the "to" attribute (recipient JID) or None.
    179          :returntype: `JID`"""
    180          if self.xmlnode.hasProp("to"):
    181              try:
    182                  return JID(from_utf8(self.xmlnode.prop("to")))
    183              except JIDError:
    184                  raise JIDMalformedProtocolError, "Bad JID in the 'to' attribute"
    185          else:
    186              return None
    187
    188      get_to_jid=get_to
    189
    190 -    def get_type(self):
    191          """Get "type" attribute of the stanza.
    192
    193          :return: value of the "type" attribute (stanza type) or None.
    194          :returntype: `unicode`"""
    195          if self.xmlnode.hasProp("type"):
    196              return from_utf8(self.xmlnode.prop("type"))
    197          else:
    198              return None
    199
    200      get_stanza_type=get_type
    201
    202 -    def get_id(self):
    203          """Get "id" attribute of the stanza.
    204
    205          :return: value of the "id" attribute (stanza identifier) or None.
    206          :returntype: `unicode`"""
    207          if self.xmlnode.hasProp("id"):
    208              return from_utf8(self.xmlnode.prop("id"))
    209          else:
    210              return None
    211
    212      get_stanza_id=get_id
    213
    214 -    def get_error(self):
    215          """Get stanza error information.
    216
    217          :return: object describing the error.
    218          :returntype: `pyxmpp.error.StanzaErrorNode`"""
    219          if self._error:
    220              return self._error
    221          n=self.xpath_eval(u"ns:error")
    222          if not n:
    223              raise ProtocolError, (None, "This stanza contains no error: %r" % (self.serialize(),))
    224          from pyxmpp.error import StanzaErrorNode
    225          self._error=StanzaErrorNode(n[0],copy=0)
    226          return self._error
    227
    228 -    def set_from(self,from_jid):
    229          """Set "from" attribute of the stanza.
    230
    231          :Parameters:
    232              - `from_jid`: new value of the "from" attribute (sender JID).
    233          :Types:
    234              - `from_jid`: `JID`"""
    235          if from_jid:
    236              return self.xmlnode.setProp("from", JID(from_jid).as_utf8())
    237          else:
    238              return self.xmlnode.unsetProp("from")
    239
    240 -    def set_to(self,to_jid):
    241          """Set "to" attribute of the stanza.
    242
    243          :Parameters:
    244              - `to_jid`: new value of the "to" attribute (recipient JID).
    245          :Types:
    246              - `to_jid`: `JID`"""
    247          if to_jid:
    248              return self.xmlnode.setProp("to", JID(to_jid).as_utf8())
    249          else:
    250              return self.xmlnode.unsetProp("to")
    251
    252 -    def set_type(self,stanza_type):
    253          """Set "type" attribute of the stanza.
    254
    255          :Parameters:
    256              - `stanza_type`: new value of the "type" attribute (stanza type).
    257          :Types:
    258              - `stanza_type`: `unicode`"""
    259          if stanza_type:
    260              return self.xmlnode.setProp("type",to_utf8(stanza_type))
    261          else:
    262              return self.xmlnode.unsetProp("type")
    263
    264 -    def set_id(self,stanza_id):
    265          """Set "id" attribute of the stanza.
    266
    267          :Parameters:
    268              - `stanza_id`: new value of the "id" attribute (stanza identifier).
    269          :Types:
    270              - `stanza_id`: `unicode`"""
    271          if stanza_id:
    272              return self.xmlnode.setProp("id",to_utf8(stanza_id))
    273          else:
    274              return self.xmlnode.unsetProp("id")
    275
    276 -    def set_content(self,content):
    277          """Set stanza content to an XML node.
    278
    279          :Parameters:
    280              - `content`: XML node to be included in the stanza.
    281          :Types:
    282              - `content`: `libxml2.xmlNode` or UTF-8 `str`
    283          """
    284          while self.xmlnode.children:
    285              self.xmlnode.children.unlinkNode()
    286          if hasattr(content,"as_xml"):
    287              content.as_xml(parent=self.xmlnode,doc=common_doc)
    288          elif isinstance(content,libxml2.xmlNode):
    289              self.xmlnode.addChild(content.docCopyNode(common_doc,1))
    290          else:
    291              self.xmlnode.setContent(content)
    292
    293 -    def add_content(self,content):
    294          """Add an XML node to the stanza's payload.
    295
    296          :Parameters:
    297              - `content`: XML node to be added to the payload.
    298          :Types:
    299              - `content`: `libxml2.xmlNode`, UTF-8 `str` or an object with
    300                "as_xml()" method.
    301          """
    302          if hasattr(content, "as_xml"):
    303              content.as_xml(parent = self.xmlnode, doc = common_doc)
    304          elif isinstance(content,libxml2.xmlNode):
    305              self.xmlnode.addChild(content.docCopyNode(common_doc,1))
    306          else:
    307              self.xmlnode.addContent(content)
    308
    309 -    def set_new_content(self,ns_uri,name):
    310          """Set stanza payload to a new XML element.
    311
    312          :Parameters:
    313              - `ns_uri`: XML namespace URI of the element.
    314              - `name`: element name.
    315          :Types:
    316              - `ns_uri`: `str`
    317              - `name`: `str` or `unicode`
    318          """
    319          while self.xmlnode.children:
    320              self.xmlnode.children.unlinkNode()
    321          return self.add_new_content(ns_uri,name)
    322
    323 -    def add_new_content(self,ns_uri,name):
    324          """Add a new XML element to the stanza payload.
    325
    326          :Parameters:
    327              - `ns_uri`: XML namespace URI of the element.
    328              - `name`: element name.
    329          :Types:
    330              - `ns_uri`: `str`
    331              - `name`: `str` or `unicode`
    332          """
    333          c=self.xmlnode.newChild(None,to_utf8(name),None)
    334          if ns_uri:
    335              ns=c.newNs(ns_uri,None)
    336              c.setNs(ns)
    337          return c
    338
    339 -    def xpath_eval(self,expr,namespaces=None):
    340          """Evaluate an XPath expression on the stanza XML node.
    341
    342          The expression will be evaluated in context where the common namespace
    343          (the one used for stanza elements, mapped to 'jabber:client',
    344          'jabber:server', etc.) is bound to prefix "ns" and other namespaces are
    345          bound accordingly to the `namespaces` list.
    346
    347          :Parameters:
    348              - `expr`: XPath expression.
    349              - `namespaces`: mapping from namespace prefixes to URIs.
    350          :Types:
    351              - `expr`: `unicode`
    352              - `namespaces`: `dict` or other mapping
    353          """
    354          ctxt = common_doc.xpathNewContext()
    355          ctxt.setContextNode(self.xmlnode)
    356          ctxt.xpathRegisterNs("ns",COMMON_NS)
    357          if namespaces:
    358              for prefix,uri in namespaces.items():
    359                  ctxt.xpathRegisterNs(unicode(prefix),uri)
    360          ret=ctxt.xpathEval(unicode(expr))
    361          ctxt.xpathFreeContext()
    362          return ret
    363
    364 -    def __eq__(self,other):
    365          if not isinstance(other,Stanza):
    366              return False
    367          return self.xmlnode.serialize()==other.xmlnode.serialize()
    368
    369 -    def __ne__(self,other):
    370          if not isinstance(other,Stanza):
    371              return True
    372          return self.xmlnode.serialize()!=other.xmlnode.serialize()
    373
    374  # vi: sts=4 et sw=4
    375