Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func LoadConfig(fileName string) error {
config.NoColor, _ = strconv.ParseBool(value)
case "color":
config.Color, _ = strconv.ParseBool(value)
default:
// Ignore unknown config options for forward compatibility
}
}

Expand Down
31 changes: 26 additions & 5 deletions internal/utils/jsonutil.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package utils

import (
"fmt"
"strings"

"github.com/antchfx/xmlquery"
Expand Down Expand Up @@ -34,11 +35,16 @@ func NodeToJSON(node *xmlquery.Node, depth int) interface{} {
case xmlquery.ElementNode:
childResult := nodeToJSONInternal(child, depth)
result[child.Data] = childResult
case xmlquery.TextNode:
case xmlquery.TextNode, xmlquery.CharDataNode:
text := strings.TrimSpace(child.Data)
if text != "" {
textParts = append(textParts, text)
}
case xmlquery.CommentNode, xmlquery.DeclarationNode, xmlquery.ProcessingInstruction, xmlquery.NotationNode:
// Skip these in JSON output
default:
// Should be impossible: all valid child node types handled above
panic(fmt.Sprintf("unknown NodeType as child of DocumentNode: %v", child.Type))
}
}

Expand All @@ -50,11 +56,16 @@ func NodeToJSON(node *xmlquery.Node, depth int) interface{} {
case xmlquery.ElementNode:
return nodeToJSONInternal(node, depth)

case xmlquery.TextNode:
case xmlquery.TextNode, xmlquery.CharDataNode:
return strings.TrimSpace(node.Data)

default:
case xmlquery.CommentNode:
// Comments passed as root, return empty
return nil

default:
// Should be impossible: DocumentNode, ElementNode, TextNode, CharDataNode, CommentNode are the only valid root nodes
panic(fmt.Sprintf("unknown NodeType passed to NodeToJSON: %v", node.Type))
}
}

Expand All @@ -71,14 +82,19 @@ func nodeToJSONInternal(node *xmlquery.Node, depth int) interface{} {
var textParts []string
for child := node.FirstChild; child != nil; child = child.NextSibling {
switch child.Type {
case xmlquery.TextNode:
case xmlquery.TextNode, xmlquery.CharDataNode:
text := strings.TrimSpace(child.Data)
if text != "" {
textParts = append(textParts, text)
}
case xmlquery.ElementNode:
childResult := nodeToJSONInternal(child, depth-1)
addToResult(result, child.Data, childResult)
case xmlquery.CommentNode, xmlquery.ProcessingInstruction:
// Skip these in JSON output
default:
// Should be impossible: all valid element child types handled above
panic(fmt.Sprintf("unknown NodeType as child of ElementNode: %v", child.Type))
}
}

Expand All @@ -96,13 +112,18 @@ func getTextContent(node *xmlquery.Node) string {
var parts []string
for child := node.FirstChild; child != nil; child = child.NextSibling {
switch child.Type {
case xmlquery.TextNode:
case xmlquery.TextNode, xmlquery.CharDataNode:
text := strings.TrimSpace(child.Data)
if text != "" {
parts = append(parts, text)
}
case xmlquery.ElementNode:
parts = append(parts, getTextContent(child))
case xmlquery.CommentNode, xmlquery.ProcessingInstruction:
// Skip these when extracting text
default:
// Should be impossible: all valid element child types handled above
panic(fmt.Sprintf("unknown NodeType in getTextContent: %v", child.Type))
}
}
return strings.Join(parts, "\n")
Expand Down
39 changes: 39 additions & 0 deletions internal/utils/jsonutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,42 @@ func TestXmlToJSON(t *testing.T) {
assert.Equal(t, expectedJson, output.String())
}
}

func TestExhaustiveNodeTypeHandling(t *testing.T) {
// Test that all xmlquery node types are handled without panicking
// This verifies our exhaustive switch statements work correctly

xmlInput := `<?xml version="1.0"?>
<!DOCTYPE root>
<!-- This is a comment -->
<root>
<element>text content</element>
<cdata><![CDATA[raw & unescaped < > content]]></cdata>
<!-- another comment inside -->
<?processing-instruction data?>
<mixed>text<child>more</child>tail</mixed>
</root>`

node, err := xmlquery.Parse(strings.NewReader(xmlInput))
assert.NoError(t, err)

// Should not panic - this exercises all the node types
result := NodeToJSON(node, -1)
assert.NotNil(t, result)

// Verify the result is a map
resultMap, ok := result.(map[string]interface{})
assert.True(t, ok)

// Verify root element exists
root, ok := resultMap["root"]
assert.True(t, ok)

rootMap, ok := root.(map[string]interface{})
assert.True(t, ok)

// Verify CDATA is preserved as text
cdataElem, ok := rootMap["cdata"]
assert.True(t, ok)
assert.Contains(t, cdataElem, "raw & unescaped")
}
13 changes: 13 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ func FormatXml(reader io.Reader, writer io.Writer, indent string, colors int) er
_, _ = fmt.Fprint(writer, tagColor("<!"), string(typedToken), tagColor(">"))
_, _ = fmt.Fprint(writer, newline, strings.Repeat(indent, level))
default:
// Should be impossible: all xml.Token types handled above
panic(fmt.Sprintf("unknown xml.Token type: %T", token))
}
}

Expand Down Expand Up @@ -403,6 +405,9 @@ func FormatHtml(reader io.Reader, writer io.Writer, indent string, colors int) e
if level == 0 {
_, _ = fmt.Fprint(writer, newline)
}
default:
// Should be impossible: all html.TokenType values handled above
panic(fmt.Sprintf("unknown html.TokenType: %v", token))
}
}

Expand Down Expand Up @@ -469,6 +474,9 @@ func FormatJson(reader io.Reader, writer io.Writer, indent string, colors int) e
level--
}
_, _ = fmt.Fprint(writer, newline, strings.Repeat(indent, level), tagColor("]"))
default:
// Should be impossible: json.Delim can only be '{', '}', '[', ']'
panic(fmt.Sprintf("unknown json.Delim: %v", tokenType))
}
case string:
escapedToken := strconv.Quote(token.(string))
Expand All @@ -485,6 +493,9 @@ func FormatJson(reader io.Reader, writer io.Writer, indent string, colors int) e
_, _ = fmt.Fprintf(writer, "%s%v", prefix, valueColor(token))
case nil:
_, _ = fmt.Fprintf(writer, "%s%s", prefix, valueColor("null"))
default:
// Should be impossible: all json.Token types handled above
panic(fmt.Sprintf("unknown json.Token type: %T", token))
}

switch tokenState {
Expand All @@ -494,6 +505,8 @@ func FormatJson(reader io.Reader, writer io.Writer, indent string, colors int) e
suffix = "," + newline + strings.Repeat(indent, level)
case jsonTokenArrayComma:
suffix = "," + newline + strings.Repeat(indent, level)
default:
// Other token states don't affect suffix formatting
}

prefix = suffix
Expand Down