mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Show commit status icon in commits table (#1688)
* Show commit status icon in commits table * Add comments * Fix icons * Few more places where commit table is displayed * Change integration test to use goquery for parsing html * Add integration tests for commit table and status icons * Fix status to return lates status correctly on all databases * Rewrote lates commit status selects
This commit is contained in:
		
							
								
								
									
										24
									
								
								vendor/github.com/andybalholm/cascadia/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/andybalholm/cascadia/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Executable file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| Copyright (c) 2011 Andy Balholm. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
|    * Redistributions of source code must retain the above copyright | ||||
| notice, this list of conditions and the following disclaimer. | ||||
|    * Redistributions in binary form must reproduce the above | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										7
									
								
								vendor/github.com/andybalholm/cascadia/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								vendor/github.com/andybalholm/cascadia/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| # cascadia | ||||
|  | ||||
| [](https://travis-ci.org/andybalholm/cascadia) | ||||
|  | ||||
| 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. | ||||
							
								
								
									
										835
									
								
								vendor/github.com/andybalholm/cascadia/parser.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										835
									
								
								vendor/github.com/andybalholm/cascadia/parser.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,835 @@ | ||||
| // Package cascadia is an implementation of CSS selectors. | ||||
| package cascadia | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"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 | ||||
| } | ||||
|  | ||||
| // parseEscape parses a backslash escape. | ||||
| func (p *parser) parseEscape() (result string, err error) { | ||||
| 	if len(p.s) < p.i+2 || p.s[p.i] != '\\' { | ||||
| 		return "", errors.New("invalid escape sequence") | ||||
| 	} | ||||
|  | ||||
| 	start := p.i + 1 | ||||
| 	c := p.s[start] | ||||
| 	switch { | ||||
| 	case c == '\r' || c == '\n' || c == '\f': | ||||
| 		return "", errors.New("escaped line ending outside string") | ||||
| 	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++ { | ||||
| 			// empty | ||||
| 		} | ||||
| 		v, _ := strconv.ParseUint(p.s[start:i], 16, 21) | ||||
| 		if len(p.s) > i { | ||||
| 			switch p.s[i] { | ||||
| 			case '\r': | ||||
| 				i++ | ||||
| 				if len(p.s) > i && p.s[i] == '\n' { | ||||
| 					i++ | ||||
| 				} | ||||
| 			case ' ', '\t', '\n', '\f': | ||||
| 				i++ | ||||
| 			} | ||||
| 		} | ||||
| 		p.i = i | ||||
| 		return string(rune(v)), nil | ||||
| 	} | ||||
|  | ||||
| 	// Return the literal character after the backslash. | ||||
| 	result = p.s[start : start+1] | ||||
| 	p.i += 2 | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| func hexDigit(c byte) bool { | ||||
| 	return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' | ||||
| } | ||||
|  | ||||
| // nameStart returns whether c can be the first character of an identifier | ||||
| // (not counting an initial hyphen, or an escape sequence). | ||||
| func nameStart(c byte) bool { | ||||
| 	return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127 | ||||
| } | ||||
|  | ||||
| // nameChar returns whether c can be a character within an identifier | ||||
| // (not counting an escape sequence). | ||||
| func nameChar(c byte) bool { | ||||
| 	return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127 || | ||||
| 		c == '-' || '0' <= c && c <= '9' | ||||
| } | ||||
|  | ||||
| // parseIdentifier parses an identifier. | ||||
| func (p *parser) parseIdentifier() (result string, err error) { | ||||
| 	startingDash := false | ||||
| 	if len(p.s) > p.i && p.s[p.i] == '-' { | ||||
| 		startingDash = true | ||||
| 		p.i++ | ||||
| 	} | ||||
|  | ||||
| 	if len(p.s) <= p.i { | ||||
| 		return "", errors.New("expected identifier, found EOF instead") | ||||
| 	} | ||||
|  | ||||
| 	if c := p.s[p.i]; !(nameStart(c) || c == '\\') { | ||||
| 		return "", fmt.Errorf("expected identifier, found %c instead", c) | ||||
| 	} | ||||
|  | ||||
| 	result, err = p.parseName() | ||||
| 	if startingDash && err == nil { | ||||
| 		result = "-" + result | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // parseName parses a name (which is like an identifier, but doesn't have | ||||
| // extra restrictions on the first character). | ||||
| func (p *parser) parseName() (result string, err error) { | ||||
| 	i := p.i | ||||
| loop: | ||||
| 	for i < len(p.s) { | ||||
| 		c := p.s[i] | ||||
| 		switch { | ||||
| 		case nameChar(c): | ||||
| 			start := i | ||||
| 			for i < len(p.s) && nameChar(p.s[i]) { | ||||
| 				i++ | ||||
| 			} | ||||
| 			result += p.s[start:i] | ||||
| 		case c == '\\': | ||||
| 			p.i = i | ||||
| 			val, err := p.parseEscape() | ||||
| 			if err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
| 			i = p.i | ||||
| 			result += val | ||||
| 		default: | ||||
| 			break loop | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if result == "" { | ||||
| 		return "", errors.New("expected name, found EOF instead") | ||||
| 	} | ||||
|  | ||||
| 	p.i = i | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // parseString parses a single- or double-quoted string. | ||||
| func (p *parser) parseString() (result string, err error) { | ||||
| 	i := p.i | ||||
| 	if len(p.s) < i+2 { | ||||
| 		return "", errors.New("expected string, found EOF instead") | ||||
| 	} | ||||
|  | ||||
| 	quote := p.s[i] | ||||
| 	i++ | ||||
|  | ||||
| loop: | ||||
| 	for i < len(p.s) { | ||||
| 		switch p.s[i] { | ||||
| 		case '\\': | ||||
| 			if len(p.s) > i+1 { | ||||
| 				switch c := p.s[i+1]; c { | ||||
| 				case '\r': | ||||
| 					if len(p.s) > i+2 && p.s[i+2] == '\n' { | ||||
| 						i += 3 | ||||
| 						continue loop | ||||
| 					} | ||||
| 					fallthrough | ||||
| 				case '\n', '\f': | ||||
| 					i += 2 | ||||
| 					continue loop | ||||
| 				} | ||||
| 			} | ||||
| 			p.i = i | ||||
| 			val, err := p.parseEscape() | ||||
| 			if err != nil { | ||||
| 				return "", err | ||||
| 			} | ||||
| 			i = p.i | ||||
| 			result += val | ||||
| 		case quote: | ||||
| 			break loop | ||||
| 		case '\r', '\n', '\f': | ||||
| 			return "", errors.New("unexpected end of line in string") | ||||
| 		default: | ||||
| 			start := i | ||||
| 			for i < len(p.s) { | ||||
| 				if c := p.s[i]; c == quote || c == '\\' || c == '\r' || c == '\n' || c == '\f' { | ||||
| 					break | ||||
| 				} | ||||
| 				i++ | ||||
| 			} | ||||
| 			result += p.s[start:i] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if i >= len(p.s) { | ||||
| 		return "", errors.New("EOF in string") | ||||
| 	} | ||||
|  | ||||
| 	// Consume the final quote. | ||||
| 	i++ | ||||
|  | ||||
| 	p.i = i | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // parseRegex parses a regular expression; the end is defined by encountering an | ||||
| // unmatched closing ')' or ']' which is not consumed | ||||
| func (p *parser) parseRegex() (rx *regexp.Regexp, err error) { | ||||
| 	i := p.i | ||||
| 	if len(p.s) < i+2 { | ||||
| 		return nil, errors.New("expected regular expression, found EOF instead") | ||||
| 	} | ||||
|  | ||||
| 	// number of open parens or brackets; | ||||
| 	// when it becomes negative, finished parsing regex | ||||
| 	open := 0 | ||||
|  | ||||
| loop: | ||||
| 	for i < len(p.s) { | ||||
| 		switch p.s[i] { | ||||
| 		case '(', '[': | ||||
| 			open++ | ||||
| 		case ')', ']': | ||||
| 			open-- | ||||
| 			if open < 0 { | ||||
| 				break loop | ||||
| 			} | ||||
| 		} | ||||
| 		i++ | ||||
| 	} | ||||
|  | ||||
| 	if i >= len(p.s) { | ||||
| 		return nil, errors.New("EOF in regular expression") | ||||
| 	} | ||||
| 	rx, err = regexp.Compile(p.s[p.i:i]) | ||||
| 	p.i = i | ||||
| 	return rx, err | ||||
| } | ||||
|  | ||||
| // skipWhitespace consumes whitespace characters and comments. | ||||
| // It returns true if there was actually anything to skip. | ||||
| func (p *parser) skipWhitespace() bool { | ||||
| 	i := p.i | ||||
| 	for i < len(p.s) { | ||||
| 		switch p.s[i] { | ||||
| 		case ' ', '\t', '\r', '\n', '\f': | ||||
| 			i++ | ||||
| 			continue | ||||
| 		case '/': | ||||
| 			if strings.HasPrefix(p.s[i:], "/*") { | ||||
| 				end := strings.Index(p.s[i+len("/*"):], "*/") | ||||
| 				if end != -1 { | ||||
| 					i += end + len("/**/") | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		break | ||||
| 	} | ||||
|  | ||||
| 	if i > p.i { | ||||
| 		p.i = i | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // consumeParenthesis consumes an opening parenthesis and any following | ||||
| // whitespace. It returns true if there was actually a parenthesis to skip. | ||||
| func (p *parser) consumeParenthesis() bool { | ||||
| 	if p.i < len(p.s) && p.s[p.i] == '(' { | ||||
| 		p.i++ | ||||
| 		p.skipWhitespace() | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // consumeClosingParenthesis consumes a closing parenthesis and any preceding | ||||
| // whitespace. It returns true if there was actually a parenthesis to skip. | ||||
| func (p *parser) consumeClosingParenthesis() bool { | ||||
| 	i := p.i | ||||
| 	p.skipWhitespace() | ||||
| 	if p.i < len(p.s) && p.s[p.i] == ')' { | ||||
| 		p.i++ | ||||
| 		return true | ||||
| 	} | ||||
| 	p.i = i | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // parseTypeSelector parses a type selector (one that matches by tag name). | ||||
| func (p *parser) parseTypeSelector() (result Selector, err error) { | ||||
| 	tag, err := p.parseIdentifier() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return typeSelector(tag), nil | ||||
| } | ||||
|  | ||||
| // parseIDSelector parses a selector that matches by id attribute. | ||||
| func (p *parser) parseIDSelector() (Selector, error) { | ||||
| 	if p.i >= len(p.s) { | ||||
| 		return nil, 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]) | ||||
| 	} | ||||
|  | ||||
| 	p.i++ | ||||
| 	id, err := p.parseName() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return attributeEqualsSelector("id", id), nil | ||||
| } | ||||
|  | ||||
| // parseClassSelector parses a selector that matches by class attribute. | ||||
| func (p *parser) parseClassSelector() (Selector, error) { | ||||
| 	if p.i >= len(p.s) { | ||||
| 		return nil, 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]) | ||||
| 	} | ||||
|  | ||||
| 	p.i++ | ||||
| 	class, err := p.parseIdentifier() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return attributeIncludesSelector("class", class), nil | ||||
| } | ||||
|  | ||||
| // parseAttributeSelector parses a selector that matches by attribute value. | ||||
| func (p *parser) parseAttributeSelector() (Selector, error) { | ||||
| 	if p.i >= len(p.s) { | ||||
| 		return nil, 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]) | ||||
| 	} | ||||
|  | ||||
| 	p.i++ | ||||
| 	p.skipWhitespace() | ||||
| 	key, err := p.parseIdentifier() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	p.skipWhitespace() | ||||
| 	if p.i >= len(p.s) { | ||||
| 		return nil, errors.New("unexpected EOF in attribute selector") | ||||
| 	} | ||||
|  | ||||
| 	if p.s[p.i] == ']' { | ||||
| 		p.i++ | ||||
| 		return attributeExistsSelector(key), nil | ||||
| 	} | ||||
|  | ||||
| 	if p.i+2 >= len(p.s) { | ||||
| 		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 nil, 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") | ||||
| 	} | ||||
| 	var val string | ||||
| 	var rx *regexp.Regexp | ||||
| 	if op == "#=" { | ||||
| 		rx, err = p.parseRegex() | ||||
| 	} else { | ||||
| 		switch p.s[p.i] { | ||||
| 		case '\'', '"': | ||||
| 			val, err = p.parseString() | ||||
| 		default: | ||||
| 			val, err = p.parseIdentifier() | ||||
| 		} | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	p.skipWhitespace() | ||||
| 	if p.i >= len(p.s) { | ||||
| 		return nil, errors.New("unexpected EOF in attribute selector") | ||||
| 	} | ||||
| 	if p.s[p.i] != ']' { | ||||
| 		return nil, 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) | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| 	if p.i >= len(p.s) { | ||||
| 		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]) | ||||
| 	} | ||||
|  | ||||
| 	p.i++ | ||||
| 	name, err := p.parseIdentifier() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	name = toLowerASCII(name) | ||||
|  | ||||
| 	switch name { | ||||
| 	case "not", "has", "haschild": | ||||
| 		if !p.consumeParenthesis() { | ||||
| 			return nil, errExpectedParenthesis | ||||
| 		} | ||||
| 		sel, parseErr := p.parseSelectorGroup() | ||||
| 		if parseErr != nil { | ||||
| 			return nil, parseErr | ||||
| 		} | ||||
| 		if !p.consumeClosingParenthesis() { | ||||
| 			return nil, errExpectedClosingParenthesis | ||||
| 		} | ||||
|  | ||||
| 		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 nil, errExpectedParenthesis | ||||
| 		} | ||||
| 		if p.i == len(p.s) { | ||||
| 			return nil, errUnmatchedParenthesis | ||||
| 		} | ||||
| 		var val string | ||||
| 		switch p.s[p.i] { | ||||
| 		case '\'', '"': | ||||
| 			val, err = p.parseString() | ||||
| 		default: | ||||
| 			val, err = p.parseIdentifier() | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		val = strings.ToLower(val) | ||||
| 		p.skipWhitespace() | ||||
| 		if p.i >= len(p.s) { | ||||
| 			return nil, errors.New("unexpected EOF in pseudo selector") | ||||
| 		} | ||||
| 		if !p.consumeClosingParenthesis() { | ||||
| 			return nil, errExpectedClosingParenthesis | ||||
| 		} | ||||
|  | ||||
| 		switch name { | ||||
| 		case "contains": | ||||
| 			return textSubstrSelector(val), nil | ||||
| 		case "containsown": | ||||
| 			return ownTextSubstrSelector(val), nil | ||||
| 		} | ||||
|  | ||||
| 	case "matches", "matchesown": | ||||
| 		if !p.consumeParenthesis() { | ||||
| 			return nil, errExpectedParenthesis | ||||
| 		} | ||||
| 		rx, err := p.parseRegex() | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if p.i >= len(p.s) { | ||||
| 			return nil, errors.New("unexpected EOF in pseudo selector") | ||||
| 		} | ||||
| 		if !p.consumeClosingParenthesis() { | ||||
| 			return nil, errExpectedClosingParenthesis | ||||
| 		} | ||||
|  | ||||
| 		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 nil, errExpectedParenthesis | ||||
| 		} | ||||
| 		a, b, err := p.parseNth() | ||||
| 		if err != nil { | ||||
| 			return nil, 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 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": | ||||
| 		return simpleNthChildSelector(1, false), nil | ||||
| 	case "last-child": | ||||
| 		return simpleNthLastChildSelector(1, false), nil | ||||
| 	case "first-of-type": | ||||
| 		return simpleNthChildSelector(1, true), nil | ||||
| 	case "last-of-type": | ||||
| 		return simpleNthLastChildSelector(1, true), nil | ||||
| 	case "only-child": | ||||
| 		return onlyChildSelector(false), nil | ||||
| 	case "only-of-type": | ||||
| 		return onlyChildSelector(true), nil | ||||
| 	case "input": | ||||
| 		return inputSelector, nil | ||||
| 	case "empty": | ||||
| 		return emptyElementSelector, nil | ||||
| 	case "root": | ||||
| 		return rootSelector, nil | ||||
| 	} | ||||
|  | ||||
| 	return nil, fmt.Errorf("unknown pseudoclass :%s", name) | ||||
| } | ||||
|  | ||||
| // parseInteger parses a  decimal integer. | ||||
| func (p *parser) parseInteger() (int, error) { | ||||
| 	i := p.i | ||||
| 	start := i | ||||
| 	for i < len(p.s) && '0' <= p.s[i] && p.s[i] <= '9' { | ||||
| 		i++ | ||||
| 	} | ||||
| 	if i == start { | ||||
| 		return 0, errors.New("expected integer, but didn't find it") | ||||
| 	} | ||||
| 	p.i = i | ||||
|  | ||||
| 	val, err := strconv.Atoi(p.s[start:i]) | ||||
| 	if err != nil { | ||||
| 		return 0, err | ||||
| 	} | ||||
|  | ||||
| 	return val, nil | ||||
| } | ||||
|  | ||||
| // parseNth parses the argument for :nth-child (normally of the form an+b). | ||||
| func (p *parser) parseNth() (a, b int, err error) { | ||||
| 	// initial state | ||||
| 	if p.i >= len(p.s) { | ||||
| 		goto eof | ||||
| 	} | ||||
| 	switch p.s[p.i] { | ||||
| 	case '-': | ||||
| 		p.i++ | ||||
| 		goto negativeA | ||||
| 	case '+': | ||||
| 		p.i++ | ||||
| 		goto positiveA | ||||
| 	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': | ||||
| 		goto positiveA | ||||
| 	case 'n', 'N': | ||||
| 		a = 1 | ||||
| 		p.i++ | ||||
| 		goto readN | ||||
| 	case 'o', 'O', 'e', 'E': | ||||
| 		id, nameErr := p.parseName() | ||||
| 		if nameErr != nil { | ||||
| 			return 0, 0, nameErr | ||||
| 		} | ||||
| 		id = toLowerASCII(id) | ||||
| 		if id == "odd" { | ||||
| 			return 2, 1, nil | ||||
| 		} | ||||
| 		if id == "even" { | ||||
| 			return 2, 0, nil | ||||
| 		} | ||||
| 		return 0, 0, fmt.Errorf("expected 'odd' or 'even', but found '%s' instead", id) | ||||
| 	default: | ||||
| 		goto invalid | ||||
| 	} | ||||
|  | ||||
| positiveA: | ||||
| 	if p.i >= len(p.s) { | ||||
| 		goto eof | ||||
| 	} | ||||
| 	switch p.s[p.i] { | ||||
| 	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': | ||||
| 		a, err = p.parseInteger() | ||||
| 		if err != nil { | ||||
| 			return 0, 0, err | ||||
| 		} | ||||
| 		goto readA | ||||
| 	case 'n', 'N': | ||||
| 		a = 1 | ||||
| 		p.i++ | ||||
| 		goto readN | ||||
| 	default: | ||||
| 		goto invalid | ||||
| 	} | ||||
|  | ||||
| negativeA: | ||||
| 	if p.i >= len(p.s) { | ||||
| 		goto eof | ||||
| 	} | ||||
| 	switch p.s[p.i] { | ||||
| 	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': | ||||
| 		a, err = p.parseInteger() | ||||
| 		if err != nil { | ||||
| 			return 0, 0, err | ||||
| 		} | ||||
| 		a = -a | ||||
| 		goto readA | ||||
| 	case 'n', 'N': | ||||
| 		a = -1 | ||||
| 		p.i++ | ||||
| 		goto readN | ||||
| 	default: | ||||
| 		goto invalid | ||||
| 	} | ||||
|  | ||||
| readA: | ||||
| 	if p.i >= len(p.s) { | ||||
| 		goto eof | ||||
| 	} | ||||
| 	switch p.s[p.i] { | ||||
| 	case 'n', 'N': | ||||
| 		p.i++ | ||||
| 		goto readN | ||||
| 	default: | ||||
| 		// The number we read as a is actually b. | ||||
| 		return 0, a, nil | ||||
| 	} | ||||
|  | ||||
| readN: | ||||
| 	p.skipWhitespace() | ||||
| 	if p.i >= len(p.s) { | ||||
| 		goto eof | ||||
| 	} | ||||
| 	switch p.s[p.i] { | ||||
| 	case '+': | ||||
| 		p.i++ | ||||
| 		p.skipWhitespace() | ||||
| 		b, err = p.parseInteger() | ||||
| 		if err != nil { | ||||
| 			return 0, 0, err | ||||
| 		} | ||||
| 		return a, b, nil | ||||
| 	case '-': | ||||
| 		p.i++ | ||||
| 		p.skipWhitespace() | ||||
| 		b, err = p.parseInteger() | ||||
| 		if err != nil { | ||||
| 			return 0, 0, err | ||||
| 		} | ||||
| 		return a, -b, nil | ||||
| 	default: | ||||
| 		return a, 0, nil | ||||
| 	} | ||||
|  | ||||
| eof: | ||||
| 	return 0, 0, errors.New("unexpected EOF while attempting to parse expression of form an+b") | ||||
|  | ||||
| invalid: | ||||
| 	return 0, 0, errors.New("unexpected character while attempting to parse expression of form an+b") | ||||
| } | ||||
|  | ||||
| // parseSimpleSelectorSequence parses a selector sequence that applies to | ||||
| // a single element. | ||||
| func (p *parser) parseSimpleSelectorSequence() (Selector, error) { | ||||
| 	var result Selector | ||||
|  | ||||
| 	if p.i >= len(p.s) { | ||||
| 		return nil, errors.New("expected selector, found EOF instead") | ||||
| 	} | ||||
|  | ||||
| 	switch p.s[p.i] { | ||||
| 	case '*': | ||||
| 		// It's the universal selector. Just skip over it, since it doesn't affect the meaning. | ||||
| 		p.i++ | ||||
| 	case '#', '.', '[', ':': | ||||
| 		// There's no type selector. Wait to process the other till the main loop. | ||||
| 	default: | ||||
| 		r, err := p.parseTypeSelector() | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		result = r | ||||
| 	} | ||||
|  | ||||
| loop: | ||||
| 	for p.i < len(p.s) { | ||||
| 		var ns Selector | ||||
| 		var err error | ||||
| 		switch p.s[p.i] { | ||||
| 		case '#': | ||||
| 			ns, err = p.parseIDSelector() | ||||
| 		case '.': | ||||
| 			ns, err = p.parseClassSelector() | ||||
| 		case '[': | ||||
| 			ns, err = p.parseAttributeSelector() | ||||
| 		case ':': | ||||
| 			ns, err = p.parsePseudoclassSelector() | ||||
| 		default: | ||||
| 			break loop | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if result == nil { | ||||
| 			result = ns | ||||
| 		} else { | ||||
| 			result = intersectionSelector(result, ns) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if result == nil { | ||||
| 		result = func(n *html.Node) bool { | ||||
| 			return n.Type == html.ElementNode | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| // parseSelector parses a selector that may include combinators. | ||||
| func (p *parser) parseSelector() (result Selector, err error) { | ||||
| 	p.skipWhitespace() | ||||
| 	result, err = p.parseSimpleSelectorSequence() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for { | ||||
| 		var combinator byte | ||||
| 		if p.skipWhitespace() { | ||||
| 			combinator = ' ' | ||||
| 		} | ||||
| 		if p.i >= len(p.s) { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		switch p.s[p.i] { | ||||
| 		case '+', '>', '~': | ||||
| 			combinator = p.s[p.i] | ||||
| 			p.i++ | ||||
| 			p.skipWhitespace() | ||||
| 		case ',', ')': | ||||
| 			// These characters can't begin a selector, but they can legally occur after one. | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if combinator == 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	panic("unreachable") | ||||
| } | ||||
|  | ||||
| // parseSelectorGroup parses a group of selectors, separated by commas. | ||||
| func (p *parser) parseSelectorGroup() (result Selector, err error) { | ||||
| 	result, err = p.parseSelector() | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for p.i < len(p.s) { | ||||
| 		if p.s[p.i] != ',' { | ||||
| 			return result, nil | ||||
| 		} | ||||
| 		p.i++ | ||||
| 		c, err := p.parseSelector() | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		result = unionSelector(result, c) | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										622
									
								
								vendor/github.com/andybalholm/cascadia/selector.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										622
									
								
								vendor/github.com/andybalholm/cascadia/selector.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,622 @@ | ||||
| package cascadia | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"golang.org/x/net/html" | ||||
| ) | ||||
|  | ||||
| // the Selector type, and functions for creating them | ||||
|  | ||||
| // A Selector is a function which tells whether a node matches or not. | ||||
| type Selector func(*html.Node) bool | ||||
|  | ||||
| // 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 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 Selector) bool { | ||||
| 	for c := n.FirstChild; c != nil; c = c.NextSibling { | ||||
| 		if a(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| // MustCompile is like Compile, but panics instead of returning an error. | ||||
| func MustCompile(sel string) Selector { | ||||
| 	compiled, err := Compile(sel) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return compiled | ||||
| } | ||||
|  | ||||
| // MatchAll returns a slice of the nodes that match the selector, | ||||
| // from n and its children. | ||||
| func (s Selector) MatchAll(n *html.Node) []*html.Node { | ||||
| 	return s.matchAllInto(n, nil) | ||||
| } | ||||
|  | ||||
| func (s Selector) matchAllInto(n *html.Node, storage []*html.Node) []*html.Node { | ||||
| 	if s(n) { | ||||
| 		storage = append(storage, n) | ||||
| 	} | ||||
|  | ||||
| 	for child := n.FirstChild; child != nil; child = child.NextSibling { | ||||
| 		storage = s.matchAllInto(child, storage) | ||||
| 	} | ||||
|  | ||||
| 	return storage | ||||
| } | ||||
|  | ||||
| // Match returns true if the node matches the selector. | ||||
| func (s Selector) Match(n *html.Node) bool { | ||||
| 	return s(n) | ||||
| } | ||||
|  | ||||
| // MatchFirst returns the first node that matches s, from n and its children. | ||||
| func (s Selector) MatchFirst(n *html.Node) *html.Node { | ||||
| 	if s.Match(n) { | ||||
| 		return n | ||||
| 	} | ||||
|  | ||||
| 	for c := n.FirstChild; c != nil; c = c.NextSibling { | ||||
| 		m := s.MatchFirst(c) | ||||
| 		if m != nil { | ||||
| 			return m | ||||
| 		} | ||||
| 	} | ||||
| 	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 { | ||||
| 		if s(n) { | ||||
| 			result = append(result, n) | ||||
| 		} | ||||
| 	} | ||||
| 	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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 }) | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| // attributeNotEqualSelector returns a Selector that 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 { | ||||
| 			return false | ||||
| 		} | ||||
| 		for _, a := range n.Attr { | ||||
| 			if a.Key == key && a.Val == val { | ||||
| 				return false | ||||
| 			} | ||||
| 		} | ||||
| 		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 | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 			} | ||||
| 			if len(s) <= len(val) { | ||||
| 				return false | ||||
| 			} | ||||
| 			if s[:len(val)] == val && s[len(val)] == '-' { | ||||
| 				return true | ||||
| 			} | ||||
| 			return false | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| // attributePrefixSelector returns a Selector that matches elements where | ||||
| // the attribute named key starts with val. | ||||
| func attributePrefixSelector(key, val string) Selector { | ||||
| 	return attributeSelector(key, | ||||
| 		func(s string) bool { | ||||
| 			if strings.TrimSpace(s) == "" { | ||||
| 				return false | ||||
| 			} | ||||
| 			return strings.HasPrefix(s, val) | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| // attributeSuffixSelector returns a Selector that matches elements where | ||||
| // the attribute named key ends with val. | ||||
| func attributeSuffixSelector(key, val string) Selector { | ||||
| 	return attributeSelector(key, | ||||
| 		func(s string) bool { | ||||
| 			if strings.TrimSpace(s) == "" { | ||||
| 				return false | ||||
| 			} | ||||
| 			return strings.HasSuffix(s, val) | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| // attributeSubstringSelector returns a Selector that matches nodes where | ||||
| // the attribute named key contains val. | ||||
| func attributeSubstringSelector(key, val string) Selector { | ||||
| 	return attributeSelector(key, | ||||
| 		func(s string) bool { | ||||
| 			if strings.TrimSpace(s) == "" { | ||||
| 				return false | ||||
| 			} | ||||
| 			return strings.Contains(s, val) | ||||
| 		}) | ||||
| } | ||||
|  | ||||
| // attributeRegexSelector returns a Selector that matches nodes where | ||||
| // the attribute named key matches the regular expression rx | ||||
| func attributeRegexSelector(key string, rx *regexp.Regexp) Selector { | ||||
| 	return attributeSelector(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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // writeNodeText writes the text contained in n and its descendants to b. | ||||
| func writeNodeText(n *html.Node, b *bytes.Buffer) { | ||||
| 	switch n.Type { | ||||
| 	case html.TextNode: | ||||
| 		b.WriteString(n.Data) | ||||
| 	case html.ElementNode: | ||||
| 		for c := n.FirstChild; c != nil; c = c.NextSibling { | ||||
| 			writeNodeText(c, b) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // nodeText returns the text contained in n and its descendants. | ||||
| func nodeText(n *html.Node) string { | ||||
| 	var b bytes.Buffer | ||||
| 	writeNodeText(n, &b) | ||||
| 	return b.String() | ||||
| } | ||||
|  | ||||
| // nodeOwnText returns the contents of the text nodes that are direct | ||||
| // children of n. | ||||
| func nodeOwnText(n *html.Node) string { | ||||
| 	var b bytes.Buffer | ||||
| 	for c := n.FirstChild; c != nil; c = c.NextSibling { | ||||
| 		if c.Type == html.TextNode { | ||||
| 			b.WriteString(c.Data) | ||||
| 		} | ||||
| 	} | ||||
| 	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)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 nthChildSelector(a, b int, last, ofType bool) Selector { | ||||
| 	return func(n *html.Node) bool { | ||||
| 		if n.Type != html.ElementNode { | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		parent := n.Parent | ||||
| 		if parent == nil { | ||||
| 			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 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		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 | ||||
| 		} | ||||
|  | ||||
| 		return i%a == 0 && i/a >= 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 		} | ||||
|  | ||||
| 		parent := n.Parent | ||||
| 		if parent == nil { | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		if parent.Type == html.DocumentNode { | ||||
| 			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 | ||||
| 			} | ||||
| 		} | ||||
| 		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 | ||||
| 		} | ||||
|  | ||||
| 		if parent.Type == html.DocumentNode { | ||||
| 			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 | ||||
| 			} | ||||
| 		} | ||||
| 		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 | ||||
| 		} | ||||
|  | ||||
| 		if parent.Type == html.DocumentNode { | ||||
| 			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 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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") | ||||
| } | ||||
|  | ||||
| // emptyElementSelector is a Selector that matches empty elements. | ||||
| func emptyElementSelector(n *html.Node) bool { | ||||
| 	if n.Type != html.ElementNode { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	for c := n.FirstChild; c != nil; c = c.NextSibling { | ||||
| 		switch c.Type { | ||||
| 		case html.ElementNode, html.TextNode: | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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) { | ||||
| 			return false | ||||
| 		} | ||||
|  | ||||
| 		for p := n.Parent; p != nil; p = p.Parent { | ||||
| 			if a(p) { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 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 | ||||
| 				} | ||||
| 				return s1(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 | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // rootSelector implements :root | ||||
| func rootSelector(n *html.Node) bool { | ||||
| 	if n.Type != html.ElementNode { | ||||
| 		return false | ||||
| 	} | ||||
| 	if n.Parent == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return n.Parent.Type == html.DocumentNode | ||||
| } | ||||
		Reference in New Issue
	
	Block a user