Skip to content

Commit

Permalink
feat: add Children and ChildNodes methods
Browse files Browse the repository at this point in the history
  • Loading branch information
rmkane committed Feb 26, 2025
1 parent 15733e6 commit e2ce71e
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 0 deletions.
36 changes: 36 additions & 0 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() { // ✅ Using helper function
children = append(children, child)
}
}
return children, nil
}

func (n *Node) sanitizedData(preserveSpaces bool) string {
if preserveSpaces {
return n.Data
Expand Down
91 changes: 91 additions & 0 deletions node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 := `<?procinst?>
<!--comment-->
Expand Down

0 comments on commit e2ce71e

Please sign in to comment.