Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 74b75cf

Browse files
author
Carlana Johnson
committedJun 22, 2024·
x/net/html: add Node.All() and Node.ChildNodes()
Fixes #62113
1 parent 9617c63 commit 74b75cf

File tree

3 files changed

+130
-6
lines changed

3 files changed

+130
-6
lines changed
 

‎html/example_test.go

+4-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5+
//go:build goexperiment.rangefunc
6+
57
// This example demonstrates parsing HTML data and walking the resulting tree.
68
package html_test
79

@@ -19,8 +21,7 @@ func ExampleParse() {
1921
if err != nil {
2022
log.Fatal(err)
2123
}
22-
var f func(*html.Node)
23-
f = func(n *html.Node) {
24+
for n := range doc.All() {
2425
if n.Type == html.ElementNode && n.Data == "a" {
2526
for _, a := range n.Attr {
2627
if a.Key == "href" {
@@ -29,11 +30,8 @@ func ExampleParse() {
2930
}
3031
}
3132
}
32-
for c := n.FirstChild; c != nil; c = c.NextSibling {
33-
f(c)
34-
}
3533
}
36-
f(doc)
34+
3735
// Output:
3836
// foo
3937
// /bar/baz

‎html/iter.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build goexperiment.rangefunc
6+
7+
package html
8+
9+
import "iter"
10+
11+
// ChildNodes returns a sequence yielding the immediate children of n.
12+
//
13+
// Mutating a Node while iterating through its ChildNodes may have unexpected results.
14+
func (n *Node) ChildNodes() iter.Seq[*Node] {
15+
return func(yield func(*Node) bool) {
16+
if n == nil {
17+
return
18+
}
19+
for c := n.FirstChild; c != nil; c = c.NextSibling {
20+
if !yield(c) {
21+
return
22+
}
23+
}
24+
}
25+
26+
}
27+
28+
func (n *Node) all(yield func(*Node) bool) bool {
29+
if n == nil {
30+
return true
31+
}
32+
if !yield(n) {
33+
return false
34+
}
35+
36+
for c := range n.ChildNodes() {
37+
if !c.all(yield) {
38+
return false
39+
}
40+
}
41+
return true
42+
}
43+
44+
// All returns a sequence yielding all descendents of n in depth-first pre-order.
45+
//
46+
// Mutating a Node while iterating through it or its descendents may have unexpected results.
47+
func (n *Node) All() iter.Seq[*Node] {
48+
return func(yield func(*Node) bool) {
49+
n.all(yield)
50+
}
51+
}

‎html/iter_test.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build goexperiment.rangefunc
6+
7+
package html
8+
9+
import (
10+
"strings"
11+
"testing"
12+
)
13+
14+
func TestNode_ChildNodes(t *testing.T) {
15+
tests := []struct {
16+
in string
17+
want string
18+
}{
19+
{"", ""},
20+
{"<a></a>", ""},
21+
{"<a><b></b></a>", "b"},
22+
{"<a>b</a>", "b"},
23+
{"<a><!--b--></a>", "b"},
24+
{"<a>b<c></c>d</a>", "b c d"},
25+
{"<a>b<c><!--d--></c>e</a>", "b c e"},
26+
{"<a><b><c>d<!--e-->f</c></b>g<!--h--><i>j</i></a>", "b g h i"},
27+
}
28+
for _, test := range tests {
29+
doc, err := Parse(strings.NewReader(test.in))
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
// Drill to <html><head></head><body> test.in
34+
n := doc.FirstChild.FirstChild.NextSibling.FirstChild
35+
var results []string
36+
for c := range n.ChildNodes() {
37+
results = append(results, c.Data)
38+
}
39+
if got := strings.Join(results, " "); got != test.want {
40+
t.Errorf("unexpected children yielded by ChildNodes; want: %q got: %q", test.want, got)
41+
}
42+
}
43+
}
44+
45+
func TestNode_All(t *testing.T) {
46+
tests := []struct {
47+
in string
48+
want string
49+
}{
50+
{"", ""},
51+
{"<a></a>", "a"},
52+
{"<a><b></b></a>", "a b"},
53+
{"<a>b</a>", "a b"},
54+
{"<a><!--b--></a>", "a b"},
55+
{"<a>b<c></c>d</a>", "a b c d"},
56+
{"<a>b<c><!--d--></c>e</a>", "a b c d e"},
57+
{"<a><b><c>d<!--e-->f</c></b>g<!--h--><i>j</i></a>", "a b c d e f g h i j"},
58+
}
59+
for _, test := range tests {
60+
doc, err := Parse(strings.NewReader(test.in))
61+
if err != nil {
62+
t.Fatal(err)
63+
}
64+
// Drill to <html><head></head><body> test.in
65+
n := doc.FirstChild.FirstChild.NextSibling.FirstChild
66+
var results []string
67+
for c := range n.All() {
68+
results = append(results, c.Data)
69+
}
70+
if got := strings.Join(results, " "); got != test.want {
71+
t.Errorf("unexpected children yielded by All; want: %q got: %q",
72+
test.want, got)
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)
Please sign in to comment.