1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18  """Jabber Service Discovery support. 
 19   
 20  Normative reference: 
 21    - `JEP 30 <http://www.jabber.org/jeps/jep-0030.html>`__ 
 22  """ 
 23   
 24  __revision__="$Id: disco.py 666 2006-12-03 15:40:54Z jajcus $" 
 25  __docformat__="restructuredtext en" 
 26   
 27  import warnings 
 28   
 29  import libxml2 
 30   
 31  from pyxmpp.xmlextra import common_doc,common_root 
 32  from pyxmpp.jid import JID 
 33  from pyxmpp import cache 
 34   
 35  from pyxmpp.utils import to_utf8 
 36  from pyxmpp.objects import StanzaPayloadWrapperObject 
 37  from pyxmpp.exceptions import ProtocolError 
 38   
 39  DISCO_NS="http://jabber.org/protocol/disco" 
 40  DISCO_ITEMS_NS=DISCO_NS+"#items" 
 41  DISCO_INFO_NS=DISCO_NS+"#info" 
 42   
 44      """An item of disco#items reply. 
 45   
 46      :Ivariables: 
 47          - `jid`: the JID of the item. 
 48          - `node`: node name of the item. 
 49          - `name`: name of the item. 
 50          - `action`: action of the item. 
 51          - `disco`: the disco reply this is the part of. 
 52          - `xmlnode`: XML element describing the item. 
 53      :Types: 
 54          - `jid`: `JID` 
 55          - `node`: `unicode` 
 56          - `name`: `unicode` 
 57          - `action`: `unicode` 
 58          - `disco`: `DiscoItems` 
 59          - `xmlnode`: `libxml2.xmlNode` 
 60      """ 
 61 -    def __init__(self,disco,xmlnode_or_jid,node=None,name=None,action=None): 
  62          """Initialize an `DiscoItem` object. 
 63   
 64          :Parameters: 
 65              - `disco`: the disco#items reply `self` is a part of. 
 66              - `xmlnode_or_jid`: XML element describing the item or the JID of 
 67                the item. 
 68              - `node`: disco node of the item. 
 69              - `name`: name of the item. 
 70              - `action`: 'action' attribute of the item. 
 71          :Types: 
 72              - `disco`: `DiscoItems` 
 73              - `xmlnode_or_jid`: `libxml2.xmlNode` or `JID` 
 74              - `node`: `unicode` 
 75              - `name`: `unicode` 
 76              - `action`: `unicode` 
 77          """ 
 78          self.disco=disco 
 79          if isinstance(xmlnode_or_jid,JID): 
 80              if disco: 
 81                  self.xmlnode=disco.xmlnode.newChild(None,"item",None) 
 82              else: 
 83                  self.xmlnode=common_root.newChild(None,"item",None) 
 84                  ns=self.xmlnode.newNs(DISCO_ITEMS_NS,None) 
 85                  self.xmlnode.setNs(ns) 
 86              self.set_jid(xmlnode_or_jid) 
 87              self.set_name(name) 
 88              self.set_node(node) 
 89              self.set_action(action) 
 90          else: 
 91              if disco is None: 
 92                  self.xmlnode=xmlnode_or_jid.copyNode(1) 
 93              else: 
 94                  self.xmlnode=xmlnode_or_jid 
 95              if name: 
 96                  self.set_name(name) 
 97              if node: 
 98                  self.set_node(node) 
 99              if action: 
100                  self.set_action(action) 
101          self.xpath_ctxt=common_doc.xpathNewContext() 
102          self.xpath_ctxt.setContextNode(self.xmlnode) 
103          self.xpath_ctxt.xpathRegisterNs("d",DISCO_ITEMS_NS) 
 104   
106          if self.disco is None: 
107              if self.xmlnode: 
108                  self.xmlnode.unlinkNode() 
109                  self.xmlnode.freeNode() 
110                  self.xmlnode=None 
111          if self.xpath_ctxt: 
112              self.xpath_ctxt.xpathFreeContext() 
 113   
116   
118          """Remove `self` from the containing `DiscoItems` object.""" 
119          if self.disco is None: 
120              return 
121          self.xmlnode.unlinkNode() 
122          oldns=self.xmlnode.ns() 
123          ns=self.xmlnode.newNs(oldns.getContent(),None) 
124          self.xmlnode.replaceNs(oldns,ns) 
125          common_root.addChild(self.xmlnode()) 
126          self.disco=None 
 127   
129          """Get the name of the item. 
130   
131          :return: the name of the item or `None`. 
132          :returntype: `unicode`""" 
133          name = self.xmlnode.prop("name") 
134          if name is None: 
135              return None 
136          return name.decode("utf-8") 
 137   
139          """Set the name of the item. 
140   
141          :Parameters: 
142              - `name`: the new name or `None`. 
143          :Types: 
144              - `name`: `unicode` """ 
145          if name is None: 
146              if self.xmlnode.hasProp("name"): 
147                  self.xmlnode.unsetProp("name") 
148              return 
149          name = unicode(name) 
150          self.xmlnode.setProp("name", name.encode("utf-8")) 
 151   
152      name = property(get_name, set_name) 
153   
155          """Get the node of the item. 
156   
157          :return: the node of the item or `None`. 
158          :returntype: `unicode`""" 
159          node = self.xmlnode.prop("node") 
160          if node is None: 
161              return None 
162          return node.decode("utf-8") 
 163   
165          """Set the node of the item. 
166   
167          :Parameters: 
168              - `node`: the new node or `None`. 
169          :Types: 
170              - `node`: `unicode` 
171          """ 
172          if node is None: 
173              if self.xmlnode.hasProp("node"): 
174                  self.xmlnode.unsetProp("node") 
175              return 
176          node = unicode(node) 
177          self.xmlnode.setProp("node", node.encode("utf-8")) 
 178   
179      node = property(get_node, set_node) 
180   
182          """Get the action attribute of the item. 
183   
184          :return: the action of the item or `None`. 
185          :returntype: `unicode`""" 
186          action=self.xmlnode.prop("action") 
187          if action is None: 
188              return None 
189          return action.decode("utf-8") 
 190   
192          """Set the action of the item. 
193   
194          :Parameters: 
195              - `action`: the new action or `None`. 
196          :Types: 
197              - `action`: `unicode` 
198          """ 
199          if action is None: 
200              if self.xmlnode.hasProp("action"): 
201                  self.xmlnode.unsetProp("action") 
202              return 
203          if action not in ("remove","update"): 
204              raise ValueError, "Action must be 'update' or 'remove'" 
205          action = unicode(action) 
206          self.xmlnode.setProp("action", action.encode("utf-8")) 
 207   
208      action = property(get_action, set_action) 
209   
211          """Get the JID of the item. 
212   
213          :return: the JID of the item. 
214          :returntype: `JID`""" 
215          jid = self.xmlnode.prop("jid") 
216          return JID( jid.decode("utf-8") ) 
 217   
219          """Set the JID of the item. 
220   
221          :Parameters: 
222              - `jid`: the new jid. 
223          :Types: 
224              - `jid`: `JID` 
225          """ 
226          self.xmlnode.setProp("jid", jid.as_unicode().encode("utf-8")) 
 227   
228      jid = property(get_jid, set_jid) 
 229   
231      """An <identity/> element of disco#info reply. 
232   
233      Identifies an item by its name, category and type. 
234   
235      :Ivariables: 
236          - `disco`: the disco reply this is the part of. 
237          - `xmlnode`: XML element describing the identity. 
238      :Types: 
239          - `disco`: `DiscoInfo` 
240          - `xmlnode`: `libxml2.xmlNode` 
241      """ 
242 -    def __init__(self, disco, xmlnode_or_name, item_category=None, item_type=None, replace=False): 
 243          """Initialize an `DiscoIdentity` object. 
244   
245          :Parameters: 
246              - `disco`: the disco#info reply `self` is a part of. 
247              - `xmlnode_or_name`: XML element describing the identity or the 
248                name of the item described. 
249              - `item_category`: category of the item described. 
250              - `item_type`: type of the item described. 
251              - `replace`: if `True` than all other <identity/> elements in 
252                `disco` will be removed. 
253          :Types: 
254              - `disco`: `DiscoItems` 
255              - `xmlnode_or_name`: `libxml2.xmlNode` or `unicode` 
256              - `item_category`: `unicode` 
257              - `item_type`: `unicode` 
258              - `replace`: `bool` 
259          """ 
260          self.disco=disco 
261          if disco and replace: 
262              old=disco.xpath_ctxt.xpathEval("d:identity") 
263              if old: 
264                  for n in old: 
265                      n.unlinkNode() 
266                      n.freeNode() 
267          if isinstance(xmlnode_or_name,libxml2.xmlNode): 
268              if disco is None: 
269                  self.xmlnode=xmlnode_or_name.copyNode(1) 
270              else: 
271                  self.xmlnode=xmlnode_or_name 
272          elif not item_category: 
273              raise ValueError,"DiscoInfo requires category" 
274          elif not item_type: 
275              raise ValueError,"DiscoInfo requires type" 
276          else: 
277              if disco: 
278                  self.xmlnode=disco.xmlnode.newChild(None,"identity",None) 
279              else: 
280                  self.xmlnode=common_root.newChild(None,"identity",None) 
281                  ns=self.xmlnode.newNs(DISCO_INFO_NS,None) 
282                  self.xmlnode.setNs(ns) 
283              self.set_name(xmlnode_or_name) 
284              self.set_category(item_category) 
285              self.set_type(item_type) 
286          self.xpath_ctxt=common_doc.xpathNewContext() 
287          self.xpath_ctxt.setContextNode(self.xmlnode) 
288          self.xpath_ctxt.xpathRegisterNs("d",DISCO_INFO_NS) 
 289   
291          if self.disco is None: 
292              if self.xmlnode: 
293                  self.xmlnode.unlinkNode() 
294                  self.xmlnode.freeNode() 
295                  self.xmlnode=None 
296          if self.xpath_ctxt: 
297              self.xpath_ctxt.xpathFreeContext() 
 298   
301   
303          """Remove `self` from the containing `DiscoInfo` object.""" 
304          if self.disco is None: 
305              return 
306          self.xmlnode.unlinkNode() 
307          oldns=self.xmlnode.ns() 
308          ns=self.xmlnode.newNs(oldns.getContent(),None) 
309          self.xmlnode.replaceNs(oldns,ns) 
310          common_root.addChild(self.xmlnode()) 
311          self.disco=None 
 312   
314          """Get the name of the item. 
315   
316          :return: the name of the item or `None`. 
317          :returntype: `unicode`""" 
318          var = self.xmlnode.prop("name") 
319          if not var: 
320              var = "" 
321          return var.decode("utf-8") 
 322   
324          """Set the name of the item. 
325   
326          :Parameters: 
327              - `name`: the new name or `None`. 
328          :Types: 
329              - `name`: `unicode` """ 
330          if not name: 
331              raise ValueError, "name is required in DiscoIdentity" 
332          name = unicode(name) 
333          self.xmlnode.setProp("name", name.encode("utf-8")) 
 334   
335      name = property(get_name, set_name) 
336   
338          """Get the category of the item. 
339   
340          :return: the category of the item. 
341          :returntype: `unicode`""" 
342          var = self.xmlnode.prop("category") 
343          if not var: 
344              var = "?" 
345          return var.decode("utf-8") 
 346   
348          """Set the category of the item. 
349   
350          :Parameters: 
351              - `category`: the new category. 
352          :Types: 
353              - `category`: `unicode` """ 
354          if not category: 
355              raise ValueError, "Category is required in DiscoIdentity" 
356          category = unicode(category) 
357          self.xmlnode.setProp("category", category.encode("utf-8")) 
 358   
359      category = property(get_category, set_category) 
360   
362          """Get the type of the item. 
363   
364          :return: the type of the item. 
365          :returntype: `unicode`""" 
366          item_type = self.xmlnode.prop("type") 
367          if not item_type: 
368              item_type = "?" 
369          return item_type.decode("utf-8") 
 370   
372          """Set the type of the item. 
373   
374          :Parameters: 
375              - `item_type`: the new type. 
376          :Types: 
377              - `item_type`: `unicode` """ 
378          if not item_type: 
379              raise ValueError,"Type is required in DiscoIdentity" 
380          item_type = unicode(item_type) 
381          self.xmlnode.setProp("type", item_type.encode("utf-8")) 
 382   
383      type = property(get_type, set_type) 
 384   
386      """A disco#items response or publish-request object. 
387   
388      :Ivariables: 
389          - `node`: node name of the disco#items element. 
390          - `items`: items in the disco#items element. 
391          - `xmlnode`: XML element listing the items. 
392      :Types: 
393          - `node`: `unicode` 
394          - `items`: `tuple` of `DiscoItem` 
395          - `xmlnode`: `libxml2.xmlNode` 
396      """ 
397 -    def __init__(self,xmlnode_or_node=None): 
 398          """Initialize an `DiscoItems` object. 
399   
400          Wrap an existing disco#items XML element or create a new one. 
401   
402          :Parameters: 
403              - `xmlnode_or_node`: XML node to be wrapped into `self` or an item 
404                node name. 
405          :Types: 
406              - `xmlnode_or_node`: `libxml2.xmlNode` or `unicode`""" 
407          self.xmlnode=None 
408          self.xpath_ctxt=None 
409          if isinstance(xmlnode_or_node,libxml2.xmlNode): 
410              ns=xmlnode_or_node.ns() 
411              if ns.getContent() != DISCO_ITEMS_NS: 
412                  raise ValueError, "Bad disco-items namespace" 
413              self.xmlnode=xmlnode_or_node.docCopyNode(common_doc,1) 
414              common_root.addChild(self.xmlnode) 
415              self.ns=self.xmlnode.ns() 
416          else: 
417              self.xmlnode=common_root.newChild(None,"query",None) 
418              self.ns=self.xmlnode.newNs(DISCO_ITEMS_NS,None) 
419              self.xmlnode.setNs(self.ns) 
420              self.set_node(xmlnode_or_node) 
421          self.xpath_ctxt=common_doc.xpathNewContext() 
422          self.xpath_ctxt.setContextNode(self.xmlnode) 
423          self.xpath_ctxt.xpathRegisterNs("d",DISCO_ITEMS_NS) 
 424   
426          if self.xmlnode: 
427              self.xmlnode.unlinkNode() 
428              self.xmlnode.freeNode() 
429              self.xmlnode=None 
430          if self.xpath_ctxt: 
431              self.xpath_ctxt.xpathFreeContext() 
432              self.xpath_ctxt=None 
 433   
435          """Get the node address of the `DiscoItems` object. 
436   
437          :return: the node name. 
438          :returntype: `unicode`""" 
439          node = self.xmlnode.prop("node") 
440          if not node: 
441              return None 
442          return node.decode("utf-8") 
 443   
445          """Set the node of the disco#item element. 
446   
447          :Parameters: 
448              - `node`: the new node or `None`. 
449          :Types: 
450              - `node`: `unicode` 
451          """ 
452          if node is None: 
453              if self.xmlnode.hasProp("node"): 
454                  self.xmlnode.unsetProp("node") 
455              return 
456          node = unicode(node) 
457          self.xmlnode.setProp("node", node.encode("utf-8")) 
 458   
459      node = property(get_node, set_node) 
460   
462          """Get the items contained in `self`. 
463   
464          :return: the items contained. 
465          :returntype: `list` of `DiscoItem`""" 
466          ret=[] 
467          l=self.xpath_ctxt.xpathEval("d:item") 
468          if l is not None: 
469              for i in l: 
470                  ret.append(DiscoItem(self, i)) 
471          return ret 
 472   
474          """Set items in the disco#items object. 
475   
476          All previous items are removed. 
477   
478          :Parameters: 
479              - `item_list`: list of items or item properties 
480                (jid,node,name,action). 
481          :Types: 
482              - `item_list`: sequence of `DiscoItem` or sequence of sequences 
483          """ 
484          for item in self.items: 
485              item.remove() 
486          for item in item_list: 
487              try: 
488                  self.add_item(item.jid,item.node,item.name,item.action) 
489              except AttributeError: 
490                  self.add_item(*item) 
 491   
492      items = property(get_items, set_items, doc = "List of `DiscoItems`") 
493   
495          """Clear cached item list.""" 
496          warnings.warn("DiscoItems.invalidate_items() is deprecated and not needed any more.", DeprecationWarning, stacklevel=1) 
 497   
498 -    def add_item(self,jid,node=None,name=None,action=None): 
 499          """Add a new item to the `DiscoItems` object. 
500   
501          :Parameters: 
502              - `jid`: item JID. 
503              - `node`: item node name. 
504              - `name`: item name. 
505              - `action`: action for a "disco push". 
506          :Types: 
507              - `jid`: `pyxmpp.JID` 
508              - `node`: `unicode` 
509              - `name`: `unicode` 
510              - `action`: `unicode` 
511   
512          :returns: the item created. 
513          :returntype: `DiscoItem`.""" 
514          return DiscoItem(self,jid,node,name,action) 
 515   
517          """Check if `self` contains an item. 
518   
519          :Parameters: 
520              - `jid`: JID of the item. 
521              - `node`: node name of the item. 
522          :Types: 
523              - `jid`: `JID` 
524              - `node`: `libxml2.xmlNode` 
525   
526          :return: `True` if the item is found in `self`. 
527          :returntype: `bool`""" 
528          l=self.xpath_ctxt.xpathEval("d:item") 
529          if l is None: 
530              return False 
531          for it in l: 
532              di=DiscoItem(self,it) 
533              if di.jid==jid and di.node==node: 
534                  return True 
535          return False 
  536   
538      """A disco#info response object. 
539   
540      :Ivariables: 
541          - `node`: node name of the disco#info element (cached). 
542          - `identities`: identities in the disco#info object. 
543          - `features`: features in the disco#info object. 
544          - `xmlnode`: XML element listing the items. 
545      :Types: 
546          - `node`: `unicode` 
547          - `identities`: `tuple` of `DiscoIdentity` 
548          - `features`: `tuple` of `unicode` 
549          - `xmlnode`: `libxml2.xmlNode` 
550      """ 
551 -    def __init__(self,xmlnode_or_node=None, parent=None, doc=None): 
 552          """Initialize an `DiscoInfo` object. 
553   
554          Wrap an existing disco#info XML element or create a new one. 
555   
556          :Parameters: 
557              - `xmlnode_or_node`: XML node to be wrapped into `self` or an item 
558                node name. 
559              - `parent`: parent node for the `DiscoInfo` element. 
560              - `doc`: document for the `DiscoInfo` element. 
561          :Types: 
562              - `xmlnode_or_node`: `libxml2.xmlNode` or `unicode` 
563              - `parent`: `libxml2.xmlNode` 
564              - `doc`: `libxml2.xmlDoc` 
565              """ 
566          self.xmlnode=None 
567          self.xpath_ctxt=None 
568          if not doc: 
569              doc=common_doc 
570          if not parent: 
571              parent=common_root 
572          if isinstance(xmlnode_or_node,libxml2.xmlNode): 
573              ns=xmlnode_or_node.ns() 
574              if ns.getContent() != DISCO_INFO_NS: 
575                  raise ValueError, "Bad disco-info namespace" 
576              self.xmlnode=xmlnode_or_node.docCopyNode(doc,1) 
577              parent.addChild(self.xmlnode) 
578          else: 
579              self.xmlnode=parent.newChild(None,"query",None) 
580              self.ns=self.xmlnode.newNs(DISCO_INFO_NS,None) 
581              self.xmlnode.setNs(self.ns) 
582              self.set_node(xmlnode_or_node) 
583   
584          self.xpath_ctxt=doc.xpathNewContext() 
585          self.xpath_ctxt.setContextNode(self.xmlnode) 
586          self.xpath_ctxt.xpathRegisterNs("d",DISCO_INFO_NS) 
 587   
589          if self.xmlnode: 
590              self.xmlnode.unlinkNode() 
591              self.xmlnode.freeNode() 
592              self.xmlnode=None 
593          if self.xpath_ctxt: 
594              self.xpath_ctxt.xpathFreeContext() 
595              self.xpath_ctxt=None 
 596   
598          """Get the node address of the `DiscoInfo` object. 
599   
600          :return: the node name. 
601          :returntype: `unicode`""" 
602   
603          node=self.xmlnode.prop("node") 
604          if not node: 
605              return None 
606          return node.decode("utf-8") 
 607   
609          """Set the node of the disco#info element. 
610   
611          :Parameters: 
612              - `node`: the new node or `None`. 
613          :Types: 
614              - `node`: `unicode` 
615          """ 
616          if node is None: 
617              if self.xmlnode.hasProp("node"): 
618                  self.xmlnode.unsetProp("node") 
619              return 
620          node = unicode(node) 
621          self.xmlnode.setProp("node", node.encode("utf-8")) 
 622   
623      node = property(get_node, set_node) 
624   
626          """Get the features contained in `self`. 
627   
628          :return: the list of features. 
629          :returntype: `list` of `unicode`""" 
630          l = self.xpath_ctxt.xpathEval("d:feature") 
631          ret = [] 
632          for f in l: 
633              if f.hasProp("var"): 
634                  ret.append( f.prop("var").decode("utf-8") ) 
635          return ret 
 636   
638          """Set features in the disco#info object. 
639   
640          All existing features are removed from `self`. 
641   
642          :Parameters: 
643              - `features`: list of features. 
644          :Types: 
645              - `features`: sequence of `unicode` 
646          """ 
647          for var in self.features: 
648              self.remove_feature(var) 
649   
650          for var in features: 
651              self.add_feature(var) 
 652   
653      features = property(get_features, set_features) 
654   
656          """Check if `self` contains the named feature. 
657   
658          :Parameters: 
659              - `var`: the feature name. 
660          :Types: 
661              - `var`: `unicode` 
662   
663          :return: `True` if the feature is found in `self`. 
664          :returntype: `bool`""" 
665          if not var: 
666              raise ValueError,"var is None" 
667          if '"' not in var: 
668              expr=u'd:feature[@var="%s"]' % (var,) 
669          elif "'" not in var: 
670              expr=u"d:feature[@var='%s']" % (var,) 
671          else: 
672              raise ValueError, "Invalid feature name" 
673   
674          l=self.xpath_ctxt.xpathEval(to_utf8(expr)) 
675          if l: 
676              return True 
677          else: 
678              return False 
 679   
681          """Clear cached feature list.""" 
682          warnings.warn("DiscoInfo.invalidate_features() is deprecated and not needed any more.", DeprecationWarning, stacklevel=1) 
 683   
685          """Add a feature to `self`. 
686   
687          :Parameters: 
688              - `var`: the feature name. 
689          :Types: 
690              - `var`: `unicode`""" 
691          if self.has_feature(var): 
692              return 
693          n=self.xmlnode.newChild(None, "feature", None) 
694          n.setProp("var", to_utf8(var)) 
 695   
697          """Remove a feature from `self`. 
698   
699          :Parameters: 
700              - `var`: the feature name. 
701          :Types: 
702              - `var`: `unicode`""" 
703          if not var: 
704              raise ValueError,"var is None" 
705          if '"' not in var: 
706              expr='d:feature[@var="%s"]' % (var,) 
707          elif "'" not in var: 
708              expr="d:feature[@var='%s']" % (var,) 
709          else: 
710              raise ValueError, "Invalid feature name" 
711   
712          l=self.xpath_ctxt.xpathEval(expr) 
713          if not l: 
714              return 
715   
716          for f in l: 
717              f.unlinkNode() 
718              f.freeNode() 
 719   
721          """List the identity objects contained in `self`. 
722   
723          :return: the list of identities. 
724          :returntype: `list` of `DiscoIdentity`""" 
725          ret=[] 
726          l=self.xpath_ctxt.xpathEval("d:identity") 
727          if l is not None: 
728              for i in l: 
729                  ret.append(DiscoIdentity(self,i)) 
730          return ret 
 731   
733          """Set identities in the disco#info object. 
734   
735          Remove all existing identities from `self`. 
736   
737          :Parameters: 
738              - `identities`: list of identities or identity properties 
739                (jid,node,category,type,name). 
740          :Types: 
741              - `identities`: sequence of `DiscoIdentity` or sequence of sequences 
742          """ 
743          for identity in self.identities: 
744              identity.remove() 
745          for identity in identities: 
746              try: 
747                  self.add_identity(identity.name,identity.category,identity.type) 
748              except AttributeError: 
749                  self.add_identity(*identity) 
 750   
751      identities = property(get_identities, set_identities) 
752   
754          """Check if the item described by `self` belongs to the given category 
755          and type. 
756   
757          :Parameters: 
758              - `item_category`: the category name. 
759              - `item_type`: the type name. If `None` then only the category is 
760                checked. 
761          :Types: 
762              - `item_category`: unicode 
763              - `item_type`: unicode 
764   
765          :return: `True` if `self` contains at least one <identity/> object with 
766              given type and category. 
767          :returntype: `bool`""" 
768          if not item_category: 
769              raise ValueError, "bad category" 
770          if not item_type: 
771              type_expr=u"" 
772          elif '"' not in item_type: 
773              type_expr=u' and @type="%s"' % (item_type,) 
774          elif "'" not in type: 
775              type_expr=u" and @type='%s'" % (item_type,) 
776          else: 
777              raise ValueError, "Invalid type name" 
778          if '"' not in item_category: 
779              expr=u'd:identity[@category="%s"%s]' % (item_category,type_expr) 
780          elif "'" not in item_category: 
781              expr=u"d:identity[@category='%s'%s]" % (item_category,type_expr) 
782          else: 
783              raise ValueError, "Invalid category name" 
784   
785          l=self.xpath_ctxt.xpathEval(to_utf8(expr)) 
786          if l: 
787              return True 
788          else: 
789              return False 
 790   
792          """Clear cached identity list.""" 
793          warnings.warn("DiscoInfo.invalidate_identities() is deprecated and not needed any more.", DeprecationWarning, stacklevel=1) 
 794   
795 -    def add_identity(self,item_name,item_category=None,item_type=None): 
 796          """Add an identity to the `DiscoInfo` object. 
797   
798          :Parameters: 
799              - `item_name`: name of the item. 
800              - `item_category`: category of the item. 
801              - `item_type`: type of the item. 
802          :Types: 
803              - `item_name`: `unicode` 
804              - `item_category`: `unicode` 
805              - `item_type`: `unicode` 
806   
807          :returns: the identity created. 
808          :returntype: `DiscoIdentity`""" 
809          return DiscoIdentity(self,item_name,item_category,item_type) 
  810   
812      """Base class for disco cache fetchers. 
813   
814      :Cvariables: 
815          - `stream`: stream used by the fetcher. 
816          - `disco_class`: disco class to be used (`DiscoInfo` or `DiscoItems`). 
817      :Types: 
818          - `stream`: `pyxmpp.stream.Stream` 
819          - `disco_class`: `classobj` 
820      """ 
821      stream=None 
822      disco_class=None 
833   
835          """Handle successful disco response. 
836   
837          :Parameters: 
838              - `stanza`: the stanza received. 
839          :Types: 
840              - `stanza`: `pyxmpp.stanza.Stanza`""" 
841          try: 
842              d=self.disco_class(stanza.get_query()) 
843              self.got_it(d) 
844          except ValueError,e: 
845              self.error(e) 
 846   
859   
861          """Handle disco timeout.""" 
862          pass 
  863   
865      """Register Service Discovery cache fetchers into given 
866      cache suite and using the stream provided. 
867   
868      :Parameters: 
869          - `cache_suite`: the cache suite where the fetchers are to be 
870            registered. 
871          - `stream`: the stream to be used by the fetchers. 
872      :Types: 
873          - `cache_suite`: `cache.CacheSuite` 
874          - `stream`: `pyxmpp.stream.Stream` 
875      """ 
876      tmp=stream 
877      class DiscoInfoCacheFetcher(DiscoCacheFetcherBase): 
878          """Cache fetcher for DiscoInfo.""" 
879          stream=tmp 
880          disco_class=DiscoInfo 
 881      class DiscoItemsCacheFetcher(DiscoCacheFetcherBase): 
882          """Cache fetcher for DiscoItems.""" 
883          stream=tmp 
884          disco_class=DiscoItems 
885      cache_suite.register_fetcher(DiscoInfo,DiscoInfoCacheFetcher) 
886      cache_suite.register_fetcher(DiscoItems,DiscoItemsCacheFetcher) 
887   
888   
889