xsdgen

package
v0.0.0-...-d9421b2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 31, 2021 License: MIT Imports: 19 Imported by: 29

Documentation

Overview

Package xsdgen generates Go source code from xml schema documents.

The xsdgen package generates type declarations and accompanying methods for marshalling and unmarshalling XML elements that adhere to an XML schema. The source code generation is configurable, and can be passed through user-defined filters.

Code generated by the xsdgen package is self-contained; only the Go standard library is used. All types generated by the xsdgen package can be unmarshalled into by the standard encoding/xml package. Where neccessary, methods are generated to satisfy the interfaces used by encoding/xml.

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultOptions = []Option{
	IgnoreAttributes("id", "href", "ref", "offset"),
	Replace(`[._ \s-]`, ""),
	PackageName("ws"),
	HandleSOAPArrayType(),
	SOAPArrayAsSlice(),
	UseFieldNames(),
}

DefaultOptions are the default options for Go source code generation. The defaults are chosen to be good enough for the majority of use cases, and produce usable, idiomatic Go code. The top-level Generate function of the xsdgen package uses these options.

Functions

This section is empty.

Types

type Code

type Code struct {
	// contains filtered or unexported fields
}

Code is the intermediate representation used by the xsdgen package. It can be used to generate a Go source file, and to lookup identifiers and attributes for a given type.

func (*Code) DocType

func (c *Code) DocType(targetNS string) (*xsd.ComplexType, bool)

DocType retrieves the complexType for the provided target namespace.

func (*Code) GenAST

func (code *Code) GenAST() (*ast.File, error)

GenAST generates a Go abstract syntax tree with the type declarations contained in the xml schema document.

func (*Code) NameOf

func (c *Code) NameOf(name xml.Name) string

NameOf returns the Go identifier associated with the canonical XML type.

type Config

type Config struct {
	// contains filtered or unexported fields
}

A Config holds user-defined overrides and filters that are used when generating Go source code from an xsd document.

func (*Config) GenAST

func (cfg *Config) GenAST(files ...string) (*ast.File, error)

GenAST creates an *ast.File containing type declarations and associated methods based on a set of XML schema.

func (*Config) GenCLI

func (cfg *Config) GenCLI(arguments ...string) error

GenCLI creates a file containing Go source generated from an XML Schema. Main is meant to be called as part of a command, and can be used to change the behavior of the xsdgen command in ways that its command-line arguments do not allow. The arguments are the same as those passed to the xsdgen command.

Example
package main

import (
	"log"

	"aqwari.net/xml/xsdgen"
)

func main() {
	var cfg xsdgen.Config
	cfg.Option(
		xsdgen.IgnoreAttributes("id", "href", "offset"),
		xsdgen.IgnoreElements("comment"),
		xsdgen.PackageName("webapi"),
		xsdgen.Replace("_", ""),
		xsdgen.HandleSOAPArrayType(),
		xsdgen.SOAPArrayAsSlice(),
	)
	if err := cfg.GenCLI("webapi.xsd", "deps/soap11.xsd"); err != nil {
		log.Fatal(err)
	}
}
Output:

func (*Config) GenCode

func (cfg *Config) GenCode(data ...[]byte) (*Code, error)

GenCode reads all xml schema definitions from the provided data. If succesful, the returned *Code value can be used to lookup identifiers and generate Go code.

func (*Config) GenSource

func (cfg *Config) GenSource(files ...string) ([]byte, error)

The GenSource method converts the AST returned by GenAST to formatted Go source code.

func (*Config) NameOf

func (cfg *Config) NameOf(name xml.Name) string

NameOf converts a canonical XML name to a Go identifier, applying any user-provided filters.

func (*Config) Option

func (cfg *Config) Option(opts ...Option) (previous Option)

The Option method is used to configure an existing configuration. The return value of the Option method can be used to revert the final option to its previous setting.

type Logger

type Logger interface {
	Printf(format string, v ...interface{})
}

Types implementing the Logger interface can receive debug information from the code generation process. The Logger interface is implemented by *log.Logger.

type Option

type Option func(*Config) Option

An Option is used to customize a Config.

func AllowType

func AllowType(name xml.Name) Option

AllowType registers the canonical XML name of a type that should be generated by the xsdgen package. If AllowType is called at least once, only types passed to AllowType, and their dependent types, will be generated.

func FollowImports

func FollowImports(follow bool) Option

FollowImports specifies whether or not to recursively read in imported schemas before attempting to parse

func HandleSOAPArrayType

func HandleSOAPArrayType() Option

The Option HandleSOAPArrayType adds a special-case pre-processing step to xsdgen that parses the wsdl:arrayType attribute of a SOAP array declaration and changes the underlying base type to match.

Example
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"aqwari.net/xml/xsdgen"
)

func tmpfile() *os.File {
	f, err := ioutil.TempFile("", "xsdgen_test")
	if err != nil {
		panic(err)
	}
	return f
}

func xsdfile(s string) (filename string) {
	file := tmpfile()
	defer file.Close()
	fmt.Fprintf(file, `
		<schema xmlns="http://www.w3.org/2001/XMLSchema"
		        xmlns:tns="http://www.example.com/"
		        xmlns:xs="http://www.w3.org/2001/XMLSchema"
		        xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
		        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
		        targetNamespace="http://www.example.com/">
		  %s
		</schema>
	`, s)
	return file.Name()
}

func main() {
	doc := xsdfile(`
	  <complexType name="BoolArray">
	    <complexContent>
	      <restriction base="soapenc:Array">
	        <attribute ref="soapenc:arrayType" wsdl:arrayType="xs:boolean[]"/>
	      </restriction>
	    </complexContent>
	  </complexType>`)

	var cfg xsdgen.Config
	cfg.Option(xsdgen.HandleSOAPArrayType())

	out, err := cfg.GenSource(doc)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", out)

}
Output:

// Code generated by xsdgen.test. DO NOT EDIT.

package ws

type BoolArray struct {
	Items  []bool `xml:",any"`
	Offset string `xml:"offset,attr,omitempty"`
	Id     string `xml:"id,attr,omitempty"`
	Href   string `xml:"href,attr,omitempty"`
}

func IgnoreAttributes

func IgnoreAttributes(names ...string) Option

IgnoreAttributes defines a list of attributes that should not be declared in the Go type.

Example
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"aqwari.net/xml/xsdgen"
)

func tmpfile() *os.File {
	f, err := ioutil.TempFile("", "xsdgen_test")
	if err != nil {
		panic(err)
	}
	return f
}

func xsdfile(s string) (filename string) {
	file := tmpfile()
	defer file.Close()
	fmt.Fprintf(file, `
		<schema xmlns="http://www.w3.org/2001/XMLSchema"
		        xmlns:tns="http://www.example.com/"
		        xmlns:xs="http://www.w3.org/2001/XMLSchema"
		        xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
		        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
		        targetNamespace="http://www.example.com/">
		  %s
		</schema>
	`, s)
	return file.Name()
}

func main() {
	doc := xsdfile(`
	  <complexType name="ArrayOfString">
	    <any maxOccurs="unbounded" />
	    <attribute name="soapenc:arrayType" type="xs:string" />
	  </complexType>
	`)
	var cfg xsdgen.Config
	cfg.Option(xsdgen.IgnoreAttributes("arrayType"))

	out, err := cfg.GenSource(doc)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", out)

}
Output:

// Code generated by xsdgen.test. DO NOT EDIT.

package ws

type ArrayOfString struct {
	Items []string `xml:",any"`
}

func IgnoreElements

func IgnoreElements(names ...string) Option

IgnoreElements defines a list of elements that should not be declared in the Go type.

Example
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"aqwari.net/xml/xsdgen"
)

func tmpfile() *os.File {
	f, err := ioutil.TempFile("", "xsdgen_test")
	if err != nil {
		panic(err)
	}
	return f
}

func xsdfile(s string) (filename string) {
	file := tmpfile()
	defer file.Close()
	fmt.Fprintf(file, `
		<schema xmlns="http://www.w3.org/2001/XMLSchema"
		        xmlns:tns="http://www.example.com/"
		        xmlns:xs="http://www.w3.org/2001/XMLSchema"
		        xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
		        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
		        targetNamespace="http://www.example.com/">
		  %s
		</schema>
	`, s)
	return file.Name()
}

func main() {
	doc := xsdfile(`
	  <complexType name="Person">
	    <sequence>
	      <element name="name" type="xs:string" />
	      <element name="deceased" type="soapenc:boolean" />
	      <element name="private" type="xs:int" />
	    </sequence>
	  </complexType>
	`)
	var cfg xsdgen.Config
	cfg.Option(
		xsdgen.IgnoreElements("private"),
		xsdgen.IgnoreAttributes("id", "href"))

	out, err := cfg.GenSource(doc)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", out)

}
Output:

// Code generated by xsdgen.test. DO NOT EDIT.

package ws

type Person struct {
	Name     string `xml:"http://www.example.com/ name"`
	Deceased bool   `xml:"http://schemas.xmlsoap.org/soap/encoding/ deceased"`
}

func LogLevel

func LogLevel(level int) Option

LogLevel sets the verbosity of messages sent to the error log configured with the LogOutput option. The level parameter should be a positive integer between 1 and 5, with 5 providing the greatest verbosity.

func LogOutput

func LogOutput(l Logger) Option

LogOutput specifies an optional Logger for warnings and debug information about the code generation process.

Example
package main

import (
	"log"
	"os"

	"aqwari.net/xml/xsdgen"
)

func main() {
	var cfg xsdgen.Config
	cfg.Option(
		xsdgen.LogOutput(log.New(os.Stderr, "", 0)),
		xsdgen.LogLevel(2))
	if err := cfg.GenCLI("file.wsdl"); err != nil {
		log.Fatal(err)
	}
}
Output:

func Namespaces

func Namespaces(xmlns ...string) Option

The Namespaces option configures the code generation process to only generate code for types declared in the configured target namespaces.

func OnlyTypes

func OnlyTypes(patterns ...string) Option

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 PackageName

func PackageName(name string) Option

PackageName specifies the name of the generated Go package.

Example
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"aqwari.net/xml/xsdgen"
)

func tmpfile() *os.File {
	f, err := ioutil.TempFile("", "xsdgen_test")
	if err != nil {
		panic(err)
	}
	return f
}

func xsdfile(s string) (filename string) {
	file := tmpfile()
	defer file.Close()
	fmt.Fprintf(file, `
		<schema xmlns="http://www.w3.org/2001/XMLSchema"
		        xmlns:tns="http://www.example.com/"
		        xmlns:xs="http://www.w3.org/2001/XMLSchema"
		        xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
		        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
		        targetNamespace="http://www.example.com/">
		  %s
		</schema>
	`, s)
	return file.Name()
}

func main() {
	doc := xsdfile(`
	  <simpleType name="zipcode">
	    <restriction base="xs:string">
	      <length value="10" />
	    </restriction>
	  </simpleType>
	`)
	var cfg xsdgen.Config
	cfg.Option(xsdgen.PackageName("postal"))

	out, err := cfg.GenSource(doc)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", out)

}
Output:

// Code generated by xsdgen.test. DO NOT EDIT.

package postal

// May be no more than 10 items long
type Zipcode string

func ProcessTypes

func ProcessTypes(fn func(xsd.Schema, xsd.Type) xsd.Type) Option

ProcessTypes allows for users to make arbitrary changes to a type before Go source code is generated.

func Replace

func Replace(pat, repl string) Option

Replace allows for substitution rules for all identifiers to be specified. If an invalid regular expression is called, no action is taken. The Replace option is additive; subsitutions will be applied in the order that each option was applied in.

Example
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"aqwari.net/xml/xsdgen"
)

func tmpfile() *os.File {
	f, err := ioutil.TempFile("", "xsdgen_test")
	if err != nil {
		panic(err)
	}
	return f
}

func xsdfile(s string) (filename string) {
	file := tmpfile()
	defer file.Close()
	fmt.Fprintf(file, `
		<schema xmlns="http://www.w3.org/2001/XMLSchema"
		        xmlns:tns="http://www.example.com/"
		        xmlns:xs="http://www.w3.org/2001/XMLSchema"
		        xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
		        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
		        targetNamespace="http://www.example.com/">
		  %s
		</schema>
	`, s)
	return file.Name()
}

func main() {
	doc := xsdfile(`
	  <complexType name="ArrayOfString">
	    <any maxOccurs="unbounded" />
	    <attribute name="soapenc:arrayType" type="xs:string" />
	  </complexType>
	`)
	var cfg xsdgen.Config
	cfg.Option(xsdgen.Replace("ArrayOf(.*)", "${1}Array"))

	out, err := cfg.GenSource(doc)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", out)

}
Output:

// Code generated by xsdgen.test. DO NOT EDIT.

package ws

type StringArray struct {
	Items     []string `xml:",any"`
	ArrayType string   `xml:"arrayType,attr,omitempty"`
}

func SOAPArrayAsSlice

func SOAPArrayAsSlice() Option

SOAP 1.1 defines an Array as

<xs:complexType name="Array">
  <xs:any maxOccurs="unbounded" />
  <xs:attribute name="arrayType" type="xs:string" />
  <!-- common attributes ellided -->
</xs:complexType>

Following the normal procedure of the xsdgen package, this would map to the following Go source (with arrayType as 'int'):

type Array struct {
	Item      []int  `xml:",any"`
	ArrayType string `xml:"http://schemas.xmlsoap.org/soap/encoding/ arrayType"`
}

While the encoding/xml package can easily marshal and unmarshal to and from such a Go type, it is not ideal to use. When using the SOAPArrayAsSlice option, if there is only one field in the Go type expression, and that field is plural, it is "unpacked". In addition, MarshalXML/UnmarshalXML methods are generated so that values can be decoded into this type. This option requires that the additional attributes ("id", "href", "offset") are either ignored or fixed by the schema.

Example
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"aqwari.net/xml/xsdgen"
)

func tmpfile() *os.File {
	f, err := ioutil.TempFile("", "xsdgen_test")
	if err != nil {
		panic(err)
	}
	return f
}

func xsdfile(s string) (filename string) {
	file := tmpfile()
	defer file.Close()
	fmt.Fprintf(file, `
		<schema xmlns="http://www.w3.org/2001/XMLSchema"
		        xmlns:tns="http://www.example.com/"
		        xmlns:xs="http://www.w3.org/2001/XMLSchema"
		        xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
		        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
		        targetNamespace="http://www.example.com/">
		  %s
		</schema>
	`, s)
	return file.Name()
}

func main() {
	doc := xsdfile(`
	  <complexType name="BoolArray">
	    <complexContent>
	      <restriction base="soapenc:Array">
	        <attribute ref="soapenc:arrayType" wsdl:arrayType="xs:boolean[]"/>
	      </restriction>
	    </complexContent>
	  </complexType>`)

	var cfg xsdgen.Config
	cfg.Option(
		xsdgen.HandleSOAPArrayType(),
		xsdgen.SOAPArrayAsSlice(),
		xsdgen.LogOutput(log.New(os.Stderr, "", 0)),
		xsdgen.LogLevel(3),
		xsdgen.IgnoreAttributes("offset", "id", "href"))

	out, err := cfg.GenSource(doc)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", out)

}
Output:

// Code generated by xsdgen.test. DO NOT EDIT.

package ws

import "encoding/xml"

type BoolArray []bool

func (a BoolArray) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	var output struct {
		ArrayType string `xml:"http://schemas.xmlsoap.org/wsdl/ arrayType,attr"`
		Items     []bool `xml:" item"`
	}
	output.Items = []bool(a)
	start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{"", "xmlns:ns1"}, Value: "http://www.w3.org/2001/XMLSchema"})
	output.ArrayType = "ns1:boolean[]"
	return e.EncodeElement(&output, start)
}
func (a *BoolArray) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) {
	var tok xml.Token
	for tok, err = d.Token(); err == nil; tok, err = d.Token() {
		if tok, ok := tok.(xml.StartElement); ok {
			var item bool
			if err = d.DecodeElement(&item, &tok); err == nil {
				*a = append(*a, item)
			}
		}
		if _, ok := tok.(xml.EndElement); ok {
			break
		}
	}
	return err
}

func UseFieldNames

func UseFieldNames() Option

The UseFieldNames Option names anonymous types based on the name of the element or attribute they describe.

Example
package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"aqwari.net/xml/xsdgen"
)

func tmpfile() *os.File {
	f, err := ioutil.TempFile("", "xsdgen_test")
	if err != nil {
		panic(err)
	}
	return f
}

func xsdfile(s string) (filename string) {
	file := tmpfile()
	defer file.Close()
	fmt.Fprintf(file, `
		<schema xmlns="http://www.w3.org/2001/XMLSchema"
		        xmlns:tns="http://www.example.com/"
		        xmlns:xs="http://www.w3.org/2001/XMLSchema"
		        xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
		        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
		        targetNamespace="http://www.example.com/">
		  %s
		</schema>
	`, s)
	return file.Name()
}

func main() {
	doc := xsdfile(`
	  <complexType name="library">
	    <sequence>
	      <element name="book" maxOccurs="unbounded">
	        <complexType>
	          <all>
	            <element name="title" type="xs:string" />
	            <element name="published" type="xs:date" />
	            <element name="author" type="xs:string" />
	          </all>
	        </complexType>
	      </element>
	    </sequence>
	  </complexType>`)

	var cfg xsdgen.Config
	cfg.Option(xsdgen.UseFieldNames())

	out, err := cfg.GenSource(doc)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", out)

}
Output:

// Code generated by xsdgen.test. DO NOT EDIT.

package ws

import (
	"bytes"
	"encoding/xml"
	"time"
)

type Book struct {
	Title     string    `xml:"http://www.example.com/ title"`
	Published time.Time `xml:"http://www.example.com/ published"`
	Author    string    `xml:"http://www.example.com/ author"`
}

func (t *Book) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	type T Book
	var layout struct {
		*T
		Published *xsdDate `xml:"http://www.example.com/ published"`
	}
	layout.T = (*T)(t)
	layout.Published = (*xsdDate)(&layout.T.Published)
	return e.EncodeElement(layout, start)
}
func (t *Book) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	type T Book
	var overlay struct {
		*T
		Published *xsdDate `xml:"http://www.example.com/ published"`
	}
	overlay.T = (*T)(t)
	overlay.Published = (*xsdDate)(&overlay.T.Published)
	return d.DecodeElement(&overlay, &start)
}

type Library struct {
	Book []Book `xml:"http://www.example.com/ book"`
}

type xsdDate time.Time

func (t *xsdDate) UnmarshalText(text []byte) error {
	return _unmarshalTime(text, (*time.Time)(t), "2006-01-02")
}
func (t xsdDate) MarshalText() ([]byte, error) {
	return _marshalTime((time.Time)(t), "2006-01-02")
}
func (t xsdDate) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	if (time.Time)(t).IsZero() {
		return nil
	}
	m, err := t.MarshalText()
	if err != nil {
		return err
	}
	return e.EncodeElement(m, start)
}
func (t xsdDate) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
	if (time.Time)(t).IsZero() {
		return xml.Attr{}, nil
	}
	m, err := t.MarshalText()
	return xml.Attr{Name: name, Value: string(m)}, err
}
func _unmarshalTime(text []byte, t *time.Time, format string) (err error) {
	s := string(bytes.TrimSpace(text))
	*t, err = time.Parse(format, s)
	if _, ok := err.(*time.ParseError); ok {
		*t, err = time.Parse(format+"Z07:00", s)
	}
	return err
}
func _marshalTime(t time.Time, format string) ([]byte, error) {
	return []byte(t.Format(format + "Z07:00")), nil
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL