From f72ceb9e3c7664a67c7f7d53f787ec0ada1c776c Mon Sep 17 00:00:00 2001 From: rmkane Date: Wed, 26 Feb 2025 11:38:48 -0500 Subject: [PATCH] feat: add Children and ChildNodes methods --- node.go | 36 +++++++++++++++++++++ node_test.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/node.go b/node.go index f864bc6..ba0ec09 100644 --- a/node.go +++ b/node.go @@ -142,6 +142,42 @@ func (n *Node) InnerText() string { return b.String() } +// IsElementLike returns true if the node type supports child nodes. +func (n *Node) IsElementLike() bool { + switch n.Type { + case ElementNode, DocumentNode, DeclarationNode: + return true + default: + return false + } +} + +// Children returns a slice of all direct child nodes of the current node. +// This includes all node types that are children, without filtering. +func (n *Node) Children() []*Node { + var children []*Node + for child := n.FirstChild; child != nil; child = child.NextSibling { + children = append(children, child) + } + return children +} + +// ChildNodes returns a slice of all direct child nodes of the current node (excluding attributes, text, comments, and char data). +// Returns an error if the node type does not support child nodes. +func (n *Node) ChildNodes() ([]*Node, error) { + if !n.IsElementLike() { + return nil, fmt.Errorf("node type %v does not support child nodes", n.Type) + } + + var children []*Node + for child := n.FirstChild; child != nil; child = child.NextSibling { + if child.IsElementLike() { + children = append(children, child) + } + } + return children, nil +} + func (n *Node) sanitizedData(preserveSpaces bool) string { if preserveSpaces { return n.Data diff --git a/node_test.go b/node_test.go index 7831274..ed20630 100644 --- a/node_test.go +++ b/node_test.go @@ -117,6 +117,44 @@ func verifyNodePointers(t *testing.T, n *Node) { testTrue(t, parent == nil || parent.LastChild == cur) } +func TestChildren(t *testing.T) { + t.Run("Has 3 children", func(t *testing.T) { + node := &Node{Type: ElementNode} + + AddChild(node, &Node{Type: CommentNode}) + AddChild(node, &Node{Type: ElementNode}) + AddChild(node, &Node{Type: TextNode}) + + children := node.Children() + + testTrue(t, len(children) == 3) + }) +} + +func TestChildNodes(t *testing.T) { + t.Run("Has 1 child node", func(t *testing.T) { + node := &Node{Type: ElementNode} + + AddChild(node, &Node{Type: CommentNode}) + AddChild(node, &Node{Type: ElementNode}) + AddChild(node, &Node{Type: TextNode}) + + children, err := node.ChildNodes() + + testTrue(t, err == nil) + testTrue(t, len(children) == 1) + }) + + t.Run("Cannot have child nodes", func(t *testing.T) { + node := &Node{Type: CommentNode} + + children, err := node.ChildNodes() + + testTrue(t, children == nil) + testTrue(t, err != nil) + }) +} + func TestAddAttr(t *testing.T) { for _, test := range []struct { name string @@ -232,6 +270,59 @@ func TestRemoveAttr(t *testing.T) { } } +func TestIsElementLike(t *testing.T) { + for _, test := range []struct { + name string + n *Node + expected bool + }{ + { + name: "DocumentNode can have children", + n: &Node{Type: DocumentNode}, + expected: true, + }, + { + name: "DeclarationNode can have children", + n: &Node{Type: DeclarationNode}, + expected: true, + }, + { + name: "ElementNode can have children", + n: &Node{Type: ElementNode}, + expected: true, + }, + { + name: "TextNode cannot have children", + n: &Node{Type: TextNode}, + expected: false, + }, + { + name: "CharDataNode cannot have children", + n: &Node{Type: CharDataNode}, + expected: false, + }, + { + name: "CommentNode cannot have children", + n: &Node{Type: CommentNode}, + expected: false, + }, + { + name: "AttributeNode cannot have children", + n: &Node{Type: AttributeNode}, + expected: false, + }, + { + name: "NotationNode cannot have children", + n: &Node{Type: NotationNode}, + expected: false, + }, + } { + t.Run(test.name, func(t *testing.T) { + testValue(t, test.n.IsElementLike(), test.expected) + }) + } +} + func TestRemoveFromTree(t *testing.T) { xml := `