-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCSSParser.py
More file actions
127 lines (117 loc) · 4.36 KB
/
CSSParser.py
File metadata and controls
127 lines (117 loc) · 4.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
from Helper.selector import TagSelector, DescendantSelector, ClassSelector
class CSSParser:
def __init__(self, s):
# text
self.s = s
# parser's current position in text
self.i = 0
def whitespace(self):
while self.i < len(self.s) and self.s[self.i].isspace():
self.i += 1
def word(self):
start = self.i
# increments i through all the word characters
# stores where it started and the substring it extracted
while self.i < len(self.s):
if self.s[self.i].isalnum() or self.s[self.i] in "#-.%":
self.i += 1
else:
break
# check that i advanced though at least one character
# otherwise it did not point at a word
assert self.i > start
return self.s[start:self.i]
def literal(self, literal):
# check if a character is literally something
assert self.i < len(self.s) and self.s[self.i] == literal
self.i += 1
def pair(self):
prop = self.word()
self.whitespace()
self.literal(":")
self.whitespace()
val = self.word()
return prop.lower(), val
def body(self):
# parse sequences by calling the parsing functions in a loop
pairs = {}
while self.i < len(self.s) and self.s[self.i] != "}":
try:
prop, val = self.pair()
pairs[prop.lower()] = val
self.whitespace()
self.literal(";")
self.whitespace()
except AssertionError:
# when we cannot correctly parse a property, value pair
# skip to the next semicolon/end of string
why = self.ignore_until([";", "}"])
if why == ";":
self.literal(";")
self.whitespace()
else:
break
return pairs
def ignore_until(self, chars):
# stops at any one of a set of characters and returns it
# returns None if at end of file
while self.i < len(self.s):
if self.s[self.i] in chars:
return self.s[self.i]
else:
self.i += 1
def selector(self):
# create selector objects
# base_selectors = []
# out = TagSelector(self.word().lower())
# self.whitespace()
# while self.i < len(self.s) and self.s[self.i] != "{":
# if self.s[self.i] == ".":
# self.i += 1 # Move past the dot character
# class_name = self.word().lower()
# class_selector = ClassSelector(class_name)
# base_selectors.append(class_selector)
# else:
# tag = self.word()
# descendant = TagSelector(tag.lower())
# base_selectors.append(descendant)
# out = DescendantSelector(base_selectors)
# self.whitespace()
# return out
out = TagSelector(self.word().lower())
self.whitespace()
while self.i < len(self.s) and self.s[self.i] != "{":
base_selectors = [out]
if self.s[self.i] == ".":
self.i += 1 # Move past the dot character
class_name = self.word().lower()
class_selector = ClassSelector(class_name)
base_selectors.append(class_selector)
else:
tag = self.word()
descendant = TagSelector(tag.lower())
base_selectors.append(descendant)
out = DescendantSelector(base_selectors)
self.whitespace()
return out
def parse(self):
# css files are a sequence of selectors and blocks
rules = []
while self.i < len(self.s):
try:
self.whitespace()
selector = self.selector()
self.literal("{")
self.whitespace()
body = self.body()
self.literal("}")
rules.append((selector, body))
except AssertionError:
# if there is a parse error skip the whole rule
why = self.ignore_until(["}"])
if why == "}":
self.literal("}")
self.whitespace()
else:
break
return rules