From e077204a16c76df4af90ba067e94c31af3d9e372 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 5 Aug 2010 20:26:41 -0400 Subject: [PATCH] Replaced the ToString class with a tostring function. The sleekxmpp.xmlstream.tostring and sleekxmpp.xmlstream.tostring26 packages have been merged to sleekxmpp.xmlstream.tostring. The __init__.py file will import the appropriate tostring function depending on the Python version. The setup.py file has been updated with the package changes. ElementBase is now a direct descendent of object and does not subclass ToString. Stanza objects now return their XML contents for __repr__. --- setup.py | 14 +- sleekxmpp/basexmpp.py | 53 +++---- sleekxmpp/xmlstream/stanzabase.py | 91 ++++++------ sleekxmpp/xmlstream/tostring/__init__.py | 13 +- sleekxmpp/xmlstream/tostring/tostring.py | 147 +++++++++++-------- sleekxmpp/xmlstream/tostring/tostring26.py | 157 +++++++++++++-------- sleekxmpp/xmlstream/tostring26/__init__.py | 65 --------- sleekxmpp/xmlstream/xmlstream.py | 101 ++++--------- 8 files changed, 298 insertions(+), 343 deletions(-) delete mode 100644 sleekxmpp/xmlstream/tostring26/__init__.py diff --git a/setup.py b/setup.py index 280ec3c0..e3acf18a 100644 --- a/setup.py +++ b/setup.py @@ -16,13 +16,13 @@ import sys # min_version = '0.6c6' # else: # min_version = '0.6a9' -# +# # try: # use_setuptools(min_version=min_version) # except TypeError: # # locally installed ez_setup won't have min_version # use_setuptools() -# +# # from setuptools import setup, find_packages, Extension, Feature VERSION = '0.2.3.1' @@ -37,17 +37,13 @@ CLASSIFIERS = [ 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries :: Python Modules', ] -packages = [ 'sleekxmpp', +packages = [ 'sleekxmpp', 'sleekxmpp/plugins', 'sleekxmpp/stanza', 'sleekxmpp/xmlstream', 'sleekxmpp/xmlstream/matcher', - 'sleekxmpp/xmlstream/handler' ] - -if sys.version_info < (3, 0): - packages.append('sleekxmpp/xmlstream/tostring26') -else: - packages.append('sleekxmpp/xmlstream/tostring') + 'sleekxmpp/xmlstream/handler', + 'sleekxmpp/xmlstream/tostring'] setup( name = "sleekxmpp", diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 2c2bb91e..b7b605b0 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -25,6 +25,7 @@ from . stanza.roster import Roster from . stanza.nick import Nick from . stanza.htmlim import HTMLIM from . stanza.error import Error +from sleekxmpp.xmlstream.tostring import tostring import logging import threading @@ -60,7 +61,7 @@ class basexmpp(object): registerStanzaPlugin(Iq, Roster) registerStanzaPlugin(Message, Nick) registerStanzaPlugin(Message, HTMLIM) - + def Message(self, *args, **kwargs): return Message(self, *args, **kwargs) @@ -69,7 +70,7 @@ class basexmpp(object): def Presence(self, *args, **kwargs): return Presence(self, *args, **kwargs) - + def set_jid(self, jid): """Rip a JID apart and claim it as our own.""" self.fulljid = jid @@ -77,12 +78,12 @@ class basexmpp(object): self.jid = self.getjidbare(jid) self.username = jid.split('@', 1)[0] self.server = jid.split('@',1)[-1].split('/', 1)[0] - + def process(self, *args, **kwargs): for idx in self.plugin: if not self.plugin[idx].post_inited: self.plugin[idx].post_init() return super(basexmpp, self).process(*args, **kwargs) - + def registerPlugin(self, plugin, pconfig = {}): """Register a plugin not in plugins.__init__.__all__ but in the plugins directory.""" @@ -97,7 +98,7 @@ class basexmpp(object): if hasattr(self.plugin[plugin], 'xep'): xep = "(XEP-%s) " % self.plugin[plugin].xep logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description)) - + def register_plugins(self): """Initiates all plugins in the plugins/__init__.__all__""" if self.plugin_whitelist: @@ -112,24 +113,24 @@ class basexmpp(object): # run post_init() for cross-plugin interaction for plugin in self.plugin: self.plugin[plugin].post_init() - + def getNewId(self): with self.id_lock: self.id += 1 return self.getId() - + def add_handler(self, mask, pointer, name=None, disposable=False, threaded=False, filter=False, instream=False): # threaded is no longer needed, but leaving it for backwards compatibility for now if name is None: name = 'add_handler_%s' % self.getNewId() self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer, threaded, disposable, instream)) - + def getId(self): return "%x".upper() % self.id def sendXML(self, data, mask=None, timeout=10): - return self.send(self.tostring(data), mask, timeout) - + return self.send(tostring(data), mask, timeout) + def send(self, data, mask=None, timeout=10): #logging.warning("Deprecated send used for \"%s\"" % (data,)) #if not type(data) == type(''): @@ -144,19 +145,19 @@ class basexmpp(object): self.sendRaw(data) if mask is not None: return waitfor.wait(timeout) - + def makeIq(self, id=0, ifrom=None): return self.Iq().setStanzaValues({'id': str(id), 'from': ifrom}) - + def makeIqGet(self, queryxmlns = None): iq = self.Iq().setStanzaValues({'type': 'get'}) if queryxmlns: iq.append(ET.Element("{%s}query" % queryxmlns)) return iq - + def makeIqResult(self, id): return self.Iq().setStanzaValues({'id': id, 'type': 'result'}) - + def makeIqSet(self, sub=None): iq = self.Iq().setStanzaValues({'type': 'set'}) if sub != None: @@ -172,13 +173,13 @@ class basexmpp(object): query = ET.Element("{%s}query" % xmlns) iq.append(query) return iq - + def makeQueryRoster(self, iq=None): query = ET.Element("{jabber:iq:roster}query") if iq: iq.append(query) return query - + def add_event_handler(self, name, pointer, threaded=False, disposable=False): if not name in self.event_handlers: self.event_handlers[name] = [] @@ -188,13 +189,13 @@ class basexmpp(object): """Remove a handler for an event.""" if not name in self.event_handlers: return - + # Need to keep handlers that do not use # the given function pointer def filter_pointers(handler): return handler[0] != pointer - self.event_handlers[name] = filter(filter_pointers, + self.event_handlers[name] = filter(filter_pointers, self.event_handlers[name]) def event(self, name, eventdata = {}): # called on an event @@ -209,7 +210,7 @@ class basexmpp(object): if handler[2]: #disposable with self.lock: self.event_handlers[name].pop(self.event_handlers[name].index(handler)) - + def makeMessage(self, mto, mbody=None, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None): message = self.Message(sto=mto, stype=mtype, sfrom=mfrom) message['body'] = mbody @@ -217,7 +218,7 @@ class basexmpp(object): if mnick is not None: message['nick'] = mnick if mhtml is not None: message['html']['html'] = mhtml return message - + def makePresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None): presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) if pshow is not None: presence['type'] = pshow @@ -226,10 +227,10 @@ class basexmpp(object): presence['priority'] = ppriority presence['status'] = pstatus return presence - + def sendMessage(self, mto, mbody, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None): self.send(self.makeMessage(mto,mbody,msubject,mtype,mhtml,mfrom,mnick)) - + def sendPresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, pfrom=None, ptype=None): self.send(self.makePresence(pshow,pstatus,ppriority,pto, ptype=ptype, pfrom=pfrom)) if not self.sentpresence: @@ -243,19 +244,19 @@ class basexmpp(object): nick.text = pnick presence.append(nick) self.send(presence) - + def getjidresource(self, fulljid): if '/' in fulljid: return fulljid.split('/', 1)[-1] else: return '' - + def getjidbare(self, fulljid): return fulljid.split('/', 1)[0] def _handleMessage(self, msg): self.event('message', msg) - + def _handlePresence(self, presence): """Update roster items based on presence""" self.event("presence_%s" % presence['type'], presence) @@ -296,7 +297,7 @@ class basexmpp(object): if name: name = "(%s) " % name logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, show,status)) - + def _handlePresenceSubscribe(self, presence): """Handling subscriptions automatically.""" if self.auto_authorize == True: diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index 94ff958c..3b5f0bf4 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -12,10 +12,7 @@ import weakref import copy from . jid import JID -if sys.version_info < (3,0): - from . import tostring26 as tostring -else: - from . import tostring +from sleekxmpp.xmlstream.tostring import tostring xmltester = type(ET.Element('xml')) @@ -29,7 +26,7 @@ def registerStanzaPlugin(stanza, plugin): stanza.plugin_tag_map[tag] = plugin -class ElementBase(tostring.ToString): +class ElementBase(object): name = 'stanza' plugin_attrib = 'plugin' namespace = 'jabber:client' @@ -70,20 +67,20 @@ class ElementBase(tostring.ToString): def __bool__(self): return True - + def __next__(self): self.idx += 1 if self.idx > len(self.iterables): self.idx = 0 raise StopIteration return self.iterables[self.idx - 1] - + def next(self): return self.__next__() def __len__(self): return len(self.iterables) - + def append(self, item): if not isinstance(item, ElementBase): if type(item) == xmltester: @@ -93,18 +90,18 @@ class ElementBase(tostring.ToString): self.xml.append(item.xml) self.iterables.append(item) return self - + def pop(self, idx=0): aff = self.iterables.pop(idx) self.xml.remove(aff.xml) return aff - + def get(self, key, defaultvalue=None): value = self[key] if value is None or value == '': return defaultvalue return value - + def keys(self): out = [] out += [x for x in self.interfaces] @@ -112,7 +109,7 @@ class ElementBase(tostring.ToString): if self.iterables: out.append('substanzas') return tuple(out) - + def match(self, matchstring): if isinstance(matchstring, str): nodes = matchstring.split('/') @@ -136,13 +133,13 @@ class ElementBase(tostring.ToString): else: return False return True - + def find(self, xpath): # for backwards compatiblity, expose elementtree interface return self.xml.find(xpath) def findall(self, xpath): return self.xml.findall(xpath) - + def setup(self, xml=None): if self.xml is None: self.xml = xml @@ -162,11 +159,11 @@ class ElementBase(tostring.ToString): def enable(self, attrib): self.initPlugin(attrib) return self - + def initPlugin(self, attrib): if attrib not in self.plugins: self.plugins[attrib] = self.plugin_attrib_map[attrib](parent=self) - + def __getitem__(self, attrib): if attrib == 'substanzas': return self.iterables @@ -183,7 +180,7 @@ class ElementBase(tostring.ToString): return self.plugins[attrib] else: return '' - + def __setitem__(self, attrib, value): if attrib in self.interfaces: if value is not None: @@ -201,7 +198,7 @@ class ElementBase(tostring.ToString): self.initPlugin(attrib) self.plugins[attrib][attrib] = value return self - + def __delitem__(self, attrib): if attrib.lower() in self.interfaces: if hasattr(self, "del%s" % attrib.title()): @@ -215,7 +212,7 @@ class ElementBase(tostring.ToString): if attrib in self.plugins: del self.plugins[attrib] return self - + def __eq__(self, other): if not isinstance(other, ElementBase): return False @@ -224,20 +221,20 @@ class ElementBase(tostring.ToString): if key not in values or values[key] != other[key]: return False return True - + def _setAttr(self, name, value): if value is None or value == '': self.__delitem__(name) else: self.xml.attrib[name] = value - + def _delAttr(self, name): if name in self.xml.attrib: del self.xml.attrib[name] - + def _getAttr(self, name, default=''): return self.xml.attrib.get(name, default) - + def _getSubText(self, name): if '}' not in name: name = "{%s}%s" % (self.namespace, name) @@ -246,7 +243,7 @@ class ElementBase(tostring.ToString): return '' else: return stanza.text - + def _setSubText(self, name, attrib={}, text=None): if '}' not in name: name = "{%s}%s" % (self.namespace, name) @@ -258,14 +255,14 @@ class ElementBase(tostring.ToString): self.xml.append(stanza) stanza.text = text return stanza - + def _delSub(self, name): if '}' not in name: name = "{%s}%s" % (self.namespace, name) for child in self.xml.getchildren(): if child.tag == name: self.xml.remove(child) - + def getStanzaValues(self): out = {} for interface in self.interfaces: @@ -279,7 +276,7 @@ class ElementBase(tostring.ToString): iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) out['substanzas'] = iterables return out - + def setStanzaValues(self, attrib): for interface in attrib: if interface == 'substanzas': @@ -298,14 +295,20 @@ class ElementBase(tostring.ToString): if interface in self.plugins: self.plugins[interface].setStanzaValues(attrib[interface]) return self - + def appendxml(self, xml): self.xml.append(xml) return self def __copy__(self): return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent) - + + def __str__(self): + return tostring(self.xml, xmlns='', stanza_ns=self.namespace) + + def __repr__(self): + return self.__str__() + #def __del__(self): #prevents garbage collection of reference cycle # if self.parent is not None: # self.parent.xml.remove(self.xml) @@ -329,7 +332,7 @@ class StanzaBase(ElementBase): if sfrom is not None: self['from'] = sfrom self.tag = "{%s}%s" % (self.namespace, self.name) - + def setType(self, value): if value in self.types: self.xml.attrib['type'] = value @@ -337,22 +340,22 @@ class StanzaBase(ElementBase): def getPayload(self): return self.xml.getchildren() - + def setPayload(self, value): self.xml.append(value) return self - + def delPayload(self): self.clear() return self - + def clear(self): for child in self.xml.getchildren(): self.xml.remove(child) for plugin in list(self.plugins.keys()): del self.plugins[plugin] return self - + def reply(self): # if it's a component, use from if self.stream and hasattr(self.stream, "is_component") and self.stream.is_component: @@ -362,32 +365,34 @@ class StanzaBase(ElementBase): del self['from'] self.clear() return self - + def error(self): self['type'] = 'error' return self - + def getTo(self): return JID(self._getAttr('to')) - + def setTo(self, value): return self._setAttr('to', str(value)) - + def getFrom(self): return JID(self._getAttr('from')) - + def setFrom(self, value): return self._setAttr('from', str(value)) - + def unhandled(self): pass - + def exception(self, e): logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) - + def send(self): self.stream.sendRaw(self.__str__()) def __copy__(self): return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream) - + + def __str__(self): + return tostring(self.xml, xmlns='', stanza_ns=self.namespace, stream=self.stream) diff --git a/sleekxmpp/xmlstream/tostring/__init__.py b/sleekxmpp/xmlstream/tostring/__init__.py index d93fe4ea..5852cba2 100644 --- a/sleekxmpp/xmlstream/tostring/__init__.py +++ b/sleekxmpp/xmlstream/tostring/__init__.py @@ -1,14 +1,19 @@ """ + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + See the file LICENSE for copying permission. """ import sys +# Import the correct tostring and xml_escape functions based on the Python +# version in order to properly handle Unicode. -# Import the correct ToString class based on the Python version. if sys.version_info < (3, 0): - from sleekxmpp.xmlstream.tostring.tostring26 import ToString + from sleekxmpp.xmlstream.tostring.tostring26 import tostring, xml_escape else: - from sleekxmpp.xmlstream.tostring.tostring import ToString + from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape -__all__ = ['ToString'] +__all__ = ['tostring', 'xml_escape'] diff --git a/sleekxmpp/xmlstream/tostring/tostring.py b/sleekxmpp/xmlstream/tostring/tostring.py index 6603cbb8..62ff1181 100644 --- a/sleekxmpp/xmlstream/tostring/tostring.py +++ b/sleekxmpp/xmlstream/tostring/tostring.py @@ -1,60 +1,91 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. -class ToString(object): - def __str__(self, xml=None, xmlns='', stringbuffer=''): - if xml is None: - xml = self.xml - newoutput = [stringbuffer] - #TODO respect ET mapped namespaces - itag = xml.tag.split('}', 1)[-1] - if '}' in xml.tag: - ixmlns = xml.tag.split('}', 1)[0][1:] - else: - ixmlns = '' - nsbuffer = '' - if xmlns != ixmlns and ixmlns != '' and ixmlns != self.namespace: - if self.stream is not None and ixmlns in self.stream.namespace_map: - if self.stream.namespace_map[ixmlns] != '': - itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag) - else: - nsbuffer = """ xmlns="%s\"""" % ixmlns - if ixmlns not in ('', xmlns, self.namespace): - nsbuffer = """ xmlns="%s\"""" % ixmlns - newoutput.append("<%s" % itag) - newoutput.append(nsbuffer) - for attrib in xml.attrib: - if '{' not in attrib: - newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) - if len(xml) or xml.text or xml.tail: - newoutput.append(">") - if xml.text: - newoutput.append(self.xmlesc(xml.text)) - if len(xml): - for child in xml.getchildren(): - newoutput.append(self.__str__(child, ixmlns)) - newoutput.append("" % (itag, )) - if xml.tail: - newoutput.append(self.xmlesc(xml.tail)) - elif xml.text: - newoutput.append(">%s" % (self.xmlesc(xml.text), itag)) - else: - newoutput.append(" />") - return ''.join(newoutput) + See the file LICENSE for copying permission. +""" - def xmlesc(self, text): - text = list(text) - cc = 0 - matches = ('&', '<', '"', '>', "'") - for c in text: - if c in matches: - if c == '&': - text[cc] = '&' - elif c == '<': - text[cc] = '<' - elif c == '>': - text[cc] = '>' - elif c == "'": - text[cc] = ''' - else: - text[cc] = '"' - cc += 1 - return ''.join(text) + +def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): + """ + Serialize an XML object to a Unicode string. + + Arguments: + xml -- The XML object to serialize. If the value is None, + then the XML object contained in this stanza + object will be used. + xmlns -- Optional namespace of an element wrapping the XML + object. + stanza_ns -- The namespace of the stanza object that contains + the XML object. + stream -- The XML stream that generated the XML object. + outbuffer -- Optional buffer for storing serializations during + recursive calls. + """ + # Add previous results to the start of the output. + output = [outbuffer] + + # Extract the element's tag name. + tag_name = xml.tag.split('}', 1)[-1] + + # Extract the element's namespace if it is defined. + if '}' in xml.tag: + tag_xmlns = xml.tag.split('}', 1)[0][1:] + else: + tag_xmlns = '' + + # Output the tag name and derived namespace of the element. + namespace = '' + if tag_xmlns not in ['', xmlns, stanza_ns]: + namespace = ' xmlns="%s"' % tag_xmlns + if stream and tag_xmlns in stream.namespace_map: + mapped_namespace = stream.namespace_map[tag_xmlns] + if mapped_namespace: + tag = "%s:%s" % (mapped_namespace, tag_name) + output.append("<%s" % tag_name) + output.append(namespace) + + # Output escaped attribute values. + for attrib, value in xml.attrib.items(): + if '{' not in attrib: + value = xml_escape(value) + output.append(' %s="%s"' % (attrib, value)) + + if len(xml) or xml.text: + # If there are additional child elements to serialize. + output.append(">") + if xml.text: + output.append(xml_escape(xml.text)) + if len(xml): + for child in xml.getchildren(): + output.append(tostring(child, tag_xmlns, stanza_ns, stream)) + output.append("" % tag_name) + elif xml.text: + # If we only have text content. + output.append(">%s" % (xml_escape(xml.text), tag_name)) + else: + # Empty element. + output.append(" />") + if xml.tail: + # If there is additional text after the element. + output.append(xml_escape(xml.tail)) + return ''.join(output) + + +def xml_escape(text): + """ + Convert special characters in XML to escape sequences. + + Arguments: + text -- The XML text to convert. + """ + text = list(text) + escapes = {'&': '&', + '<': '<', + '>': '>', + "'": ''', + '"': '"'} + for i, c in enumerate(text): + text[i] = escapes.get(c, c) + return ''.join(text) diff --git a/sleekxmpp/xmlstream/tostring/tostring26.py b/sleekxmpp/xmlstream/tostring/tostring26.py index 9711c300..9dba2717 100644 --- a/sleekxmpp/xmlstream/tostring/tostring26.py +++ b/sleekxmpp/xmlstream/tostring/tostring26.py @@ -1,65 +1,100 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from __future__ import unicode_literals import types -class ToString(object): - def __str__(self, xml=None, xmlns='', stringbuffer=''): - if xml is None: - xml = self.xml - newoutput = [stringbuffer] - #TODO respect ET mapped namespaces - itag = xml.tag.split('}', 1)[-1] - if '}' in xml.tag: - ixmlns = xml.tag.split('}', 1)[0][1:] - else: - ixmlns = '' - nsbuffer = '' - if xmlns != ixmlns and ixmlns != u'' and ixmlns != self.namespace: - if self.stream is not None and ixmlns in self.stream.namespace_map: - if self.stream.namespace_map[ixmlns] != u'': - itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag) - else: - nsbuffer = """ xmlns="%s\"""" % ixmlns - if ixmlns not in ('', xmlns, self.namespace): - nsbuffer = """ xmlns="%s\"""" % ixmlns - newoutput.append("<%s" % itag) - newoutput.append(nsbuffer) - for attrib in xml.attrib: - if '{' not in attrib: - newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) - if len(xml) or xml.text or xml.tail: - newoutput.append(u">") - if xml.text: - newoutput.append(self.xmlesc(xml.text)) - if len(xml): - for child in xml.getchildren(): - newoutput.append(self.__str__(child, ixmlns)) - newoutput.append(u"" % (itag, )) - if xml.tail: - newoutput.append(self.xmlesc(xml.tail)) - elif xml.text: - newoutput.append(">%s" % (self.xmlesc(xml.text), itag)) - else: - newoutput.append(" />") - return u''.join(newoutput) - def xmlesc(self, text): - if type(text) != types.UnicodeType: - text = list(unicode(text, 'utf-8', 'ignore')) - else: - text = list(text) +def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''): + """ + Serialize an XML object to a Unicode string. - cc = 0 - matches = (u'&', u'<', u'"', u'>', u"'") - for c in text: - if c in matches: - if c == u'&': - text[cc] = u'&' - elif c == u'<': - text[cc] = u'<' - elif c == u'>': - text[cc] = u'>' - elif c == u"'": - text[cc] = u''' - else: - text[cc] = u'"' - cc += 1 - return ''.join(text) + Arguments: + xml -- The XML object to serialize. If the value is None, + then the XML object contained in this stanza + object will be used. + xmlns -- Optional namespace of an element wrapping the XML + object. + stanza_ns -- The namespace of the stanza object that contains + the XML object. + stream -- The XML stream that generated the XML object. + outbuffer -- Optional buffer for storing serializations during + recursive calls. + """ + # Add previous results to the start of the output. + output = [outbuffer] + + # Extract the element's tag name. + tag_name = xml.tag.split('}', 1)[-1] + + # Extract the element's namespace if it is defined. + if '}' in xml.tag: + tag_xmlns = xml.tag.split('}', 1)[0][1:] + else: + tag_xmlns = u'' + + # Output the tag name and derived namespace of the element. + namespace = u'' + if tag_xmlns not in ['', xmlns, stanza_ns]: + namespace = u' xmlns="%s"' % tag_xmlns + if stream and tag_xmlns in stream.namespace_map: + mapped_namespace = stream.namespace_map[tag_xmlns] + if mapped_namespace: + tag = u"%s:%s" % (mapped_namespace, tag_name) + output.append(u"<%s" % tag_name) + output.append(namespace) + + # Output escaped attribute values. + for attrib, value in xml.attrib.items(): + if '{' not in attrib: + value = xml_escape(value) + output.append(u' %s="%s"' % (attrib, value)) + + if len(xml) or xml.text: + # If there are additional child elements to serialize. + output.append(u">") + if xml.text: + output.append(xml_escape(xml.text)) + if len(xml): + for child in xml.getchildren(): + output.append(tostring(child, tag_xmlns, stanza_ns, stream)) + output.append(u"" % tag_name) + if xml.tail: + # If there is additional text after the element. + output.append(xml_escape(xml.tail)) + elif xml.text: + # If we only have text content. + output.append(u">%s" % (xml_escape(xml.text), tag_name)) + else: + # Empty element. + output.append(u" />") + if xml.tail: + # If there is additional text after the element. + output.append(xml_escape(xml.tail)) + return u''.join(output) + + +def xml_escape(text): + """ + Convert special characters in XML to escape sequences. + + Arguments: + text -- The XML text to convert. + """ + if type(text) != types.UnicodeType: + text = list(unicode(text, 'utf-8', 'ignore')) + else: + text = list(text) + escapes = {u'&': u'&', + u'<': u'<', + u'>': u'>', + u"'": u''', + u'"': u'"'} + for i, c in enumerate(text): + text[i] = escapes.get(c, c) + return u''.join(text) diff --git a/sleekxmpp/xmlstream/tostring26/__init__.py b/sleekxmpp/xmlstream/tostring26/__init__.py deleted file mode 100644 index 9711c300..00000000 --- a/sleekxmpp/xmlstream/tostring26/__init__.py +++ /dev/null @@ -1,65 +0,0 @@ -import types - -class ToString(object): - def __str__(self, xml=None, xmlns='', stringbuffer=''): - if xml is None: - xml = self.xml - newoutput = [stringbuffer] - #TODO respect ET mapped namespaces - itag = xml.tag.split('}', 1)[-1] - if '}' in xml.tag: - ixmlns = xml.tag.split('}', 1)[0][1:] - else: - ixmlns = '' - nsbuffer = '' - if xmlns != ixmlns and ixmlns != u'' and ixmlns != self.namespace: - if self.stream is not None and ixmlns in self.stream.namespace_map: - if self.stream.namespace_map[ixmlns] != u'': - itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag) - else: - nsbuffer = """ xmlns="%s\"""" % ixmlns - if ixmlns not in ('', xmlns, self.namespace): - nsbuffer = """ xmlns="%s\"""" % ixmlns - newoutput.append("<%s" % itag) - newoutput.append(nsbuffer) - for attrib in xml.attrib: - if '{' not in attrib: - newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) - if len(xml) or xml.text or xml.tail: - newoutput.append(u">") - if xml.text: - newoutput.append(self.xmlesc(xml.text)) - if len(xml): - for child in xml.getchildren(): - newoutput.append(self.__str__(child, ixmlns)) - newoutput.append(u"" % (itag, )) - if xml.tail: - newoutput.append(self.xmlesc(xml.tail)) - elif xml.text: - newoutput.append(">%s" % (self.xmlesc(xml.text), itag)) - else: - newoutput.append(" />") - return u''.join(newoutput) - - def xmlesc(self, text): - if type(text) != types.UnicodeType: - text = list(unicode(text, 'utf-8', 'ignore')) - else: - text = list(text) - - cc = 0 - matches = (u'&', u'<', u'"', u'>', u"'") - for c in text: - if c in matches: - if c == u'&': - text[cc] = u'&' - elif c == u'<': - text[cc] = u'<' - elif c == u'>': - text[cc] = u'>' - elif c == u"'": - text[cc] = u''' - else: - text[cc] = u'"' - cc += 1 - return ''.join(text) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 94fed64a..ffaa6514 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -23,6 +23,7 @@ import types import copy import xml.sax.saxutils from . import scheduler +from sleekxmpp.xmlstream.tostring import tostring RESPONSE_TIMEOUT = 10 HANDLER_THREADS = 1 @@ -37,7 +38,7 @@ if sys.version_info < (3, 0): #monkey patch broken filesocket object from . import filesocket #socket._fileobject = filesocket.filesocket - + class RestartStream(Exception): pass @@ -82,7 +83,7 @@ class XMLStream(object): self.namespace_map = {} self.run = True - + def setSocket(self, socket): "Set the socket" self.socket = socket @@ -90,10 +91,10 @@ class XMLStream(object): self.filesocket = socket.makefile('rb', 0) # ElementTree.iterparse requires a file. 0 buffer files have to be binary self.state.set('connected', True) - + def setFileSocket(self, filesocket): self.filesocket = filesocket - + def connect(self, host='', port=0, use_ssl=False, use_tls=True): "Link to connectTCP" return self.connectTCP(host, port, use_ssl, use_tls) @@ -125,7 +126,7 @@ class XMLStream(object): except socket.error as serr: logging.error("Could not connect. Socket Error #%s: %s" % (serr.errno, serr.strerror)) time.sleep(1) - + def connectUnix(self, filepath): "Connect to Unix file and create socket" @@ -146,7 +147,7 @@ class XMLStream(object): logging.warning("Tried to enable TLS, but ssl module not found.") return False raise RestartStream() - + def process(self, threaded=True): self.scheduler.process(threaded=True) for t in range(0, HANDLER_THREADS): @@ -160,10 +161,10 @@ class XMLStream(object): self.__thread['process'].start() else: self._process() - + def schedule(self, name, seconds, callback, args=None, kwargs=None, repeat=False): self.scheduler.add(name, seconds, callback, args, kwargs, repeat, qpointer=self.eventqueue) - + def _process(self): "Start processing the socket." firstrun = True @@ -212,7 +213,7 @@ class XMLStream(object): #self.__thread['readXML'].start() #self.__thread['spawnEvents'] = threading.Thread(name='spawnEvents', target=self.__spawnEvents) #self.__thread['spawnEvents'].start() - + def __readXML(self): "Parses the incoming stream, adding to xmlin queue as it goes" #build cElementTree object from expat was we go @@ -245,7 +246,7 @@ class XMLStream(object): if event == b'start': edepth += 1 logging.debug("Ending readXML loop") - + def _sendThread(self): while self.run: data = self.sendqueue.get(True) @@ -260,11 +261,11 @@ class XMLStream(object): if self.state.reconnect: logging.exception("Disconnected. Socket Error.") self.disconnect(reconnect=True) - + def sendRaw(self, data): self.sendqueue.put(data) return True - + def disconnect(self, reconnect=False): self.state.set('reconnect', reconnect) if self.state['disconnecting']: @@ -290,20 +291,20 @@ class XMLStream(object): if self.state['processing']: #raise CloseStream pass - + def reconnect(self): self.state.set('tls',False) self.state.set('ssl',False) time.sleep(1) self.connect() - + def incoming_filter(self, xmlobj): return xmlobj - + def __spawnEvent(self, xmlobj): "watching xmlOut and processes handlers" #convert XML into Stanza - logging.debug("RECV: %s" % cElementTree.tostring(xmlobj)) + logging.debug("RECV: %s" % tostring(xmlobj)) xmlobj = self.incoming_filter(xmlobj) stanza_type = StanzaBase for stanza_class in self.__root_stanza: @@ -323,7 +324,7 @@ class XMLStream(object): stanza.unhandled() #loop through handlers and test match #spawn threads as necessary, call handlers, sending Stanza - + def _eventRunner(self): logging.debug("Loading event runner") while self.run: @@ -354,11 +355,11 @@ class XMLStream(object): elif etype == 'quit': logging.debug("Quitting eventRunner thread") return False - + def registerHandler(self, handler, before=None, after=None): "Add handler with matcher class and parameters." self.__handlers.append(handler) - + def removeHandler(self, name): "Removes the handler." idx = 0 @@ -367,81 +368,27 @@ class XMLStream(object): self.__handlers.pop(idx) return idx += 1 - + def registerStanza(self, stanza_class): "Adds stanza. If root stanzas build stanzas sent in events while non-root stanzas build substanza objects." self.__root_stanza.append(stanza_class) - + def registerStanzaExtension(self, stanza_class, stanza_extension): if stanza_class not in stanza_extensions: stanza_extensions[stanza_class] = [stanza_extension] else: stanza_extensions[stanza_class].append(stanza_extension) - + def removeStanza(self, stanza_class, root=False): "Removes the stanza's registration." if root: del self.__root_stanza[stanza_class] else: del self.__stanza[stanza_class] - + def removeStanzaExtension(self, stanza_class, stanza_extension): stanza_extension[stanza_class].pop(stanza_extension) - def tostring(self, xml, xmlns='', stringbuffer=''): - newoutput = [stringbuffer] - #TODO respect ET mapped namespaces - itag = xml.tag.split('}', 1)[-1] - if '}' in xml.tag: - ixmlns = xml.tag.split('}', 1)[0][1:] - else: - ixmlns = '' - nsbuffer = '' - if xmlns != ixmlns and ixmlns != '': - if ixmlns in self.namespace_map: - if self.namespace_map[ixmlns] != '': - itag = "%s:%s" % (self.namespace_map[ixmlns], itag) - else: - nsbuffer = """ xmlns="%s\"""" % ixmlns - newoutput.append("<%s" % itag) - newoutput.append(nsbuffer) - for attrib in xml.attrib: - newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib]))) - if len(xml) or xml.text or xml.tail: - newoutput.append(">") - if xml.text: - newoutput.append(self.xmlesc(xml.text)) - if len(xml): - for child in xml.getchildren(): - newoutput.append(self.tostring(child, ixmlns)) - newoutput.append("" % (itag, )) - if xml.tail: - newoutput.append(self.xmlesc(xml.tail)) - elif xml.text: - newoutput.append(">%s" % (self.xmlesc(xml.text), itag)) - else: - newoutput.append(" />") - return ''.join(newoutput) - - def xmlesc(self, text): - text = list(text) - cc = 0 - matches = ('&', '<', '"', '>', "'") - for c in text: - if c in matches: - if c == '&': - text[cc] = '&' - elif c == '<': - text[cc] = '<' - elif c == '>': - text[cc] = '>' - elif c == "'": - text[cc] = ''' - elif self.escape_quotes: - text[cc] = '"' - cc += 1 - return ''.join(text) - def start_stream_handler(self, xml): """Meant to be overridden""" pass