Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
578e55f
Add -c compact flag & hooks for custom element handling
raduionita-wk Oct 8, 2025
be22936
Update readme
raduionita-wk Oct 9, 2025
2ceb6d4
fix tests
raduionita-wk Oct 9, 2025
a2cf018
move hook to different file + comments
raduionita-wk Oct 9, 2025
b145dca
temp module name change
raduionita-wk Oct 10, 2025
936b08b
pass along compact and hooks
raduionita-wk Oct 10, 2025
12af6c2
only attr & elem compact
raduionita-wk Oct 10, 2025
14eee86
refactor hooks and add stopping the flow from inside the hook
raduionita-wk Oct 13, 2025
121a7d6
refactor to hook, adds OnAddContent callback
raduionita-wk Oct 13, 2025
14e797f
removing compact
eleenmohd-wk Oct 28, 2025
9fb581e
update module name from raduionita-wk/xgen to xuri/xgen and remove un…
eleenmohd-wk Oct 28, 2025
aa94a8b
Merge pull request #1 from raduionita-wk/ri/compact-and-hook
eleenmohd-wk Oct 28, 2025
1400e11
updated documentation
eleenmohd-wk Oct 28, 2025
207d163
Add comprehensive and error handling hooks for XML parsing tests
eleenmohd-wk Oct 28, 2025
f67577a
Add Hook interface for customizable XSD parsing and code generation
eleenmohd-wk Oct 28, 2025
2323121
Add tests for OnCharData error handling and skip behavior
eleenmohd-wk Oct 30, 2025
5641012
Add comprehensive tests for OnAddContent hook coverage
eleenmohd-wk Oct 30, 2025
ccbff7d
Clean up tests - remove some tests
eleenmohd-wk Oct 30, 2025
60d0fa7
Remove hook-coverage test fixture files
eleenmohd-wk Oct 30, 2025
874bdf4
more tests
eleenmohd-wk Oct 31, 2025
4cf8ad1
more tests
eleenmohd-wk Oct 31, 2025
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
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,67 @@ $ xgen [<flag> ...] <XSD file or directory> ...
-v Output version and exit
```

## Programmatic Usage

You can use xgen as a library in your Go code for more control over the parsing and code generation process.

### Basic Usage

```go
import "github.com/xuri/xgen"

parser := xgen.NewParser(&xgen.Options{
FilePath: "schema.xsd",
OutputDir: "output",
Lang: "Go",
Package: "mypackage",
IncludeMap: make(map[string]bool),
LocalNameNSMap: make(map[string]string),
NSSchemaLocationMap: make(map[string]string),
ParseFileList: make(map[string]bool),
ParseFileMap: make(map[string][]interface{}),
ProtoTree: make([]interface{}, 0),
})
err := parser.Parse()
```

### Customization with Hooks

The `Hook` interface allows you to customize the parsing and code generation process by intercepting events at various stages:

```go
type CustomHook struct{}

func (h *CustomHook) OnStartElement(opt *xgen.Options, ele xml.StartElement, protoTree []interface{}) (bool, error) {
// Intercept XML elements during parsing
return true, nil
}

func (h *CustomHook) OnGenerate(gen *xgen.CodeGenerator, protoName string, v interface{}) (bool, error) {
// Intercept code generation for each type
return true, nil
}

func (h *CustomHook) OnAddContent(gen *xgen.CodeGenerator, content *string) {
// Modify generated code before writing to file
}

// ... implement other Hook methods ...

parser := xgen.NewParser(&xgen.Options{
// ... other options ...
Hook: &CustomHook{},
})
```

Use cases for hooks include:
- Parsing custom XSD extensions or vendor-specific annotations
- Customizing type mappings between XSD and target language types
- Injecting additional methods or documentation into generated code
- Filtering elements during parsing or code generation

See the `Hook` interface documentation and `TestParseGoWithAppinfoHook` in `parser_test.go` for complete examples.

## XSD (XML Schema Definition)

XSD, a recommendation of the World Wide Web Consortium ([W3C](https://www.w3.org)), specifies how to formally describe the elements in an Extensible Markup Language ([XML](https://www.w3.org/TR/xml/)) document. It can be used by programmers to verify each piece of item content in a document. They can check if it adheres to the description of the element it is placed in.
Expand Down
61 changes: 61 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,67 @@ $ xgen [<flag> ...] <XSD file or directory> ...
-v 查看版本号并退出
```

## 编程方式使用

您可以在 Go 代码中将 xgen 作为库使用,以便更好地控制解析和代码生成过程。

### 基本使用

```go
import "github.com/xuri/xgen"

parser := xgen.NewParser(&xgen.Options{
FilePath: "schema.xsd",
OutputDir: "output",
Lang: "Go",
Package: "mypackage",
IncludeMap: make(map[string]bool),
LocalNameNSMap: make(map[string]string),
NSSchemaLocationMap: make(map[string]string),
ParseFileList: make(map[string]bool),
ParseFileMap: make(map[string][]interface{}),
ProtoTree: make([]interface{}, 0),
})
err := parser.Parse()
```

### 通过 Hook 自定义

`Hook` 接口允许您通过拦截各个阶段的事件来自定义解析和代码生成过程:

```go
type CustomHook struct{}

func (h *CustomHook) OnStartElement(opt *xgen.Options, ele xml.StartElement, protoTree []interface{}) (bool, error) {
// 在解析期间拦截 XML 元素
return true, nil
}

func (h *CustomHook) OnGenerate(gen *xgen.CodeGenerator, protoName string, v interface{}) (bool, error) {
// 拦截每个类型的代码生成
return true, nil
}

func (h *CustomHook) OnAddContent(gen *xgen.CodeGenerator, content *string) {
// 在写入文件之前修改生成的代码
}

// ... 实现其他 Hook 方法 ...

parser := xgen.NewParser(&xgen.Options{
// ... 其他选项 ...
Hook: &CustomHook{},
})
```

Hook 的使用场景包括:
- 解析自定义 XSD 扩展或供应商特定的注释
- 自定义 XSD 和目标语言类型之间的类型映射
- 向生成的代码中注入额外的方法或文档
- 在解析或代码生成过程中过滤元素

有关完整示例,请参阅 `Hook` 接口文档和 `parser_test.go` 中的 `TestParseGoWithAppinfoHook`。

## XSD (XML Schema Definition)

XSD 是万维网联盟 ([W3C](https://www.w3.org)) 推荐的标准,它指定了在可扩展标记语言 ([XML](https://www.w3.org/TR/xml/)) 文档中描述元素的规范。开发者可以使用它来验证文档中的每个项目内容,并可以检查它是否符合放置元素的说明。
Expand Down
75 changes: 66 additions & 9 deletions genGo.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type CodeGenerator struct {
ImportEncodingXML bool // For Go language
ProtoTree []interface{}
StructAST map[string]string
Hook Hook
}

var goBuildinType = map[string]bool{
Expand Down Expand Up @@ -59,12 +60,28 @@ var goBuildinType = map[string]bool{
// GenGo generate Go programming language source code for XML schema
// definition files.
func (gen *CodeGenerator) GenGo() error {
err := error(nil)
fieldNameCount = make(map[string]int)
for _, ele := range gen.ProtoTree {
if ele == nil {
continue
}
funcName := fmt.Sprintf("Go%s", reflect.TypeOf(ele).String()[6:])

next := true
protoName := reflect.TypeOf(ele).String()[6:]
if gen.Hook != nil {
next, err = gen.Hook.OnGenerate(gen, protoName, ele)
if err != nil {
return err
}

// skip to next element (in tree)
if !next {
continue
}
}

funcName := fmt.Sprintf("Go%s", protoName)
callFuncByName(gen, funcName, []reflect.Value{reflect.ValueOf(ele)})
}
f, err := os.Create(gen.FileWithExtension(".go"))
Expand Down Expand Up @@ -139,7 +156,12 @@ func (gen *CodeGenerator) GoSimpleType(v *SimpleType) {
content := fmt.Sprintf(" []%s\n", genGoFieldType(fieldType))
gen.StructAST[v.Name] = content
fieldName := genGoFieldName(v.Name, true)
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])

output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
if gen.Hook != nil {
gen.Hook.OnAddContent(gen, &output)
}
gen.Field += output
return
}
}
Expand All @@ -162,15 +184,25 @@ func (gen *CodeGenerator) GoSimpleType(v *SimpleType) {
}
content += "}\n"
gen.StructAST[v.Name] = content
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])

output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
if gen.Hook != nil {
gen.Hook.OnAddContent(gen, &output)
}
gen.Field += output
}
return
}
if _, ok := gen.StructAST[v.Name]; !ok {
content := fmt.Sprintf(" %s\n", genGoFieldType(getBasefromSimpleType(trimNSPrefix(v.Base), gen.ProtoTree)))
gen.StructAST[v.Name] = content
fieldName := genGoFieldName(v.Name, true)
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])

output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
if gen.Hook != nil {
gen.Hook.OnAddContent(gen, &output)
}
gen.Field += output
}
}

Expand Down Expand Up @@ -244,7 +276,12 @@ func (gen *CodeGenerator) GoComplexType(v *ComplexType) {
}
content += "}\n"
gen.StructAST[v.Name] = content
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])

output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
if gen.Hook != nil {
gen.Hook.OnAddContent(gen, &output)
}
gen.Field += output
}
}

Expand Down Expand Up @@ -280,7 +317,12 @@ func (gen *CodeGenerator) GoGroup(v *Group) {

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

output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
if gen.Hook != nil {
gen.Hook.OnAddContent(gen, &output)
}
gen.Field += output
}
}

Expand All @@ -303,7 +345,12 @@ func (gen *CodeGenerator) GoAttributeGroup(v *AttributeGroup) {
}
content += "}\n"
gen.StructAST[v.Name] = content
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])

output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
if gen.Hook != nil {
gen.Hook.OnAddContent(gen, &output)
}
gen.Field += output
}
}

Expand All @@ -317,7 +364,12 @@ func (gen *CodeGenerator) GoElement(v *Element) {
content := fmt.Sprintf("\t%s%s\n", plural, genGoFieldType(getBasefromSimpleType(trimNSPrefix(v.Type), gen.ProtoTree)))
gen.StructAST[v.Name] = content
fieldName := genGoFieldName(v.Name, false)
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])

output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
if gen.Hook != nil {
gen.Hook.OnAddContent(gen, &output)
}
gen.Field += output
}
}

Expand All @@ -331,7 +383,12 @@ func (gen *CodeGenerator) GoAttribute(v *Attribute) {
content := fmt.Sprintf("\t%s%s\n", plural, genGoFieldType(getBasefromSimpleType(trimNSPrefix(v.Type), gen.ProtoTree)))
gen.StructAST[v.Name] = content
fieldName := genGoFieldName(v.Name, true)
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])

output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
if gen.Hook != nil {
gen.Hook.OnAddContent(gen, &output)
}
gen.Field += output
}
}

Expand Down
64 changes: 64 additions & 0 deletions hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package xgen

import "encoding/xml"

// Hook provides a mechanism for customizing the XSD parsing and code generation process
// by intercepting events at various stages. Implementations can filter, modify, or extend
// the default behavior without modifying xgen's core logic.
//
// Use cases include:
// - Parsing custom XSD extensions or vendor-specific annotations
// - Filtering elements to skip during parsing or code generation
// - Customizing type mappings between XSD and target language types
// - Injecting additional code, methods, or documentation into generated output
// - Logging and debugging the parsing/generation flow
//
// Example:
//
// type CustomHook struct{}
//
// func (h *CustomHook) OnStartElement(opt *Options, ele xml.StartElement, protoTree []interface{}) (next bool, err error) {
// if ele.Name.Local == "customElement" {
// // Handle custom XSD element
// return false, nil // Skip default processing
// }
// return true, nil // Continue with default processing
// }
//
// // ... implement other Hook methods ...
//
// parser := NewParser(&Options{
// FilePath: "schema.xsd",
// OutputDir: "output",
// Lang: "Go",
// Hook: &CustomHook{},
// })
//
// For a complete working example, see TestParseGoWithAppinfoHook in parser_test.go.
type Hook interface {
// OnStartElement is called when an XML start element is encountered during parsing.
// Return next=false to skip xgen's default processing of this element.
// Return an error to halt parsing.
OnStartElement(opt *Options, ele xml.StartElement, protoTree []interface{}) (next bool, err error)

// OnEndElement is called when an XML end element is encountered during parsing.
// Return next=false to skip xgen's default processing of this element.
// Return an error to halt parsing.
OnEndElement(opt *Options, ele xml.EndElement, protoTree []interface{}) (next bool, err error)

// OnCharData is called when XML character data (text content) is encountered during parsing.
// Return next=false to skip xgen's default processing of this content.
// Return an error to halt parsing.
OnCharData(opt *Options, ele string, protoTree []interface{}) (next bool, err error)

// OnGenerate is called before generating code for each type (SimpleType, ComplexType, etc.).
// The protoName identifies the type being generated (e.g., "SimpleType", "ComplexType").
// Return next=false to skip code generation for this type.
// Return an error to halt code generation.
OnGenerate(gen *CodeGenerator, protoName string, v interface{}) (next bool, err error)

// OnAddContent is called after each code block is generated, allowing modification
// of the generated code. The content parameter is a pointer to the generated code string
// and can be modified directly.
OnAddContent(gen *CodeGenerator, content *string)
}
Loading