parse.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  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. multiline_comment = inline_comment = False
  21. try:
  22. for c in css:
  23. char = c
  24. if multiline_comment:
  25. # Multiline comment end?
  26. if c == '/' and prev_char == '*':
  27. multiline_comment = False
  28. elif inline_comment:
  29. # Inline comment end?
  30. if c == '\n':
  31. inline_comment = False
  32. elif c == '{':
  33. # Block start
  34. if selectors is not None:
  35. # Block is nested, save group selector
  36. current_group = []
  37. groups.append((selectors, current_group))
  38. selectors = split_selectors(stack)
  39. #print stack.strip(), '->', selectors
  40. stack = ''
  41. assert len(selectors)
  42. elif c == '}':
  43. if selectors is None:
  44. # Closing group
  45. current_group = root_group
  46. else:
  47. # Closing block
  48. current_group.append((selectors, properties))
  49. selectors = None
  50. properties = []
  51. elif c == ';':
  52. # Property definition
  53. assert selectors is not None
  54. name, value = map(str.strip, stack.split(':', 1))
  55. assert '\n' not in name
  56. properties.append((name, value))
  57. stack = ''
  58. elif c == '*' and prev_char == '/':
  59. # Multiline comment start
  60. multiline_comment = True
  61. elif c == '/' and prev_char == '/':
  62. # Inline comment start
  63. inline_comment = True
  64. else:
  65. if c == '\n':
  66. lineno += 1
  67. stack += c
  68. prev_char = c
  69. except AssertionError:
  70. raise Exception('unexpected \'%c\' on line %d' % (char, lineno))
  71. return groups