func (cfg *Config) genTypeSpec(t xsd.Type) (result []spec, err error) { var s []spec cfg.debugf("generating type spec for %q", xsd.XMLName(t).Local) switch t := t.(type) { case *xsd.SimpleType: s, err = cfg.genSimpleType(t) case *xsd.ComplexType: s, err = cfg.genComplexType(t) case xsd.Builtin: // Some built-ins, though built-in, require marshal/unmarshal methods // to be able to use them with the encoding/xml package. switch t { case xsd.Date, xsd.Time, xsd.DateTime, xsd.GDay, xsd.GMonth, xsd.GMonthDay, xsd.GYear, xsd.GYearMonth: s, err = cfg.genTimeSpec(t) case xsd.HexBinary, xsd.Base64Binary: s, err = cfg.genBinarySpec(t) case xsd.ENTITIES, xsd.IDREFS, xsd.NMTOKENS: s, err = cfg.genTokenListSpec(t) } default: cfg.logf("unexpected %T %s", t, xsd.XMLName(t).Local) } if err != nil || s == nil { return result, err } return append(result, s...), nil }
// Generate a type declaration for the bult-in list values, along with // marshal/unmarshal methods func (cfg *Config) genTokenListSpec(t xsd.Builtin) ([]spec, error) { cfg.debugf("generating Go source for token list %q", xsd.XMLName(t).Local) s := spec{ name: strings.ToLower(t.String()), expr: builtinExpr(t), xsdType: t, } marshal, err := gen.Func("MarshalText"). Receiver("x "+s.name). Returns("[]byte", "error"). Body(` return []byte(strings.Join(x, " ")), nil `).Decl() if err != nil { return nil, fmt.Errorf("MarshalText %s: %v", s.name, err) } unmarshal, err := gen.Func("UnmarshalText"). Receiver("x " + s.name). Args("text []byte"). Returns("error"). Body(` *x = bytes.Fields(text) return nil `).Decl() if err != nil { return nil, fmt.Errorf("UnmarshalText %s: %v", s.name, err) } s.methods = append(s.methods, marshal, unmarshal) return []spec{s}, nil }
func (cfg *Config) genSimpleType(t *xsd.SimpleType) ([]spec, error) { var result []spec if t.List { return cfg.genSimpleListSpec(t) } if len(t.Union) > 0 { // We don't support unions because the code that needs // to be generated to check which of the member types // the value would be is too complex. result = append(result, spec{ name: cfg.typeName(t.Name), expr: builtinExpr(xsd.String), xsdType: t, }) return result, nil } base, err := cfg.expr(t.Base) if err != nil { return nil, fmt.Errorf("simpleType %s: base type %s: %v", t.Name.Local, xsd.XMLName(t.Base).Local, err) } result = append(result, spec{ name: cfg.typeName(t.Name), expr: base, xsdType: t, }) return result, nil }
// Flatten out our tree of dependent types. If a type is marked as // private by a user filter and not used as a struct field or embedded // struct, it is ommitted from the output. func (cfg *Config) flatten(types map[xml.Name]xsd.Type) []xsd.Type { var result []xsd.Type push := func(t xsd.Type) { result = append(result, t) } for _, t := range types { if cfg.filterTypes != nil && cfg.filterTypes(t) { continue } if t := cfg.flatten1(t, push); t != nil { result = append(result, t) } } // Remove duplicates seen := make(map[xml.Name]bool) var a []xsd.Type for _, v := range result { name := xsd.XMLName(v) if _, ok := seen[name]; !ok { seen[name] = true a = append(a, v) } } cfg.debugf("discovered %d types", len(a)) return a }
// Generate a type declaration for the built-in binary values, along with // marshal/unmarshal methods for them. func (cfg *Config) genBinarySpec(t xsd.Builtin) ([]spec, error) { cfg.debugf("generating Go source for binary type %q", xsd.XMLName(t).Local) s := spec{ expr: builtinExpr(t), name: xsd.XMLName(t).Local, xsdType: t, } marshal := gen.Func("MarshalText").Receiver("b "+s.name).Returns("[]byte", "error") unmarshal := gen.Func("UnmarshalText").Receiver("b " + s.name).Args("text []byte"). Returns("err error") switch t { case xsd.HexBinary: unmarshal.Body(` *b, err = hex.DecodeString(string(text)) return err `) marshal.Body(` n := hex.EncodedLen([]byte(b)) buf := make([]byte, n) hex.Encode(buf, []byte(b)) return buf, nil `) case xsd.Base64Binary: unmarshal.Body(` *b, err = base64.StdEncoding.DecodeString(string(text)) return err `) marshal.Body(` var buf bytes.Buffer enc := base64.NewEncoder(base64.StdEncoding, &buf) enc.Write([]byte(b)) enc.Close() return buf.Bytes() `) } marshalFn, err := marshal.Decl() if err != nil { return nil, fmt.Errorf("MarshalText %s: %v", s.name, err) } unmarshalFn, err := unmarshal.Decl() if err != nil { return nil, fmt.Errorf("UnmarshalText %s: %v", s.name, err) } s.methods = append(s.methods, unmarshalFn, marshalFn) return []spec{s}, nil }
// Return the identifier for non-builtin types, and the Go expression // mapped to the built-in type. func (cfg *Config) expr(t xsd.Type) (ast.Expr, error) { if t, ok := t.(xsd.Builtin); ok { ex := builtinExpr(t) if ex == nil { return nil, fmt.Errorf("Unknown built-in type %q", t.Name().Local) } return ex, nil } return ast.NewIdent(cfg.typeName(xsd.XMLName(t))), nil }
// SOAP arrays are declared as follows (unimportant fields ellided): // // <xs:complexType name="Array"> // <xs:attribute name="arrayType" type="xs:string" /> // <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" /> // </xs:complexType> // // Then schemas that want to declare a fixed-type soap array do so like this: // // <xs:complexType name="IntArray"> // <xs:complexContent> // <xs:restriction base="soapenc:Array> // <xs:attribute ref="soapenc:arrayType" wsdl:arrayType="xs:int[]" /> // </xs:restriction> // </xs:complexContent> // </xs:complexType> // // XML Schema is wonderful, aint it? func (cfg *Config) parseSOAPArrayType(s xsd.Schema, t xsd.Type) xsd.Type { const soapenc = "http://schemas.xmlsoap.org/soap/encoding/" const wsdl = "http://schemas.xmlsoap.org/wsdl/" var itemType xml.Name c, ok := t.(*xsd.ComplexType) if !ok { return t } var attr []xsd.Attribute for _, v := range c.Attributes { if v.Name.Local != "arrayType" { attr = append(attr, v) continue } for _, a := range v.Attr { if (a.Name != xml.Name{wsdl, "arrayType"}) { continue } itemType = v.Resolve(a.Value) break } break } if itemType.Local == "" { return c } itemType.Local = strings.TrimSpace(itemType.Local) itemType.Local = strings.TrimSuffix(itemType.Local, "[]") if b := s.FindType(itemType); b != nil { c = cfg.overrideWildcardType(c, b) } else { cfg.logf("could not lookup item type %q in namespace %q", itemType.Local, itemType.Space) } // Have to remove arrayType from the "base" type, without affecting // others inheriting from this type. basep, ok := c.Base.(*xsd.ComplexType) if !ok { cfg.logf("type %s derives from non-complexType %s", c.Name.Local, xsd.XMLName(c.Base).Local) return c } base := *basep base.Attributes = make([]xsd.Attribute, 0, len(basep.Attributes)-1) for _, v := range basep.Attributes { if v.Name.Local != "arrayType" { base.Attributes = append(base.Attributes, v) } } c.Base = &base c.Attributes = attr return c }
// Generate a type declaration for the built-in time values, along with // marshal/unmarshal methods for them. func (cfg *Config) genTimeSpec(t xsd.Builtin) ([]spec, error) { var timespec string cfg.debugf("generating Go source for time type %q", xsd.XMLName(t).Local) s := spec{ expr: ast.NewIdent("time.Time"), name: builtinExpr(t).(*ast.Ident).Name, xsdType: t, } switch t { case xsd.GDay: timespec = "---02" case xsd.GMonth: timespec = "--01" case xsd.GMonthDay: timespec = "--01-02" case xsd.GYear: timespec = "2006" case xsd.GYearMonth: timespec = "2006-01" case xsd.Time: timespec = "15:04:05.999999999" case xsd.Date: timespec = "2006-01-02" case xsd.DateTime: timespec = "2006-01-02T15:04:05.999999999" } unmarshal, err := gen.Func("UnmarshalText"). Receiver("t *"+s.name). Args("text []byte"). Returns("error"). Body(` return _unmarshalTime(text, (*time.Time)(t), %q) `, timespec).Decl() if err != nil { return nil, fmt.Errorf("could not generate unmarshal function for %s: %v", s.name, err) } marshal, err := gen.Func("MarshalText"). Receiver("t *"+s.name). Returns("[]byte", "error"). Body(` return []byte((*time.Time)(t).Format(%q)), nil `, timespec).Decl() if err != nil { return nil, fmt.Errorf("could not generate marshal function for %s: %v", s.name, err) } s.methods = append(s.methods, unmarshal, marshal) if helper := cfg.helper("_unmarshalTime"); helper != nil { //panic(fmt.Sprint("adding ", helper.Name.Name, " to functions for ", s.name)) s.methods = append(s.methods, helper) } return []spec{s}, nil }
func (cfg *Config) overrideWildcardType(t *xsd.ComplexType, base xsd.Type) *xsd.ComplexType { var elem xsd.Element var found bool var replaced bool Loop: for x := xsd.Type(t); xsd.Base(x) != nil; x = xsd.Base(x) { c, ok := x.(*xsd.ComplexType) if !ok { cfg.logf("warning: soap-encoded array %s extends %T %s", xsd.XMLName(x).Local, base, xsd.XMLName(base).Local) return t } for _, v := range c.Elements { if v.Wildcard { elem = v found = true break Loop } } } if !found { cfg.logf("could not override wildcard type for %s; not found in type hierarchy", t.Name.Local) return t } cfg.debugf("overriding wildcard element of %s type from %s to %s", t.Name.Local, xsd.XMLName(elem.Type).Local, xsd.XMLName(base).Local) elem.Type = base for i, v := range t.Elements { if v.Wildcard { t.Elements[i] = elem replaced = true } } if !replaced { t.Elements = append(t.Elements, elem) } return t }
// OnlyTypes defines a whitelist of fully-qualified type name patterns // to include in the generated Go source. Only types in the whitelist, // and types that they depend on, will be included in the Go source. func OnlyTypes(patterns ...string) Option { pat := strings.Join(patterns, "|") reg, err := regexp.Compile(pat) return func(cfg *Config) Option { return replacePropertyFilter(&cfg.filterTypes, func(v interface{}) bool { t, ok := v.(xsd.Type) if !ok { panic(fmt.Sprintf("non-type %[1]T %[1]v passed to cfg.filterTypes", v)) } if err != nil { cfg.logf("invalid regex %q passed to OnlyTypes: %v", pat, err) return false } return !reg.MatchString(xsd.XMLName(t).Local) })(cfg) } }
// Generate a type declaration for a <list> type, along with marshal/unmarshal // methods. func (cfg *Config) genSimpleListSpec(t *xsd.SimpleType) ([]spec, error) { cfg.debugf("generating Go source for simple list %q", xsd.XMLName(t).Local) expr, err := cfg.expr(t.Base) if err != nil { return nil, err } s := spec{ name: cfg.typeName(t.Name), expr: expr, xsdType: t, } marshal, err := gen.Func("MarshalText"). Receiver("x *"+s.name). Returns("[]byte", "error"). Body(` return nil, nil `).Decl() if err != nil { return nil, fmt.Errorf("MarshalText %s: %v", s.name, err) } unmarshal, err := gen.Func("UnmarshalText"). Receiver("x *" + s.name). Args("text []byte"). Returns("error"). Body(` return nil `).Decl() if err != nil { return nil, fmt.Errorf("UnmarshalText %s: %v", s.name, err) } s.methods = append(s.methods, marshal, unmarshal) return []spec{s}, nil }
func (cfg *Config) genComplexType(t *xsd.ComplexType) ([]spec, error) { var result []spec var fields []ast.Expr if t.Extends { base, err := cfg.expr(t.Base) if err != nil { return nil, fmt.Errorf("%s base type %s: %v", t.Name.Local, xsd.XMLName(t.Base).Local, err) } switch b := t.Base.(type) { case *xsd.SimpleType: cfg.debugf("complexType %[1]s extends simpleType %[2]s. Naming"+ " the chardata struct field after %[2]s", t.Name.Local, b.Name.Local) fields = append(fields, base, base, gen.String(`xml:",chardata"`)) case xsd.Builtin: if b == xsd.AnyType { // extending anyType doesn't really make sense, but // we can just ignore it. cfg.debugf("complexType %s: don't know how to extend anyType, ignoring", t.Name.Local) break } // Name the field after the xsd type name. cfg.debugf("complexType %[1]s extends %[2]s, naming chardata struct field %[2]s", t.Name.Local, b) fields = append(fields, ast.NewIdent(b.String()), base, gen.String(`xml:",chardata"`)) case *xsd.ComplexType: // Use struct embedding when extending a complex type cfg.debugf("complexType %s extends %s, embedding struct", t.Name.Local, b.Name.Local) fields = append(fields, nil, base, nil) } } else { // When restricting a complex type, all attributes are "inherited" from // the base type (but not elements!). In addition, any <xs:any> elements, // while not explicitly inherited, do not disappear. switch b := t.Base.(type) { case *xsd.ComplexType: t.Attributes = mergeAttributes(t, b) hasWildcard := false for _, el := range t.Elements { if el.Wildcard { hasWildcard = true break } } if hasWildcard { break } for _, el := range b.Elements { if el.Wildcard { t.Elements = append(t.Elements, el) break } } } } attributes, elements := cfg.filterFields(t) cfg.debugf("complexType %s: generating struct fields for %d elements and %d attributes", xsd.XMLName(t).Local, len(elements), len(attributes)) hasDefault := false for _, attr := range attributes { hasDefault = hasDefault || (attr.Default != "") tag := fmt.Sprintf(`xml:"%s,attr"`, attr.Name.Local) base, err := cfg.expr(attr.Type) if err != nil { return nil, fmt.Errorf("%s attribute %s: %v", t.Name.Local, attr.Name.Local, err) } fields = append(fields, ast.NewIdent(cfg.public(attr.Name)), base, gen.String(tag)) } for _, el := range elements { hasDefault = hasDefault || (el.Default != "") tag := fmt.Sprintf(`xml:"%s %s"`, el.Name.Space, el.Name.Local) base, err := cfg.expr(el.Type) if err != nil { return nil, fmt.Errorf("%s element %s: %v", t.Name.Local, el.Name.Local, err) } name := ast.NewIdent(cfg.public(el.Name)) if el.Wildcard { tag = `xml:",any"` if el.Plural { name = ast.NewIdent("Items") } else { name = ast.NewIdent("Item") } if b, ok := el.Type.(xsd.Builtin); ok && b == xsd.AnyType { cfg.debugf("complexType %s: defaulting wildcard element to []string", t.Name.Local) base = builtinExpr(xsd.String) } } if el.Plural { base = &ast.ArrayType{Elt: base} } fields = append(fields, name, base, gen.String(tag)) } expr := gen.Struct(fields...) s := spec{ name: cfg.typeName(t.Name), expr: expr, xsdType: t, } if hasDefault { unmarshal, marshal, err := cfg.genMarshalComplexType(t) if err != nil { //NOTE(droyo) may want to log this instead of stopping the generator return result, err } else { if unmarshal != nil { s.methods = append(s.methods, unmarshal) } if marshal != nil { s.methods = append(s.methods, marshal) } } } result = append(result, s) return result, nil }
// To reduce the size of the Go source generated, all intermediate types // are "squashed"; every type should be based on a Builtin or another // type that the user wants included in the Go source. func (cfg *Config) flatten1(t xsd.Type, push func(xsd.Type)) xsd.Type { switch t := t.(type) { case *xsd.SimpleType: var ( chain []xsd.Type base, builtin xsd.Type ok bool ) // TODO: handle list/union types for base = xsd.Base(t); base != nil; base = xsd.Base(base) { if builtin, ok = base.(xsd.Builtin); ok { break } chain = append(chain, base) } for _, v := range chain { if v, ok := v.(*xsd.SimpleType); ok { v.Base = builtin push(v) } } t.Base = builtin return t case *xsd.ComplexType: // We can "unpack" a struct if it is extending a simple // or built-in type and we are ignoring all of its attributes. switch t.Base.(type) { case xsd.Builtin, *xsd.SimpleType: if b, ok := t.Base.(xsd.Builtin); ok && b == xsd.AnyType { break } attributes, _ := cfg.filterFields(t) if len(attributes) == 0 { cfg.debugf("complexType %s extends simpleType %s, but all attributes are filtered. unpacking.", t.Name.Local, xsd.XMLName(t.Base)) switch b := t.Base.(type) { case xsd.Builtin: return b case *xsd.SimpleType: return cfg.flatten1(t.Base, push) } } } // We can flatten a struct field if its type does not // need additional methods for unmarshalling. for i, el := range t.Elements { el.Type = cfg.flatten1(el.Type, push) if b, ok := el.Type.(*xsd.SimpleType); ok { if !b.List && len(b.Union) == 0 { el.Type = xsd.Base(el.Type) } } t.Elements[i] = el } for i, attr := range t.Attributes { attr.Type = cfg.flatten1(attr.Type, push) if b, ok := attr.Type.(*xsd.SimpleType); ok { if !b.List && len(b.Union) == 0 { attr.Type = xsd.Base(attr.Type) } } t.Attributes[i] = attr } return t case xsd.Builtin: // There are a few built-ins that do not map directly to Go types. // for these, we will declare them in the Go source. switch t { case xsd.ENTITIES, xsd.IDREFS, xsd.NMTOKENS: push(t) case xsd.Base64Binary, xsd.HexBinary: push(t) case xsd.Date, xsd.Time, xsd.DateTime: push(t) case xsd.GDay, xsd.GMonth, xsd.GMonthDay, xsd.GYear, xsd.GYearMonth: push(t) } return t } panic(fmt.Sprintf("unexpected %T", t)) }
func (cfg *Config) genAST(schema xsd.Schema, extra ...xsd.Schema) (*ast.File, error) { var errList errorList decls := make(map[string]spec) cfg.addStandardHelpers() collect := make(map[xml.Name]xsd.Type) for k, v := range schema.Types { collect[k] = v } for _, schema := range extra { for k, v := range schema.Types { collect[k] = v } } prev := schema.Types schema.Types = collect if cfg.preprocessType != nil { cfg.debugf("running user-defined pre-processing functions") for name, t := range prev { if t := cfg.preprocessType(schema, t); t != nil { prev[name] = t } } } schema.Types = prev cfg.debugf("generating Go source for schema %q", schema.TargetNS) typeList := cfg.flatten(schema.Types) for _, t := range typeList { specs, err := cfg.genTypeSpec(t) if err != nil { errList = append(errList, fmt.Errorf("generate type %q: %v", xsd.XMLName(t).Local, err)) } else { for _, s := range specs { decls[s.name] = s } } } if cfg.postprocessType != nil { cfg.debugf("running user-defined post-processing functions") for name, s := range decls { decls[name] = cfg.postprocessType(s) } } if len(errList) > 0 { return nil, errList } var result []ast.Decl keys := make([]string, 0, len(decls)) for name := range decls { keys = append(keys, name) } sort.Strings(keys) for _, name := range keys { info := decls[name] typeDecl := &ast.GenDecl{ Tok: token.TYPE, Specs: []ast.Spec{ &ast.TypeSpec{ Name: ast.NewIdent(name), Type: info.expr, }, }, } result = append(result, typeDecl) for _, f := range info.methods { result = append(result, f) } } if cfg.pkgname == "" { cfg.pkgname = "ws" } file := &ast.File{ Decls: result, Name: ast.NewIdent(cfg.pkgname), Doc: nil, } return file, nil }