parse.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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 filter(None, (' '.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 = (None, [])
  18. groups = []
  19. selectors = None
  20. comment = False
  21. nesting_level = 0
  22. property_name = None
  23. try:
  24. for c in css:
  25. char = c
  26. if char == '\n':
  27. lineno += 1
  28. if comment:
  29. # Comment end?
  30. if char == '/' and prev_char == '*':
  31. comment = False
  32. elif char == '{':
  33. nesting_level += 1
  34. # Block start
  35. if selectors is not None:
  36. # Block is nested, push current root group and continue
  37. # with this group
  38. if len(current_group[1]):
  39. groups.append(current_group)
  40. current_group = (selectors, [])
  41. selectors = split_selectors(stack)
  42. stack = ''
  43. assert len(selectors)
  44. elif char == '}':
  45. assert nesting_level > 0
  46. nesting_level -= 1
  47. if selectors is None:
  48. # Closing group
  49. groups.append(current_group)
  50. current_group = (None, [])
  51. else:
  52. # Closing block
  53. # Last property may not have been closed with a semicolon
  54. property_value = stack.strip()
  55. if len(property_value):
  56. assert property_name is not None
  57. properties.append((property_name, property_value))
  58. property_name = None
  59. stack = ''
  60. current_group[1].append((selectors, properties))
  61. selectors = None
  62. properties = []
  63. elif char == ':' and nesting_level > 0:
  64. assert selectors is not None
  65. # Property name
  66. property_name = stack.strip()
  67. assert '\n' not in property_name
  68. stack = ''
  69. elif char == ';':
  70. # Property value
  71. property_value = stack.strip()
  72. if len(property_value):
  73. assert property_name is not None
  74. properties.append((property_name, property_value))
  75. property_name = None
  76. stack = ''
  77. else:
  78. assert property_name is None
  79. elif char == '*' and prev_char == '/':
  80. # Comment start
  81. comment = True
  82. stack = stack[:-1]
  83. else:
  84. stack += char
  85. prev_char = char
  86. if len(current_group[1]):
  87. groups.append(current_group)
  88. if stack.split() or nesting_level > 0:
  89. char = '<EOF>'
  90. raise AssertionError()
  91. except AssertionError:
  92. if len(char) < 2:
  93. char = "'" + char + "'"
  94. raise Exception('unexpected %s on line %d' % (char, lineno))
  95. return groups