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  __revision__="$Id: stanza.py 714 2010-04-05 10:20:10Z 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 unicode, 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 elif isinstance(content,unicode): 291 self.xmlnode.setContent(to_utf8(content)) 292 else: 293 self.xmlnode.setContent(content)
294
295 - def add_content(self,content):
296 """Add an XML node to the stanza's payload. 297 298 :Parameters: 299 - `content`: XML node to be added to the payload. 300 :Types: 301 - `content`: `libxml2.xmlNode`, UTF-8 `str` or unicode, or 302 an object with "as_xml()" method. 303 """ 304 if hasattr(content, "as_xml"): 305 content.as_xml(parent = self.xmlnode, doc = common_doc) 306 elif isinstance(content,libxml2.xmlNode): 307 self.xmlnode.addChild(content.docCopyNode(common_doc,1)) 308 elif isinstance(content,unicode): 309 self.xmlnode.addContent(to_utf8(content)) 310 else: 311 self.xmlnode.addContent(content)
312
313 - def set_new_content(self,ns_uri,name):
314 """Set stanza payload to a new XML element. 315 316 :Parameters: 317 - `ns_uri`: XML namespace URI of the element. 318 - `name`: element name. 319 :Types: 320 - `ns_uri`: `str` 321 - `name`: `str` or `unicode` 322 """ 323 while self.xmlnode.children: 324 self.xmlnode.children.unlinkNode() 325 return self.add_new_content(ns_uri,name)
326
327 - def add_new_content(self,ns_uri,name):
328 """Add a new XML element to the stanza payload. 329 330 :Parameters: 331 - `ns_uri`: XML namespace URI of the element. 332 - `name`: element name. 333 :Types: 334 - `ns_uri`: `str` 335 - `name`: `str` or `unicode` 336 """ 337 c=self.xmlnode.newChild(None,to_utf8(name),None) 338 if ns_uri: 339 ns=c.newNs(ns_uri,None) 340 c.setNs(ns) 341 return c
342
343 - def xpath_eval(self,expr,namespaces=None):
344 """Evaluate an XPath expression on the stanza XML node. 345 346 The expression will be evaluated in context where the common namespace 347 (the one used for stanza elements, mapped to 'jabber:client', 348 'jabber:server', etc.) is bound to prefix "ns" and other namespaces are 349 bound accordingly to the `namespaces` list. 350 351 :Parameters: 352 - `expr`: XPath expression. 353 - `namespaces`: mapping from namespace prefixes to URIs. 354 :Types: 355 - `expr`: `unicode` 356 - `namespaces`: `dict` or other mapping 357 """ 358 ctxt = common_doc.xpathNewContext() 359 ctxt.setContextNode(self.xmlnode) 360 ctxt.xpathRegisterNs("ns",COMMON_NS) 361 if namespaces: 362 for prefix,uri in namespaces.items(): 363 ctxt.xpathRegisterNs(unicode(prefix),uri) 364 ret=ctxt.xpathEval(unicode(expr)) 365 ctxt.xpathFreeContext() 366 return ret
367
368 - def __eq__(self,other):
369 if not isinstance(other,Stanza): 370 return False 371 return self.xmlnode.serialize()==other.xmlnode.serialize()
372
373 - def __ne__(self,other):
374 if not isinstance(other,Stanza): 375 return True 376 return self.xmlnode.serialize()!=other.xmlnode.serialize()
377 378 # vi: sts=4 et sw=4 379