ref: d09f37f9a1ae9736abe320071764ffdfa3a61957
parent: 294b8735087508d83c93f56e7991d64a97c3edea
author: Philip Silva <philip.silva@protonmail.com>
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 {