Skip to content
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

Add Support for XSD Extensions #36

Merged
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
test/**/output
data/
**/.DS_Store*
.idea/
28 changes: 20 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ Submit tests for your changes. Go has a great test framework built in; use it!
Take a look at existing tests for inspiration. Run the full test on your branch
before submitting a pull request.

The parser test suite, in `parser_test.go`, looks for a `data` directory in the
root of the repository with the following structure:
The parser test suite, in `parser_test.go` works on two sources of inputs and their expected outputs:
1. The basic tests under `test` which include a source xsd along with the expected output for all languages.
2. Any additional tests located in the `data` directory present at the root of the repository with the following structure:

```
data
Expand All @@ -114,14 +115,12 @@ data
└── ts
```

Any `xsd` files in the `xsd` subdirectory are used as test inputs. For each
language being tested, the generated code is placed in `<language>/output/`.
Both sources are treated similarly. Any `xsd` files in the `xsd` subdirectory are used as test inputs.
For each language being tested, the generated code is placed in `<language>/output/`.
Each `<language>` folder must contain the expected generated output from each
input file.
input file (see example with the basic set in `test`).

The `xgen` repository contains a small example parser test in the `test`
directory; to run it, rename (or copy) the `test` directory to `data`, then
simply use `go test .`. (Do not use `./...`, which will cause build failures
To run tests on both sources, run `go test .`. (Do not use `./...`, which will cause build failures
due to redeclaration errors, since the generated code and the reference
versions co-exist.)

Expand All @@ -130,6 +129,19 @@ repository](https://github.com/xuri/xsd). To run it, copy the top-level
contents of this repository into the `data` directory in the `xgen` working
copy, then run `go test .`.

#### Validity of generated code
To validate generated code can marshal and unmarshal correctly, you can use the
test in `xml_test.go`. Those test cases require the code to have been generated already
so it works best with the basic tests included in the `test` directory which are also used
as the expected outputs for the `parser_test.go`.

The test can also be modified to run through new test cases of generated code in the `data`
directory from custom xsd. However, since the tests in `data` are external, any additional
test cases in `xml_test.go` should not be committed. To add new xml tests to be committed, first
include some version of a simplified xsd file in `test` along with the expected generated output.
Once this is done, a new xml file can be added to `xmlFixtures` and a new test case can be added
that uses those two things.

### Successful Changes

Before contributing large or high impact changes, make the effort to coordinate
Expand Down
7 changes: 6 additions & 1 deletion genC.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ func (gen *CodeGenerator) CSimpleType(v *SimpleType) {
if v.Union && len(v.MemberTypes) > 0 {
if _, ok := gen.StructAST[v.Name]; !ok {
content := "struct {\n"
for memberName, memberType := range v.MemberTypes {
for _, member := range toSortedPairs(v.MemberTypes) {
memberName := member.key
memberType := member.value

if memberType == "" { // fix order issue
memberType = getBasefromSimpleType(memberName, gen.ProtoTree)
}
Expand Down Expand Up @@ -175,6 +178,8 @@ func (gen *CodeGenerator) CComplexType(v *ComplexType) {
}
content += fmt.Sprintf("\t%s %s%s;\n", fieldType, genCFieldName(element.Name), plural)
}
// TODO: Implement handling of v.Base for the cases of the type being a built-in one and
// the case of inheritance/embedding
content += "}"
gen.StructAST[v.Name] = content
fieldName := genCFieldName(v.Name)
Expand Down
20 changes: 19 additions & 1 deletion genGo.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ func (gen *CodeGenerator) GoSimpleType(v *SimpleType) {
gen.ImportEncodingXML = true
content += fmt.Sprintf("\tXMLName\txml.Name\t`xml:\"%s\"`\n", v.Name)
}
for memberName, memberType := range v.MemberTypes {
for _, member := range toSortedPairs(v.MemberTypes) {
memberName := member.key
memberType := member.value

if memberType == "" { // fix order issue
memberType = getBasefromSimpleType(memberName, gen.ProtoTree)
}
Expand Down Expand Up @@ -217,13 +220,28 @@ func (gen *CodeGenerator) GoComplexType(v *ComplexType) {
}
content += fmt.Sprintf("\t%s\t%s%s\t`xml:\"%s\"`\n", genGoFieldName(element.Name), plural, fieldType, element.Name)
}
if len(v.Base) > 0 {
// If the type is a built-in type, generate a Value field as chardata.
// If it's not built-in one, embed the base type in the struct for the child type
// to effectively inherit all of the base type's fields
if isGoBuiltInType(v.Base) {
content += fmt.Sprintf("\tValue\t%s\t`xml:\",chardata\"`\n", genGoFieldType(v.Base))
} else {
content += fmt.Sprintf("\t%s\n", genGoFieldType(v.Base))
}
}
content += "}\n"
gen.StructAST[v.Name] = content
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
}
return
}

func isGoBuiltInType(typeName string) bool {
_, builtIn := goBuildinType[typeName]
return builtIn
}

// GoGroup generates code for group XML schema in Go language syntax.
func (gen *CodeGenerator) GoGroup(v *Group) {
if _, ok := gen.StructAST[v.Name]; !ok {
Expand Down
28 changes: 25 additions & 3 deletions genJava.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;`
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;`

f.Write([]byte(fmt.Sprintf("%s\n\npackage %s;\n\n%s\n%s", copyright, packageName, importPackage, gen.Field)))
return err
Expand Down Expand Up @@ -104,7 +105,10 @@ func (gen *CodeGenerator) JavaSimpleType(v *SimpleType) {
if v.Union && len(v.MemberTypes) > 0 {
if _, ok := gen.StructAST[v.Name]; !ok {
content := " {\n"
for memberName, memberType := range v.MemberTypes {
for _, member := range toSortedPairs(v.MemberTypes) {
memberName := member.key
memberType := member.value

if memberType == "" { // fix order issue
memberType = getBasefromSimpleType(memberName, gen.ProtoTree)
}
Expand Down Expand Up @@ -161,14 +165,32 @@ func (gen *CodeGenerator) JavaComplexType(v *ComplexType) {
}
content += fmt.Sprintf("\t@XmlElement(required = true, name = \"%s\")\n\tprotected %s %s;\n", element.Name, fieldType, genJavaFieldName(element.Name))
}

if len(v.Base) > 0 && isBuiltInJavaType(v.Base) {
fieldType := genJavaFieldType(getBasefromSimpleType(trimNSPrefix(v.Base), gen.ProtoTree))
content += fmt.Sprintf("\t@XmlValue\n\tprotected %s value;\n", fieldType)
}

content += "}\n"
gen.StructAST[v.Name] = content
fieldName := genJavaFieldName(v.Name)
gen.Field += fmt.Sprintf("%spublic class %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])

typeExtension := ""
if len(v.Base) > 0 && !isBuiltInJavaType(v.Base) {
fieldType := genJavaFieldType(getBasefromSimpleType(trimNSPrefix(v.Base), gen.ProtoTree))
typeExtension = fmt.Sprintf(" extends %s ", fieldType)
}

gen.Field += fmt.Sprintf("%spublic class %s%s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, typeExtension, gen.StructAST[v.Name])
}
return
}

func isBuiltInJavaType(typeName string) bool {
_, builtIn := javaBuildInType[typeName]
return builtIn
}

// JavaGroup generates code for group XML schema in Java language syntax.
func (gen *CodeGenerator) JavaGroup(v *Group) {
if _, ok := gen.StructAST[v.Name]; !ok {
Expand Down
21 changes: 19 additions & 2 deletions genRust.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,10 @@ func (gen *CodeGenerator) RustSimpleType(v *SimpleType) {
if v.Union && len(v.MemberTypes) > 0 {
if _, ok := gen.StructAST[v.Name]; !ok {
var content string
for memberName, memberType := range v.MemberTypes {
for _, member := range toSortedPairs(v.MemberTypes) {
memberName := member.key
memberType := member.value

if memberType == "" { // fix order issue
memberType = getBasefromSimpleType(memberName, gen.ProtoTree)
}
Expand Down Expand Up @@ -237,7 +240,16 @@ func (gen *CodeGenerator) RustComplexType(v *ComplexType) {
content += fmt.Sprintf("\t#[serde(rename = \"%s\")]\n\tpub %s: %s,\n", element.Name, fieldName, fieldType)
}
}

}
if len(v.Base) > 0 {
fieldType := genRustFieldType(getBasefromSimpleType(trimNSPrefix(v.Base), gen.ProtoTree))
if isRustBuiltInType(v.Base) {
content += fmt.Sprintf("\t#[serde(rename = \"$value\")]\n\tpub value: %s,\n", fieldType)
} else {
fieldName := genRustFieldName(fieldType)
// If the type is not a built-in one, add the base type as a nested field tagged with flatten
content += fmt.Sprintf("\t#[serde(flatten)]\n\tpub %s: %s,\n", fieldName, fieldType)
}
}
gen.StructAST[v.Name] = content
fieldName := genRustStructName(v.Name)
Expand All @@ -246,6 +258,11 @@ func (gen *CodeGenerator) RustComplexType(v *ComplexType) {
return
}

func isRustBuiltInType(typeName string) bool {
_, builtIn := rustBuildinType[typeName]
return builtIn
}

// RustGroup generates code for group XML schema in Rust language syntax.
func (gen *CodeGenerator) RustGroup(v *Group) {
if _, ok := gen.StructAST[v.Name]; !ok {
Expand Down
24 changes: 22 additions & 2 deletions genTypeScript.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ func (gen *CodeGenerator) TypeScriptSimpleType(v *SimpleType) {
if v.Union && len(v.MemberTypes) > 0 {
if _, ok := gen.StructAST[v.Name]; !ok {
content := " {\n"
for memberName, memberType := range v.MemberTypes {
for _, member := range toSortedPairs(v.MemberTypes) {
memberName := member.key
memberType := member.value

if memberType == "" { // fix order issue
memberType = getBasefromSimpleType(memberName, gen.ProtoTree)
}
Expand Down Expand Up @@ -158,14 +161,31 @@ func (gen *CodeGenerator) TypeScriptComplexType(v *ComplexType) {
fieldType := genTypeScriptFieldType(getBasefromSimpleType(trimNSPrefix(element.Type), gen.ProtoTree), element.Plural)
content += fmt.Sprintf("\t%s: %s;\n", genTypeScriptFieldName(element.Name), fieldType)
}

if len(v.Base) > 0 && isBuiltInTypeScriptType(v.Base) {
fieldType := genTypeScriptFieldType(getBasefromSimpleType(trimNSPrefix(v.Base), gen.ProtoTree), false)
content += fmt.Sprintf("\tValue: %s;\n", fieldType)
}
content += "}\n"
gen.StructAST[v.Name] = content
fieldName := genTypeScriptFieldName(v.Name)
gen.Field += fmt.Sprintf("%sexport class %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
typeExtension := ""
if len(v.Base) > 0 && !isBuiltInTypeScriptType(v.Base) {
fieldType := genTypeScriptFieldType(getBasefromSimpleType(trimNSPrefix(v.Base), gen.ProtoTree), false)
content += fmt.Sprintf("\tValue: %s;\n", fieldType)
typeExtension = fmt.Sprintf(" extends %s ", fieldType)
}

gen.Field += fmt.Sprintf("%sexport class %s%s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, typeExtension, gen.StructAST[v.Name])
}
return
}

func isBuiltInTypeScriptType(typeName string) bool {
_, builtIn := typeScriptBuildInType[typeName]
return builtIn
}

// TypeScriptGroup generates code for group XML schema in TypeScript language syntax.
func (gen *CodeGenerator) TypeScriptGroup(v *Group) {
if _, ok := gen.StructAST[v.Name]; !ok {
Expand Down
2 changes: 1 addition & 1 deletion parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func (opt *Options) Parse() (err error) {
if err != nil {
return
}
defer xmlFile.Close()
if !opt.Extract {
opt.ParseFileList[opt.FilePath] = true
opt.ParseFileMap[opt.FilePath] = opt.ProtoTree
Expand Down Expand Up @@ -124,7 +125,6 @@ func (opt *Options) Parse() (err error) {
}

}
defer xmlFile.Close()

if !opt.Extract {
opt.ParseFileList[opt.FilePath] = true
Expand Down
Loading