Package pyxmpp :: Module stanza |
[hide private]

# Source Code for Module pyxmpp.stanza

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