def test_series(self): assert parse_series('1n+3') == (1, 3) assert parse_series('n-5') == (1, -5) assert parse_series('odd') == (2, 1) assert parse_series('even') == (2, 0) assert parse_series('3n') == (3, 0) assert parse_series('n') == (1, 0) assert parse_series('+n') == (1, 0) assert parse_series('-n') == (-1, 0) assert parse_series('5') == (0, 5) self.assertRaises(ValueError, parse_series, 'foo') self.assertRaises(ValueError, parse_series, 'n+')
def xpath_nth_child_function(self, xpath, function, last=False, add_name_test=True): try: a, b = parse_series(function.arguments) except ValueError: raise ExpressionError("Invalid series: '%r'" % function.arguments) if add_name_test: xpath.add_name_test() xpath.add_star_prefix() if a == 0: if last: b = 'last() - %s' % b return xpath.add_condition('position() = %s' % b) if last: # FIXME: I'm not sure if this is right a = -a b = -b if b > 0: b_neg = str(-b) else: b_neg = '+%s' % (-b) if a != 1: expr = ['(position() %s) mod %s = 0' % (b_neg, a)] else: expr = [] if b >= 0: expr.append('position() >= %s' % b) elif b < 0 and last: expr.append('position() < (last() %s)' % b) expr = ' and '.join(expr) if expr: xpath.add_condition(expr) return xpath
def series(css): selector, = parse(':nth-child(%s)' % css) args = selector.parsed_tree.arguments try: return parse_series(args) except ValueError: return None
def xpath_nth_child_function(self, xpath, function, last=False, add_name_test=True): a, b = parse_series(function.arguments) if not a and not b and not last: # a=0 means nothing is returned... return xpath.add_condition('false() and position() = 0') if add_name_test: xpath.add_name_test() xpath.add_star_prefix() if a == 0: if last: b = 'last() - %s' % b return xpath.add_condition('position() = %s' % b) if last: # FIXME: I'm not sure if this is right a = -a b = -b if b > 0: b_neg = str(-b) else: b_neg = '+%s' % (-b) if a != 1: expr = ['(position() %s) mod %s = 0' % (b_neg, a)] else: expr = [] if b >= 0: expr.append('position() >= %s' % b) elif b < 0 and last: expr.append('position() < (last() %s)' % b) expr = ' and '.join(expr) if expr: xpath.add_condition(expr) return xpath
def test_series(self): assert parse_series('1n+3') == (1, 3) assert parse_series('n-5') == (1, -5) assert parse_series('odd') == (2, 1) assert parse_series('even') == (2, 0) assert parse_series('3n') == (3, 0) assert parse_series('n') == (1, 0) assert parse_series('5') == (0, 5)
def xpath_nth_child_function(self, xpath, function, last=False, add_name_test=True): try: a, b = parse_series(function.arguments) except ValueError: raise ExpressionError("Invalid series: '%r'" % function.arguments) # From https://www.w3.org/TR/css3-selectors/#structural-pseudos: # # :nth-child(an+b) # an+b-1 siblings before # # :nth-last-child(an+b) # an+b-1 siblings after # # :nth-of-type(an+b) # an+b-1 siblings with the same expanded element name before # # :nth-last-of-type(an+b) # an+b-1 siblings with the same expanded element name after # # So, # for :nth-child and :nth-of-type # # count(preceding-sibling::<nodetest>) = an+b-1 # # for :nth-last-child and :nth-last-of-type # # count(following-sibling::<nodetest>) = an+b-1 # # therefore, # count(...) - (b-1) ≡ 0 (mod a) # # if a == 0: # ~~~~~~~~~~ # count(...) = b-1 # # if a < 0: # ~~~~~~~~~ # count(...) - b +1 <= 0 # -> count(...) <= b-1 # # if a > 0: # ~~~~~~~~~ # count(...) - b +1 >= 0 # -> count(...) >= b-1 # work with b-1 instead b_min_1 = b - 1 # early-exit condition 1: # ~~~~~~~~~~~~~~~~~~~~~~~ # for a == 1, nth-*(an+b) means n+b-1 siblings before/after, # and since n ∈ {0, 1, 2, ...}, if b-1<=0, # there is always an "n" matching any number of siblings (maybe none) if a == 1 and b_min_1 <=0: return xpath # early-exit condition 2: # ~~~~~~~~~~~~~~~~~~~~~~~ # an+b-1 siblings with a<0 and (b-1)<0 is not possible if a < 0 and b_min_1 < 0: return xpath.add_condition('0') # `add_name_test` boolean is inverted and somewhat counter-intuitive: # # nth_of_type() calls nth_child(add_name_test=False) if add_name_test: nodetest = '*' else: nodetest = '%s' % xpath.element # count siblings before or after the element if not last: siblings_count = 'count(preceding-sibling::%s)' % nodetest else: siblings_count = 'count(following-sibling::%s)' % nodetest # special case of fixed position: nth-*(0n+b) # if a == 0: # ~~~~~~~~~~ # count(***-sibling::***) = b-1 if a == 0: return xpath.add_condition('%s = %s' % (siblings_count, b_min_1)) expr = [] if a > 0: # siblings count, an+b-1, is always >= 0, # so if a>0, and (b-1)<=0, an "n" exists to satisfy this, # therefore, the predicate is only interesting if (b-1)>0 if b_min_1 > 0: expr.append('%s >= %s' % (siblings_count, b_min_1)) else: # if a<0, and (b-1)<0, no "n" satisfies this, # this is tested above as an early exist condition # otherwise, expr.append('%s <= %s' % (siblings_count, b_min_1)) # operations modulo 1 or -1 are simpler, one only needs to verify: # # - either: # count(***-sibling::***) - (b-1) = n = 0, 1, 2, 3, etc., # i.e. count(***-sibling::***) >= (b-1) # # - or: # count(***-sibling::***) - (b-1) = -n = 0, -1, -2, -3, etc., # i.e. count(***-sibling::***) <= (b-1) # we we just did above. # if abs(a) != 1: # count(***-sibling::***) - (b-1) ≡ 0 (mod a) left = siblings_count # apply "modulo a" on 2nd term, -(b-1), # to simplify things like "(... +6) % -3", # and also make it positive with |a| b_neg = (-b_min_1) % abs(a) if b_neg != 0: b_neg = '+%s' % (b_neg) left = '(%s %s)' % (left, b_neg) expr.append('%s mod %s = 0' % (left, a)) xpath.add_condition(' and '.join(expr)) return xpath
def xpath_nth_child_function(self, xpath, function, last=False, add_name_test=True): try: a, b = parse_series(function.arguments) except ValueError: raise ExpressionError("Invalid series: '%r'" % function.arguments) # From https://www.w3.org/TR/css3-selectors/#structural-pseudos: # # :nth-child(an+b) # an+b-1 siblings before # # :nth-last-child(an+b) # an+b-1 siblings after # # :nth-of-type(an+b) # an+b-1 siblings with the same expanded element name before # # :nth-last-of-type(an+b) # an+b-1 siblings with the same expanded element name after # # So, # for :nth-child and :nth-of-type # # count(preceding-sibling::<nodetest>) = an+b-1 # # for :nth-last-child and :nth-last-of-type # # count(following-sibling::<nodetest>) = an+b-1 # # therefore, # count(...) - (b-1) ≡ 0 (mod a) # # if a == 0: # ~~~~~~~~~~ # count(...) = b-1 # # if a < 0: # ~~~~~~~~~ # count(...) - b +1 <= 0 # -> count(...) <= b-1 # # if a > 0: # ~~~~~~~~~ # count(...) - b +1 >= 0 # -> count(...) >= b-1 # work with b-1 instead b_min_1 = b - 1 # early-exit condition 1: # ~~~~~~~~~~~~~~~~~~~~~~~ # for a == 1, nth-*(an+b) means n+b-1 siblings before/after, # and since n ∈ {0, 1, 2, ...}, if b-1<=0, # there is always an "n" matching any number of siblings (maybe none) if a == 1 and b_min_1 <= 0: return xpath # early-exit condition 2: # ~~~~~~~~~~~~~~~~~~~~~~~ # an+b-1 siblings with a<0 and (b-1)<0 is not possible if a < 0 and b_min_1 < 0: return xpath.add_condition('0') # `add_name_test` boolean is inverted and somewhat counter-intuitive: # # nth_of_type() calls nth_child(add_name_test=False) if add_name_test: nodetest = '*' else: nodetest = '%s' % xpath.element # count siblings before or after the element if not last: siblings_count = 'count(preceding-sibling::%s)' % nodetest else: siblings_count = 'count(following-sibling::%s)' % nodetest # special case of fixed position: nth-*(0n+b) # if a == 0: # ~~~~~~~~~~ # count(***-sibling::***) = b-1 if a == 0: return xpath.add_condition('%s = %s' % (siblings_count, b_min_1)) expr = [] if a > 0: # siblings count, an+b-1, is always >= 0, # so if a>0, and (b-1)<=0, an "n" exists to satisfy this, # therefore, the predicate is only interesting if (b-1)>0 if b_min_1 > 0: expr.append('%s >= %s' % (siblings_count, b_min_1)) else: # if a<0, and (b-1)<0, no "n" satisfies this, # this is tested above as an early exist condition # otherwise, expr.append('%s <= %s' % (siblings_count, b_min_1)) # operations modulo 1 or -1 are simpler, one only needs to verify: # # - either: # count(***-sibling::***) - (b-1) = n = 0, 1, 2, 3, etc., # i.e. count(***-sibling::***) >= (b-1) # # - or: # count(***-sibling::***) - (b-1) = -n = 0, -1, -2, -3, etc., # i.e. count(***-sibling::***) <= (b-1) # we we just did above. # if abs(a) != 1: # count(***-sibling::***) - (b-1) ≡ 0 (mod a) left = siblings_count # apply "modulo a" on 2nd term, -(b-1), # to simplify things like "(... +6) % -3", # and also make it positive with |a| b_neg = (-b_min_1) % abs(a) if b_neg != 0: b_neg = '+%s' % (b_neg) left = '(%s %s)' % (left, b_neg) expr.append('%s mod %s = 0' % (left, a)) xpath.add_condition(' and '.join(expr)) return xpath