parser.go

  1// Package cascadia is an implementation of CSS selectors.
  2package cascadia
  3
  4import (
  5	"errors"
  6	"fmt"
  7	"regexp"
  8	"strconv"
  9	"strings"
 10)
 11
 12// a parser for CSS selectors
 13type parser struct {
 14	s string // the source text
 15	i int    // the current position
 16
 17	// if `false`, parsing a pseudo-element
 18	// returns an error.
 19	acceptPseudoElements bool
 20}
 21
 22// parseEscape parses a backslash escape.
 23func (p *parser) parseEscape() (result string, err error) {
 24	if len(p.s) < p.i+2 || p.s[p.i] != '\\' {
 25		return "", errors.New("invalid escape sequence")
 26	}
 27
 28	start := p.i + 1
 29	c := p.s[start]
 30	switch {
 31	case c == '\r' || c == '\n' || c == '\f':
 32		return "", errors.New("escaped line ending outside string")
 33	case hexDigit(c):
 34		// unicode escape (hex)
 35		var i int
 36		for i = start; i < start+6 && i < len(p.s) && hexDigit(p.s[i]); i++ {
 37			// empty
 38		}
 39		v, _ := strconv.ParseUint(p.s[start:i], 16, 64)
 40		if len(p.s) > i {
 41			switch p.s[i] {
 42			case '\r':
 43				i++
 44				if len(p.s) > i && p.s[i] == '\n' {
 45					i++
 46				}
 47			case ' ', '\t', '\n', '\f':
 48				i++
 49			}
 50		}
 51		p.i = i
 52		return string(rune(v)), nil
 53	}
 54
 55	// Return the literal character after the backslash.
 56	result = p.s[start : start+1]
 57	p.i += 2
 58	return result, nil
 59}
 60
 61// toLowerASCII returns s with all ASCII capital letters lowercased.
 62func toLowerASCII(s string) string {
 63	var b []byte
 64	for i := 0; i < len(s); i++ {
 65		if c := s[i]; 'A' <= c && c <= 'Z' {
 66			if b == nil {
 67				b = make([]byte, len(s))
 68				copy(b, s)
 69			}
 70			b[i] = s[i] + ('a' - 'A')
 71		}
 72	}
 73
 74	if b == nil {
 75		return s
 76	}
 77
 78	return string(b)
 79}
 80
 81func hexDigit(c byte) bool {
 82	return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
 83}
 84
 85// nameStart returns whether c can be the first character of an identifier
 86// (not counting an initial hyphen, or an escape sequence).
 87func nameStart(c byte) bool {
 88	return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127
 89}
 90
 91// nameChar returns whether c can be a character within an identifier
 92// (not counting an escape sequence).
 93func nameChar(c byte) bool {
 94	return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127 ||
 95		c == '-' || '0' <= c && c <= '9'
 96}
 97
 98// parseIdentifier parses an identifier.
 99func (p *parser) parseIdentifier() (result string, err error) {
100	const prefix = '-'
101	var numPrefix int
102
103	for len(p.s) > p.i && p.s[p.i] == prefix {
104		p.i++
105		numPrefix++
106	}
107
108	if len(p.s) <= p.i {
109		return "", errors.New("expected identifier, found EOF instead")
110	}
111
112	if c := p.s[p.i]; !(nameStart(c) || c == '\\') {
113		return "", fmt.Errorf("expected identifier, found %c instead", c)
114	}
115
116	result, err = p.parseName()
117	if numPrefix > 0 && err == nil {
118		result = strings.Repeat(string(prefix), numPrefix) + result
119	}
120	return
121}
122
123// parseName parses a name (which is like an identifier, but doesn't have
124// extra restrictions on the first character).
125func (p *parser) parseName() (result string, err error) {
126	i := p.i
127loop:
128	for i < len(p.s) {
129		c := p.s[i]
130		switch {
131		case nameChar(c):
132			start := i
133			for i < len(p.s) && nameChar(p.s[i]) {
134				i++
135			}
136			result += p.s[start:i]
137		case c == '\\':
138			p.i = i
139			val, err := p.parseEscape()
140			if err != nil {
141				return "", err
142			}
143			i = p.i
144			result += val
145		default:
146			break loop
147		}
148	}
149
150	if result == "" {
151		return "", errors.New("expected name, found EOF instead")
152	}
153
154	p.i = i
155	return result, nil
156}
157
158// parseString parses a single- or double-quoted string.
159func (p *parser) parseString() (result string, err error) {
160	i := p.i
161	if len(p.s) < i+2 {
162		return "", errors.New("expected string, found EOF instead")
163	}
164
165	quote := p.s[i]
166	i++
167
168loop:
169	for i < len(p.s) {
170		switch p.s[i] {
171		case '\\':
172			if len(p.s) > i+1 {
173				switch c := p.s[i+1]; c {
174				case '\r':
175					if len(p.s) > i+2 && p.s[i+2] == '\n' {
176						i += 3
177						continue loop
178					}
179					fallthrough
180				case '\n', '\f':
181					i += 2
182					continue loop
183				}
184			}
185			p.i = i
186			val, err := p.parseEscape()
187			if err != nil {
188				return "", err
189			}
190			i = p.i
191			result += val
192		case quote:
193			break loop
194		case '\r', '\n', '\f':
195			return "", errors.New("unexpected end of line in string")
196		default:
197			start := i
198			for i < len(p.s) {
199				if c := p.s[i]; c == quote || c == '\\' || c == '\r' || c == '\n' || c == '\f' {
200					break
201				}
202				i++
203			}
204			result += p.s[start:i]
205		}
206	}
207
208	if i >= len(p.s) {
209		return "", errors.New("EOF in string")
210	}
211
212	// Consume the final quote.
213	i++
214
215	p.i = i
216	return result, nil
217}
218
219// parseRegex parses a regular expression; the end is defined by encountering an
220// unmatched closing ')' or ']' which is not consumed
221func (p *parser) parseRegex() (rx *regexp.Regexp, err error) {
222	i := p.i
223	if len(p.s) < i+2 {
224		return nil, errors.New("expected regular expression, found EOF instead")
225	}
226
227	// number of open parens or brackets;
228	// when it becomes negative, finished parsing regex
229	open := 0
230
231loop:
232	for i < len(p.s) {
233		switch p.s[i] {
234		case '(', '[':
235			open++
236		case ')', ']':
237			open--
238			if open < 0 {
239				break loop
240			}
241		}
242		i++
243	}
244
245	if i >= len(p.s) {
246		return nil, errors.New("EOF in regular expression")
247	}
248	rx, err = regexp.Compile(p.s[p.i:i])
249	p.i = i
250	return rx, err
251}
252
253// skipWhitespace consumes whitespace characters and comments.
254// It returns true if there was actually anything to skip.
255func (p *parser) skipWhitespace() bool {
256	i := p.i
257	for i < len(p.s) {
258		switch p.s[i] {
259		case ' ', '\t', '\r', '\n', '\f':
260			i++
261			continue
262		case '/':
263			if strings.HasPrefix(p.s[i:], "/*") {
264				end := strings.Index(p.s[i+len("/*"):], "*/")
265				if end != -1 {
266					i += end + len("/**/")
267					continue
268				}
269			}
270		}
271		break
272	}
273
274	if i > p.i {
275		p.i = i
276		return true
277	}
278
279	return false
280}
281
282// consumeParenthesis consumes an opening parenthesis and any following
283// whitespace. It returns true if there was actually a parenthesis to skip.
284func (p *parser) consumeParenthesis() bool {
285	if p.i < len(p.s) && p.s[p.i] == '(' {
286		p.i++
287		p.skipWhitespace()
288		return true
289	}
290	return false
291}
292
293// consumeClosingParenthesis consumes a closing parenthesis and any preceding
294// whitespace. It returns true if there was actually a parenthesis to skip.
295func (p *parser) consumeClosingParenthesis() bool {
296	i := p.i
297	p.skipWhitespace()
298	if p.i < len(p.s) && p.s[p.i] == ')' {
299		p.i++
300		return true
301	}
302	p.i = i
303	return false
304}
305
306// parseTypeSelector parses a type selector (one that matches by tag name).
307func (p *parser) parseTypeSelector() (result tagSelector, err error) {
308	tag, err := p.parseIdentifier()
309	if err != nil {
310		return
311	}
312	return tagSelector{tag: toLowerASCII(tag)}, nil
313}
314
315// parseIDSelector parses a selector that matches by id attribute.
316func (p *parser) parseIDSelector() (idSelector, error) {
317	if p.i >= len(p.s) {
318		return idSelector{}, fmt.Errorf("expected id selector (#id), found EOF instead")
319	}
320	if p.s[p.i] != '#' {
321		return idSelector{}, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i])
322	}
323
324	p.i++
325	id, err := p.parseName()
326	if err != nil {
327		return idSelector{}, err
328	}
329
330	return idSelector{id: id}, nil
331}
332
333// parseClassSelector parses a selector that matches by class attribute.
334func (p *parser) parseClassSelector() (classSelector, error) {
335	if p.i >= len(p.s) {
336		return classSelector{}, fmt.Errorf("expected class selector (.class), found EOF instead")
337	}
338	if p.s[p.i] != '.' {
339		return classSelector{}, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i])
340	}
341
342	p.i++
343	class, err := p.parseIdentifier()
344	if err != nil {
345		return classSelector{}, err
346	}
347
348	return classSelector{class: class}, nil
349}
350
351// parseAttributeSelector parses a selector that matches by attribute value.
352func (p *parser) parseAttributeSelector() (attrSelector, error) {
353	if p.i >= len(p.s) {
354		return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead")
355	}
356	if p.s[p.i] != '[' {
357		return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found '%c' instead", p.s[p.i])
358	}
359
360	p.i++
361	p.skipWhitespace()
362	key, err := p.parseIdentifier()
363	if err != nil {
364		return attrSelector{}, err
365	}
366	key = toLowerASCII(key)
367
368	p.skipWhitespace()
369	if p.i >= len(p.s) {
370		return attrSelector{}, errors.New("unexpected EOF in attribute selector")
371	}
372
373	if p.s[p.i] == ']' {
374		p.i++
375		return attrSelector{key: key, operation: ""}, nil
376	}
377
378	if p.i+2 >= len(p.s) {
379		return attrSelector{}, errors.New("unexpected EOF in attribute selector")
380	}
381
382	op := p.s[p.i : p.i+2]
383	if op[0] == '=' {
384		op = "="
385	} else if op[1] != '=' {
386		return attrSelector{}, fmt.Errorf(`expected equality operator, found "%s" instead`, op)
387	}
388	p.i += len(op)
389
390	p.skipWhitespace()
391	if p.i >= len(p.s) {
392		return attrSelector{}, errors.New("unexpected EOF in attribute selector")
393	}
394	var val string
395	var rx *regexp.Regexp
396	if op == "#=" {
397		rx, err = p.parseRegex()
398	} else {
399		switch p.s[p.i] {
400		case '\'', '"':
401			val, err = p.parseString()
402		default:
403			val, err = p.parseIdentifier()
404		}
405	}
406	if err != nil {
407		return attrSelector{}, err
408	}
409
410	p.skipWhitespace()
411	if p.i >= len(p.s) {
412		return attrSelector{}, errors.New("unexpected EOF in attribute selector")
413	}
414
415	// check if the attribute contains an ignore case flag
416	ignoreCase := false
417	if p.s[p.i] == 'i' || p.s[p.i] == 'I' {
418		ignoreCase = true
419		p.i++
420	}
421
422	p.skipWhitespace()
423	if p.i >= len(p.s) {
424		return attrSelector{}, errors.New("unexpected EOF in attribute selector")
425	}
426
427	if p.s[p.i] != ']' {
428		return attrSelector{}, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
429	}
430	p.i++
431
432	switch op {
433	case "=", "!=", "~=", "|=", "^=", "$=", "*=", "#=":
434		return attrSelector{key: key, val: val, operation: op, regexp: rx, insensitive: ignoreCase}, nil
435	default:
436		return attrSelector{}, fmt.Errorf("attribute operator %q is not supported", op)
437	}
438}
439
440var (
441	errExpectedParenthesis        = errors.New("expected '(' but didn't find it")
442	errExpectedClosingParenthesis = errors.New("expected ')' but didn't find it")
443	errUnmatchedParenthesis       = errors.New("unmatched '('")
444)
445
446// parsePseudoclassSelector parses a pseudoclass selector like :not(p) or a pseudo-element
447// For backwards compatibility, both ':' and '::' prefix are allowed for pseudo-elements.
448// https://drafts.csswg.org/selectors-3/#pseudo-elements
449// Returning a nil `Sel` (and a nil `error`) means we found a pseudo-element.
450func (p *parser) parsePseudoclassSelector() (out Sel, pseudoElement string, err error) {
451	if p.i >= len(p.s) {
452		return nil, "", fmt.Errorf("expected pseudoclass selector (:pseudoclass), found EOF instead")
453	}
454	if p.s[p.i] != ':' {
455		return nil, "", fmt.Errorf("expected attribute selector (:pseudoclass), found '%c' instead", p.s[p.i])
456	}
457
458	p.i++
459	var mustBePseudoElement bool
460	if p.i >= len(p.s) {
461		return nil, "", fmt.Errorf("got empty pseudoclass (or pseudoelement)")
462	}
463	if p.s[p.i] == ':' { // we found a pseudo-element
464		mustBePseudoElement = true
465		p.i++
466	}
467
468	name, err := p.parseIdentifier()
469	if err != nil {
470		return
471	}
472	name = toLowerASCII(name)
473	if mustBePseudoElement && (name != "after" && name != "backdrop" && name != "before" &&
474		name != "cue" && name != "first-letter" && name != "first-line" && name != "grammar-error" &&
475		name != "marker" && name != "placeholder" && name != "selection" && name != "spelling-error") {
476		return out, "", fmt.Errorf("unknown pseudoelement :%s", name)
477	}
478
479	switch name {
480	case "not", "has", "haschild":
481		if !p.consumeParenthesis() {
482			return out, "", errExpectedParenthesis
483		}
484		sel, parseErr := p.parseSelectorGroup()
485		if parseErr != nil {
486			return out, "", parseErr
487		}
488		if !p.consumeClosingParenthesis() {
489			return out, "", errExpectedClosingParenthesis
490		}
491
492		out = relativePseudoClassSelector{name: name, match: sel}
493
494	case "contains", "containsown":
495		if !p.consumeParenthesis() {
496			return out, "", errExpectedParenthesis
497		}
498		if p.i == len(p.s) {
499			return out, "", errUnmatchedParenthesis
500		}
501		var val string
502		switch p.s[p.i] {
503		case '\'', '"':
504			val, err = p.parseString()
505		default:
506			val, err = p.parseIdentifier()
507		}
508		if err != nil {
509			return out, "", err
510		}
511		val = strings.ToLower(val)
512		p.skipWhitespace()
513		if p.i >= len(p.s) {
514			return out, "", errors.New("unexpected EOF in pseudo selector")
515		}
516		if !p.consumeClosingParenthesis() {
517			return out, "", errExpectedClosingParenthesis
518		}
519
520		out = containsPseudoClassSelector{own: name == "containsown", value: val}
521
522	case "matches", "matchesown":
523		if !p.consumeParenthesis() {
524			return out, "", errExpectedParenthesis
525		}
526		rx, err := p.parseRegex()
527		if err != nil {
528			return out, "", err
529		}
530		if p.i >= len(p.s) {
531			return out, "", errors.New("unexpected EOF in pseudo selector")
532		}
533		if !p.consumeClosingParenthesis() {
534			return out, "", errExpectedClosingParenthesis
535		}
536
537		out = regexpPseudoClassSelector{own: name == "matchesown", regexp: rx}
538
539	case "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type":
540		if !p.consumeParenthesis() {
541			return out, "", errExpectedParenthesis
542		}
543		a, b, err := p.parseNth()
544		if err != nil {
545			return out, "", err
546		}
547		if !p.consumeClosingParenthesis() {
548			return out, "", errExpectedClosingParenthesis
549		}
550		last := name == "nth-last-child" || name == "nth-last-of-type"
551		ofType := name == "nth-of-type" || name == "nth-last-of-type"
552		out = nthPseudoClassSelector{a: a, b: b, last: last, ofType: ofType}
553
554	case "first-child":
555		out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: false}
556	case "last-child":
557		out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: true}
558	case "first-of-type":
559		out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: false}
560	case "last-of-type":
561		out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: true}
562	case "only-child":
563		out = onlyChildPseudoClassSelector{ofType: false}
564	case "only-of-type":
565		out = onlyChildPseudoClassSelector{ofType: true}
566	case "input":
567		out = inputPseudoClassSelector{}
568	case "empty":
569		out = emptyElementPseudoClassSelector{}
570	case "root":
571		out = rootPseudoClassSelector{}
572	case "link":
573		out = linkPseudoClassSelector{}
574	case "lang":
575		if !p.consumeParenthesis() {
576			return out, "", errExpectedParenthesis
577		}
578		if p.i == len(p.s) {
579			return out, "", errUnmatchedParenthesis
580		}
581		val, err := p.parseIdentifier()
582		if err != nil {
583			return out, "", err
584		}
585		val = strings.ToLower(val)
586		p.skipWhitespace()
587		if p.i >= len(p.s) {
588			return out, "", errors.New("unexpected EOF in pseudo selector")
589		}
590		if !p.consumeClosingParenthesis() {
591			return out, "", errExpectedClosingParenthesis
592		}
593		out = langPseudoClassSelector{lang: val}
594	case "enabled":
595		out = enabledPseudoClassSelector{}
596	case "disabled":
597		out = disabledPseudoClassSelector{}
598	case "checked":
599		out = checkedPseudoClassSelector{}
600	case "visited", "hover", "active", "focus", "target":
601		// Not applicable in a static context: never match.
602		out = neverMatchSelector{value: ":" + name}
603	case "after", "backdrop", "before", "cue", "first-letter", "first-line", "grammar-error", "marker", "placeholder", "selection", "spelling-error":
604		return nil, name, nil
605	default:
606		return out, "", fmt.Errorf("unknown pseudoclass or pseudoelement :%s", name)
607	}
608	return
609}
610
611// parseInteger parses a  decimal integer.
612func (p *parser) parseInteger() (int, error) {
613	i := p.i
614	start := i
615	for i < len(p.s) && '0' <= p.s[i] && p.s[i] <= '9' {
616		i++
617	}
618	if i == start {
619		return 0, errors.New("expected integer, but didn't find it")
620	}
621	p.i = i
622
623	val, err := strconv.Atoi(p.s[start:i])
624	if err != nil {
625		return 0, err
626	}
627
628	return val, nil
629}
630
631// parseNth parses the argument for :nth-child (normally of the form an+b).
632func (p *parser) parseNth() (a, b int, err error) {
633	// initial state
634	if p.i >= len(p.s) {
635		goto eof
636	}
637	switch p.s[p.i] {
638	case '-':
639		p.i++
640		goto negativeA
641	case '+':
642		p.i++
643		goto positiveA
644	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
645		goto positiveA
646	case 'n', 'N':
647		a = 1
648		p.i++
649		goto readN
650	case 'o', 'O', 'e', 'E':
651		id, nameErr := p.parseName()
652		if nameErr != nil {
653			return 0, 0, nameErr
654		}
655		id = toLowerASCII(id)
656		if id == "odd" {
657			return 2, 1, nil
658		}
659		if id == "even" {
660			return 2, 0, nil
661		}
662		return 0, 0, fmt.Errorf("expected 'odd' or 'even', but found '%s' instead", id)
663	default:
664		goto invalid
665	}
666
667positiveA:
668	if p.i >= len(p.s) {
669		goto eof
670	}
671	switch p.s[p.i] {
672	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
673		a, err = p.parseInteger()
674		if err != nil {
675			return 0, 0, err
676		}
677		goto readA
678	case 'n', 'N':
679		a = 1
680		p.i++
681		goto readN
682	default:
683		goto invalid
684	}
685
686negativeA:
687	if p.i >= len(p.s) {
688		goto eof
689	}
690	switch p.s[p.i] {
691	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
692		a, err = p.parseInteger()
693		if err != nil {
694			return 0, 0, err
695		}
696		a = -a
697		goto readA
698	case 'n', 'N':
699		a = -1
700		p.i++
701		goto readN
702	default:
703		goto invalid
704	}
705
706readA:
707	if p.i >= len(p.s) {
708		goto eof
709	}
710	switch p.s[p.i] {
711	case 'n', 'N':
712		p.i++
713		goto readN
714	default:
715		// The number we read as a is actually b.
716		return 0, a, nil
717	}
718
719readN:
720	p.skipWhitespace()
721	if p.i >= len(p.s) {
722		goto eof
723	}
724	switch p.s[p.i] {
725	case '+':
726		p.i++
727		p.skipWhitespace()
728		b, err = p.parseInteger()
729		if err != nil {
730			return 0, 0, err
731		}
732		return a, b, nil
733	case '-':
734		p.i++
735		p.skipWhitespace()
736		b, err = p.parseInteger()
737		if err != nil {
738			return 0, 0, err
739		}
740		return a, -b, nil
741	default:
742		return a, 0, nil
743	}
744
745eof:
746	return 0, 0, errors.New("unexpected EOF while attempting to parse expression of form an+b")
747
748invalid:
749	return 0, 0, errors.New("unexpected character while attempting to parse expression of form an+b")
750}
751
752// parseSimpleSelectorSequence parses a selector sequence that applies to
753// a single element.
754func (p *parser) parseSimpleSelectorSequence() (Sel, error) {
755	var selectors []Sel
756
757	if p.i >= len(p.s) {
758		return nil, errors.New("expected selector, found EOF instead")
759	}
760
761	switch p.s[p.i] {
762	case '*':
763		// It's the universal selector. Just skip over it, since it doesn't affect the meaning.
764		p.i++
765		if p.i+2 < len(p.s) && p.s[p.i:p.i+2] == "|*" { // other version of universal selector
766			p.i += 2
767		}
768	case '#', '.', '[', ':':
769		// There's no type selector. Wait to process the other till the main loop.
770	default:
771		r, err := p.parseTypeSelector()
772		if err != nil {
773			return nil, err
774		}
775		selectors = append(selectors, r)
776	}
777
778	var pseudoElement string
779loop:
780	for p.i < len(p.s) {
781		var (
782			ns               Sel
783			newPseudoElement string
784			err              error
785		)
786		switch p.s[p.i] {
787		case '#':
788			ns, err = p.parseIDSelector()
789		case '.':
790			ns, err = p.parseClassSelector()
791		case '[':
792			ns, err = p.parseAttributeSelector()
793		case ':':
794			ns, newPseudoElement, err = p.parsePseudoclassSelector()
795		default:
796			break loop
797		}
798		if err != nil {
799			return nil, err
800		}
801		// From https://drafts.csswg.org/selectors-3/#pseudo-elements :
802		// "Only one pseudo-element may appear per selector, and if present
803		// it must appear after the sequence of simple selectors that
804		// represents the subjects of the selector.""
805		if ns == nil { // we found a pseudo-element
806			if pseudoElement != "" {
807				return nil, fmt.Errorf("only one pseudo-element is accepted per selector, got %s and %s", pseudoElement, newPseudoElement)
808			}
809			if !p.acceptPseudoElements {
810				return nil, fmt.Errorf("pseudo-element %s found, but pseudo-elements support is disabled", newPseudoElement)
811			}
812			pseudoElement = newPseudoElement
813		} else {
814			if pseudoElement != "" {
815				return nil, fmt.Errorf("pseudo-element %s must be at the end of selector", pseudoElement)
816			}
817			selectors = append(selectors, ns)
818		}
819
820	}
821	if len(selectors) == 1 && pseudoElement == "" { // no need wrap the selectors in compoundSelector
822		return selectors[0], nil
823	}
824	return compoundSelector{selectors: selectors, pseudoElement: pseudoElement}, nil
825}
826
827// parseSelector parses a selector that may include combinators.
828func (p *parser) parseSelector() (Sel, error) {
829	p.skipWhitespace()
830	result, err := p.parseSimpleSelectorSequence()
831	if err != nil {
832		return nil, err
833	}
834
835	for {
836		var (
837			combinator byte
838			c          Sel
839		)
840		if p.skipWhitespace() {
841			combinator = ' '
842		}
843		if p.i >= len(p.s) {
844			return result, nil
845		}
846
847		switch p.s[p.i] {
848		case '+', '>', '~':
849			combinator = p.s[p.i]
850			p.i++
851			p.skipWhitespace()
852		case ',', ')':
853			// These characters can't begin a selector, but they can legally occur after one.
854			return result, nil
855		}
856
857		if combinator == 0 {
858			return result, nil
859		}
860
861		c, err = p.parseSimpleSelectorSequence()
862		if err != nil {
863			return nil, err
864		}
865		result = combinedSelector{first: result, combinator: combinator, second: c}
866	}
867}
868
869// parseSelectorGroup parses a group of selectors, separated by commas.
870func (p *parser) parseSelectorGroup() (SelectorGroup, error) {
871	current, err := p.parseSelector()
872	if err != nil {
873		return nil, err
874	}
875	result := SelectorGroup{current}
876
877	for p.i < len(p.s) {
878		if p.s[p.i] != ',' {
879			break
880		}
881		p.i++
882		c, err := p.parseSelector()
883		if err != nil {
884			return nil, err
885		}
886		result = append(result, c)
887	}
888	return result, nil
889}