Skip to content

Better handling of imported xsds and namespace tag generation #144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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
70 changes: 48 additions & 22 deletions xsd/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,10 @@ func Parse(docs ...[]byte) ([]Schema, error) {

for _, root := range schema {
tns := root.Attr("", "targetNamespace")
s := Schema{TargetNS: tns, Types: make(map[xml.Name]Type)}
efd := parseForm(root.Attr("", "elementFormDefault"), FormOptionUndefined)
afd := parseForm(root.Attr("", "attributeFormDefault"), FormOptionUndefined)

s := Schema{TargetNS: tns, ElementFormDefault: efd, AttributeFormDefault: afd, Types: make(map[xml.Name]Type)}
if err := s.parse(root); err != nil {
return nil, err
}
Expand Down Expand Up @@ -404,7 +407,7 @@ func deref(ref, real *xmltree.Element) *xmltree.Element {
el := new(xmltree.Element)
el.Scope = ref.Scope
el.Name = real.Name
el.StartElement.Attr = append([]xml.Attr{}, real.StartElement.Attr...)
el.StartElement.Attr = []xml.Attr{}
el.Content = append([]byte{}, real.Content...)
el.Children = append([]xmltree.Element{}, real.Children...)

Expand All @@ -413,11 +416,17 @@ func deref(ref, real *xmltree.Element) *xmltree.Element {
hasQName := map[xml.Name]bool{
xml.Name{"", "type"}: true,
}
for i, attr := range el.StartElement.Attr {
// Some attribute should not be copied
dontCopy := map[xml.Name]bool{
xml.Name{"", "name"}: true,
xml.Name{"", "form"}: true,
}
for _, attr := range real.StartElement.Attr {
if hasQName[attr.Name] {
xmlname := real.Resolve(attr.Value)
attr.Value = ref.Prefix(xmlname)
el.StartElement.Attr[i] = attr
el.SetAttr(attr.Name.Space, attr.Name.Local, ref.Prefix(xmlname))
} else if !dontCopy[attr.Name] {
el.SetAttr(attr.Name.Space, attr.Name.Local, attr.Value)
}
}
// If there are child elements, rather than checking all children
Expand All @@ -429,11 +438,16 @@ func deref(ref, real *xmltree.Element) *xmltree.Element {
// Attributes added to the reference overwrite attributes in the
// referenced element.
for _, attr := range ref.StartElement.Attr {
if (attr.Name != xml.Name{"", "ref"}) {
if (attr.Name == xml.Name{"", "ref"}) {
el.SetAttr("", "name", attr.Value)
} else {
el.SetAttr(attr.Name.Space, attr.Name.Local, attr.Value)
}
}

// Referenced elements are always qualified
el.SetAttr("", "form", "qualified")

return el
}

Expand Down Expand Up @@ -582,9 +596,10 @@ func attributeDefaultType(root *xmltree.Element) {
var (
isAttr = isElem(schemaNS, "attribute")
hasNoType = hasAttrValue("", "type", "")
hasNoRef = hasAttrValue("", "ref", "")
anyType = xml.Name{Space: schemaNS, Local: "anySimpleType"}
)
for _, el := range root.SearchFunc(and(isAttr, hasNoType)) {
for _, el := range root.SearchFunc(and(isAttr, hasNoType, hasNoRef)) {
el.SetAttr("", "type", el.Prefix(anyType))
}
}
Expand All @@ -598,9 +613,10 @@ func elementDefaultType(root *xmltree.Element) {
var (
isElement = isElem(schemaNS, "element")
hasNoType = hasAttrValue("", "type", "")
hasNoRef = hasAttrValue("", "ref", "")
anyType = xml.Name{Space: schemaNS, Local: "anyType"}
)
for _, el := range root.SearchFunc(and(isElement, hasNoType)) {
for _, el := range root.SearchFunc(and(isElement, hasNoType, hasNoRef)) {
el.SetAttr("", "type", el.Prefix(anyType))
}
}
Expand Down Expand Up @@ -658,9 +674,9 @@ func (s *Schema) parseComplexType(root *xmltree.Element) *ComplexType {
case "annotation":
doc = doc.append(parseAnnotation(el))
case "simpleContent":
t.parseSimpleContent(s.TargetNS, el)
t.parseSimpleContent(s.TargetNS, s.AttributeFormDefault, el)
case "complexContent":
t.parseComplexContent(s.TargetNS, el)
t.parseComplexContent(s.TargetNS, s.ElementFormDefault, s.AttributeFormDefault, el)
default:
stop("unexpected element " + el.Name.Local)
}
Expand All @@ -671,7 +687,7 @@ func (s *Schema) parseComplexType(root *xmltree.Element) *ComplexType {

// simpleContent indicates that the content model of the new type
// contains only character data and no elements
func (t *ComplexType) parseSimpleContent(ns string, root *xmltree.Element) {
func (t *ComplexType) parseSimpleContent(ns string, afd FormOption, root *xmltree.Element) {
var doc annotation

t.Mixed = true
Expand All @@ -685,7 +701,7 @@ func (t *ComplexType) parseSimpleContent(ns string, root *xmltree.Element) {
t.Base = parseType(el.Resolve(el.Attr("", "base")))
t.Extends = true
for _, v := range el.Search(schemaNS, "attribute") {
t.Attributes = append(t.Attributes, parseAttribute(ns, v))
t.Attributes = append(t.Attributes, parseAttribute(ns, afd, v))
}
}
})
Expand All @@ -694,7 +710,7 @@ func (t *ComplexType) parseSimpleContent(ns string, root *xmltree.Element) {

// The complexContent element signals that we intend to restrict or extend
// the content model of a complex type.
func (t *ComplexType) parseComplexContent(ns string, root *xmltree.Element) {
func (t *ComplexType) parseComplexContent(ns string, efd FormOption, afd FormOption, root *xmltree.Element) {
var doc annotation
if mixed := root.Attr("", "mixed"); mixed != "" {
t.Mixed = parseBool(mixed)
Expand All @@ -714,7 +730,7 @@ func (t *ComplexType) parseComplexContent(ns string, root *xmltree.Element) {

usedElt := make(map[xml.Name]int)
for _, v := range el.Search(schemaNS, "element") {
elt := parseElement(ns, v)
elt := parseElement(ns, efd, afd, v)
if existing, ok := usedElt[elt.Name]; !ok {
usedElt[elt.Name] = len(t.Elements)
t.Elements = append(t.Elements, elt)
Expand All @@ -724,7 +740,7 @@ func (t *ComplexType) parseComplexContent(ns string, root *xmltree.Element) {
}

for _, v := range el.Search(schemaNS, "attribute") {
t.Attributes = append(t.Attributes, parseAttribute(ns, v))
t.Attributes = append(t.Attributes, parseAttribute(ns, afd, v))
}
case "annotation":
doc = doc.append(parseAnnotation(el))
Expand Down Expand Up @@ -814,10 +830,11 @@ func parseAnyElement(ns string, el *xmltree.Element) Element {
}
}

func parseElement(ns string, el *xmltree.Element) Element {
func parseElement(ns string, efd FormOption, afd FormOption, el *xmltree.Element) Element {
var doc annotation
e := Element{
Name: el.ResolveDefault(el.Attr("", "name"), ns),
Form: parseForm(el.Attr("", "form"), efd),
Type: parseType(el.Resolve(el.Attr("", "type"))),
Default: el.Attr("", "default"),
Abstract: parseBool(el.Attr("", "abstract")),
Expand All @@ -838,25 +855,22 @@ func parseElement(ns string, el *xmltree.Element) Element {
doc = doc.append(parseAnnotation(el))
}
})
t, ok := e.Type.(linkedType)
if ok {
e.Name.Space = t.Space
}
e.Doc = string(doc)
e.Attr = el.StartElement.Attr
return e
}

func parseAttribute(ns string, el *xmltree.Element) Attribute {
func parseAttribute(ns string, afd FormOption, el *xmltree.Element) Attribute {
var a Attribute
var doc annotation
// Non-QName xml attributes explicitly do *not* have a namespace.
if name := el.Attr("", "name"); strings.Contains(name, ":") {
a.Name = el.Resolve(el.Attr("", "name"))
} else {
a.Name.Local = name
a.Name.Space = ns
}
a.Name.Space = ns
a.Form = parseForm(el.Attr("", "form"), afd)
a.Type = parseType(el.Resolve(el.Attr("", "type")))
a.Default = el.Attr("", "default")
a.Scope = el.Scope
Expand Down Expand Up @@ -1107,3 +1121,15 @@ func (s *Schema) lookupType(name linkedType, ext map[xml.Name]Type) (Type, bool)
v, ok := s.Types[xml.Name(name)]
return v, ok
}

func parseForm(s string, d FormOption) FormOption {
s = strings.TrimSpace(s)
form := FormOption(s)

if form == FormOptionUndefined {
return d
} else {
return form
}

}
4 changes: 2 additions & 2 deletions xsd/testdata/AttributeGroup.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<complexType name="myElementType">
<attributeGroup ref="tns:myAttributeGroup"/>
<element name="Field1" ref="tns:SomeElement" />
<element ref="tns:Field1" />
</complexType>

<element name="SomeElement" type="string" />
<element name="Field1" type="string" />
26 changes: 13 additions & 13 deletions xsd/testdata/ComplexType.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
{
"CustomerType": {
"Elements": [
{"Name": {"Local": "CompanyName"}, "Type": 38},
{"Name": {"Local": "ContactName"}, "Type": 38},
{"Name": {"Local": "ContactTitle"}, "Type": 38},
{"Name": {"Local": "Phone"}, "Type": 38},
{"Name": {"Local": "Fax"}, "Type": 38},
{"Name": {"Local": "CompanyName", "Space": "tns"}, "Type": 38},
{"Name": {"Local": "ContactName", "Space": "tns"}, "Type": 38},
{"Name": {"Local": "ContactTitle", "Space": "tns"}, "Type": 38},
{"Name": {"Local": "Phone", "Space": "tns"}, "Type": 38},
{"Name": {"Local": "Fax", "Space": "tns"}, "Type": 38},
{
"Name": {"Local": "FullAddress"},
"Name": {"Local": "FullAddress", "Space": "tns"},
"Type": {
"Elements": [
{"Name": {"Local": "Address"}, "Type": 38},
{"Name": {"Local": "City"}, "Type": 38},
{"Name": {"Local": "Region"}, "Type": 38},
{"Name": {"Local": "PostalCode"}, "Type": 38},
{"Name": {"Local": "Country"}, "Type": 38}
{"Name": {"Local": "Address", "Space": "tns"}, "Type": 38},
{"Name": {"Local": "City", "Space": "tns"}, "Type": 38},
{"Name": {"Local": "Region", "Space": "tns"}, "Type": 38},
{"Name": {"Local": "PostalCode", "Space": "tns"}, "Type": 38},
{"Name": {"Local": "Country", "Space": "tns"}, "Type": 38}
],
"Attributes": [{"Name": {"Local": "CustomerID"}, "Type": 40}]
"Attributes": [{"Name": {"Local": "CustomerID", "Space": "tns"}, "Type": 40}]
}
}
],
"Attributes": [
{"Name": {"Local": "CustomerID"}, "Type": 40}
{"Name": {"Local": "CustomerID", "Space": "tns"}, "Type": 40}
]
}
}
7 changes: 3 additions & 4 deletions xsd/testdata/ComplexType.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<element name='ContactTitle' type='string'/>
<element name='Phone' type='string'/>
<element name='Fax' minOccurs='0' type='string'/>
<element name='FullAddress' ref='tns:AddressType'/>
<element name='FullAddress' type='tns:AddressType'/>
</sequence>
<attribute name='CustomerID' type='token'/>
</complexType>

<element name='AddressType'>
<complexType>

<complexType name='AddressType'>
<sequence>
<element name='Address' type='string'/>
<element name='City' type='string'/>
Expand All @@ -21,4 +21,3 @@
</sequence>
<attribute name='CustomerID' type='token'/>
</complexType>
</element>
16 changes: 16 additions & 0 deletions xsd/xsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type Element struct {
Doc string
// The canonical name of this element
Name xml.Name
// What form of the naming to expect
Form FormOption
// True if this element can have any name. See
// http://www.w3.org/TR/2004/REC-xmlschema-1-20041028/structures.html#element-any
Wildcard bool
Expand Down Expand Up @@ -80,6 +82,8 @@ type Attribute struct {
// The canonical name of this attribute. It is uncommon for attributes
// to have a name space.
Name xml.Name
// What form of the naming to expect
Form FormOption
// Annotation provided for this attribute by the schema author.
Doc string
// The type of the attribute value. Must be a simple or built-in Type.
Expand All @@ -103,6 +107,10 @@ type Schema struct {
// The Target namespace of the schema. All types defined in this
// schema will be in this name space.
TargetNS string `xml:"targetNamespace,attr"`
// ElementFormDefault
ElementFormDefault FormOption `xml:"elementFormDefault,attr,omitempty"`
// AttributeFormDefault
AttributeFormDefault FormOption `xml:"attributeFormDefault,attr,omitempty"`
// Types defined in this schema declaration
Types map[xml.Name]Type
// Any annotations declared at the top-level of the schema, separated
Expand Down Expand Up @@ -329,3 +337,11 @@ var StandardSchema = [][]byte{
wsdl2003xsd, // http://schemas.xmlsoap.org/wsdl/
xlinkxsd, // http://www.w3.org/1999/xlink
}

type FormOption string

const (
FormOptionUndefined FormOption = ""
FormOptionUnqualified FormOption = "unqualified"
FormOptionQualified FormOption = "qualified"
)
1 change: 1 addition & 0 deletions xsd/xsd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func testCompare(t *testing.T, prefix []string, got, want interface{}) bool {
}
if got != want {
t.Errorf("%s: got %#v, wanted %#v", path, got, want)
return false
}
return true
}
Expand Down
38 changes: 25 additions & 13 deletions xsdgen/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"go/ast"
"io/ioutil"
"path/filepath"
"strings"

"aqwari.net/xml/internal/commandline"
Expand Down Expand Up @@ -62,22 +63,31 @@ func (cfg *Config) GenAST(files ...string) (*ast.File, error) {
return code.GenAST()
}

func (cfg *Config) readFiles(files ...string) ([][]byte,error) {
func (cfg *Config) readFiles(files ...string) ([][]byte, error) {
data := make([][]byte, 0, len(files))
for _, filename := range files {
b, err := ioutil.ReadFile(filename)
path, err := filepath.Abs(filename)
if err != nil {
return nil, err
}
cfg.debugf("read %s", filename)
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
cfg.debugf("read %s(%s)", path, filename)
if cfg.followImports {
dir := filepath.Dir(path)
importedRefs, err := xsd.Imports(b)
if err != nil {
return nil, fmt.Errorf("error discovering imports: %v", err)
}
importedFiles := make([]string, 0, len(importedRefs))
for _, r := range importedRefs {
importedFiles = append(importedFiles, r.Location)
if filepath.IsAbs(r.Location) {
importedFiles = append(importedFiles, r.Location)
} else {
importedFiles = append(importedFiles, filepath.Join(dir, r.Location))
}
}
referencedData, err := cfg.readFiles(importedFiles...)
if err != nil {
Expand Down Expand Up @@ -110,15 +120,16 @@ func (cfg *Config) GenSource(files ...string) ([]byte, error) {
// same as those passed to the xsdgen command.
func (cfg *Config) GenCLI(arguments ...string) error {
var (
err error
replaceRules commandline.ReplaceRuleList
xmlns commandline.Strings
fs = flag.NewFlagSet("xsdgen", flag.ExitOnError)
packageName = fs.String("pkg", "", "name of the the generated package")
output = fs.String("o", "xsdgen_output.go", "name of the output file")
followImports = fs.Bool("f", false, "follow import statements; load imported references recursively into scope")
verbose = fs.Bool("v", false, "print verbose output")
debug = fs.Bool("vv", false, "print debug output")
err error
replaceRules commandline.ReplaceRuleList
xmlns commandline.Strings
fs = flag.NewFlagSet("xsdgen", flag.ExitOnError)
packageName = fs.String("pkg", "", "name of the the generated package")
output = fs.String("o", "xsdgen_output.go", "name of the output file")
followImports = fs.Bool("f", false, "follow import statements; load imported references recursively into scope")
targetNamespacesOnly = fs.Bool("t", false, "restict output of types to these declared in the target namespace(s) provided")
verbose = fs.Bool("v", false, "print verbose output")
debug = fs.Bool("vv", false, "print debug output")
)
fs.Var(&replaceRules, "r", "replacement rule 'regex -> repl' (can be used multiple times)")
fs.Var(&xmlns, "ns", "target namespace(s) to generate types for")
Expand All @@ -136,6 +147,7 @@ func (cfg *Config) GenCLI(arguments ...string) error {
}
cfg.Option(Namespaces(xmlns...))
cfg.Option(FollowImports(*followImports))
cfg.Option(TargetNamespacesOnly(*targetNamespacesOnly))
for _, r := range replaceRules {
cfg.Option(replaceAllNamesRegex(r.From, r.To))
}
Expand Down
Loading