From fc6dd5c83c96b4ff1d13dd96536758cb3f737171 Mon Sep 17 00:00:00 2001 From: Meir Blumenfeld <33358938+meblum@users.noreply.github.com> Date: Mon, 11 Aug 2025 22:19:31 -0400 Subject: [PATCH] fix: `WithCompactTypes` generate repeated child elements as slice Fixes #43 --- element.go | 48 +++++++++++++++++++++++++++++++++++++++++++++-- generator_test.go | 8 ++++---- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/element.go b/element.go index 65524da..dcaabce 100644 --- a/element.go +++ b/element.go @@ -212,11 +212,23 @@ func (e *element) writeGoType(w io.Writer, options *generateOptions, indentPrefi } fieldNames[exportedChildName] = struct{}{} + // Check for repeated/optional, considering compact types path. + _, repeated := e.repeatedChildren[childElement.name] + _, optional := e.optionalChildren[childElement.name] + + // For compact types, also check if any element along the compact path is repeated. + if options.compactTypes && !repeated { + targetChild := firstNotContainerElement(childElement) + if targetChild != childElement { + repeated = isRepeatedInCompactPath(childElement, targetChild) + } + } + fmt.Fprintf(w, "%s\t%s ", indentPrefix, exportedChildName) - if _, repeated := e.repeatedChildren[childElement.name]; repeated { + if repeated { fmt.Fprintf(w, "[]") } else if options.usePointersForOptionalFields { - if _, optional := e.optionalChildren[childElement.name]; optional { + if optional { fmt.Fprintf(w, "*") } } @@ -257,6 +269,38 @@ func firstNotContainerElement(el *element) *element { return el } +// isRepeatedInCompactPath checks if any element along the compact path from start to target is repeated. +func isRepeatedInCompactPath(start, target *element) bool { + current := start + for current != target && current.isContainer() { + for childName, childElement := range current.childElements { + if _, repeated := current.repeatedChildren[childName]; repeated { + return true + } + if childElement == target || isAncestorOf(childElement, target) { + current = childElement + break + } + } + } + return false +} + +// isAncestorOf checks if ancestor is an ancestor of descendant in the element tree. +func isAncestorOf(ancestor, descendant *element) bool { + if ancestor == descendant { + return true + } + if ancestor.isContainer() { + for _, child := range ancestor.childElements { + if isAncestorOf(child, descendant) { + return true + } + } + } + return false +} + func exportedName(el *element, options *generateOptions) string { return exportedNameWithoutSuffix(el, options) + options.elemNameSuffix } diff --git a/generator_test.go b/generator_test.go index 0f7e898..712e974 100644 --- a/generator_test.go +++ b/generator_test.go @@ -649,8 +649,8 @@ func TestGenerator(t *testing.T) { ` C struct {`, "\t\t\tCharData string `xml:\",chardata\"`", ` D struct {`, - "\t\t\t\tE struct{} `xml:\"e\"`", - "\t\t\t\tG struct{} `xml:\"f>g\"`", + "\t\t\t\tE struct{} `xml:\"e\"`", + "\t\t\t\tG []struct{} `xml:\"f>g\"`", "\t\t\t} `xml:\"d\"`", "\t\t} `xml:\"c\"`", "\t} `xml:\"b\"`", @@ -702,8 +702,8 @@ func TestGenerator(t *testing.T) { "}", "", "type D struct {", - "\tE struct{} `xml:\"e\"`", - "\tG struct{} `xml:\"f>g\"`", + "\tE struct{} `xml:\"e\"`", + "\tG []struct{} `xml:\"f>g\"`", "}", ), },