Diff
diff --git a/selector/main.go b/selector/main.go
index f1d4ff2..1df32cf 100644
--- a/selector/main.go
+++ b/selector/main.go
@@ -4,4 +3,0 @@ import (
- "fmt"
- "grim/element"
- "regexp"
- "strconv"
@@ -8,0 +5,2 @@ import (
+
+ "golang.org/x/net/html"
@@ -12,15 +9,0 @@ import (
-// + :nth-last-child()etc..
-
-// !TODO: Make var() and :root
-
-type SelectorParts struct {
- TagName string
- ID string
- Classes []string
- Attributes map[string]string
-}
-
-func splitSelector(selector string, key rune) []string {
- var parts []string
- var current strings.Builder
- nesting := 0
@@ -28,31 +11,11 @@ func splitSelector(selector string, key rune) []string {
- for _, r := range selector {
- switch r {
- case '(':
- nesting++
- current.WriteRune(r)
- case ')':
- if nesting > 0 {
- nesting--
- }
- current.WriteRune(r)
- case '[':
- nesting++
- current.WriteRune(r)
- case ']':
- if nesting > 0 {
- nesting--
- }
- current.WriteRune(r)
- case '{':
- nesting++
- current.WriteRune(r)
- case '}':
- if nesting > 0 {
- nesting--
- }
- current.WriteRune(r)
- case key:
- if nesting == 0 {
- // End of the current selector part
- parts = append(parts, strings.TrimSpace(current.String()))
- current.Reset()
+func GetInitCSSSelectors(node *html.Node, selectors []string) []string {
+ if node.Type == html.ElementNode {
+ selectors = append(selectors, node.Data)
+ for _, attr := range node.Attr {
+ if attr.Key == "class" {
+ classes := strings.Split(attr.Val, " ")
+ for _, class := range classes {
+ selectors = append(selectors, "."+class)
+ }
+ } else if attr.Key == "id" {
+ selectors = append(selectors, "#"+attr.Val)
@@ -60,2 +23 @@ func splitSelector(selector string, key rune) []string {
- // Inside nested context, add comma to the current part
- current.WriteRune(r)
+ selectors = append(selectors, "["+attr.Key+"=\""+attr.Val+"\"]")
@@ -63,2 +24,0 @@ func splitSelector(selector string, key rune) []string {
- default:
- current.WriteRune(r)
@@ -68,6 +28 @@ func splitSelector(selector string, key rune) []string {
- // Add the last part if non-empty
- if current.Len() > 0 {
- parts = append(parts, strings.TrimSpace(current.String()))
- }
-
- return parts
+ return selectors
@@ -76,15 +31,3 @@ func splitSelector(selector string, key rune) []string {
-// nthChildMatch checks if the given index matches the nth-child pattern.
-func NthChildMatch(pattern string, index int) bool {
- pattern = strings.ReplaceAll(pattern, " ", "")
- // Handle special cases for "odd" and "even"
- lowerPattern := strings.ToLower(strings.TrimSpace(pattern))
- if lowerPattern == "odd" {
- return index%2 == 1
- }
- if lowerPattern == "even" {
- return index%2 == 0
- }
-
- // Coefficients for "an+b"
- a, b := 0, 0
- nIndex := strings.Index(lowerPattern, "n")
+func SplitSelector(s string) []string {
+ var result []string
+ var current strings.Builder
@@ -92,14 +35,3 @@ func NthChildMatch(pattern string, index int) bool {
- // Parse pattern with 'n'
- if nIndex != -1 {
- // Parse coefficient of "n" (before 'n')
- if nIndex == 0 || lowerPattern[0] == '+' {
- a = 1
- } else if lowerPattern[0] == '-' && nIndex == 1 {
- a = -1
- } else {
- var err error
- a, err = strconv.Atoi(lowerPattern[:nIndex])
- if err != nil {
- return false
- }
- }
+ // Check if there's a `::` and split the string
+ var prePseudo string
+ var pseudo string
@@ -107,4 +39,3 @@ func NthChildMatch(pattern string, index int) bool {
- // Parse constant term (after 'n')
- if nIndex+1 < len(lowerPattern) {
- b, _ = strconv.Atoi(lowerPattern[nIndex+1:])
- }
+ if idx := strings.Index(s, "::"); idx != -1 {
+ prePseudo = s[:idx]
+ pseudo = s[idx:] // Keep everything after `::` together
@@ -112,24 +43 @@ func NthChildMatch(pattern string, index int) bool {
- // Handle single integer patterns like "3"
- var err error
- b, err = strconv.Atoi(lowerPattern)
- if err != nil {
- return false
- }
- }
-
- // Check if index matches the formula a*n + b
- if a == 0 {
- return index == b
- }
- return (index-b)%a == 0 && (index-b)/a >= 0
-}
-func Contains(selector []string, node []string) bool {
- selectorSet := make(map[string]struct{}, len(node))
- for _, s := range node {
- selectorSet[strings.TrimSpace(s)] = struct{}{}
- }
-
- for _, s := range selector {
- if _, exists := selectorSet[strings.TrimSpace(s)]; !exists {
- return false
- }
+ prePseudo = s
@@ -137,9 +44,0 @@ func Contains(selector []string, node []string) bool {
- return true
-}
-
-func TestSelector(n *element.Node, selector string) bool {
- selectors := splitSelector(selector, ',')
- // fmt.Println(selectors)
- // fmt.Println("START: ", n.Properties.Id, selector)
-
- if selector[0] == ':' {
@@ -147,7 +46,7 @@ func TestSelector(n *element.Node, selector string) bool {
- if len(selector) >= 5 && selector[0:5] == ":has(" {
- m := false
- for _, v := range n.Children {
- m = TestSelector(v, selector[5:len(selector)-1])
- // fmt.Println("HAS", selector[5:len(selector)-1], m)
- if m {
- break
+ // Process the part before the pseudo-element selector
+ for _, char := range prePseudo {
+ switch char {
+ case '.', '#', '[', ']', ':':
+ if current.Len() > 0 {
+ if char == ']' {
+ current.WriteRune(char)
@@ -154,0 +54,2 @@ func TestSelector(n *element.Node, selector string) bool {
+ result = append(result, current.String())
+ current.Reset()
@@ -156,20 +57,2 @@ func TestSelector(n *element.Node, selector string) bool {
- return m
- } else if len(selector) >= 7 && selector[0:7] == ":where(" {
- m := TestSelector(n, selector[7:len(selector)-1])
- // fmt.Println("WHERE", selector[7:len(selector)-1], m)
- return m
- } else if len(selector) >= 4 && selector[0:4] == ":is(" {
- m := TestSelector(n, selector[4:len(selector)-1])
- // fmt.Println("IS: ", selector[4:len(selector)-1], m)
- return m
- } else if len(selector) >= 5 && selector[0:5] == ":not(" {
- m := !TestSelector(n, selector[5:len(selector)-1])
- // fmt.Println("NOT: ", selector[5:len(selector)-1], m)
- return m
- } else if len(selector) >= 11 && selector[0:11] == ":nth-child(" {
- index := 0
- for _, v := range n.Parent.Children {
- index++
- if v.Properties.Id == n.Properties.Id {
- break
- }
+ if char != ']' {
+ current.WriteRune(char)
@@ -177,11 +60,2 @@ func TestSelector(n *element.Node, selector string) bool {
- m := NthChildMatch(selector[11:len(selector)-1], index)
- // fmt.Println("NTH: ", selector[11:len(selector)-1], m)
- return m
- } else if selector == ":required" {
- return n.Required
- } else if selector == ":enabled" {
- return !n.Disabled
- } else if selector == ":disabled" {
- return n.Disabled
- } else if selector == ":checked" {
- return n.Checked
+ default:
+ current.WriteRune(char)
@@ -190,78 +63,0 @@ func TestSelector(n *element.Node, selector string) bool {
- has := false
- for _, s := range selectors {
- directChildren := splitSelector(s, '>')
- if len(directChildren) > 1 {
- currentElement := n
- for i := len(directChildren) - 2; i >= 0; i-- {
- currentElement = currentElement.Parent
- if TestSelector(currentElement, directChildren[i]) {
- has = true
- break
- }
- }
- }
- for _, dc := range directChildren {
- adjacentSiblings := splitSelector(dc, '+')
- if len(adjacentSiblings) > 1 {
- // fmt.Println("ADJ: ", adjacentSiblings, len(adjacentSiblings), n.Properties.Id)
- match := false
- index := 0
- for _, sel := range adjacentSiblings {
- // fmt.Println("TESTING ADJ: ", sel, len(n.Parent.Children), index)
- for i := index; i < len(n.Parent.Children); i++ {
- v := n.Parent.Children[i]
- // fmt.Println("ON: ", v.Properties.Id)
- if TestSelector(v, sel) {
- if match {
- fmt.Println("SIBLINGS: ", i == index, i, index)
- return i == index
- }
- match = true
- index = i + 1
- if index > len(n.Parent.Children)-1 {
- has = false
- break
- }
- break
- }
- }
- if !match {
- break
- }
- }
- // fmt.Println("ADJ RES: ", match)
- has = match
- break
- }
- for _, as := range adjacentSiblings {
- generalSiblings := splitSelector(as, '~')
- if len(generalSiblings) > 1 {
- // fmt.Println("GEN SIB: ", generalSiblings, len(generalSiblings), n.Properties.Id)
- match := false
- for _, sel := range generalSiblings {
- // fmt.Println("TESTING GEN SIB: ", sel, len(n.Parent.Children))
- for i := 0; i < len(n.Parent.Children); i++ {
- v := n.Parent.Children[i]
- // fmt.Println("ON: ", v.Properties.Id)
- if TestSelector(v, sel) {
- if match {
- return true
- }
- match = true
- break
- }
- }
- if !match {
- break
- }
- }
- // fmt.Println("GEN SIB RES: ", match)
- has = match
- break
- }
- for _, gs := range generalSiblings {
- descendants := splitSelector(gs, ' ')
- for _, d := range descendants {
- computeAble := splitSelector(d, ':')
- baseNode := computeAble[0]
- // fmt.Println("BN", baseNode, computeAble, len(computeAble))
@@ -269,23 +65,2 @@ func TestSelector(n *element.Node, selector string) bool {
- if len(computeAble) > 1 {
- match := true
- for _, v := range computeAble[1:] {
- // fmt.Println("TESTING COMPUTABLE")
- m := TestSelector(n, ":"+v)
- if !m {
- match = false
- }
- }
- has = match
- }
- if has || !(len(computeAble) > 1) {
- m := CompareSelector(baseNode, n)
- // fmt.Println("NO COMPUTABLE: ", m, baseNode, n.Properties.Id)
- has = m
- }
- }
- }
- }
- }
- if has {
- return true
- }
+ if current.Len() > 0 {
+ result = append(result, current.String())
@@ -293,2 +67,0 @@ func TestSelector(n *element.Node, selector string) bool {
- return has
-}
@@ -296,4 +69,3 @@ func TestSelector(n *element.Node, selector string) bool {
-func ParseSelector(selector string) SelectorParts {
- // Initialize parts
- parts := SelectorParts{
- Attributes: make(map[string]string),
+ // Add the pseudo-element (if any) as a single item
+ if pseudo != "" {
+ result = append(result, pseudo)
@@ -302,5 +74,2 @@ func ParseSelector(selector string) SelectorParts {
- // Regular expressions for components
- tagRegex := `^[a-zA-Z][a-zA-Z0-9-]*`
- idRegex := `#([a-zA-Z0-9_-]+)`
- classRegex := `\.([a-zA-Z0-9_-]+)`
- attrRegex := `\[(.+?)\]`
+ return result
+}
@@ -308,5 +77,4 @@ func ParseSelector(selector string) SelectorParts {
- // Extract tag name
- tagMatch := regexp.MustCompile(tagRegex).FindString(selector)
- if tagMatch != "" {
- parts.TagName = tagMatch
- selector = strings.TrimPrefix(selector, tagMatch)
+func Contains(selector []string, node []string) bool {
+ selectorSet := make(map[string]struct{}, len(node))
+ for _, s := range node {
+ selectorSet[s] = struct{}{}
@@ -315,12 +83,3 @@ func ParseSelector(selector string) SelectorParts {
- // Extract attributes
- attrMatches := regexp.MustCompile(attrRegex).FindAllStringSubmatch(selector, -1)
- for _, match := range attrMatches {
- attr := match[1]
- kv := strings.SplitN(attr, "=", 2)
- if len(kv) == 2 {
- key := kv[0]
- value := strings.Trim(kv[1], `"`)
- parts.Attributes[key] = value
- } else {
- // Handle attributes without values
- parts.Attributes[kv[0]] = ""
+ for _, s := range selector {
+ if _, exists := selectorSet[s]; !exists {
+ return false
@@ -328,14 +86,0 @@ func ParseSelector(selector string) SelectorParts {
- selector = strings.Replace(selector, match[0], "", 1)
- }
-
- // Extract ID
- idMatch := regexp.MustCompile(idRegex).FindStringSubmatch(selector)
- if len(idMatch) > 1 {
- parts.ID = idMatch[1]
- selector = strings.Replace(selector, idMatch[0], "", 1)
- }
-
- // Extract classes
- classMatches := regexp.MustCompile(classRegex).FindAllStringSubmatch(selector, -1)
- for _, match := range classMatches {
- parts.Classes = append(parts.Classes, match[1])
@@ -343,2 +88 @@ func ParseSelector(selector string) SelectorParts {
-
- return parts
+ return true
@@ -347,32 +91,4 @@ func ParseSelector(selector string) SelectorParts {
-func CompareSelector(selector string, n *element.Node) bool {
- baseParts := ParseSelector(selector)
- has := false
- if baseParts.ID == n.Id || baseParts.ID == "" {
- if baseParts.TagName == n.TagName || baseParts.TagName == "" {
- match := true
- for _, v := range baseParts.Classes {
- bpc := false
- for _, c := range n.ClassList.Classes {
- if v == c {
- bpc = true
- }
- }
-
- if !bpc {
- match = false
- }
- }
-
- for k, v := range baseParts.Attributes {
- if n.Attribute[k] != v {
- match = false
- }
- }
-
- if match {
- has = true
- }
- }
- }
- return has
-}
+// precompile selectors so you don't have to split selector on every style sheet and every selector everytime, put in a map
+// if the selector is the same as another merge them.
+// if the selector has a parent or child selector then make a css prop "Selector": "full selector". should be blank if not
+// can run the actual test in that case, just see if a part of the selector matches, can be cleaned up later