# HG changeset patch # User cmlenz # Date 1157553590 0 # Node ID e4dad1145f84b822f1cc8a16d1ca58ab07ec93e9 # Parent 861105f3afe327302362e34c172f2dcae3005f61 Implement support for namespace prefixes in XPath expressions. diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -32,6 +32,7 @@ matches the attributes of descendants. * Fixes for `py:match` which would get confused when it should be applied to multiple elements (ticket #49). + * Using namespace prefixes in XPath expressions is now supported. Version 0.2 diff --git a/markup/core.py b/markup/core.py --- a/markup/core.py +++ b/markup/core.py @@ -498,6 +498,11 @@ >>> qname in Namespace('http://www.w3.org/2002/06/xhtml2') False """ + def __new__(cls, uri): + if type(uri) is cls: + return uri + return object.__new__(cls, uri) + def __init__(self, uri): self.uri = unicode(uri) diff --git a/markup/path.py b/markup/path.py --- a/markup/path.py +++ b/markup/path.py @@ -34,7 +34,8 @@ from math import ceil, floor import re -from markup.core import Stream, START, END, TEXT, COMMENT, PI +from markup.core import Stream, Attrs, Namespace, QName +from markup.core import START, END, TEXT, COMMENT, PI __all__ = ['Path', 'PathSyntaxError'] @@ -86,11 +87,11 @@ for axis, nodetest, predicates in path: steps.append('%s::%s' % (axis, nodetest)) for predicate in predicates: - steps.append('[%s]' % predicate) + steps[-1] += '[%s]' % predicate paths.append('/'.join(steps)) return '<%s "%s">' % (self.__class__.__name__, '|'.join(paths)) - def select(self, stream, variables=None): + def select(self, stream, namespaces=None, variables=None): """Returns a substream of the given stream that matches the path. If there are no matches, this method returns an empty stream. @@ -105,13 +106,19 @@ Text @param stream: the stream to select from + @param namespaces: (optional) a mapping of namespace prefixes to URIs + @param variables: (optional) a mapping of variable names to values @return: the substream matching the path, or an empty stream """ + if namespaces is None: + namespaces = {} + if variables is None: + variables = {} stream = iter(stream) def _generate(): test = self.test() for kind, data, pos in stream: - result = test(kind, data, pos, variables) + result = test(kind, data, pos, namespaces, variables) if result is True: yield kind, data, pos depth = 1 @@ -122,7 +129,7 @@ elif subkind is END: depth -= 1 yield subkind, subdata, subpos - test(subkind, subdata, subpos, variables) + test(subkind, subdata, subpos, namespaces, variables) elif result: yield result return Stream(_generate()) @@ -132,21 +139,24 @@ a specific stream event. The function returned expects the positional arguments `kind`, `data`, - and `pos`, i.e. basically an unpacked stream event. If the path matches - the event, the function returns the match (for example, a `START` or - `TEXT` event.) Otherwise, it returns `None`. + `pos` (basically an unpacked stream event), as well as `namespaces` + and `variables`. The latter two are a mapping of namespace prefixes to + URIs, and a mapping of variable names to values, respectively. + + If the path matches the event, the function returns the match (for + example, a `START` or `TEXT` event.) Otherwise, it returns `None`. >>> from markup.input import XML >>> xml = XML('') >>> test = Path('child').test() >>> for kind, data, pos in xml: - ... if test(kind, data, pos, {}): + ... if test(kind, data, pos, {}, {}): ... print kind, data START (u'child', [(u'id', u'2')]) """ paths = [(steps, len(steps), [0], []) for steps in self.paths] - def _test(kind, data, pos, variables): + def _test(kind, data, pos, namespaces, variables): for steps, size, stack, cutoff in paths: # Manage the stack that tells us "where we are" in the stream if kind is END: @@ -178,7 +188,7 @@ last_step = cursor + 1 == size # Perform the actual node test - matched = nodetest(kind, data, pos, variables) + matched = nodetest(kind, data, pos, namespaces, variables) # The node test matched if matched: @@ -186,7 +196,8 @@ # Check all the predicates for this step if predicates: for predicate in predicates: - if not predicate(kind, data, pos, variables): + if not predicate(kind, data, pos, namespaces, + variables): matched = None break @@ -234,7 +245,8 @@ if k is DESCENDANT or k is DESCENDANT_OR_SELF] backsteps.reverse() for axis, nodetest, predicates in backsteps: - matched = nodetest(kind, data, pos, variables) + matched = nodetest(kind, data, pos, namespaces, + variables) if not matched: cursor -= 1 cutoff[:] = [] @@ -360,10 +372,20 @@ return axis, nodetest, predicates def _node_test(self, axis=None): - test = None - if self.peek_token() in ('(', '()'): # Node type test + test = prefix = None + next_token = self.peek_token() + if next_token in ('(', '()'): # Node type test test = self._node_type() + elif next_token == ':': # Namespace prefix + prefix = self.cur_token + self.next_token() + localname = self.next_token() + if localname == '*': + test = QualifiedPrincipalTypeTest(axis, prefix) + else: + test = QualifiedNameTest(axis, prefix, localname) + else: # Name test if self.cur_token == '*': test = PrincipalTypeTest(axis) @@ -492,7 +514,7 @@ __slots__ = ['principal_type'] def __init__(self, principal_type): self.principal_type = principal_type - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): if kind is START: if self.principal_type is ATTRIBUTE: return data[1] or None @@ -501,6 +523,24 @@ def __repr__(self): return '*' +class QualifiedPrincipalTypeTest(object): + """Node test that matches any event with the given principal type in a + specific namespace.""" + __slots__ = ['principal_type', 'prefix'] + def __init__(self, principal_type, prefix): + self.principal_type = principal_type + self.prefix = prefix + def __call__(self, kind, data, pos, namespaces, variables): + namespace = Namespace(namespaces.get(self.prefix)) + if kind is START: + if self.principal_type is ATTRIBUTE and data[1]: + return Attrs([(name, value) for name, value in data[1] + if name in namespace]) or None + else: + return data[0] in namespace + def __repr__(self): + return '%s:*' % self.prefix + class LocalNameTest(object): """Node test that matches any event with the given prinipal type and local name. @@ -509,7 +549,7 @@ def __init__(self, principal_type, name): self.principal_type = principal_type self.name = name - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): if kind is START: if self.principal_type is ATTRIBUTE and self.name in data[1]: return TEXT, data[1].get(self.name), pos @@ -518,10 +558,29 @@ def __repr__(self): return self.name +class QualifiedNameTest(object): + """Node test that matches any event with the given prinipal type and + qualified name. + """ + __slots__ = ['principal_type', 'prefix', 'name'] + def __init__(self, principal_type, prefix, name): + self.principal_type = principal_type + self.prefix = prefix + self.name = name + def __call__(self, kind, data, pos, namespaces, variables): + qname = QName('%s}%s' % (namespaces.get(self.prefix), self.name)) + if kind is START: + if self.principal_type is ATTRIBUTE and qname in data[1]: + return TEXT, data[1].get(qname), pos + else: + return data[0] == qname + def __repr__(self): + return '%s:%s' % (self.prefix, self.name) + class CommentNodeTest(object): """Node test that matches any comment events.""" __slots__ = [] - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): return kind is COMMENT and (kind, data, pos) def __repr__(self): return 'comment()' @@ -529,7 +588,7 @@ class NodeTest(object): """Node test that matches any node.""" __slots__ = [] - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): if kind is START: return True return kind, data, pos @@ -541,7 +600,7 @@ __slots__ = ['target'] def __init__(self, target=None): self.target = target - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): if kind is PI and (not self.target or data[0] == self.target): return (kind, data, pos) def __repr__(self): @@ -553,7 +612,7 @@ class TextNodeTest(object): """Node test that matches any text event.""" __slots__ = [] - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): return kind is TEXT and (kind, data, pos) def __repr__(self): return 'text()' @@ -574,8 +633,8 @@ __slots__ = ['expr'] def __init__(self, expr): self.expr = expr - def __call__(self, kind, data, pos, variables): - val = self.expr(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + val = self.expr(kind, data, pos, namespaces, variables) if type(val) is tuple: val = val[1] return bool(val) @@ -589,8 +648,8 @@ __slots__ = ['number'] def __init__(self, number): self.number = number - def __call__(self, kind, data, pos, variables): - number = self.number(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + number = self.number(kind, data, pos, namespaces, variables) if type(number) is tuple: number = number[1] return ceil(float(number)) @@ -604,9 +663,10 @@ __slots__ = ['exprs'] def __init__(self, *exprs): self.exprs = exprs - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): strings = [] - for item in [expr(kind, data, pos, variables) for expr in self.exprs]: + for item in [expr(kind, data, pos, namespaces, variables) + for expr in self.exprs]: if type(item) is tuple: assert item[0] is TEXT item = item[1] @@ -623,11 +683,11 @@ def __init__(self, string1, string2): self.string1 = string1 self.string2 = string2 - def __call__(self, kind, data, pos, variables): - string1 = self.string1(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + string1 = self.string1(kind, data, pos, namespaces, variables) if type(string1) is tuple: string1 = string1[1] - string2 = self.string2(kind, data, pos, variables) + string2 = self.string2(kind, data, pos, namespaces, variables) if type(string2) is tuple: string2 = string2[1] return string2 in string1 @@ -637,7 +697,7 @@ class FalseFunction(Function): """The `false` function, which always returns the boolean `false` value.""" __slots__ = [] - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): return False def __repr__(self): return 'false()' @@ -649,8 +709,8 @@ __slots__ = ['number'] def __init__(self, number): self.number = number - def __call__(self, kind, data, pos, variables): - number = self.number(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + number = self.number(kind, data, pos, namespaces, variables) if type(number) is tuple: number = number[1] return floor(float(number)) @@ -662,7 +722,7 @@ element. """ __slots__ = [] - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): if kind is START: return TEXT, data[0].localname, pos def __repr__(self): @@ -673,7 +733,7 @@ element. """ __slots__ = [] - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): if kind is START: return TEXT, data[0], pos def __repr__(self): @@ -684,7 +744,7 @@ current element. """ __slots__ = [] - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): if kind is START: return TEXT, data[0].namespace, pos def __repr__(self): @@ -697,8 +757,8 @@ __slots__ = ['expr'] def __init__(self, expr): self.expr = expr - def __call__(self, kind, data, pos, variables): - return not self.expr(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + return not self.expr(kind, data, pos, namespaces, variables) def __repr__(self): return 'not(%s)' % self.expr @@ -711,8 +771,8 @@ _normalize = re.compile(r'\s{2,}').sub def __init__(self, expr): self.expr = expr - def __call__(self, kind, data, pos, variables): - string = self.expr(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + string = self.expr(kind, data, pos, namespaces, variables) if type(string) is tuple: string = string[1] return self._normalize(' ', string.strip()) @@ -724,8 +784,8 @@ __slots__ = ['expr'] def __init__(self, expr): self.expr = expr - def __call__(self, kind, data, pos, variables): - val = self.expr(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + val = self.expr(kind, data, pos, namespaces, variables) if type(val) is tuple: val = val[1] return float(val) @@ -739,8 +799,8 @@ __slots__ = ['number'] def __init__(self, number): self.number = number - def __call__(self, kind, data, pos, variables): - number = self.number(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + number = self.number(kind, data, pos, namespaces, variables) if type(number) is tuple: number = number[1] return round(float(number)) @@ -755,11 +815,11 @@ def __init__(self, string1, string2): self.string1 = string2 self.string2 = string2 - def __call__(self, kind, data, pos, variables): - string1 = self.string1(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + string1 = self.string1(kind, data, pos, namespaces, variables) if type(string1) is tuple: string1 = string1[1] - string2 = self.string2(kind, data, pos, variables) + string2 = self.string2(kind, data, pos, namespaces, variables) if type(string2) is tuple: string2 = string2[1] return string1.startswith(string2) @@ -773,8 +833,8 @@ __slots__ = ['expr'] def __init__(self, expr): self.expr = expr - def __call__(self, kind, data, pos, variables): - string = self.expr(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + string = self.expr(kind, data, pos, namespaces, variables) if type(string) is tuple: string = string[1] return len(string) @@ -790,16 +850,16 @@ self.string = string self.start = start self.length = length - def __call__(self, kind, data, pos, variables): - string = self.string(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + string = self.string(kind, data, pos, namespaces, variables) if type(string) is tuple: string = string[1] - start = self.start(kind, data, pos, variables) + start = self.start(kind, data, pos, namespaces, variables) if type(start) is tuple: start = start[1] length = 0 if self.length is not None: - length = self.length(kind, data, pos, variables) + length = self.length(kind, data, pos, namespaces, variables) if type(length) is tuple: length = length[1] return string[int(start):len(string) - int(length)] @@ -818,11 +878,11 @@ def __init__(self, string1, string2): self.string1 = string1 self.string2 = string2 - def __call__(self, kind, data, pos, variables): - string1 = self.string1(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + string1 = self.string1(kind, data, pos, namespaces, variables) if type(string1) is tuple: string1 = string1[1] - string2 = self.string2(kind, data, pos, variables) + string2 = self.string2(kind, data, pos, namespaces, variables) if type(string2) is tuple: string2 = string2[1] index = string1.find(string2) @@ -840,11 +900,11 @@ def __init__(self, string1, string2): self.string1 = string1 self.string2 = string2 - def __call__(self, kind, data, pos, variables): - string1 = self.string1(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + string1 = self.string1(kind, data, pos, namespaces, variables) if type(string1) is tuple: string1 = string1[1] - string2 = self.string2(kind, data, pos, variables) + string2 = self.string2(kind, data, pos, namespaces, variables) if type(string2) is tuple: string2 = string2[1] index = string1.find(string2) @@ -863,14 +923,14 @@ self.string = string self.fromchars = fromchars self.tochars = tochars - def __call__(self, kind, data, pos, variables): - string = self.string(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + string = self.string(kind, data, pos, namespaces, variables) if type(string) is tuple: string = string[1] - fromchars = self.fromchars(kind, data, pos, variables) + fromchars = self.fromchars(kind, data, pos, namespaces, variables) if type(fromchars) is tuple: fromchars = fromchars[1] - tochars = self.tochars(kind, data, pos, variables) + tochars = self.tochars(kind, data, pos, namespaces, variables) if type(tochars) is tuple: tochars = tochars[1] table = dict(zip([ord(c) for c in fromchars], @@ -883,7 +943,7 @@ class TrueFunction(Function): """The `true` function, which always returns the boolean `true` value.""" __slots__ = [] - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): return True def __repr__(self): return 'true()' @@ -912,7 +972,7 @@ __slots__ = ['text'] def __init__(self, text): self.text = text - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): return TEXT, self.text, (None, -1, -1) def __repr__(self): return '"%s"' % self.text @@ -922,7 +982,7 @@ __slots__ = ['number'] def __init__(self, number): self.number = number - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): return TEXT, self.number, (None, -1, -1) def __repr__(self): return str(self.number) @@ -932,7 +992,7 @@ __slots__ = ['name'] def __init__(self, name): self.name = name - def __call__(self, kind, data, pos, variables): + def __call__(self, kind, data, pos, namespaces, variables): return TEXT, variables.get(self.name), (None, -1, -1) def __repr__(self): return str(self.name) @@ -945,13 +1005,13 @@ def __init__(self, lval, rval): self.lval = lval self.rval = rval - def __call__(self, kind, data, pos, variables): - lval = self.lval(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + lval = self.lval(kind, data, pos, namespaces, variables) if type(lval) is tuple: lval = lval[1] if not lval: return False - rval = self.rval(kind, data, pos, variables) + rval = self.rval(kind, data, pos, namespaces, variables) if type(rval) is tuple: rval = rval[1] return bool(rval) @@ -964,11 +1024,11 @@ def __init__(self, lval, rval): self.lval = lval self.rval = rval - def __call__(self, kind, data, pos, variables): - lval = self.lval(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + lval = self.lval(kind, data, pos, namespaces, variables) if type(lval) is tuple: lval = lval[1] - rval = self.rval(kind, data, pos, variables) + rval = self.rval(kind, data, pos, namespaces, variables) if type(rval) is tuple: rval = rval[1] return lval == rval @@ -981,11 +1041,11 @@ def __init__(self, lval, rval): self.lval = lval self.rval = rval - def __call__(self, kind, data, pos, variables): - lval = self.lval(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + lval = self.lval(kind, data, pos, namespaces, variables) if type(lval) is tuple: lval = lval[1] - rval = self.rval(kind, data, pos, variables) + rval = self.rval(kind, data, pos, namespaces, variables) if type(rval) is tuple: rval = rval[1] return lval != rval @@ -998,13 +1058,13 @@ def __init__(self, lval, rval): self.lval = lval self.rval = rval - def __call__(self, kind, data, pos, variables): - lval = self.lval(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + lval = self.lval(kind, data, pos, namespaces, variables) if type(lval) is tuple: lval = lval[1] if lval: return True - rval = self.rval(kind, data, pos, variables) + rval = self.rval(kind, data, pos, namespaces, variables) if type(rval) is tuple: rval = rval[1] return bool(rval) @@ -1017,11 +1077,11 @@ def __init__(self, lval, rval): self.lval = lval self.rval = rval - def __call__(self, kind, data, pos, variables): - lval = self.lval(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + lval = self.lval(kind, data, pos, namespaces, variables) if type(lval) is tuple: lval = lval[1] - rval = self.rval(kind, data, pos, variables) + rval = self.rval(kind, data, pos, namespaces, variables) if type(rval) is tuple: rval = rval[1] return float(lval) > float(rval) @@ -1034,11 +1094,11 @@ def __init__(self, lval, rval): self.lval = lval self.rval = rval - def __call__(self, kind, data, pos, variables): - lval = self.lval(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + lval = self.lval(kind, data, pos, namespaces, variables) if type(lval) is tuple: lval = lval[1] - rval = self.rval(kind, data, pos, variables) + rval = self.rval(kind, data, pos, namespaces, variables) if type(rval) is tuple: rval = rval[1] return float(lval) > float(rval) @@ -1051,11 +1111,11 @@ def __init__(self, lval, rval): self.lval = lval self.rval = rval - def __call__(self, kind, data, pos, variables): - lval = self.lval(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + lval = self.lval(kind, data, pos, namespaces, variables) if type(lval) is tuple: lval = lval[1] - rval = self.rval(kind, data, pos, variables) + rval = self.rval(kind, data, pos, namespaces, variables) if type(rval) is tuple: rval = rval[1] return float(lval) >= float(rval) @@ -1068,11 +1128,11 @@ def __init__(self, lval, rval): self.lval = lval self.rval = rval - def __call__(self, kind, data, pos, variables): - lval = self.lval(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + lval = self.lval(kind, data, pos, namespaces, variables) if type(lval) is tuple: lval = lval[1] - rval = self.rval(kind, data, pos, variables) + rval = self.rval(kind, data, pos, namespaces, variables) if type(rval) is tuple: rval = rval[1] return float(lval) < float(rval) @@ -1085,11 +1145,11 @@ def __init__(self, lval, rval): self.lval = lval self.rval = rval - def __call__(self, kind, data, pos, variables): - lval = self.lval(kind, data, pos, variables) + def __call__(self, kind, data, pos, namespaces, variables): + lval = self.lval(kind, data, pos, namespaces, variables) if type(lval) is tuple: lval = lval[1] - rval = self.rval(kind, data, pos, variables) + rval = self.rval(kind, data, pos, namespaces, variables) if type(rval) is tuple: rval = rval[1] return float(lval) <= float(rval) diff --git a/markup/template.py b/markup/template.py --- a/markup/template.py +++ b/markup/template.py @@ -1010,6 +1010,7 @@ """ if match_templates is None: match_templates = ctxt._match_templates + nsprefix = {} # mapping of namespace prefixes to URIs tail = [] def _strip(stream): @@ -1037,12 +1038,12 @@ for idx, (test, path, template, directives) in \ enumerate(match_templates): - if test(kind, data, pos, ctxt) is True: + if test(kind, data, pos, nsprefix, ctxt) is True: # Let the remaining match templates know about the event so # they get a chance to update their internal state for test in [mt[0] for mt in match_templates[idx + 1:]]: - test(kind, data, pos, ctxt) + test(kind, data, pos, nsprefix, ctxt) # Consume and store all events until an end event # corresponding to this start event is encountered @@ -1051,7 +1052,7 @@ kind, data, pos = tail[0] for test in [mt[0] for mt in match_templates]: - test(kind, data, pos, ctxt) + test(kind, data, pos, nsprefix, ctxt) # Make the select() function available in the body of the # match template diff --git a/markup/tests/path.py b/markup/tests/path.py --- a/markup/tests/path.py +++ b/markup/tests/path.py @@ -404,7 +404,44 @@ xml = XML('bar') path = Path('*[name()=$bar]') variables = {'bar': 'foo'} - self.assertEqual('bar', path.select(xml, variables).render()) + self.assertEqual('bar', + path.select(xml, variables=variables).render()) + + def test_name_with_namespace(self): + xml = XML('bar') + path = Path('f:foo') + self.assertEqual('', repr(path)) + namespaces = {'f': 'FOO'} + self.assertEqual('bar', + path.select(xml, namespaces=namespaces).render()) + + def test_wildcard_with_namespace(self): + xml = XML('bar') + path = Path('f:*') + self.assertEqual('', repr(path)) + namespaces = {'f': 'FOO'} + self.assertEqual('bar', + path.select(xml, namespaces=namespaces).render()) + + # FIXME: the following two don't work due to a problem in XML serialization: + # attributes that would need a namespace prefix that isn't in the + # prefix map would need to get an artificial prefix, but currently + # don't + # + #def test_attrname_with_namespace(self): + # xml = XML('') + # path = Path('foo[@f:bar]') + # print path + # namespaces = {'f': 'FOO'} + # self.assertEqual('', + # path.select(xml, namespaces=namespaces).render()) + # + #def test_attrwildcard_with_namespace(self): + # xml = XML('') + # path = Path('foo[@f:*]') + # namespaces = {'f': 'FOO'} + # self.assertEqual('', + # path.select(xml, namespaces=namespaces).render()) def suite():