Edgewall Software

Changeset 1025


Ignore:
Timestamp:
Mar 11, 2009, 6:03:03 PM (15 years ago)
Author:
cmlenz
Message:

Merged soc2008-xpath branch back into trunk.

Location:
trunk
Files:
3 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/genshi/filters/transform.py

    r1000 r1025  
    711711        test = self.path.test()
    712712        stream = iter(stream)
     713        next = stream.next
    713714        for mark, event in stream:
    714715            if mark is None:
    715716                yield mark, event
    716717                continue
    717             result = test(event, {}, {})
     718            result = test(event, namespaces, variables)
    718719            # XXX This is effectively genshi.core._ensure() for transform
    719720            # streams.
     
    723724                    depth = 1
    724725                    while depth > 0:
    725                         mark, subevent = stream.next()
     726                        mark, subevent = next()
    726727                        if subevent[0] is START:
    727728                            depth += 1
     
    732733                        else:
    733734                            yield INSIDE, subevent
    734                         test(subevent, {}, {}, updateonly=True)
     735                        test(subevent, namespaces, variables, updateonly=True)
    735736                else:
    736737                    yield OUTSIDE, event
  • trunk/genshi/path.py

    r970 r1025  
    3939"""
    4040
     41from collections import deque
    4142try:
    4243    from functools import reduce
     
    4647import operator
    4748import re
     49from itertools import chain
    4850
    4951from genshi.core import Stream, Attrs, Namespace, QName
     
    7981
    8082
     83class GenericStrategy(object):
     84
     85    @classmethod
     86    def supports(cls, path):
     87        return True
     88
     89    def __init__(self, path):
     90        self.path = path
     91
     92    def test(self, ignore_context):
     93        p = self.path
     94        if ignore_context:
     95            if p[0][0] is ATTRIBUTE:
     96                steps = [_DOTSLASHSLASH] + p
     97            else:
     98                steps = [(DESCENDANT_OR_SELF, p[0][1], p[0][2])] + p[1:]
     99        elif p[0][0] is CHILD or p[0][0] is ATTRIBUTE \
     100                or p[0][0] is DESCENDANT:
     101            steps = [_DOTSLASH] + p
     102        else:
     103            steps = p
     104
     105        # for node it contains all positions of xpath expression
     106        # where its child should start checking for matches
     107        # with list of corresponding context counters
     108        # there can be many of them, because position that is from
     109        # descendant-like axis can be achieved from different nodes
     110        # for example <a><a><b/></a></a> should match both //a//b[1]
     111        # and //a//b[2]
     112        # positions always form increasing sequence (invariant)
     113        stack = [[(0, [[]])]]
     114
     115        def _test(event, namespaces, variables, updateonly=False):
     116            kind, data, pos = event[:3]
     117            retval = None
     118
     119            # Manage the stack that tells us "where we are" in the stream
     120            if kind is END:
     121                if stack:
     122                    stack.pop()
     123                return None
     124            if kind is START_NS or kind is END_NS \
     125                    or kind is START_CDATA or kind is END_CDATA:
     126                # should we make namespaces work?
     127                return None
     128
     129            pos_queue = deque([(pos, cou, []) for pos, cou in stack[-1]])
     130            next_pos = []
     131
     132            # length of real part of path - we omit attribute axis
     133            real_len = len(steps) - ((steps[-1][0] == ATTRIBUTE) or 1 and 0)
     134            last_checked = -1
     135
     136            # places where we have to check for match, are these
     137            # provided by parent
     138            while pos_queue:
     139                x, pcou, mcou = pos_queue.popleft()
     140                axis, nodetest, predicates = steps[x]
     141
     142                # we need to push descendant-like positions from parent
     143                # further
     144                if (axis is DESCENDANT or axis is DESCENDANT_OR_SELF) and pcou:
     145                    if next_pos and next_pos[-1][0] == x:
     146                        next_pos[-1][1].extend(pcou)
     147                    else:
     148                        next_pos.append((x, pcou))
     149
     150                # nodetest first
     151                if not nodetest(kind, data, pos, namespaces, variables):
     152                    continue
     153
     154                # counters packs that were already bad
     155                missed = set()
     156                counters_len = len(pcou) + len(mcou)
     157
     158                # number of counters - we have to create one
     159                # for every context position based predicate
     160                cnum = 0
     161
     162                # tells if we have match with position x
     163                matched = True
     164
     165                if predicates:
     166                    for predicate in predicates:
     167                        pretval = predicate(kind, data, pos,
     168                                            namespaces,
     169                                            variables)
     170                        if type(pretval) is float: # FIXME <- need to check
     171                                                   # this for other types that
     172                                                   # can be coerced to float
     173
     174                            # each counter pack needs to be checked
     175                            for i, cou in enumerate(chain(pcou, mcou)):
     176                                # it was bad before
     177                                if i in missed:
     178                                    continue
     179
     180                                if len(cou) < cnum + 1:
     181                                    cou.append(0)
     182                                cou[cnum] += 1
     183
     184                                # it is bad now
     185                                if cou[cnum] != int(pretval):
     186                                    missed.add(i)
     187
     188                            # none of counters pack was good
     189                            if len(missed) == counters_len:
     190                                pretval = False
     191                            cnum += 1
     192
     193                        if not pretval:
     194                             matched = False
     195                             break
     196
     197                if not matched:
     198                    continue
     199
     200                # counter for next position with current node as context node
     201                child_counter = []
     202
     203                if x + 1 == real_len:
     204                    # we reached end of expression, because x + 1
     205                    # is equal to the length of expression
     206                    matched = True
     207                    axis, nodetest, predicates = steps[-1]
     208                    if axis is ATTRIBUTE:
     209                        matched = nodetest(kind, data, pos, namespaces,
     210                                           variables)
     211                    if matched:
     212                        retval = matched
     213                else:
     214                    next_axis = steps[x + 1][0]
     215
     216                    # if next axis allows matching self we have
     217                    # to add next position to our queue
     218                    if next_axis is DESCENDANT_OR_SELF or next_axis is SELF:
     219                        if not pos_queue or pos_queue[0][0] > x + 1:
     220                            pos_queue.appendleft((x + 1, [], [child_counter]))
     221                        else:
     222                            pos_queue[0][2].append(child_counter)
     223
     224                    # if axis is not self we have to add it to child's list
     225                    if next_axis is not SELF:
     226                        next_pos.append((x + 1, [child_counter]))
     227
     228            if kind is START:
     229                stack.append(next_pos)
     230
     231            return retval
     232
     233        return _test
     234
     235
     236class SimplePathStrategy(object):
     237    """Strategy for path with only local names, attributes and text nodes."""
     238
     239    @classmethod
     240    def supports(cls, path):
     241        if path[0][0] is ATTRIBUTE:
     242            return False
     243        allowed_tests = (LocalNameTest, CommentNodeTest, TextNodeTest)
     244        for _, nodetest, predicates in path:
     245            if predicates:
     246                return False
     247            if not isinstance(nodetest, allowed_tests):
     248                return False
     249        return True
     250
     251    def __init__(self, path):
     252        # fragments is list of tuples (fragment, pi, attr, self_beginning)
     253        # fragment is list of nodetests for fragment of path with only
     254        # child:: axes between
     255        # pi is KMP partial match table for this fragment
     256        # attr is attribute nodetest if fragment ends with @ and None otherwise
     257        # self_beginning is True if axis for first fragment element
     258        # was self (first fragment) or descendant-or-self (farther fragment)
     259        self.fragments = []
     260
     261        self_beginning = False
     262        fragment = []
     263
     264        def nodes_equal(node1, node2):
     265            """Tests if two node tests are equal"""
     266            if node1.__class__ is not node2.__class__:
     267                return False
     268            if node1.__class__ == LocalNameTest:
     269                return node1.name == node2.name
     270            return True
     271
     272        def calculate_pi(f):
     273            """KMP prefix calculation for table"""
     274            # the indexes in prefix table are shifted by one
     275            # in comparision with common implementations
     276            # pi[i] = NORMAL_PI[i + 1]
     277            if len(f) == 0:
     278                return []
     279            pi = [0]
     280            s = 0
     281            for i in xrange(1, len(f)):
     282                while s > 0 and not nodes_equal(f[s], f[i]):
     283                    s = pi[s-1]
     284                if nodes_equal(f[s], f[i]):
     285                    s += 1
     286                pi.append(s)
     287            return pi
     288
     289        for axis in path:
     290            if axis[0] is SELF:
     291                if len(fragment) != 0:
     292                    # if element is not first in fragment it has to be
     293                    # the same as previous one
     294                    # for example child::a/self::b is always wrong
     295                    if axis[1] != fragment[-1][1]:
     296                        self.fragments = None
     297                        return
     298                else:
     299                    self_beginning = True
     300                    fragment.append(axis[1])
     301            elif axis[0] is CHILD:
     302                fragment.append(axis[1])
     303            elif axis[0] is ATTRIBUTE:
     304                pi = calculate_pi(fragment)
     305                self.fragments.append((fragment, pi, axis[1], self_beginning))
     306                # attribute has always to be at the end, so we can jump out
     307                return
     308            else:
     309                pi = calculate_pi(fragment)
     310                self.fragments.append((fragment, pi, None, self_beginning))
     311                fragment = [axis[1]]
     312                if axis[0] is DESCENDANT:
     313                    self_beginning = False
     314                else: # DESCENDANT_OR_SELF
     315                    self_beginning = True
     316        pi = calculate_pi(fragment)
     317        self.fragments.append((fragment, pi, None, self_beginning))
     318
     319    def test(self, ignore_context):
     320        # stack of triples (fid, p, ic)
     321        # fid is index of current fragment
     322        # p is position in this fragment
     323        # ic is if we ignore context in this fragment
     324        stack = []
     325        stack_push = stack.append
     326        stack_pop = stack.pop
     327        frags = self.fragments
     328        frags_len = len(frags)
     329
     330        def _test(event, namespaces, variables, updateonly=False):
     331            # expression found impossible during init
     332            if frags is None:
     333                return None
     334
     335            kind, data, pos = event[:3]
     336
     337            # skip events we don't care about
     338            if kind is END:
     339                if stack:
     340                    stack_pop()
     341                return None
     342            if kind is START_NS or kind is END_NS \
     343                    or kind is START_CDATA or kind is END_CDATA:
     344                return None
     345
     346            if not stack:
     347                # root node, nothing on stack, special case
     348                fid = 0
     349                # skip empty fragments (there can be actually only one)
     350                while not frags[fid][0]:
     351                    fid += 1
     352                p = 0
     353                # empty fragment means descendant node at beginning
     354                ic = ignore_context or (fid > 0)
     355
     356                # expression can match first node, if first axis is self::,
     357                # descendant-or-self:: or if ignore_context is True and
     358                # axis is not descendant::
     359                if not frags[fid][3] and (not ignore_context or fid > 0):
     360                    # axis is not self-beggining, we have to skip this node
     361                    stack_push((fid, p, ic))
     362                    return None
     363            else:
     364                # take position of parent
     365                fid, p, ic = stack[-1]
     366
     367            if fid is not None and not ic:
     368                # fragment not ignoring context - we can't jump back
     369                frag, pi, attrib, _ = frags[fid]
     370                frag_len = len(frag)
     371
     372                if p == frag_len:
     373                    # that probably means empty first fragment
     374                    pass
     375                elif frag[p](kind, data, pos, namespaces, variables):
     376                    # match, so we can go further
     377                    p += 1
     378                else:
     379                    # not matched, so there will be no match in subtree
     380                    fid, p = None, None
     381
     382                if p == frag_len and fid + 1 != frags_len:
     383                    # we made it to end of fragment, we can go to following
     384                    fid += 1
     385                    p = 0
     386                    ic = True
     387
     388            if fid is None:
     389                # there was no match in fragment not ignoring context
     390                if kind is START:
     391                    stack_push((fid, p, ic))
     392                return None
     393
     394            if ic:
     395                # we are in fragment ignoring context
     396                while True:
     397                    frag, pi, attrib, _ = frags[fid]
     398                    frag_len = len(frag)
     399
     400                    # KMP new "character"
     401                    while p > 0 and (p >= frag_len or not \
     402                            frag[p](kind, data, pos, namespaces, variables)):
     403                        p = pi[p-1]
     404                    if frag[p](kind, data, pos, namespaces, variables):
     405                        p += 1
     406
     407                    if p == frag_len:
     408                        # end of fragment reached
     409                        if fid + 1 == frags_len:
     410                            # that was last fragment
     411                            break
     412                        else:
     413                            fid += 1
     414                            p = 0
     415                            ic = True
     416                            if not frags[fid][3]:
     417                                # next fragment not self-beginning
     418                                break
     419                    else:
     420                        break
     421
     422            if kind is START:
     423                # we have to put new position on stack, for children
     424
     425                if not ic and fid + 1 == frags_len and p == frag_len:
     426                    # it is end of the only, not context ignoring fragment
     427                    # so there will be no matches in subtree
     428                    stack_push((None, None, ic))
     429                else:
     430                    stack_push((fid, p, ic))
     431
     432            # have we reached the end of the last fragment?
     433            if fid + 1 == frags_len and p == frag_len:
     434                if attrib: # attribute ended path, return value
     435                    return attrib(kind, data, pos, namespaces, variables)
     436                return True
     437
     438            return None
     439
     440        return _test
     441
     442
     443class SingleStepStrategy(object):
     444
     445    @classmethod
     446    def supports(cls, path):
     447        return len(path) == 1
     448
     449    def __init__(self, path):
     450        self.path = path
     451
     452    def test(self, ignore_context):
     453        steps = self.path
     454        if steps[0][0] is ATTRIBUTE:
     455            steps = [_DOTSLASH] + steps
     456        select_attr = steps[-1][0] is ATTRIBUTE and steps[-1][1] or None
     457
     458        # for every position in expression stores counters' list
     459        # it is used for position based predicates
     460        counters = []
     461        depth = [0]
     462
     463        def _test(event, namespaces, variables, updateonly=False):
     464            kind, data, pos = event[:3]
     465
     466            # Manage the stack that tells us "where we are" in the stream
     467            if kind is END:
     468                if not ignore_context:
     469                    depth[0] -= 1
     470                return None
     471            elif kind is START_NS or kind is END_NS \
     472                    or kind is START_CDATA or kind is END_CDATA:
     473                # should we make namespaces work?
     474                return None
     475
     476            if not ignore_context:
     477                outside = (steps[0][0] is SELF and depth[0] != 0) \
     478                       or (steps[0][0] is CHILD and depth[0] != 1) \
     479                       or (steps[0][0] is DESCENDANT and depth[0] < 1)
     480                if kind is START:
     481                    depth[0] += 1
     482                if outside:
     483                    return None
     484
     485            axis, nodetest, predicates = steps[0]
     486            if not nodetest(kind, data, pos, namespaces, variables):
     487                return None
     488
     489            if predicates:
     490                cnum = 0
     491                for predicate in predicates:
     492                    pretval = predicate(kind, data, pos, namespaces, variables)
     493                    if type(pretval) is float: # FIXME <- need to check this
     494                                               # for other types that can be
     495                                               # coerced to float
     496                        if len(counters) < cnum + 1:
     497                            counters.append(0)
     498                        counters[cnum] += 1
     499                        if counters[cnum] != int(pretval):
     500                            pretval = False
     501                        cnum += 1
     502                    if not pretval:
     503                         return None
     504
     505            if select_attr:
     506                return select_attr(kind, data, pos, namespaces, variables)
     507
     508            return True
     509
     510        return _test
     511
     512
    81513class Path(object):
    82514    """Implements basic XPath support on streams.
    83515   
    84     Instances of this class represent a "compiled" XPath expression, and provide
    85     methods for testing the path against a stream, as well as extracting a
    86     substream matching that path.
    87     """
     516    Instances of this class represent a "compiled" XPath expression, and
     517    provide methods for testing the path against a stream, as well as
     518    extracting a substream matching that path.
     519    """
     520
     521    STRATEGIES = (SingleStepStrategy, SimplePathStrategy, GenericStrategy)
    88522
    89523    def __init__(self, text, filename=None, lineno=-1):
     
    97531        self.source = text
    98532        self.paths = PathParser(text, filename, lineno).parse()
     533        self.strategies = []
     534        for path in self.paths:
     535            for strategy_class in self.STRATEGIES:
     536                if strategy_class.supports(path):
     537                    self.strategies.append(strategy_class(path))
     538                    break
     539            else:
     540                raise NotImplemented, "This path is not implemented"
    99541
    100542    def __repr__(self):
     
    134576            variables = {}
    135577        stream = iter(stream)
    136         def _generate():
     578        def _generate(stream=stream, ns=namespaces, vs=variables):
     579            next = stream.next
    137580            test = self.test()
    138581            for event in stream:
    139                 result = test(event, namespaces, variables)
     582                result = test(event, ns, vs)
    140583                if result is True:
    141584                    yield event
     
    143586                        depth = 1
    144587                        while depth > 0:
    145                             subevent = stream.next()
     588                            subevent = next()
    146589                            if subevent[0] is START:
    147590                                depth += 1
     
    149592                                depth -= 1
    150593                            yield subevent
    151                             test(subevent, namespaces, variables,
    152                                  updateonly=True)
     594                            test(subevent, ns, vs, updateonly=True)
    153595                elif result:
    154596                    yield result
     
    174616        >>> xml = XML('<root><elem><child id="1"/></elem><child id="2"/></root>')
    175617        >>> test = Path('child').test()
     618        >>> namespaces, variables = {}, {}
    176619        >>> for event in xml:
    177         ...     if test(event, {}, {}):
     620        ...     if test(event, namespaces, variables):
    178621        ...         print event[0], repr(event[1])
    179622        START (QName(u'child'), Attrs([(QName(u'id'), u'2')]))
     
    186629        :rtype: ``function``
    187630        """
    188         paths = [(p, len(p), [0], [], [0] * len(p)) for p in [
    189             (ignore_context and [_DOTSLASHSLASH] or []) + p for p in self.paths
    190         ]]
    191 
    192         def _test(event, namespaces, variables, updateonly=False):
    193             kind, data, pos = event[:3]
     631        tests = [s.test(ignore_context) for s in self.strategies]
     632        if len(tests) == 1:
     633            return tests[0]
     634
     635        def _multi(event, namespaces, variables, updateonly=False):
    194636            retval = None
    195             for steps, size, cursors, cutoff, counter in paths:
    196                 # Manage the stack that tells us "where we are" in the stream
    197                 if kind is END:
    198                     if cursors:
    199                         cursors.pop()
    200                     continue
    201                 elif kind is START:
    202                     cursors.append(cursors and cursors[-1] or 0)
    203                 elif kind is START_NS or kind is END_NS \
    204                         or kind is START_CDATA or kind is END_CDATA:
    205                     continue
    206 
    207                 if updateonly or retval or not cursors:
    208                     continue
    209                 cursor = cursors[-1]
    210                 depth = len(cursors)
    211 
    212                 if cutoff and depth + int(kind is not START) > cutoff[0]:
    213                     continue
    214 
    215                 ctxtnode = not ignore_context and kind is START \
    216                                               and depth == 2
    217                 matched = None
    218                 while 1:
    219                     # Fetch the next location step
    220                     axis, nodetest, predicates = steps[cursor]
    221 
    222                     # If this is the start event for the context node, and the
    223                     # axis of the location step doesn't include the current
    224                     # element, skip the test
    225                     if ctxtnode and (axis is CHILD or axis is DESCENDANT):
    226                         break
    227 
    228                     # Is this the last step of the location path?
    229                     last_step = cursor + 1 == size
    230 
    231                     # Perform the actual node test
    232                     matched = nodetest(kind, data, pos, namespaces, variables)
    233 
    234                     # The node test matched
    235                     if matched:
    236 
    237                         # Check all the predicates for this step
    238                         if predicates:
    239                             for predicate in predicates:
    240                                 pretval = predicate(kind, data, pos, namespaces,
    241                                                     variables)
    242                                 if type(pretval) is float: # FIXME <- need to
    243                                                            # check this for
    244                                                            # other types that
    245                                                            # can be coerced to
    246                                                            # float
    247                                     counter[cursor] += 1
    248                                     if counter[cursor] != int(pretval):
    249                                         pretval = False
    250                                 if not pretval:
    251                                     matched = None
    252                                     break
    253 
    254                         # Both the node test and the predicates matched
    255                         if matched:
    256                             if last_step:
    257                                 if not ctxtnode or kind is not START \
    258                                         or axis is ATTRIBUTE or axis is SELF:
    259                                     retval = matched
    260                             elif not ctxtnode or axis is SELF \
    261                                               or axis is DESCENDANT_OR_SELF:
    262                                 cursor += 1
    263                                 cursors[-1] = cursor
    264                             cutoff[:] = []
    265 
    266                     if kind is START:
    267                         if last_step and not (axis is DESCENDANT or
    268                                               axis is DESCENDANT_OR_SELF):
    269                             cutoff[:] = [depth]
    270 
    271                         elif steps[cursor][0] is ATTRIBUTE:
    272                             # If the axis of the next location step is the
    273                             # attribute axis, we need to move on to processing
    274                             # that step without waiting for the next markup
    275                             # event
    276                             continue
    277 
    278                     # We're done with this step if it's the last step or the
    279                     # axis isn't "self"
    280                     if not matched or last_step or not (
    281                             axis is SELF or axis is DESCENDANT_OR_SELF):
    282                         break
    283                     if ctxtnode and axis is DESCENDANT_OR_SELF:
    284                         ctxtnode = False
    285 
    286                 if (retval or not matched) and kind is START and \
    287                         not (axis is DESCENDANT or axis is DESCENDANT_OR_SELF):
    288                     # If this step is not a closure, it cannot be matched until
    289                     # the current element is closed... so we need to move the
    290                     # cursor back to the previous closure and retest that
    291                     # against the current element
    292                     backsteps = [(i, k, d, p) for i, (k, d, p)
    293                                  in enumerate(steps[:cursor])
    294                                  if k is DESCENDANT or k is DESCENDANT_OR_SELF]
    295                     backsteps.reverse()
    296                     for cursor, axis, nodetest, predicates in backsteps:
    297                         if nodetest(kind, data, pos, namespaces, variables):
    298                             cutoff[:] = []
    299                             break
    300                     cursors[-1] = cursor
    301 
     637            for test in tests:
     638                val = test(event, namespaces, variables, updateonly=updateonly)
     639                if retval is None:
     640                    retval = val
    302641            return retval
    303 
    304         return _test
     642        return _multi
    305643
    306644
     
    375713        while True:
    376714            if self.cur_token.startswith('/'):
    377                 if self.cur_token == '//':
     715                if not steps:
     716                    if self.cur_token == '//':
     717                        # hack to make //* match every node - also root
     718                        self.next_token()
     719                        axis, nodetest, predicates = self._location_step()
     720                        steps.append((DESCENDANT_OR_SELF, nodetest,
     721                                      predicates))
     722                        if self.at_end or not self.cur_token.startswith('/'):
     723                            break
     724                        continue
     725                    else:
     726                        raise PathSyntaxError('Absolute location paths not '
     727                                              'supported', self.filename,
     728                                              self.lineno)
     729                elif self.cur_token == '//':
    378730                    steps.append((DESCENDANT_OR_SELF, NodeTest(), []))
    379                 elif not steps:
    380                     raise PathSyntaxError('Absolute location paths not '
    381                                           'supported', self.filename,
    382                                           self.lineno)
    383731                self.next_token()
    384732
     
    387735                axis = CHILD
    388736            steps.append((axis, nodetest, predicates))
    389 
    390737            if self.at_end or not self.cur_token.startswith('/'):
    391738                break
     
    7161063    """
    7171064    __slots__ = ['expr']
     1065    _return_type = bool
    7181066    def __init__(self, expr):
    7191067        self.expr = expr
     
    11731521
    11741522_DOTSLASHSLASH = (DESCENDANT_OR_SELF, PrincipalTypeTest(None), ())
     1523_DOTSLASH = (SELF, PrincipalTypeTest(None), ())
  • trunk/genshi/tests/path.py

    r754 r1025  
    1616
    1717from genshi.input import XML
    18 from genshi.path import Path, PathSyntaxError
    19 
     18from genshi.path import Path, PathParser, PathSyntaxError, GenericStrategy, \
     19                        SingleStepStrategy, SimplePathStrategy
     20
     21
     22class FakePath(Path):
     23    def __init__(self, strategy):
     24        self.strategy = strategy
     25    def test(self, ignore_context = False):
     26        return self.strategy.test(ignore_context)
    2027
    2128class PathTestCase(unittest.TestCase):
     29
     30    strategies = [GenericStrategy, SingleStepStrategy, SimplePathStrategy]
     31    def _create_path(self, expression, expected):
     32        return path
     33
     34    def _test_strategies(self, stream, path, render,
     35                             namespaces=None, variables=None):
     36        for strategy in self.strategies:
     37            if not strategy.supports(path):
     38                continue
     39            s = strategy(path)
     40            rendered = FakePath(s).select(stream,namespaces=namespaces,
     41                                            variables=variables).render()
     42            msg = "Bad render using %s strategy"%str(strategy)
     43            msg += "\nExpected:\t'%s'"%render
     44            msg += "\nRendered:\t'%s'"%rendered
     45            self.assertEqual(render, rendered, msg)
     46
     47    def _test_expression(self, text, expected, stream=None, render="",
     48                            namespaces=None, variables=None):
     49        path = Path(text)
     50        if expected is not None:
     51            self.assertEqual(expected, repr(path))
     52
     53        if stream is None:
     54            return
     55
     56        rendered = path.select(stream, namespaces=namespaces,
     57                                    variables=variables).render()
     58        msg = "Bad render using whole path"
     59        msg += "\nExpected:\t'%s'"%render
     60        msg += "\nRendered:\t'%s'"%rendered
     61        self.assertEqual(render, rendered, msg)
     62
     63        if len(path.paths) == 1:
     64            self._test_strategies(stream, path.paths[0], render,
     65                                namespaces=namespaces, variables=variables)
     66
    2267
    2368    def test_error_no_absolute_path(self):
     
    3176        xml = XML('<root><elem/></root>')
    3277
    33         path = Path('elem')
    34         self.assertEqual('<Path "child::elem">', repr(path))
    35         self.assertEqual('<elem/>', path.select(xml).render())
    36 
    37         path = Path('child::elem')
    38         self.assertEqual('<Path "child::elem">', repr(path))
    39         self.assertEqual('<elem/>', path.select(xml).render())
    40 
    41         path = Path('//elem')
    42         self.assertEqual('<Path "descendant-or-self::node()/child::elem">',
    43                          repr(path))
    44         self.assertEqual('<elem/>', path.select(xml).render())
    45 
    46         path = Path('descendant::elem')
    47         self.assertEqual('<Path "descendant::elem">', repr(path))
    48         self.assertEqual('<elem/>', path.select(xml).render())
     78        self._test_expression(  'elem',
     79                                '<Path "child::elem">',
     80                                xml,
     81                                '<elem/>')
     82
     83        self._test_expression(  'elem',
     84                                '<Path "child::elem">',
     85                                xml,
     86                                '<elem/>')
     87
     88        self._test_expression(  'child::elem',
     89                                '<Path "child::elem">',
     90                                xml,
     91                                '<elem/>')
     92
     93        self._test_expression(  '//elem',
     94                                '<Path "descendant-or-self::elem">',
     95                                xml,
     96                                '<elem/>')
     97
     98        self._test_expression(  'descendant::elem',
     99                                '<Path "descendant::elem">',
     100                                xml,
     101                                '<elem/>')
    49102
    50103    def test_1step_self(self):
    51104        xml = XML('<root><elem/></root>')
    52105
    53         path = Path('.')
    54         self.assertEqual('<Path "self::node()">', repr(path))
    55         self.assertEqual('<root><elem/></root>', path.select(xml).render())
    56 
    57         path = Path('self::node()')
    58         self.assertEqual('<Path "self::node()">', repr(path))
    59         self.assertEqual('<root><elem/></root>', path.select(xml).render())
     106        self._test_expression(  '.',
     107                                '<Path "self::node()">',
     108                                xml,
     109                                '<root><elem/></root>')
     110
     111        self._test_expression(  'self::node()',
     112                                '<Path "self::node()">',
     113                                xml,
     114                                '<root><elem/></root>')
    60115
    61116    def test_1step_wildcard(self):
    62117        xml = XML('<root><elem/></root>')
    63118
    64         path = Path('*')
    65         self.assertEqual('<Path "child::*">', repr(path))
    66         self.assertEqual('<elem/>', path.select(xml).render())
    67 
    68         path = Path('child::*')
    69         self.assertEqual('<Path "child::*">', repr(path))
    70         self.assertEqual('<elem/>', path.select(xml).render())
    71 
    72         path = Path('child::node()')
    73         self.assertEqual('<Path "child::node()">', repr(path))
    74         self.assertEqual('<elem/>', Path('child::node()').select(xml).render())
    75 
    76         path = Path('//*')
    77         self.assertEqual('<Path "descendant-or-self::node()/child::*">',
    78                          repr(path))
    79         self.assertEqual('<root><elem/></root>', path.select(xml).render())
     119        self._test_expression(  '*',
     120                                '<Path "child::*">',
     121                                xml,
     122                                '<elem/>')
     123
     124        self._test_expression(  'child::*',
     125                                '<Path "child::*">',
     126                                xml,
     127                                '<elem/>')
     128
     129        self._test_expression(  'child::node()',
     130                                '<Path "child::node()">',
     131                                xml,
     132                                '<elem/>')
     133
     134        self._test_expression(  '//*',
     135                                '<Path "descendant-or-self::*">',
     136                                xml,
     137                                '<root><elem/></root>')
    80138
    81139    def test_1step_attribute(self):
    82         path = Path('@foo')
    83         self.assertEqual('<Path "attribute::foo">', repr(path))
    84 
    85         xml = XML('<root/>')
    86         self.assertEqual('', path.select(xml).render())
     140        self._test_expression(  '@foo',
     141                                '<Path "attribute::foo">',
     142                                XML('<root/>'),
     143                                '')
    87144
    88145        xml = XML('<root foo="bar"/>')
    89         self.assertEqual('bar', path.select(xml).render())
    90 
    91         path = Path('./@foo')
    92         self.assertEqual('<Path "self::node()/attribute::foo">', repr(path))
    93         self.assertEqual('bar', path.select(xml).render())
     146
     147        self._test_expression(  '@foo',
     148                                '<Path "attribute::foo">',
     149                                xml,
     150                                'bar')
     151
     152        self._test_expression(  './@foo',
     153                                '<Path "self::node()/attribute::foo">',
     154                                xml,
     155                                'bar')
    94156
    95157    def test_1step_text(self):
    96158        xml = XML('<root>Hey</root>')
    97159
    98         path = Path('text()')
    99         self.assertEqual('<Path "child::text()">', repr(path))
    100         self.assertEqual('Hey', path.select(xml).render())
    101 
    102         path = Path('./text()')
    103         self.assertEqual('<Path "self::node()/child::text()">', repr(path))
    104         self.assertEqual('Hey', path.select(xml).render())
    105 
    106         path = Path('//text()')
    107         self.assertEqual('<Path "descendant-or-self::node()/child::text()">',
    108                          repr(path))
    109         self.assertEqual('Hey', path.select(xml).render())
    110 
    111         path = Path('.//text()')
    112         self.assertEqual('<Path "self::node()/descendant-or-self::node()/child::text()">',
    113                          repr(path))
    114         self.assertEqual('Hey', path.select(xml).render())
     160        self._test_expression(  'text()',
     161                                '<Path "child::text()">',
     162                                xml,
     163                                'Hey')
     164
     165        self._test_expression(  './text()',
     166                                '<Path "self::node()/child::text()">',
     167                                xml,
     168                                'Hey')
     169
     170        self._test_expression(  '//text()',
     171                                '<Path "descendant-or-self::text()">',
     172                                xml,
     173                                'Hey')
     174
     175        self._test_expression(  './/text()',
     176            '<Path "self::node()/descendant-or-self::node()/child::text()">',
     177                                xml,
     178                                'Hey')
    115179
    116180    def test_2step(self):
    117181        xml = XML('<root><foo/><bar/></root>')
    118         self.assertEqual('<foo/><bar/>', Path('*').select(xml).render())
    119         self.assertEqual('<bar/>', Path('bar').select(xml).render())
    120         self.assertEqual('', Path('baz').select(xml).render())
     182        self._test_expression('*', None, xml, '<foo/><bar/>')
     183        self._test_expression('bar', None, xml, '<bar/>')
     184        self._test_expression('baz', None, xml, '')
    121185
    122186    def test_2step_attribute(self):
    123187        xml = XML('<elem class="x"><span id="joe">Hey Joe</span></elem>')
    124         self.assertEqual('x', Path('@*').select(xml).render())
    125         self.assertEqual('x', Path('./@*').select(xml).render())
    126         self.assertEqual('xjoe', Path('.//@*').select(xml).render())
    127         self.assertEqual('joe', Path('*/@*').select(xml).render())
     188        self._test_expression('@*', None, xml, 'x')
     189        self._test_expression('./@*', None, xml, 'x')
     190        self._test_expression('.//@*', None, xml, 'xjoe')
     191        self._test_expression('*/@*', None, xml, 'joe')
    128192
    129193        xml = XML('<elem><foo id="1"/><foo id="2"/></elem>')
    130         self.assertEqual('', Path('@*').select(xml).render())
    131         self.assertEqual('12', Path('foo/@*').select(xml).render())
     194        self._test_expression('@*', None, xml, '')
     195        self._test_expression('foo/@*', None, xml, '12')
    132196
    133197    def test_2step_complex(self):
    134198        xml = XML('<root><foo><bar/></foo></root>')
    135199
    136         path = Path('foo/bar')
    137         self.assertEqual('<Path "child::foo/child::bar">', repr(path))
    138         self.assertEqual('<bar/>', path.select(xml).render())
    139 
    140         path = Path('./bar')
    141         self.assertEqual('<Path "self::node()/child::bar">', repr(path))
    142         self.assertEqual('', path.select(xml).render())
    143 
    144         path = Path('foo/*')
    145         self.assertEqual('<Path "child::foo/child::*">', repr(path))
    146         self.assertEqual('<bar/>', path.select(xml).render())
     200        self._test_expression(  'foo/bar',
     201                                '<Path "child::foo/child::bar">',
     202                                xml,
     203                                '<bar/>')
     204
     205        self._test_expression(  './bar',
     206                                '<Path "self::node()/child::bar">',
     207                                xml,
     208                                '')
     209
     210        self._test_expression(  'foo/*',
     211                                '<Path "child::foo/child::*">',
     212                                xml,
     213                                '<bar/>')
    147214
    148215        xml = XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>')
    149         path = Path('./bar')
    150         self.assertEqual('<Path "self::node()/child::bar">', repr(path))
    151         self.assertEqual('<bar id="2"/>', path.select(xml).render())
     216        self._test_expression(  './bar',
     217                                '<Path "self::node()/child::bar">',
     218                                xml,
     219                                '<bar id="2"/>')
    152220
    153221    def test_2step_text(self):
    154222        xml = XML('<root><item>Foo</item></root>')
    155223
    156         path = Path('item/text()')
    157         self.assertEqual('<Path "child::item/child::text()">', repr(path))
    158         self.assertEqual('Foo', path.select(xml).render())
    159 
    160         path = Path('*/text()')
    161         self.assertEqual('<Path "child::*/child::text()">', repr(path))
    162         self.assertEqual('Foo', path.select(xml).render())
    163 
    164         path = Path('//text()')
    165         self.assertEqual('<Path "descendant-or-self::node()/child::text()">',
    166                          repr(path))
    167         self.assertEqual('Foo', path.select(xml).render())
    168 
    169         path = Path('./text()')
    170         self.assertEqual('<Path "self::node()/child::text()">', repr(path))
    171         self.assertEqual('', path.select(xml).render())
     224        self._test_expression(  'item/text()',
     225                                '<Path "child::item/child::text()">',
     226                                xml,
     227                                'Foo')
     228
     229        self._test_expression(  '*/text()',
     230                                '<Path "child::*/child::text()">',
     231                                xml,
     232                                'Foo')
     233
     234        self._test_expression(  '//text()',
     235                                '<Path "descendant-or-self::text()">',
     236                                xml,
     237                                'Foo')
     238
     239        self._test_expression(  './text()',
     240                                '<Path "self::node()/child::text()">',
     241                                xml,
     242                                '')
    172243
    173244        xml = XML('<root><item>Foo</item><item>Bar</item></root>')
    174         path = Path('item/text()')
    175         self.assertEqual('<Path "child::item/child::text()">', repr(path))
    176         self.assertEqual('FooBar', path.select(xml).render())
    177 
    178         xml = XML('<root><item>Foo</item><item>Bar</item></root>')
    179         self.assertEqual('FooBar', path.select(xml).render())
     245        self._test_expression(  'item/text()',
     246                                '<Path "child::item/child::text()">',
     247                                xml,
     248                                'FooBar')
    180249
    181250    def test_3step(self):
    182251        xml = XML('<root><foo><bar/></foo></root>')
    183         path = Path('foo/*')
    184         self.assertEqual('<Path "child::foo/child::*">', repr(path))
    185         self.assertEqual('<bar/>', path.select(xml).render())
     252        self._test_expression(  'foo/*',
     253                                '<Path "child::foo/child::*">',
     254                                xml,
     255                                '<bar/>')
    186256
    187257    def test_3step_complex(self):
    188258        xml = XML('<root><foo><bar/></foo></root>')
    189         path = Path('*/bar')
    190         self.assertEqual('<Path "child::*/child::bar">', repr(path))
    191         self.assertEqual('<bar/>', path.select(xml).render())
     259        self._test_expression(  '*/bar',
     260                                '<Path "child::*/child::bar">',
     261                                xml,
     262                                '<bar/>')
    192263
    193264        xml = XML('<root><foo><bar id="1"/></foo><bar id="2"/></root>')
    194         path = Path('//bar')
    195         self.assertEqual('<Path "descendant-or-self::node()/child::bar">',
    196                          repr(path))
    197         self.assertEqual('<bar id="1"/><bar id="2"/>',
    198                          path.select(xml).render())
     265        self._test_expression(  '//bar',
     266                                '<Path "descendant-or-self::bar">',
     267                                xml,
     268                                '<bar id="1"/><bar id="2"/>')
    199269
    200270    def test_node_type_comment(self):
    201271        xml = XML('<root><!-- commented --></root>')
    202         path = Path('comment()')
    203         self.assertEqual('<Path "child::comment()">', repr(path))
    204         self.assertEqual('<!-- commented -->', path.select(xml).render())
     272        self._test_expression(  'comment()',
     273                                '<Path "child::comment()">',
     274                                xml,
     275                                '<!-- commented -->')
    205276
    206277    def test_node_type_text(self):
    207278        xml = XML('<root>Some text <br/>in here.</root>')
    208         path = Path('text()')
    209         self.assertEqual('<Path "child::text()">', repr(path))
    210         self.assertEqual('Some text in here.', path.select(xml).render())
     279        self._test_expression(  'text()',
     280                                '<Path "child::text()">',
     281                                xml,
     282                                'Some text in here.')
    211283
    212284    def test_node_type_node(self):
    213285        xml = XML('<root>Some text <br/>in here.</root>')
    214         path = Path('node()')
    215         self.assertEqual('<Path "child::node()">', repr(path))
    216         self.assertEqual('Some text <br/>in here.', path.select(xml).render())
     286        self._test_expression(  'node()',
     287                                '<Path "child::node()">',
     288                                xml,
     289                                'Some text <br/>in here.',)
    217290
    218291    def test_node_type_processing_instruction(self):
    219292        xml = XML('<?python x = 2 * 3 ?><root><?php echo("x") ?></root>')
    220293
    221         path = Path('processing-instruction()')
    222         self.assertEqual('<Path "child::processing-instruction()">',
    223                          repr(path))
    224         self.assertEqual('<?python x = 2 * 3 ?><?php echo("x") ?>',
    225                          path.select(xml).render())
    226 
    227         path = Path('processing-instruction("php")')
    228         self.assertEqual('<Path "child::processing-instruction(\"php\")">',
    229                          repr(path))
    230         self.assertEqual('<?php echo("x") ?>', path.select(xml).render())
     294        self._test_expression(  '//processing-instruction()',
     295                        '<Path "descendant-or-self::processing-instruction()">',
     296                                xml,
     297                                '<?python x = 2 * 3 ?><?php echo("x") ?>')
     298
     299        self._test_expression(  'processing-instruction()',
     300                                '<Path "child::processing-instruction()">',
     301                                xml,
     302                                '<?php echo("x") ?>')
     303
     304        self._test_expression(  'processing-instruction("php")',
     305                        '<Path "child::processing-instruction(\"php\")">',
     306                                xml,
     307                                '<?php echo("x") ?>')
    231308
    232309    def test_simple_union(self):
    233310        xml = XML("""<body>1<br />2<br />3<br /></body>""")
    234         path = Path('*|text()')
    235         self.assertEqual('<Path "child::*|child::text()">', repr(path))
    236         self.assertEqual('1<br/>2<br/>3<br/>', path.select(xml).render())
     311        self._test_expression(  '*|text()',
     312                                '<Path "child::*|child::text()">',
     313                                xml,
     314                                '1<br/>2<br/>3<br/>')
    237315
    238316    def test_predicate_name(self):
    239317        xml = XML('<root><foo/><bar/></root>')
    240         path = Path('*[name()="foo"]')
    241         self.assertEqual('<foo/>', path.select(xml).render())
     318        self._test_expression('*[name()="foo"]', None, xml, '<foo/>')
    242319
    243320    def test_predicate_localname(self):
    244321        xml = XML('<root><foo xmlns="NS"/><bar/></root>')
    245         path = Path('*[local-name()="foo"]')
    246         self.assertEqual('<foo xmlns="NS"/>', path.select(xml).render())
     322        self._test_expression('*[local-name()="foo"]', None, xml,
     323                                '<foo xmlns="NS"/>')
    247324
    248325    def test_predicate_namespace(self):
    249326        xml = XML('<root><foo xmlns="NS"/><bar/></root>')
    250         path = Path('*[namespace-uri()="NS"]')
    251         self.assertEqual('<foo xmlns="NS"/>', path.select(xml).render())
     327        self._test_expression('*[namespace-uri()="NS"]', None, xml,
     328                                '<foo xmlns="NS"/>')
    252329
    253330    def test_predicate_not_name(self):
    254331        xml = XML('<root><foo/><bar/></root>')
    255         path = Path('*[not(name()="foo")]')
    256         self.assertEqual('<bar/>', path.select(xml).render())
     332        self._test_expression('*[not(name()="foo")]', None, xml, '<bar/>')
    257333
    258334    def test_predicate_attr(self):
    259335        xml = XML('<root><item/><item important="very"/></root>')
    260         path = Path('item[@important]')
    261         self.assertEqual('<item important="very"/>', path.select(xml).render())
    262         path = Path('item[@important="very"]')
    263         self.assertEqual('<item important="very"/>', path.select(xml).render())
     336        self._test_expression('item[@important]', None, xml,
     337                                '<item important="very"/>')
     338        self._test_expression('item[@important="very"]', None, xml,
     339                                '<item important="very"/>')
    264340
    265341    def test_predicate_attr_equality(self):
    266342        xml = XML('<root><item/><item important="notso"/></root>')
    267         path = Path('item[@important="very"]')
    268         self.assertEqual('', path.select(xml).render())
    269         path = Path('item[@important!="very"]')
    270         self.assertEqual('<item/><item important="notso"/>',
    271                          path.select(xml).render())
     343        self._test_expression('item[@important="very"]', None, xml, '')
     344        self._test_expression('item[@important!="very"]', None, xml,
     345                                '<item/><item important="notso"/>')
    272346
    273347    def test_predicate_attr_greater_than(self):
    274348        xml = XML('<root><item priority="3"/></root>')
    275         path = Path('item[@priority>3]')
    276         self.assertEqual('', path.select(xml).render())
    277         path = Path('item[@priority>2]')
    278         self.assertEqual('<item priority="3"/>', path.select(xml).render())
     349        self._test_expression('item[@priority>3]', None, xml, '')
     350        self._test_expression('item[@priority>2]', None, xml,
     351                                '<item priority="3"/>')
    279352
    280353    def test_predicate_attr_less_than(self):
    281354        xml = XML('<root><item priority="3"/></root>')
    282         path = Path('item[@priority<3]')
    283         self.assertEqual('', path.select(xml).render())
    284         path = Path('item[@priority<4]')
    285         self.assertEqual('<item priority="3"/>', path.select(xml).render())
     355        self._test_expression('item[@priority<3]', None, xml, '')
     356        self._test_expression('item[@priority<4]', None, xml,
     357                                '<item priority="3"/>')
    286358
    287359    def test_predicate_attr_and(self):
    288360        xml = XML('<root><item/><item important="very"/></root>')
    289         path = Path('item[@important and @important="very"]')
    290         self.assertEqual('<item important="very"/>', path.select(xml).render())
    291         path = Path('item[@important and @important="notso"]')
    292         self.assertEqual('', path.select(xml).render())
     361        self._test_expression('item[@important and @important="very"]',
     362                                None, xml, '<item important="very"/>')
     363        self._test_expression('item[@important and @important="notso"]',
     364                                None, xml, '')
    293365
    294366    def test_predicate_attr_or(self):
    295367        xml = XML('<root><item/><item important="very"/></root>')
    296         path = Path('item[@urgent or @important]')
    297         self.assertEqual('<item important="very"/>', path.select(xml).render())
    298         path = Path('item[@urgent or @notso]')
    299         self.assertEqual('', path.select(xml).render())
     368        self._test_expression('item[@urgent or @important]', None, xml,
     369                                '<item important="very"/>')
     370        self._test_expression('item[@urgent or @notso]', None, xml, '')
    300371
    301372    def test_predicate_boolean_function(self):
    302373        xml = XML('<root><foo>bar</foo></root>')
    303         path = Path('*[boolean("")]')
    304         self.assertEqual('', path.select(xml).render())
    305         path = Path('*[boolean("yo")]')
    306         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
    307         path = Path('*[boolean(0)]')
    308         self.assertEqual('', path.select(xml).render())
    309         path = Path('*[boolean(42)]')
    310         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
    311         path = Path('*[boolean(false())]')
    312         self.assertEqual('', path.select(xml).render())
    313         path = Path('*[boolean(true())]')
    314         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     374        self._test_expression('*[boolean("")]', None, xml, '')
     375        self._test_expression('*[boolean("yo")]', None, xml, '<foo>bar</foo>')
     376        self._test_expression('*[boolean(0)]', None, xml, '')
     377        self._test_expression('*[boolean(42)]', None, xml, '<foo>bar</foo>')
     378        self._test_expression('*[boolean(false())]', None, xml, '')
     379        self._test_expression('*[boolean(true())]', None, xml,
     380                                '<foo>bar</foo>')
    315381
    316382    def test_predicate_ceil_function(self):
    317383        xml = XML('<root><foo>bar</foo></root>')
    318         path = Path('*[ceiling("4.5")=5]')
    319         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     384        self._test_expression('*[ceiling("4.5")=5]', None, xml,
     385                                '<foo>bar</foo>')
    320386
    321387    def test_predicate_concat_function(self):
    322388        xml = XML('<root><foo>bar</foo></root>')
    323         path = Path('*[name()=concat("f", "oo")]')
    324         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     389        self._test_expression('*[name()=concat("f", "oo")]', None, xml,
     390                                '<foo>bar</foo>')
    325391
    326392    def test_predicate_contains_function(self):
    327393        xml = XML('<root><foo>bar</foo></root>')
    328         path = Path('*[contains(name(), "oo")]')
    329         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     394        self._test_expression('*[contains(name(), "oo")]', None, xml,
     395                                '<foo>bar</foo>')
    330396
    331397    def test_predicate_matches_function(self):
    332398        xml = XML('<root><foo>bar</foo><bar>foo</bar></root>')
    333         path = Path('*[matches(name(), "foo|bar")]')
    334         self.assertEqual('<foo>bar</foo><bar>foo</bar>', path.select(xml).render())
     399        self._test_expression('*[matches(name(), "foo|bar")]', None, xml,
     400                                '<foo>bar</foo><bar>foo</bar>')
    335401
    336402    def test_predicate_false_function(self):
    337403        xml = XML('<root><foo>bar</foo></root>')
    338         path = Path('*[false()]')
    339         self.assertEqual('', path.select(xml).render())
     404        self._test_expression('*[false()]', None, xml, '')
    340405
    341406    def test_predicate_floor_function(self):
    342407        xml = XML('<root><foo>bar</foo></root>')
    343         path = Path('*[floor("4.5")=4]')
    344         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     408        self._test_expression('*[floor("4.5")=4]', None, xml,
     409                                '<foo>bar</foo>')
    345410
    346411    def test_predicate_normalize_space_function(self):
    347412        xml = XML('<root><foo>bar</foo></root>')
    348         path = Path('*[normalize-space(" foo   bar  ")="foo bar"]')
    349         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     413        self._test_expression('*[normalize-space(" foo   bar  ")="foo bar"]',
     414                                None, xml, '<foo>bar</foo>')
    350415
    351416    def test_predicate_number_function(self):
    352417        xml = XML('<root><foo>bar</foo></root>')
    353         path = Path('*[number("3.0")=3]')
    354         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
    355         path = Path('*[number("3.0")=3.0]')
    356         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
    357         path = Path('*[number("0.1")=.1]')
    358         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     418        self._test_expression('*[number("3.0")=3]', None, xml,
     419                                 '<foo>bar</foo>')
     420        self._test_expression('*[number("3.0")=3.0]', None, xml,
     421                                '<foo>bar</foo>')
     422        self._test_expression('*[number("0.1")=.1]', None, xml,
     423                                '<foo>bar</foo>')
    359424
    360425    def test_predicate_round_function(self):
    361426        xml = XML('<root><foo>bar</foo></root>')
    362         path = Path('*[round("4.4")=4]')
    363         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
    364         path = Path('*[round("4.6")=5]')
    365         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     427        self._test_expression('*[round("4.4")=4]', None, xml,
     428                                '<foo>bar</foo>')
     429        self._test_expression('*[round("4.6")=5]', None, xml,
     430                                '<foo>bar</foo>')
    366431
    367432    def test_predicate_starts_with_function(self):
    368433        xml = XML('<root><foo>bar</foo></root>')
    369         path = Path('*[starts-with(name(), "f")]')
    370         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
    371         path = Path('*[starts-with(name(), "b")]')
    372         self.assertEqual('', path.select(xml).render())
     434        self._test_expression('*[starts-with(name(), "f")]', None, xml,
     435                                '<foo>bar</foo>')
     436        self._test_expression('*[starts-with(name(), "b")]', None, xml, '')
    373437
    374438    def test_predicate_string_length_function(self):
    375439        xml = XML('<root><foo>bar</foo></root>')
    376         path = Path('*[string-length(name())=3]')
    377         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     440        self._test_expression('*[string-length(name())=3]', None, xml,
     441                                '<foo>bar</foo>')
    378442
    379443    def test_predicate_substring_function(self):
    380444        xml = XML('<root><foo>bar</foo></root>')
    381         path = Path('*[substring(name(), 1)="oo"]')
    382         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
    383         path = Path('*[substring(name(), 1, 1)="o"]')
    384         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     445        self._test_expression('*[substring(name(), 1)="oo"]', None, xml,
     446                                '<foo>bar</foo>')
     447        self._test_expression('*[substring(name(), 1, 1)="o"]', None, xml,
     448                                '<foo>bar</foo>')
    385449
    386450    def test_predicate_substring_after_function(self):
    387451        xml = XML('<root><foo>bar</foo></root>')
    388         path = Path('*[substring-after(name(), "f")="oo"]')
    389         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     452        self._test_expression('*[substring-after(name(), "f")="oo"]', None, xml,
     453                                '<foo>bar</foo>')
    390454
    391455    def test_predicate_substring_before_function(self):
    392456        xml = XML('<root><foo>bar</foo></root>')
    393         path = Path('*[substring-before(name(), "oo")="f"]')
    394         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     457        self._test_expression('*[substring-before(name(), "oo")="f"]',
     458                                None, xml, '<foo>bar</foo>')
    395459
    396460    def test_predicate_translate_function(self):
    397461        xml = XML('<root><foo>bar</foo></root>')
    398         path = Path('*[translate(name(), "fo", "ba")="baa"]')
    399         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     462        self._test_expression('*[translate(name(), "fo", "ba")="baa"]',
     463                                None, xml, '<foo>bar</foo>')
    400464
    401465    def test_predicate_true_function(self):
    402466        xml = XML('<root><foo>bar</foo></root>')
    403         path = Path('*[true()]')
    404         self.assertEqual('<foo>bar</foo>', path.select(xml).render())
     467        self._test_expression('*[true()]', None, xml, '<foo>bar</foo>')
    405468
    406469    def test_predicate_variable(self):
    407470        xml = XML('<root><foo>bar</foo></root>')
    408         path = Path('*[name()=$bar]')
    409471        variables = {'bar': 'foo'}
    410         self.assertEqual('<foo>bar</foo>',
    411                          path.select(xml, variables=variables).render())
     472        self._test_expression('*[name()=$bar]', None, xml, '<foo>bar</foo>',
     473                                variables = variables)
    412474
    413475    def test_predicate_position(self):
    414476        xml = XML('<root><foo id="a1"/><foo id="a2"/><foo id="a3"/></root>')
    415         path = Path('*[2]')
    416         self.assertEqual('<foo id="a2"/>', path.select(xml).render())
     477        self._test_expression('*[2]', None, xml, '<foo id="a2"/>')
    417478
    418479    def test_predicate_attr_and_position(self):
    419480        xml = XML('<root><foo/><foo id="a1"/><foo id="a2"/></root>')
    420         path = Path('*[@id][2]')
    421         self.assertEqual('<foo id="a2"/>', path.select(xml).render())
     481        self._test_expression('*[@id][2]', None, xml, '<foo id="a2"/>')
    422482
    423483    def test_predicate_position_and_attr(self):
    424484        xml = XML('<root><foo/><foo id="a1"/><foo id="a2"/></root>')
    425         path = Path('*[1][@id]')
    426         self.assertEqual('', path.select(xml).render())
    427         path = Path('*[2][@id]')
    428         self.assertEqual('<foo id="a1"/>', path.select(xml).render())
     485        self._test_expression('*[1][@id]', None, xml, '')
     486        self._test_expression('*[2][@id]', None, xml, '<foo id="a1"/>')
     487
     488    def test_predicate_advanced_position(self):
     489        xml = XML('<root><a><b><c><d><e/></d></c></b></a></root>')
     490        self._test_expression(   'descendant-or-self::*/'
     491                                'descendant-or-self::*/'
     492                                'descendant-or-self::*[2]/'
     493                                'self::*/descendant::*[3]', None, xml,
     494                                '<d><e/></d>')
     495
     496    def test_predicate_child_position(self):
     497        xml = XML('\
     498<root><a><b>1</b><b>2</b><b>3</b></a><a><b>4</b><b>5</b></a></root>')
     499        self._test_expression('//a/b[2]', None, xml, '<b>2</b><b>5</b>')
     500        self._test_expression('//a/b[3]', None, xml, '<b>3</b>')
    429501
    430502    def test_name_with_namespace(self):
    431503        xml = XML('<root xmlns:f="FOO"><f:foo>bar</f:foo></root>')
    432         path = Path('f:foo')
    433         self.assertEqual('<Path "child::f:foo">', repr(path))
    434         namespaces = {'f': 'FOO'}
    435         self.assertEqual('<foo xmlns="FOO">bar</foo>',
    436                          path.select(xml, namespaces=namespaces).render())
     504        self._test_expression('f:foo', '<Path "child::f:foo">', xml,
     505                                '<foo xmlns="FOO">bar</foo>',
     506                                namespaces = {'f': 'FOO'})
    437507
    438508    def test_wildcard_with_namespace(self):
    439509        xml = XML('<root xmlns:f="FOO"><f:foo>bar</f:foo></root>')
    440         path = Path('f:*')
    441         self.assertEqual('<Path "child::f:*">', repr(path))
    442         namespaces = {'f': 'FOO'}
    443         self.assertEqual('<foo xmlns="FOO">bar</foo>',
    444                          path.select(xml, namespaces=namespaces).render())
     510        self._test_expression('f:*', '<Path "child::f:*">', xml,
     511                                '<foo xmlns="FOO">bar</foo>',
     512                                namespaces = {'f': 'FOO'})
    445513
    446514    def test_predicate_termination(self):
     
    450518        """
    451519        xml = XML('<ul flag="1"><li>a</li><li>b</li></ul>')
    452         path = Path('.[@flag="1"]/*')
    453         self.assertEqual('<li>a</li><li>b</li>', path.select(xml).render())
     520        self._test_expression('.[@flag="1"]/*', None, xml,
     521                                '<li>a</li><li>b</li>')
    454522
    455523        xml = XML('<ul flag="1"><li>a</li><li>b</li></ul>')
    456         path = Path('.[@flag="0"]/*')
    457         self.assertEqual('', path.select(xml).render())
     524        self._test_expression('.[@flag="0"]/*', None, xml, '')
    458525
    459526    def test_attrname_with_namespace(self):
    460527        xml = XML('<root xmlns:f="FOO"><foo f:bar="baz"/></root>')
    461         path = Path('foo[@f:bar]')
    462         self.assertEqual('<foo xmlns:ns1="FOO" ns1:bar="baz"/>',
    463                          path.select(xml, namespaces={'f': 'FOO'}).render())
     528        self._test_expression('foo[@f:bar]', None, xml,
     529                                '<foo xmlns:ns1="FOO" ns1:bar="baz"/>',
     530                                namespaces={'f': 'FOO'})
    464531
    465532    def test_attrwildcard_with_namespace(self):
    466533        xml = XML('<root xmlns:f="FOO"><foo f:bar="baz"/></root>')
    467         path = Path('foo[@f:*]')
    468         self.assertEqual('<foo xmlns:ns1="FOO" ns1:bar="baz"/>',
    469                          path.select(xml, namespaces={'f': 'FOO'}).render())
    470 
     534        self._test_expression('foo[@f:*]', None, xml,
     535                                '<foo xmlns:ns1="FOO" ns1:bar="baz"/>',
     536                                namespaces={'f': 'FOO'})
     537    def test_self_and_descendant(self):
     538        xml = XML('<root><foo/></root>')
     539        self._test_expression('self::root', None, xml, '<root><foo/></root>')
     540        self._test_expression('self::foo', None, xml, '')
     541        self._test_expression('descendant::root', None, xml, '')
     542        self._test_expression('descendant::foo', None, xml, '<foo/>')
     543        self._test_expression('descendant-or-self::root', None, xml,
     544                                '<root><foo/></root>')
     545        self._test_expression('descendant-or-self::foo', None, xml, '<foo/>')
     546
     547    def test_long_simple_paths(self):
     548        xml = XML('<root><a><b><a><d><a><b><a><b><a><b><a><c>!'
     549                    '</c></a></b></a></b></a></b></a></d></a></b></a></root>')
     550        self._test_expression('//a/b/a/b/a/c', None, xml, '<c>!</c>')
     551        self._test_expression('//a/b/a/c', None, xml, '<c>!</c>')
     552        self._test_expression('//a/c', None, xml, '<c>!</c>')
     553        self._test_expression('//c', None, xml, '<c>!</c>')
     554        # Please note that a//b is NOT the same as a/descendant::b
     555        # it is a/descendant-or-self::node()/b, which SimplePathStrategy
     556        # does NOT support
     557        self._test_expression('a/b/descendant::a/c', None, xml, '<c>!</c>')
     558        self._test_expression('a/b/descendant::a/d/descendant::a/c',
     559                                None, xml, '<c>!</c>')
     560        self._test_expression('a/b/descendant::a/d/a/c', None, xml, '')
     561        self._test_expression('//d/descendant::b/descendant::b/descendant::b'
     562                                '/descendant::c', None, xml, '<c>!</c>')
     563        self._test_expression('//d/descendant::b/descendant::b/descendant::b'
     564                                '/descendant::b/descendant::c', None, xml, '')
     565    def _test_support(self, strategy_class, text):
     566        path = PathParser(text, None, -1).parse()[0]
     567        return strategy_class.supports(path)
     568    def test_simple_strategy_support(self):
     569        self.assert_(self._test_support(SimplePathStrategy, 'a/b'))
     570        self.assert_(self._test_support(SimplePathStrategy, 'self::a/b'))
     571        self.assert_(self._test_support(SimplePathStrategy, 'descendant::a/b'))
     572        self.assert_(self._test_support(SimplePathStrategy,
     573                         'descendant-or-self::a/b'))
     574        self.assert_(self._test_support(SimplePathStrategy, '//a/b'))
     575        self.assert_(self._test_support(SimplePathStrategy, 'a/@b'))
     576        self.assert_(self._test_support(SimplePathStrategy, 'a/text()'))
     577
     578        # a//b is a/descendant-or-self::node()/b
     579        self.assert_(not self._test_support(SimplePathStrategy, 'a//b'))
     580        self.assert_(not self._test_support(SimplePathStrategy, 'node()/@a'))
     581        self.assert_(not self._test_support(SimplePathStrategy, '@a'))
     582        self.assert_(not self._test_support(SimplePathStrategy, 'foo:bar'))
     583        self.assert_(not self._test_support(SimplePathStrategy, 'a/@foo:bar'))
    471584
    472585def suite():
Note: See TracChangeset for help on using the changeset viewer.