From 41a5c2214962b75aefdf1de6562ddb6bd54e43b2 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 24 May 2026 22:30:18 +0200 Subject: [PATCH] perf: replace rxClassTrim regexp with strings.Replacer The regex [\t\r\n] only does single-byte substitution, which is exactly what strings.Replacer's byteReplacer is optimized for. This removes the regexp dependency from property.go and drops the redundant ContainsAny fast-path guard in HasClass, since byteReplacer already returns the original string unchanged (no allocation) when nothing matches. Benchmark shows ~18% improvement on HasClass with 0 allocs either way. --- property.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/property.go b/property.go index 918e0bd..8b0e12f 100644 --- a/property.go +++ b/property.go @@ -1,13 +1,12 @@ package goquery import ( - "regexp" "strings" "golang.org/x/net/html" ) -var rxClassTrim = regexp.MustCompile("[\t\r\n]") +var classTrimReplacer = strings.NewReplacer("\t", " ", "\r", " ", "\n", " ") // Attr gets the specified attribute's value for the first element in the // Selection. To get the value for each element individually, use a looping @@ -140,9 +139,14 @@ func (s *Selection) AddClass(class ...string) *Selection { func (s *Selection) HasClass(class string) bool { class = " " + class + " " for _, n := range s.Nodes { - classes, _ := getClassesAndAttr(n, false) - if strings.Contains(classes, class) { - return true + if n.Type != html.ElementNode { + continue + } + if attr := getAttributePtr("class", n); attr != nil { + val := classTrimReplacer.Replace(attr.Val) + if strings.Contains(" "+val+" ", class) { + return true + } } } return false @@ -243,17 +247,15 @@ func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.At if attr == nil { classes = " " - } else if strings.ContainsAny(attr.Val, "\t\r\n") { - classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ") } else { - classes = " " + attr.Val + " " + classes = classTrimReplacer.Replace(" " + attr.Val + " ") } return } func getClassesSlice(classes string) []string { - return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ") + return strings.Split(classTrimReplacer.Replace(" "+classes+" "), " ") } func removeAttr(n *html.Node, attrName string) {