summaryrefslogtreecommitdiff
path: root/sleekxmpp/xmlstream
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp/xmlstream')
-rw-r--r--sleekxmpp/xmlstream/__init__.py11
-rw-r--r--sleekxmpp/xmlstream/filesocket.py2
-rw-r--r--sleekxmpp/xmlstream/handler/__init__.py12
-rw-r--r--sleekxmpp/xmlstream/handler/base.py2
-rw-r--r--sleekxmpp/xmlstream/handler/callback.py2
-rw-r--r--sleekxmpp/xmlstream/handler/waiter.py2
-rw-r--r--sleekxmpp/xmlstream/handler/xmlcallback.py2
-rw-r--r--sleekxmpp/xmlstream/handler/xmlwaiter.py2
-rw-r--r--sleekxmpp/xmlstream/jid.py121
-rw-r--r--sleekxmpp/xmlstream/matcher/__init__.py13
-rw-r--r--sleekxmpp/xmlstream/matcher/base.py2
-rw-r--r--sleekxmpp/xmlstream/matcher/id.py2
-rw-r--r--sleekxmpp/xmlstream/matcher/many.py2
-rw-r--r--sleekxmpp/xmlstream/matcher/stanzapath.py2
-rw-r--r--sleekxmpp/xmlstream/matcher/xmlmask.py2
-rw-r--r--sleekxmpp/xmlstream/matcher/xpath.py2
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py880
-rw-r--r--sleekxmpp/xmlstream/statemachine.py2
-rw-r--r--sleekxmpp/xmlstream/statemanager.py139
-rw-r--r--sleekxmpp/xmlstream/tostring/__init__.py75
-rw-r--r--sleekxmpp/xmlstream/tostring/tostring.py95
-rw-r--r--sleekxmpp/xmlstream/tostring/tostring26.py104
-rw-r--r--sleekxmpp/xmlstream/tostring26/__init__.py65
-rw-r--r--sleekxmpp/xmlstream/xmlstream.py131
24 files changed, 1069 insertions, 603 deletions
diff --git a/sleekxmpp/xmlstream/__init__.py b/sleekxmpp/xmlstream/__init__.py
index e69de29b..c82ab346 100644
--- a/sleekxmpp/xmlstream/__init__.py
+++ b/sleekxmpp/xmlstream/__init__.py
@@ -0,0 +1,11 @@
+"""
+ 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 sleekxmpp.xmlstream.jid import JID
+from sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase
+from sleekxmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT
diff --git a/sleekxmpp/xmlstream/filesocket.py b/sleekxmpp/xmlstream/filesocket.py
index f60c5b8e..07b395dc 100644
--- a/sleekxmpp/xmlstream/filesocket.py
+++ b/sleekxmpp/xmlstream/filesocket.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from socket import _fileobject
import socket
diff --git a/sleekxmpp/xmlstream/handler/__init__.py b/sleekxmpp/xmlstream/handler/__init__.py
index e69de29b..50e286a3 100644
--- a/sleekxmpp/xmlstream/handler/__init__.py
+++ b/sleekxmpp/xmlstream/handler/__init__.py
@@ -0,0 +1,12 @@
+"""
+ 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 sleekxmpp.xmlstream.handler.callback import Callback
+from sleekxmpp.xmlstream.handler.waiter import Waiter
+from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback
+from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter
diff --git a/sleekxmpp/xmlstream/handler/base.py b/sleekxmpp/xmlstream/handler/base.py
index 5d55f4ee..720846d6 100644
--- a/sleekxmpp/xmlstream/handler/base.py
+++ b/sleekxmpp/xmlstream/handler/base.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
class BaseHandler(object):
diff --git a/sleekxmpp/xmlstream/handler/callback.py b/sleekxmpp/xmlstream/handler/callback.py
index 49cfa14d..889b0aa7 100644
--- a/sleekxmpp/xmlstream/handler/callback.py
+++ b/sleekxmpp/xmlstream/handler/callback.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from . import base
import logging
diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py
index c85a0c46..7c4330a4 100644
--- a/sleekxmpp/xmlstream/handler/waiter.py
+++ b/sleekxmpp/xmlstream/handler/waiter.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from . import base
try:
diff --git a/sleekxmpp/xmlstream/handler/xmlcallback.py b/sleekxmpp/xmlstream/handler/xmlcallback.py
index 632c142b..67879dfe 100644
--- a/sleekxmpp/xmlstream/handler/xmlcallback.py
+++ b/sleekxmpp/xmlstream/handler/xmlcallback.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
import threading
from . callback import Callback
diff --git a/sleekxmpp/xmlstream/handler/xmlwaiter.py b/sleekxmpp/xmlstream/handler/xmlwaiter.py
index 2344403b..cf90751d 100644
--- a/sleekxmpp/xmlstream/handler/xmlwaiter.py
+++ b/sleekxmpp/xmlstream/handler/xmlwaiter.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from . waiter import Waiter
diff --git a/sleekxmpp/xmlstream/jid.py b/sleekxmpp/xmlstream/jid.py
new file mode 100644
index 00000000..292abd92
--- /dev/null
+++ b/sleekxmpp/xmlstream/jid.py
@@ -0,0 +1,121 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+
+class JID(object):
+ """
+ A representation of a Jabber ID, or JID.
+
+ Each JID may have three components: a user, a domain, and an optional
+ resource. For example: user@domain/resource
+
+ When a resource is not used, the JID is called a bare JID.
+ The JID is a full JID otherwise.
+
+ Attributes:
+ jid -- Alias for 'full'.
+ full -- The value of the full JID.
+ bare -- The value of the bare JID.
+ user -- The username portion of the JID.
+ domain -- The domain name portion of the JID.
+ server -- Alias for 'domain'.
+ resource -- The resource portion of the JID.
+
+ Methods:
+ reset -- Use a new JID value.
+ regenerate -- Recreate the JID from its components.
+ """
+
+ def __init__(self, jid):
+ """Initialize a new JID"""
+ self.reset(jid)
+
+ def reset(self, jid):
+ """
+ Start fresh from a new JID string.
+
+ Arguments:
+ jid - The new JID value.
+ """
+ self._full = self._jid = str(jid)
+ self._domain = None
+ self._resource = None
+ self._user = None
+ self._bare = None
+
+ def __getattr__(self, name):
+ """
+ Handle getting the JID values, using cache if available.
+
+ Arguments:
+ name -- One of: user, server, domain, resource,
+ full, or bare.
+ """
+ if name == 'resource':
+ if self._resource is None:
+ self._resource = self._jid.split('/', 1)[-1]
+ return self._resource
+ elif name == 'user':
+ if self._user is None:
+ if '@' in self._jid:
+ self._user = self._jid.split('@', 1)[0]
+ else:
+ self._user = self._user
+ return self._user
+ elif name in ('server', 'domain'):
+ if self._domain is None:
+ self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0]
+ return self._domain
+ elif name == 'full':
+ return self._jid
+ elif name == 'bare':
+ if self._bare is None:
+ self._bare = self._jid.split('/', 1)[0]
+ return self._bare
+
+ def __setattr__(self, name, value):
+ """
+ Edit a JID by updating it's individual values, resetting the
+ generated JID in the end.
+
+ Arguments:
+ name -- The name of the JID part. One of: user, domain,
+ server, resource, full, jid, or bare.
+ value -- The new value for the JID part.
+ """
+ if name in ('resource', 'user', 'domain'):
+ object.__setattr__(self, "_%s" % name, value)
+ self.regenerate()
+ elif name == 'server':
+ self.domain = value
+ elif name in ('full', 'jid'):
+ self.reset(value)
+ elif name == 'bare':
+ if '@' in value:
+ u, d = value.split('@', 1)
+ object.__setattr__(self, "_user", u)
+ object.__setattr__(self, "_domain", d)
+ else:
+ object.__setattr__(self, "_domain", value)
+ self.regenerate()
+ else:
+ object.__setattr__(self, name, value)
+
+ def regenerate(self):
+ """Generate a new JID based on current values, useful after editing."""
+ jid = ""
+ if self.user:
+ jid = "%s@" % self.user
+ jid += self.domain
+ if self.resource:
+ jid += "/%s" % self.resource
+ self.reset(jid)
+
+ def __str__(self):
+ """Use the full JID as the string value."""
+ return self.full
diff --git a/sleekxmpp/xmlstream/matcher/__init__.py b/sleekxmpp/xmlstream/matcher/__init__.py
index e69de29b..91cb8d6e 100644
--- a/sleekxmpp/xmlstream/matcher/__init__.py
+++ b/sleekxmpp/xmlstream/matcher/__init__.py
@@ -0,0 +1,13 @@
+"""
+ 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 sleekxmpp.xmlstream.matcher.id import MatcherId
+from sleekxmpp.xmlstream.matcher.many import MatchMany
+from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
+from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask
+from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
diff --git a/sleekxmpp/xmlstream/matcher/base.py b/sleekxmpp/xmlstream/matcher/base.py
index 8185bdc5..51da0942 100644
--- a/sleekxmpp/xmlstream/matcher/base.py
+++ b/sleekxmpp/xmlstream/matcher/base.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
class MatcherBase(object):
diff --git a/sleekxmpp/xmlstream/matcher/id.py b/sleekxmpp/xmlstream/matcher/id.py
index bb858fc4..43972c23 100644
--- a/sleekxmpp/xmlstream/matcher/id.py
+++ b/sleekxmpp/xmlstream/matcher/id.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from . import base
diff --git a/sleekxmpp/xmlstream/matcher/many.py b/sleekxmpp/xmlstream/matcher/many.py
index cf860e62..ff0c4e4d 100644
--- a/sleekxmpp/xmlstream/matcher/many.py
+++ b/sleekxmpp/xmlstream/matcher/many.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from . import base
from xml.etree import cElementTree
diff --git a/sleekxmpp/xmlstream/matcher/stanzapath.py b/sleekxmpp/xmlstream/matcher/stanzapath.py
index bd091c0e..e315445d 100644
--- a/sleekxmpp/xmlstream/matcher/stanzapath.py
+++ b/sleekxmpp/xmlstream/matcher/stanzapath.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from . import base
from xml.etree import cElementTree
diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py
index eba3e954..89fd6422 100644
--- a/sleekxmpp/xmlstream/matcher/xmlmask.py
+++ b/sleekxmpp/xmlstream/matcher/xmlmask.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from . import base
from xml.etree import cElementTree
diff --git a/sleekxmpp/xmlstream/matcher/xpath.py b/sleekxmpp/xmlstream/matcher/xpath.py
index f6d04243..7f3d20be 100644
--- a/sleekxmpp/xmlstream/matcher/xpath.py
+++ b/sleekxmpp/xmlstream/matcher/xpath.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from . import base
from xml.etree import cElementTree
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index 64020c8f..83a8ddfc 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -3,386 +3,516 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
-from xml.etree import cElementTree as ET
+
+import copy
import logging
-import traceback
import sys
import weakref
+from xml.etree import cElementTree as ET
+
+from sleekxmpp.xmlstream import JID
+from sleekxmpp.xmlstream.tostring import tostring
+
+
+# Used to check if an argument is an XML object.
+XML_TYPE = type(ET.Element('xml'))
+
+
+def registerStanzaPlugin(stanza, plugin):
+ """
+ Associate a stanza object as a plugin for another stanza.
+
+ Arguments:
+ stanza -- The class of the parent stanza.
+ plugin -- The class of the plugin stanza.
+ """
+ tag = "{%s}%s" % (plugin.namespace, plugin.name)
+ stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
+ stanza.plugin_tag_map[tag] = plugin
+
+
+class ElementBase(object):
+ name = 'stanza'
+ plugin_attrib = 'plugin'
+ namespace = 'jabber:client'
+ interfaces = set(('type', 'to', 'from', 'id', 'payload'))
+ types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
+ sub_interfaces = tuple()
+ plugin_attrib_map = {}
+ plugin_tag_map = {}
+ subitem = None
+
+ def __init__(self, xml=None, parent=None):
+ """
+ Create a new stanza object.
+
+ Arguments:
+ xml -- Initialize the stanza with optional existing XML.
+ parent -- Optional stanza object that contains this stanza.
+ """
+ self.xml = xml
+ self.plugins = {}
+ self.iterables = []
+ self.idx = 0
+ if parent is None:
+ self.parent = None
+ else:
+ self.parent = weakref.ref(parent)
+
+ if self.setup(xml):
+ # If we generated our own XML, then everything is ready.
+ return
+
+ # Initialize values using provided XML
+ for child in self.xml.getchildren():
+ if child.tag in self.plugin_tag_map:
+ plugin = self.plugin_tag_map[child.tag]
+ self.plugins[plugin.plugin_attrib] = plugin(child, self)
+ if self.subitem is not None:
+ for sub in self.subitem:
+ if child.tag == "{%s}%s" % (sub.namespace, sub.name):
+ self.iterables.append(sub(child, self))
+ break
+
+ def setup(self, xml=None):
+ """
+ Initialize the stanza's XML contents.
+
+ Will return True if XML was generated according to the stanza's
+ definition.
+
+ Arguments:
+ xml -- Optional XML object to use for the stanza's content
+ instead of generating XML.
+ """
+ if self.xml is None:
+ self.xml = xml
+
+ if self.xml is None:
+ # Generate XML from the stanza definition
+ for ename in self.name.split('/'):
+ new = ET.Element("{%s}%s" % (self.namespace, ename))
+ if self.xml is None:
+ self.xml = new
+ else:
+ last_xml.append(new)
+ last_xml = new
+ if self.parent is not None:
+ self.parent().xml.append(self.xml)
+
+ # We had to generate XML
+ return True
+ else:
+ # We did not generate XML
+ return False
+
+ def enable(self, attrib):
+ """
+ Enable and initialize a stanza plugin.
+
+ Alias for initPlugin.
+
+ Arguments:
+ attrib -- The stanza interface for the plugin.
+ """
+ return self.initPlugin(attrib)
+
+ def initPlugin(self, attrib):
+ """
+ Enable and initialize a stanza plugin.
+
+ Arguments:
+ attrib -- The stanza interface for the plugin.
+ """
+ if attrib not in self.plugins:
+ plugin_class = self.plugin_attrib_map[attrib]
+ self.plugins[attrib] = plugin_class(parent=self)
+ return self
+
+ def getStanzaValues(self):
+ """
+ Return a dictionary of the stanza's interface values.
+
+ Stanza plugin values are included as nested dictionaries.
+ """
+ values = {}
+ for interface in self.interfaces:
+ values[interface] = self[interface]
+ for plugin, stanza in self.plugins.items():
+ values[plugin] = stanza.getStanzaValues()
+ if self.iterables:
+ iterables = []
+ for stanza in self.iterables:
+ iterables.append(stanza.getStanzaValues())
+ iterables[-1].update({
+ '__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)
+ })
+ values['substanzas'] = iterables
+ return values
+
+ def setStanzaValues(self, values):
+ """
+ Set multiple stanza interface values using a dictionary.
+
+ Stanza plugin values may be set using nested dictionaries.
+
+ Arguments:
+ values -- A dictionary mapping stanza interface with values.
+ Plugin interfaces may accept a nested dictionary that
+ will be used recursively.
+ """
+ for interface, value in values.items():
+ if interface == 'substanzas':
+ for subdict in value:
+ if '__childtag__' in subdict:
+ for subclass in self.subitem:
+ child_tag = "{%s}%s" % (subclass.namespace,
+ subclass.name)
+ if subdict['__childtag__'] == child_tag:
+ sub = subclass(parent=self)
+ sub.setStanzaValues(subdict)
+ self.iterables.append(sub)
+ break
+ elif interface in self.interfaces:
+ self[interface] = value
+ elif interface in self.plugin_attrib_map:
+ if interface not in self.plugins:
+ self.initPlugin(interface)
+ self.plugins[interface].setStanzaValues(value)
+ return self
+
+ def __getitem__(self, attrib):
+ """
+ Return the value of a stanza interface using dictionary-like syntax.
+
+ Example:
+ >>> msg['body']
+ 'Message contents'
+
+ Stanza interfaces are typically mapped directly to the underlying XML
+ object, but can be overridden by the presence of a getAttrib method
+ (or getFoo where the interface is named foo, etc).
+
+ The search order for interface value retrieval for an interface
+ named 'foo' is:
+ 1. The list of substanzas.
+ 2. The result of calling getFoo.
+ 3. The contents of the foo subelement, if foo is a sub interface.
+ 4. The value of the foo attribute of the XML object.
+ 5. The plugin named 'foo'
+ 6. An empty string.
+
+ Arguments:
+ attrib -- The name of the requested stanza interface.
+ """
+ if attrib == 'substanzas':
+ return self.iterables
+ elif attrib in self.interfaces:
+ get_method = "get%s" % attrib.title()
+ if hasattr(self, get_method):
+ return getattr(self, get_method)()
+ else:
+ if attrib in self.sub_interfaces:
+ return self._getSubText(attrib)
+ else:
+ return self._getAttr(attrib)
+ elif attrib in self.plugin_attrib_map:
+ if attrib not in self.plugins:
+ self.initPlugin(attrib)
+ return self.plugins[attrib]
+ else:
+ return ''
+
+ def __setitem__(self, attrib, value):
+ """
+ Set the value of a stanza interface using dictionary-like syntax.
+
+ Example:
+ >>> msg['body'] = "Hi!"
+ >>> msg['body']
+ 'Hi!'
+
+ Stanza interfaces are typically mapped directly to the underlying XML
+ object, but can be overridden by the presence of a setAttrib method
+ (or setFoo where the interface is named foo, etc).
+
+ The effect of interface value assignment for an interface
+ named 'foo' will be one of:
+ 1. Delete the interface's contents if the value is None.
+ 2. Call setFoo, if it exists.
+ 3. Set the text of a foo element, if foo is in sub_interfaces.
+ 4. Set the value of a top level XML attribute name foo.
+ 5. Attempt to pass value to a plugin named foo using the plugin's
+ foo interface.
+ 6. Do nothing.
+
+ Arguments:
+ attrib -- The name of the stanza interface to modify.
+ value -- The new value of the stanza interface.
+ """
+ if attrib in self.interfaces:
+ if value is not None:
+ if hasattr(self, "set%s" % attrib.title()):
+ getattr(self, "set%s" % attrib.title())(value,)
+ else:
+ if attrib in self.sub_interfaces:
+ return self._setSubText(attrib, text=value)
+ else:
+ self._setAttr(attrib, value)
+ else:
+ self.__delitem__(attrib)
+ elif attrib in self.plugin_attrib_map:
+ if attrib not in self.plugins:
+ self.initPlugin(attrib)
+ self.plugins[attrib][attrib] = value
+ return self
+
+ @property
+ def attrib(self): #backwards compatibility
+ return self
-if sys.version_info < (3,0):
- from . import tostring26 as tostring
-else:
- from . import tostring
-
-xmltester = type(ET.Element('xml'))
-
-class JID(object):
- def __init__(self, jid):
- self.jid = jid
-
- def __getattr__(self, name):
- if name == 'resource':
- return self.jid.split('/', 1)[-1]
- elif name == 'user':
- if '@' in self.jid:
- return self.jid.split('@', 1)[0]
- else:
- return ''
- elif name == 'server':
- return self.jid.split('@', 1)[-1].split('/', 1)[0]
- elif name == 'full':
- return self.jid
- elif name == 'bare':
- return self.jid.split('/', 1)[0]
-
- def __str__(self):
- return self.jid
-
-class ElementBase(tostring.ToString):
- name = 'stanza'
- plugin_attrib = 'plugin'
- namespace = 'jabber:client'
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
- types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
- sub_interfaces = tuple()
- plugin_attrib_map = {}
- plugin_tag_map = {}
- subitem = None
-
- def __init__(self, xml=None, parent=None):
- if parent is None:
- self.parent = None
- else:
- self.parent = weakref.ref(parent)
- self.xml = xml
- self.plugins = {}
- self.iterables = []
- self.idx = 0
- if not self.setup(xml):
- for child in self.xml.getchildren():
- if child.tag in self.plugin_tag_map:
- self.plugins[self.plugin_tag_map[child.tag].plugin_attrib] = self.plugin_tag_map[child.tag](xml=child, parent=self)
- if self.subitem is not None:
- for sub in self.subitem:
- if child.tag == "{%s}%s" % (sub.namespace, sub.name):
- self.iterables.append(sub(xml=child, parent=self))
- break
-
-
- @property
- def attrib(self): #backwards compatibility
- return self
-
- def __iter__(self):
- self.idx = 0
- return self
-
- 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:
- return self.appendxml(item)
- else:
- raise TypeError
- 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]
- out += [x for x in self.plugins]
- if self.iterables:
- out.append('substanzas')
- return tuple(out)
-
- def match(self, matchstring):
- if isinstance(matchstring, str):
- nodes = matchstring.split('/')
- else:
- nodes = matchstring
- tagargs = nodes[0].split('@')
- if tagargs[0] not in (self.plugins, self.plugin_attrib): return False
- founditerable = False
- for iterable in self.iterables:
- if nodes[1:] == []:
- break
- founditerable = iterable.match(nodes[1:])
- if founditerable: break;
- for evals in tagargs[1:]:
- x,y = evals.split('=')
- if self[x] != y: return False
- if not founditerable and len(nodes) > 1:
- next = nodes[1].split('@')[0]
- if next in self.plugins:
- return self.plugins[next].match(nodes[1:])
- 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
- if self.xml is None:
- for ename in self.name.split('/'):
- new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace})
- if self.xml is None:
- self.xml = new
- else:
- self.xml.append(new)
- if self.parent is not None:
- self.parent().xml.append(self.xml)
- return True #had to generate XML
- else:
- return False
-
- 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
- elif attrib in self.interfaces:
- if hasattr(self, "get%s" % attrib.title()):
- return getattr(self, "get%s" % attrib.title())()
- else:
- if attrib in self.sub_interfaces:
- return self._getSubText(attrib)
- else:
- return self._getAttr(attrib)
- elif attrib in self.plugin_attrib_map:
- if attrib not in self.plugins: self.initPlugin(attrib)
- return self.plugins[attrib]
- else:
- return ''
-
- def __setitem__(self, attrib, value):
- if attrib in self.interfaces:
- if value is not None:
- if hasattr(self, "set%s" % attrib.title()):
- getattr(self, "set%s" % attrib.title())(value,)
- else:
- if attrib in self.sub_interfaces:
- return self._setSubText(attrib, text=value)
- else:
- self._setAttr(attrib, value)
- else:
- self.__delitem__(attrib)
- elif attrib in self.plugin_attrib_map:
- if attrib not in self.plugins: self.initPlugin(attrib)
- 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()):
- getattr(self, "del%s" % attrib.title())()
- else:
- if attrib in self.sub_interfaces:
- return self._delSub(attrib)
- else:
- self._delAttr(attrib)
- elif attrib in self.plugin_attrib_map:
- if attrib in self.plugins:
- del self.plugins[attrib]
- return self
-
- def __eq__(self, other):
- if not isinstance(other, ElementBase):
- return False
- values = self.getValues()
- for key in other:
- 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):
- return self.xml.attrib.get(name, '')
-
- def _getSubText(self, name):
- stanza = self.xml.find("{%s}%s" % (self.namespace, name))
- if stanza is None or stanza.text is None:
- return ''
- else:
- return stanza.text
-
- def _setSubText(self, name, attrib={}, text=None):
- if text is None or text == '':
- return self.__delitem__(name)
- stanza = self.xml.find("{%s}%s" % (self.namespace, name))
- if stanza is None:
- #self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib))
- stanza = ET.Element("{%s}%s" % (self.namespace, name))
- self.xml.append(stanza)
- stanza.text = text
- return stanza
-
- def _delSub(self, name):
- for child in self.xml.getchildren():
- if child.tag == "{%s}%s" % (self.namespace, name):
- self.xml.remove(child)
-
- def getValues(self):
- out = {}
- for interface in self.interfaces:
- out[interface] = self[interface]
- for pluginkey in self.plugins:
- out[pluginkey] = self.plugins[pluginkey].getValues()
- if self.iterables:
- iterables = []
- for stanza in self.iterables:
- iterables.append(stanza.getValues())
- iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)})
- out['substanzas'] = iterables
- return out
-
- def setValues(self, attrib):
- for interface in attrib:
- if interface == 'substanzas':
- for subdict in attrib['substanzas']:
- if '__childtag__' in subdict:
- for subclass in self.subitem:
- if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name):
- sub = subclass(parent=self)
- sub.setValues(subdict)
- self.iterables.append(sub)
- break
- elif interface in self.interfaces:
- self[interface] = attrib[interface]
- elif interface in self.plugin_attrib_map and interface not in self.plugins:
- self.initPlugin(interface)
- if interface in self.plugins:
- self.plugins[interface].setValues(attrib[interface])
- return self
-
- def appendxml(self, xml):
- self.xml.append(xml)
- return self
-
- #def __del__(self): #prevents garbage collection of reference cycle
- # if self.parent is not None:
- # self.parent.xml.remove(self.xml)
+ def __iter__(self):
+ self.idx = 0
+ return self
+
+ 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) == XML_TYPE:
+ return self.appendxml(item)
+ else:
+ raise TypeError
+ 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]
+ out += [x for x in self.plugins]
+ if self.iterables:
+ out.append('substanzas')
+ return tuple(out)
+
+ def match(self, matchstring):
+ if isinstance(matchstring, str):
+ nodes = matchstring.split('/')
+ else:
+ nodes = matchstring
+ tagargs = nodes[0].split('@')
+ if tagargs[0] not in (self.plugins, self.plugin_attrib): return False
+ founditerable = False
+ for iterable in self.iterables:
+ if nodes[1:] == []:
+ break
+ founditerable = iterable.match(nodes[1:])
+ if founditerable: break;
+ for evals in tagargs[1:]:
+ x,y = evals.split('=')
+ if self[x] != y: return False
+ if not founditerable and len(nodes) > 1:
+ next = nodes[1].split('@')[0]
+ if next in self.plugins:
+ return self.plugins[next].match(nodes[1:])
+ 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 __delitem__(self, attrib):
+ if attrib.lower() in self.interfaces:
+ if hasattr(self, "del%s" % attrib.title()):
+ getattr(self, "del%s" % attrib.title())()
+ else:
+ if attrib in self.sub_interfaces:
+ return self._delSub(attrib)
+ else:
+ self._delAttr(attrib)
+ elif attrib in self.plugin_attrib_map:
+ if attrib in self.plugins:
+ del self.plugins[attrib]
+ return self
+
+ def __eq__(self, other):
+ if not isinstance(other, ElementBase):
+ return False
+ values = self.getStanzaValues()
+ for key in other:
+ 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)
+ stanza = self.xml.find(name)
+ if stanza is None or stanza.text is None:
+ return ''
+ else:
+ return stanza.text
+
+ def _setSubText(self, name, attrib={}, text=None):
+ if '}' not in name:
+ name = "{%s}%s" % (self.namespace, name)
+ if text is None or text == '':
+ return self.__delitem__(name)
+ stanza = self.xml.find(name)
+ if stanza is None:
+ stanza = ET.Element(name)
+ 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 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)
class StanzaBase(ElementBase):
- name = 'stanza'
- namespace = 'jabber:client'
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
- types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
- sub_interfaces = tuple()
-
- def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None):
- self.stream = stream
- if stream is not None:
- self.namespace = stream.default_ns
- ElementBase.__init__(self, xml)
- if stype is not None:
- self['type'] = stype
- if sto is not None:
- self['to'] = sto
- 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
- return self
-
- 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):
- self['from'], self['to'] = self['to'], 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.error(traceback.format_tb(e))
-
- def send(self):
- self.stream.sendRaw(self.__str__())
+ name = 'stanza'
+ namespace = 'jabber:client'
+ interfaces = set(('type', 'to', 'from', 'id', 'payload'))
+ types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
+ sub_interfaces = tuple()
+
+ def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None):
+ self.stream = stream
+ if stream is not None:
+ self.namespace = stream.default_ns
+ ElementBase.__init__(self, xml)
+ if stype is not None:
+ self['type'] = stype
+ if sto is not None:
+ self['to'] = sto
+ 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
+ return self
+
+ 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:
+ self['from'], self['to'] = self['to'], self['from']
+ else:
+ self['to'] = self['from']
+ 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/statemachine.py b/sleekxmpp/xmlstream/statemachine.py
index fb7d1508..8a1aa22d 100644
--- a/sleekxmpp/xmlstream/statemachine.py
+++ b/sleekxmpp/xmlstream/statemachine.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from __future__ import with_statement
import threading
diff --git a/sleekxmpp/xmlstream/statemanager.py b/sleekxmpp/xmlstream/statemanager.py
new file mode 100644
index 00000000..c7f76e75
--- /dev/null
+++ b/sleekxmpp/xmlstream/statemanager.py
@@ -0,0 +1,139 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+from __future__ import with_statement
+import threading
+
+
+class StateError(Exception):
+ """Raised whenever a state transition was attempted but failed."""
+
+
+class StateManager(object):
+ """
+ At the very core of SleekXMPP there is a need to track various
+ library configuration settings, XML stream features, and the
+ network connection status. The state manager is responsible for
+ tracking this information in a thread-safe manner.
+
+ State 'variables' store the current state of these items as simple
+ string values or booleans. Changing those values must be done
+ according to transitions defined when creating the state variable.
+
+ If a state variable is given a value that is not allowed according
+ to the transition definitions, a StateError is raised. When a
+ valid value is assigned an event is raised named:
+
+ _state_changed_nameofthestatevariable
+
+ The event carries a dictionary containing the previous and the new
+ state values.
+ """
+
+ def __init__(self, event_func=None):
+ """
+ Initialize the state manager. The parameter event_func should be
+ the event() method of a SleekXMPP object in order to enable
+ _state_changed_* events.
+ """
+ self.main_lock = threading.Lock()
+ self.locks = {}
+ self.state_variables = {}
+
+ if event_func is not None:
+ self.event = event_func
+ else:
+ self.event = lambda name, data: None
+
+ def add(self, name, default=False, values=None, transitions=None):
+ """
+ Create a new state variable.
+
+ When transitions is specified, only those defined state change
+ transitions will be allowed.
+
+ When values is specified (and not transitions), any state changes
+ between those values are allowed.
+
+ If neither values nor transitions are defined, then the state variable
+ will be a binary switch between True and False.
+ """
+ if name in self.state_variables:
+ raise IndexError("State variable %s already exists" % name)
+
+ self.locks[name] = threading.Lock()
+ with self.locks[name]:
+ var = {'value': default,
+ 'default': default,
+ 'transitions': {}}
+
+ if transitions is not None:
+ for start in transitions:
+ var['transitions'][start] = set(transitions[start])
+ elif values is not None:
+ values = set(values)
+ for value in values:
+ var['transitions'][value] = values
+ elif values is None:
+ var['transitions'] = {True: [False],
+ False: [True]}
+
+ self.state_variables[name] = var
+
+ def addStates(self, var_defs):
+ """
+ Create multiple state variables at once.
+ """
+ for var, data in var_defs:
+ self.add(var,
+ default=data.get('default', False),
+ values=data.get('values', None),
+ transitions=data.get('transitions', None))
+
+ def force_set(self, name, val):
+ """
+ Force setting a state variable's value by overriding transition checks.
+ """
+ with self.locks[name]:
+ self.state_variables[name]['value'] = val
+
+ def reset(self, name):
+ """
+ Reset a state variable to its default value.
+ """
+ with self.locks[name]:
+ default = self.state_variables[name]['default']
+ self.state_variables[name]['value'] = default
+
+ def __getitem__(self, name):
+ """
+ Get the value of a state variable if it exists.
+ """
+ with self.locks[name]:
+ if name not in self.state_variables:
+ raise IndexError("State variable %s does not exist" % name)
+ return self.state_variables[name]['value']
+
+ def __setitem__(self, name, val):
+ """
+ Attempt to set the value of a state variable, but raise StateError
+ if the transition is undefined.
+
+ A _state_changed_* event is triggered after a successful transition.
+ """
+ with self.locks[name]:
+ if name not in self.state_variables:
+ raise IndexError("State variable %s does not exist" % name)
+ current = self.state_variables[name]['value']
+ if current == val:
+ return
+ if val in self.state_variables[name]['transitions'][current]:
+ self.state_variables[name]['value'] = val
+ self.event('_state_changed_%s' % name, {'from': current, 'to': val})
+ else:
+ raise StateError("Can not transition from '%s' to '%s'" % (str(current), str(val)))
diff --git a/sleekxmpp/xmlstream/tostring/__init__.py b/sleekxmpp/xmlstream/tostring/__init__.py
index 6603cbb8..5852cba2 100644
--- a/sleekxmpp/xmlstream/tostring/__init__.py
+++ b/sleekxmpp/xmlstream/tostring/__init__.py
@@ -1,60 +1,19 @@
+"""
+ 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("</%s>" % (itag, ))
- if xml.tail:
- newoutput.append(self.xmlesc(xml.tail))
- elif xml.text:
- newoutput.append(">%s</%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] = '&amp;'
- elif c == '<':
- text[cc] = '&lt;'
- elif c == '>':
- text[cc] = '&gt;'
- elif c == "'":
- text[cc] = '&apos;'
- else:
- text[cc] = '&quot;'
- cc += 1
- return ''.join(text)
+import sys
+
+# Import the correct tostring and xml_escape functions based on the Python
+# version in order to properly handle Unicode.
+
+if sys.version_info < (3, 0):
+ from sleekxmpp.xmlstream.tostring.tostring26 import tostring, xml_escape
+else:
+ from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape
+
+__all__ = ['tostring', 'xml_escape']
diff --git a/sleekxmpp/xmlstream/tostring/tostring.py b/sleekxmpp/xmlstream/tostring/tostring.py
new file mode 100644
index 00000000..c2696321
--- /dev/null
+++ b/sleekxmpp/xmlstream/tostring/tostring.py
@@ -0,0 +1,95 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2010 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ See the file LICENSE for copying permission.
+"""
+
+
+def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
+ """
+ Serialize an XML object to a Unicode string.
+
+ If namespaces are provided using xmlns or stanza_ns, then elements
+ that use those namespaces will not include the xmlns attribute in
+ the output.
+
+ 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("</%s>" % tag_name)
+ elif xml.text:
+ # If we only have text content.
+ output.append(">%s</%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 = {'&': '&amp;',
+ '<': '&lt;',
+ '>': '&gt;',
+ "'": '&apos;',
+ '"': '&quot;'}
+ 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
new file mode 100644
index 00000000..7a376374
--- /dev/null
+++ b/sleekxmpp/xmlstream/tostring/tostring26.py
@@ -0,0 +1,104 @@
+"""
+ 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
+
+
+def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
+ """
+ Serialize an XML object to a Unicode string.
+
+ If namespaces are provided using xmlns or stanza_ns, then elements
+ that use those namespaces will not include the xmlns attribute in
+ the output.
+
+ 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"</%s>" % 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</%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'&amp;',
+ u'<': u'&lt;',
+ u'>': u'&gt;',
+ u"'": u'&apos;',
+ u'"': u'&quot;'}
+ 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"</%s>" % (itag, ))
- if xml.tail:
- newoutput.append(self.xmlesc(xml.tail))
- elif xml.text:
- newoutput.append(">%s</%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'&amp;'
- elif c == u'<':
- text[cc] = u'&lt;'
- elif c == u'>':
- text[cc] = u'&gt;'
- elif c == u"'":
- text[cc] = u'&apos;'
- else:
- text[cc] = u'&quot;'
- cc += 1
- return ''.join(text)
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
index 6b92abca..bf39bb33 100644
--- a/sleekxmpp/xmlstream/xmlstream.py
+++ b/sleekxmpp/xmlstream/xmlstream.py
@@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
- See the file license.txt for copying permission.
+ See the file LICENSE for copying permission.
"""
from __future__ import with_statement, unicode_literals
@@ -19,11 +19,13 @@ import logging
import socket
import threading
import time
-import traceback
import types
+import copy
import xml.sax.saxutils
from . import scheduler
+from sleekxmpp.xmlstream.tostring import tostring
+RESPONSE_TIMEOUT = 10
HANDLER_THREADS = 1
ssl_support = True
@@ -36,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
@@ -71,6 +73,7 @@ class XMLStream(object):
self.use_ssl = False
self.use_tls = False
+ self.default_ns = ''
self.stream_header = "<stream>"
self.stream_footer = "</stream>"
@@ -81,7 +84,7 @@ class XMLStream(object):
self.namespace_map = {}
self.run = True
-
+
def setSocket(self, socket):
"Set the socket"
self.socket = socket
@@ -89,10 +92,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)
@@ -124,7 +127,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"
@@ -145,7 +148,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):
@@ -159,10 +162,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
@@ -194,14 +197,14 @@ class XMLStream(object):
return
else:
self.state.set('processing', False)
- traceback.print_exc()
+ logging.exception('Socket Error')
self.disconnect(reconnect=True)
except:
if not self.state.reconnect:
return
else:
self.state.set('processing', False)
- traceback.print_exc()
+ logging.exception('Connection error. Reconnecting.')
self.disconnect(reconnect=True)
if self.state['reconnect']:
self.reconnect()
@@ -211,7 +214,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
@@ -244,7 +247,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)
@@ -257,14 +260,13 @@ class XMLStream(object):
logging.warning("Failed to send %s" % data)
self.state.set('connected', False)
if self.state.reconnect:
- logging.error("Disconnected. Socket Error.")
- traceback.print_exc()
+ 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,41 +292,40 @@ 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, xmlns=self.default_ns, stream=self))
xmlobj = self.incoming_filter(xmlobj)
- stanza = None
+ stanza_type = StanzaBase
for stanza_class in self.__root_stanza:
if xmlobj.tag == "{%s}%s" % (self.default_ns, stanza_class.name):
- #if self.__root_stanza[stanza_class].match(xmlobj):
- stanza = stanza_class(self, xmlobj)
+ stanza_type = stanza_class
break
- if stanza is None:
- stanza = StanzaBase(self, xmlobj)
unhandled = True
+ stanza = stanza_type(self, xmlobj)
for handler in self.__handlers:
if handler.match(stanza):
- handler.prerun(stanza)
- self.eventqueue.put(('stanza', handler, stanza))
+ stanza_copy = stanza_type(self, copy.deepcopy(xmlobj))
+ handler.prerun(stanza_copy)
+ self.eventqueue.put(('stanza', handler, stanza_copy))
if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler))
unhandled = False
if unhandled:
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:
@@ -344,22 +345,22 @@ class XMLStream(object):
try:
handler.run(args[0])
except Exception as e:
- traceback.print_exc()
+ logging.exception('Error processing event handler: %s' % handler.name)
args[0].exception(e)
elif etype == 'schedule':
try:
logging.debug(args)
handler(*args[0])
except:
- logging.error(traceback.format_exc())
+ logging.exception('Error processing scheduled task')
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
@@ -368,81 +369,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("</%s>" % (itag, ))
- if xml.tail:
- newoutput.append(self.xmlesc(xml.tail))
- elif xml.text:
- newoutput.append(">%s</%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] = '&amp;'
- elif c == '<':
- text[cc] = '&lt;'
- elif c == '>':
- text[cc] = '&gt;'
- elif c == "'":
- text[cc] = '&apos;'
- elif self.escape_quotes:
- text[cc] = '&quot;'
- cc += 1
- return ''.join(text)
-
def start_stream_handler(self, xml):
"""Meant to be overridden"""
pass