Skip to content

Commit 41aaa0e

Browse files
bukzorclaude
andcommitted
Fix #160: Preserve CDATA content when converting XML to JSON
CDATA sections were being silently ignored during XML-to-JSON conversion because the NodeToJSON function only handled TextNode types, not CharDataNode. This caused data loss when using the -j flag on XML with CDATA sections. Changes: - Add CharDataNode handling to all text processing in jsonutil.go - Add test demonstrating CDATA content is preserved in JSON output 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 727399c commit 41aaa0e

File tree

2 files changed

+17
-5
lines changed

2 files changed

+17
-5
lines changed

cmd/root_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"testing"
1010

11+
"github.com/antchfx/xmlquery"
1112
"github.com/sibprogrammer/xq/internal/utils"
1213
"github.com/spf13/cobra"
1314
"github.com/spf13/pflag"
@@ -102,6 +103,17 @@ func TestRootCmd(t *testing.T) {
102103
assert.ErrorContains(t, err, "invalid argument")
103104
}
104105

106+
func TestCDATASupport(t *testing.T) {
107+
input := "<root><![CDATA[1 & 2]]></root>"
108+
doc, err := xmlquery.Parse(strings.NewReader(input))
109+
assert.Nil(t, err)
110+
111+
result := utils.NodeToJSON(doc, 10)
112+
expected := map[string]interface{}{"root": "1 & 2"}
113+
114+
assert.Equal(t, expected, result)
115+
}
116+
105117
func TestProcessAsJSON(t *testing.T) {
106118
tests := []struct {
107119
name string

internal/utils/jsonutil.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func NodeToJSON(node *xmlquery.Node, depth int) interface{} {
2121
var textParts []string
2222

2323
// Process the next sibling of the document node first (if any)
24-
if node.NextSibling != nil && node.NextSibling.Type == xmlquery.TextNode {
24+
if node.NextSibling != nil && (node.NextSibling.Type == xmlquery.TextNode || node.NextSibling.Type == xmlquery.CharDataNode) {
2525
text := strings.TrimSpace(node.NextSibling.Data)
2626
if text != "" {
2727
textParts = append(textParts, text)
@@ -34,7 +34,7 @@ func NodeToJSON(node *xmlquery.Node, depth int) interface{} {
3434
case xmlquery.ElementNode:
3535
childResult := nodeToJSONInternal(child, depth)
3636
result[child.Data] = childResult
37-
case xmlquery.TextNode:
37+
case xmlquery.TextNode, xmlquery.CharDataNode: // Text and CDATA
3838
text := strings.TrimSpace(child.Data)
3939
if text != "" {
4040
textParts = append(textParts, text)
@@ -50,7 +50,7 @@ func NodeToJSON(node *xmlquery.Node, depth int) interface{} {
5050
case xmlquery.ElementNode:
5151
return nodeToJSONInternal(node, depth)
5252

53-
case xmlquery.TextNode:
53+
case xmlquery.TextNode, xmlquery.CharDataNode: // Text and CDATA
5454
return strings.TrimSpace(node.Data)
5555

5656
default:
@@ -71,7 +71,7 @@ func nodeToJSONInternal(node *xmlquery.Node, depth int) interface{} {
7171
var textParts []string
7272
for child := node.FirstChild; child != nil; child = child.NextSibling {
7373
switch child.Type {
74-
case xmlquery.TextNode:
74+
case xmlquery.TextNode, xmlquery.CharDataNode: // Text and CDATA
7575
text := strings.TrimSpace(child.Data)
7676
if text != "" {
7777
textParts = append(textParts, text)
@@ -96,7 +96,7 @@ func getTextContent(node *xmlquery.Node) string {
9696
var parts []string
9797
for child := node.FirstChild; child != nil; child = child.NextSibling {
9898
switch child.Type {
99-
case xmlquery.TextNode:
99+
case xmlquery.TextNode, xmlquery.CharDataNode: // Text and CDATA
100100
text := strings.TrimSpace(child.Data)
101101
if text != "" {
102102
parts = append(parts, text)

0 commit comments

Comments
 (0)