Updated ElementBase.match and added unit tests.
This commit is contained in:
parent
5d458bf6c2
commit
1eaa9cb28c
2 changed files with 96 additions and 24 deletions
|
@ -454,7 +454,60 @@ class ElementBase(object):
|
||||||
# If we don't want to delete elements up the tree, stop
|
# If we don't want to delete elements up the tree, stop
|
||||||
# after deleting the first level of elements.
|
# after deleting the first level of elements.
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def match(self, xpath):
|
||||||
|
"""
|
||||||
|
Compare a stanza object with an XPath expression. If the XPath matches
|
||||||
|
the contents of the stanza object, the match is successful.
|
||||||
|
|
||||||
|
The XPath expression may include checks for stanza attributes.
|
||||||
|
For example:
|
||||||
|
presence@show=xa@priority=2/status
|
||||||
|
Would match a presence stanza whose show value is set to 'xa', has a
|
||||||
|
priority value of '2', and has a status element.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
xpath -- The XPath expression to check against. It may be either a
|
||||||
|
string or a list of element names with attribute checks.
|
||||||
|
"""
|
||||||
|
if isinstance(xpath, str):
|
||||||
|
xpath = xpath.split('/')
|
||||||
|
|
||||||
|
# Extract the tag name and attribute checks for the first XPath node.
|
||||||
|
components = xpath[0].split('@')
|
||||||
|
tag = components[0]
|
||||||
|
attributes = components[1:]
|
||||||
|
|
||||||
|
if tag not in (self.name, self.plugins, self.plugin_attrib):
|
||||||
|
# The requested tag is not in this stanza, so no match.
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check the rest of the XPath against any substanzas.
|
||||||
|
matched_substanzas = False
|
||||||
|
for substanza in self.iterables:
|
||||||
|
if xpath[1:] == []:
|
||||||
|
break
|
||||||
|
matched_substanzas = substanza.match(xpath[1:])
|
||||||
|
if matched_substanzas:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check attribute values.
|
||||||
|
for attribute in attributes:
|
||||||
|
name, value = attribute.split('=')
|
||||||
|
if self[name] != value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Attempt to continue matching the XPath using the stanza's plugins.
|
||||||
|
if not matched_substanzas and len(xpath) > 1:
|
||||||
|
next_tag = xpath[1].split('@')[0]
|
||||||
|
if next_tag in self.plugins:
|
||||||
|
return self.plugins[next_tag].match(xpath[1:])
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Everything matched.
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attrib(self): #backwards compatibility
|
def attrib(self): #backwards compatibility
|
||||||
return self
|
return self
|
||||||
|
@ -511,30 +564,6 @@ class ElementBase(object):
|
||||||
out.append('substanzas')
|
out.append('substanzas')
|
||||||
return tuple(out)
|
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
|
def find(self, xpath): # for backwards compatiblity, expose elementtree interface
|
||||||
return self.xml.find(xpath)
|
return self.xml.find(xpath)
|
||||||
|
|
||||||
|
|
|
@ -428,5 +428,48 @@ class TestElementBase(SleekTest):
|
||||||
</foo>
|
</foo>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
def testMatch(self):
|
||||||
|
"""Test matching a stanza against an XPath expression."""
|
||||||
|
|
||||||
|
class TestSubStanza(ElementBase):
|
||||||
|
name = "sub"
|
||||||
|
namespace = "foo"
|
||||||
|
interfaces = set(('attrib',))
|
||||||
|
|
||||||
|
class TestStanza(ElementBase):
|
||||||
|
name = "foo"
|
||||||
|
namespace = "foo"
|
||||||
|
interfaces = set(('bar','baz'))
|
||||||
|
subitem = (TestSubStanza,)
|
||||||
|
|
||||||
|
class TestStanzaPlugin(ElementBase):
|
||||||
|
name = "plugin"
|
||||||
|
namespace = "foo"
|
||||||
|
interfaces = set(('attrib',))
|
||||||
|
|
||||||
|
registerStanzaPlugin(TestStanza, TestStanzaPlugin)
|
||||||
|
|
||||||
|
stanza = TestStanza()
|
||||||
|
self.failUnless(stanza.match("foo"),
|
||||||
|
"Stanza did not match its own tag name.")
|
||||||
|
|
||||||
|
stanza['bar'] = 'a'
|
||||||
|
self.failUnless(stanza.match("foo@bar=a"),
|
||||||
|
"Stanza did not match its own name with attribute value check.")
|
||||||
|
|
||||||
|
stanza['baz'] = 'b'
|
||||||
|
self.failUnless(stanza.match("foo@bar=a@baz=b"),
|
||||||
|
"Stanza did not match its own name with multiple attributes.")
|
||||||
|
|
||||||
|
stanza['plugin']['attrib'] = 'c'
|
||||||
|
self.failUnless(stanza.match("foo/plugin@attrib=c"),
|
||||||
|
"Stanza did not match with plugin and attribute.")
|
||||||
|
|
||||||
|
substanza = TestSubStanza()
|
||||||
|
substanza['attrib'] = 'd'
|
||||||
|
stanza.append(substanza)
|
||||||
|
self.failUnless(stanza.match("foo/sub@attrib=d"),
|
||||||
|
"Stanza did not match with substanzas and attribute.")
|
||||||
|
|
||||||
|
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)
|
||||||
|
|
Loading…
Reference in a new issue