Skip to content

Commit

Permalink
x/net/html: add Node.All() and Node.ChildNodes()
Browse files Browse the repository at this point in the history
Fixes #62113
  • Loading branch information
Carlana Johnson authored and earthboundkid committed Oct 28, 2024
1 parent 4783315 commit 37b10a0
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 6 deletions.
10 changes: 4 additions & 6 deletions html/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build goexperiment.rangefunc

// This example demonstrates parsing HTML data and walking the resulting tree.
package html_test

Expand All @@ -19,8 +21,7 @@ func ExampleParse() {
if err != nil {
log.Fatal(err)
}
var f func(*html.Node)
f = func(n *html.Node) {
for n := range doc.All() {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "href" {
Expand All @@ -29,11 +30,8 @@ func ExampleParse() {
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(doc)

// Output:
// foo
// /bar/baz
Expand Down
51 changes: 51 additions & 0 deletions html/iter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build goexperiment.rangefunc

package html

import "iter"

// ChildNodes returns a sequence yielding the immediate children of n.
//
// Mutating a Node while iterating through its ChildNodes may have unexpected results.
func (n *Node) ChildNodes() iter.Seq[*Node] {
return func(yield func(*Node) bool) {
if n == nil {
return
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
if !yield(c) {
return
}
}
}

}

func (n *Node) all(yield func(*Node) bool) bool {
if n == nil {
return true
}
if !yield(n) {
return false
}

for c := range n.ChildNodes() {
if !c.all(yield) {
return false
}
}
return true
}

// All returns a sequence yielding all descendents of n in depth-first pre-order.
//
// Mutating a Node while iterating through it or its descendents may have unexpected results.
func (n *Node) All() iter.Seq[*Node] {
return func(yield func(*Node) bool) {
n.all(yield)
}
}
75 changes: 75 additions & 0 deletions html/iter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build goexperiment.rangefunc

package html

import (
"strings"
"testing"
)

func TestNode_ChildNodes(t *testing.T) {
tests := []struct {
in string
want string
}{
{"", ""},
{"<a></a>", ""},
{"<a><b></b></a>", "b"},
{"<a>b</a>", "b"},
{"<a><!--b--></a>", "b"},
{"<a>b<c></c>d</a>", "b c d"},
{"<a>b<c><!--d--></c>e</a>", "b c e"},
{"<a><b><c>d<!--e-->f</c></b>g<!--h--><i>j</i></a>", "b g h i"},
}
for _, test := range tests {
doc, err := Parse(strings.NewReader(test.in))
if err != nil {
t.Fatal(err)
}
// Drill to <html><head></head><body> test.in
n := doc.FirstChild.FirstChild.NextSibling.FirstChild
var results []string
for c := range n.ChildNodes() {
results = append(results, c.Data)
}
if got := strings.Join(results, " "); got != test.want {
t.Errorf("unexpected children yielded by ChildNodes; want: %q got: %q", test.want, got)
}
}
}

func TestNode_All(t *testing.T) {
tests := []struct {
in string
want string
}{
{"", ""},
{"<a></a>", "a"},
{"<a><b></b></a>", "a b"},
{"<a>b</a>", "a b"},
{"<a><!--b--></a>", "a b"},
{"<a>b<c></c>d</a>", "a b c d"},
{"<a>b<c><!--d--></c>e</a>", "a b c d e"},
{"<a><b><c>d<!--e-->f</c></b>g<!--h--><i>j</i></a>", "a b c d e f g h i j"},
}
for _, test := range tests {
doc, err := Parse(strings.NewReader(test.in))
if err != nil {
t.Fatal(err)
}
// Drill to <html><head></head><body> test.in
n := doc.FirstChild.FirstChild.NextSibling.FirstChild
var results []string
for c := range n.All() {
results = append(results, c.Data)
}
if got := strings.Join(results, " "); got != test.want {
t.Errorf("unexpected children yielded by All; want: %q got: %q",
test.want, got)
}
}
}

0 comments on commit 37b10a0

Please sign in to comment.