stanzabase.py 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654
  1. # -*- coding: utf-8 -*-
  2. """
  3. sleekxmpp.xmlstream.stanzabase
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  5. module implements a wrapper layer for XML objects
  6. that allows them to be treated like dictionaries.
  7. Part of SleekXMPP: The Sleek XMPP Library
  8. :copyright: (c) 2011 Nathanael C. Fritz
  9. :license: MIT, see LICENSE for more details
  10. """
  11. from __future__ import with_statement, unicode_literals
  12. import copy
  13. import logging
  14. import weakref
  15. from xml.etree import cElementTree as ET
  16. from sleekxmpp.util import safedict
  17. from sleekxmpp.xmlstream import JID
  18. from sleekxmpp.xmlstream.tostring import tostring
  19. from sleekxmpp.thirdparty import OrderedDict
  20. log = logging.getLogger(__name__)
  21. # Used to check if an argument is an XML object.
  22. XML_TYPE = type(ET.Element('xml'))
  23. XML_NS = 'http://www.w3.org/XML/1998/namespace'
  24. def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
  25. """
  26. Associate a stanza object as a plugin for another stanza.
  27. >>> from sleekxmpp.xmlstream import register_stanza_plugin
  28. >>> register_stanza_plugin(Iq, CustomStanza)
  29. Plugin stanzas marked as iterable will be included in the list of
  30. substanzas for the parent, using ``parent['substanzas']``. If the
  31. attribute ``plugin_multi_attrib`` was defined for the plugin, then
  32. the substanza set can be filtered to only instances of the plugin
  33. class. For example, given a plugin class ``Foo`` with
  34. ``plugin_multi_attrib = 'foos'`` then::
  35. parent['foos']
  36. would return a collection of all ``Foo`` substanzas.
  37. :param class stanza: The class of the parent stanza.
  38. :param class plugin: The class of the plugin stanza.
  39. :param bool iterable: Indicates if the plugin stanza should be
  40. included in the parent stanza's iterable
  41. ``'substanzas'`` interface results.
  42. :param bool overrides: Indicates if the plugin should be allowed
  43. to override the interface handlers for
  44. the parent stanza, based on the plugin's
  45. ``overrides`` field.
  46. .. versionadded:: 1.0-Beta1
  47. Made ``register_stanza_plugin`` the default name. The prior
  48. ``registerStanzaPlugin`` function name remains as an alias.
  49. """
  50. tag = "{%s}%s" % (plugin.namespace, plugin.name)
  51. # Prevent weird memory reference gotchas by ensuring
  52. # that the parent stanza class has its own set of
  53. # plugin info maps and is not using the mappings from
  54. # an ancestor class (like ElementBase).
  55. plugin_info = ('plugin_attrib_map', 'plugin_tag_map',
  56. 'plugin_iterables', 'plugin_overrides')
  57. for attr in plugin_info:
  58. info = getattr(stanza, attr)
  59. setattr(stanza, attr, info.copy())
  60. stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
  61. stanza.plugin_tag_map[tag] = plugin
  62. if iterable:
  63. stanza.plugin_iterables.add(plugin)
  64. if plugin.plugin_multi_attrib:
  65. multiplugin = multifactory(plugin, plugin.plugin_multi_attrib)
  66. register_stanza_plugin(stanza, multiplugin)
  67. if overrides:
  68. for interface in plugin.overrides:
  69. stanza.plugin_overrides[interface] = plugin.plugin_attrib
  70. # To maintain backwards compatibility for now, preserve the camel case name.
  71. registerStanzaPlugin = register_stanza_plugin
  72. def multifactory(stanza, plugin_attrib):
  73. """
  74. Returns a ElementBase class for handling reoccuring child stanzas
  75. """
  76. def plugin_filter(self):
  77. return lambda x: isinstance(x, self._multistanza)
  78. def plugin_lang_filter(self, lang):
  79. return lambda x: isinstance(x, self._multistanza) and \
  80. x['lang'] == lang
  81. class Multi(ElementBase):
  82. """
  83. Template class for multifactory
  84. """
  85. def setup(self, xml=None):
  86. self.xml = ET.Element('')
  87. def get_multi(self, lang=None):
  88. parent = self.parent()
  89. if not lang or lang == '*':
  90. res = filter(plugin_filter(self), parent)
  91. else:
  92. res = filter(plugin_filter(self, lang), parent)
  93. return list(res)
  94. def set_multi(self, val, lang=None):
  95. parent = self.parent()
  96. del_multi = getattr(self, 'del_%s' % plugin_attrib)
  97. del_multi(lang)
  98. for sub in val:
  99. parent.append(sub)
  100. def del_multi(self, lang=None):
  101. parent = self.parent()
  102. if not lang or lang == '*':
  103. res = filter(plugin_filter(self), parent)
  104. else:
  105. res = filter(plugin_filter(self, lang), parent)
  106. res = list(res)
  107. if not res:
  108. del parent.plugins[(plugin_attrib, None)]
  109. parent.loaded_plugins.remove(plugin_attrib)
  110. try:
  111. parent.xml.remove(self.xml)
  112. except ValueError:
  113. pass
  114. else:
  115. for stanza in list(res):
  116. parent.iterables.remove(stanza)
  117. parent.xml.remove(stanza.xml)
  118. Multi.is_extension = True
  119. Multi.plugin_attrib = plugin_attrib
  120. Multi._multistanza = stanza
  121. Multi.interfaces = set([plugin_attrib])
  122. Multi.lang_interfaces = set([plugin_attrib])
  123. setattr(Multi, "get_%s" % plugin_attrib, get_multi)
  124. setattr(Multi, "set_%s" % plugin_attrib, set_multi)
  125. setattr(Multi, "del_%s" % plugin_attrib, del_multi)
  126. return Multi
  127. def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
  128. """Apply the stanza's namespace to elements in an XPath expression.
  129. :param string xpath: The XPath expression to fix with namespaces.
  130. :param bool split: Indicates if the fixed XPath should be left as a
  131. list of element names with namespaces. Defaults to
  132. False, which returns a flat string path.
  133. :param bool propagate_ns: Overrides propagating parent element
  134. namespaces to child elements. Useful if
  135. you wish to simply split an XPath that has
  136. non-specified namespaces, and child and
  137. parent namespaces are known not to always
  138. match. Defaults to True.
  139. """
  140. fixed = []
  141. # Split the XPath into a series of blocks, where a block
  142. # is started by an element with a namespace.
  143. ns_blocks = xpath.split('{')
  144. for ns_block in ns_blocks:
  145. if '}' in ns_block:
  146. # Apply the found namespace to following elements
  147. # that do not have namespaces.
  148. namespace = ns_block.split('}')[0]
  149. elements = ns_block.split('}')[1].split('/')
  150. else:
  151. # Apply the stanza's namespace to the following
  152. # elements since no namespace was provided.
  153. namespace = default_ns
  154. elements = ns_block.split('/')
  155. for element in elements:
  156. if element:
  157. # Skip empty entry artifacts from splitting.
  158. if propagate_ns and element[0] != '*':
  159. tag = '{%s}%s' % (namespace, element)
  160. else:
  161. tag = element
  162. fixed.append(tag)
  163. if split:
  164. return fixed
  165. return '/'.join(fixed)
  166. class ElementBase(object):
  167. """
  168. The core of SleekXMPP's stanza XML manipulation and handling is provided
  169. by ElementBase. ElementBase wraps XML cElementTree objects and enables
  170. access to the XML contents through dictionary syntax, similar in style
  171. to the Ruby XMPP library Blather's stanza implementation.
  172. Stanzas are defined by their name, namespace, and interfaces. For
  173. example, a simplistic Message stanza could be defined as::
  174. >>> class Message(ElementBase):
  175. ... name = "message"
  176. ... namespace = "jabber:client"
  177. ... interfaces = set(('to', 'from', 'type', 'body'))
  178. ... sub_interfaces = set(('body',))
  179. The resulting Message stanza's contents may be accessed as so::
  180. >>> message['to'] = "user@example.com"
  181. >>> message['body'] = "Hi!"
  182. >>> message['body']
  183. "Hi!"
  184. >>> del message['body']
  185. >>> message['body']
  186. ""
  187. The interface values map to either custom access methods, stanza
  188. XML attributes, or (if the interface is also in sub_interfaces) the
  189. text contents of a stanza's subelement.
  190. Custom access methods may be created by adding methods of the
  191. form "getInterface", "setInterface", or "delInterface", where
  192. "Interface" is the titlecase version of the interface name.
  193. Stanzas may be extended through the use of plugins. A plugin
  194. is simply a stanza that has a plugin_attrib value. For example::
  195. >>> class MessagePlugin(ElementBase):
  196. ... name = "custom_plugin"
  197. ... namespace = "custom"
  198. ... interfaces = set(('useful_thing', 'custom'))
  199. ... plugin_attrib = "custom"
  200. The plugin stanza class must be associated with its intended
  201. container stanza by using register_stanza_plugin as so::
  202. >>> register_stanza_plugin(Message, MessagePlugin)
  203. The plugin may then be accessed as if it were built-in to the parent
  204. stanza::
  205. >>> message['custom']['useful_thing'] = 'foo'
  206. If a plugin provides an interface that is the same as the plugin's
  207. plugin_attrib value, then the plugin's interface may be assigned
  208. directly from the parent stanza, as shown below, but retrieving
  209. information will require all interfaces to be used, as so::
  210. >>> # Same as using message['custom']['custom']
  211. >>> message['custom'] = 'bar'
  212. >>> # Must use all interfaces
  213. >>> message['custom']['custom']
  214. 'bar'
  215. If the plugin sets :attr:`is_extension` to ``True``, then both setting
  216. and getting an interface value that is the same as the plugin's
  217. plugin_attrib value will work, as so::
  218. >>> message['custom'] = 'bar' # Using is_extension=True
  219. >>> message['custom']
  220. 'bar'
  221. :param xml: Initialize the stanza object with an existing XML object.
  222. :param parent: Optionally specify a parent stanza object will
  223. contain this substanza.
  224. """
  225. #: The XML tag name of the element, not including any namespace
  226. #: prefixes. For example, an :class:`ElementBase` object for
  227. #: ``<message />`` would use ``name = 'message'``.
  228. name = 'stanza'
  229. #: The XML namespace for the element. Given ``<foo xmlns="bar" />``,
  230. #: then ``namespace = "bar"`` should be used. The default namespace
  231. #: is ``jabber:client`` since this is being used in an XMPP library.
  232. namespace = 'jabber:client'
  233. #: For :class:`ElementBase` subclasses which are intended to be used
  234. #: as plugins, the ``plugin_attrib`` value defines the plugin name.
  235. #: Plugins may be accessed by using the ``plugin_attrib`` value as
  236. #: the interface. An example using ``plugin_attrib = 'foo'``::
  237. #:
  238. #: register_stanza_plugin(Message, FooPlugin)
  239. #: msg = Message()
  240. #: msg['foo']['an_interface_from_the_foo_plugin']
  241. plugin_attrib = 'plugin'
  242. #: For :class:`ElementBase` subclasses that are intended to be an
  243. #: iterable group of items, the ``plugin_multi_attrib`` value defines
  244. #: an interface for the parent stanza which returns the entire group
  245. #: of matching substanzas. So the following are equivalent::
  246. #:
  247. #: # Given stanza class Foo, with plugin_multi_attrib = 'foos'
  248. #: parent['foos']
  249. #: filter(isinstance(item, Foo), parent['substanzas'])
  250. plugin_multi_attrib = ''
  251. #: The set of keys that the stanza provides for accessing and
  252. #: manipulating the underlying XML object. This set may be augmented
  253. #: with the :attr:`plugin_attrib` value of any registered
  254. #: stanza plugins.
  255. interfaces = set(('type', 'to', 'from', 'id', 'payload'))
  256. #: A subset of :attr:`interfaces` which maps interfaces to direct
  257. #: subelements of the underlying XML object. Using this set, the text
  258. #: of these subelements may be set, retrieved, or removed without
  259. #: needing to define custom methods.
  260. sub_interfaces = set()
  261. #: A subset of :attr:`interfaces` which maps the presence of
  262. #: subelements to boolean values. Using this set allows for quickly
  263. #: checking for the existence of empty subelements like ``<required />``.
  264. #:
  265. #: .. versionadded:: 1.1
  266. bool_interfaces = set()
  267. #: .. versionadded:: 1.1.2
  268. lang_interfaces = set()
  269. #: In some cases you may wish to override the behaviour of one of the
  270. #: parent stanza's interfaces. The ``overrides`` list specifies the
  271. #: interface name and access method to be overridden. For example,
  272. #: to override setting the parent's ``'condition'`` interface you
  273. #: would use::
  274. #:
  275. #: overrides = ['set_condition']
  276. #:
  277. #: Getting and deleting the ``'condition'`` interface would not
  278. #: be affected.
  279. #:
  280. #: .. versionadded:: 1.0-Beta5
  281. overrides = []
  282. #: If you need to add a new interface to an existing stanza, you
  283. #: can create a plugin and set ``is_extension = True``. Be sure
  284. #: to set the :attr:`plugin_attrib` value to the desired interface
  285. #: name, and that it is the only interface listed in
  286. #: :attr:`interfaces`. Requests for the new interface from the
  287. #: parent stanza will be passed to the plugin directly.
  288. #:
  289. #: .. versionadded:: 1.0-Beta5
  290. is_extension = False
  291. #: A map of interface operations to the overriding functions.
  292. #: For example, after overriding the ``set`` operation for
  293. #: the interface ``body``, :attr:`plugin_overrides` would be::
  294. #:
  295. #: {'set_body': <some function>}
  296. #:
  297. #: .. versionadded: 1.0-Beta5
  298. plugin_overrides = {}
  299. #: A mapping of the :attr:`plugin_attrib` values of registered
  300. #: plugins to their respective classes.
  301. plugin_attrib_map = {}
  302. #: A mapping of root element tag names (in ``'{namespace}elementname'``
  303. #: format) to the plugin classes responsible for them.
  304. plugin_tag_map = {}
  305. #: The set of stanza classes that can be iterated over using
  306. #: the 'substanzas' interface. Classes are added to this set
  307. #: when registering a plugin with ``iterable=True``::
  308. #:
  309. #: register_stanza_plugin(DiscoInfo, DiscoItem, iterable=True)
  310. #:
  311. #: .. versionadded:: 1.0-Beta5
  312. plugin_iterables = set()
  313. #: A deprecated version of :attr:`plugin_iterables` that remains
  314. #: for backward compatibility. It required a parent stanza to
  315. #: know beforehand what stanza classes would be iterable::
  316. #:
  317. #: class DiscoItem(ElementBase):
  318. #: ...
  319. #:
  320. #: class DiscoInfo(ElementBase):
  321. #: subitem = (DiscoItem, )
  322. #: ...
  323. #:
  324. #: .. deprecated:: 1.0-Beta5
  325. subitem = set()
  326. #: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
  327. xml_ns = XML_NS
  328. def __init__(self, xml=None, parent=None):
  329. self._index = 0
  330. #: The underlying XML object for the stanza. It is a standard
  331. #: :class:`xml.etree.cElementTree` object.
  332. self.xml = xml
  333. #: An ordered dictionary of plugin stanzas, mapped by their
  334. #: :attr:`plugin_attrib` value.
  335. self.plugins = OrderedDict()
  336. self.loaded_plugins = set()
  337. #: A list of child stanzas whose class is included in
  338. #: :attr:`plugin_iterables`.
  339. self.iterables = []
  340. #: The name of the tag for the stanza's root element. It is the
  341. #: same as calling :meth:`tag_name()` and is formatted as
  342. #: ``'{namespace}elementname'``.
  343. self.tag = self.tag_name()
  344. #: A :class:`weakref.weakref` to the parent stanza, if there is one.
  345. #: If not, then :attr:`parent` is ``None``.
  346. self.parent = None
  347. if parent is not None:
  348. if not isinstance(parent, weakref.ReferenceType):
  349. self.parent = weakref.ref(parent)
  350. else:
  351. self.parent = parent
  352. if self.subitem is not None:
  353. for sub in self.subitem:
  354. self.plugin_iterables.add(sub)
  355. if self.setup(xml):
  356. # If we generated our own XML, then everything is ready.
  357. return
  358. # Initialize values using provided XML
  359. for child in self.xml:
  360. if child.tag in self.plugin_tag_map:
  361. plugin_class = self.plugin_tag_map[child.tag]
  362. self.init_plugin(plugin_class.plugin_attrib,
  363. existing_xml=child,
  364. reuse=False)
  365. def setup(self, xml=None):
  366. """Initialize the stanza's XML contents.
  367. Will return ``True`` if XML was generated according to the stanza's
  368. definition instead of building a stanza object from an existing
  369. XML object.
  370. :param xml: An existing XML object to use for the stanza's content
  371. instead of generating new XML.
  372. """
  373. if self.xml is None:
  374. self.xml = xml
  375. last_xml = self.xml
  376. if self.xml is None:
  377. # Generate XML from the stanza definition
  378. for ename in self.name.split('/'):
  379. new = ET.Element("{%s}%s" % (self.namespace, ename))
  380. if self.xml is None:
  381. self.xml = new
  382. else:
  383. last_xml.append(new)
  384. last_xml = new
  385. if self.parent is not None:
  386. self.parent().xml.append(self.xml)
  387. # We had to generate XML
  388. return True
  389. else:
  390. # We did not generate XML
  391. return False
  392. def enable(self, attrib, lang=None):
  393. """Enable and initialize a stanza plugin.
  394. Alias for :meth:`init_plugin`.
  395. :param string attrib: The :attr:`plugin_attrib` value of the
  396. plugin to enable.
  397. """
  398. return self.init_plugin(attrib, lang)
  399. def _get_plugin(self, name, lang=None, check=False):
  400. if lang is None:
  401. lang = self.get_lang()
  402. if name not in self.plugin_attrib_map:
  403. return None
  404. plugin_class = self.plugin_attrib_map[name]
  405. if plugin_class.is_extension:
  406. if (name, None) in self.plugins:
  407. return self.plugins[(name, None)]
  408. else:
  409. return None if check else self.init_plugin(name, lang)
  410. else:
  411. if (name, lang) in self.plugins:
  412. return self.plugins[(name, lang)]
  413. else:
  414. return None if check else self.init_plugin(name, lang)
  415. def init_plugin(self, attrib, lang=None, existing_xml=None, reuse=True):
  416. """Enable and initialize a stanza plugin.
  417. :param string attrib: The :attr:`plugin_attrib` value of the
  418. plugin to enable.
  419. """
  420. default_lang = self.get_lang()
  421. if not lang:
  422. lang = default_lang
  423. plugin_class = self.plugin_attrib_map[attrib]
  424. if plugin_class.is_extension and (attrib, None) in self.plugins:
  425. return self.plugins[(attrib, None)]
  426. if reuse and (attrib, lang) in self.plugins:
  427. return self.plugins[(attrib, lang)]
  428. plugin = plugin_class(parent=self, xml=existing_xml)
  429. if plugin.is_extension:
  430. self.plugins[(attrib, None)] = plugin
  431. else:
  432. if lang != default_lang:
  433. plugin['lang'] = lang
  434. self.plugins[(attrib, lang)] = plugin
  435. if plugin_class in self.plugin_iterables:
  436. self.iterables.append(plugin)
  437. if plugin_class.plugin_multi_attrib:
  438. self.init_plugin(plugin_class.plugin_multi_attrib)
  439. self.loaded_plugins.add(attrib)
  440. return plugin
  441. def _get_stanza_values(self):
  442. """Return A JSON/dictionary version of the XML content
  443. exposed through the stanza's interfaces::
  444. >>> msg = Message()
  445. >>> msg.values
  446. {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
  447. 'to': , 'type': 'normal', 'id': '', 'subject': ''}
  448. Likewise, assigning to :attr:`values` will change the XML
  449. content::
  450. >>> msg = Message()
  451. >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
  452. >>> msg
  453. '<message to="user@example.com"><body>Hi!</body></message>'
  454. .. versionadded:: 1.0-Beta1
  455. """
  456. values = OrderedDict()
  457. values['lang'] = self['lang']
  458. for interface in self.interfaces:
  459. if isinstance(self[interface], JID):
  460. values[interface] = self[interface].jid
  461. else:
  462. values[interface] = self[interface]
  463. if interface in self.lang_interfaces:
  464. values['%s|*' % interface] = self['%s|*' % interface]
  465. for plugin, stanza in self.plugins.items():
  466. lang = stanza['lang']
  467. if lang:
  468. values['%s|%s' % (plugin[0], lang)] = stanza.values
  469. else:
  470. values[plugin[0]] = stanza.values
  471. if self.iterables:
  472. iterables = []
  473. for stanza in self.iterables:
  474. iterables.append(stanza.values)
  475. iterables[-1]['__childtag__'] = stanza.tag
  476. values['substanzas'] = iterables
  477. return values
  478. def _set_stanza_values(self, values):
  479. """Set multiple stanza interface values using a dictionary.
  480. Stanza plugin values may be set using nested dictionaries.
  481. :param values: A dictionary mapping stanza interface with values.
  482. Plugin interfaces may accept a nested dictionary that
  483. will be used recursively.
  484. .. versionadded:: 1.0-Beta1
  485. """
  486. iterable_interfaces = [p.plugin_attrib for \
  487. p in self.plugin_iterables]
  488. if 'lang' in values:
  489. self['lang'] = values['lang']
  490. if 'substanzas' in values:
  491. # Remove existing substanzas
  492. for stanza in self.iterables:
  493. try:
  494. self.xml.remove(stanza.xml)
  495. except ValueError:
  496. pass
  497. self.iterables = []
  498. # Add new substanzas
  499. for subdict in values['substanzas']:
  500. if '__childtag__' in subdict:
  501. for subclass in self.plugin_iterables:
  502. child_tag = "{%s}%s" % (subclass.namespace,
  503. subclass.name)
  504. if subdict['__childtag__'] == child_tag:
  505. sub = subclass(parent=self)
  506. sub.values = subdict
  507. self.iterables.append(sub)
  508. for interface, value in values.items():
  509. full_interface = interface
  510. interface_lang = ('%s|' % interface).split('|')
  511. interface = interface_lang[0]
  512. lang = interface_lang[1] or self.get_lang()
  513. if interface == 'lang':
  514. continue
  515. elif interface == 'substanzas':
  516. continue
  517. elif interface in self.interfaces:
  518. self[full_interface] = value
  519. elif interface in self.plugin_attrib_map:
  520. if interface not in iterable_interfaces:
  521. plugin = self._get_plugin(interface, lang)
  522. if plugin:
  523. plugin.values = value
  524. return self
  525. def __getitem__(self, attrib):
  526. """Return the value of a stanza interface using dict-like syntax.
  527. Example::
  528. >>> msg['body']
  529. 'Message contents'
  530. Stanza interfaces are typically mapped directly to the underlying XML
  531. object, but can be overridden by the presence of a ``get_attrib``
  532. method (or ``get_foo`` where the interface is named ``'foo'``, etc).
  533. The search order for interface value retrieval for an interface
  534. named ``'foo'`` is:
  535. 1. The list of substanzas (``'substanzas'``)
  536. 2. The result of calling the ``get_foo`` override handler.
  537. 3. The result of calling ``get_foo``.
  538. 4. The result of calling ``getFoo``.
  539. 5. The contents of the ``foo`` subelement, if ``foo`` is listed
  540. in :attr:`sub_interfaces`.
  541. 6. True or False depending on the existence of a ``foo``
  542. subelement and ``foo`` is in :attr:`bool_interfaces`.
  543. 7. The value of the ``foo`` attribute of the XML object.
  544. 8. The plugin named ``'foo'``
  545. 9. An empty string.
  546. :param string attrib: The name of the requested stanza interface.
  547. """
  548. full_attrib = attrib
  549. attrib_lang = ('%s|' % attrib).split('|')
  550. attrib = attrib_lang[0]
  551. lang = attrib_lang[1] or None
  552. kwargs = {}
  553. if lang and attrib in self.lang_interfaces:
  554. kwargs['lang'] = lang
  555. kwargs = safedict(kwargs)
  556. if attrib == 'substanzas':
  557. return self.iterables
  558. elif attrib in self.interfaces or attrib == 'lang':
  559. get_method = "get_%s" % attrib.lower()
  560. get_method2 = "get%s" % attrib.title()
  561. if self.plugin_overrides:
  562. name = self.plugin_overrides.get(get_method, None)
  563. if name:
  564. plugin = self._get_plugin(name, lang)
  565. if plugin:
  566. handler = getattr(plugin, get_method, None)
  567. if handler:
  568. return handler(**kwargs)
  569. if hasattr(self, get_method):
  570. return getattr(self, get_method)(**kwargs)
  571. elif hasattr(self, get_method2):
  572. return getattr(self, get_method2)(**kwargs)
  573. else:
  574. if attrib in self.sub_interfaces:
  575. return self._get_sub_text(attrib, lang=lang)
  576. elif attrib in self.bool_interfaces:
  577. elem = self.xml.find('{%s}%s' % (self.namespace, attrib))
  578. return elem is not None
  579. else:
  580. return self._get_attr(attrib)
  581. elif attrib in self.plugin_attrib_map:
  582. plugin = self._get_plugin(attrib, lang)
  583. if plugin and plugin.is_extension:
  584. return plugin[full_attrib]
  585. return plugin
  586. else:
  587. return ''
  588. def __setitem__(self, attrib, value):
  589. """Set the value of a stanza interface using dictionary-like syntax.
  590. Example::
  591. >>> msg['body'] = "Hi!"
  592. >>> msg['body']
  593. 'Hi!'
  594. Stanza interfaces are typically mapped directly to the underlying XML
  595. object, but can be overridden by the presence of a ``set_attrib``
  596. method (or ``set_foo`` where the interface is named ``'foo'``, etc).
  597. The effect of interface value assignment for an interface
  598. named ``'foo'`` will be one of:
  599. 1. Delete the interface's contents if the value is None.
  600. 2. Call the ``set_foo`` override handler, if it exists.
  601. 3. Call ``set_foo``, if it exists.
  602. 4. Call ``setFoo``, if it exists.
  603. 5. Set the text of a ``foo`` element, if ``'foo'`` is
  604. in :attr:`sub_interfaces`.
  605. 6. Add or remove an empty subelement ``foo``
  606. if ``foo`` is in :attr:`bool_interfaces`.
  607. 7. Set the value of a top level XML attribute named ``foo``.
  608. 8. Attempt to pass the value to a plugin named ``'foo'`` using
  609. the plugin's ``'foo'`` interface.
  610. 9. Do nothing.
  611. :param string attrib: The name of the stanza interface to modify.
  612. :param value: The new value of the stanza interface.
  613. """
  614. full_attrib = attrib
  615. attrib_lang = ('%s|' % attrib).split('|')
  616. attrib = attrib_lang[0]
  617. lang = attrib_lang[1] or None
  618. kwargs = {}
  619. if lang and attrib in self.lang_interfaces:
  620. kwargs['lang'] = lang
  621. kwargs = safedict(kwargs)
  622. if attrib in self.interfaces or attrib == 'lang':
  623. if value is not None:
  624. set_method = "set_%s" % attrib.lower()
  625. set_method2 = "set%s" % attrib.title()
  626. if self.plugin_overrides:
  627. name = self.plugin_overrides.get(set_method, None)
  628. if name:
  629. plugin = self._get_plugin(name, lang)
  630. if plugin:
  631. handler = getattr(plugin, set_method, None)
  632. if handler:
  633. return handler(value, **kwargs)
  634. if hasattr(self, set_method):
  635. getattr(self, set_method)(value, **kwargs)
  636. elif hasattr(self, set_method2):
  637. getattr(self, set_method2)(value, **kwargs)
  638. else:
  639. if attrib in self.sub_interfaces:
  640. if lang == '*':
  641. return self._set_all_sub_text(attrib,
  642. value,
  643. lang='*')
  644. return self._set_sub_text(attrib, text=value,
  645. lang=lang)
  646. elif attrib in self.bool_interfaces:
  647. if value:
  648. return self._set_sub_text(attrib, '',
  649. keep=True,
  650. lang=lang)
  651. else:
  652. return self._set_sub_text(attrib, '',
  653. keep=False,
  654. lang=lang)
  655. else:
  656. self._set_attr(attrib, value)
  657. else:
  658. self.__delitem__(attrib)
  659. elif attrib in self.plugin_attrib_map:
  660. plugin = self._get_plugin(attrib, lang)
  661. if plugin:
  662. plugin[full_attrib] = value
  663. return self
  664. def __delitem__(self, attrib):
  665. """Delete the value of a stanza interface using dict-like syntax.
  666. Example::
  667. >>> msg['body'] = "Hi!"
  668. >>> msg['body']
  669. 'Hi!'
  670. >>> del msg['body']
  671. >>> msg['body']
  672. ''
  673. Stanza interfaces are typically mapped directly to the underlyig XML
  674. object, but can be overridden by the presence of a ``del_attrib``
  675. method (or ``del_foo`` where the interface is named ``'foo'``, etc).
  676. The effect of deleting a stanza interface value named ``foo`` will be
  677. one of:
  678. 1. Call ``del_foo`` override handler, if it exists.
  679. 2. Call ``del_foo``, if it exists.
  680. 3. Call ``delFoo``, if it exists.
  681. 4. Delete ``foo`` element, if ``'foo'`` is in
  682. :attr:`sub_interfaces`.
  683. 5. Remove ``foo`` element if ``'foo'`` is in
  684. :attr:`bool_interfaces`.
  685. 6. Delete top level XML attribute named ``foo``.
  686. 7. Remove the ``foo`` plugin, if it was loaded.
  687. 8. Do nothing.
  688. :param attrib: The name of the affected stanza interface.
  689. """
  690. full_attrib = attrib
  691. attrib_lang = ('%s|' % attrib).split('|')
  692. attrib = attrib_lang[0]
  693. lang = attrib_lang[1] or None
  694. kwargs = {}
  695. if lang and attrib in self.lang_interfaces:
  696. kwargs['lang'] = lang
  697. kwargs = safedict(kwargs)
  698. if attrib in self.interfaces or attrib == 'lang':
  699. del_method = "del_%s" % attrib.lower()
  700. del_method2 = "del%s" % attrib.title()
  701. if self.plugin_overrides:
  702. name = self.plugin_overrides.get(del_method, None)
  703. if name:
  704. plugin = self._get_plugin(attrib, lang)
  705. if plugin:
  706. handler = getattr(plugin, del_method, None)
  707. if handler:
  708. return handler(**kwargs)
  709. if hasattr(self, del_method):
  710. getattr(self, del_method)(**kwargs)
  711. elif hasattr(self, del_method2):
  712. getattr(self, del_method2)(**kwargs)
  713. else:
  714. if attrib in self.sub_interfaces:
  715. return self._del_sub(attrib, lang=lang)
  716. elif attrib in self.bool_interfaces:
  717. return self._del_sub(attrib, lang=lang)
  718. else:
  719. self._del_attr(attrib)
  720. elif attrib in self.plugin_attrib_map:
  721. plugin = self._get_plugin(attrib, lang, check=True)
  722. if not plugin:
  723. return self
  724. if plugin.is_extension:
  725. del plugin[full_attrib]
  726. del self.plugins[(attrib, None)]
  727. else:
  728. del self.plugins[(attrib, plugin['lang'])]
  729. self.loaded_plugins.remove(attrib)
  730. try:
  731. self.xml.remove(plugin.xml)
  732. except ValueError:
  733. pass
  734. return self
  735. def _set_attr(self, name, value):
  736. """Set the value of a top level attribute of the XML object.
  737. If the new value is None or an empty string, then the attribute will
  738. be removed.
  739. :param name: The name of the attribute.
  740. :param value: The new value of the attribute, or None or '' to
  741. remove it.
  742. """
  743. if value is None or value == '':
  744. self.__delitem__(name)
  745. else:
  746. self.xml.attrib[name] = value
  747. def _del_attr(self, name):
  748. """Remove a top level attribute of the XML object.
  749. :param name: The name of the attribute.
  750. """
  751. if name in self.xml.attrib:
  752. del self.xml.attrib[name]
  753. def _get_attr(self, name, default=''):
  754. """Return the value of a top level attribute of the XML object.
  755. In case the attribute has not been set, a default value can be
  756. returned instead. An empty string is returned if no other default
  757. is supplied.
  758. :param name: The name of the attribute.
  759. :param default: Optional value to return if the attribute has not
  760. been set. An empty string is returned otherwise.
  761. """
  762. return self.xml.attrib.get(name, default)
  763. def _get_sub_text(self, name, default='', lang=None):
  764. """Return the text contents of a sub element.
  765. In case the element does not exist, or it has no textual content,
  766. a default value can be returned instead. An empty string is returned
  767. if no other default is supplied.
  768. :param name: The name or XPath expression of the element.
  769. :param default: Optional default to return if the element does
  770. not exists. An empty string is returned otherwise.
  771. """
  772. name = self._fix_ns(name)
  773. if lang == '*':
  774. return self._get_all_sub_text(name, default, None)
  775. default_lang = self.get_lang()
  776. if not lang:
  777. lang = default_lang
  778. stanzas = self.xml.findall(name)
  779. if not stanzas:
  780. return default
  781. for stanza in stanzas:
  782. if stanza.attrib.get('{%s}lang' % XML_NS, default_lang) == lang:
  783. if stanza.text is None:
  784. return default
  785. return stanza.text
  786. return default
  787. def _get_all_sub_text(self, name, default='', lang=None):
  788. name = self._fix_ns(name)
  789. default_lang = self.get_lang()
  790. results = OrderedDict()
  791. stanzas = self.xml.findall(name)
  792. if stanzas:
  793. for stanza in stanzas:
  794. stanza_lang = stanza.attrib.get('{%s}lang' % XML_NS,
  795. default_lang)
  796. if not lang or lang == '*' or stanza_lang == lang:
  797. results[stanza_lang] = stanza.text
  798. return results
  799. def _set_sub_text(self, name, text=None, keep=False, lang=None):
  800. """Set the text contents of a sub element.
  801. In case the element does not exist, a element will be created,
  802. and its text contents will be set.
  803. If the text is set to an empty string, or None, then the
  804. element will be removed, unless keep is set to True.
  805. :param name: The name or XPath expression of the element.
  806. :param text: The new textual content of the element. If the text
  807. is an empty string or None, the element will be removed
  808. unless the parameter keep is True.
  809. :param keep: Indicates if the element should be kept if its text is
  810. removed. Defaults to False.
  811. """
  812. default_lang = self.get_lang()
  813. if lang is None:
  814. lang = default_lang
  815. if not text and not keep:
  816. return self._del_sub(name, lang=lang)
  817. path = self._fix_ns(name, split=True)
  818. name = path[-1]
  819. parent = self.xml
  820. # The first goal is to find the parent of the subelement, or, if
  821. # we can't find that, the closest grandparent element.
  822. missing_path = []
  823. search_order = path[:-1]
  824. while search_order:
  825. parent = self.xml.find('/'.join(search_order))
  826. ename = search_order.pop()
  827. if parent is not None:
  828. break
  829. else:
  830. missing_path.append(ename)
  831. missing_path.reverse()
  832. # Find all existing elements that match the desired
  833. # element path (there may be multiples due to different
  834. # languages values).
  835. if parent is not None:
  836. elements = self.xml.findall('/'.join(path))
  837. else:
  838. parent = self.xml
  839. elements = []
  840. # Insert the remaining grandparent elements that don't exist yet.
  841. for ename in missing_path:
  842. element = ET.Element(ename)
  843. parent.append(element)
  844. parent = element
  845. # Re-use an existing element with the proper language, if one exists.
  846. for element in elements:
  847. elang = element.attrib.get('{%s}lang' % XML_NS, default_lang)
  848. if not lang and elang == default_lang or lang and lang == elang:
  849. element.text = text
  850. return element
  851. # No useable element exists, so create a new one.
  852. element = ET.Element(name)
  853. element.text = text
  854. if lang and lang != default_lang:
  855. element.attrib['{%s}lang' % XML_NS] = lang
  856. parent.append(element)
  857. return element
  858. def _set_all_sub_text(self, name, values, keep=False, lang=None):
  859. self._del_sub(name, lang)
  860. for value_lang, value in values.items():
  861. if not lang or lang == '*' or value_lang == lang:
  862. self._set_sub_text(name, text=value,
  863. keep=keep,
  864. lang=value_lang)
  865. def _del_sub(self, name, all=False, lang=None):
  866. """Remove sub elements that match the given name or XPath.
  867. If the element is in a path, then any parent elements that become
  868. empty after deleting the element may also be deleted if requested
  869. by setting all=True.
  870. :param name: The name or XPath expression for the element(s) to remove.
  871. :param bool all: If True, remove all empty elements in the path to the
  872. deleted element. Defaults to False.
  873. """
  874. path = self._fix_ns(name, split=True)
  875. original_target = path[-1]
  876. default_lang = self.get_lang()
  877. if not lang:
  878. lang = default_lang
  879. for level, _ in enumerate(path):
  880. # Generate the paths to the target elements and their parent.
  881. element_path = "/".join(path[:len(path) - level])
  882. parent_path = "/".join(path[:len(path) - level - 1])
  883. elements = self.xml.findall(element_path)
  884. parent = self.xml.find(parent_path)
  885. if elements:
  886. if parent is None:
  887. parent = self.xml
  888. for element in elements:
  889. if element.tag == original_target or not list(element):
  890. # Only delete the originally requested elements, and
  891. # any parent elements that have become empty.
  892. elem_lang = element.attrib.get('{%s}lang' % XML_NS,
  893. default_lang)
  894. if lang == '*' or elem_lang == lang:
  895. parent.remove(element)
  896. if not all:
  897. # If we don't want to delete elements up the tree, stop
  898. # after deleting the first level of elements.
  899. return
  900. def match(self, xpath):
  901. """Compare a stanza object with an XPath-like expression.
  902. If the XPath matches the contents of the stanza object, the match
  903. is successful.
  904. The XPath expression may include checks for stanza attributes.
  905. For example::
  906. 'presence@show=xa@priority=2/status'
  907. Would match a presence stanza whose show value is set to ``'xa'``,
  908. has a priority value of ``'2'``, and has a status element.
  909. :param string xpath: The XPath expression to check against. It
  910. may be either a string or a list of element
  911. names with attribute checks.
  912. """
  913. if not isinstance(xpath, list):
  914. xpath = self._fix_ns(xpath, split=True, propagate_ns=False)
  915. # Extract the tag name and attribute checks for the first XPath node.
  916. components = xpath[0].split('@')
  917. tag = components[0]
  918. attributes = components[1:]
  919. if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \
  920. tag not in self.loaded_plugins and tag not in self.plugin_attrib:
  921. # The requested tag is not in this stanza, so no match.
  922. return False
  923. # Check the rest of the XPath against any substanzas.
  924. matched_substanzas = False
  925. for substanza in self.iterables:
  926. if xpath[1:] == []:
  927. break
  928. matched_substanzas = substanza.match(xpath[1:])
  929. if matched_substanzas:
  930. break
  931. # Check attribute values.
  932. for attribute in attributes:
  933. name, value = attribute.split('=')
  934. if self[name] != value:
  935. return False
  936. # Check sub interfaces.
  937. if len(xpath) > 1:
  938. next_tag = xpath[1]
  939. if next_tag in self.sub_interfaces and self[next_tag]:
  940. return True
  941. # Attempt to continue matching the XPath using the stanza's plugins.
  942. if not matched_substanzas and len(xpath) > 1:
  943. # Convert {namespace}tag@attribs to just tag
  944. next_tag = xpath[1].split('@')[0].split('}')[-1]
  945. langs = [name[1] for name in self.plugins if name[0] == next_tag]
  946. for lang in langs:
  947. plugin = self._get_plugin(next_tag, lang)
  948. if plugin and plugin.match(xpath[1:]):
  949. return True
  950. return False
  951. # Everything matched.
  952. return True
  953. def find(self, xpath):
  954. """Find an XML object in this stanza given an XPath expression.
  955. Exposes ElementTree interface for backwards compatibility.
  956. .. note::
  957. Matching on attribute values is not supported in Python 2.6
  958. or Python 3.1
  959. :param string xpath: An XPath expression matching a single
  960. desired element.
  961. """
  962. return self.xml.find(xpath)
  963. def findall(self, xpath):
  964. """Find multiple XML objects in this stanza given an XPath expression.
  965. Exposes ElementTree interface for backwards compatibility.
  966. .. note::
  967. Matching on attribute values is not supported in Python 2.6
  968. or Python 3.1.
  969. :param string xpath: An XPath expression matching multiple
  970. desired elements.
  971. """
  972. return self.xml.findall(xpath)
  973. def get(self, key, default=None):
  974. """Return the value of a stanza interface.
  975. If the found value is None or an empty string, return the supplied
  976. default value.
  977. Allows stanza objects to be used like dictionaries.
  978. :param string key: The name of the stanza interface to check.
  979. :param default: Value to return if the stanza interface has a value
  980. of ``None`` or ``""``. Will default to returning None.
  981. """
  982. value = self[key]
  983. if value is None or value == '':
  984. return default
  985. return value
  986. def keys(self):
  987. """Return the names of all stanza interfaces provided by the
  988. stanza object.
  989. Allows stanza objects to be used like dictionaries.
  990. """
  991. out = []
  992. out += [x for x in self.interfaces]
  993. out += [x for x in self.loaded_plugins]
  994. out.append('lang')
  995. if self.iterables:
  996. out.append('substanzas')
  997. return out
  998. def append(self, item):
  999. """Append either an XML object or a substanza to this stanza object.
  1000. If a substanza object is appended, it will be added to the list
  1001. of iterable stanzas.
  1002. Allows stanza objects to be used like lists.
  1003. :param item: Either an XML object or a stanza object to add to
  1004. this stanza's contents.
  1005. """
  1006. if not isinstance(item, ElementBase):
  1007. if type(item) == XML_TYPE:
  1008. return self.appendxml(item)
  1009. else:
  1010. raise TypeError
  1011. self.xml.append(item.xml)
  1012. self.iterables.append(item)
  1013. if item.__class__ in self.plugin_iterables:
  1014. if item.__class__.plugin_multi_attrib:
  1015. self.init_plugin(item.__class__.plugin_multi_attrib)
  1016. elif item.__class__ == self.plugin_tag_map.get(item.tag_name(), None):
  1017. self.init_plugin(item.plugin_attrib,
  1018. existing_xml=item.xml,
  1019. reuse=False)
  1020. return self
  1021. def appendxml(self, xml):
  1022. """Append an XML object to the stanza's XML.
  1023. The added XML will not be included in the list of
  1024. iterable substanzas.
  1025. :param XML xml: The XML object to add to the stanza.
  1026. """
  1027. self.xml.append(xml)
  1028. return self
  1029. def pop(self, index=0):
  1030. """Remove and return the last substanza in the list of
  1031. iterable substanzas.
  1032. Allows stanza objects to be used like lists.
  1033. :param int index: The index of the substanza to remove.
  1034. """
  1035. substanza = self.iterables.pop(index)
  1036. self.xml.remove(substanza.xml)
  1037. return substanza
  1038. def next(self):
  1039. """Return the next iterable substanza."""
  1040. return self.__next__()
  1041. def clear(self):
  1042. """Remove all XML element contents and plugins.
  1043. Any attribute values will be preserved.
  1044. """
  1045. for child in list(self.xml):
  1046. self.xml.remove(child)
  1047. for plugin in list(self.plugins.keys()):
  1048. del self.plugins[plugin]
  1049. return self
  1050. @classmethod
  1051. def tag_name(cls):
  1052. """Return the namespaced name of the stanza's root element.
  1053. The format for the tag name is::
  1054. '{namespace}elementname'
  1055. For example, for the stanza ``<foo xmlns="bar" />``,
  1056. ``stanza.tag_name()`` would return ``"{bar}foo"``.
  1057. """
  1058. return "{%s}%s" % (cls.namespace, cls.name)
  1059. def get_lang(self, lang=None):
  1060. result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
  1061. if not result and self.parent and self.parent():
  1062. return self.parent()['lang']
  1063. return result
  1064. def set_lang(self, lang):
  1065. self.del_lang()
  1066. attr = '{%s}lang' % XML_NS
  1067. if lang:
  1068. self.xml.attrib[attr] = lang
  1069. def del_lang(self):
  1070. attr = '{%s}lang' % XML_NS
  1071. if attr in self.xml.attrib:
  1072. del self.xml.attrib[attr]
  1073. @property
  1074. def attrib(self):
  1075. """Return the stanza object itself.
  1076. Older implementations of stanza objects used XML objects directly,
  1077. requiring the use of ``.attrib`` to access attribute values.
  1078. Use of the dictionary syntax with the stanza object itself for
  1079. accessing stanza interfaces is preferred.
  1080. .. deprecated:: 1.0
  1081. """
  1082. return self
  1083. def _fix_ns(self, xpath, split=False, propagate_ns=True):
  1084. return fix_ns(xpath, split=split,
  1085. propagate_ns=propagate_ns,
  1086. default_ns=self.namespace)
  1087. def __eq__(self, other):
  1088. """Compare the stanza object with another to test for equality.
  1089. Stanzas are equal if their interfaces return the same values,
  1090. and if they are both instances of ElementBase.
  1091. :param ElementBase other: The stanza object to compare against.
  1092. """
  1093. if not isinstance(other, ElementBase):
  1094. return False
  1095. # Check that this stanza is a superset of the other stanza.
  1096. values = self.values
  1097. for key in other.keys():
  1098. if key not in values or values[key] != other[key]:
  1099. return False
  1100. # Check that the other stanza is a superset of this stanza.
  1101. values = other.values
  1102. for key in self.keys():
  1103. if key not in values or values[key] != self[key]:
  1104. return False
  1105. # Both stanzas are supersets of each other, therefore they
  1106. # must be equal.
  1107. return True
  1108. def __ne__(self, other):
  1109. """Compare the stanza object with another to test for inequality.
  1110. Stanzas are not equal if their interfaces return different values,
  1111. or if they are not both instances of ElementBase.
  1112. :param ElementBase other: The stanza object to compare against.
  1113. """
  1114. return not self.__eq__(other)
  1115. def __bool__(self):
  1116. """Stanza objects should be treated as True in boolean contexts.
  1117. Python 3.x version.
  1118. """
  1119. return True
  1120. def __nonzero__(self):
  1121. """Stanza objects should be treated as True in boolean contexts.
  1122. Python 2.x version.
  1123. """
  1124. return True
  1125. def __len__(self):
  1126. """Return the number of iterable substanzas in this stanza."""
  1127. return len(self.iterables)
  1128. def __iter__(self):
  1129. """Return an iterator object for the stanza's substanzas.
  1130. The iterator is the stanza object itself. Attempting to use two
  1131. iterators on the same stanza at the same time is discouraged.
  1132. """
  1133. self._index = 0
  1134. return self
  1135. def __next__(self):
  1136. """Return the next iterable substanza."""
  1137. self._index += 1
  1138. if self._index > len(self.iterables):
  1139. self._index = 0
  1140. raise StopIteration
  1141. return self.iterables[self._index - 1]
  1142. def __copy__(self):
  1143. """Return a copy of the stanza object that does not share the same
  1144. underlying XML object.
  1145. """
  1146. return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent)
  1147. def __str__(self, top_level_ns=True):
  1148. """Return a string serialization of the underlying XML object.
  1149. .. seealso:: :ref:`tostring`
  1150. :param bool top_level_ns: Display the top-most namespace.
  1151. Defaults to True.
  1152. """
  1153. return tostring(self.xml, xmlns='',
  1154. top_level=True)
  1155. def __repr__(self):
  1156. """Use the stanza's serialized XML as its representation."""
  1157. return self.__str__()
  1158. class StanzaBase(ElementBase):
  1159. """
  1160. StanzaBase provides the foundation for all other stanza objects used
  1161. by SleekXMPP, and defines a basic set of interfaces common to nearly
  1162. all stanzas. These interfaces are the ``'id'``, ``'type'``, ``'to'``,
  1163. and ``'from'`` attributes. An additional interface, ``'payload'``, is
  1164. available to access the XML contents of the stanza. Most stanza objects
  1165. will provided more specific interfaces, however.
  1166. **Stanza Interfaces:**
  1167. :id: An optional id value that can be used to associate stanzas
  1168. :to: A JID object representing the recipient's JID.
  1169. :from: A JID object representing the sender's JID.
  1170. with their replies.
  1171. :type: The type of stanza, typically will be ``'normal'``,
  1172. ``'error'``, ``'get'``, or ``'set'``, etc.
  1173. :payload: The XML contents of the stanza.
  1174. :param XMLStream stream: Optional :class:`sleekxmpp.xmlstream.XMLStream`
  1175. object responsible for sending this stanza.
  1176. :param XML xml: Optional XML contents to initialize stanza values.
  1177. :param string stype: Optional stanza type value.
  1178. :param sto: Optional string or :class:`sleekxmpp.xmlstream.JID`
  1179. object of the recipient's JID.
  1180. :param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID`
  1181. object of the sender's JID.
  1182. :param string sid: Optional ID value for the stanza.
  1183. :param parent: Optionally specify a parent stanza object will
  1184. contain this substanza.
  1185. """
  1186. #: The default XMPP client namespace
  1187. namespace = 'jabber:client'
  1188. #: There is a small set of attributes which apply to all XMPP stanzas:
  1189. #: the stanza type, the to and from JIDs, the stanza ID, and, especially
  1190. #: in the case of an Iq stanza, a payload.
  1191. interfaces = set(('type', 'to', 'from', 'id', 'payload'))
  1192. #: A basic set of allowed values for the ``'type'`` interface.
  1193. types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
  1194. def __init__(self, stream=None, xml=None, stype=None,
  1195. sto=None, sfrom=None, sid=None, parent=None):
  1196. self.stream = stream
  1197. if stream is not None:
  1198. self.namespace = stream.default_ns
  1199. ElementBase.__init__(self, xml, parent)
  1200. if stype is not None:
  1201. self['type'] = stype
  1202. if sto is not None:
  1203. self['to'] = sto
  1204. if sfrom is not None:
  1205. self['from'] = sfrom
  1206. if sid is not None:
  1207. self['id'] = sid
  1208. self.tag = "{%s}%s" % (self.namespace, self.name)
  1209. def set_type(self, value):
  1210. """Set the stanza's ``'type'`` attribute.
  1211. Only type values contained in :attr:`types` are accepted.
  1212. :param string value: One of the values contained in :attr:`types`
  1213. """
  1214. if value in self.types:
  1215. self.xml.attrib['type'] = value
  1216. return self
  1217. def get_to(self):
  1218. """Return the value of the stanza's ``'to'`` attribute."""
  1219. return JID(self._get_attr('to'))
  1220. def set_to(self, value):
  1221. """Set the ``'to'`` attribute of the stanza.
  1222. :param value: A string or :class:`sleekxmpp.xmlstream.JID` object
  1223. representing the recipient's JID.
  1224. """
  1225. return self._set_attr('to', str(value))
  1226. def get_from(self):
  1227. """Return the value of the stanza's ``'from'`` attribute."""
  1228. return JID(self._get_attr('from'))
  1229. def set_from(self, value):
  1230. """Set the 'from' attribute of the stanza.
  1231. Arguments:
  1232. from -- A string or JID object representing the sender's JID.
  1233. """
  1234. return self._set_attr('from', str(value))
  1235. def get_payload(self):
  1236. """Return a list of XML objects contained in the stanza."""
  1237. return list(self.xml)
  1238. def set_payload(self, value):
  1239. """Add XML content to the stanza.
  1240. :param value: Either an XML or a stanza object, or a list
  1241. of XML or stanza objects.
  1242. """
  1243. if not isinstance(value, list):
  1244. value = [value]
  1245. for val in value:
  1246. self.append(val)
  1247. return self
  1248. def del_payload(self):
  1249. """Remove the XML contents of the stanza."""
  1250. self.clear()
  1251. return self
  1252. def reply(self, clear=True):
  1253. """Prepare the stanza for sending a reply.
  1254. Swaps the ``'from'`` and ``'to'`` attributes.
  1255. If ``clear=True``, then also remove the stanza's
  1256. contents to make room for the reply content.
  1257. For client streams, the ``'from'`` attribute is removed.
  1258. :param bool clear: Indicates if the stanza's contents should be
  1259. removed. Defaults to ``True``.
  1260. """
  1261. # if it's a component, use from
  1262. if self.stream and hasattr(self.stream, "is_component") and \
  1263. self.stream.is_component:
  1264. self['from'], self['to'] = self['to'], self['from']
  1265. else:
  1266. self['to'] = self['from']
  1267. del self['from']
  1268. if clear:
  1269. self.clear()
  1270. return self
  1271. def error(self):
  1272. """Set the stanza's type to ``'error'``."""
  1273. self['type'] = 'error'
  1274. return self
  1275. def unhandled(self):
  1276. """Called if no handlers have been registered to process this stanza.
  1277. Meant to be overridden.
  1278. """
  1279. pass
  1280. def exception(self, e):
  1281. """Handle exceptions raised during stanza processing.
  1282. Meant to be overridden.
  1283. """
  1284. log.exception('Error handling {%s}%s stanza', self.namespace,
  1285. self.name)
  1286. def send(self, now=False):
  1287. """Queue the stanza to be sent on the XML stream.
  1288. :param bool now: Indicates if the queue should be skipped and the
  1289. stanza sent immediately. Useful for stream
  1290. initialization. Defaults to ``False``.
  1291. """
  1292. self.stream.send(self, now=now)
  1293. def __copy__(self):
  1294. """Return a copy of the stanza object that does not share the
  1295. same underlying XML object, but does share the same XML stream.
  1296. """
  1297. return self.__class__(xml=copy.deepcopy(self.xml),
  1298. stream=self.stream)
  1299. def __str__(self, top_level_ns=False):
  1300. """Serialize the stanza's XML to a string.
  1301. :param bool top_level_ns: Display the top-most namespace.
  1302. Defaults to ``False``.
  1303. """
  1304. xmlns = self.stream.default_ns if self.stream else ''
  1305. return tostring(self.xml, xmlns=xmlns,
  1306. stream=self.stream,
  1307. top_level=(self.stream is None))
  1308. #: A JSON/dictionary version of the XML content exposed through
  1309. #: the stanza interfaces::
  1310. #:
  1311. #: >>> msg = Message()
  1312. #: >>> msg.values
  1313. #: {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
  1314. #: 'to': , 'type': 'normal', 'id': '', 'subject': ''}
  1315. #:
  1316. #: Likewise, assigning to the :attr:`values` will change the XML
  1317. #: content::
  1318. #:
  1319. #: >>> msg = Message()
  1320. #: >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
  1321. #: >>> msg
  1322. #: '<message to="user@example.com"><body>Hi!</body></message>'
  1323. #:
  1324. #: Child stanzas are exposed as nested dictionaries.
  1325. ElementBase.values = property(ElementBase._get_stanza_values,
  1326. ElementBase._set_stanza_values)
  1327. # To comply with PEP8, method names now use underscores.
  1328. # Deprecated method names are re-mapped for backwards compatibility.
  1329. ElementBase.initPlugin = ElementBase.init_plugin
  1330. ElementBase._getAttr = ElementBase._get_attr
  1331. ElementBase._setAttr = ElementBase._set_attr
  1332. ElementBase._delAttr = ElementBase._del_attr
  1333. ElementBase._getSubText = ElementBase._get_sub_text
  1334. ElementBase._setSubText = ElementBase._set_sub_text
  1335. ElementBase._delSub = ElementBase._del_sub
  1336. ElementBase.getStanzaValues = ElementBase._get_stanza_values
  1337. ElementBase.setStanzaValues = ElementBase._set_stanza_values
  1338. StanzaBase.setType = StanzaBase.set_type
  1339. StanzaBase.getTo = StanzaBase.get_to
  1340. StanzaBase.setTo = StanzaBase.set_to
  1341. StanzaBase.getFrom = StanzaBase.get_from
  1342. StanzaBase.setFrom = StanzaBase.set_from
  1343. StanzaBase.getPayload = StanzaBase.get_payload
  1344. StanzaBase.setPayload = StanzaBase.set_payload
  1345. StanzaBase.delPayload = StanzaBase.del_payload