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/.travis.yml14
-rw-r--r--[-rwxr-xr-x]vendor/github.com/andybalholm/cascadia/LICENSE0
-rw-r--r--vendor/github.com/andybalholm/cascadia/README.md2
-rw-r--r--vendor/github.com/andybalholm/cascadia/parser.go319
-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
7 files changed, 508 insertions, 961 deletions
diff --git a/vendor/github.com/andybalholm/cascadia/.travis.yml b/vendor/github.com/andybalholm/cascadia/.travis.yml
new file mode 100644
index 0000000..6f22751
--- /dev/null
+++ b/vendor/github.com/andybalholm/cascadia/.travis.yml
@@ -0,0 +1,14 @@
+language: go
+
+go:
+ - 1.3
+ - 1.4
+
+install:
+ - go get github.com/andybalholm/cascadia
+
+script:
+ - go test -v
+
+notifications:
+ email: false
diff --git a/vendor/github.com/andybalholm/cascadia/LICENSE b/vendor/github.com/andybalholm/cascadia/LICENSE
index ee5ad35..ee5ad35 100755..100644
--- a/vendor/github.com/andybalholm/cascadia/LICENSE
+++ b/vendor/github.com/andybalholm/cascadia/LICENSE
diff --git a/vendor/github.com/andybalholm/cascadia/README.md b/vendor/github.com/andybalholm/cascadia/README.md
index 26f4c37..9021cb9 100644
--- a/vendor/github.com/andybalholm/cascadia/README.md
+++ b/vendor/github.com/andybalholm/cascadia/README.md
@@ -5,5 +5,3 @@
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/parser.go b/vendor/github.com/andybalholm/cascadia/parser.go
index bebf0af..495db9c 100644
--- a/vendor/github.com/andybalholm/cascadia/parser.go
+++ b/vendor/github.com/andybalholm/cascadia/parser.go
@@ -7,16 +7,14 @@ 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.
@@ -58,26 +56,6 @@ 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'
}
@@ -302,92 +280,92 @@ func (p *parser) consumeClosingParenthesis() bool {
}
// parseTypeSelector parses a type selector (one that matches by tag name).
-func (p *parser) parseTypeSelector() (result tagSelector, err error) {
+func (p *parser) parseTypeSelector() (result Selector, err error) {
tag, err := p.parseIdentifier()
if err != nil {
- return
+ return nil, err
}
- return tagSelector{tag: toLowerASCII(tag)}, nil
+
+ return typeSelector(tag), nil
}
// parseIDSelector parses a selector that matches by id attribute.
-func (p *parser) parseIDSelector() (idSelector, error) {
+func (p *parser) parseIDSelector() (Selector, error) {
if p.i >= len(p.s) {
- return idSelector{}, fmt.Errorf("expected id selector (#id), found EOF instead")
+ return nil, fmt.Errorf("expected id selector (#id), found EOF instead")
}
if p.s[p.i] != '#' {
- return idSelector{}, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i])
+ return nil, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i])
}
p.i++
id, err := p.parseName()
if err != nil {
- return idSelector{}, err
+ return nil, err
}
- return idSelector{id: id}, nil
+ return attributeEqualsSelector("id", id), nil
}
// parseClassSelector parses a selector that matches by class attribute.
-func (p *parser) parseClassSelector() (classSelector, error) {
+func (p *parser) parseClassSelector() (Selector, error) {
if p.i >= len(p.s) {
- return classSelector{}, fmt.Errorf("expected class selector (.class), found EOF instead")
+ return nil, fmt.Errorf("expected class selector (.class), found EOF instead")
}
if p.s[p.i] != '.' {
- return classSelector{}, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i])
+ return nil, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i])
}
p.i++
class, err := p.parseIdentifier()
if err != nil {
- return classSelector{}, err
+ return nil, err
}
- return classSelector{class: class}, nil
+ return attributeIncludesSelector("class", class), nil
}
// parseAttributeSelector parses a selector that matches by attribute value.
-func (p *parser) parseAttributeSelector() (attrSelector, error) {
+func (p *parser) parseAttributeSelector() (Selector, error) {
if p.i >= len(p.s) {
- return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead")
+ return nil, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead")
}
if p.s[p.i] != '[' {
- return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found '%c' instead", p.s[p.i])
+ return nil, 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 attrSelector{}, err
+ return nil, err
}
- key = toLowerASCII(key)
p.skipWhitespace()
if p.i >= len(p.s) {
- return attrSelector{}, errors.New("unexpected EOF in attribute selector")
+ return nil, errors.New("unexpected EOF in attribute selector")
}
if p.s[p.i] == ']' {
p.i++
- return attrSelector{key: key, operation: ""}, nil
+ return attributeExistsSelector(key), nil
}
if p.i+2 >= len(p.s) {
- return attrSelector{}, errors.New("unexpected EOF in attribute selector")
+ return nil, errors.New("unexpected EOF in attribute selector")
}
op := p.s[p.i : p.i+2]
if op[0] == '=' {
op = "="
} else if op[1] != '=' {
- return attrSelector{}, fmt.Errorf(`expected equality operator, found "%s" instead`, op)
+ return nil, fmt.Errorf(`expected equality operator, found "%s" instead`, op)
}
p.i += len(op)
p.skipWhitespace()
if p.i >= len(p.s) {
- return attrSelector{}, errors.New("unexpected EOF in attribute selector")
+ return nil, errors.New("unexpected EOF in attribute selector")
}
var val string
var rx *regexp.Regexp
@@ -402,84 +380,88 @@ func (p *parser) parseAttributeSelector() (attrSelector, error) {
}
}
if err != nil {
- return attrSelector{}, err
+ return nil, err
}
p.skipWhitespace()
if p.i >= len(p.s) {
- return attrSelector{}, errors.New("unexpected EOF in attribute selector")
+ return nil, errors.New("unexpected EOF in attribute selector")
}
if p.s[p.i] != ']' {
- return attrSelector{}, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
+ return nil, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
}
p.i++
switch 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)
- }
+ 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)
}
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) 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) {
+// parsePseudoclassSelector parses a pseudoclass selector like :not(p).
+func (p *parser) parsePseudoclassSelector() (Selector, 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
+ return nil, err
}
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 out, "", errExpectedParenthesis
+ return nil, errExpectedParenthesis
}
sel, parseErr := p.parseSelectorGroup()
if parseErr != nil {
- return out, "", parseErr
+ return nil, parseErr
}
if !p.consumeClosingParenthesis() {
- return out, "", errExpectedClosingParenthesis
+ return nil, errExpectedClosingParenthesis
}
- out = relativePseudoClassSelector{name: name, match: sel}
+ switch name {
+ case "not":
+ return negatedSelector(sel), nil
+ case "has":
+ return hasDescendantSelector(sel), nil
+ case "haschild":
+ return hasChildSelector(sel), nil
+ }
case "contains", "containsown":
if !p.consumeParenthesis() {
- return out, "", errExpectedParenthesis
+ return nil, errExpectedParenthesis
}
if p.i == len(p.s) {
- return out, "", errUnmatchedParenthesis
+ return nil, errUnmatchedParenthesis
}
var val string
switch p.s[p.i] {
@@ -489,75 +471,95 @@ func (p *parser) parsePseudoclassSelector() (out Sel, pseudoElement string, err
val, err = p.parseIdentifier()
}
if err != nil {
- return out, "", err
+ return nil, err
}
val = strings.ToLower(val)
p.skipWhitespace()
if p.i >= len(p.s) {
- return out, "", errors.New("unexpected EOF in pseudo selector")
+ return nil, errors.New("unexpected EOF in pseudo selector")
}
if !p.consumeClosingParenthesis() {
- return out, "", errExpectedClosingParenthesis
+ return nil, errExpectedClosingParenthesis
}
- out = containsPseudoClassSelector{own: name == "containsown", value: val}
+ switch name {
+ case "contains":
+ return textSubstrSelector(val), nil
+ case "containsown":
+ return ownTextSubstrSelector(val), nil
+ }
case "matches", "matchesown":
if !p.consumeParenthesis() {
- return out, "", errExpectedParenthesis
+ return nil, errExpectedParenthesis
}
rx, err := p.parseRegex()
if err != nil {
- return out, "", err
+ return nil, err
}
if p.i >= len(p.s) {
- return out, "", errors.New("unexpected EOF in pseudo selector")
+ return nil, errors.New("unexpected EOF in pseudo selector")
}
if !p.consumeClosingParenthesis() {
- return out, "", errExpectedClosingParenthesis
+ return nil, errExpectedClosingParenthesis
}
- out = regexpPseudoClassSelector{own: name == "matchesown", regexp: rx}
+ switch name {
+ case "matches":
+ return textRegexSelector(rx), nil
+ case "matchesown":
+ return ownTextRegexSelector(rx), nil
+ }
case "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type":
if !p.consumeParenthesis() {
- return out, "", errExpectedParenthesis
+ return nil, errExpectedParenthesis
}
a, b, err := p.parseNth()
if err != nil {
- return out, "", err
+ return nil, err
}
if !p.consumeClosingParenthesis() {
- return out, "", errExpectedClosingParenthesis
+ 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
+ }
}
- 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}
+ return nthChildSelector(a, b,
+ name == "nth-last-child" || name == "nth-last-of-type",
+ name == "nth-of-type" || name == "nth-last-of-type"),
+ nil
case "first-child":
- out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: false}
+ return simpleNthChildSelector(1, false), nil
case "last-child":
- out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: true}
+ return simpleNthLastChildSelector(1, false), nil
case "first-of-type":
- out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: false}
+ return simpleNthChildSelector(1, true), nil
case "last-of-type":
- out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: true}
+ return simpleNthLastChildSelector(1, true), nil
case "only-child":
- out = onlyChildPseudoClassSelector{ofType: false}
+ return onlyChildSelector(false), nil
case "only-of-type":
- out = onlyChildPseudoClassSelector{ofType: true}
+ return onlyChildSelector(true), nil
case "input":
- out = inputPseudoClassSelector{}
+ return inputSelector, nil
case "empty":
- out = emptyElementPseudoClassSelector{}
+ return emptyElementSelector, nil
case "root":
- 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 rootSelector, nil
}
- return
+
+ return nil, fmt.Errorf("unknown pseudoclass :%s", name)
}
// parseInteger parses a decimal integer.
@@ -703,8 +705,8 @@ invalid:
// parseSimpleSelectorSequence parses a selector sequence that applies to
// a single element.
-func (p *parser) parseSimpleSelectorSequence() (Sel, error) {
- var selectors []Sel
+func (p *parser) parseSimpleSelectorSequence() (Selector, error) {
+ var result Selector
if p.i >= len(p.s) {
return nil, errors.New("expected selector, found EOF instead")
@@ -721,17 +723,13 @@ func (p *parser) parseSimpleSelectorSequence() (Sel, error) {
if err != nil {
return nil, err
}
- selectors = append(selectors, r)
+ result = r
}
- var pseudoElement string
loop:
for p.i < len(p.s) {
- var (
- ns Sel
- newPseudoElement string
- err error
- )
+ var ns Selector
+ var err error
switch p.s[p.i] {
case '#':
ns, err = p.parseIDSelector()
@@ -740,57 +738,44 @@ loop:
case '[':
ns, err = p.parseAttributeSelector()
case ':':
- ns, newPseudoElement, err = p.parsePseudoclassSelector()
+ ns, err = p.parsePseudoclassSelector()
default:
break loop
}
if err != nil {
return nil, err
}
- // 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
+ if result == nil {
+ result = ns
} else {
- if pseudoElement != "" {
- return nil, fmt.Errorf("pseudo-element %s must be at the end of selector", pseudoElement)
- }
- selectors = append(selectors, ns)
+ result = intersectionSelector(result, ns)
}
-
}
- if len(selectors) == 1 && pseudoElement == "" { // no need wrap the selectors in compoundSelector
- return selectors[0], nil
+
+ if result == nil {
+ result = func(n *html.Node) bool {
+ return n.Type == html.ElementNode
+ }
}
- return compoundSelector{selectors: selectors, pseudoElement: pseudoElement}, nil
+
+ return result, nil
}
// parseSelector parses a selector that may include combinators.
-func (p *parser) parseSelector() (Sel, error) {
+func (p *parser) parseSelector() (result Selector, err error) {
p.skipWhitespace()
- result, err := p.parseSimpleSelectorSequence()
+ result, err = p.parseSimpleSelectorSequence()
if err != nil {
- return nil, err
+ return
}
for {
- var (
- combinator byte
- c Sel
- )
+ var combinator byte
if p.skipWhitespace() {
combinator = ' '
}
if p.i >= len(p.s) {
- return result, nil
+ return
}
switch p.s[p.i] {
@@ -800,39 +785,51 @@ func (p *parser) parseSelector() (Sel, error) {
p.skipWhitespace()
case ',', ')':
// These characters can't begin a selector, but they can legally occur after one.
- return result, nil
+ return
}
if combinator == 0 {
- return result, nil
+ return
}
- c, err = p.parseSimpleSelectorSequence()
+ c, err := p.parseSimpleSelectorSequence()
if err != nil {
return nil, err
}
- result = combinedSelector{first: result, combinator: combinator, second: c}
+
+ switch combinator {
+ case ' ':
+ result = descendantSelector(result, c)
+ case '>':
+ result = childSelector(result, c)
+ case '+':
+ result = siblingSelector(result, c, true)
+ case '~':
+ result = siblingSelector(result, c, false)
+ }
}
+
+ panic("unreachable")
}
// parseSelectorGroup parses a group of selectors, separated by commas.
-func (p *parser) parseSelectorGroup() (SelectorGroup, error) {
- current, err := p.parseSelector()
+func (p *parser) parseSelectorGroup() (result Selector, err error) {
+ result, err = p.parseSelector()
if err != nil {
- return nil, err
+ return
}
- result := SelectorGroup{current}
for p.i < len(p.s) {
if p.s[p.i] != ',' {
- break
+ return result, nil
}
p.i++
c, err := p.parseSelector()
if err != nil {
return nil, err
}
- result = append(result, c)
+ result = unionSelector(result, c)
}
- return result, nil
+
+ return
}
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
}
diff --git a/vendor/github.com/andybalholm/cascadia/serialize.go b/vendor/github.com/andybalholm/cascadia/serialize.go
deleted file mode 100644
index f15b079..0000000
--- a/vendor/github.com/andybalholm/cascadia/serialize.go
+++ /dev/null
@@ -1,120 +0,0 @@
-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
deleted file mode 100644
index 8db864f..0000000
--- a/vendor/github.com/andybalholm/cascadia/specificity.go
+++ /dev/null
@@ -1,26 +0,0 @@
-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
-}