shithub: mycel

Download patch

ref: d09f37f9a1ae9736abe320071764ffdfa3a61957
parent: 294b8735087508d83c93f56e7991d64a97c3edea
author: Philip Silva <[email protected]>
date: Fri Jan 14 19:44:30 EST 2022

parse at-rules in css

--- a/style/css.go
+++ b/style/css.go
@@ -1,7 +1,9 @@
 package style
 
 import (
+	"bytes"
 	"fmt"
+	"github.com/psilva261/opossum"
 	"github.com/psilva261/opossum/logger"
 	"github.com/tdewolff/parse/v2"
 	"github.com/tdewolff/parse/v2/css"
@@ -31,11 +33,50 @@
 	Val       string
 }
 
-func Parse(rd io.Reader, inline bool) (s Sheet, err error) {
+func Preprocess(s string) (bs []byte, ct opossum.ContentType, err error) {
+	buf := bytes.NewBufferString("")
+	l := css.NewLexer(parse.NewInputString(s))
+	ct.MediaType = "text/css"
+	ct.Params = make(map[string]string)
+	at := ""
+	for {
+		tt, data := l.Next()
+		if tt == css.ErrorToken {
+			if err != io.EOF {
+				err = l.Err()
+			}
+			break
+		}
+		if d := string(data); tt == css.AtKeywordToken && (d == "@charset" || d == "@import") {
+			at = d
+		} else if tt == css.SemicolonToken {
+			at = ""
+		}
+		switch at {
+		case "@charset":
+			if tt == css.StringToken {
+				ct.Params["charset"] = string(data)
+			}
+			continue
+		case "@import":
+			continue
+		}
+		if _, err := buf.Write(data); err != nil {
+			return nil, ct, err
+		}
+	}
+	return buf.Bytes(), ct, nil
+}
+
+func Parse(str string, inline bool) (s Sheet, err error) {
 	s.Rules = make([]Rule, 0, 1000)
 	stack := make([]Rule, 0, 2)
 	selectors := make([]Selector, 0, 1)
-	p := css.NewParser(parse.NewInput(rd), inline)
+	bs, ct, err := Preprocess(str)
+	if err != nil {
+		return s, fmt.Errorf("preprocess: %v", err)
+	}
+	p := css.NewParser(parse.NewInputString(ct.Utf8(bs)), inline)
 	if inline {
 		stack = append(stack, Rule{})
 		defer func() {
--- a/style/css_test.go
+++ b/style/css_test.go
@@ -1,13 +1,12 @@
 package style
 
 import (
-	"bytes"
 	"testing"
 )
 
 func TestParseInline(t *testing.T) {
-	b := bytes.NewBufferString("color: red;")
-	s, err := Parse(b, true)
+	css := "color: red;"
+	s, err := Parse(css, true)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -26,7 +25,7 @@
 }
 
 func TestParseMin(t *testing.T) {
-	b := bytes.NewBufferString(`
+	css := `
 		h1 {
 			font-weight: bold;
 			font-size: 100px;
@@ -42,8 +41,8 @@
 		b {
 			color: var(--emph);
 		}
-	`)
-	s, err := Parse(b, false)
+	`
+	s, err := Parse(css, false)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -78,14 +77,14 @@
 }
 
 func TestParseMedia(t *testing.T) {
-	b := bytes.NewBufferString(`
+	css := `
 		@media only screen and (max-width: 600px) {
 		  body {
 		    background-color: lightblue;
 		  }
 		}
-	`)
-	s, err := Parse(b, false)
+	`
+	s, err := Parse(css, false)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -106,7 +105,7 @@
 }
 
 func TestParseComment(t *testing.T) {
-	b := bytes.NewBufferString(`
+	css := `
 		h1 {
 			font-weight: bold;
 			font-size: 100px;
@@ -115,8 +114,8 @@
 		p {
 			color: grey !important;
 		}
-	`)
-	s, err := Parse(b, false)
+	`
+	s, err := Parse(css, false)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -143,7 +142,7 @@
 }
 
 func TestParseQual(t *testing.T) {
-	b := bytes.NewBufferString(`
+	css := `
 		h1 {
 			font-weight: bold;
 			font-size: 100px;
@@ -155,8 +154,8 @@
 		  color: blue;
 		  margin-right: 2px;
 		}
-	`)
-	s, err := Parse(b, false)
+	`
+	s, err := Parse(css, false)
 	if err != nil {
 		t.Fatalf("%v", err)
 	}
@@ -179,5 +178,59 @@
 	d = r.Declarations[0]
 	if d.Prop != "color" || d.Val != "blue" {
 		t.Fail()
+	}
+}
+
+func TestParseAtRule(t *testing.T) {
+	css := `
+		@charset "UTF-8";.info{z-index:3;}
+		@media only screen and (max-width: 600px) {
+		  body {
+		    background-color: lightblue;
+		  }
+		}
+	`
+	s, err := Parse(css, false)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Logf("s: %+v", s)
+	if len(s.Rules) != 2 {
+		t.Fail()
+	}
+	r := s.Rules[1]
+	if len(r.Declarations) != 0 || len(r.Selectors) > 0 {
+		t.Fatalf("%+v", r)
+	}
+	d := r.Rules[0].Declarations[0]
+	if d.Prop != "background-color" || d.Val != "lightblue" {
+		t.Fatalf("%+v", d)
+	}
+}
+
+func TestParseAtRule2(t *testing.T) {
+	css := `
+		@import url(https://fonts.googleapis.com/css?family=Montserrat:400,400i,700,800);.info{z-index:3;}
+		@media only screen and (max-width: 600px) {
+		  body {
+		    background-color: lightblue;
+		  }
+		}
+	`
+	s, err := Parse(css, false)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Logf("s: %+v", s)
+	if len(s.Rules) != 2 {
+		t.Fail()
+	}
+	r := s.Rules[1]
+	if len(r.Declarations) != 0 || len(r.Selectors) > 0 {
+		t.Fatalf("%+v", r)
+	}
+	d := r.Rules[0].Declarations[0]
+	if d.Prop != "background-color" || d.Val != "lightblue" {
+		t.Fatalf("%+v", d)
 	}
 }
--- a/style/stylesheets.go
+++ b/style/stylesheets.go
@@ -148,7 +148,7 @@
 func FetchNodeRules(doc *html.Node, cssText string, windowWidth int) (m map[*html.Node][]Rule, rVars map[string]string, err error) {
 	m = make(map[*html.Node][]Rule)
 	rVars = make(map[string]string)
-	s, err := Parse(strings.NewReader(cssText), false)
+	s, err := Parse(cssText, false)
 	if err != nil {
 		return nil, nil, fmt.Errorf("parse: %w", err)
 	}
@@ -237,7 +237,7 @@
 			if !strings.HasSuffix(v, ";") {
 				v += ";"
 			}
-			st, err := Parse(strings.NewReader(v), true)
+			st, err := Parse(v, true)
 
 			var decls []Declaration
 			if len(st.Rules) > 0 {