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 (both inline and multiline).
  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 comment:
  33. # Comment end?
  34. if c == '/' and prev_char == '*':
  35. comment = False
  36. elif c == '{':
  37. # Block start
  38. if selectors is not None:
  39. # Block is nested, save group selector
  40. current_group = []
  41. groups.append((selectors, current_group))
  42. selectors = split_selectors(stack)
  43. #print stack.strip(), '->', selectors
  44. stack = ''
  45. assert len(selectors)
  46. elif c == '}':
  47. # Last property may not have been closed with a semicolon
  48. parse_property()
  49. if selectors is None:
  50. # Closing group
  51. current_group = root_group
  52. else:
  53. # Closing block
  54. current_group.append((selectors, properties))
  55. selectors = None
  56. properties = []
  57. elif c == ';':
  58. # Property definition
  59. parse_property()
  60. stack = ''
  61. elif c == '*' and prev_char == '/':
  62. # Comment start
  63. comment = True
  64. stack = stack[:-1]
  65. else:
  66. if c == '\n':
  67. lineno += 1
  68. stack += c
  69. prev_char = c
  70. except AssertionError:
  71. raise Exception('unexpected \'%c\' on line %d' % (char, lineno))
  72. return groups