mirror of
https://github.com/PuerkitoBio/goquery.git
synced 2026-10-19 16:15:33 +00:00
Merge pull request #612 from sergioperezcheco/fix/class-ascii-token-boundaries
Fix class helpers splitting non-ASCII whitespace class names
This commit is contained in:
+8
-5
@@ -108,7 +108,7 @@ func (s *Selection) Html() (ret string, e error) {
|
||||
// AddClass adds the given class(es) to each element in the set of matched elements.
|
||||
// Multiple class names can be specified, separated by a space or via multiple arguments.
|
||||
func (s *Selection) AddClass(class ...string) *Selection {
|
||||
classStr := strings.TrimSpace(strings.Join(class, " "))
|
||||
classStr := strings.Trim(strings.Join(class, " "), " \t\r\n\f")
|
||||
|
||||
if classStr == "" {
|
||||
return s
|
||||
@@ -157,7 +157,7 @@ func (s *Selection) HasClass(class string) bool {
|
||||
func (s *Selection) RemoveClass(class ...string) *Selection {
|
||||
var rclasses []string
|
||||
|
||||
classStr := strings.TrimSpace(strings.Join(class, " "))
|
||||
classStr := strings.Trim(strings.Join(class, " "), " \t\r\n\f")
|
||||
remove := classStr == ""
|
||||
|
||||
if !remove {
|
||||
@@ -183,7 +183,7 @@ func (s *Selection) RemoveClass(class ...string) *Selection {
|
||||
// ToggleClass adds or removes the given class(es) for each element in the set of matched elements.
|
||||
// Multiple class names can be specified, separated by a space or via multiple arguments.
|
||||
func (s *Selection) ToggleClass(class ...string) *Selection {
|
||||
classStr := strings.TrimSpace(strings.Join(class, " "))
|
||||
classStr := strings.Trim(strings.Join(class, " "), " \t\r\n\f")
|
||||
|
||||
if classStr == "" {
|
||||
return s
|
||||
@@ -258,7 +258,10 @@ func getClassesAndAttr(n *html.Node) (classes string, attr *html.Attribute) {
|
||||
}
|
||||
|
||||
func getClassesSlice(classes string) []string {
|
||||
return strings.Fields(classes)
|
||||
// HTML class tokens are separated by ASCII whitespace, not Unicode whitespace.
|
||||
return strings.FieldsFunc(classes, func(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\r' || r == '\n' || r == '\f'
|
||||
})
|
||||
}
|
||||
|
||||
func removeAttr(n *html.Node, attrName string) {
|
||||
@@ -272,7 +275,7 @@ func removeAttr(n *html.Node, attrName string) {
|
||||
}
|
||||
|
||||
func setClasses(n *html.Node, attr *html.Attribute, classes string) {
|
||||
classes = strings.TrimSpace(classes)
|
||||
classes = strings.Trim(classes, " \t\r\n\f")
|
||||
if classes == "" {
|
||||
removeAttr(n, "class")
|
||||
return
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClassOperationsPreserveNonASCIIWhitespace(t *testing.T) {
|
||||
for _, whitespace := range []string{"\u00a0", "\u2003", "\u202f", "\v"} {
|
||||
for _, class := range []string{"a" + whitespace + "b", whitespace + "a", "a" + whitespace, whitespace} {
|
||||
for _, op := range []struct {
|
||||
name string
|
||||
run func(*Selection, string) *Selection
|
||||
want string
|
||||
}{
|
||||
{"add", func(s *Selection, c string) *Selection { return s.AddClass(c) }, class + " keep"},
|
||||
{"remove", func(s *Selection, c string) *Selection { return s.RemoveClass(c) }, "keep"},
|
||||
{"toggle", func(s *Selection, c string) *Selection { return s.ToggleClass(c) }, "keep"},
|
||||
} {
|
||||
t.Run(op.name+"/"+class, func(t *testing.T) {
|
||||
doc, err := NewDocumentFromReader(strings.NewReader("<div></div>"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sel := doc.Find("div").SetAttr("class", class+" keep")
|
||||
if !sel.HasClass(class) {
|
||||
t.Fatal("existing class not recognized")
|
||||
}
|
||||
if op.run(sel, "\t"+class+"\r\n") != sel {
|
||||
t.Fatal("lost chaining")
|
||||
}
|
||||
if got := sel.AttrOr("class", ""); got != op.want {
|
||||
t.Fatalf("class = %q, want %q", got, op.want)
|
||||
}
|
||||
markup, err := OuterHtml(sel)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reopened, err := NewDocumentFromReader(strings.NewReader(markup))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := reopened.Find("div").AttrOr("class", ""); got != op.want {
|
||||
t.Fatalf("round trip = %q, want %q", got, op.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassTokensASCIIWhitespace(t *testing.T) {
|
||||
for _, sep := range []string{" ", "\t", "\r", "\n", "\f"} {
|
||||
got := getClassesSlice(sep + "a\u00a0b" + sep + sep + "keep" + sep)
|
||||
if len(got) != 2 || got[0] != "a\u00a0b" || got[1] != "keep" {
|
||||
t.Errorf("separator %q: %q", sep, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassOperationsKeepUnicodeAtAttributeEdges(t *testing.T) {
|
||||
doc, err := NewDocumentFromReader(strings.NewReader("<div></div>"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sel := doc.Find("div")
|
||||
sel.AddClass("\u00a0a\u00a0").AddClass("keep").RemoveClass("keep")
|
||||
if got := sel.AttrOr("class", ""); got != "\u00a0a\u00a0" {
|
||||
t.Fatalf("class = %q", got)
|
||||
}
|
||||
sel.RemoveClass()
|
||||
if _, exists := sel.Attr("class"); exists {
|
||||
t.Fatal("RemoveClass() did not clear attribute")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user