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