aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/andybalholm/cascadia
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/andybalholm/cascadia')
-rw-r--r--vendor/github.com/andybalholm/cascadia/README.md2
-rw-r--r--vendor/github.com/andybalholm/cascadia/go.mod6
-rw-r--r--vendor/github.com/andybalholm/cascadia/parser.go321
-rw-r--r--vendor/github.com/andybalholm/cascadia/selector.go988
-rw-r--r--vendor/github.com/andybalholm/cascadia/serialize.go120
-rw-r--r--vendor/github.com/andybalholm/cascadia/specificity.go26
6 files changed, 966 insertions, 497 deletions
diff --git a/vendor/github.com/andybalholm/cascadia/README.md b/vendor/github.com/andybalholm/cascadia/README.md
index 9021cb9..26f4c37 100644
--- a/vendor/github.com/andybalholm/cascadia/README.md
+++ b/vendor/github.com/andybalholm/cascadia/README.md
@@ -5,3 +5,5 @@
The Cascadia package implements CSS selectors for use with the parse trees produced by the html package.
To test CSS selectors without writing Go code, check out [cascadia](https://github.com/suntong/cascadia) the command line tool, a thin wrapper around this package.
+
+[Refer to godoc here](https://godoc.org/github.com/andybalholm/cascadia).
diff --git a/vendor/github.com/andybalholm/cascadia/go.mod b/vendor/github.com/andybalholm/cascadia/go.mod
index e6febbb..51a330b 100644
--- a/vendor/github.com/andybalholm/cascadia/go.mod
+++ b/vendor/github.com/andybalholm/cascadia/go.mod
@@ -1,3 +1,5 @@
-module "github.com/andybalholm/cascadia"
+module github.com/andybalholm/cascadia
-require "golang.org/x/net" v0.0.0-20180218175443-cbe0f9307d01
+require golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01
+
+go 1.13
diff --git a/vendor/github.com/andybalholm/cascadia/parser.go b/vendor/github.com/andybalholm/cascadia/parser.go
index 495db9c..c40a39f 100644
--- a/vendor/github.com/andybalholm/cascadia/parser.go
+++ b/vendor/github.com/andybalholm/cascadia/parser.go
@@ -7,14 +7,16 @@ import (
"regexp"
"strconv"
"strings"
-
- "golang.org/x/net/html"
)
// a parser for CSS selectors
type parser struct {
s string // the source text
i int // the current position
+
+ // if `false`, parsing a pseudo-element
+ // returns an error.
+ acceptPseudoElements bool
}
// parseEscape parses a backslash escape.
@@ -31,7 +33,7 @@ func (p *parser) parseEscape() (result string, err error) {
case hexDigit(c):
// unicode escape (hex)
var i int
- for i = start; i < p.i+6 && i < len(p.s) && hexDigit(p.s[i]); i++ {
+ for i = start; i < start+6 && i < len(p.s) && hexDigit(p.s[i]); i++ {
// empty
}
v, _ := strconv.ParseUint(p.s[start:i], 16, 21)
@@ -56,6 +58,26 @@ func (p *parser) parseEscape() (result string, err error) {
return result, nil
}
+// 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')
+ }
+ }
+
+ if b == nil {
+ return s
+ }
+
+ return string(b)
+}
+
func hexDigit(c byte) bool {
return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
}
@@ -280,92 +302,92 @@ func (p *parser) consumeClosingParenthesis() bool {
}
// parseTypeSelector parses a type selector (one that matches by tag name).
-func (p *parser) parseTypeSelector() (result Selector, err error) {
+func (p *parser) parseTypeSelector() (result tagSelector, err error) {
tag, err := p.parseIdentifier()
if err != nil {
- return nil, err
+ return
}
-
- return typeSelector(tag), nil
+ return tagSelector{tag: toLowerASCII(tag)}, nil
}
// parseIDSelector parses a selector that matches by id attribute.
-func (p *parser) parseIDSelector() (Selector, error) {
+func (p *parser) parseIDSelector() (idSelector, error) {
if p.i >= len(p.s) {
- return nil, fmt.Errorf("expected id selector (#id), found EOF instead")
+ return idSelector{}, fmt.Errorf("expected id selector (#id), found EOF instead")
}
if p.s[p.i] != '#' {
- return nil, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i])
+ return idSelector{}, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i])
}
p.i++
id, err := p.parseName()
if err != nil {
- return nil, err
+ return idSelector{}, err
}
- return attributeEqualsSelector("id", id), nil
+ return idSelector{id: id}, nil
}
// parseClassSelector parses a selector that matches by class attribute.
-func (p *parser) parseClassSelector() (Selector, error) {
+func (p *parser) parseClassSelector() (classSelector, error) {
if p.i >= len(p.s) {
- return nil, fmt.Errorf("expected class selector (.class), found EOF instead")
+ return classSelector{}, fmt.Errorf("expected class selector (.class), found EOF instead")
}
if p.s[p.i] != '.' {
- return nil, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i])
+ return classSelector{}, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i])
}
p.i++
class, err := p.parseIdentifier()
if err != nil {
- return nil, err
+ return classSelector{}, err
}
- return attributeIncludesSelector("class", class), nil
+ return classSelector{class: class}, nil
}
// parseAttributeSelector parses a selector that matches by attribute value.
-func (p *parser) parseAttributeSelector() (Selector, error) {
+func (p *parser) parseAttributeSelector() (attrSelector, error) {
if p.i >= len(p.s) {
- return nil, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead")
+ return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead")
}
if p.s[p.i] != '[' {
- return nil, fmt.Errorf("expected attribute selector ([attribute]), found '%c' instead", p.s[p.i])
+ return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found '%c' instead", p.s[p.i])
}
p.i++
p.skipWhitespace()
key, err := p.parseIdentifier()
if err != nil {
- return nil, err
+ return attrSelector{}, err
}
+ key = toLowerASCII(key)
p.skipWhitespace()
if p.i >= len(p.s) {
- return nil, errors.New("unexpected EOF in attribute selector")
+ return attrSelector{}, errors.New("unexpected EOF in attribute selector")
}
if p.s[p.i] == ']' {
p.i++
- return attributeExistsSelector(key), nil
+ return attrSelector{key: key, operation: ""}, nil
}
if p.i+2 >= len(p.s) {
- return nil, errors.New("unexpected EOF in attribute selector")
+ return attrSelector{}, errors.New("unexpected EOF in attribute selector")
}
op := p.s[p.i : p.i+2]
if op[0] == '=' {
op = "="
} else if op[1] != '=' {
- return nil, fmt.Errorf(`expected equality operator, found "%s" instead`, op)
+ return attrSelector{}, fmt.Errorf(`expected equality operator, found "%s" instead`, op)
}
p.i += len(op)
p.skipWhitespace()
if p.i >= len(p.s) {
- return nil, errors.New("unexpected EOF in attribute selector")
+ return attrSelector{}, errors.New("unexpected EOF in attribute selector")
}
var val string
var rx *regexp.Regexp
@@ -380,88 +402,84 @@ func (p *parser) parseAttributeSelector() (Selector, error) {
}
}
if err != nil {
- return nil, err
+ return attrSelector{}, err
}
p.skipWhitespace()
if p.i >= len(p.s) {
- return nil, errors.New("unexpected EOF in attribute selector")
+ return attrSelector{}, errors.New("unexpected EOF in attribute selector")
}
if p.s[p.i] != ']' {
- return nil, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
+ return attrSelector{}, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
}
p.i++
switch op {
- case "=":
- return attributeEqualsSelector(key, val), nil
- case "!=":
- return attributeNotEqualSelector(key, val), nil
- case "~=":
- return attributeIncludesSelector(key, val), nil
- case "|=":
- return attributeDashmatchSelector(key, val), nil
- case "^=":
- return attributePrefixSelector(key, val), nil
- case "$=":
- return attributeSuffixSelector(key, val), nil
- case "*=":
- return attributeSubstringSelector(key, val), nil
- case "#=":
- return attributeRegexSelector(key, rx), nil
- }
-
- return nil, fmt.Errorf("attribute operator %q is not supported", op)
+ case "=", "!=", "~=", "|=", "^=", "$=", "*=", "#=":
+ return attrSelector{key: key, val: val, operation: op, regexp: rx}, nil
+ default:
+ return attrSelector{}, fmt.Errorf("attribute operator %q is not supported", op)
+ }
}
var errExpectedParenthesis = errors.New("expected '(' but didn't find it")
var errExpectedClosingParenthesis = errors.New("expected ')' but didn't find it")
var errUnmatchedParenthesis = errors.New("unmatched '('")
-// parsePseudoclassSelector parses a pseudoclass selector like :not(p).
-func (p *parser) parsePseudoclassSelector() (Selector, error) {
+// parsePseudoclassSelector parses a pseudoclass selector like :not(p) or a pseudo-element
+// For backwards compatibility, both ':' and '::' prefix are allowed for pseudo-elements.
+// https://drafts.csswg.org/selectors-3/#pseudo-elements
+// Returning a nil `Sel` (and a nil `error`) means we found a pseudo-element.
+func (p *parser) parsePseudoclassSelector() (out Sel, pseudoElement string, err error) {
if p.i >= len(p.s) {
- return nil, fmt.Errorf("expected pseudoclass selector (:pseudoclass), found EOF instead")
+ return nil, "", fmt.Errorf("expected pseudoclass selector (:pseudoclass), found EOF instead")
}
if p.s[p.i] != ':' {
- return nil, fmt.Errorf("expected attribute selector (:pseudoclass), found '%c' instead", p.s[p.i])
+ return nil, "", fmt.Errorf("expected attribute selector (:pseudoclass), found '%c' instead", p.s[p.i])
}
p.i++
+ var mustBePseudoElement bool
+ if p.i >= len(p.s) {
+ return nil, "", fmt.Errorf("got empty pseudoclass (or pseudoelement)")
+ }
+ if p.s[p.i] == ':' { // we found a pseudo-element
+ mustBePseudoElement = true
+ p.i++
+ }
+
name, err := p.parseIdentifier()
if err != nil {
- return nil, err
+ return
}
name = toLowerASCII(name)
+ if mustBePseudoElement && (name != "after" && name != "backdrop" && name != "before" &&
+ name != "cue" && name != "first-letter" && name != "first-line" && name != "grammar-error" &&
+ name != "marker" && name != "placeholder" && name != "selection" && name != "spelling-error") {
+ return out, "", fmt.Errorf("unknown pseudoelement :%s", name)
+ }
switch name {
case "not", "has", "haschild":
if !p.consumeParenthesis() {
- return nil, errExpectedParenthesis
+ return out, "", errExpectedParenthesis
}
sel, parseErr := p.parseSelectorGroup()
if parseErr != nil {
- return nil, parseErr
+ return out, "", parseErr
}
if !p.consumeClosingParenthesis() {
- return nil, errExpectedClosingParenthesis
+ return out, "", errExpectedClosingParenthesis
}
- switch name {
- case "not":
- return negatedSelector(sel), nil
- case "has":
- return hasDescendantSelector(sel), nil
- case "haschild":
- return hasChildSelector(sel), nil
- }
+ out = relativePseudoClassSelector{name: name, match: sel}
case "contains", "containsown":
if !p.consumeParenthesis() {
- return nil, errExpectedParenthesis
+ return out, "", errExpectedParenthesis
}
if p.i == len(p.s) {
- return nil, errUnmatchedParenthesis
+ return out, "", errUnmatchedParenthesis
}
var val string
switch p.s[p.i] {
@@ -471,95 +489,75 @@ func (p *parser) parsePseudoclassSelector() (Selector, error) {
val, err = p.parseIdentifier()
}
if err != nil {
- return nil, err
+ return out, "", err
}
val = strings.ToLower(val)
p.skipWhitespace()
if p.i >= len(p.s) {
- return nil, errors.New("unexpected EOF in pseudo selector")
+ return out, "", errors.New("unexpected EOF in pseudo selector")
}
if !p.consumeClosingParenthesis() {
- return nil, errExpectedClosingParenthesis
+ return out, "", errExpectedClosingParenthesis
}
- switch name {
- case "contains":
- return textSubstrSelector(val), nil
- case "containsown":
- return ownTextSubstrSelector(val), nil
- }
+ out = containsPseudoClassSelector{own: name == "containsown", value: val}
case "matches", "matchesown":
if !p.consumeParenthesis() {
- return nil, errExpectedParenthesis
+ return out, "", errExpectedParenthesis
}
rx, err := p.parseRegex()
if err != nil {
- return nil, err
+ return out, "", err
}
if p.i >= len(p.s) {
- return nil, errors.New("unexpected EOF in pseudo selector")
+ return out, "", errors.New("unexpected EOF in pseudo selector")
}
if !p.consumeClosingParenthesis() {
- return nil, errExpectedClosingParenthesis
+ return out, "", errExpectedClosingParenthesis
}
- switch name {
- case "matches":
- return textRegexSelector(rx), nil
- case "matchesown":
- return ownTextRegexSelector(rx), nil
- }
+ out = regexpPseudoClassSelector{own: name == "matchesown", regexp: rx}
case "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type":
if !p.consumeParenthesis() {
- return nil, errExpectedParenthesis
+ return out, "", errExpectedParenthesis
}
a, b, err := p.parseNth()
if err != nil {
- return nil, err
+ return out, "", err
}
if !p.consumeClosingParenthesis() {
- return nil, errExpectedClosingParenthesis
- }
- if a == 0 {
- switch name {
- case "nth-child":
- return simpleNthChildSelector(b, false), nil
- case "nth-of-type":
- return simpleNthChildSelector(b, true), nil
- case "nth-last-child":
- return simpleNthLastChildSelector(b, false), nil
- case "nth-last-of-type":
- return simpleNthLastChildSelector(b, true), nil
- }
+ return out, "", errExpectedClosingParenthesis
}
- return nthChildSelector(a, b,
- name == "nth-last-child" || name == "nth-last-of-type",
- name == "nth-of-type" || name == "nth-last-of-type"),
- nil
+ last := name == "nth-last-child" || name == "nth-last-of-type"
+ ofType := name == "nth-of-type" || name == "nth-last-of-type"
+ out = nthPseudoClassSelector{a: a, b: b, last: last, ofType: ofType}
case "first-child":
- return simpleNthChildSelector(1, false), nil
+ out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: false}
case "last-child":
- return simpleNthLastChildSelector(1, false), nil
+ out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: true}
case "first-of-type":
- return simpleNthChildSelector(1, true), nil
+ out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: false}
case "last-of-type":
- return simpleNthLastChildSelector(1, true), nil
+ out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: true}
case "only-child":
- return onlyChildSelector(false), nil
+ out = onlyChildPseudoClassSelector{ofType: false}
case "only-of-type":
- return onlyChildSelector(true), nil
+ out = onlyChildPseudoClassSelector{ofType: true}
case "input":
- return inputSelector, nil
+ out = inputPseudoClassSelector{}
case "empty":
- return emptyElementSelector, nil
+ out = emptyElementPseudoClassSelector{}
case "root":
- return rootSelector, nil
+ out = rootPseudoClassSelector{}
+ case "after", "backdrop", "before", "cue", "first-letter", "first-line", "grammar-error", "marker", "placeholder", "selection", "spelling-error":
+ return nil, name, nil
+ default:
+ return out, "", fmt.Errorf("unknown pseudoclass or pseudoelement :%s", name)
}
-
- return nil, fmt.Errorf("unknown pseudoclass :%s", name)
+ return
}
// parseInteger parses a decimal integer.
@@ -705,8 +703,8 @@ invalid:
// parseSimpleSelectorSequence parses a selector sequence that applies to
// a single element.
-func (p *parser) parseSimpleSelectorSequence() (Selector, error) {
- var result Selector
+func (p *parser) parseSimpleSelectorSequence() (Sel, error) {
+ var selectors []Sel
if p.i >= len(p.s) {
return nil, errors.New("expected selector, found EOF instead")
@@ -723,13 +721,17 @@ func (p *parser) parseSimpleSelectorSequence() (Selector, error) {
if err != nil {
return nil, err
}
- result = r
+ selectors = append(selectors, r)
}
+ var pseudoElement string
loop:
for p.i < len(p.s) {
- var ns Selector
- var err error
+ var (
+ ns Sel
+ newPseudoElement string
+ err error
+ )
switch p.s[p.i] {
case '#':
ns, err = p.parseIDSelector()
@@ -738,44 +740,57 @@ loop:
case '[':
ns, err = p.parseAttributeSelector()
case ':':
- ns, err = p.parsePseudoclassSelector()
+ ns, newPseudoElement, err = p.parsePseudoclassSelector()
default:
break loop
}
if err != nil {
return nil, err
}
- if result == nil {
- result = ns
+ // From https://drafts.csswg.org/selectors-3/#pseudo-elements :
+ // "Only one pseudo-element may appear per selector, and if present
+ // it must appear after the sequence of simple selectors that
+ // represents the subjects of the selector.""
+ if ns == nil { // we found a pseudo-element
+ if pseudoElement != "" {
+ return nil, fmt.Errorf("only one pseudo-element is accepted per selector, got %s and %s", pseudoElement, newPseudoElement)
+ }
+ if !p.acceptPseudoElements {
+ return nil, fmt.Errorf("pseudo-element %s found, but pseudo-elements support is disabled", newPseudoElement)
+ }
+ pseudoElement = newPseudoElement
} else {
- result = intersectionSelector(result, ns)
+ if pseudoElement != "" {
+ return nil, fmt.Errorf("pseudo-element %s must be at the end of selector", pseudoElement)
+ }
+ selectors = append(selectors, ns)
}
- }
- if result == nil {
- result = func(n *html.Node) bool {
- return n.Type == html.ElementNode
- }
}
-
- return result, nil
+ if len(selectors) == 1 && pseudoElement == "" { // no need wrap the selectors in compoundSelector
+ return selectors[0], nil
+ }
+ return compoundSelector{selectors: selectors, pseudoElement: pseudoElement}, nil
}
// parseSelector parses a selector that may include combinators.
-func (p *parser) parseSelector() (result Selector, err error) {
+func (p *parser) parseSelector() (Sel, error) {
p.skipWhitespace()
- result, err = p.parseSimpleSelectorSequence()
+ result, err := p.parseSimpleSelectorSequence()
if err != nil {
- return
+ return nil, err
}
for {
- var combinator byte
+ var (
+ combinator byte
+ c Sel
+ )
if p.skipWhitespace() {
combinator = ' '
}
if p.i >= len(p.s) {
- return
+ return result, nil
}
switch p.s[p.i] {
@@ -785,51 +800,39 @@ func (p *parser) parseSelector() (result Selector, err error) {
p.skipWhitespace()
case ',', ')':
// These characters can't begin a selector, but they can legally occur after one.
- return
+ return result, nil
}
if combinator == 0 {
- return
+ return result, nil
}
- c, err := p.parseSimpleSelectorSequence()
+ c, err = p.parseSimpleSelectorSequence()
if err != nil {
return nil, err
}
-
- switch combinator {
- case ' ':
- result = descendantSelector(result, c)
- case '>':
- result = childSelector(result, c)
- case '+':
- result = siblingSelector(result, c, true)
- case '~':
- result = siblingSelector(result, c, false)
- }
+ result = combinedSelector{first: result, combinator: combinator, second: c}
}
-
- panic("unreachable")
}
// parseSelectorGroup parses a group of selectors, separated by commas.
-func (p *parser) parseSelectorGroup() (result Selector, err error) {
- result, err = p.parseSelector()
+func (p *parser) parseSelectorGroup() (SelectorGroup, error) {
+ current, err := p.parseSelector()
if err != nil {
- return
+ return nil, err
}
+ result := SelectorGroup{current}
for p.i < len(p.s) {
if p.s[p.i] != ',' {
- return result, nil
+ break
}
p.i++
c, err := p.parseSelector()
if err != nil {
return nil, err
}
- result = unionSelector(result, c)
+ result = append(result, c)
}
-
- return
+ return result, nil
}
diff --git a/vendor/github.com/andybalholm/cascadia/selector.go b/vendor/github.com/andybalholm/cascadia/selector.go
index 9fb05cc..e2a6dc4 100644
--- a/vendor/github.com/andybalholm/cascadia/selector.go
+++ b/vendor/github.com/andybalholm/cascadia/selector.go
@@ -9,36 +9,60 @@ import (
"golang.org/x/net/html"
)
-// the Selector type, and functions for creating them
+// Matcher is the interface for basic selector functionality.
+// Match returns whether a selector matches n.
+type Matcher interface {
+ Match(n *html.Node) bool
+}
-// A Selector is a function which tells whether a node matches or not.
-type Selector func(*html.Node) bool
+// Sel is the interface for all the functionality provided by selectors.
+type Sel interface {
+ Matcher
+ Specificity() Specificity
-// 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
- }
+ // Returns a CSS input compiling to this selector.
+ String() string
+
+ // 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
}
- return false
+
+ if p.i < len(sel) {
+ return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
+ }
+
+ return compiled, nil
}
-// 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
- }
+// 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
}
- return false
+
+ if p.i < len(sel) {
+ return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
+ }
+
+ return compiled, nil
}
-// 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) {
+// 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) {
p := &parser{s: sel}
compiled, err := p.parseSelectorGroup()
if err != nil {
@@ -52,6 +76,39 @@ func Compile(sel string) (Selector, 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)
@@ -79,6 +136,23 @@ 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)
@@ -99,6 +173,21 @@ 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 {
@@ -109,106 +198,148 @@ func (s Selector) Filter(nodes []*html.Node) (result []*html.Node) {
return result
}
-// 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
+// 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)
+ }
}
+ return result
}
-// 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')
- }
- }
+type tagSelector struct {
+ tag string
+}
- if b == nil {
- return s
- }
+// Matches elements with a given tag name.
+func (t tagSelector) Match(n *html.Node) bool {
+ return n.Type == html.ElementNode && n.Data == t.tag
+}
- return string(b)
+func (c tagSelector) Specificity() Specificity {
+ return Specificity{0, 0, 1}
}
-// 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
- }
+func (c tagSelector) PseudoElement() string {
+ return ""
}
-// 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 })
+type classSelector struct {
+ class string
}
-// 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
- })
+// 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
+ })
+}
+
+func (c idSelector) Specificity() Specificity {
+ return Specificity{1, 0, 0}
+}
+
+func (c idSelector) PseudoElement() string {
+ return ""
+}
+
+type attrSelector struct {
+ key, val, operation string
+ regexp *regexp.Regexp
+}
+
+// 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))
+ }
}
-// attributeNotEqualSelector returns a Selector that matches elements where
+// 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
+}
+
+// attributeNotEqualMatch matches elements where
// the attribute named key does not have the value val.
-func attributeNotEqualSelector(key, val string) Selector {
- key = toLowerASCII(key)
- return func(n *html.Node) bool {
- if n.Type != html.ElementNode {
+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 {
return false
}
- for _, a := range n.Attr {
- if a.Key == key && a.Val == val {
- return false
- }
- }
- return true
}
+ return true
}
-// 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
- })
+// 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
}
-// 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,
+// 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,
func(s string) bool {
if s == val {
return true
@@ -223,10 +354,10 @@ func attributeDashmatchSelector(key, val string) Selector {
})
}
-// attributePrefixSelector returns a Selector that matches elements where
+// attributePrefixMatch returns a Selector that matches elements where
// the attribute named key starts with val.
-func attributePrefixSelector(key, val string) Selector {
- return attributeSelector(key,
+func attributePrefixMatch(key, val string, n *html.Node) bool {
+ return matchAttribute(n, key,
func(s string) bool {
if strings.TrimSpace(s) == "" {
return false
@@ -235,10 +366,10 @@ func attributePrefixSelector(key, val string) Selector {
})
}
-// attributeSuffixSelector returns a Selector that matches elements where
+// attributeSuffixMatch matches elements where
// the attribute named key ends with val.
-func attributeSuffixSelector(key, val string) Selector {
- return attributeSelector(key,
+func attributeSuffixMatch(key, val string, n *html.Node) bool {
+ return matchAttribute(n, key,
func(s string) bool {
if strings.TrimSpace(s) == "" {
return false
@@ -247,10 +378,10 @@ func attributeSuffixSelector(key, val string) Selector {
})
}
-// attributeSubstringSelector returns a Selector that matches nodes where
+// attributeSubstringMatch matches nodes where
// the attribute named key contains val.
-func attributeSubstringSelector(key, val string) Selector {
- return attributeSelector(key,
+func attributeSubstringMatch(key, val string, n *html.Node) bool {
+ return matchAttribute(n, key,
func(s string) bool {
if strings.TrimSpace(s) == "" {
return false
@@ -259,39 +390,130 @@ func attributeSubstringSelector(key, val string) Selector {
})
}
-// attributeRegexSelector returns a Selector that matches nodes where
+// attributeRegexMatch matches nodes where
// the attribute named key matches the regular expression rx
-func attributeRegexSelector(key string, rx *regexp.Regexp) Selector {
- return attributeSelector(key,
+func attributeRegexMatch(key string, rx *regexp.Regexp, n *html.Node) bool {
+ return matchAttribute(n, key,
func(s string) bool {
return rx.MatchString(s)
})
}
-// 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)
+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))
}
}
-// 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)
+// 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
}
-// 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
+// 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
}
- return !a(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
+ }
+ }
+ 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.
@@ -325,221 +547,214 @@ func nodeOwnText(n *html.Node) string {
return b.String()
}
-// 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)
- }
-}
-
-// 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)
- }
-}
-
-// 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))
- }
+func (s regexpPseudoClassSelector) Specificity() Specificity {
+ return Specificity{0, 1, 0}
}
-// 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))
- }
+func (c regexpPseudoClassSelector) PseudoElement() string {
+ return ""
}
-// 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)
- }
+type nthPseudoClassSelector struct {
+ a, b int
+ last, ofType bool
}
-// 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
+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)
}
- return hasDescendantMatch(n, a)
}
+ return nthChildMatch(s.a, s.b, s.last, s.ofType, n)
}
-// nthChildSelector returns a selector that implements :nth-child(an+b).
+// 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 nthChildSelector(a, b int, last, ofType bool) Selector {
- return func(n *html.Node) bool {
- if n.Type != html.ElementNode {
- return false
- }
+func nthChildMatch(a, b int, last, ofType bool, 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 {
+ return false
+ }
- 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
- }
- }
+ i := -1
+ count := 0
+ for c := parent.FirstChild; c != nil; c = c.NextSibling {
+ if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
+ continue
}
-
- if i == -1 {
- // This shouldn't happen, since n should always be one of its parent's children.
- return false
+ count++
+ if c == n {
+ i = count
+ if !last {
+ break
+ }
}
+ }
- if last {
- i = count - i + 1
- }
+ if i == -1 {
+ // This shouldn't happen, since n should always be one of its parent's children.
+ return false
+ }
- i -= b
- if a == 0 {
- return i == 0
- }
+ if last {
+ i = count - i + 1
+ }
- return i%a == 0 && i/a >= 0
+ i -= b
+ if a == 0 {
+ return i == 0
}
+
+ return i%a == 0 && i/a >= 0
}
-// simpleNthChildSelector returns a selector that implements :nth-child(b).
+// simpleNthChildMatch 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
- }
+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
- }
+ 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.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
+}
- 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
- }
- }
+// 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
}
-}
-// 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
- }
+ 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.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
- }
- }
+// Specificity for nth-child pseudo-class.
+// Does not support a list of selectors
+func (s nthPseudoClassSelector) Specificity() Specificity {
+ return Specificity{0, 1, 0}
+}
+
+func (c nthPseudoClassSelector) PseudoElement() string {
+ return ""
+}
+
+type onlyChildPseudoClassSelector struct {
+ ofType bool
+}
+
+// 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
}
-}
-// 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
- }
+ 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.FirstChild; c != nil; c = c.NextSibling {
+ if (c.Type != html.ElementNode) || (s.ofType && c.Data != n.Data) {
+ continue
+ }
+ count++
+ if count > 1 {
return false
}
+ }
- 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
- }
- }
+ return count == 1
+}
- return count == 1
- }
+func (s onlyChildPseudoClassSelector) Specificity() Specificity {
+ return Specificity{0, 1, 0}
}
-// inputSelector is a Selector that matches input, select, textarea and button elements.
-func inputSelector(n *html.Node) bool {
+func (c onlyChildPseudoClassSelector) PseudoElement() string {
+ return ""
+}
+
+type inputPseudoClassSelector struct{}
+
+// Matches input, select, textarea and button elements.
+func (s inputPseudoClassSelector) Match(n *html.Node) bool {
return n.Type == html.ElementNode && (n.Data == "input" || n.Data == "select" || n.Data == "textarea" || n.Data == "button")
}
-// emptyElementSelector is a Selector that matches empty elements.
-func emptyElementSelector(n *html.Node) bool {
+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 {
if n.Type != html.ElementNode {
return false
}
@@ -554,69 +769,170 @@ func emptyElementSelector(n *html.Node) bool {
return true
}
-// 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) {
+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) {
return false
}
+ }
+ return true
+}
- for p := n.Parent; p != nil; p = p.Parent {
- if a(p) {
- 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
+}
- return false
+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")
}
}
-// 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 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
}
-// siblingSelector returns a Selector that matches an element
-// if it matches s2 and in is preceded by an element that matches s1.
+// 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)
+}
+
+// matches an element if it matches s2 and is preceded by an element that matches s1.
// If adjacent is true, the sibling must be immediately before the element.
-func siblingSelector(s1, s2 Selector, adjacent bool) Selector {
- return func(n *html.Node) bool {
- if !s2(n) {
- return false
- }
+func siblingMatch(s1, s2 Matcher, adjacent bool, n *html.Node) bool {
+ if !s2.Match(n) {
+ return false
+ }
- if adjacent {
- for n = n.PrevSibling; n != nil; n = n.PrevSibling {
- if n.Type == html.TextNode || n.Type == html.CommentNode {
- continue
- }
- return s1(n)
+ if adjacent {
+ for n = n.PrevSibling; n != nil; n = n.PrevSibling {
+ if n.Type == html.TextNode || n.Type == html.CommentNode {
+ continue
}
- return false
+ return s1.Match(n)
}
+ return false
+ }
- // Walk backwards looking for element that matches s1
- for c := n.PrevSibling; c != nil; c = c.PrevSibling {
- if s1(c) {
- return true
- }
+ // Walk backwards looking for element that matches s1
+ for c := n.PrevSibling; c != nil; c = c.PrevSibling {
+ if s1.Match(c) {
+ return true
}
+ }
- return false
+ return false
+}
+
+func (s combinedSelector) Specificity() Specificity {
+ spec := s.first.Specificity()
+ if s.second != nil {
+ spec = spec.Add(s.second.Specificity())
}
+ return spec
}
-// rootSelector implements :root
-func rootSelector(n *html.Node) bool {
- if n.Type != html.ElementNode {
- return false
+// 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 ""
}
- if n.Parent == nil {
- 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
+ }
}
- return n.Parent.Type == html.DocumentNode
+ return false
}
diff --git a/vendor/github.com/andybalholm/cascadia/serialize.go b/vendor/github.com/andybalholm/cascadia/serialize.go
new file mode 100644
index 0000000..f15b079
--- /dev/null
+++ b/vendor/github.com/andybalholm/cascadia/serialize.go
@@ -0,0 +1,120 @@
+package cascadia
+
+import (
+ "fmt"
+ "strings"
+)
+
+// implements the reverse operation Sel -> string
+
+func (c tagSelector) String() string {
+ return c.tag
+}
+
+func (c idSelector) String() string {
+ return "#" + c.id
+}
+
+func (c classSelector) String() string {
+ return "." + c.class
+}
+
+func (c attrSelector) String() string {
+ val := c.val
+ if c.operation == "#=" {
+ val = c.regexp.String()
+ } else if c.operation != "" {
+ val = fmt.Sprintf(`"%s"`, val)
+ }
+ return fmt.Sprintf(`[%s%s%s]`, c.key, c.operation, val)
+}
+
+func (c relativePseudoClassSelector) String() string {
+ return fmt.Sprintf(":%s(%s)", c.name, c.match.String())
+}
+func (c containsPseudoClassSelector) String() string {
+ s := "contains"
+ if c.own {
+ s += "Own"
+ }
+ return fmt.Sprintf(`:%s("%s")`, s, c.value)
+}
+func (c regexpPseudoClassSelector) String() string {
+ s := "matches"
+ if c.own {
+ s += "Own"
+ }
+ return fmt.Sprintf(":%s(%s)", s, c.regexp.String())
+}
+func (c nthPseudoClassSelector) String() string {
+ if c.a == 0 && c.b == 1 { // special cases
+ s := ":first-"
+ if c.last {
+ s = ":last-"
+ }
+ if c.ofType {
+ s += "of-type"
+ } else {
+ s += "child"
+ }
+ return s
+ }
+ var name string
+ switch [2]bool{c.last, c.ofType} {
+ case [2]bool{true, true}:
+ name = "nth-last-of-type"
+ case [2]bool{true, false}:
+ name = "nth-last-child"
+ case [2]bool{false, true}:
+ name = "nth-of-type"
+ case [2]bool{false, false}:
+ name = "nth-child"
+ }
+ return fmt.Sprintf(":%s(%dn+%d)", name, c.a, c.b)
+}
+func (c onlyChildPseudoClassSelector) String() string {
+ if c.ofType {
+ return ":only-of-type"
+ }
+ return ":only-child"
+}
+func (c inputPseudoClassSelector) String() string {
+ return ":input"
+}
+func (c emptyElementPseudoClassSelector) String() string {
+ return ":empty"
+}
+func (c rootPseudoClassSelector) String() string {
+ return ":root"
+}
+
+func (c compoundSelector) String() string {
+ if len(c.selectors) == 0 && c.pseudoElement == "" {
+ return "*"
+ }
+ chunks := make([]string, len(c.selectors))
+ for i, sel := range c.selectors {
+ chunks[i] = sel.String()
+ }
+ s := strings.Join(chunks, "")
+ if c.pseudoElement != "" {
+ s += "::" + c.pseudoElement
+ }
+ return s
+}
+
+func (c combinedSelector) String() string {
+ start := c.first.String()
+ if c.second != nil {
+ start += fmt.Sprintf(" %s %s", string(c.combinator), c.second.String())
+ }
+ return start
+}
+
+func (c SelectorGroup) String() string {
+ ck := make([]string, len(c))
+ for i, s := range c {
+ ck[i] = s.String()
+ }
+ return strings.Join(ck, ", ")
+}
diff --git a/vendor/github.com/andybalholm/cascadia/specificity.go b/vendor/github.com/andybalholm/cascadia/specificity.go
new file mode 100644
index 0000000..8db864f
--- /dev/null
+++ b/vendor/github.com/andybalholm/cascadia/specificity.go
@@ -0,0 +1,26 @@
+package cascadia
+
+// Specificity is the CSS specificity as defined in
+// https://www.w3.org/TR/selectors/#specificity-rules
+// with the convention Specificity = [A,B,C].
+type Specificity [3]int
+
+// returns `true` if s < other (strictly), false otherwise
+func (s Specificity) Less(other Specificity) bool {
+ for i := range s {
+ if s[i] < other[i] {
+ return true
+ }
+ if s[i] > other[i] {
+ return false
+ }
+ }
+ return false
+}
+
+func (s Specificity) Add(other Specificity) Specificity {
+ for i, sp := range other {
+ s[i] += sp
+ }
+ return s
+}