parse.py 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. def split_selectors(raw_selector):
  2. """
  3. Split a selector with commas and arbitrary whitespaces into a list of
  4. selector swith single-space whitespaces.
  5. """
  6. return [' '.join(s.split()) for s in raw_selector.split(',')]
  7. def parse_groups(css):
  8. """
  9. Parse CSS code one character at a time. This is more efficient than
  10. simply splitting on brackets, especially for large style sheets. All
  11. comments are ignored.
  12. """
  13. stack = char = ''
  14. prev_char = None
  15. lineno = 1
  16. properties = []
  17. current_group = root_group = []
  18. groups = [(None, root_group)]
  19. selectors = None
  20. comment = False
  21. def parse_property():
  22. assert selectors is not None
  23. if stack.strip():
  24. parts = stack.split(':', 1)
  25. assert len(parts) == 2
  26. name, value = map(str.strip, parts)
  27. assert '\n' not in name
  28. properties.append((name, value))
  29. try:
  30. for c in css:
  31. char = c
  32. if char == '\n':
  33. lineno += 1
  34. if comment:
  35. # Comment end?
  36. if char == '/' and prev_char == '*':
  37. comment = False
  38. elif char == '{':
  39. # Block start
  40. if selectors is not None:
  41. # Block is nested, save group selector
  42. current_group = []
  43. groups.append((selectors, current_group))
  44. selectors = split_selectors(stack)
  45. #print stack.strip(), '->', selectors
  46. stack = ''
  47. assert len(selectors)
  48. elif char == '}':
  49. # Last property may not have been closed with a semicolon
  50. parse_property()
  51. if selectors is None:
  52. # Closing group
  53. current_group = root_group
  54. else:
  55. # Closing block
  56. current_group.append((selectors, properties))
  57. selectors = None
  58. properties = []
  59. elif char == ';':
  60. # Property definition
  61. parse_property()
  62. stack = ''
  63. elif char == '*' and prev_char == '/':
  64. # Comment start
  65. comment = True
  66. stack = stack[:-1]
  67. else:
  68. stack += char
  69. prev_char = char
  70. except AssertionError:
  71. raise Exception('unexpected \'%c\' on line %d' % (char, lineno))
  72. return groups