summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLance Stout <lancestout@gmail.com>2012-06-05 16:54:26 -0700
committerLance Stout <lancestout@gmail.com>2012-06-18 22:00:33 -0700
commit181aea737d5bce9479795b58c29b5a92da3bd48b (patch)
tree75fed30d668542593e12a208bc88775c19d6d6b6
parentee702f40710219290508f95ac01df50a96d04f3c (diff)
downloadslixmpp-181aea737d5bce9479795b58c29b5a92da3bd48b.tar.gz
slixmpp-181aea737d5bce9479795b58c29b5a92da3bd48b.tar.bz2
slixmpp-181aea737d5bce9479795b58c29b5a92da3bd48b.tar.xz
slixmpp-181aea737d5bce9479795b58c29b5a92da3bd48b.zip
Add initial support for xml:lang for streams and stanza plugins.
Remaining items are suitable default actions for language supporting interfaces.
-rw-r--r--sleekxmpp/basexmpp.py11
-rw-r--r--sleekxmpp/clientxmpp.py11
-rw-r--r--sleekxmpp/plugins/xep_0033.py2
-rw-r--r--sleekxmpp/plugins/xep_0092/version.py4
-rw-r--r--sleekxmpp/stanza/roster.py1
-rw-r--r--sleekxmpp/test/sleektest.py8
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py260
-rw-r--r--sleekxmpp/xmlstream/tostring.py14
-rw-r--r--sleekxmpp/xmlstream/xmlstream.py9
-rw-r--r--tests/test_stanza_element.py12
10 files changed, 243 insertions, 89 deletions
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index 43ea6063..4f48f809 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -31,6 +31,7 @@ from sleekxmpp.xmlstream import XMLStream, JID
from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback
+from sleekxmpp.xmlstream.stanzabase import XML_NS
from sleekxmpp.features import *
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
@@ -180,6 +181,8 @@ class BaseXMPP(XMLStream):
:param xml: The incoming stream's root element.
"""
self.stream_id = xml.get('id', '')
+ self.stream_version = xml.get('version', '')
+ self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
def process(self, *args, **kwargs):
"""Initialize plugins and begin processing the XML stream.
@@ -272,7 +275,9 @@ class BaseXMPP(XMLStream):
def Message(self, *args, **kwargs):
"""Create a Message stanza associated with this stream."""
- return Message(self, *args, **kwargs)
+ msg = Message(self, *args, **kwargs)
+ msg['lang'] = self.default_lang
+ return msg
def Iq(self, *args, **kwargs):
"""Create an Iq stanza associated with this stream."""
@@ -280,7 +285,9 @@ class BaseXMPP(XMLStream):
def Presence(self, *args, **kwargs):
"""Create a Presence stanza associated with this stream."""
- return Presence(self, *args, **kwargs)
+ pres = Presence(self, *args, **kwargs)
+ pres['lang'] = self.default_lang
+ return pres
def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
"""Create a new Iq stanza with a given Id and from JID.
diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py
index 94ced031..8a941867 100644
--- a/sleekxmpp/clientxmpp.py
+++ b/sleekxmpp/clientxmpp.py
@@ -60,8 +60,8 @@ class ClientXMPP(BaseXMPP):
:param escape_quotes: **Deprecated.**
"""
- def __init__(self, jid, password, ssl=False, plugin_config={},
- plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
+ def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
+ escape_quotes=True, sasl_mech=None, lang='en'):
BaseXMPP.__init__(self, jid, 'jabber:client')
self.set_jid(jid)
@@ -69,15 +69,18 @@ class ClientXMPP(BaseXMPP):
self.plugin_config = plugin_config
self.plugin_whitelist = plugin_whitelist
self.default_port = 5222
+ self.default_lang = lang
self.credentials = {}
self.password = password
- self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % (
+ self.stream_header = "<stream:stream to='%s' %s %s %s %s>" % (
self.boundjid.host,
"xmlns:stream='%s'" % self.stream_ns,
- "xmlns='%s'" % self.default_ns)
+ "xmlns='%s'" % self.default_ns,
+ "xml:lang='%s'" % self.default_lang,
+ "version='1.0'")
self.stream_footer = "</stream:stream>"
self.features = set()
diff --git a/sleekxmpp/plugins/xep_0033.py b/sleekxmpp/plugins/xep_0033.py
index feef5a13..9276b807 100644
--- a/sleekxmpp/plugins/xep_0033.py
+++ b/sleekxmpp/plugins/xep_0033.py
@@ -42,6 +42,8 @@ class Addresses(ElementBase):
self.delAddresses(set_type)
for addr in addresses:
addr = dict(addr)
+ if 'lang' in addr:
+ del addr['lang']
# Remap 'type' to 'atype' to match the add method
if set_type is not None:
addr['type'] = set_type
diff --git a/sleekxmpp/plugins/xep_0092/version.py b/sleekxmpp/plugins/xep_0092/version.py
index c6223c10..5e84b2ff 100644
--- a/sleekxmpp/plugins/xep_0092/version.py
+++ b/sleekxmpp/plugins/xep_0092/version.py
@@ -79,5 +79,7 @@ class XEP_0092(BasePlugin):
result = iq.send()
if result and result['type'] != 'error':
- return result['software_version'].values
+ values = result['software_version'].values
+ del values['lang']
+ return values
return False
diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py
index 253c2aea..3fae42cc 100644
--- a/sleekxmpp/stanza/roster.py
+++ b/sleekxmpp/stanza/roster.py
@@ -102,6 +102,7 @@ class Roster(ElementBase):
# Remove extra JID reference to keep everything
# backward compatible
del items[item['jid']]['jid']
+ del items[item['jid']]['lang']
return items
def del_items(self):
diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py
index ba79dce8..92a7688a 100644
--- a/sleekxmpp/test/sleektest.py
+++ b/sleekxmpp/test/sleektest.py
@@ -333,6 +333,9 @@ class SleekTest(unittest.TestCase):
# Remove unique ID prefix to make it easier to test
self.xmpp._id_prefix = ''
self.xmpp._disconnect_wait_for_threads = False
+ self.xmpp.default_lang = None
+ self.xmpp.peer_default_lang = None
+
# We will use this to wait for the session_start event
# for live connections.
@@ -386,6 +389,7 @@ class SleekTest(unittest.TestCase):
sid='',
stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client",
+ default_lang="en",
version="1.0",
xml_header=True):
"""
@@ -413,6 +417,8 @@ class SleekTest(unittest.TestCase):
parts.append('from="%s"' % sfrom)
if sid:
parts.append('id="%s"' % sid)
+ if default_lang:
+ parts.append('xml:lang="%s"' % default_lang)
parts.append('version="%s"' % version)
parts.append('xmlns:stream="%s"' % stream_ns)
parts.append('xmlns="%s"' % default_ns)
@@ -564,6 +570,7 @@ class SleekTest(unittest.TestCase):
sid='',
stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client",
+ default_lang="en",
version="1.0",
xml_header=False,
timeout=1):
@@ -585,6 +592,7 @@ class SleekTest(unittest.TestCase):
header = self.make_header(sto, sfrom, sid,
stream_ns=stream_ns,
default_ns=default_ns,
+ default_lang=default_lang,
version=version,
xml_header=xml_header)
sent_header = self.xmpp.socket.next_sent(timeout)
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index 5d572437..f06573bf 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -12,6 +12,8 @@
:license: MIT, see LICENSE for more details
"""
+from __future__ import with_statement, unicode_literals
+
import copy
import logging
import weakref
@@ -29,6 +31,9 @@ log = logging.getLogger(__name__)
XML_TYPE = type(ET.Element('xml'))
+XML_NS = 'http://www.w3.org/XML/1998/namespace'
+
+
def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
"""
Associate a stanza object as a plugin for another stanza.
@@ -101,20 +106,26 @@ def multifactory(stanza, plugin_attrib):
def setup(self, xml=None):
self.xml = ET.Element('')
- def get_multi(self):
+ def get_multi(self, lang=None):
parent = self.parent()
- res = filter(lambda sub: isinstance(sub, self._multistanza), parent)
+ if lang is None:
+ res = filter(lambda sub: isinstance(sub, self._multistanza), parent)
+ else:
+ res = filter(lambda sub: isinstance(sub, self._multistanza) and sub['lang'] == lang, parent)
return list(res)
- def set_multi(self, val):
+ def set_multi(self, val, lang=None):
parent = self.parent()
del parent[self.plugin_attrib]
for sub in val:
parent.append(sub)
- def del_multi(self):
+ def del_multi(self, lang=None):
parent = self.parent()
- res = filter(lambda sub: isinstance(sub, self._multistanza), parent)
+ if lang is None:
+ res = filter(lambda sub: isinstance(sub, self._multistanza), parent)
+ else:
+ res = filter(lambda sub: isinstance(sub, self._multistanza) and sub['lang'] == lang, parent)
for stanza in list(res):
parent.iterables.remove(stanza)
parent.xml.remove(stanza.xml)
@@ -122,7 +133,8 @@ def multifactory(stanza, plugin_attrib):
Multi.is_extension = True
Multi.plugin_attrib = plugin_attrib
Multi._multistanza = stanza
- Multi.interfaces = (plugin_attrib,)
+ Multi.interfaces = set([plugin_attrib])
+ Multi.lang_interfaces = set([plugin_attrib])
setattr(Multi, "get_%s" % plugin_attrib, get_multi)
setattr(Multi, "set_%s" % plugin_attrib, set_multi)
setattr(Multi, "del_%s" % plugin_attrib, del_multi)
@@ -289,14 +301,17 @@ class ElementBase(object):
#: subelements of the underlying XML object. Using this set, the text
#: of these subelements may be set, retrieved, or removed without
#: needing to define custom methods.
- sub_interfaces = tuple()
+ sub_interfaces = set()
#: A subset of :attr:`interfaces` which maps the presence of
#: subelements to boolean values. Using this set allows for quickly
#: checking for the existence of empty subelements like ``<required />``.
#:
#: .. versionadded:: 1.1
- bool_interfaces = tuple()
+ bool_interfaces = set()
+
+ #: .. versionadded:: 1.1.2
+ lang_interfaces = set()
#: In some cases you may wish to override the behaviour of one of the
#: parent stanza's interfaces. The ``overrides`` list specifies the
@@ -363,7 +378,7 @@ class ElementBase(object):
subitem = set()
#: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
- xml_ns = 'http://www.w3.org/XML/1998/namespace'
+ xml_ns = XML_NS
def __init__(self, xml=None, parent=None):
self._index = 0
@@ -375,6 +390,7 @@ class ElementBase(object):
#: An ordered dictionary of plugin stanzas, mapped by their
#: :attr:`plugin_attrib` value.
self.plugins = OrderedDict()
+ self.loaded_plugins = set()
#: A list of child stanzas whose class is included in
#: :attr:`plugin_iterables`.
@@ -385,6 +401,12 @@ class ElementBase(object):
#: ``'{namespace}elementname'``.
self.tag = self.tag_name()
+ if 'lang' not in self.interfaces:
+ if isinstance(self.interfaces, tuple):
+ self.interfaces += ('lang',)
+ else:
+ self.interfaces.add('lang')
+
#: A :class:`weakref.weakref` to the parent stanza, if there is one.
#: If not, then :attr:`parent` is ``None``.
self.parent = None
@@ -406,10 +428,9 @@ class ElementBase(object):
for child in self.xml.getchildren():
if child.tag in self.plugin_tag_map:
plugin_class = self.plugin_tag_map[child.tag]
- plugin = plugin_class(child, self)
- self.plugins[plugin.plugin_attrib] = plugin
- if plugin_class in self.plugin_iterables:
- self.iterables.append(plugin)
+ self.init_plugin(plugin_class.plugin_attrib,
+ existing_xml=child,
+ reuse=False)
def setup(self, xml=None):
"""Initialize the stanza's XML contents.
@@ -443,7 +464,7 @@ class ElementBase(object):
# We did not generate XML
return False
- def enable(self, attrib):
+ def enable(self, attrib, lang=None):
"""Enable and initialize a stanza plugin.
Alias for :meth:`init_plugin`.
@@ -451,24 +472,60 @@ class ElementBase(object):
:param string attrib: The :attr:`plugin_attrib` value of the
plugin to enable.
"""
- return self.init_plugin(attrib)
+ return self.init_plugin(attrib, lang)
+
+ def _get_plugin(self, name, lang=None):
+ if lang is None:
+ lang = self.get_lang()
+ plugin_class = self.plugin_attrib_map[name]
+ if plugin_class.is_extension:
+ if (name, None) in self.plugins:
+ return self.plugins[(name, None)]
+ else:
+ return self.init_plugin(name, lang)
+ else:
+ if (name, lang) in self.plugins:
+ return self.plugins[(name, lang)]
+ else:
+ return self.init_plugin(name, lang)
- def init_plugin(self, attrib):
+ def init_plugin(self, attrib, lang=None, existing_xml=None, reuse=True):
"""Enable and initialize a stanza plugin.
:param string attrib: The :attr:`plugin_attrib` value of the
plugin to enable.
"""
- if attrib not in self.plugins:
- plugin_class = self.plugin_attrib_map[attrib]
+ if lang is None:
+ lang = self.get_lang()
+
+ plugin_class = self.plugin_attrib_map[attrib]
+
+ if plugin_class.is_extension and (attrib, None) in self.plugins:
+ return self.plugins[(attrib, None)]
+ if reuse and (attrib, lang) in self.plugins:
+ return self.plugins[(attrib, lang)]
+
+ if existing_xml is None:
existing_xml = self.xml.find(plugin_class.tag_name())
- plugin = plugin_class(parent=self, xml=existing_xml)
- self.plugins[attrib] = plugin
- if plugin_class in self.plugin_iterables:
- self.iterables.append(plugin)
- if plugin_class.plugin_multi_attrib:
- self.init_plugin(plugin_class.plugin_multi_attrib)
- return self
+ if existing_xml is not None and existing_xml.attrib.get('{%s}lang' % XML_NS, '') != lang:
+ existing_xml = None
+
+ plugin = plugin_class(parent=self, xml=existing_xml)
+
+ if plugin.is_extension:
+ self.plugins[(attrib, None)] = plugin
+ else:
+ plugin['lang'] = lang
+ self.plugins[(attrib, lang)] = plugin
+
+ if plugin_class in self.plugin_iterables:
+ self.iterables.append(plugin)
+ if plugin_class.plugin_multi_attrib:
+ self.init_plugin(plugin_class.plugin_multi_attrib)
+
+ self.loaded_plugins.add(attrib)
+
+ return plugin
def _get_stanza_values(self):
"""Return A JSON/dictionary version of the XML content
@@ -493,7 +550,11 @@ class ElementBase(object):
for interface in self.interfaces:
values[interface] = self[interface]
for plugin, stanza in self.plugins.items():
- values[plugin] = stanza.values
+ lang = stanza['lang']
+ if lang:
+ values['%s|%s' % (plugin, lang)] = stanza.values
+ else:
+ values[plugin[0]] = stanza.values
if self.iterables:
iterables = []
for stanza in self.iterables:
@@ -517,6 +578,11 @@ class ElementBase(object):
p in self.plugin_iterables]
for interface, value in values.items():
+ full_interface = interface
+ interface_lang = ('%s|' % interface).split('|')
+ interface = interface_lang[0]
+ lang = interface_lang[1] or self.get_lang()
+
if interface == 'substanzas':
# Remove existing substanzas
for stanza in self.iterables:
@@ -538,9 +604,8 @@ class ElementBase(object):
self[interface] = value
elif interface in self.plugin_attrib_map:
if interface not in iterable_interfaces:
- if interface not in self.plugins:
- self.init_plugin(interface)
- self.plugins[interface].values = value
+ plugin = self._get_plugin(interface, lang)
+ plugin.values = value
return self
def __getitem__(self, attrib):
@@ -572,6 +637,15 @@ class ElementBase(object):
:param string attrib: The name of the requested stanza interface.
"""
+ full_attrib = attrib
+ attrib_lang = ('%s|' % attrib).split('|')
+ attrib = attrib_lang[0]
+ lang = attrib_lang[1] or ''
+
+ kwargs = {}
+ if lang and attrib in self.lang_interfaces:
+ kwargs['lang'] = lang
+
if attrib == 'substanzas':
return self.iterables
elif attrib in self.interfaces:
@@ -579,18 +653,17 @@ class ElementBase(object):
get_method2 = "get%s" % attrib.title()
if self.plugin_overrides:
- plugin = self.plugin_overrides.get(get_method, None)
- if plugin:
- if plugin not in self.plugins:
- self.init_plugin(plugin)
- handler = getattr(self.plugins[plugin], get_method, None)
+ name = self.plugin_overrides.get(get_method, None)
+ if name:
+ plugin = self._get_plugin(name, lang)
+ handler = getattr(plugin, get_method, None)
if handler:
- return handler()
+ return handler(**kwargs)
if hasattr(self, get_method):
- return getattr(self, get_method)()
+ return getattr(self, get_method)(**kwargs)
elif hasattr(self, get_method2):
- return getattr(self, get_method2)()
+ return getattr(self, get_method2)(**kwargs)
else:
if attrib in self.sub_interfaces:
return self._get_sub_text(attrib)
@@ -600,11 +673,10 @@ class ElementBase(object):
else:
return self._get_attr(attrib)
elif attrib in self.plugin_attrib_map:
- if attrib not in self.plugins:
- self.init_plugin(attrib)
- if self.plugins[attrib].is_extension:
- return self.plugins[attrib][attrib]
- return self.plugins[attrib]
+ plugin = self._get_plugin(attrib, lang)
+ if plugin.is_extension:
+ return plugin[full_attrib]
+ return plugin
else:
return ''
@@ -640,25 +712,32 @@ class ElementBase(object):
:param string attrib: The name of the stanza interface to modify.
:param value: The new value of the stanza interface.
"""
+ full_attrib = attrib
+ attrib_lang = ('%s|' % attrib).split('|')
+ attrib = attrib_lang[0]
+ lang = attrib_lang[1] or ''
+
+ kwargs = {}
+ if lang and attrib in self.lang_interfaces:
+ kwargs['lang'] = lang
+
if attrib in self.interfaces:
if value is not None:
set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title()
if self.plugin_overrides:
- plugin = self.plugin_overrides.get(set_method, None)
- if plugin:
- if plugin not in self.plugins:
- self.init_plugin(plugin)
- handler = getattr(self.plugins[plugin],
- set_method, None)
+ name = self.plugin_overrides.get(set_method, None)
+ if name:
+ plugin = self._get_plugin(name, lang)
+ handler = getattr(plugin, set_method, None)
if handler:
- return handler(value)
+ return handler(value, **kwargs)
if hasattr(self, set_method):
- getattr(self, set_method)(value,)
+ getattr(self, set_method)(value, **kwargs)
elif hasattr(self, set_method2):
- getattr(self, set_method2)(value,)
+ getattr(self, set_method2)(value, **kwargs)
else:
if attrib in self.sub_interfaces:
return self._set_sub_text(attrib, text=value)
@@ -672,9 +751,8 @@ class ElementBase(object):
else:
self.__delitem__(attrib)
elif attrib in self.plugin_attrib_map:
- if attrib not in self.plugins:
- self.init_plugin(attrib)
- self.plugins[attrib][attrib] = value
+ plugin = self._get_plugin(attrib, lang)
+ plugin[full_attrib] = value
return self
def __delitem__(self, attrib):
@@ -709,23 +787,31 @@ class ElementBase(object):
:param attrib: The name of the affected stanza interface.
"""
+ full_attrib = attrib
+ attrib_lang = ('%s|' % attrib).split('|')
+ attrib = attrib_lang[0]
+ lang = attrib_lang[1] or ''
+
+ kwargs = {}
+ if lang and attrib in self.lang_interfaces:
+ kwargs['lang'] = lang
+
if attrib in self.interfaces:
del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title()
if self.plugin_overrides:
- plugin = self.plugin_overrides.get(del_method, None)
- if plugin:
- if plugin not in self.plugins:
- self.init_plugin(plugin)
- handler = getattr(self.plugins[plugin], del_method, None)
+ name = self.plugin_overrides.get(del_method, None)
+ if name:
+ plugin = self._get_plugin(attrib, lang)
+ handler = getattr(plugin, del_method, None)
if handler:
- return handler()
+ return handler(**kwargs)
if hasattr(self, del_method):
- getattr(self, del_method)()
+ getattr(self, del_method)(**kwargs)
elif hasattr(self, del_method2):
- getattr(self, del_method2)()
+ getattr(self, del_method2)(**kwargs)
else:
if attrib in self.sub_interfaces:
return self._del_sub(attrib)
@@ -734,15 +820,17 @@ class ElementBase(object):
else:
self._del_attr(attrib)
elif attrib in self.plugin_attrib_map:
- if attrib in self.plugins:
- xml = self.plugins[attrib].xml
- if self.plugins[attrib].is_extension:
- del self.plugins[attrib][attrib]
- del self.plugins[attrib]
- try:
- self.xml.remove(xml)
- except:
- pass
+ plugin = self._get_plugin(attrib, lang)
+ if plugin.is_extension:
+ del plugin[full_attrib]
+ del self.plugins[(attrib, None)]
+ else:
+ del self.plugins[(attrib, lang)]
+ self.loaded_plugins.remove(attrib)
+ try:
+ self.xml.remove(plugin.xml)
+ except:
+ pass
return self
def _set_attr(self, name, value):
@@ -903,7 +991,7 @@ class ElementBase(object):
attributes = components[1:]
if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \
- tag not in self.plugins and tag not in self.plugin_attrib:
+ tag not in self.loaded_plugins and tag not in self.plugin_attrib:
# The requested tag is not in this stanza, so no match.
return False
@@ -932,10 +1020,11 @@ class ElementBase(object):
if not matched_substanzas and len(xpath) > 1:
# Convert {namespace}tag@attribs to just tag
next_tag = xpath[1].split('@')[0].split('}')[-1]
- if next_tag in self.plugins:
- return self.plugins[next_tag].match(xpath[1:])
- else:
- return False
+ langs = [name[1] for name in self.plugins if name[0] == next_tag]
+ for lang in langs:
+ if self._get_plugin(next_tag, lang).match(xpath[1:]):
+ return True
+ return False
# Everything matched.
return True
@@ -995,7 +1084,7 @@ class ElementBase(object):
"""
out = []
out += [x for x in self.interfaces]
- out += [x for x in self.plugins]
+ out += [x for x in self.loaded_plugins]
if self.iterables:
out.append('substanzas')
return out
@@ -1075,6 +1164,23 @@ class ElementBase(object):
"""
return "{%s}%s" % (cls.namespace, cls.name)
+ def get_lang(self):
+ result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
+ if not result and self.parent and self.parent():
+ return self.parent()['lang']
+ return result
+
+ def set_lang(self, lang):
+ self.del_lang()
+ attr = '{%s}lang' % XML_NS
+ if lang:
+ self.xml.attrib[attr] = lang
+
+ def del_lang(self):
+ attr = '{%s}lang' % XML_NS
+ if attr in self.xml.attrib:
+ del self.xml.attrib[attr]
+
@property
def attrib(self):
"""Return the stanza object itself.
diff --git a/sleekxmpp/xmlstream/tostring.py b/sleekxmpp/xmlstream/tostring.py
index 8e729f79..379ea09a 100644
--- a/sleekxmpp/xmlstream/tostring.py
+++ b/sleekxmpp/xmlstream/tostring.py
@@ -13,14 +13,19 @@
:license: MIT, see LICENSE for more details
"""
+from __future__ import unicode_literals
+
import sys
if sys.version_info < (3, 0):
import types
+XML_NS = 'http://www.w3.org/XML/1998/namespace'
+
+
def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
- outbuffer='', top_level=False):
+ outbuffer='', top_level=False, open_only=False):
"""Serialize an XML object to a Unicode string.
If namespaces are provided using ``xmlns`` or ``stanza_ns``, then
@@ -88,6 +93,13 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
output.append(' %s:%s="%s"' % (mapped_ns,
attrib,
value))
+ elif attrib_ns == XML_NS:
+ output.append(' xml:%s="%s"' % (attrib, value))
+
+ if open_only:
+ # Only output the opening tag, regardless of content.
+ output.append(">")
+ return ''.join(output)
if len(xml) or xml.text:
# If there are additional child elements to serialize.
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
index 40ae38c9..14c13e72 100644
--- a/sleekxmpp/xmlstream/xmlstream.py
+++ b/sleekxmpp/xmlstream/xmlstream.py
@@ -223,6 +223,9 @@ class XMLStream(object):
#: stream wrapper itself.
self.default_ns = ''
+ self.default_lang = None
+ self.peer_default_lang = None
+
#: The namespace of the enveloping stream element.
self.stream_ns = ''
@@ -1431,6 +1434,10 @@ class XMLStream(object):
if depth == 0:
# We have received the start of the root element.
root = xml
+ log.debug('RECV: %s', tostring(root, xmlns=self.default_ns,
+ stream=self,
+ top_level=True,
+ open_only=True))
# Perform any stream initialization actions, such
# as handshakes.
self.stream_end_event.clear()
@@ -1478,6 +1485,8 @@ class XMLStream(object):
stanza_type = stanza_class
break
stanza = stanza_type(self, xml)
+ if stanza['lang'] is None and self.peer_default_lang:
+ stanza['lang'] = self.peer_default_lang
return stanza
def __spawn_event(self, xml):
diff --git a/tests/test_stanza_element.py b/tests/test_stanza_element.py
index 09093003..1b47e733 100644
--- a/tests/test_stanza_element.py
+++ b/tests/test_stanza_element.py
@@ -64,14 +64,18 @@ class TestElementBase(SleekTest):
stanza.append(substanza)
values = stanza.getStanzaValues()
- expected = {'bar': 'a',
+ expected = {'lang': '',
+ 'bar': 'a',
'baz': '',
- 'foo2': {'bar': '',
+ 'foo2': {'lang': '',
+ 'bar': '',
'baz': 'b'},
'substanzas': [{'__childtag__': '{foo}foo2',
+ 'lang': '',
'bar': '',
'baz': 'b'},
{'__childtag__': '{foo}subfoo',
+ 'lang': '',
'bar': 'c',
'baz': ''}]}
self.failUnless(values == expected,
@@ -555,12 +559,12 @@ class TestElementBase(SleekTest):
stanza = TestStanza()
- self.failUnless(set(stanza.keys()) == set(('bar', 'baz')),
+ self.failUnless(set(stanza.keys()) == set(('lang', 'bar', 'baz')),
"Returned set of interface keys does not match expected.")
stanza.enable('qux')
- self.failUnless(set(stanza.keys()) == set(('bar', 'baz', 'qux')),
+ self.failUnless(set(stanza.keys()) == set(('lang', 'bar', 'baz', 'qux')),
"Incorrect set of interface and plugin keys.")
def testGet(self):