2020-10-24 12:40:39 +02:00
|
|
|
package cascadia
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2021-12-13 10:29:47 +01:00
|
|
|
"strconv"
|
2020-10-24 12:40:39 +02:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
// implements the reverse operation Sel -> string
|
|
|
|
|
2021-12-13 10:29:47 +01:00
|
|
|
var specialCharReplacer *strings.Replacer
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
var pairs []string
|
|
|
|
for _, s := range ",!\"#$%&'()*+ -./:;<=>?@[\\]^`{|}~" {
|
|
|
|
pairs = append(pairs, string(s), "\\"+string(s))
|
|
|
|
}
|
|
|
|
specialCharReplacer = strings.NewReplacer(pairs...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// espace special CSS char
|
|
|
|
func escape(s string) string { return specialCharReplacer.Replace(s) }
|
|
|
|
|
2020-10-24 12:40:39 +02:00
|
|
|
func (c tagSelector) String() string {
|
|
|
|
return c.tag
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c idSelector) String() string {
|
2021-12-13 10:29:47 +01:00
|
|
|
return "#" + escape(c.id)
|
2020-10-24 12:40:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c classSelector) String() string {
|
2021-12-13 10:29:47 +01:00
|
|
|
return "." + escape(c.class)
|
2020-10-24 12:40:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c attrSelector) String() string {
|
|
|
|
val := c.val
|
|
|
|
if c.operation == "#=" {
|
|
|
|
val = c.regexp.String()
|
|
|
|
} else if c.operation != "" {
|
|
|
|
val = fmt.Sprintf(`"%s"`, val)
|
|
|
|
}
|
2021-12-13 10:29:47 +01:00
|
|
|
|
|
|
|
ignoreCase := ""
|
|
|
|
|
|
|
|
if c.insensitive {
|
|
|
|
ignoreCase = " i"
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf(`[%s%s%s%s]`, c.key, c.operation, val, ignoreCase)
|
2020-10-24 12:40:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c relativePseudoClassSelector) String() string {
|
|
|
|
return fmt.Sprintf(":%s(%s)", c.name, c.match.String())
|
|
|
|
}
|
2021-12-13 10:29:47 +01:00
|
|
|
|
2020-10-24 12:40:39 +02:00
|
|
|
func (c containsPseudoClassSelector) String() string {
|
|
|
|
s := "contains"
|
|
|
|
if c.own {
|
|
|
|
s += "Own"
|
|
|
|
}
|
|
|
|
return fmt.Sprintf(`:%s("%s")`, s, c.value)
|
|
|
|
}
|
2021-12-13 10:29:47 +01:00
|
|
|
|
2020-10-24 12:40:39 +02:00
|
|
|
func (c regexpPseudoClassSelector) String() string {
|
|
|
|
s := "matches"
|
|
|
|
if c.own {
|
|
|
|
s += "Own"
|
|
|
|
}
|
|
|
|
return fmt.Sprintf(":%s(%s)", s, c.regexp.String())
|
|
|
|
}
|
2021-12-13 10:29:47 +01:00
|
|
|
|
2020-10-24 12:40:39 +02:00
|
|
|
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"
|
|
|
|
}
|
2021-12-13 10:29:47 +01:00
|
|
|
s := fmt.Sprintf("+%d", c.b)
|
|
|
|
if c.b < 0 { // avoid +-8 invalid syntax
|
|
|
|
s = strconv.Itoa(c.b)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf(":%s(%dn%s)", name, c.a, s)
|
2020-10-24 12:40:39 +02:00
|
|
|
}
|
2021-12-13 10:29:47 +01:00
|
|
|
|
2020-10-24 12:40:39 +02:00
|
|
|
func (c onlyChildPseudoClassSelector) String() string {
|
|
|
|
if c.ofType {
|
|
|
|
return ":only-of-type"
|
|
|
|
}
|
|
|
|
return ":only-child"
|
|
|
|
}
|
2021-12-13 10:29:47 +01:00
|
|
|
|
2020-10-24 12:40:39 +02:00
|
|
|
func (c inputPseudoClassSelector) String() string {
|
|
|
|
return ":input"
|
|
|
|
}
|
2021-12-13 10:29:47 +01:00
|
|
|
|
2020-10-24 12:40:39 +02:00
|
|
|
func (c emptyElementPseudoClassSelector) String() string {
|
|
|
|
return ":empty"
|
|
|
|
}
|
2021-12-13 10:29:47 +01:00
|
|
|
|
2020-10-24 12:40:39 +02:00
|
|
|
func (c rootPseudoClassSelector) String() string {
|
|
|
|
return ":root"
|
|
|
|
}
|
|
|
|
|
2021-12-13 10:29:47 +01:00
|
|
|
func (c linkPseudoClassSelector) String() string {
|
|
|
|
return ":link"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c langPseudoClassSelector) String() string {
|
|
|
|
return fmt.Sprintf(":lang(%s)", c.lang)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c neverMatchSelector) String() string {
|
|
|
|
return c.value
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c enabledPseudoClassSelector) String() string {
|
|
|
|
return ":enabled"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c disabledPseudoClassSelector) String() string {
|
|
|
|
return ":disabled"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c checkedPseudoClassSelector) String() string {
|
|
|
|
return ":checked"
|
|
|
|
}
|
|
|
|
|
2020-10-24 12:40:39 +02:00
|
|
|
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, ", ")
|
|
|
|
}
|