1package css
2
3import "strings"
4
5// StateFlags are the supported CSS pseudo-class states.
6type StateFlags uint8
7
8const (
9 Hover StateFlags = 1 << iota
10 Active
11 Focus
12 FocusVisible
13 Disabled
14 Checked
15)
16
17// Element is the matching identity for one Compute call. Parentage comes from Computed.
18type Element struct {
19 Type, ID string
20 Classes []string
21 State StateFlags
22 Inline *Declarations
23}
24type Specificity struct{ IDs, Classes, Types int }
25
26func (s Specificity) greater(t Specificity) bool {
27 if s.IDs != t.IDs {
28 return s.IDs > t.IDs
29 }
30 if s.Classes != t.Classes {
31 return s.Classes > t.Classes
32 }
33 return s.Types > t.Types
34}
35
36type compound struct {
37 typ, id string
38 classes, pseudos []string
39 combinator string
40 typID, idID uint32
41 classIDs []uint32
42 state StateFlags
43}
44type Selector struct {
45 parts []compound
46 Specificity Specificity
47}
48
49func parseSelectors(ts []Token) ([]Selector, bool) {
50 var result []Selector
51 start := 0
52 for i := 0; i <= len(ts); i++ {
53 if i == len(ts) || ts[i].Kind == "," {
54 s, ok := parseSelector(ts[start:i])
55 if !ok {
56 return nil, false
57 }
58 result = append(result, s)
59 start = i + 1
60 }
61 }
62 return result, len(result) > 0
63}
64func parseSelector(ts []Token) (Selector, bool) {
65 s := Selector{}
66 i := 0
67 pending := ""
68 for i < len(ts) {
69 space := false
70 for i < len(ts) && ts[i].Kind == "space" {
71 space = true
72 i++
73 }
74 if space && len(s.parts) > 0 && pending == "" {
75 pending = " "
76 }
77 if i >= len(ts) {
78 break
79 }
80 if ts[i].Kind == ">" {
81 if len(s.parts) == 0 || pending == ">" {
82 return s, false
83 }
84 pending = ">"
85 i++
86 continue
87 }
88 p := compound{combinator: pending}
89 pending = ""
90 seen := false
91 for i < len(ts) {
92 t := ts[i]
93 if t.Kind == "space" || t.Kind == ">" {
94 break
95 }
96 switch t.Kind {
97 case "hash":
98 if p.id != "" {
99 return s, false
100 }
101 p.id = t.Value
102 s.Specificity.IDs++
103 seen = true
104 i++
105 case "ident":
106 if seen || p.typ != "" {
107 return s, false
108 }
109 p.typ = strings.ToLower(t.Value)
110 s.Specificity.Types++
111 seen = true
112 i++
113 case ".", "#", ":":
114 if i+1 >= len(ts) || ts[i+1].Kind != "ident" {
115 return s, false
116 }
117 v := ts[i+1].Value
118 switch t.Kind {
119 case ".":
120 p.classes = append(p.classes, v)
121 s.Specificity.Classes++
122 case "#":
123 if p.id != "" {
124 return s, false
125 }
126 p.id = v
127 s.Specificity.IDs++
128 case ":":
129 switch strings.ToLower(v) {
130 case "hover", "active", "focus", "focus-visible", "disabled", "checked":
131 p.pseudos = append(p.pseudos, strings.ToLower(v))
132 s.Specificity.Classes++
133 default:
134 return s, false
135 }
136 }
137 seen = true
138 i += 2
139 default:
140 return s, false
141 }
142 }
143 if !seen || len(s.parts) > 0 && p.combinator == "" {
144 return s, false
145 }
146 s.parts = append(s.parts, p)
147 }
148 return s, len(s.parts) > 0 && pending != ">"
149}