nth.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import re
  2. from .parser import _next_significant, _to_token_iterator
  3. def parse_nth(input):
  4. """Parse `<An+B> <https://drafts.csswg.org/css-syntax-3/#anb>`_,
  5. as found in `:nth-child()
  6. <https://drafts.csswg.org/selectors/#nth-child-pseudo>`_
  7. and related Selector pseudo-classes.
  8. Although tinycss2 does not include a full Selector parser,
  9. this bit of syntax is included as it is particularly tricky to define
  10. on top of a CSS tokenizer.
  11. :type input: :obj:`str` or :term:`iterable`
  12. :param input: A string or an iterable of :term:`component values`.
  13. :returns:
  14. A ``(a, b)`` tuple of integers, or :obj:`None` if the input is invalid.
  15. """
  16. tokens = _to_token_iterator(input, skip_comments=True)
  17. token = _next_significant(tokens)
  18. if token is None:
  19. return
  20. token_type = token.type
  21. if token_type == 'number' and token.is_integer:
  22. return parse_end(tokens, 0, token.int_value)
  23. elif token_type == 'dimension' and token.is_integer:
  24. unit = token.lower_unit
  25. if unit == 'n':
  26. return parse_b(tokens, token.int_value)
  27. elif unit == 'n-':
  28. return parse_signless_b(tokens, token.int_value, -1)
  29. else:
  30. match = N_DASH_DIGITS_RE.match(unit)
  31. if match:
  32. return parse_end(tokens, token.int_value, int(match.group(1)))
  33. elif token_type == 'ident':
  34. ident = token.lower_value
  35. if ident == 'even':
  36. return parse_end(tokens, 2, 0)
  37. elif ident == 'odd':
  38. return parse_end(tokens, 2, 1)
  39. elif ident == 'n':
  40. return parse_b(tokens, 1)
  41. elif ident == '-n':
  42. return parse_b(tokens, -1)
  43. elif ident == 'n-':
  44. return parse_signless_b(tokens, 1, -1)
  45. elif ident == '-n-':
  46. return parse_signless_b(tokens, -1, -1)
  47. elif ident[0] == '-':
  48. match = N_DASH_DIGITS_RE.match(ident[1:])
  49. if match:
  50. return parse_end(tokens, -1, int(match.group(1)))
  51. else:
  52. match = N_DASH_DIGITS_RE.match(ident)
  53. if match:
  54. return parse_end(tokens, 1, int(match.group(1)))
  55. elif token == '+':
  56. token = next(tokens) # Whitespace after an initial '+' is invalid.
  57. if token.type == 'ident':
  58. ident = token.lower_value
  59. if ident == 'n':
  60. return parse_b(tokens, 1)
  61. elif ident == 'n-':
  62. return parse_signless_b(tokens, 1, -1)
  63. else:
  64. match = N_DASH_DIGITS_RE.match(ident)
  65. if match:
  66. return parse_end(tokens, 1, int(match.group(1)))
  67. def parse_b(tokens, a):
  68. token = _next_significant(tokens)
  69. if token is None:
  70. return (a, 0)
  71. elif token == '+':
  72. return parse_signless_b(tokens, a, 1)
  73. elif token == '-':
  74. return parse_signless_b(tokens, a, -1)
  75. elif (token.type == 'number' and token.is_integer and
  76. token.representation[0] in '-+'):
  77. return parse_end(tokens, a, token.int_value)
  78. def parse_signless_b(tokens, a, b_sign):
  79. token = _next_significant(tokens)
  80. if (token.type == 'number' and token.is_integer and
  81. token.representation[0] not in '-+'):
  82. return parse_end(tokens, a, b_sign * token.int_value)
  83. def parse_end(tokens, a, b):
  84. if _next_significant(tokens) is None:
  85. return (a, b)
  86. N_DASH_DIGITS_RE = re.compile('^n(-[0-9]+)$')