aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/andybalholm/cascadia/selector.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/andybalholm/cascadia/selector.go')
-rw-r--r--vendor/github.com/andybalholm/cascadia/selector.go988
1 files changed, 336 insertions, 652 deletions
diff --git a/vendor/github.com/andybalholm/cascadia/selector.go b/vendor/github.com/andybalholm/cascadia/selector.go
index e2a6dc4..9fb05cc 100644
--- a/vendor/github.com/andybalholm/cascadia/selector.go
+++ b/vendor/github.com/andybalholm/cascadia/selector.go
@@ -9,60 +9,36 @@ import (
"golang.org/x/net/html"
)
-// Matcher is the interface for basic selector functionality.
-// Match returns whether a selector matches n.
-type Matcher interface {
- Match(n *html.Node) bool
-}
-
-// Sel is the interface for all the functionality provided by selectors.
-type Sel interface {
- Matcher
- Specificity() Specificity
-
- // Returns a CSS input compiling to this selector.
- String() string
+// the Selector type, and functions for creating them
- // Returns a pseudo-element, or an empty string.
- PseudoElement() string
-}
-
-// Parse parses a selector. Use `ParseWithPseudoElement`
-// if you need support for pseudo-elements.
-func Parse(sel string) (Sel, error) {
- p := &parser{s: sel}
- compiled, err := p.parseSelector()
- if err != nil {
- return nil, err
- }
+// A Selector is a function which tells whether a node matches or not.
+type Selector func(*html.Node) bool
- if p.i < len(sel) {
- return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
+// hasChildMatch returns whether n has any child that matches a.
+func hasChildMatch(n *html.Node, a Selector) bool {
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ if a(c) {
+ return true
+ }
}
-
- return compiled, nil
+ return false
}
-// ParseWithPseudoElement parses a single selector,
-// with support for pseudo-element.
-func ParseWithPseudoElement(sel string) (Sel, error) {
- p := &parser{s: sel, acceptPseudoElements: true}
- compiled, err := p.parseSelector()
- if err != nil {
- return nil, err
- }
-
- if p.i < len(sel) {
- return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
+// hasDescendantMatch performs a depth-first search of n's descendants,
+// testing whether any of them match a. It returns true as soon as a match is
+// found, or false if no match is found.
+func hasDescendantMatch(n *html.Node, a Selector) bool {
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ if a(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) {
+ return true
+ }
}
-
- return compiled, nil
+ return false
}
-// ParseGroup parses a selector, or a group of selectors separated by commas.
-// Use `ParseGroupWithPseudoElements`
-// if you need support for pseudo-elements.
-func ParseGroup(sel string) (SelectorGroup, error) {
+// Compile parses a selector and returns, if successful, a Selector object
+// that can be used to match against html.Node objects.
+func Compile(sel string) (Selector, error) {
p := &parser{s: sel}
compiled, err := p.parseSelectorGroup()
if err != nil {
@@ -76,39 +52,6 @@ func ParseGroup(sel string) (SelectorGroup, error) {
return compiled, nil
}
-// ParseGroupWithPseudoElements parses a selector, or a group of selectors separated by commas.
-// It supports pseudo-elements.
-func ParseGroupWithPseudoElements(sel string) (SelectorGroup, error) {
- p := &parser{s: sel, acceptPseudoElements: true}
- compiled, err := p.parseSelectorGroup()
- if err != nil {
- return nil, err
- }
-
- if p.i < len(sel) {
- return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
- }
-
- return compiled, nil
-}
-
-// A Selector is a function which tells whether a node matches or not.
-//
-// This type is maintained for compatibility; I recommend using the newer and
-// more idiomatic interfaces Sel and Matcher.
-type Selector func(*html.Node) bool
-
-// Compile parses a selector and returns, if successful, a Selector object
-// that can be used to match against html.Node objects.
-func Compile(sel string) (Selector, error) {
- compiled, err := ParseGroup(sel)
- if err != nil {
- return nil, err
- }
-
- return Selector(compiled.Match), nil
-}
-
// MustCompile is like Compile, but panics instead of returning an error.
func MustCompile(sel string) Selector {
compiled, err := Compile(sel)
@@ -136,23 +79,6 @@ func (s Selector) matchAllInto(n *html.Node, storage []*html.Node) []*html.Node
return storage
}
-func queryInto(n *html.Node, m Matcher, storage []*html.Node) []*html.Node {
- for child := n.FirstChild; child != nil; child = child.NextSibling {
- if m.Match(child) {
- storage = append(storage, child)
- }
- storage = queryInto(child, m, storage)
- }
-
- return storage
-}
-
-// QueryAll returns a slice of all the nodes that match m, from the descendants
-// of n.
-func QueryAll(n *html.Node, m Matcher) []*html.Node {
- return queryInto(n, m, nil)
-}
-
// Match returns true if the node matches the selector.
func (s Selector) Match(n *html.Node) bool {
return s(n)
@@ -173,21 +99,6 @@ func (s Selector) MatchFirst(n *html.Node) *html.Node {
return nil
}
-// Query returns the first node that matches m, from the descendants of n.
-// If none matches, it returns nil.
-func Query(n *html.Node, m Matcher) *html.Node {
- for c := n.FirstChild; c != nil; c = c.NextSibling {
- if m.Match(c) {
- return c
- }
- if matched := Query(c, m); matched != nil {
- return matched
- }
- }
-
- return nil
-}
-
// Filter returns the nodes in nodes that match the selector.
func (s Selector) Filter(nodes []*html.Node) (result []*html.Node) {
for _, n := range nodes {
@@ -198,148 +109,106 @@ func (s Selector) Filter(nodes []*html.Node) (result []*html.Node) {
return result
}
-// Filter returns the nodes that match m.
-func Filter(nodes []*html.Node, m Matcher) (result []*html.Node) {
- for _, n := range nodes {
- if m.Match(n) {
- result = append(result, n)
- }
+// typeSelector returns a Selector that matches elements with a given tag name.
+func typeSelector(tag string) Selector {
+ tag = toLowerASCII(tag)
+ return func(n *html.Node) bool {
+ return n.Type == html.ElementNode && n.Data == tag
}
- return result
}
-type tagSelector struct {
- tag string
-}
-
-// Matches elements with a given tag name.
-func (t tagSelector) Match(n *html.Node) bool {
- return n.Type == html.ElementNode && n.Data == t.tag
-}
-
-func (c tagSelector) Specificity() Specificity {
- return Specificity{0, 0, 1}
-}
-
-func (c tagSelector) PseudoElement() string {
- return ""
-}
-
-type classSelector struct {
- class string
-}
-
-// Matches elements by class attribute.
-func (t classSelector) Match(n *html.Node) bool {
- return matchAttribute(n, "class", func(s string) bool {
- return matchInclude(t.class, s)
- })
-}
-
-func (c classSelector) Specificity() Specificity {
- return Specificity{0, 1, 0}
-}
-
-func (c classSelector) PseudoElement() string {
- return ""
-}
-
-type idSelector struct {
- id string
-}
-
-// Matches elements by id attribute.
-func (t idSelector) Match(n *html.Node) bool {
- return matchAttribute(n, "id", func(s string) bool {
- return s == t.id
- })
-}
+// toLowerASCII returns s with all ASCII capital letters lowercased.
+func toLowerASCII(s string) string {
+ var b []byte
+ for i := 0; i < len(s); i++ {
+ if c := s[i]; 'A' <= c && c <= 'Z' {
+ if b == nil {
+ b = make([]byte, len(s))
+ copy(b, s)
+ }
+ b[i] = s[i] + ('a' - 'A')
+ }
+ }
-func (c idSelector) Specificity() Specificity {
- return Specificity{1, 0, 0}
-}
+ if b == nil {
+ return s
+ }
-func (c idSelector) PseudoElement() string {
- return ""
+ return string(b)
}
-type attrSelector struct {
- key, val, operation string
- regexp *regexp.Regexp
+// attributeSelector returns a Selector that matches elements
+// where the attribute named key satisifes the function f.
+func attributeSelector(key string, f func(string) bool) Selector {
+ key = toLowerASCII(key)
+ return func(n *html.Node) bool {
+ if n.Type != html.ElementNode {
+ return false
+ }
+ for _, a := range n.Attr {
+ if a.Key == key && f(a.Val) {
+ return true
+ }
+ }
+ return false
+ }
}
-// Matches elements by attribute value.
-func (t attrSelector) Match(n *html.Node) bool {
- switch t.operation {
- case "":
- return matchAttribute(n, t.key, func(string) bool { return true })
- case "=":
- return matchAttribute(n, t.key, func(s string) bool { return s == t.val })
- case "!=":
- return attributeNotEqualMatch(t.key, t.val, n)
- case "~=":
- // matches elements where the attribute named key is a whitespace-separated list that includes val.
- return matchAttribute(n, t.key, func(s string) bool { return matchInclude(t.val, s) })
- case "|=":
- return attributeDashMatch(t.key, t.val, n)
- case "^=":
- return attributePrefixMatch(t.key, t.val, n)
- case "$=":
- return attributeSuffixMatch(t.key, t.val, n)
- case "*=":
- return attributeSubstringMatch(t.key, t.val, n)
- case "#=":
- return attributeRegexMatch(t.key, t.regexp, n)
- default:
- panic(fmt.Sprintf("unsuported operation : %s", t.operation))
- }
+// attributeExistsSelector returns a Selector that matches elements that have
+// an attribute named key.
+func attributeExistsSelector(key string) Selector {
+ return attributeSelector(key, func(string) bool { return true })
}
-// matches elements where the attribute named key satisifes the function f.
-func matchAttribute(n *html.Node, key string, f func(string) bool) bool {
- if n.Type != html.ElementNode {
- return false
- }
- for _, a := range n.Attr {
- if a.Key == key && f(a.Val) {
- return true
- }
- }
- return false
+// attributeEqualsSelector returns a Selector that matches elements where
+// the attribute named key has the value val.
+func attributeEqualsSelector(key, val string) Selector {
+ return attributeSelector(key,
+ func(s string) bool {
+ return s == val
+ })
}
-// attributeNotEqualMatch matches elements where
+// attributeNotEqualSelector returns a Selector that matches elements where
// the attribute named key does not have the value val.
-func attributeNotEqualMatch(key, val string, n *html.Node) bool {
- if n.Type != html.ElementNode {
- return false
- }
- for _, a := range n.Attr {
- if a.Key == key && a.Val == val {
+func attributeNotEqualSelector(key, val string) Selector {
+ key = toLowerASCII(key)
+ return func(n *html.Node) bool {
+ if n.Type != html.ElementNode {
return false
}
+ for _, a := range n.Attr {
+ if a.Key == key && a.Val == val {
+ return false
+ }
+ }
+ return true
}
- return true
}
-// returns true if s is a whitespace-separated list that includes val.
-func matchInclude(val, s string) bool {
- for s != "" {
- i := strings.IndexAny(s, " \t\r\n\f")
- if i == -1 {
- return s == val
- }
- if s[:i] == val {
- return true
- }
- s = s[i+1:]
- }
- return false
+// attributeIncludesSelector returns a Selector that matches elements where
+// the attribute named key is a whitespace-separated list that includes val.
+func attributeIncludesSelector(key, val string) Selector {
+ return attributeSelector(key,
+ func(s string) bool {
+ for s != "" {
+ i := strings.IndexAny(s, " \t\r\n\f")
+ if i == -1 {
+ return s == val
+ }
+ if s[:i] == val {
+ return true
+ }
+ s = s[i+1:]
+ }
+ return false
+ })
}
-// matches elements where the attribute named key equals val or starts with val plus a hyphen.
-func attributeDashMatch(key, val string, n *html.Node) bool {
- return matchAttribute(n, key,
+// attributeDashmatchSelector returns a Selector that matches elements where
+// the attribute named key equals val or starts with val plus a hyphen.
+func attributeDashmatchSelector(key, val string) Selector {
+ return attributeSelector(key,
func(s string) bool {
if s == val {
return true
@@ -354,10 +223,10 @@ func attributeDashMatch(key, val string, n *html.Node) bool {
})
}
-// attributePrefixMatch returns a Selector that matches elements where
+// attributePrefixSelector returns a Selector that matches elements where
// the attribute named key starts with val.
-func attributePrefixMatch(key, val string, n *html.Node) bool {
- return matchAttribute(n, key,
+func attributePrefixSelector(key, val string) Selector {
+ return attributeSelector(key,
func(s string) bool {
if strings.TrimSpace(s) == "" {
return false
@@ -366,10 +235,10 @@ func attributePrefixMatch(key, val string, n *html.Node) bool {
})
}
-// attributeSuffixMatch matches elements where
+// attributeSuffixSelector returns a Selector that matches elements where
// the attribute named key ends with val.
-func attributeSuffixMatch(key, val string, n *html.Node) bool {
- return matchAttribute(n, key,
+func attributeSuffixSelector(key, val string) Selector {
+ return attributeSelector(key,
func(s string) bool {
if strings.TrimSpace(s) == "" {
return false
@@ -378,10 +247,10 @@ func attributeSuffixMatch(key, val string, n *html.Node) bool {
})
}
-// attributeSubstringMatch matches nodes where
+// attributeSubstringSelector returns a Selector that matches nodes where
// the attribute named key contains val.
-func attributeSubstringMatch(key, val string, n *html.Node) bool {
- return matchAttribute(n, key,
+func attributeSubstringSelector(key, val string) Selector {
+ return attributeSelector(key,
func(s string) bool {
if strings.TrimSpace(s) == "" {
return false
@@ -390,130 +259,39 @@ func attributeSubstringMatch(key, val string, n *html.Node) bool {
})
}
-// attributeRegexMatch matches nodes where
+// attributeRegexSelector returns a Selector that matches nodes where
// the attribute named key matches the regular expression rx
-func attributeRegexMatch(key string, rx *regexp.Regexp, n *html.Node) bool {
- return matchAttribute(n, key,
+func attributeRegexSelector(key string, rx *regexp.Regexp) Selector {
+ return attributeSelector(key,
func(s string) bool {
return rx.MatchString(s)
})
}
-func (c attrSelector) Specificity() Specificity {
- return Specificity{0, 1, 0}
-}
-
-func (c attrSelector) PseudoElement() string {
- return ""
-}
-
-// ---------------- Pseudo class selectors ----------------
-// we use severals concrete types of pseudo-class selectors
-
-type relativePseudoClassSelector struct {
- name string // one of "not", "has", "haschild"
- match SelectorGroup
-}
-
-func (s relativePseudoClassSelector) Match(n *html.Node) bool {
- if n.Type != html.ElementNode {
- return false
- }
- switch s.name {
- case "not":
- // matches elements that do not match a.
- return !s.match.Match(n)
- case "has":
- // matches elements with any descendant that matches a.
- return hasDescendantMatch(n, s.match)
- case "haschild":
- // matches elements with a child that matches a.
- return hasChildMatch(n, s.match)
- default:
- panic(fmt.Sprintf("unsupported relative pseudo class selector : %s", s.name))
+// intersectionSelector returns a selector that matches nodes that match
+// both a and b.
+func intersectionSelector(a, b Selector) Selector {
+ return func(n *html.Node) bool {
+ return a(n) && b(n)
}
}
-// hasChildMatch returns whether n has any child that matches a.
-func hasChildMatch(n *html.Node, a Matcher) bool {
- for c := n.FirstChild; c != nil; c = c.NextSibling {
- if a.Match(c) {
- return true
- }
- }
- return false
-}
-
-// hasDescendantMatch performs a depth-first search of n's descendants,
-// testing whether any of them match a. It returns true as soon as a match is
-// found, or false if no match is found.
-func hasDescendantMatch(n *html.Node, a Matcher) bool {
- for c := n.FirstChild; c != nil; c = c.NextSibling {
- if a.Match(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) {
- return true
- }
+// unionSelector returns a selector that matches elements that match
+// either a or b.
+func unionSelector(a, b Selector) Selector {
+ return func(n *html.Node) bool {
+ return a(n) || b(n)
}
- return false
}
-// Specificity returns the specificity of the most specific selectors
-// in the pseudo-class arguments.
-// See https://www.w3.org/TR/selectors/#specificity-rules
-func (s relativePseudoClassSelector) Specificity() Specificity {
- var max Specificity
- for _, sel := range s.match {
- newSpe := sel.Specificity()
- if max.Less(newSpe) {
- max = newSpe
+// negatedSelector returns a selector that matches elements that do not match a.
+func negatedSelector(a Selector) Selector {
+ return func(n *html.Node) bool {
+ if n.Type != html.ElementNode {
+ return false
}
+ return !a(n)
}
- return max
-}
-
-func (c relativePseudoClassSelector) PseudoElement() string {
- return ""
-}
-
-type containsPseudoClassSelector struct {
- own bool
- value string
-}
-
-func (s containsPseudoClassSelector) Match(n *html.Node) bool {
- var text string
- if s.own {
- // matches nodes that directly contain the given text
- text = strings.ToLower(nodeOwnText(n))
- } else {
- // matches nodes that contain the given text.
- text = strings.ToLower(nodeText(n))
- }
- return strings.Contains(text, s.value)
-}
-
-func (s containsPseudoClassSelector) Specificity() Specificity {
- return Specificity{0, 1, 0}
-}
-
-func (c containsPseudoClassSelector) PseudoElement() string {
- return ""
-}
-
-type regexpPseudoClassSelector struct {
- own bool
- regexp *regexp.Regexp
-}
-
-func (s regexpPseudoClassSelector) Match(n *html.Node) bool {
- var text string
- if s.own {
- // matches nodes whose text directly matches the specified regular expression
- text = nodeOwnText(n)
- } else {
- // matches nodes whose text matches the specified regular expression
- text = nodeText(n)
- }
- return s.regexp.MatchString(text)
}
// writeNodeText writes the text contained in n and its descendants to b.
@@ -547,214 +325,221 @@ func nodeOwnText(n *html.Node) string {
return b.String()
}
-func (s regexpPseudoClassSelector) Specificity() Specificity {
- return Specificity{0, 1, 0}
-}
-
-func (c regexpPseudoClassSelector) PseudoElement() string {
- return ""
-}
-
-type nthPseudoClassSelector struct {
- a, b int
- last, ofType bool
-}
-
-func (s nthPseudoClassSelector) Match(n *html.Node) bool {
- if s.a == 0 {
- if s.last {
- return simpleNthLastChildMatch(s.b, s.ofType, n)
- } else {
- return simpleNthChildMatch(s.b, s.ofType, n)
- }
+// textSubstrSelector returns a selector that matches nodes that
+// contain the given text.
+func textSubstrSelector(val string) Selector {
+ return func(n *html.Node) bool {
+ text := strings.ToLower(nodeText(n))
+ return strings.Contains(text, val)
}
- return nthChildMatch(s.a, s.b, s.last, s.ofType, n)
}
-// nthChildMatch implements :nth-child(an+b).
-// If last is true, implements :nth-last-child instead.
-// If ofType is true, implements :nth-of-type instead.
-func nthChildMatch(a, b int, last, ofType bool, n *html.Node) bool {
- if n.Type != html.ElementNode {
- return false
+// ownTextSubstrSelector returns a selector that matches nodes that
+// directly contain the given text
+func ownTextSubstrSelector(val string) Selector {
+ return func(n *html.Node) bool {
+ text := strings.ToLower(nodeOwnText(n))
+ return strings.Contains(text, val)
}
+}
- parent := n.Parent
- if parent == nil {
- return false
+// textRegexSelector returns a selector that matches nodes whose text matches
+// the specified regular expression
+func textRegexSelector(rx *regexp.Regexp) Selector {
+ return func(n *html.Node) bool {
+ return rx.MatchString(nodeText(n))
}
+}
- if parent.Type == html.DocumentNode {
- return false
+// ownTextRegexSelector returns a selector that matches nodes whose text
+// directly matches the specified regular expression
+func ownTextRegexSelector(rx *regexp.Regexp) Selector {
+ return func(n *html.Node) bool {
+ return rx.MatchString(nodeOwnText(n))
}
+}
- i := -1
- count := 0
- for c := parent.FirstChild; c != nil; c = c.NextSibling {
- if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
- continue
- }
- count++
- if c == n {
- i = count
- if !last {
- break
- }
+// hasChildSelector returns a selector that matches elements
+// with a child that matches a.
+func hasChildSelector(a Selector) Selector {
+ return func(n *html.Node) bool {
+ if n.Type != html.ElementNode {
+ return false
}
+ return hasChildMatch(n, a)
}
+}
- if i == -1 {
- // This shouldn't happen, since n should always be one of its parent's children.
- return false
- }
-
- if last {
- i = count - i + 1
- }
-
- i -= b
- if a == 0 {
- return i == 0
+// hasDescendantSelector returns a selector that matches elements
+// with any descendant that matches a.
+func hasDescendantSelector(a Selector) Selector {
+ return func(n *html.Node) bool {
+ if n.Type != html.ElementNode {
+ return false
+ }
+ return hasDescendantMatch(n, a)
}
-
- return i%a == 0 && i/a >= 0
}
-// simpleNthChildMatch implements :nth-child(b).
+// nthChildSelector returns a selector that implements :nth-child(an+b).
+// If last is true, implements :nth-last-child instead.
// If ofType is true, implements :nth-of-type instead.
-func simpleNthChildMatch(b int, ofType bool, n *html.Node) bool {
- if n.Type != html.ElementNode {
- return false
- }
-
- parent := n.Parent
- if parent == nil {
- return false
- }
+func nthChildSelector(a, b int, last, ofType bool) Selector {
+ return func(n *html.Node) bool {
+ if n.Type != html.ElementNode {
+ return false
+ }
- if parent.Type == html.DocumentNode {
- return false
- }
+ parent := n.Parent
+ if parent == nil {
+ return false
+ }
- count := 0
- for c := parent.FirstChild; c != nil; c = c.NextSibling {
- if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
- continue
+ if parent.Type == html.DocumentNode {
+ return false
}
- count++
- if c == n {
- return count == b
+
+ i := -1
+ count := 0
+ for c := parent.FirstChild; c != nil; c = c.NextSibling {
+ if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
+ continue
+ }
+ count++
+ if c == n {
+ i = count
+ if !last {
+ break
+ }
+ }
}
- if count >= b {
+
+ if i == -1 {
+ // This shouldn't happen, since n should always be one of its parent's children.
return false
}
- }
- return false
-}
-// simpleNthLastChildMatch implements :nth-last-child(b).
-// If ofType is true, implements :nth-last-of-type instead.
-func simpleNthLastChildMatch(b int, ofType bool, n *html.Node) bool {
- if n.Type != html.ElementNode {
- return false
- }
+ if last {
+ i = count - i + 1
+ }
- parent := n.Parent
- if parent == nil {
- return false
- }
+ i -= b
+ if a == 0 {
+ return i == 0
+ }
- if parent.Type == html.DocumentNode {
- return false
+ return i%a == 0 && i/a >= 0
}
+}
- count := 0
- for c := parent.LastChild; c != nil; c = c.PrevSibling {
- if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
- continue
- }
- count++
- if c == n {
- return count == b
+// simpleNthChildSelector returns a selector that implements :nth-child(b).
+// If ofType is true, implements :nth-of-type instead.
+func simpleNthChildSelector(b int, ofType bool) Selector {
+ return func(n *html.Node) bool {
+ if n.Type != html.ElementNode {
+ return false
}
- if count >= b {
+
+ parent := n.Parent
+ if parent == nil {
return false
}
- }
- return false
-}
-// Specificity for nth-child pseudo-class.
-// Does not support a list of selectors
-func (s nthPseudoClassSelector) Specificity() Specificity {
- return Specificity{0, 1, 0}
-}
+ if parent.Type == html.DocumentNode {
+ return false
+ }
-func (c nthPseudoClassSelector) PseudoElement() string {
- return ""
+ count := 0
+ for c := parent.FirstChild; c != nil; c = c.NextSibling {
+ if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
+ continue
+ }
+ count++
+ if c == n {
+ return count == b
+ }
+ if count >= b {
+ return false
+ }
+ }
+ return false
+ }
}
-type onlyChildPseudoClassSelector struct {
- ofType bool
-}
+// simpleNthLastChildSelector returns a selector that implements
+// :nth-last-child(b). If ofType is true, implements :nth-last-of-type
+// instead.
+func simpleNthLastChildSelector(b int, ofType bool) Selector {
+ return func(n *html.Node) bool {
+ if n.Type != html.ElementNode {
+ return false
+ }
-// Match implements :only-child.
-// If `ofType` is true, it implements :only-of-type instead.
-func (s onlyChildPseudoClassSelector) Match(n *html.Node) bool {
- if n.Type != html.ElementNode {
- return false
- }
+ parent := n.Parent
+ if parent == nil {
+ return false
+ }
- parent := n.Parent
- if parent == nil {
- return false
- }
+ if parent.Type == html.DocumentNode {
+ return false
+ }
- if parent.Type == html.DocumentNode {
+ count := 0
+ for c := parent.LastChild; c != nil; c = c.PrevSibling {
+ if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
+ continue
+ }
+ count++
+ if c == n {
+ return count == b
+ }
+ if count >= b {
+ return false
+ }
+ }
return false
}
+}
- count := 0
- for c := parent.FirstChild; c != nil; c = c.NextSibling {
- if (c.Type != html.ElementNode) || (s.ofType && c.Data != n.Data) {
- continue
+// onlyChildSelector returns a selector that implements :only-child.
+// If ofType is true, it implements :only-of-type instead.
+func onlyChildSelector(ofType bool) Selector {
+ return func(n *html.Node) bool {
+ if n.Type != html.ElementNode {
+ return false
}
- count++
- if count > 1 {
+
+ parent := n.Parent
+ if parent == nil {
return false
}
- }
- return count == 1
-}
+ if parent.Type == html.DocumentNode {
+ return false
+ }
-func (s onlyChildPseudoClassSelector) Specificity() Specificity {
- return Specificity{0, 1, 0}
-}
+ count := 0
+ for c := parent.FirstChild; c != nil; c = c.NextSibling {
+ if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
+ continue
+ }
+ count++
+ if count > 1 {
+ return false
+ }
+ }
-func (c onlyChildPseudoClassSelector) PseudoElement() string {
- return ""
+ return count == 1
+ }
}
-type inputPseudoClassSelector struct{}
-
-// Matches input, select, textarea and button elements.
-func (s inputPseudoClassSelector) Match(n *html.Node) bool {
+// inputSelector is a Selector that matches input, select, textarea and button elements.
+func inputSelector(n *html.Node) bool {
return n.Type == html.ElementNode && (n.Data == "input" || n.Data == "select" || n.Data == "textarea" || n.Data == "button")
}
-func (s inputPseudoClassSelector) Specificity() Specificity {
- return Specificity{0, 1, 0}
-}
-
-func (c inputPseudoClassSelector) PseudoElement() string {
- return ""
-}
-
-type emptyElementPseudoClassSelector struct{}
-
-// Matches empty elements.
-func (s emptyElementPseudoClassSelector) Match(n *html.Node) bool {
+// emptyElementSelector is a Selector that matches empty elements.
+func emptyElementSelector(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
@@ -769,170 +554,69 @@ func (s emptyElementPseudoClassSelector) Match(n *html.Node) bool {
return true
}
-func (s emptyElementPseudoClassSelector) Specificity() Specificity {
- return Specificity{0, 1, 0}
-}
-
-func (c emptyElementPseudoClassSelector) PseudoElement() string {
- return ""
-}
-
-type rootPseudoClassSelector struct{}
-
-// Match implements :root
-func (s rootPseudoClassSelector) Match(n *html.Node) bool {
- if n.Type != html.ElementNode {
- return false
- }
- if n.Parent == nil {
- return false
- }
- return n.Parent.Type == html.DocumentNode
-}
-
-func (s rootPseudoClassSelector) Specificity() Specificity {
- return Specificity{0, 1, 0}
-}
-
-func (c rootPseudoClassSelector) PseudoElement() string {
- return ""
-}
-
-type compoundSelector struct {
- selectors []Sel
- pseudoElement string
-}
-
-// Matches elements if each sub-selectors matches.
-func (t compoundSelector) Match(n *html.Node) bool {
- if len(t.selectors) == 0 {
- return n.Type == html.ElementNode
- }
-
- for _, sel := range t.selectors {
- if !sel.Match(n) {
+// descendantSelector returns a Selector that matches an element if
+// it matches d and has an ancestor that matches a.
+func descendantSelector(a, d Selector) Selector {
+ return func(n *html.Node) bool {
+ if !d(n) {
return false
}
- }
- return true
-}
-func (s compoundSelector) Specificity() Specificity {
- var out Specificity
- for _, sel := range s.selectors {
- out = out.Add(sel.Specificity())
- }
- if s.pseudoElement != "" {
- // https://drafts.csswg.org/selectors-3/#specificity
- out = out.Add(Specificity{0, 0, 1})
- }
- return out
-}
-
-func (c compoundSelector) PseudoElement() string {
- return c.pseudoElement
-}
-
-type combinedSelector struct {
- first Sel
- combinator byte
- second Sel
-}
-
-func (t combinedSelector) Match(n *html.Node) bool {
- if t.first == nil {
- return false // maybe we should panic
- }
- switch t.combinator {
- case 0:
- return t.first.Match(n)
- case ' ':
- return descendantMatch(t.first, t.second, n)
- case '>':
- return childMatch(t.first, t.second, n)
- case '+':
- return siblingMatch(t.first, t.second, true, n)
- case '~':
- return siblingMatch(t.first, t.second, false, n)
- default:
- panic("unknown combinator")
- }
-}
+ for p := n.Parent; p != nil; p = p.Parent {
+ if a(p) {
+ return true
+ }
+ }
-// matches an element if it matches d and has an ancestor that matches a.
-func descendantMatch(a, d Matcher, n *html.Node) bool {
- if !d.Match(n) {
return false
}
-
- for p := n.Parent; p != nil; p = p.Parent {
- if a.Match(p) {
- return true
- }
- }
-
- return false
}
-// matches an element if it matches d and its parent matches a.
-func childMatch(a, d Matcher, n *html.Node) bool {
- return d.Match(n) && n.Parent != nil && a.Match(n.Parent)
+// childSelector returns a Selector that matches an element if
+// it matches d and its parent matches a.
+func childSelector(a, d Selector) Selector {
+ return func(n *html.Node) bool {
+ return d(n) && n.Parent != nil && a(n.Parent)
+ }
}
-// matches an element if it matches s2 and is preceded by an element that matches s1.
+// siblingSelector returns a Selector that matches an element
+// if it matches s2 and in is preceded by an element that matches s1.
// If adjacent is true, the sibling must be immediately before the element.
-func siblingMatch(s1, s2 Matcher, adjacent bool, n *html.Node) bool {
- if !s2.Match(n) {
- return false
- }
+func siblingSelector(s1, s2 Selector, adjacent bool) Selector {
+ return func(n *html.Node) bool {
+ if !s2(n) {
+ return false
+ }
- if adjacent {
- for n = n.PrevSibling; n != nil; n = n.PrevSibling {
- if n.Type == html.TextNode || n.Type == html.CommentNode {
- continue
+ if adjacent {
+ for n = n.PrevSibling; n != nil; n = n.PrevSibling {
+ if n.Type == html.TextNode || n.Type == html.CommentNode {
+ continue
+ }
+ return s1(n)
}
- return s1.Match(n)
+ return false
}
- return false
- }
- // Walk backwards looking for element that matches s1
- for c := n.PrevSibling; c != nil; c = c.PrevSibling {
- if s1.Match(c) {
- return true
+ // Walk backwards looking for element that matches s1
+ for c := n.PrevSibling; c != nil; c = c.PrevSibling {
+ if s1(c) {
+ return true
+ }
}
- }
-
- return false
-}
-func (s combinedSelector) Specificity() Specificity {
- spec := s.first.Specificity()
- if s.second != nil {
- spec = spec.Add(s.second.Specificity())
+ return false
}
- return spec
}
-// on combinedSelector, a pseudo-element only makes sens on the last
-// selector, although others increase specificity.
-func (c combinedSelector) PseudoElement() string {
- if c.second == nil {
- return ""
+// rootSelector implements :root
+func rootSelector(n *html.Node) bool {
+ if n.Type != html.ElementNode {
+ return false
}
- return c.second.PseudoElement()
-}
-
-// A SelectorGroup is a list of selectors, which matches if any of the
-// individual selectors matches.
-type SelectorGroup []Sel
-
-// Match returns true if the node matches one of the single selectors.
-func (s SelectorGroup) Match(n *html.Node) bool {
- for _, sel := range s {
- if sel.Match(n) {
- return true
- }
+ if n.Parent == nil {
+ return false
}
- return false
+ return n.Parent.Type == html.DocumentNode
}