1 # -*- coding: utf-8 -*-
3 slixmpp.xmlstream.stanzabase
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 module implements a wrapper layer for XML objects
7 that allows them to be treated like dictionaries.
9 Part of Slixmpp: The Slick XMPP Library
11 :copyright: (c) 2011 Nathanael C. Fritz
12 :license: MIT, see LICENSE for more details
15 from __future__
import with_statement
, unicode_literals
20 from xml
.etree
import cElementTree
as ET
22 from slixmpp
.xmlstream
import JID
23 from slixmpp
.xmlstream
.tostring
import tostring
24 from slixmpp
.thirdparty
import OrderedDict
27 log
= logging
.getLogger(__name__
)
30 # Used to check if an argument is an XML object.
31 XML_TYPE
= type(ET
.Element('xml'))
34 XML_NS
= 'http://www.w3.org/XML/1998/namespace'
37 def register_stanza_plugin(stanza
, plugin
, iterable
=False, overrides
=False):
39 Associate a stanza object as a plugin for another stanza.
41 >>> from slixmpp.xmlstream import register_stanza_plugin
42 >>> register_stanza_plugin(Iq, CustomStanza)
44 Plugin stanzas marked as iterable will be included in the list of
45 substanzas for the parent, using ``parent['substanzas']``. If the
46 attribute ``plugin_multi_attrib`` was defined for the plugin, then
47 the substanza set can be filtered to only instances of the plugin
48 class. For example, given a plugin class ``Foo`` with
49 ``plugin_multi_attrib = 'foos'`` then::
53 would return a collection of all ``Foo`` substanzas.
55 :param class stanza: The class of the parent stanza.
56 :param class plugin: The class of the plugin stanza.
57 :param bool iterable: Indicates if the plugin stanza should be
58 included in the parent stanza's iterable
59 ``'substanzas'`` interface results.
60 :param bool overrides: Indicates if the plugin should be allowed
61 to override the interface handlers for
62 the parent stanza, based on the plugin's
65 .. versionadded:: 1.0-Beta1
66 Made ``register_stanza_plugin`` the default name. The prior
67 ``registerStanzaPlugin`` function name remains as an alias.
69 tag
= "{%s}%s" % (plugin
.namespace
, plugin
.name
)
71 # Prevent weird memory reference gotchas by ensuring
72 # that the parent stanza class has its own set of
73 # plugin info maps and is not using the mappings from
74 # an ancestor class (like ElementBase).
75 plugin_info
= ('plugin_attrib_map', 'plugin_tag_map',
76 'plugin_iterables', 'plugin_overrides')
77 for attr
in plugin_info
:
78 info
= getattr(stanza
, attr
)
79 setattr(stanza
, attr
, info
.copy())
81 stanza
.plugin_attrib_map
[plugin
.plugin_attrib
] = plugin
82 stanza
.plugin_tag_map
[tag
] = plugin
85 stanza
.plugin_iterables
.add(plugin
)
86 if plugin
.plugin_multi_attrib
:
87 multiplugin
= multifactory(plugin
, plugin
.plugin_multi_attrib
)
88 register_stanza_plugin(stanza
, multiplugin
)
90 for interface
in plugin
.overrides
:
91 stanza
.plugin_overrides
[interface
] = plugin
.plugin_attrib
94 # To maintain backwards compatibility for now, preserve the camel case name.
95 registerStanzaPlugin
= register_stanza_plugin
98 def multifactory(stanza
, plugin_attrib
):
100 Returns a ElementBase class for handling reoccuring child stanzas
103 def plugin_filter(self
):
104 return lambda x
: isinstance(x
, self
._multistanza
)
106 def plugin_lang_filter(self
, lang
):
107 return lambda x
: isinstance(x
, self
._multistanza
) and \
110 class Multi(ElementBase
):
112 Template class for multifactory
114 def setup(self
, xml
=None):
115 self
.xml
= ET
.Element('')
117 def get_multi(self
, lang
=None):
118 parent
= self
.parent()
119 if not lang
or lang
== '*':
120 res
= filter(plugin_filter(self
), parent
)
122 res
= filter(plugin_filter(self
, lang
), parent
)
125 def set_multi(self
, val
, lang
=None):
126 parent
= self
.parent()
127 del_multi
= getattr(self
, 'del_%s' % plugin_attrib
)
132 def del_multi(self
, lang
=None):
133 parent
= self
.parent()
134 if not lang
or lang
== '*':
135 res
= filter(plugin_filter(self
), parent
)
137 res
= filter(plugin_filter(self
, lang
), parent
)
140 del parent
.plugins
[(plugin_attrib
, None)]
141 parent
.loaded_plugins
.remove(plugin_attrib
)
143 parent
.xml
.remove(self
.xml
)
147 for stanza
in list(res
):
148 parent
.iterables
.remove(stanza
)
149 parent
.xml
.remove(stanza
.xml
)
151 Multi
.is_extension
= True
152 Multi
.plugin_attrib
= plugin_attrib
153 Multi
._multistanza
= stanza
154 Multi
.interfaces
= set([plugin_attrib
])
155 Multi
.lang_interfaces
= set([plugin_attrib
])
156 setattr(Multi
, "get_%s" % plugin_attrib
, get_multi
)
157 setattr(Multi
, "set_%s" % plugin_attrib
, set_multi
)
158 setattr(Multi
, "del_%s" % plugin_attrib
, del_multi
)
162 def fix_ns(xpath
, split
=False, propagate_ns
=True, default_ns
=''):
163 """Apply the stanza's namespace to elements in an XPath expression.
165 :param string xpath: The XPath expression to fix with namespaces.
166 :param bool split: Indicates if the fixed XPath should be left as a
167 list of element names with namespaces. Defaults to
168 False, which returns a flat string path.
169 :param bool propagate_ns: Overrides propagating parent element
170 namespaces to child elements. Useful if
171 you wish to simply split an XPath that has
172 non-specified namespaces, and child and
173 parent namespaces are known not to always
174 match. Defaults to True.
177 # Split the XPath into a series of blocks, where a block
178 # is started by an element with a namespace.
179 ns_blocks
= xpath
.split('{')
180 for ns_block
in ns_blocks
:
182 # Apply the found namespace to following elements
183 # that do not have namespaces.
184 namespace
= ns_block
.split('}')[0]
185 elements
= ns_block
.split('}')[1].split('/')
187 # Apply the stanza's namespace to the following
188 # elements since no namespace was provided.
189 namespace
= default_ns
190 elements
= ns_block
.split('/')
192 for element
in elements
:
194 # Skip empty entry artifacts from splitting.
195 if propagate_ns
and element
[0] != '*':
196 tag
= '{%s}%s' % (namespace
, element
)
202 return '/'.join(fixed
)
205 class ElementBase(object):
208 The core of Slixmpp's stanza XML manipulation and handling is provided
209 by ElementBase. ElementBase wraps XML cElementTree objects and enables
210 access to the XML contents through dictionary syntax, similar in style
211 to the Ruby XMPP library Blather's stanza implementation.
213 Stanzas are defined by their name, namespace, and interfaces. For
214 example, a simplistic Message stanza could be defined as::
216 >>> class Message(ElementBase):
218 ... namespace = "jabber:client"
219 ... interfaces = set(('to', 'from', 'type', 'body'))
220 ... sub_interfaces = set(('body',))
222 The resulting Message stanza's contents may be accessed as so::
224 >>> message['to'] = "user@example.com"
225 >>> message['body'] = "Hi!"
228 >>> del message['body']
232 The interface values map to either custom access methods, stanza
233 XML attributes, or (if the interface is also in sub_interfaces) the
234 text contents of a stanza's subelement.
236 Custom access methods may be created by adding methods of the
237 form "getInterface", "setInterface", or "delInterface", where
238 "Interface" is the titlecase version of the interface name.
240 Stanzas may be extended through the use of plugins. A plugin
241 is simply a stanza that has a plugin_attrib value. For example::
243 >>> class MessagePlugin(ElementBase):
244 ... name = "custom_plugin"
245 ... namespace = "custom"
246 ... interfaces = set(('useful_thing', 'custom'))
247 ... plugin_attrib = "custom"
249 The plugin stanza class must be associated with its intended
250 container stanza by using register_stanza_plugin as so::
252 >>> register_stanza_plugin(Message, MessagePlugin)
254 The plugin may then be accessed as if it were built-in to the parent
257 >>> message['custom']['useful_thing'] = 'foo'
259 If a plugin provides an interface that is the same as the plugin's
260 plugin_attrib value, then the plugin's interface may be assigned
261 directly from the parent stanza, as shown below, but retrieving
262 information will require all interfaces to be used, as so::
264 >>> # Same as using message['custom']['custom']
265 >>> message['custom'] = 'bar'
266 >>> # Must use all interfaces
267 >>> message['custom']['custom']
270 If the plugin sets :attr:`is_extension` to ``True``, then both setting
271 and getting an interface value that is the same as the plugin's
272 plugin_attrib value will work, as so::
274 >>> message['custom'] = 'bar' # Using is_extension=True
275 >>> message['custom']
279 :param xml: Initialize the stanza object with an existing XML object.
280 :param parent: Optionally specify a parent stanza object will
281 contain this substanza.
284 #: The XML tag name of the element, not including any namespace
285 #: prefixes. For example, an :class:`ElementBase` object for
286 #: ``<message />`` would use ``name = 'message'``.
289 #: The XML namespace for the element. Given ``<foo xmlns="bar" />``,
290 #: then ``namespace = "bar"`` should be used. The default namespace
291 #: is ``jabber:client`` since this is being used in an XMPP library.
292 namespace
= 'jabber:client'
294 #: For :class:`ElementBase` subclasses which are intended to be used
295 #: as plugins, the ``plugin_attrib`` value defines the plugin name.
296 #: Plugins may be accessed by using the ``plugin_attrib`` value as
297 #: the interface. An example using ``plugin_attrib = 'foo'``::
299 #: register_stanza_plugin(Message, FooPlugin)
301 #: msg['foo']['an_interface_from_the_foo_plugin']
302 plugin_attrib
= 'plugin'
304 #: For :class:`ElementBase` subclasses that are intended to be an
305 #: iterable group of items, the ``plugin_multi_attrib`` value defines
306 #: an interface for the parent stanza which returns the entire group
307 #: of matching substanzas. So the following are equivalent::
309 #: # Given stanza class Foo, with plugin_multi_attrib = 'foos'
311 #: filter(isinstance(item, Foo), parent['substanzas'])
312 plugin_multi_attrib
= ''
314 #: The set of keys that the stanza provides for accessing and
315 #: manipulating the underlying XML object. This set may be augmented
316 #: with the :attr:`plugin_attrib` value of any registered
318 interfaces
= set(('type', 'to', 'from', 'id', 'payload'))
320 #: A subset of :attr:`interfaces` which maps interfaces to direct
321 #: subelements of the underlying XML object. Using this set, the text
322 #: of these subelements may be set, retrieved, or removed without
323 #: needing to define custom methods.
324 sub_interfaces
= set()
326 #: A subset of :attr:`interfaces` which maps the presence of
327 #: subelements to boolean values. Using this set allows for quickly
328 #: checking for the existence of empty subelements like ``<required />``.
330 #: .. versionadded:: 1.1
331 bool_interfaces
= set()
333 #: .. versionadded:: 1.1.2
334 lang_interfaces
= set()
336 #: In some cases you may wish to override the behaviour of one of the
337 #: parent stanza's interfaces. The ``overrides`` list specifies the
338 #: interface name and access method to be overridden. For example,
339 #: to override setting the parent's ``'condition'`` interface you
342 #: overrides = ['set_condition']
344 #: Getting and deleting the ``'condition'`` interface would not
347 #: .. versionadded:: 1.0-Beta5
350 #: If you need to add a new interface to an existing stanza, you
351 #: can create a plugin and set ``is_extension = True``. Be sure
352 #: to set the :attr:`plugin_attrib` value to the desired interface
353 #: name, and that it is the only interface listed in
354 #: :attr:`interfaces`. Requests for the new interface from the
355 #: parent stanza will be passed to the plugin directly.
357 #: .. versionadded:: 1.0-Beta5
360 #: A map of interface operations to the overriding functions.
361 #: For example, after overriding the ``set`` operation for
362 #: the interface ``body``, :attr:`plugin_overrides` would be::
364 #: {'set_body': <some function>}
366 #: .. versionadded: 1.0-Beta5
367 plugin_overrides
= {}
369 #: A mapping of the :attr:`plugin_attrib` values of registered
370 #: plugins to their respective classes.
371 plugin_attrib_map
= {}
373 #: A mapping of root element tag names (in ``'{namespace}elementname'``
374 #: format) to the plugin classes responsible for them.
377 #: The set of stanza classes that can be iterated over using
378 #: the 'substanzas' interface. Classes are added to this set
379 #: when registering a plugin with ``iterable=True``::
381 #: register_stanza_plugin(DiscoInfo, DiscoItem, iterable=True)
383 #: .. versionadded:: 1.0-Beta5
384 plugin_iterables
= set()
386 #: A deprecated version of :attr:`plugin_iterables` that remains
387 #: for backward compatibility. It required a parent stanza to
388 #: know beforehand what stanza classes would be iterable::
390 #: class DiscoItem(ElementBase):
393 #: class DiscoInfo(ElementBase):
394 #: subitem = (DiscoItem, )
397 #: .. deprecated:: 1.0-Beta5
400 #: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
403 def __init__(self
, xml
=None, parent
=None):
406 #: The underlying XML object for the stanza. It is a standard
407 #: :class:`xml.etree.cElementTree` object.
410 #: An ordered dictionary of plugin stanzas, mapped by their
411 #: :attr:`plugin_attrib` value.
412 self
.plugins
= OrderedDict()
413 self
.loaded_plugins
= set()
415 #: A list of child stanzas whose class is included in
416 #: :attr:`plugin_iterables`.
419 #: The name of the tag for the stanza's root element. It is the
420 #: same as calling :meth:`tag_name()` and is formatted as
421 #: ``'{namespace}elementname'``.
422 self
.tag
= self
.tag_name()
424 #: A :class:`weakref.weakref` to the parent stanza, if there is one.
425 #: If not, then :attr:`parent` is ``None``.
427 if parent
is not None:
428 if not isinstance(parent
, weakref
.ReferenceType
):
429 self
.parent
= weakref
.ref(parent
)
433 if self
.subitem
is not None:
434 for sub
in self
.subitem
:
435 self
.plugin_iterables
.add(sub
)
438 # If we generated our own XML, then everything is ready.
441 # Initialize values using provided XML
442 for child
in self
.xml
:
443 if child
.tag
in self
.plugin_tag_map
:
444 plugin_class
= self
.plugin_tag_map
[child
.tag
]
445 self
.init_plugin(plugin_class
.plugin_attrib
,
449 def setup(self
, xml
=None):
450 """Initialize the stanza's XML contents.
452 Will return ``True`` if XML was generated according to the stanza's
453 definition instead of building a stanza object from an existing
456 :param xml: An existing XML object to use for the stanza's content
457 instead of generating new XML.
464 # Generate XML from the stanza definition
465 for ename
in self
.name
.split('/'):
466 new
= ET
.Element("{%s}%s" % (self
.namespace
, ename
))
472 if self
.parent
is not None:
473 self
.parent().xml
.append(self
.xml
)
475 # We had to generate XML
478 # We did not generate XML
481 def enable(self
, attrib
, lang
=None):
482 """Enable and initialize a stanza plugin.
484 Alias for :meth:`init_plugin`.
486 :param string attrib: The :attr:`plugin_attrib` value of the
489 return self
.init_plugin(attrib
, lang
)
491 def _get_plugin(self
, name
, lang
=None, check
=False):
493 lang
= self
.get_lang()
495 if name
not in self
.plugin_attrib_map
:
498 plugin_class
= self
.plugin_attrib_map
[name
]
500 if plugin_class
.is_extension
:
501 if (name
, None) in self
.plugins
:
502 return self
.plugins
[(name
, None)]
504 return None if check
else self
.init_plugin(name
, lang
)
506 if (name
, lang
) in self
.plugins
:
507 return self
.plugins
[(name
, lang
)]
509 return None if check
else self
.init_plugin(name
, lang
)
511 def init_plugin(self
, attrib
, lang
=None, existing_xml
=None, reuse
=True):
512 """Enable and initialize a stanza plugin.
514 :param string attrib: The :attr:`plugin_attrib` value of the
517 default_lang
= self
.get_lang()
521 plugin_class
= self
.plugin_attrib_map
[attrib
]
523 if plugin_class
.is_extension
and (attrib
, None) in self
.plugins
:
524 return self
.plugins
[(attrib
, None)]
525 if reuse
and (attrib
, lang
) in self
.plugins
:
526 return self
.plugins
[(attrib
, lang
)]
528 plugin
= plugin_class(parent
=self
, xml
=existing_xml
)
530 if plugin
.is_extension
:
531 self
.plugins
[(attrib
, None)] = plugin
533 if lang
!= default_lang
:
534 plugin
['lang'] = lang
535 self
.plugins
[(attrib
, lang
)] = plugin
537 if plugin_class
in self
.plugin_iterables
:
538 self
.iterables
.append(plugin
)
539 if plugin_class
.plugin_multi_attrib
:
540 self
.init_plugin(plugin_class
.plugin_multi_attrib
)
542 self
.loaded_plugins
.add(attrib
)
546 def _get_stanza_values(self
):
547 """Return A JSON/dictionary version of the XML content
548 exposed through the stanza's interfaces::
552 {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
553 'to': , 'type': 'normal', 'id': '', 'subject': ''}
555 Likewise, assigning to :attr:`values` will change the XML
559 >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
561 '<message to="user@example.com"><body>Hi!</body></message>'
563 .. versionadded:: 1.0-Beta1
566 values
['lang'] = self
['lang']
567 for interface
in self
.interfaces
:
568 values
[interface
] = self
[interface
]
569 if interface
in self
.lang_interfaces
:
570 values
['%s|*' % interface
] = self
['%s|*' % interface
]
571 for plugin
, stanza
in self
.plugins
.items():
572 lang
= stanza
['lang']
574 values
['%s|%s' % (plugin
[0], lang
)] = stanza
.values
576 values
[plugin
[0]] = stanza
.values
579 for stanza
in self
.iterables
:
580 iterables
.append(stanza
.values
)
581 iterables
[-1]['__childtag__'] = stanza
.tag
582 values
['substanzas'] = iterables
585 def _set_stanza_values(self
, values
):
586 """Set multiple stanza interface values using a dictionary.
588 Stanza plugin values may be set using nested dictionaries.
590 :param values: A dictionary mapping stanza interface with values.
591 Plugin interfaces may accept a nested dictionary that
592 will be used recursively.
594 .. versionadded:: 1.0-Beta1
596 iterable_interfaces
= [p
.plugin_attrib
for \
597 p
in self
.plugin_iterables
]
600 self
['lang'] = values
['lang']
602 if 'substanzas' in values
:
603 # Remove existing substanzas
604 for stanza
in self
.iterables
:
606 self
.xml
.remove(stanza
.xml
)
612 for subdict
in values
['substanzas']:
613 if '__childtag__' in subdict
:
614 for subclass
in self
.plugin_iterables
:
615 child_tag
= "{%s}%s" % (subclass
.namespace
,
617 if subdict
['__childtag__'] == child_tag
:
618 sub
= subclass(parent
=self
)
620 self
.iterables
.append(sub
)
622 for interface
, value
in values
.items():
623 full_interface
= interface
624 interface_lang
= ('%s|' % interface
).split('|')
625 interface
= interface_lang
[0]
626 lang
= interface_lang
[1] or self
.get_lang()
628 if interface
== 'lang':
630 elif interface
== 'substanzas':
632 elif interface
in self
.interfaces
:
633 self
[full_interface
] = value
634 elif interface
in self
.plugin_attrib_map
:
635 if interface
not in iterable_interfaces
:
636 plugin
= self
._get
_plugin
(interface
, lang
)
638 plugin
.values
= value
641 def __getitem__(self
, attrib
):
642 """Return the value of a stanza interface using dict-like syntax.
649 Stanza interfaces are typically mapped directly to the underlying XML
650 object, but can be overridden by the presence of a ``get_attrib``
651 method (or ``get_foo`` where the interface is named ``'foo'``, etc).
653 The search order for interface value retrieval for an interface
656 1. The list of substanzas (``'substanzas'``)
657 2. The result of calling the ``get_foo`` override handler.
658 3. The result of calling ``get_foo``.
659 4. The result of calling ``getFoo``.
660 5. The contents of the ``foo`` subelement, if ``foo`` is listed
661 in :attr:`sub_interfaces`.
662 6. True or False depending on the existence of a ``foo``
663 subelement and ``foo`` is in :attr:`bool_interfaces`.
664 7. The value of the ``foo`` attribute of the XML object.
665 8. The plugin named ``'foo'``
668 :param string attrib: The name of the requested stanza interface.
671 attrib_lang
= ('%s|' % attrib
).split('|')
672 attrib
= attrib_lang
[0]
673 lang
= attrib_lang
[1] or None
676 if lang
and attrib
in self
.lang_interfaces
:
677 kwargs
['lang'] = lang
679 if attrib
== 'substanzas':
680 return self
.iterables
681 elif attrib
in self
.interfaces
or attrib
== 'lang':
682 get_method
= "get_%s" % attrib
.lower()
683 get_method2
= "get%s" % attrib
.title()
685 if self
.plugin_overrides
:
686 name
= self
.plugin_overrides
.get(get_method
, None)
688 plugin
= self
._get
_plugin
(name
, lang
)
690 handler
= getattr(plugin
, get_method
, None)
692 return handler(**kwargs
)
694 if hasattr(self
, get_method
):
695 return getattr(self
, get_method
)(**kwargs
)
696 elif hasattr(self
, get_method2
):
697 return getattr(self
, get_method2
)(**kwargs
)
699 if attrib
in self
.sub_interfaces
:
700 return self
._get
_sub
_text
(attrib
, lang
=lang
)
701 elif attrib
in self
.bool_interfaces
:
702 elem
= self
.xml
.find('{%s}%s' % (self
.namespace
, attrib
))
703 return elem
is not None
705 return self
._get
_attr
(attrib
)
706 elif attrib
in self
.plugin_attrib_map
:
707 plugin
= self
._get
_plugin
(attrib
, lang
)
708 if plugin
and plugin
.is_extension
:
709 return plugin
[full_attrib
]
714 def __setitem__(self
, attrib
, value
):
715 """Set the value of a stanza interface using dictionary-like syntax.
719 >>> msg['body'] = "Hi!"
723 Stanza interfaces are typically mapped directly to the underlying XML
724 object, but can be overridden by the presence of a ``set_attrib``
725 method (or ``set_foo`` where the interface is named ``'foo'``, etc).
727 The effect of interface value assignment for an interface
728 named ``'foo'`` will be one of:
730 1. Delete the interface's contents if the value is None.
731 2. Call the ``set_foo`` override handler, if it exists.
732 3. Call ``set_foo``, if it exists.
733 4. Call ``setFoo``, if it exists.
734 5. Set the text of a ``foo`` element, if ``'foo'`` is
735 in :attr:`sub_interfaces`.
736 6. Add or remove an empty subelement ``foo``
737 if ``foo`` is in :attr:`bool_interfaces`.
738 7. Set the value of a top level XML attribute named ``foo``.
739 8. Attempt to pass the value to a plugin named ``'foo'`` using
740 the plugin's ``'foo'`` interface.
743 :param string attrib: The name of the stanza interface to modify.
744 :param value: The new value of the stanza interface.
747 attrib_lang
= ('%s|' % attrib
).split('|')
748 attrib
= attrib_lang
[0]
749 lang
= attrib_lang
[1] or None
752 if lang
and attrib
in self
.lang_interfaces
:
753 kwargs
['lang'] = lang
755 if attrib
in self
.interfaces
or attrib
== 'lang':
756 if value
is not None:
757 set_method
= "set_%s" % attrib
.lower()
758 set_method2
= "set%s" % attrib
.title()
760 if self
.plugin_overrides
:
761 name
= self
.plugin_overrides
.get(set_method
, None)
763 plugin
= self
._get
_plugin
(name
, lang
)
765 handler
= getattr(plugin
, set_method
, None)
767 return handler(value
, **kwargs
)
769 if hasattr(self
, set_method
):
770 getattr(self
, set_method
)(value
, **kwargs
)
771 elif hasattr(self
, set_method2
):
772 getattr(self
, set_method2
)(value
, **kwargs
)
774 if attrib
in self
.sub_interfaces
:
776 return self
._set
_all
_sub
_text
(attrib
,
779 return self
._set
_sub
_text
(attrib
, text
=value
,
781 elif attrib
in self
.bool_interfaces
:
783 return self
._set
_sub
_text
(attrib
, '',
787 return self
._set
_sub
_text
(attrib
, '',
791 self
._set
_attr
(attrib
, value
)
793 self
.__delitem
__(attrib
)
794 elif attrib
in self
.plugin_attrib_map
:
795 plugin
= self
._get
_plugin
(attrib
, lang
)
797 plugin
[full_attrib
] = value
800 def __delitem__(self
, attrib
):
801 """Delete the value of a stanza interface using dict-like syntax.
805 >>> msg['body'] = "Hi!"
812 Stanza interfaces are typically mapped directly to the underlyig XML
813 object, but can be overridden by the presence of a ``del_attrib``
814 method (or ``del_foo`` where the interface is named ``'foo'``, etc).
816 The effect of deleting a stanza interface value named ``foo`` will be
819 1. Call ``del_foo`` override handler, if it exists.
820 2. Call ``del_foo``, if it exists.
821 3. Call ``delFoo``, if it exists.
822 4. Delete ``foo`` element, if ``'foo'`` is in
823 :attr:`sub_interfaces`.
824 5. Remove ``foo`` element if ``'foo'`` is in
825 :attr:`bool_interfaces`.
826 6. Delete top level XML attribute named ``foo``.
827 7. Remove the ``foo`` plugin, if it was loaded.
830 :param attrib: The name of the affected stanza interface.
833 attrib_lang
= ('%s|' % attrib
).split('|')
834 attrib
= attrib_lang
[0]
835 lang
= attrib_lang
[1] or None
838 if lang
and attrib
in self
.lang_interfaces
:
839 kwargs
['lang'] = lang
841 if attrib
in self
.interfaces
or attrib
== 'lang':
842 del_method
= "del_%s" % attrib
.lower()
843 del_method2
= "del%s" % attrib
.title()
845 if self
.plugin_overrides
:
846 name
= self
.plugin_overrides
.get(del_method
, None)
848 plugin
= self
._get
_plugin
(attrib
, lang
)
850 handler
= getattr(plugin
, del_method
, None)
852 return handler(**kwargs
)
854 if hasattr(self
, del_method
):
855 getattr(self
, del_method
)(**kwargs
)
856 elif hasattr(self
, del_method2
):
857 getattr(self
, del_method2
)(**kwargs
)
859 if attrib
in self
.sub_interfaces
:
860 return self
._del
_sub
(attrib
, lang
=lang
)
861 elif attrib
in self
.bool_interfaces
:
862 return self
._del
_sub
(attrib
, lang
=lang
)
864 self
._del
_attr
(attrib
)
865 elif attrib
in self
.plugin_attrib_map
:
866 plugin
= self
._get
_plugin
(attrib
, lang
, check
=True)
869 if plugin
.is_extension
:
870 del plugin
[full_attrib
]
871 del self
.plugins
[(attrib
, None)]
873 del self
.plugins
[(attrib
, plugin
['lang'])]
874 self
.loaded_plugins
.remove(attrib
)
876 self
.xml
.remove(plugin
.xml
)
881 def _set_attr(self
, name
, value
):
882 """Set the value of a top level attribute of the XML object.
884 If the new value is None or an empty string, then the attribute will
887 :param name: The name of the attribute.
888 :param value: The new value of the attribute, or None or '' to
891 if value
is None or value
== '':
892 self
.__delitem
__(name
)
894 self
.xml
.attrib
[name
] = value
896 def _del_attr(self
, name
):
897 """Remove a top level attribute of the XML object.
899 :param name: The name of the attribute.
901 if name
in self
.xml
.attrib
:
902 del self
.xml
.attrib
[name
]
904 def _get_attr(self
, name
, default
=''):
905 """Return the value of a top level attribute of the XML object.
907 In case the attribute has not been set, a default value can be
908 returned instead. An empty string is returned if no other default
911 :param name: The name of the attribute.
912 :param default: Optional value to return if the attribute has not
913 been set. An empty string is returned otherwise.
915 return self
.xml
.attrib
.get(name
, default
)
917 def _get_sub_text(self
, name
, default
='', lang
=None):
918 """Return the text contents of a sub element.
920 In case the element does not exist, or it has no textual content,
921 a default value can be returned instead. An empty string is returned
922 if no other default is supplied.
924 :param name: The name or XPath expression of the element.
925 :param default: Optional default to return if the element does
926 not exists. An empty string is returned otherwise.
928 name
= self
._fix
_ns
(name
)
930 return self
._get
_all
_sub
_text
(name
, default
, None)
932 default_lang
= self
.get_lang()
936 stanzas
= self
.xml
.findall(name
)
939 for stanza
in stanzas
:
940 if stanza
.attrib
.get('{%s}lang' % XML_NS
, default_lang
) == lang
:
941 if stanza
.text
is None:
946 def _get_all_sub_text(self
, name
, default
='', lang
=None):
947 name
= self
._fix
_ns
(name
)
949 default_lang
= self
.get_lang()
950 results
= OrderedDict()
951 stanzas
= self
.xml
.findall(name
)
953 for stanza
in stanzas
:
954 stanza_lang
= stanza
.attrib
.get('{%s}lang' % XML_NS
,
956 if not lang
or lang
== '*' or stanza_lang
== lang
:
957 results
[stanza_lang
] = stanza
.text
960 def _set_sub_text(self
, name
, text
=None, keep
=False, lang
=None):
961 """Set the text contents of a sub element.
963 In case the element does not exist, a element will be created,
964 and its text contents will be set.
966 If the text is set to an empty string, or None, then the
967 element will be removed, unless keep is set to True.
969 :param name: The name or XPath expression of the element.
970 :param text: The new textual content of the element. If the text
971 is an empty string or None, the element will be removed
972 unless the parameter keep is True.
973 :param keep: Indicates if the element should be kept if its text is
974 removed. Defaults to False.
976 default_lang
= self
.get_lang()
980 if not text
and not keep
:
981 return self
._del
_sub
(name
, lang
=lang
)
983 path
= self
._fix
_ns
(name
, split
=True)
987 # The first goal is to find the parent of the subelement, or, if
988 # we can't find that, the closest grandparent element.
990 search_order
= path
[:-1]
992 parent
= self
.xml
.find('/'.join(search_order
))
993 ename
= search_order
.pop()
994 if parent
is not None:
997 missing_path
.append(ename
)
998 missing_path
.reverse()
1000 # Find all existing elements that match the desired
1001 # element path (there may be multiples due to different
1002 # languages values).
1003 if parent
is not None:
1004 elements
= self
.xml
.findall('/'.join(path
))
1009 # Insert the remaining grandparent elements that don't exist yet.
1010 for ename
in missing_path
:
1011 element
= ET
.Element(ename
)
1012 parent
.append(element
)
1015 # Re-use an existing element with the proper language, if one exists.
1016 for element
in elements
:
1017 elang
= element
.attrib
.get('{%s}lang' % XML_NS
, default_lang
)
1018 if not lang
and elang
== default_lang
or lang
and lang
== elang
:
1022 # No useable element exists, so create a new one.
1023 element
= ET
.Element(name
)
1025 if lang
and lang
!= default_lang
:
1026 element
.attrib
['{%s}lang' % XML_NS
] = lang
1027 parent
.append(element
)
1030 def _set_all_sub_text(self
, name
, values
, keep
=False, lang
=None):
1031 self
._del
_sub
(name
, lang
)
1032 for value_lang
, value
in values
.items():
1033 if not lang
or lang
== '*' or value_lang
== lang
:
1034 self
._set
_sub
_text
(name
, text
=value
,
1038 def _del_sub(self
, name
, all
=False, lang
=None):
1039 """Remove sub elements that match the given name or XPath.
1041 If the element is in a path, then any parent elements that become
1042 empty after deleting the element may also be deleted if requested
1043 by setting all=True.
1045 :param name: The name or XPath expression for the element(s) to remove.
1046 :param bool all: If True, remove all empty elements in the path to the
1047 deleted element. Defaults to False.
1049 path
= self
._fix
_ns
(name
, split
=True)
1050 original_target
= path
[-1]
1052 default_lang
= self
.get_lang()
1056 for level
, _
in enumerate(path
):
1057 # Generate the paths to the target elements and their parent.
1058 element_path
= "/".join(path
[:len(path
) - level
])
1059 parent_path
= "/".join(path
[:len(path
) - level
- 1])
1061 elements
= self
.xml
.findall(element_path
)
1062 parent
= self
.xml
.find(parent_path
)
1067 for element
in elements
:
1068 if element
.tag
== original_target
or not list(element
):
1069 # Only delete the originally requested elements, and
1070 # any parent elements that have become empty.
1071 elem_lang
= element
.attrib
.get('{%s}lang' % XML_NS
,
1073 if lang
== '*' or elem_lang
== lang
:
1074 parent
.remove(element
)
1076 # If we don't want to delete elements up the tree, stop
1077 # after deleting the first level of elements.
1080 def match(self
, xpath
):
1081 """Compare a stanza object with an XPath-like expression.
1083 If the XPath matches the contents of the stanza object, the match
1086 The XPath expression may include checks for stanza attributes.
1089 'presence@show=xa@priority=2/status'
1091 Would match a presence stanza whose show value is set to ``'xa'``,
1092 has a priority value of ``'2'``, and has a status element.
1094 :param string xpath: The XPath expression to check against. It
1095 may be either a string or a list of element
1096 names with attribute checks.
1098 if not isinstance(xpath
, list):
1099 xpath
= self
._fix
_ns
(xpath
, split
=True, propagate_ns
=False)
1101 # Extract the tag name and attribute checks for the first XPath node.
1102 components
= xpath
[0].split('@')
1104 attributes
= components
[1:]
1106 if tag
not in (self
.name
, "{%s}%s" % (self
.namespace
, self
.name
)) and \
1107 tag
not in self
.loaded_plugins
and tag
not in self
.plugin_attrib
:
1108 # The requested tag is not in this stanza, so no match.
1111 # Check the rest of the XPath against any substanzas.
1112 matched_substanzas
= False
1113 for substanza
in self
.iterables
:
1116 matched_substanzas
= substanza
.match(xpath
[1:])
1117 if matched_substanzas
:
1120 # Check attribute values.
1121 for attribute
in attributes
:
1122 name
, value
= attribute
.split('=')
1123 if self
[name
] != value
:
1126 # Check sub interfaces.
1129 if next_tag
in self
.sub_interfaces
and self
[next_tag
]:
1132 # Attempt to continue matching the XPath using the stanza's plugins.
1133 if not matched_substanzas
and len(xpath
) > 1:
1134 # Convert {namespace}tag@attribs to just tag
1135 next_tag
= xpath
[1].split('@')[0].split('}')[-1]
1136 langs
= [name
[1] for name
in self
.plugins
if name
[0] == next_tag
]
1138 plugin
= self
._get
_plugin
(next_tag
, lang
)
1139 if plugin
and plugin
.match(xpath
[1:]):
1143 # Everything matched.
1146 def find(self
, xpath
):
1147 """Find an XML object in this stanza given an XPath expression.
1149 Exposes ElementTree interface for backwards compatibility.
1153 Matching on attribute values is not supported in Python 2.6
1156 :param string xpath: An XPath expression matching a single
1159 return self
.xml
.find(xpath
)
1161 def findall(self
, xpath
):
1162 """Find multiple XML objects in this stanza given an XPath expression.
1164 Exposes ElementTree interface for backwards compatibility.
1168 Matching on attribute values is not supported in Python 2.6
1171 :param string xpath: An XPath expression matching multiple
1174 return self
.xml
.findall(xpath
)
1176 def get(self
, key
, default
=None):
1177 """Return the value of a stanza interface.
1179 If the found value is None or an empty string, return the supplied
1182 Allows stanza objects to be used like dictionaries.
1184 :param string key: The name of the stanza interface to check.
1185 :param default: Value to return if the stanza interface has a value
1186 of ``None`` or ``""``. Will default to returning None.
1189 if value
is None or value
== '':
1194 """Return the names of all stanza interfaces provided by the
1197 Allows stanza objects to be used like dictionaries.
1200 out
+= [x
for x
in self
.interfaces
]
1201 out
+= [x
for x
in self
.loaded_plugins
]
1204 out
.append('substanzas')
1207 def append(self
, item
):
1208 """Append either an XML object or a substanza to this stanza object.
1210 If a substanza object is appended, it will be added to the list
1211 of iterable stanzas.
1213 Allows stanza objects to be used like lists.
1215 :param item: Either an XML object or a stanza object to add to
1216 this stanza's contents.
1218 if not isinstance(item
, ElementBase
):
1219 if type(item
) == XML_TYPE
:
1220 return self
.appendxml(item
)
1223 self
.xml
.append(item
.xml
)
1224 self
.iterables
.append(item
)
1225 if item
.__class
__ in self
.plugin_iterables
:
1226 if item
.__class
__.plugin_multi_attrib
:
1227 self
.init_plugin(item
.__class
__.plugin_multi_attrib
)
1228 elif item
.__class
__ == self
.plugin_tag_map
.get(item
.tag_name(), None):
1229 self
.init_plugin(item
.plugin_attrib
,
1230 existing_xml
=item
.xml
,
1234 def appendxml(self
, xml
):
1235 """Append an XML object to the stanza's XML.
1237 The added XML will not be included in the list of
1238 iterable substanzas.
1240 :param XML xml: The XML object to add to the stanza.
1242 self
.xml
.append(xml
)
1245 def pop(self
, index
=0):
1246 """Remove and return the last substanza in the list of
1247 iterable substanzas.
1249 Allows stanza objects to be used like lists.
1251 :param int index: The index of the substanza to remove.
1253 substanza
= self
.iterables
.pop(index
)
1254 self
.xml
.remove(substanza
.xml
)
1258 """Return the next iterable substanza."""
1259 return self
.__next
__()
1262 """Remove all XML element contents and plugins.
1264 Any attribute values will be preserved.
1266 for child
in list(self
.xml
):
1267 self
.xml
.remove(child
)
1269 for plugin
in list(self
.plugins
.keys()):
1270 del self
.plugins
[plugin
]
1275 """Return the namespaced name of the stanza's root element.
1277 The format for the tag name is::
1279 '{namespace}elementname'
1281 For example, for the stanza ``<foo xmlns="bar" />``,
1282 ``stanza.tag_name()`` would return ``"{bar}foo"``.
1284 return "{%s}%s" % (cls
.namespace
, cls
.name
)
1286 def get_lang(self
, lang
=None):
1287 result
= self
.xml
.attrib
.get('{%s}lang' % XML_NS
, '')
1288 if not result
and self
.parent
and self
.parent():
1289 return self
.parent()['lang']
1292 def set_lang(self
, lang
):
1294 attr
= '{%s}lang' % XML_NS
1296 self
.xml
.attrib
[attr
] = lang
1299 attr
= '{%s}lang' % XML_NS
1300 if attr
in self
.xml
.attrib
:
1301 del self
.xml
.attrib
[attr
]
1305 """Return the stanza object itself.
1307 Older implementations of stanza objects used XML objects directly,
1308 requiring the use of ``.attrib`` to access attribute values.
1310 Use of the dictionary syntax with the stanza object itself for
1311 accessing stanza interfaces is preferred.
1317 def _fix_ns(self
, xpath
, split
=False, propagate_ns
=True):
1318 return fix_ns(xpath
, split
=split
,
1319 propagate_ns
=propagate_ns
,
1320 default_ns
=self
.namespace
)
1322 def __eq__(self
, other
):
1323 """Compare the stanza object with another to test for equality.
1325 Stanzas are equal if their interfaces return the same values,
1326 and if they are both instances of ElementBase.
1328 :param ElementBase other: The stanza object to compare against.
1330 if not isinstance(other
, ElementBase
):
1333 # Check that this stanza is a superset of the other stanza.
1334 values
= self
.values
1335 for key
in other
.keys():
1336 if key
not in values
or values
[key
] != other
[key
]:
1339 # Check that the other stanza is a superset of this stanza.
1340 values
= other
.values
1341 for key
in self
.keys():
1342 if key
not in values
or values
[key
] != self
[key
]:
1345 # Both stanzas are supersets of each other, therefore they
1349 def __ne__(self
, other
):
1350 """Compare the stanza object with another to test for inequality.
1352 Stanzas are not equal if their interfaces return different values,
1353 or if they are not both instances of ElementBase.
1355 :param ElementBase other: The stanza object to compare against.
1357 return not self
.__eq
__(other
)
1360 """Stanza objects should be treated as True in boolean contexts.
1366 def __nonzero__(self
):
1367 """Stanza objects should be treated as True in boolean contexts.
1374 """Return the number of iterable substanzas in this stanza."""
1375 return len(self
.iterables
)
1378 """Return an iterator object for the stanza's substanzas.
1380 The iterator is the stanza object itself. Attempting to use two
1381 iterators on the same stanza at the same time is discouraged.
1387 """Return the next iterable substanza."""
1389 if self
._index
> len(self
.iterables
):
1392 return self
.iterables
[self
._index
- 1]
1395 """Return a copy of the stanza object that does not share the same
1396 underlying XML object.
1398 return self
.__class
__(xml
=copy
.deepcopy(self
.xml
), parent
=self
.parent
)
1400 def __str__(self
, top_level_ns
=True):
1401 """Return a string serialization of the underlying XML object.
1403 .. seealso:: :ref:`tostring`
1405 :param bool top_level_ns: Display the top-most namespace.
1408 return tostring(self
.xml
, xmlns
='',
1412 """Use the stanza's serialized XML as its representation."""
1413 return self
.__str
__()
1416 class StanzaBase(ElementBase
):
1419 StanzaBase provides the foundation for all other stanza objects used
1420 by Slixmpp, and defines a basic set of interfaces common to nearly
1421 all stanzas. These interfaces are the ``'id'``, ``'type'``, ``'to'``,
1422 and ``'from'`` attributes. An additional interface, ``'payload'``, is
1423 available to access the XML contents of the stanza. Most stanza objects
1424 will provided more specific interfaces, however.
1426 **Stanza Interfaces:**
1428 :id: An optional id value that can be used to associate stanzas
1429 :to: A JID object representing the recipient's JID.
1430 :from: A JID object representing the sender's JID.
1432 :type: The type of stanza, typically will be ``'normal'``,
1433 ``'error'``, ``'get'``, or ``'set'``, etc.
1434 :payload: The XML contents of the stanza.
1436 :param XMLStream stream: Optional :class:`slixmpp.xmlstream.XMLStream`
1437 object responsible for sending this stanza.
1438 :param XML xml: Optional XML contents to initialize stanza values.
1439 :param string stype: Optional stanza type value.
1440 :param sto: Optional string or :class:`slixmpp.xmlstream.JID`
1441 object of the recipient's JID.
1442 :param sfrom: Optional string or :class:`slixmpp.xmlstream.JID`
1443 object of the sender's JID.
1444 :param string sid: Optional ID value for the stanza.
1445 :param parent: Optionally specify a parent stanza object will
1446 contain this substanza.
1449 #: The default XMPP client namespace
1450 namespace
= 'jabber:client'
1452 #: There is a small set of attributes which apply to all XMPP stanzas:
1453 #: the stanza type, the to and from JIDs, the stanza ID, and, especially
1454 #: in the case of an Iq stanza, a payload.
1455 interfaces
= set(('type', 'to', 'from', 'id', 'payload'))
1457 #: A basic set of allowed values for the ``'type'`` interface.
1458 types
= set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
1460 def __init__(self
, stream
=None, xml
=None, stype
=None,
1461 sto
=None, sfrom
=None, sid
=None, parent
=None):
1462 self
.stream
= stream
1463 if stream
is not None:
1464 self
.namespace
= stream
.default_ns
1465 ElementBase
.__init
__(self
, xml
, parent
)
1466 if stype
is not None:
1467 self
['type'] = stype
1470 if sfrom
is not None:
1471 self
['from'] = sfrom
1474 self
.tag
= "{%s}%s" % (self
.namespace
, self
.name
)
1476 def set_type(self
, value
):
1477 """Set the stanza's ``'type'`` attribute.
1479 Only type values contained in :attr:`types` are accepted.
1481 :param string value: One of the values contained in :attr:`types`
1483 if value
in self
.types
:
1484 self
.xml
.attrib
['type'] = value
1488 """Return the value of the stanza's ``'to'`` attribute."""
1489 return JID(self
._get
_attr
('to'))
1491 def set_to(self
, value
):
1492 """Set the ``'to'`` attribute of the stanza.
1494 :param value: A string or :class:`slixmpp.xmlstream.JID` object
1495 representing the recipient's JID.
1497 return self
._set
_attr
('to', str(value
))
1500 """Return the value of the stanza's ``'from'`` attribute."""
1501 return JID(self
._get
_attr
('from'))
1503 def set_from(self
, value
):
1504 """Set the 'from' attribute of the stanza.
1507 from -- A string or JID object representing the sender's JID.
1509 return self
._set
_attr
('from', str(value
))
1511 def get_payload(self
):
1512 """Return a list of XML objects contained in the stanza."""
1513 return list(self
.xml
)
1515 def set_payload(self
, value
):
1516 """Add XML content to the stanza.
1518 :param value: Either an XML or a stanza object, or a list
1519 of XML or stanza objects.
1521 if not isinstance(value
, list):
1527 def del_payload(self
):
1528 """Remove the XML contents of the stanza."""
1532 def reply(self
, clear
=True):
1533 """Prepare the stanza for sending a reply.
1535 Swaps the ``'from'`` and ``'to'`` attributes.
1537 If ``clear=True``, then also remove the stanza's
1538 contents to make room for the reply content.
1540 For client streams, the ``'from'`` attribute is removed.
1542 :param bool clear: Indicates if the stanza's contents should be
1543 removed. Defaults to ``True``.
1545 # if it's a component, use from
1546 if self
.stream
and hasattr(self
.stream
, "is_component") and \
1547 self
.stream
.is_component
:
1548 self
['from'], self
['to'] = self
['to'], self
['from']
1550 self
['to'] = self
['from']
1557 """Set the stanza's type to ``'error'``."""
1558 self
['type'] = 'error'
1561 def unhandled(self
):
1562 """Called if no handlers have been registered to process this stanza.
1564 Meant to be overridden.
1568 def exception(self
, e
):
1569 """Handle exceptions raised during stanza processing.
1571 Meant to be overridden.
1573 log
.exception('Error handling {%s}%s stanza', self
.namespace
,
1577 """Queue the stanza to be sent on the XML stream.
1579 :param bool now: Indicates if the queue should be skipped and the
1580 stanza sent immediately. Useful for stream
1581 initialization. Defaults to ``False``.
1583 self
.stream
.send(self
)
1586 """Return a copy of the stanza object that does not share the
1587 same underlying XML object, but does share the same XML stream.
1589 return self
.__class
__(xml
=copy
.deepcopy(self
.xml
),
1592 def __str__(self
, top_level_ns
=False):
1593 """Serialize the stanza's XML to a string.
1595 :param bool top_level_ns: Display the top-most namespace.
1596 Defaults to ``False``.
1598 xmlns
= self
.stream
.default_ns
if self
.stream
else ''
1599 return tostring(self
.xml
, xmlns
=xmlns
,
1601 top_level
=(self
.stream
is None))
1604 #: A JSON/dictionary version of the XML content exposed through
1605 #: the stanza interfaces::
1607 #: >>> msg = Message()
1609 #: {'body': '', 'from': , 'mucnick': '', 'mucroom': '',
1610 #: 'to': , 'type': 'normal', 'id': '', 'subject': ''}
1612 #: Likewise, assigning to the :attr:`values` will change the XML
1615 #: >>> msg = Message()
1616 #: >>> msg.values = {'body': 'Hi!', 'to': 'user@example.com'}
1618 #: '<message to="user@example.com"><body>Hi!</body></message>'
1620 #: Child stanzas are exposed as nested dictionaries.
1621 ElementBase
.values
= property(ElementBase
._get
_stanza
_values
,
1622 ElementBase
._set
_stanza
_values
)
1625 # To comply with PEP8, method names now use underscores.
1626 # Deprecated method names are re-mapped for backwards compatibility.
1627 ElementBase
.initPlugin
= ElementBase
.init_plugin
1628 ElementBase
._getAttr
= ElementBase
._get
_attr
1629 ElementBase
._setAttr
= ElementBase
._set
_attr
1630 ElementBase
._delAttr
= ElementBase
._del
_attr
1631 ElementBase
._getSubText
= ElementBase
._get
_sub
_text
1632 ElementBase
._setSubText
= ElementBase
._set
_sub
_text
1633 ElementBase
._delSub
= ElementBase
._del
_sub
1634 ElementBase
.getStanzaValues
= ElementBase
._get
_stanza
_values
1635 ElementBase
.setStanzaValues
= ElementBase
._set
_stanza
_values
1637 StanzaBase
.setType
= StanzaBase
.set_type
1638 StanzaBase
.getTo
= StanzaBase
.get_to
1639 StanzaBase
.setTo
= StanzaBase
.set_to
1640 StanzaBase
.getFrom
= StanzaBase
.get_from
1641 StanzaBase
.setFrom
= StanzaBase
.set_from
1642 StanzaBase
.getPayload
= StanzaBase
.get_payload
1643 StanzaBase
.setPayload
= StanzaBase
.set_payload
1644 StanzaBase
.delPayload
= StanzaBase
.del_payload