Add initial support for xml:lang for streams and stanza plugins.

Remaining items are suitable default actions for language supporting
interfaces.
This commit is contained in:
Lance Stout 2012-06-05 16:54:26 -07:00
parent ee702f4071
commit 181aea737d
10 changed files with 243 additions and 89 deletions

View File

@ -31,6 +31,7 @@ from sleekxmpp.xmlstream import XMLStream, JID
from sleekxmpp.xmlstream import ET, register_stanza_plugin from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import MatchXPath from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.stanzabase import XML_NS
from sleekxmpp.features import * from sleekxmpp.features import *
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
@ -180,6 +181,8 @@ class BaseXMPP(XMLStream):
:param xml: The incoming stream's root element. :param xml: The incoming stream's root element.
""" """
self.stream_id = xml.get('id', '') 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): def process(self, *args, **kwargs):
"""Initialize plugins and begin processing the XML stream. """Initialize plugins and begin processing the XML stream.
@ -272,7 +275,9 @@ class BaseXMPP(XMLStream):
def Message(self, *args, **kwargs): def Message(self, *args, **kwargs):
"""Create a Message stanza associated with this stream.""" """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): def Iq(self, *args, **kwargs):
"""Create an Iq stanza associated with this stream.""" """Create an Iq stanza associated with this stream."""
@ -280,7 +285,9 @@ class BaseXMPP(XMLStream):
def Presence(self, *args, **kwargs): def Presence(self, *args, **kwargs):
"""Create a Presence stanza associated with this stream.""" """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): 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. """Create a new Iq stanza with a given Id and from JID.

View File

@ -60,8 +60,8 @@ class ClientXMPP(BaseXMPP):
:param escape_quotes: **Deprecated.** :param escape_quotes: **Deprecated.**
""" """
def __init__(self, jid, password, ssl=False, plugin_config={}, def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
plugin_whitelist=[], escape_quotes=True, sasl_mech=None): escape_quotes=True, sasl_mech=None, lang='en'):
BaseXMPP.__init__(self, jid, 'jabber:client') BaseXMPP.__init__(self, jid, 'jabber:client')
self.set_jid(jid) self.set_jid(jid)
@ -69,15 +69,18 @@ class ClientXMPP(BaseXMPP):
self.plugin_config = plugin_config self.plugin_config = plugin_config
self.plugin_whitelist = plugin_whitelist self.plugin_whitelist = plugin_whitelist
self.default_port = 5222 self.default_port = 5222
self.default_lang = lang
self.credentials = {} self.credentials = {}
self.password = password 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, self.boundjid.host,
"xmlns:stream='%s'" % self.stream_ns, "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.stream_footer = "</stream:stream>"
self.features = set() self.features = set()

View File

@ -42,6 +42,8 @@ class Addresses(ElementBase):
self.delAddresses(set_type) self.delAddresses(set_type)
for addr in addresses: for addr in addresses:
addr = dict(addr) addr = dict(addr)
if 'lang' in addr:
del addr['lang']
# Remap 'type' to 'atype' to match the add method # Remap 'type' to 'atype' to match the add method
if set_type is not None: if set_type is not None:
addr['type'] = set_type addr['type'] = set_type

View File

@ -79,5 +79,7 @@ class XEP_0092(BasePlugin):
result = iq.send() result = iq.send()
if result and result['type'] != 'error': if result and result['type'] != 'error':
return result['software_version'].values values = result['software_version'].values
del values['lang']
return values
return False return False

View File

@ -102,6 +102,7 @@ class Roster(ElementBase):
# Remove extra JID reference to keep everything # Remove extra JID reference to keep everything
# backward compatible # backward compatible
del items[item['jid']]['jid'] del items[item['jid']]['jid']
del items[item['jid']]['lang']
return items return items
def del_items(self): def del_items(self):

View File

@ -333,6 +333,9 @@ class SleekTest(unittest.TestCase):
# Remove unique ID prefix to make it easier to test # Remove unique ID prefix to make it easier to test
self.xmpp._id_prefix = '' self.xmpp._id_prefix = ''
self.xmpp._disconnect_wait_for_threads = False 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 # We will use this to wait for the session_start event
# for live connections. # for live connections.
@ -386,6 +389,7 @@ class SleekTest(unittest.TestCase):
sid='', sid='',
stream_ns="http://etherx.jabber.org/streams", stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client", default_ns="jabber:client",
default_lang="en",
version="1.0", version="1.0",
xml_header=True): xml_header=True):
""" """
@ -413,6 +417,8 @@ class SleekTest(unittest.TestCase):
parts.append('from="%s"' % sfrom) parts.append('from="%s"' % sfrom)
if sid: if sid:
parts.append('id="%s"' % sid) parts.append('id="%s"' % sid)
if default_lang:
parts.append('xml:lang="%s"' % default_lang)
parts.append('version="%s"' % version) parts.append('version="%s"' % version)
parts.append('xmlns:stream="%s"' % stream_ns) parts.append('xmlns:stream="%s"' % stream_ns)
parts.append('xmlns="%s"' % default_ns) parts.append('xmlns="%s"' % default_ns)
@ -564,6 +570,7 @@ class SleekTest(unittest.TestCase):
sid='', sid='',
stream_ns="http://etherx.jabber.org/streams", stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client", default_ns="jabber:client",
default_lang="en",
version="1.0", version="1.0",
xml_header=False, xml_header=False,
timeout=1): timeout=1):
@ -585,6 +592,7 @@ class SleekTest(unittest.TestCase):
header = self.make_header(sto, sfrom, sid, header = self.make_header(sto, sfrom, sid,
stream_ns=stream_ns, stream_ns=stream_ns,
default_ns=default_ns, default_ns=default_ns,
default_lang=default_lang,
version=version, version=version,
xml_header=xml_header) xml_header=xml_header)
sent_header = self.xmpp.socket.next_sent(timeout) sent_header = self.xmpp.socket.next_sent(timeout)

View File

@ -12,6 +12,8 @@
:license: MIT, see LICENSE for more details :license: MIT, see LICENSE for more details
""" """
from __future__ import with_statement, unicode_literals
import copy import copy
import logging import logging
import weakref import weakref
@ -29,6 +31,9 @@ log = logging.getLogger(__name__)
XML_TYPE = type(ET.Element('xml')) 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): def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
""" """
Associate a stanza object as a plugin for another stanza. Associate a stanza object as a plugin for another stanza.
@ -101,20 +106,26 @@ def multifactory(stanza, plugin_attrib):
def setup(self, xml=None): def setup(self, xml=None):
self.xml = ET.Element('') self.xml = ET.Element('')
def get_multi(self): def get_multi(self, lang=None):
parent = self.parent() 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) return list(res)
def set_multi(self, val): def set_multi(self, val, lang=None):
parent = self.parent() parent = self.parent()
del parent[self.plugin_attrib] del parent[self.plugin_attrib]
for sub in val: for sub in val:
parent.append(sub) parent.append(sub)
def del_multi(self): def del_multi(self, lang=None):
parent = self.parent() 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): for stanza in list(res):
parent.iterables.remove(stanza) parent.iterables.remove(stanza)
parent.xml.remove(stanza.xml) parent.xml.remove(stanza.xml)
@ -122,7 +133,8 @@ def multifactory(stanza, plugin_attrib):
Multi.is_extension = True Multi.is_extension = True
Multi.plugin_attrib = plugin_attrib Multi.plugin_attrib = plugin_attrib
Multi._multistanza = stanza 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, "get_%s" % plugin_attrib, get_multi)
setattr(Multi, "set_%s" % plugin_attrib, set_multi) setattr(Multi, "set_%s" % plugin_attrib, set_multi)
setattr(Multi, "del_%s" % plugin_attrib, del_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 #: subelements of the underlying XML object. Using this set, the text
#: of these subelements may be set, retrieved, or removed without #: of these subelements may be set, retrieved, or removed without
#: needing to define custom methods. #: needing to define custom methods.
sub_interfaces = tuple() sub_interfaces = set()
#: A subset of :attr:`interfaces` which maps the presence of #: A subset of :attr:`interfaces` which maps the presence of
#: subelements to boolean values. Using this set allows for quickly #: subelements to boolean values. Using this set allows for quickly
#: checking for the existence of empty subelements like ``<required />``. #: checking for the existence of empty subelements like ``<required />``.
#: #:
#: .. versionadded:: 1.1 #: .. 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 #: In some cases you may wish to override the behaviour of one of the
#: parent stanza's interfaces. The ``overrides`` list specifies the #: parent stanza's interfaces. The ``overrides`` list specifies the
@ -363,7 +378,7 @@ class ElementBase(object):
subitem = set() subitem = set()
#: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``. #: 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): def __init__(self, xml=None, parent=None):
self._index = 0 self._index = 0
@ -375,6 +390,7 @@ class ElementBase(object):
#: An ordered dictionary of plugin stanzas, mapped by their #: An ordered dictionary of plugin stanzas, mapped by their
#: :attr:`plugin_attrib` value. #: :attr:`plugin_attrib` value.
self.plugins = OrderedDict() self.plugins = OrderedDict()
self.loaded_plugins = set()
#: A list of child stanzas whose class is included in #: A list of child stanzas whose class is included in
#: :attr:`plugin_iterables`. #: :attr:`plugin_iterables`.
@ -385,6 +401,12 @@ class ElementBase(object):
#: ``'{namespace}elementname'``. #: ``'{namespace}elementname'``.
self.tag = self.tag_name() 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. #: A :class:`weakref.weakref` to the parent stanza, if there is one.
#: If not, then :attr:`parent` is ``None``. #: If not, then :attr:`parent` is ``None``.
self.parent = None self.parent = None
@ -406,10 +428,9 @@ class ElementBase(object):
for child in self.xml.getchildren(): for child in self.xml.getchildren():
if child.tag in self.plugin_tag_map: if child.tag in self.plugin_tag_map:
plugin_class = self.plugin_tag_map[child.tag] plugin_class = self.plugin_tag_map[child.tag]
plugin = plugin_class(child, self) self.init_plugin(plugin_class.plugin_attrib,
self.plugins[plugin.plugin_attrib] = plugin existing_xml=child,
if plugin_class in self.plugin_iterables: reuse=False)
self.iterables.append(plugin)
def setup(self, xml=None): def setup(self, xml=None):
"""Initialize the stanza's XML contents. """Initialize the stanza's XML contents.
@ -443,7 +464,7 @@ class ElementBase(object):
# We did not generate XML # We did not generate XML
return False return False
def enable(self, attrib): def enable(self, attrib, lang=None):
"""Enable and initialize a stanza plugin. """Enable and initialize a stanza plugin.
Alias for :meth:`init_plugin`. Alias for :meth:`init_plugin`.
@ -451,24 +472,60 @@ class ElementBase(object):
:param string attrib: The :attr:`plugin_attrib` value of the :param string attrib: The :attr:`plugin_attrib` value of the
plugin to enable. plugin to enable.
""" """
return self.init_plugin(attrib) return self.init_plugin(attrib, lang)
def init_plugin(self, attrib): 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, lang=None, existing_xml=None, reuse=True):
"""Enable and initialize a stanza plugin. """Enable and initialize a stanza plugin.
:param string attrib: The :attr:`plugin_attrib` value of the :param string attrib: The :attr:`plugin_attrib` value of the
plugin to enable. plugin to enable.
""" """
if attrib not in self.plugins: if lang is None:
plugin_class = self.plugin_attrib_map[attrib] 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()) existing_xml = self.xml.find(plugin_class.tag_name())
plugin = plugin_class(parent=self, xml=existing_xml) if existing_xml is not None and existing_xml.attrib.get('{%s}lang' % XML_NS, '') != lang:
self.plugins[attrib] = plugin existing_xml = None
if plugin_class in self.plugin_iterables:
self.iterables.append(plugin) plugin = plugin_class(parent=self, xml=existing_xml)
if plugin_class.plugin_multi_attrib:
self.init_plugin(plugin_class.plugin_multi_attrib) if plugin.is_extension:
return self 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): def _get_stanza_values(self):
"""Return A JSON/dictionary version of the XML content """Return A JSON/dictionary version of the XML content
@ -493,7 +550,11 @@ class ElementBase(object):
for interface in self.interfaces: for interface in self.interfaces:
values[interface] = self[interface] values[interface] = self[interface]
for plugin, stanza in self.plugins.items(): 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: if self.iterables:
iterables = [] iterables = []
for stanza in self.iterables: for stanza in self.iterables:
@ -517,6 +578,11 @@ class ElementBase(object):
p in self.plugin_iterables] p in self.plugin_iterables]
for interface, value in values.items(): 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': if interface == 'substanzas':
# Remove existing substanzas # Remove existing substanzas
for stanza in self.iterables: for stanza in self.iterables:
@ -538,9 +604,8 @@ class ElementBase(object):
self[interface] = value self[interface] = value
elif interface in self.plugin_attrib_map: elif interface in self.plugin_attrib_map:
if interface not in iterable_interfaces: if interface not in iterable_interfaces:
if interface not in self.plugins: plugin = self._get_plugin(interface, lang)
self.init_plugin(interface) plugin.values = value
self.plugins[interface].values = value
return self return self
def __getitem__(self, attrib): def __getitem__(self, attrib):
@ -572,6 +637,15 @@ class ElementBase(object):
:param string attrib: The name of the requested stanza interface. :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': if attrib == 'substanzas':
return self.iterables return self.iterables
elif attrib in self.interfaces: elif attrib in self.interfaces:
@ -579,18 +653,17 @@ class ElementBase(object):
get_method2 = "get%s" % attrib.title() get_method2 = "get%s" % attrib.title()
if self.plugin_overrides: if self.plugin_overrides:
plugin = self.plugin_overrides.get(get_method, None) name = self.plugin_overrides.get(get_method, None)
if plugin: if name:
if plugin not in self.plugins: plugin = self._get_plugin(name, lang)
self.init_plugin(plugin) handler = getattr(plugin, get_method, None)
handler = getattr(self.plugins[plugin], get_method, None)
if handler: if handler:
return handler() return handler(**kwargs)
if hasattr(self, get_method): if hasattr(self, get_method):
return getattr(self, get_method)() return getattr(self, get_method)(**kwargs)
elif hasattr(self, get_method2): elif hasattr(self, get_method2):
return getattr(self, get_method2)() return getattr(self, get_method2)(**kwargs)
else: else:
if attrib in self.sub_interfaces: if attrib in self.sub_interfaces:
return self._get_sub_text(attrib) return self._get_sub_text(attrib)
@ -600,11 +673,10 @@ class ElementBase(object):
else: else:
return self._get_attr(attrib) return self._get_attr(attrib)
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins: plugin = self._get_plugin(attrib, lang)
self.init_plugin(attrib) if plugin.is_extension:
if self.plugins[attrib].is_extension: return plugin[full_attrib]
return self.plugins[attrib][attrib] return plugin
return self.plugins[attrib]
else: else:
return '' return ''
@ -640,25 +712,32 @@ class ElementBase(object):
:param string attrib: The name of the stanza interface to modify. :param string attrib: The name of the stanza interface to modify.
:param value: The new value of the stanza interface. :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 attrib in self.interfaces:
if value is not None: if value is not None:
set_method = "set_%s" % attrib.lower() set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title() set_method2 = "set%s" % attrib.title()
if self.plugin_overrides: if self.plugin_overrides:
plugin = self.plugin_overrides.get(set_method, None) name = self.plugin_overrides.get(set_method, None)
if plugin: if name:
if plugin not in self.plugins: plugin = self._get_plugin(name, lang)
self.init_plugin(plugin) handler = getattr(plugin, set_method, None)
handler = getattr(self.plugins[plugin],
set_method, None)
if handler: if handler:
return handler(value) return handler(value, **kwargs)
if hasattr(self, set_method): if hasattr(self, set_method):
getattr(self, set_method)(value,) getattr(self, set_method)(value, **kwargs)
elif hasattr(self, set_method2): elif hasattr(self, set_method2):
getattr(self, set_method2)(value,) getattr(self, set_method2)(value, **kwargs)
else: else:
if attrib in self.sub_interfaces: if attrib in self.sub_interfaces:
return self._set_sub_text(attrib, text=value) return self._set_sub_text(attrib, text=value)
@ -672,9 +751,8 @@ class ElementBase(object):
else: else:
self.__delitem__(attrib) self.__delitem__(attrib)
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins: plugin = self._get_plugin(attrib, lang)
self.init_plugin(attrib) plugin[full_attrib] = value
self.plugins[attrib][attrib] = value
return self return self
def __delitem__(self, attrib): def __delitem__(self, attrib):
@ -709,23 +787,31 @@ class ElementBase(object):
:param attrib: The name of the affected stanza interface. :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: if attrib in self.interfaces:
del_method = "del_%s" % attrib.lower() del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title() del_method2 = "del%s" % attrib.title()
if self.plugin_overrides: if self.plugin_overrides:
plugin = self.plugin_overrides.get(del_method, None) name = self.plugin_overrides.get(del_method, None)
if plugin: if name:
if plugin not in self.plugins: plugin = self._get_plugin(attrib, lang)
self.init_plugin(plugin) handler = getattr(plugin, del_method, None)
handler = getattr(self.plugins[plugin], del_method, None)
if handler: if handler:
return handler() return handler(**kwargs)
if hasattr(self, del_method): if hasattr(self, del_method):
getattr(self, del_method)() getattr(self, del_method)(**kwargs)
elif hasattr(self, del_method2): elif hasattr(self, del_method2):
getattr(self, del_method2)() getattr(self, del_method2)(**kwargs)
else: else:
if attrib in self.sub_interfaces: if attrib in self.sub_interfaces:
return self._del_sub(attrib) return self._del_sub(attrib)
@ -734,15 +820,17 @@ class ElementBase(object):
else: else:
self._del_attr(attrib) self._del_attr(attrib)
elif attrib in self.plugin_attrib_map: elif attrib in self.plugin_attrib_map:
if attrib in self.plugins: plugin = self._get_plugin(attrib, lang)
xml = self.plugins[attrib].xml if plugin.is_extension:
if self.plugins[attrib].is_extension: del plugin[full_attrib]
del self.plugins[attrib][attrib] del self.plugins[(attrib, None)]
del self.plugins[attrib] else:
try: del self.plugins[(attrib, lang)]
self.xml.remove(xml) self.loaded_plugins.remove(attrib)
except: try:
pass self.xml.remove(plugin.xml)
except:
pass
return self return self
def _set_attr(self, name, value): def _set_attr(self, name, value):
@ -903,7 +991,7 @@ class ElementBase(object):
attributes = components[1:] attributes = components[1:]
if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \ 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. # The requested tag is not in this stanza, so no match.
return False return False
@ -932,10 +1020,11 @@ class ElementBase(object):
if not matched_substanzas and len(xpath) > 1: if not matched_substanzas and len(xpath) > 1:
# Convert {namespace}tag@attribs to just tag # Convert {namespace}tag@attribs to just tag
next_tag = xpath[1].split('@')[0].split('}')[-1] next_tag = xpath[1].split('@')[0].split('}')[-1]
if next_tag in self.plugins: langs = [name[1] for name in self.plugins if name[0] == next_tag]
return self.plugins[next_tag].match(xpath[1:]) for lang in langs:
else: if self._get_plugin(next_tag, lang).match(xpath[1:]):
return False return True
return False
# Everything matched. # Everything matched.
return True return True
@ -995,7 +1084,7 @@ class ElementBase(object):
""" """
out = [] out = []
out += [x for x in self.interfaces] 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: if self.iterables:
out.append('substanzas') out.append('substanzas')
return out return out
@ -1075,6 +1164,23 @@ class ElementBase(object):
""" """
return "{%s}%s" % (cls.namespace, cls.name) 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 @property
def attrib(self): def attrib(self):
"""Return the stanza object itself. """Return the stanza object itself.

View File

@ -13,14 +13,19 @@
:license: MIT, see LICENSE for more details :license: MIT, see LICENSE for more details
""" """
from __future__ import unicode_literals
import sys import sys
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
import types import types
XML_NS = 'http://www.w3.org/XML/1998/namespace'
def tostring(xml=None, xmlns='', stanza_ns='', stream=None, 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. """Serialize an XML object to a Unicode string.
If namespaces are provided using ``xmlns`` or ``stanza_ns``, then 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, output.append(' %s:%s="%s"' % (mapped_ns,
attrib, attrib,
value)) 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 len(xml) or xml.text:
# If there are additional child elements to serialize. # If there are additional child elements to serialize.

View File

@ -223,6 +223,9 @@ class XMLStream(object):
#: stream wrapper itself. #: stream wrapper itself.
self.default_ns = '' self.default_ns = ''
self.default_lang = None
self.peer_default_lang = None
#: The namespace of the enveloping stream element. #: The namespace of the enveloping stream element.
self.stream_ns = '' self.stream_ns = ''
@ -1431,6 +1434,10 @@ class XMLStream(object):
if depth == 0: if depth == 0:
# We have received the start of the root element. # We have received the start of the root element.
root = xml 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 # Perform any stream initialization actions, such
# as handshakes. # as handshakes.
self.stream_end_event.clear() self.stream_end_event.clear()
@ -1478,6 +1485,8 @@ class XMLStream(object):
stanza_type = stanza_class stanza_type = stanza_class
break break
stanza = stanza_type(self, xml) stanza = stanza_type(self, xml)
if stanza['lang'] is None and self.peer_default_lang:
stanza['lang'] = self.peer_default_lang
return stanza return stanza
def __spawn_event(self, xml): def __spawn_event(self, xml):

View File

@ -64,14 +64,18 @@ class TestElementBase(SleekTest):
stanza.append(substanza) stanza.append(substanza)
values = stanza.getStanzaValues() values = stanza.getStanzaValues()
expected = {'bar': 'a', expected = {'lang': '',
'bar': 'a',
'baz': '', 'baz': '',
'foo2': {'bar': '', 'foo2': {'lang': '',
'bar': '',
'baz': 'b'}, 'baz': 'b'},
'substanzas': [{'__childtag__': '{foo}foo2', 'substanzas': [{'__childtag__': '{foo}foo2',
'lang': '',
'bar': '', 'bar': '',
'baz': 'b'}, 'baz': 'b'},
{'__childtag__': '{foo}subfoo', {'__childtag__': '{foo}subfoo',
'lang': '',
'bar': 'c', 'bar': 'c',
'baz': ''}]} 'baz': ''}]}
self.failUnless(values == expected, self.failUnless(values == expected,
@ -555,12 +559,12 @@ class TestElementBase(SleekTest):
stanza = TestStanza() 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.") "Returned set of interface keys does not match expected.")
stanza.enable('qux') 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.") "Incorrect set of interface and plugin keys.")
def testGet(self): def testGet(self):