shithub: mycel

Download patch

ref: 62b73b4e51b6d2db873956a9df9671e9cc2116c4
parent: 7c284bdddd481c95525f88e1a989bd512088b4a2
author: Philip Silva <[email protected]>
date: Sat Jan 8 21:25:07 EST 2022

more up-to-date css lib

--- a/browser/browser_test.go
+++ b/browser/browser_test.go
@@ -3,7 +3,6 @@
 import (
 	"9fans.net/go/draw"
 	"fmt"
-	"github.com/chris-ramon/douceur/css"
 	"github.com/mjl-/duit"
 	"github.com/psilva261/opossum/browser/duitx"
 	"github.com/psilva261/opossum/logger"
@@ -69,11 +68,11 @@
 		h3 := nt.Find("h3")
 
 		m := style.Map{
-			Declarations: make(map[string]css.Declaration),
+			Declarations: make(map[string]style.Declaration),
 		}
-		m.Declarations["display"] = css.Declaration{
-			Property: "display",
-			Value:    d,
+		m.Declarations["display"] = style.Declaration{
+			Prop: "display",
+			Val:    d,
 		}
 		h1.Map = m
 		h2.Map = m
--- a/browser/fs/experimental.go
+++ b/browser/fs/experimental.go
@@ -197,7 +197,7 @@
 				var v string
 				for p, d := range st.cs.Declarations {
 					if p == k {
-						v = d.Value
+						v = d.Val
 					}
 				}
 				return []byte(v)
--- a/go.mod
+++ b/go.mod
@@ -8,10 +8,6 @@
 
 replace github.com/mjl-/duit v0.0.0-20200330125617-580cb0b2843f => github.com/psilva261/duit v0.0.0-20210802155600-7e8fedefa7ba
 
-exclude github.com/aymerick/douceur v0.1.0
-
-exclude github.com/aymerick/douceur v0.2.0
-
 exclude github.com/hanwen/go-fuse v1.0.0
 
 exclude github.com/hanwen/go-fuse/v2 v2.0.3
@@ -19,12 +15,11 @@
 require (
 	9fans.net/go v0.0.2
 	github.com/andybalholm/cascadia v1.3.1
-	github.com/chris-ramon/douceur v0.2.1-0.20160603235419-f3463056cd52
-	github.com/gorilla/css v1.0.0 // indirect
 	github.com/knusbaum/go9p v1.18.0
 	github.com/mjl-/duit v0.0.0-20200330125617-580cb0b2843f
 	github.com/srwiley/oksvg v0.0.0-20211120171407-1837d6608d8c
 	github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780
+	github.com/tdewolff/parse/v2 v2.5.26
 	golang.org/x/image v0.0.0-20211028202545-6944b10bf410
 	golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
 	golang.org/x/text v0.3.7
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,6 @@
 github.com/Plan9-Archive/libauth v0.0.0-20180917063427-d1ca9e94969d/go.mod h1:UKp8dv9aeaZoQFWin7eQXtz89iHly1YAFZNn3MCutmQ=
 github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
-github.com/chris-ramon/douceur v0.2.1-0.20160603235419-f3463056cd52 h1:xJWyi77j4VQwdeo6bO3wQSQ7o7yVwEM0ZvwXpyKHZZ8=
-github.com/chris-ramon/douceur v0.2.1-0.20160603235419-f3463056cd52/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
@@ -10,8 +8,6 @@
 github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
 github.com/fhs/mux9p v0.3.1 h1:x1UswUWZoA9vrA02jfisndCq3xQm+wrQUxUt5N99E08=
 github.com/fhs/mux9p v0.3.1/go.mod h1:F4hwdenmit0WDoNVT2VMWlLJrBVCp/8UhzJa7scfjEQ=
-github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
-github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 github.com/knusbaum/go9p v1.18.0 h1:/Y67RNvNKX1ZV1IOdnO1lIetiF0X+CumOyvEc0011GI=
 github.com/knusbaum/go9p v1.18.0/go.mod h1:HtMoJKqZUe1Oqag5uJqG5RKQ9gWPSP+wolsnLLv44r8=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -27,6 +23,10 @@
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tdewolff/parse/v2 v2.5.26 h1:a/q3lwDCi4GIQ+sSbs4UOHuObhqp8GHAhfqop/zDyQQ=
+github.com/tdewolff/parse/v2 v2.5.26/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
+github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
+github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
 golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
 golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
--- a/nodes/nodes.go
+++ b/nodes/nodes.go
@@ -3,7 +3,6 @@
 import (
 	"bytes"
 	"fmt"
-	"github.com/chris-ramon/douceur/css"
 	"github.com/psilva261/opossum/logger"
 	"github.com/psilva261/opossum/style"
 	"golang.org/x/net/html"
@@ -30,7 +29,7 @@
 // First applies the parent style and at the end the local style attribute's style is attached.
 func NewNodeTree(doc *html.Node, ps style.Map, nodeMap map[*html.Node]style.Map, parent *Node) (n *Node) {
 	ncs := style.Map{
-		Declarations: make(map[string]css.Declaration),
+		Declarations: make(map[string]style.Declaration),
 	}
 	ncs = ps.ApplyChildStyle(ncs, false)
 
@@ -58,9 +57,9 @@
 	n.Wrappable = doc.Type == html.TextNode || doc.Data == "span" // TODO: probably this list needs to be extended
 	if doc.Type == html.TextNode {
 		n.Text = filterText(doc.Data)
-		n.Map.Declarations["display"] = css.Declaration{
-			Property: "display",
-			Value:    "inline",
+		n.Map.Declarations["display"] = style.Declaration{
+			Prop: "display",
+			Val:  "inline",
 		}
 	}
 	i := 0
@@ -395,7 +394,7 @@
 		if len(n.Map.Declarations) > 0 {
 			l := make([]string, 0, 2)
 			for k, d := range n.Map.Declarations {
-				s := fmt.Sprintf("%v=%v", k, d.Value)
+				s := fmt.Sprintf("%v=%v", k, d.Val)
 				if d.Important {
 					s += "!"
 				}
--- a/opossum_test.go
+++ /dev/null
@@ -1,16 +1,0 @@
-package opossum
-
-import (
-	"testing"
-
-	"github.com/chris-ramon/douceur/parser"
-)
-
-// aymerick douceur issues #6
-func TestInfiniteLoop(t *testing.T) {
-	parser.Parse(`
-@media ( __desktop ) {
-  background-color: red;
-}
-`)
-}
--- /dev/null
+++ b/style/css.go
@@ -1,0 +1,121 @@
+package style
+
+import (
+	"fmt"
+	"github.com/psilva261/opossum/logger"
+	"github.com/tdewolff/parse/v2"
+	"github.com/tdewolff/parse/v2/css"
+	"io"
+	"strings"
+)
+
+type Sheet struct {
+	Rules []Rule
+}
+
+type Rule struct {
+	Prelude      string
+	Selectors    []Selector
+	Declarations []Declaration
+
+	Rules []Rule
+}
+
+type Selector struct {
+	Val string
+}
+
+type Declaration struct {
+	Important bool
+	Prop      string
+	Val       string
+}
+
+func Parse(rd io.Reader, 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)
+	if inline {
+		stack = append(stack, Rule{})
+		defer func() {
+			s.Rules = append(s.Rules, stack[0])
+		}()
+	}
+	for {
+		gt, _, data := p.Next()
+		switch gt {
+		case css.ErrorGrammar:
+			if err := p.Err(); err == io.EOF {
+				return s, nil
+			} else {
+				return s, fmt.Errorf("next: %v", err)
+			}
+			break
+		case css.QualifiedRuleGrammar:
+			sel := Selector{}
+			for _, val := range p.Values() {
+				sel.Val += string(val.Data)
+			}
+			selectors = append(selectors, sel)
+		case css.AtRuleGrammar, css.BeginAtRuleGrammar, css.BeginRulesetGrammar, css.DeclarationGrammar, css.CustomPropertyGrammar:
+			var d Declaration
+			if gt == css.BeginRulesetGrammar || gt == css.BeginAtRuleGrammar || gt == css.AtRuleGrammar {
+				// TODO: why also gt == css.AtRuleGrammar? some sites crash otherwise
+				stack = append(stack, Rule{})
+			}
+			r := &(stack[len(stack)-1])
+			if gt == css.DeclarationGrammar || gt == css.CustomPropertyGrammar {
+				d.Prop = string(data)
+			}
+			if gt == css.BeginAtRuleGrammar {
+				r.Prelude = string(data)
+			}
+			vals := p.Values()
+			for i, val := range vals {
+				if gt == css.DeclarationGrammar || gt == css.CustomPropertyGrammar {
+					if string(val.Data) == "!" && len(vals) == i+2 && string(vals[i+1].Data) == "important" {
+						d.Important = true
+						break
+					} else {
+						d.Val += string(val.Data)
+					}
+				} else if gt == css.BeginRulesetGrammar {
+					if len(selectors) == 0 {
+						sel := Selector{
+							Val: string(val.Data),
+						}
+						selectors = append(selectors, sel)
+					} else {
+						selectors[len(selectors)-1].Val += string(val.Data)
+					}
+				} else if gt == css.BeginAtRuleGrammar {
+					r.Prelude += string(val.Data)
+				} else {
+				}
+			}
+			if gt == css.DeclarationGrammar || gt == css.CustomPropertyGrammar {
+				d.Val = strings.TrimSpace(d.Val)
+				r.Declarations = append(r.Declarations, d)
+			}
+		case css.EndRulesetGrammar, css.EndAtRuleGrammar:
+			var r Rule
+			if len(stack) == 1 {
+				r, stack = stack[len(stack)-1], stack[:len(stack)-1]
+				r.Selectors = append([]Selector{}, selectors...)
+				s.Rules = append(s.Rules, r)
+			} else {
+				p := &(stack[len(stack)-2])
+				r, stack = stack[len(stack)-1], stack[:len(stack)-1]
+				r.Selectors = append([]Selector{}, selectors...)
+				p.Rules = append(p.Rules, r)
+			}
+
+			selectors = make([]Selector, 0, 1)
+		case css.CommentGrammar:
+		default:
+			log.Errorf("unknown token type %+v", gt)
+		}
+	}
+	return
+}
--- /dev/null
+++ b/style/css_test.go
@@ -1,0 +1,183 @@
+package style
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestParseInline(t *testing.T) {
+	b := bytes.NewBufferString("color: red;")
+	s, err := Parse(b, true)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Logf("s: %+v", s)
+	if len(s.Rules) != 1 {
+		t.Fail()
+	}
+	r := s.Rules[0]
+	if len(r.Declarations) != 1 {
+		t.Fail()
+	}
+	d := r.Declarations[0]
+	if d.Prop != "color" || d.Val != "red" {
+		t.Fail()
+	}
+}
+
+func TestParseMin(t *testing.T) {
+	b := bytes.NewBufferString(`
+		h1 {
+			font-weight: bold;
+			font-size: 100px;
+		}
+		p, quote, a < b, div {
+			color: grey !important;
+		}
+		:root {
+			--emph: red;
+			--h: 10px;
+		}
+
+		b {
+			color: var(--emph);
+		}
+	`)
+	s, err := Parse(b, false)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Logf("s: %+v", s)
+	if len(s.Rules) != 4 {
+		t.Fatalf("%+v", s)
+	}
+	r := s.Rules[0]
+	if len(r.Declarations) != 2 || len(r.Selectors) != 1 || r.Selectors[0].Val != "h1" {
+		t.Fatalf("%+v", r)
+	}
+	d := r.Declarations[0]
+	if d.Prop != "font-weight" || d.Val != "bold" || d.Important {
+		t.Fatalf("%+v", d)
+	}
+	r = s.Rules[1]
+	if len(r.Declarations) != 1 || len(r.Selectors) != 3 {
+		t.Fatalf("%+v", r)
+	}
+	d = r.Declarations[0]
+	if d.Prop != "color" || d.Val != "grey" || !d.Important {
+		t.Fatalf("%+v", d)
+	}
+	r = s.Rules[2]
+	if len(r.Declarations) != 2 || len(r.Selectors) != 1 || r.Selectors[0].Val != ":root" {
+		t.Fatalf("%+v", r)
+	}
+	d = r.Declarations[0]
+	if d.Prop != "--emph" || d.Val != "red" {
+		t.Fatalf("%+v %+v", r, d)
+	}
+}
+
+func TestParseMedia(t *testing.T) {
+	b := bytes.NewBufferString(`
+		@media only screen and (max-width: 600px) {
+		  body {
+		    background-color: lightblue;
+		  }
+		}
+	`)
+	s, err := Parse(b, false)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Logf("s: %+v", s)
+	t.Logf("s.Rules[0].Prelude: %+v", s.Rules[0].Prelude)
+	//t.Logf("s.Rules[0].Prelude: %+v", s.Rules[0].Rules[0].Prelude)
+	if len(s.Rules) != 1 {
+		t.Fatalf("%+v", s)
+	}
+	r := s.Rules[0]
+	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 TestParseComment(t *testing.T) {
+	b := bytes.NewBufferString(`
+		h1 {
+			font-weight: bold;
+			font-size: 100px;
+		}
+		/* grey text */
+		p {
+			color: grey !important;
+		}
+	`)
+	s, err := Parse(b, false)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Logf("s: %+v", s)
+	if len(s.Rules) != 2 {
+		t.Fatalf("%v", s)
+	}
+	r := s.Rules[0]
+	if len(r.Declarations) != 2 || r.Selectors[0].Val != "h1" {
+		t.Fatalf("%v", r)
+	}
+	d := r.Declarations[0]
+	if d.Prop != "font-weight" || d.Val != "bold" {
+		t.Fatalf("%v", d)
+	}
+	r = s.Rules[1]
+	if len(r.Declarations) != 1 || r.Selectors[0].Val != "p" {
+		t.Fatalf("%v", r)
+	}
+	d = r.Declarations[0]
+	if d.Prop != "color" || d.Val != "grey" || !d.Important {
+		t.Fatalf("%v", d)
+	}
+}
+
+func TestParseQual(t *testing.T) {
+	b := bytes.NewBufferString(`
+		h1 {
+			font-weight: bold;
+			font-size: 100px;
+		}
+		p {
+			color: grey !important;
+		}
+		a[href] {
+		  color: blue;
+		  margin-right: 2px;
+		}
+	`)
+	s, err := Parse(b, false)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	t.Logf("s: %+v", s)
+	if len(s.Rules) != 3 {
+		t.Fail()
+	}
+	r := s.Rules[0]
+	if len(r.Declarations) != 2 || r.Selectors[0].Val != "h1" {
+		t.Fail()
+	}
+	d := r.Declarations[0]
+	if d.Prop != "font-weight" || d.Val != "bold" {
+		t.Fail()
+	}
+	r = s.Rules[2]
+	if len(r.Declarations) != 2 || r.Selectors[0].Val != "a[href]" {
+		t.Fatalf("%+v", r)
+	}
+	d = r.Declarations[0]
+	if d.Prop != "color" || d.Val != "blue" {
+		t.Fail()
+	}
+}
--- a/style/experimental.go
+++ b/style/experimental.go
@@ -3,7 +3,6 @@
 import (
 	"9fans.net/go/draw"
 	"fmt"
-	"github.com/chris-ramon/douceur/css"
 	"github.com/mjl-/duit"
 	"github.com/psilva261/opossum"
 	"github.com/psilva261/opossum/img"
@@ -20,10 +19,10 @@
 }
 
 var TextNode = Map{
-	Declarations: map[string]css.Declaration{
-		"display": css.Declaration{
-			Property: "display",
-			Value:    "inline",
+	Declarations: map[string]Declaration{
+		"display": Declaration{
+			Prop: "display",
+			Val:  "inline",
 		},
 	},
 }
@@ -62,7 +61,7 @@
 func (cs Map) backgroundColor() (c draw.Color, ok bool) {
 	d, ok := cs.Declarations["background-color"]
 	if ok {
-		c, ok = colorHex(d.Value)
+		c, ok = colorHex(d.Val)
 		if !ok {
 			return
 		}
@@ -70,7 +69,7 @@
 	}
 	d, ok = cs.Declarations["background"]
 	if ok {
-		c, ok = colorHex(d.Value)
+		c, ok = colorHex(d.Val)
 		if !ok {
 			return
 		}
@@ -99,7 +98,7 @@
 	if !ok {
 		return
 	}
-	v := strings.TrimSpace(d.Value)
+	v := strings.TrimSpace(d.Val)
 	if strings.HasPrefix(v, "linear-gradient(") {
 		v = strings.TrimPrefix(v, "linear-gradient(")
 	} else {
@@ -155,8 +154,8 @@
 	return draw.Color(cc)
 }
 
-func backgroundImageUrl(decl css.Declaration) (url string, ok bool) {
-	if v := decl.Value; strings.Contains(v, "url(") && strings.Contains(v, ")") {
+func backgroundImageUrl(decl Declaration) (url string, ok bool) {
+	if v := decl.Val; strings.Contains(v, "url(") && strings.Contains(v, ")") {
 		v = strings.ReplaceAll(v, `"`, "")
 		v = strings.ReplaceAll(v, `'`, "")
 		from := strings.Index(v, "url(")
--- a/style/experimental_test.go
+++ b/style/experimental_test.go
@@ -2,7 +2,6 @@
 
 import (
 	"9fans.net/go/draw"
-	"github.com/chris-ramon/douceur/css"
 	"github.com/psilva261/opossum/logger"
 	"testing"
 )
@@ -15,8 +14,8 @@
 	suffix := ""
 	for _, quote := range []string{"", "'", `"`} {
 		url := "/foo.png"
-		decl := css.Declaration{
-			Value: "url(" + quote + url + quote + ")" + suffix,
+		decl := Declaration{
+			Val: "url(" + quote + url + quote + ")" + suffix,
 		}
 		imgUrl, ok := backgroundImageUrl(decl)
 		if !ok {
@@ -36,12 +35,12 @@
 
 	for _, k := range []string{"background", "background-color"} {
 		m := Map{
-			Declarations: make(map[string]css.Declaration),
+			Declarations: make(map[string]Declaration),
 		}
 		for hex, d := range colors {
-			m.Declarations[k] = css.Declaration{
-				Property: k,
-				Value:    hex,
+			m.Declarations[k] = Declaration{
+				Prop: k,
+				Val:  hex,
 			}
 
 			if b, ok := m.backgroundColor(); !ok || b != d {
@@ -59,11 +58,11 @@
 	}
 	for v, cc := range values {
 		m := Map{
-			Declarations: make(map[string]css.Declaration),
+			Declarations: make(map[string]Declaration),
 		}
-		m.Declarations["background"] = css.Declaration{
-			Property: "background",
-			Value:    v,
+		m.Declarations["background"] = Declaration{
+			Prop: "background",
+			Val:  v,
 		}
 		c, ok := m.backgroundGradient()
 		if !ok {
--- a/style/stylesheets.go
+++ b/style/stylesheets.go
@@ -5,8 +5,6 @@
 	"bytes"
 	"fmt"
 	"github.com/andybalholm/cascadia"
-	"github.com/chris-ramon/douceur/css"
-	"github.com/chris-ramon/douceur/parser"
 	"github.com/mjl-/duit"
 	"github.com/psilva261/opossum/logger"
 	"golang.org/x/image/colornames"
@@ -25,8 +23,8 @@
 var dui *duit.DUI
 var availableFontNames []string
 
-var rMinWidth = regexp.MustCompile(`min-width: (\d+)(px|em|rem)`)
-var rMaxWidth = regexp.MustCompile(`max-width: (\d+)(px|em|rem)`)
+var rMinWidth = regexp.MustCompile(`min-width:\s*(\d+)(px|em|rem)`)
+var rMaxWidth = regexp.MustCompile(`max-width:\s*(\d+)(px|em|rem)`)
 
 const FontBaseSize = 11.0
 
@@ -118,20 +116,20 @@
 	}
 	m = make(map[*html.Node]Map)
 	for n, rs := range mr {
-		ds := make(map[string]css.Declaration)
+		ds := make(map[string]Declaration)
 		for _, r := range rs {
 			for _, d := range r.Declarations {
-				if exist, ok := ds[d.Property]; ok && smaller(*d, exist) {
+				if exist, ok := ds[d.Prop]; ok && smaller(d, exist) {
 					continue
 				}
-				if strings.HasPrefix(d.Value, "var(") {
-					v := strings.TrimPrefix(d.Value, "var(")
+				if strings.HasPrefix(d.Val, "var(") {
+					v := strings.TrimPrefix(d.Val, "var(")
 					v = strings.TrimSuffix(v, ")")
 					if vv, ok := rv[v]; ok {
-						d.Value = vv
+						d.Val = vv
 					}
 				}
-				ds[d.Property] = *d
+				ds[d.Prop] = d
 			}
 		}
 		m[n] = Map{Declarations: ds}
@@ -139,46 +137,37 @@
 	return
 }
 
-func smaller(d, dd css.Declaration) bool {
+func smaller(d, dd Declaration) bool {
 	return dd.Important
 }
 
 func compile(v string) (cs cascadia.Selector, err error) {
-	l := strings.Split(v, " ")
-	for _, s := range l {
-		s = strings.TrimSpace(s)
-		if strings.HasSuffix(s, ":") {
-			// TODO: selectors like .selector: would crash otherwise
-			err = fmt.Errorf("unsupported selector: %v", s)
-			return
-		}
-	}
 	return cascadia.Compile(v)
 }
 
-func FetchNodeRules(doc *html.Node, cssText string, windowWidth int) (m map[*html.Node][]*css.Rule, rVars map[string]string, err error) {
-	m = make(map[*html.Node][]*css.Rule)
+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 := parser.Parse(cssText)
+	s, err := Parse(strings.NewReader(cssText), false)
 	if err != nil {
-		return nil, nil, fmt.Errorf("douceur parse: %w", err)
+		return nil, nil, fmt.Errorf("parse: %w", err)
 	}
-	processRule := func(m map[*html.Node][]*css.Rule, r *css.Rule) (err error) {
+	processRule := func(m map[*html.Node][]Rule, r Rule) (err error) {
 		for _, sel := range r.Selectors {
-			if sel.Value == ":root" {
+			if sel.Val == ":root" {
 				for _, d := range r.Declarations {
-					rVars[d.Property] = d.Value
+					rVars[d.Prop] = d.Val
 				}
 			}
-			cs, err := compile(sel.Value)
+			cs, err := compile(sel.Val)
 			if err != nil {
-				log.Printf("cssSel compile %v: %v", sel.Value, err)
+				log.Printf("cssSel compile %v: %v", sel.Val, err)
 				continue
 			}
 			for _, el := range cascadia.QueryAll(doc, cs) {
 				existing, ok := m[el]
 				if !ok {
-					existing = make([]*css.Rule, 0, 3)
+					existing = make([]Rule, 0, 3)
 				}
 				existing = append(existing, r)
 				m[el] = existing
@@ -233,13 +222,13 @@
 }
 
 type Map struct {
-	Declarations map[string]css.Declaration
+	Declarations map[string]Declaration
 	DomTree      `json:"-"`
 }
 
 func NewMap(n *html.Node) Map {
 	s := Map{
-		Declarations: make(map[string]css.Declaration),
+		Declarations: make(map[string]Declaration),
 	}
 
 	for _, a := range n.Attr {
@@ -248,8 +237,13 @@
 			if !strings.HasSuffix(v, ";") {
 				v += ";"
 			}
-			decls, err := parser.ParseDeclarations(v)
+			st, err := Parse(strings.NewReader(v), true)
 
+			var decls []Declaration
+			if len(st.Rules) > 0 {
+				decls = st.Rules[0].Declarations
+			}
+
 			if err != nil {
 				log.Printf("could not parse '%v'", a.Val)
 				break
@@ -256,7 +250,7 @@
 			}
 
 			for _, d := range decls {
-				s.Declarations[d.Property] = *d
+				s.Declarations[d.Prop] = d
 			}
 		} else if a.Key == "height" || a.Key == "width" {
 			v := a.Val
@@ -265,14 +259,14 @@
 				v += "px"
 			}
 
-			s.Declarations[a.Key] = css.Declaration{
-				Property: a.Key,
-				Value:    v,
+			s.Declarations[a.Key] = Declaration{
+				Prop: a.Key,
+				Val:  v,
 			}
 		} else if a.Key == "bgcolor" {
-			s.Declarations["background-color"] = css.Declaration{
-				Property: "background-color",
-				Value:    a.Val,
+			s.Declarations["background-color"] = Declaration{
+				Prop: "background-color",
+				Val:  a.Val,
 			}
 		}
 	}
@@ -281,7 +275,7 @@
 }
 
 func (cs Map) ApplyChildStyle(ccs Map, copyAll bool) (res Map) {
-	res.Declarations = make(map[string]css.Declaration)
+	res.Declarations = make(map[string]Declaration)
 
 	for k, v := range cs.Declarations {
 		switch k {
@@ -296,7 +290,7 @@
 	}
 	// overwrite with higher prio child props
 	for k, v := range ccs.Declarations {
-		if v.Value == "inherit" {
+		if v.Val == "inherit" {
 			continue
 		}
 		res.Declarations[k] = v
@@ -361,21 +355,21 @@
 
 func (cs Map) FontSize() float64 {
 	fs, ok := cs.Declarations["font-size"]
-	if !ok || fs.Value == "" {
+	if !ok || fs.Val == "" {
 		return FontBaseSize
 	}
 
-	if len(fs.Value) <= 2 {
-		log.Printf("error parsing font size %v", fs.Value)
+	if len(fs.Val) <= 2 {
+		log.Printf("error parsing font size %v", fs.Val)
 		return FontBaseSize
 	}
-	numStr := fs.Value[0 : len(fs.Value)-2]
+	numStr := fs.Val[0 : len(fs.Val)-2]
 	f, err := strconv.ParseFloat(numStr, 64)
 	if err != nil {
-		log.Printf("error parsing font size %v", fs.Value)
+		log.Printf("error parsing font size %v", fs.Val)
 		return FontBaseSize
 	}
-	if strings.HasSuffix(fs.Value, "em") {
+	if strings.HasSuffix(fs.Val, "em") {
 		f *= FontBaseSize
 	}
 	return f
@@ -388,7 +382,7 @@
 
 func (cs Map) Color() draw.Color {
 	if d, ok := cs.Declarations["color"]; ok {
-		if h, ok := colorHex(d.Value); ok {
+		if h, ok := colorHex(d.Val); ok {
 			c := draw.Color(h)
 			return c
 		}
@@ -486,13 +480,13 @@
 
 func (cs Map) IsInline() bool {
 	propVal, ok := cs.Declarations["float"]
-	if ok && propVal.Value == "left" {
+	if ok && propVal.Val == "left" {
 		return true
 	}
 	propVal, ok = cs.Declarations["display"]
 	if ok {
-		return propVal.Value == "inline" ||
-			propVal.Value == "inline-block"
+		return propVal.Val == "inline" ||
+			propVal.Val == "inline-block"
 	}
 	return false
 }
@@ -499,21 +493,21 @@
 
 func (cs Map) IsDisplayNone() bool {
 	propVal, ok := cs.Declarations["display"]
-	if ok && propVal.Value == "none" {
+	if ok && propVal.Val == "none" {
 		return true
 	}
 	/*propVal, ok = cs.Declarations["position"]
-	if ok && propVal.Value == "fixed" {
+	if ok && propVal.Val == "fixed" {
 		return true
 	}*/
 	propVal, ok = cs.Declarations["clip"]
-	if ok && strings.ReplaceAll(propVal.Value, " ", "") == "rect(1px,1px,1px,1px)" {
+	if ok && strings.ReplaceAll(propVal.Val, " ", "") == "rect(1px,1px,1px,1px)" {
 		return true
 	}
 	propVal, ok = cs.Declarations["width"]
-	if ok && propVal.Value == "1px" {
+	if ok && propVal.Val == "1px" {
 		propVal, ok = cs.Declarations["height"]
-		if ok && propVal.Value == "1px" {
+		if ok && propVal.Val == "1px" {
 			return true
 		}
 	}
@@ -523,7 +517,7 @@
 func (cs Map) IsFlex() bool {
 	propVal, ok := cs.Declarations["display"]
 	if ok {
-		return propVal.Value == "flex"
+		return propVal.Val == "flex"
 	}
 	return false
 }
@@ -531,7 +525,7 @@
 func (cs Map) IsFlexDirectionRow() bool {
 	propVal, ok := cs.Declarations["flex-direction"]
 	if ok {
-		switch propVal.Value {
+		switch propVal.Val {
 		case "row":
 			return true
 		case "column":
@@ -545,7 +539,7 @@
 // margin-top, ...-right, ...-bottom, ...-left.
 func (cs *Map) Tlbr(key string) (s duit.Space, err error) {
 	if all, ok := cs.Declarations[key]; ok {
-		parts := strings.Split(all.Value, " ")
+		parts := strings.Split(all.Val, " ")
 		nums := make([]int, len(parts))
 		for i, p := range parts {
 			if f, _, err := length(cs, p); err == nil {
@@ -702,7 +696,7 @@
 func (cs *Map) Height() int {
 	d, ok := cs.Declarations["height"]
 	if ok {
-		f, _, err := length(cs, d.Value)
+		f, _, err := length(cs, d.Val)
 		if err != nil {
 			log.Errorf("cannot parse height: %v", err)
 		}
@@ -715,7 +709,7 @@
 	w := cs.width()
 	if w > 0 {
 		if d, ok := cs.Declarations["max-width"]; ok {
-			f, _, err := length(&cs, d.Value)
+			f, _, err := length(&cs, d.Val)
 			if err != nil {
 				log.Errorf("cannot parse width: %v", err)
 			}
@@ -730,7 +724,7 @@
 func (cs Map) width() int {
 	d, ok := cs.Declarations["width"]
 	if ok {
-		f, _, err := length(&cs, d.Value)
+		f, _, err := length(&cs, d.Val)
 		if err != nil {
 			log.Errorf("cannot parse width: %v", err)
 		}
@@ -762,7 +756,7 @@
 	if !ok {
 		return ""
 	}
-	return d.Value
+	return d.Val
 }
 
 func (cs *Map) CssPx(propName string) (l int, err error) {
@@ -770,7 +764,7 @@
 	if !ok {
 		return 0, fmt.Errorf("property doesn't exist")
 	}
-	f, _, err := length(cs, d.Value)
+	f, _, err := length(cs, d.Val)
 	if err != nil {
 		return 0, err
 	}
@@ -779,8 +773,8 @@
 }
 
 func (cs Map) SetCss(k, v string) {
-	cs.Declarations[k] = css.Declaration{
-		Property: k,
-		Value:    v,
+	cs.Declarations[k] = Declaration{
+		Prop: k,
+		Val:  v,
 	}
 }
--- a/style/stylesheets_test.go
+++ b/style/stylesheets_test.go
@@ -1,7 +1,6 @@
 package style
 
 import (
-	"github.com/chris-ramon/douceur/css"
 	"github.com/mjl-/duit"
 	"github.com/psilva261/opossum/logger"
 	"golang.org/x/net/html"
@@ -103,10 +102,10 @@
 
 		if w == 400 {
 			_ = m[body][0]
-			if v := m[body][0].Declarations[0].Value; v != "lightblue" {
+			if v := m[body][0].Declarations[0].Val; v != "lightblue" {
 				t.Fatalf("%v", v)
 			}
-			t.Logf("%v", m[body][0].Name)
+			t.Logf("%v", m[body][0])
 		} else {
 			if _, ok := m[body]; ok {
 				t.Fatalf("body ok")
@@ -197,7 +196,7 @@
 		h2 := grep(doc, "h2")
 		m := NewMap(h2)
 
-		if m.Declarations["color"].Value != "green" {
+		if m.Declarations["color"].Val != "green" {
 			t.Errorf("%+v", m)
 		}
 	}
@@ -220,10 +219,10 @@
 }
 
 func TestSmaller(t *testing.T) {
-	d := css.Declaration{
+	d := Declaration{
 		Important: false,
 	}
-	dd := css.Declaration{
+	dd := Declaration{
 		Important: true,
 	}
 	if !smaller(d, dd) {
@@ -233,18 +232,18 @@
 
 func TestApplyChildStyleInherit(t *testing.T) {
 	parent := Map{
-		Declarations: make(map[string]css.Declaration),
+		Declarations: make(map[string]Declaration),
 	}
-	parent.Declarations["height"] = css.Declaration{
-		Property: "height",
-		Value:    "80px",
+	parent.Declarations["height"] = Declaration{
+		Prop: "height",
+		Val:  "80px",
 	}
 	child := Map{
-		Declarations: make(map[string]css.Declaration),
+		Declarations: make(map[string]Declaration),
 	}
 
 	res := parent.ApplyChildStyle(child, true)
-	if v := res.Declarations["height"].Value; v != "80px" {
+	if v := res.Declarations["height"].Val; v != "80px" {
 		t.Fatalf(v)
 	}
 }
@@ -251,22 +250,22 @@
 
 func TestApplyChildStyleInherit2(t *testing.T) {
 	parent := Map{
-		Declarations: make(map[string]css.Declaration),
+		Declarations: make(map[string]Declaration),
 	}
 	child := Map{
-		Declarations: make(map[string]css.Declaration),
+		Declarations: make(map[string]Declaration),
 	}
-	parent.Declarations["font-size"] = css.Declaration{
-		Property: "font-size",
-		Value:    "12pt",
+	parent.Declarations["font-size"] = Declaration{
+		Prop: "font-size",
+		Val:  "12pt",
 	}
-	child.Declarations["font-size"] = css.Declaration{
-		Property: "font-size",
-		Value:    "inherit",
+	child.Declarations["font-size"] = Declaration{
+		Prop: "font-size",
+		Val:  "inherit",
 	}
 
 	res := parent.ApplyChildStyle(child, true)
-	if v := res.Declarations["font-size"].Value; v != "12pt" {
+	if v := res.Declarations["font-size"].Val; v != "12pt" {
 		t.Fatalf(v)
 	}
 }
@@ -339,11 +338,11 @@
 	}
 	for v, exp := range cases {
 		m := Map{
-			Declarations: make(map[string]css.Declaration),
+			Declarations: make(map[string]Declaration),
 		}
-		m.Declarations["margin"] = css.Declaration{
-			Property: "margin",
-			Value:    v,
+		m.Declarations["margin"] = Declaration{
+			Prop: "margin",
+			Val:  v,
 		}
 		s, err := m.Tlbr("margin")
 		if err != nil {
@@ -403,7 +402,7 @@
 	}
 	d := nm[b]
 	t.Logf("d=%+v", d)
-	if d.Declarations["color"].Value != "red" {
-		t.Fail()
+	if d.Declarations["color"].Val != "red" {
+		t.Fatalf("%+v", d.Declarations)
 	}
 }