From 2923f5656150448e10f6f3a58a63470a987d47e1 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 17 Jan 2012 22:28:44 -0800 Subject: [PATCH] Pre-parse StanzaPath paths to speed up matching. The parsing and namespace cleaning isn't terribly expensive, but it does add up. It was adding an extra 5sec when processing 100,000 basic message stanzas. --- sleekxmpp/xmlstream/matcher/stanzapath.py | 11 ++- sleekxmpp/xmlstream/stanzabase.py | 88 ++++++++++++----------- 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/sleekxmpp/xmlstream/matcher/stanzapath.py b/sleekxmpp/xmlstream/matcher/stanzapath.py index 61c5332c..a4c0fda0 100644 --- a/sleekxmpp/xmlstream/matcher/stanzapath.py +++ b/sleekxmpp/xmlstream/matcher/stanzapath.py @@ -10,6 +10,7 @@ """ from sleekxmpp.xmlstream.matcher.base import MatcherBase +from sleekxmpp.xmlstream.stanzabase import fix_ns class StanzaPath(MatcherBase): @@ -18,8 +19,16 @@ class StanzaPath(MatcherBase): The StanzaPath matcher selects stanzas that match a given "stanza path", which is similar to a normal XPath except that it uses the interfaces and plugins of the stanza instead of the actual, underlying XML. + + :param criteria: Object to compare some aspect of a stanza against. """ + def __init__(self, criteria): + self._criteria = fix_ns(criteria, split=True, + propagate_ns=False, + default_ns='jabber:client') + self._raw_criteria = criteria + def match(self, stanza): """ Compare a stanza against a "stanza path". A stanza path is similar to @@ -31,4 +40,4 @@ class StanzaPath(MatcherBase): :param stanza: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` stanza to compare against. """ - return stanza.match(self._criteria) + return stanza.match(self._criteria) or stanza.match(self._raw_criteria) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index d1409706..dff8c997 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -77,6 +77,49 @@ def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False): registerStanzaPlugin = register_stanza_plugin +def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''): + """Apply the stanza's namespace to elements in an XPath expression. + + :param string xpath: The XPath expression to fix with namespaces. + :param bool split: Indicates if the fixed XPath should be left as a + list of element names with namespaces. Defaults to + False, which returns a flat string path. + :param bool propagate_ns: Overrides propagating parent element + namespaces to child elements. Useful if + you wish to simply split an XPath that has + non-specified namespaces, and child and + parent namespaces are known not to always + match. Defaults to True. + """ + fixed = [] + # Split the XPath into a series of blocks, where a block + # is started by an element with a namespace. + ns_blocks = xpath.split('{') + for ns_block in ns_blocks: + if '}' in ns_block: + # Apply the found namespace to following elements + # that do not have namespaces. + namespace = ns_block.split('}')[0] + elements = ns_block.split('}')[1].split('/') + else: + # Apply the stanza's namespace to the following + # elements since no namespace was provided. + namespace = default_ns + elements = ns_block.split('/') + + for element in elements: + if element: + # Skip empty entry artifacts from splitting. + if propagate_ns: + tag = '{%s}%s' % (namespace, element) + else: + tag = element + fixed.append(tag) + if split: + return fixed + return '/'.join(fixed) + + class ElementBase(object): """ @@ -760,7 +803,7 @@ class ElementBase(object): may be either a string or a list of element names with attribute checks. """ - if isinstance(xpath, str): + if not isinstance(xpath, list): xpath = self._fix_ns(xpath, split=True, propagate_ns=False) # Extract the tag name and attribute checks for the first XPath node. @@ -952,46 +995,9 @@ class ElementBase(object): return self def _fix_ns(self, xpath, split=False, propagate_ns=True): - """Apply the stanza's namespace to elements in an XPath expression. - - :param string xpath: The XPath expression to fix with namespaces. - :param bool split: Indicates if the fixed XPath should be left as a - list of element names with namespaces. Defaults to - False, which returns a flat string path. - :param bool propagate_ns: Overrides propagating parent element - namespaces to child elements. Useful if - you wish to simply split an XPath that has - non-specified namespaces, and child and - parent namespaces are known not to always - match. Defaults to True. - """ - fixed = [] - # Split the XPath into a series of blocks, where a block - # is started by an element with a namespace. - ns_blocks = xpath.split('{') - for ns_block in ns_blocks: - if '}' in ns_block: - # Apply the found namespace to following elements - # that do not have namespaces. - namespace = ns_block.split('}')[0] - elements = ns_block.split('}')[1].split('/') - else: - # Apply the stanza's namespace to the following - # elements since no namespace was provided. - namespace = self.namespace - elements = ns_block.split('/') - - for element in elements: - if element: - # Skip empty entry artifacts from splitting. - if propagate_ns: - tag = '{%s}%s' % (namespace, element) - else: - tag = element - fixed.append(tag) - if split: - return fixed - return '/'.join(fixed) + return fix_ns(xpath, split=split, + propagate_ns=propagate_ns, + default_ns=self.namespace) def __eq__(self, other): """Compare the stanza object with another to test for equality.