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:
parent
ee702f4071
commit
181aea737d
10 changed files with 243 additions and 89 deletions
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 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.
|
||||
|
||||
: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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in a new issue