From 8c878b861a719494dfe24f301da1d7f64634ecdb Mon Sep 17 00:00:00 2001 From: SoMuchForSubtlety Date: Sat, 7 Aug 2021 00:51:30 +0200 Subject: [PATCH] improve anonymous type handling --- gentests/_testgen/testgen.go | 5 +- xmltree/compare.go | 4 +- xmltree/example_test.go | 10 +-- xmltree/marshal.go | 4 +- xmltree/xmltree.go | 6 +- xmltree/xmltree_test.go | 6 +- xsd/parse.go | 125 ++++++++++++++++++++++---------- xsd/search.go | 2 +- xsd/walk.go | 2 +- xsd/xsd.go | 2 +- xsdgen/config.go | 10 +-- xsdgen/testdata/anon.go.golden | 15 ++++ xsdgen/testdata/anon.xsd | 38 ++++++++++ xsdgen/testdata/anon2.go.golden | 19 +++++ xsdgen/testdata/anon2.xsd | 38 ++++++++++ xsdgen/xsdgen.go | 4 +- 16 files changed, 223 insertions(+), 67 deletions(-) create mode 100644 xsdgen/testdata/anon.go.golden create mode 100644 xsdgen/testdata/anon.xsd create mode 100644 xsdgen/testdata/anon2.go.golden create mode 100644 xsdgen/testdata/anon2.xsd diff --git a/gentests/_testgen/testgen.go b/gentests/_testgen/testgen.go index 8ceb31f..adb461b 100644 --- a/gentests/_testgen/testgen.go +++ b/gentests/_testgen/testgen.go @@ -174,7 +174,6 @@ func genXSDTests(cfg xsdgen.Config, data []byte, pkg string) (code, tests *ast.F xmltree.MarshalIndent(inputTree, "", " ")) } `, params).Decl() - if err != nil { return nil, nil, err } @@ -186,10 +185,10 @@ type Element struct { Name, Type xml.Name } -func topLevelElements(root *xmltree.Element) []Element { +func topLevelElements(root *xmltree.Element) []*Element { const schemaNS = "http://www.w3.org/2001/XMLSchema" - result := make([]Element, 0) + result := make([]*Element, 0) root = &xmltree.Element{Scope: root.Scope, Children: []xmltree.Element{*root}} for _, schema := range root.Search(schemaNS, "schema") { tns := schema.Attr("", "targetNamespace") diff --git a/xmltree/compare.go b/xmltree/compare.go index 4e31dbd..47335d6 100644 --- a/xmltree/compare.go +++ b/xmltree/compare.go @@ -12,7 +12,7 @@ func Equal(a, b *Element) bool { return equal(a, b, 0) } -type byName []Element +type byName []*Element func (l byName) Len() int { return len(l) } func (l byName) Less(i, j int) bool { @@ -37,7 +37,7 @@ func equal(a, b *Element, depth int) bool { sort.Sort(byName(a.Children)) sort.Sort(byName(b.Children)) for i := range a.Children { - if !equal(&a.Children[i], &b.Children[i], depth+1) { + if !equal(a.Children[i], b.Children[i], depth+1) { return false } } diff --git a/xmltree/example_test.go b/xmltree/example_test.go index d8ffa00..91ad831 100644 --- a/xmltree/example_test.go +++ b/xmltree/example_test.go @@ -119,7 +119,7 @@ func ExampleElement_SearchFunc() { } func ExampleUnmarshal() { - var input = []byte(` + input := []byte(` Page title edit=sysop:move=sysop @@ -178,7 +178,7 @@ func ExampleUnmarshal() { } func ExampleMarshal() { - var input = []byte(` + input := []byte(` @@ -200,7 +200,7 @@ func ExampleMarshal() { `) - var chapters []xmltree.Element + var chapters []*xmltree.Element root, err := xmltree.Parse(input) if err != nil { log.Fatal(err) @@ -211,7 +211,7 @@ func ExampleMarshal() { el.Content = child.Content } el.Children = nil - chapters = append(chapters, *el) + chapters = append(chapters, el) } root.Children = chapters fmt.Printf("%s\n", xmltree.MarshalIndent(root, "", " ")) @@ -226,7 +226,7 @@ func ExampleMarshal() { } func ExampleMarshalNested() { - var input = []byte(` + input := []byte(` diff --git a/xmltree/marshal.go b/xmltree/marshal.go index ca949fe..a7a975e 100644 --- a/xmltree/marshal.go +++ b/xmltree/marshal.go @@ -103,7 +103,7 @@ func (e *encoder) encode(el, parent *Element, visited map[*Element]struct{}) err } for i := range el.Children { visited[el] = struct{}{} - if err := e.encode(&el.Children[i], el, visited); err != nil { + if err := e.encode(el.Children[i], el, visited); err != nil { return err } delete(visited, el) @@ -139,7 +139,7 @@ func (e *encoder) encodeOpenTag(el *Element, scope Scope, depth int) error { io.WriteString(e.w, e.indent) } } - var tag = struct { + tag := struct { *Element NS []xml.Name }{Element: el, NS: scope.ns} diff --git a/xmltree/xmltree.go b/xmltree/xmltree.go index 47567f4..a859912 100644 --- a/xmltree/xmltree.go +++ b/xmltree/xmltree.go @@ -48,7 +48,7 @@ type Element struct { // end tags. Uses the underlying byte array passed to Parse. Content []byte // Sub-elements contained within this element. - Children []Element + Children []*Element } // Attr gets the value of the first attribute whose name matches the @@ -262,7 +262,7 @@ walk: for scanner.scan() { switch tok := scanner.tok.(type) { case xml.StartElement: - child := Element{StartElement: tok.Copy(), Scope: el.Scope} + child := &Element{StartElement: tok.Copy(), Scope: el.Scope} if err := child.parse(scanner, data, depth+1); err != nil { return err } @@ -284,7 +284,7 @@ walk: // immediately. func (el *Element) walk(fn walkFunc) error { for i := 0; i < len(el.Children); i++ { - fn(&el.Children[i]) + fn(el.Children[i]) } return nil } diff --git a/xmltree/xmltree_test.go b/xmltree/xmltree_test.go index ba62701..88c1fb1 100644 --- a/xmltree/xmltree_test.go +++ b/xmltree/xmltree_test.go @@ -235,10 +235,10 @@ func TestModification(t *testing.T) { root := parseDoc(t, from) // Remove any non-
  • children from all
      elements // in the document. - valid := make([]Element, 0, len(root.Children)) + valid := make([]*Element, 0, len(root.Children)) for _, p := range root.Search("", "li") { t.Logf("%#v", *p) - valid = append(valid, *p) + valid = append(valid, p) } root.Children = valid if s := root.String(); s != to { @@ -249,7 +249,7 @@ func TestModification(t *testing.T) { func TestStringPreserveNS(t *testing.T) { root := parseDoc(t, exampleDoc) var doc []byte - var descent = 4 + descent := 4 for _, el := range root.Flatten() { descent-- if descent <= 0 { diff --git a/xsd/parse.go b/xsd/parse.go index 0e1ac4f..d6ba14f 100644 --- a/xsd/parse.go +++ b/xsd/parse.go @@ -18,7 +18,7 @@ func hasCycle(root *xmltree.Element, visited map[*xmltree.Element]struct{}) bool } visited[root] = struct{}{} for i := range root.Children { - el := &root.Children[i] + el := root.Children[i] if _, ok := visited[el]; ok { return true } @@ -109,6 +109,7 @@ func Normalize(docs ...[]byte) ([]*xmltree.Element, error) { elementDefaultType(root) copyEltNamesToAnonTypes(root) } + typeCounter := 0 for _, root := range result { if err := nameAnonymousTypes(root, &typeCounter); err != nil { @@ -203,12 +204,12 @@ to */ func copyEltNamesToAnonTypes(root *xmltree.Element) { - used := make(map[xml.Name]struct{}) + used := make(map[xml.Name]xmltree.Element) tns := root.Attr("", "targetNamespace") namedTypes := and(isType, hasAttr("", "name")) for _, el := range root.SearchFunc(namedTypes) { - used[el.ResolveDefault(el.Attr("", "name"), tns)] = struct{}{} + used[el.ResolveDefault(el.Attr("", "name"), tns)] = *el } eltWithAnonType := and( @@ -216,23 +217,63 @@ func copyEltNamesToAnonTypes(root *xmltree.Element) { hasAttr("", "name"), hasAnonymousType) - for _, el := range root.SearchFunc(eltWithAnonType) { - // Make sure we can use this element's name - xmlname := el.ResolveDefault(el.Attr("", "name"), tns) - if _, ok := used[xmlname]; ok { - continue + withAnonElems := root.SearchFunc(eltWithAnonType) + for _, parentElem := range withAnonElems { + // create a copy of the original element so we can compare it later in its original state + cpy := *parentElem + // clone slices so they can't be modified + clonedContent := make([]byte, len(cpy.Content)) + copy(clonedContent, cpy.Content) + cpy.Content = clonedContent + clonedAttrs := make([]xml.Attr, len(cpy.StartElement.Attr)) + copy(clonedAttrs, cpy.StartElement.Attr) + cpy.StartElement.Attr = clonedAttrs + + xmlname := parentElem.ResolveDefault(parentElem.Attr("", "name"), tns) + tmpName := xmlname + var offset int + prevMatch := false + for { + // check if the name is already in use + if anonChild, ok := used[tmpName]; ok { + // if the name is in use, check if the elements area identical + if xmltree.Equal(&anonChild, parentElem) { + prevMatch = true + if offset > 0 { + anonChild.SetAttr("", "name", anonChild.Prefix(tmpName)) + } + break + } + // try again with an integer suffix (Name, Name1, Name2, etc.) until we find a free name + offset++ + tmpName.Local = xmlname.Local + strconv.Itoa(offset) + } else { + // set the name once we find an unused one + if offset > 0 { + anonChild.SetAttr("", "name", anonChild.Prefix(tmpName)) + } + break + } } - used[xmlname] = struct{}{} - for i, t := range el.Children { - if !isAnonymousType(&t) { + // mark the name as used by the element + used[tmpName] = cpy + + for i, anonChild := range parentElem.Children { + if !isAnonymousType(anonChild) { continue } - t.SetAttr("", "name", el.Attr("", "name")) - el.SetAttr("", "type", el.Prefix(xmlname)) + anonChild.SetAttr("", "name", parentElem.Prefix(tmpName)) + parentElem.SetAttr("", "type", parentElem.Prefix(tmpName)) - el.Children = append(el.Children[:i], el.Children[i+1:]...) - el.Content = nil - root.Children = append(root.Children, t) + // remove the anonymous child element from its parent + parentElem.Children = append(parentElem.Children[:i], parentElem.Children[i+1:]...) + parentElem.Content = nil + + // don't add the same anon type twice + if !prevMatch { + // add the anonymous type as an element on root level + root.Children = append(root.Children, anonChild) + } break } } @@ -241,7 +282,6 @@ func copyEltNamesToAnonTypes(root *xmltree.Element) { // Inside a , set all children to optional // If child is a set its children to optional func setChoicesOptional(root *xmltree.Element) error { - for _, el := range root.SearchFunc(isElem(schemaNS, "choice")) { for i := 0; i < len(el.Children); i++ { t := el.Children[i] @@ -294,7 +334,9 @@ func nameAnonymousTypes(root *xmltree.Element, typeCounter *int) error { accum bool ) targetNS := root.Attr("", "targetNamespace") - for _, el := range root.SearchFunc(hasAnonymousType) { + + anonElementParents := root.SearchFunc(hasAnonymousType) + for _, el := range anonElementParents { if el.Name.Space != schemaNS { continue } @@ -318,7 +360,7 @@ func nameAnonymousTypes(root *xmltree.Element, typeCounter *int) error { for i := 0; i < len(el.Children); i++ { t := el.Children[i] - if !isAnonymousType(&t) { + if !isAnonymousType(t) { continue } *typeCounter++ @@ -406,7 +448,7 @@ func deref(ref, real *xmltree.Element) *xmltree.Element { el.Name = real.Name el.StartElement.Attr = append([]xml.Attr{}, real.StartElement.Attr...) el.Content = append([]byte{}, real.Content...) - el.Children = append([]xmltree.Element{}, real.Children...) + el.Children = append([]*xmltree.Element{}, real.Children...) // Some attributes can contain a qname, and must be converted to use the // xmlns prefixes in ref's scope. @@ -444,9 +486,9 @@ func unpackGroups(doc *xmltree.Element) { hasGroups := hasChild(isGroup) for _, el := range doc.SearchFunc(hasGroups) { - children := make([]xmltree.Element, 0, len(el.Children)) + children := make([]*xmltree.Element, 0, len(el.Children)) for _, c := range el.Children { - if isGroup(&c) { + if isGroup(c) { children = append(children, c.Children...) } else { children = append(children, c) @@ -464,10 +506,10 @@ func expandComplexShorthand(root *xmltree.Element) { Loop: for _, el := range root.SearchFunc(isComplexType) { - newChildren := make([]xmltree.Element, 0, len(el.Children)) - restrict := xmltree.Element{ + newChildren := make([]*xmltree.Element, 0, len(el.Children)) + restrict := &xmltree.Element{ Scope: el.Scope, - Children: make([]xmltree.Element, 0, len(el.Children)), + Children: make([]*xmltree.Element, 0, len(el.Children)), } for _, child := range el.Children { @@ -488,9 +530,9 @@ Loop: restrict.Name.Local = "restriction" restrict.SetAttr("", "base", restrict.Prefix(AnyType.Name())) - content := xmltree.Element{ + content := &xmltree.Element{ Scope: el.Scope, - Children: []xmltree.Element{restrict}, + Children: []*xmltree.Element{restrict}, } content.Name.Space = schemaNS content.Name.Local = "complexContent" @@ -626,7 +668,8 @@ func (s *Schema) parseTypes(root *xmltree.Element) (err error) { } func (s *Schema) parseSelfType(root *xmltree.Element) *ComplexType { - self := *root + tmp1 := *root + self := &tmp1 self.Content = nil self.Children = nil for _, el := range root.Children { @@ -634,12 +677,14 @@ func (s *Schema) parseSelfType(root *xmltree.Element) *ComplexType { self.Children = append(self.Children, el) } } - newdoc := self + // copy from pointer and back, to avoid cycle + tmp2 := *self + newdoc := &tmp2 self.Name.Local = "complexType" self.SetAttr("", "name", "_self") - newdoc.Children = []xmltree.Element{self} - expandComplexShorthand(&newdoc) - return s.parseComplexType(&newdoc.Children[0]) + newdoc.Children = []*xmltree.Element{self} + expandComplexShorthand(newdoc) + return s.parseComplexType(newdoc.Children[0]) } // http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-complexType @@ -719,7 +764,7 @@ func (t *ComplexType) parseComplexContent(ns string, root *xmltree.Element) { usedElt[elt.Name] = len(t.Elements) t.Elements = append(t.Elements, elt) } else { - t.Elements[existing] = joinElem(t.Elements[existing], elt) + t.Elements[existing] = joinElem(*t.Elements[existing], *elt) } } @@ -735,7 +780,7 @@ func (t *ComplexType) parseComplexContent(ns string, root *xmltree.Element) { t.Doc += string(doc) } -func joinElem(a, b Element) Element { +func joinElem(a, b Element) *Element { if a.Doc != "" { a.Doc += "\n" } @@ -749,7 +794,7 @@ func joinElem(a, b Element) Element { a.Default = "" } - return a + return &a } func parseInt(s string) int { @@ -801,20 +846,22 @@ func parsePlural(el *xmltree.Element) bool { return false } -func parseAnyElement(ns string, el *xmltree.Element) Element { +func parseAnyElement(ns string, el *xmltree.Element) *Element { var base Type = AnyType typeattr := el.Attr("", "type") if typeattr != "" { base = parseType(el.Resolve(typeattr)) } - return Element{ + elem := Element{ Plural: parsePlural(el), Type: base, Wildcard: true, } + + return &elem } -func parseElement(ns string, el *xmltree.Element) Element { +func parseElement(ns string, el *xmltree.Element) *Element { var doc annotation e := Element{ Name: el.ResolveDefault(el.Attr("", "name"), ns), @@ -844,7 +891,7 @@ func parseElement(ns string, el *xmltree.Element) Element { } e.Doc = string(doc) e.Attr = el.StartElement.Attr - return e + return &e } func parseAttribute(ns string, el *xmltree.Element) Attribute { diff --git a/xsd/search.go b/xsd/search.go index b76ebc3..4081bb1 100644 --- a/xsd/search.go +++ b/xsd/search.go @@ -30,7 +30,7 @@ func or(fns ...func(el *xmltree.Element) bool) predicate { func hasChild(fn predicate) predicate { return func(el *xmltree.Element) bool { for i := range el.Children { - if fn(&el.Children[i]) { + if fn(el.Children[i]) { return true } } diff --git a/xsd/walk.go b/xsd/walk.go index c5f77b0..f43ed1d 100644 --- a/xsd/walk.go +++ b/xsd/walk.go @@ -48,7 +48,7 @@ func walk(root *xmltree.Element, fn func(*xmltree.Element)) { if root.Children[i].Name.Space != schemaNS { continue } - fn(&root.Children[i]) + fn(root.Children[i]) } } diff --git a/xsd/xsd.go b/xsd/xsd.go index fb84c36..7e73ece 100644 --- a/xsd/xsd.go +++ b/xsd/xsd.go @@ -159,7 +159,7 @@ type ComplexType struct { // True if this is an anonymous type Anonymous bool // XML elements that this type may contain in its content. - Elements []Element + Elements []*Element // Possible attributes for the element's opening tag. Attributes []Attribute // An abstract type does not appear in the xml document, but diff --git a/xsdgen/config.go b/xsdgen/config.go index a2bf601..850af72 100644 --- a/xsdgen/config.go +++ b/xsdgen/config.go @@ -429,10 +429,10 @@ func (cfg *Config) filterFields(t *xsd.ComplexType) ([]xsd.Attribute, []xsd.Elem attributes = append(attributes, attr) } for _, el := range t.Elements { - if cfg.filterElements != nil && cfg.filterElements(&el) { + if cfg.filterElements != nil && cfg.filterElements(el) { continue } - elements = append(elements, el) + elements = append(elements, *el) } return attributes, elements } @@ -566,7 +566,7 @@ Loop: } for _, v := range c.Elements { if v.Wildcard { - elem = v + elem = *v found = true break Loop } @@ -581,12 +581,12 @@ Loop: elem.Type = base for i, v := range t.Elements { if v.Wildcard { - t.Elements[i] = elem + t.Elements[i] = &elem replaced = true } } if !replaced { - t.Elements = append(t.Elements, elem) + t.Elements = append(t.Elements, &elem) } return t } diff --git a/xsdgen/testdata/anon.go.golden b/xsdgen/testdata/anon.go.golden new file mode 100644 index 0000000..dcf0332 --- /dev/null +++ b/xsdgen/testdata/anon.go.golden @@ -0,0 +1,15 @@ +// Code generated by xsdgen.test. DO NOT EDIT. + +package ws + +type ListType struct { + Elem []string `xml:"http://example.org/ Elem,omitempty"` +} + +type Type1 struct { + ListType ListType `xml:"http://example.org/ ListType,omitempty"` +} + +type Type2 struct { + ListType ListType `xml:"http://example.org/ ListType,omitempty"` +} diff --git a/xsdgen/testdata/anon.xsd b/xsdgen/testdata/anon.xsd new file mode 100644 index 0000000..c6c894d --- /dev/null +++ b/xsdgen/testdata/anon.xsd @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xsdgen/testdata/anon2.go.golden b/xsdgen/testdata/anon2.go.golden new file mode 100644 index 0000000..ab84f03 --- /dev/null +++ b/xsdgen/testdata/anon2.go.golden @@ -0,0 +1,19 @@ +// Code generated by xsdgen.test. DO NOT EDIT. + +package ws + +type ListType struct { + Elem []string `xml:"http://example.org/ Elem,omitempty"` +} + +type ListType1 struct { + Elem []string `xml:"http://example.org/ Elem,omitempty"` +} + +type Type1 struct { + ListType ListType `xml:"http://example.org/ ListType,omitempty"` +} + +type Type2 struct { + ListType ListType1 `xml:"http://example.org/ ListType,omitempty"` +} diff --git a/xsdgen/testdata/anon2.xsd b/xsdgen/testdata/anon2.xsd new file mode 100644 index 0000000..17966db --- /dev/null +++ b/xsdgen/testdata/anon2.xsd @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xsdgen/xsdgen.go b/xsdgen/xsdgen.go index 0604c4d..d19958d 100644 --- a/xsdgen/xsdgen.go +++ b/xsdgen/xsdgen.go @@ -47,7 +47,7 @@ func lookupTargetNS(data ...[]byte) []string { continue } outer := xmltree.Element{ - Children: []xmltree.Element{*tree}, + Children: []*xmltree.Element{tree}, } elts := outer.Search("http://www.w3.org/2001/XMLSchema", "schema") for _, el := range elts { @@ -323,7 +323,7 @@ func (cfg *Config) expandComplexTypes(types []xsd.Type) []xsd.Type { shadowedAttributes[attr.Name] = struct{}{} } - elements := []xsd.Element{} + elements := []*xsd.Element{} for _, el := range b.Elements { if _, ok := shadowedElements[el.Name]; !ok { elements = append(elements, el)