Skip to content

Commit 663f25f

Browse files
Add Hook Interface for Customizable XSD Parsing and Code Generation (#114)
- Add -c compact flag & hooks for custom element handling - Update README --------- Co-authored-by: Radu Ionita <[email protected]>
1 parent 2e6da02 commit 663f25f

File tree

12 files changed

+915
-28
lines changed

12 files changed

+915
-28
lines changed

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,67 @@ $ xgen [<flag> ...] <XSD file or directory> ...
4343
-v Output version and exit
4444
```
4545

46+
## Programmatic Usage
47+
48+
You can use xgen as a library in your Go code for more control over the parsing and code generation process.
49+
50+
### Basic Usage
51+
52+
```go
53+
import "github.com/xuri/xgen"
54+
55+
parser := xgen.NewParser(&xgen.Options{
56+
FilePath: "schema.xsd",
57+
OutputDir: "output",
58+
Lang: "Go",
59+
Package: "mypackage",
60+
IncludeMap: make(map[string]bool),
61+
LocalNameNSMap: make(map[string]string),
62+
NSSchemaLocationMap: make(map[string]string),
63+
ParseFileList: make(map[string]bool),
64+
ParseFileMap: make(map[string][]interface{}),
65+
ProtoTree: make([]interface{}, 0),
66+
})
67+
err := parser.Parse()
68+
```
69+
70+
### Customization with Hooks
71+
72+
The `Hook` interface allows you to customize the parsing and code generation process by intercepting events at various stages:
73+
74+
```go
75+
type CustomHook struct{}
76+
77+
func (h *CustomHook) OnStartElement(opt *xgen.Options, ele xml.StartElement, protoTree []interface{}) (bool, error) {
78+
// Intercept XML elements during parsing
79+
return true, nil
80+
}
81+
82+
func (h *CustomHook) OnGenerate(gen *xgen.CodeGenerator, protoName string, v interface{}) (bool, error) {
83+
// Intercept code generation for each type
84+
return true, nil
85+
}
86+
87+
func (h *CustomHook) OnAddContent(gen *xgen.CodeGenerator, content *string) {
88+
// Modify generated code before writing to file
89+
}
90+
91+
// ... implement other Hook methods ...
92+
93+
parser := xgen.NewParser(&xgen.Options{
94+
// ... other options ...
95+
Hook: &CustomHook{},
96+
})
97+
```
98+
99+
Use cases for hooks include:
100+
- Parsing custom XSD extensions or vendor-specific annotations
101+
- Customizing type mappings between XSD and target language types
102+
- Injecting additional methods or documentation into generated code
103+
- Filtering elements during parsing or code generation
104+
105+
See the `Hook` interface documentation and `TestParseGoWithAppinfoHook` in `parser_test.go` for complete examples.
106+
46107
## XSD (XML Schema Definition)
47108

48109
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.

README_zh.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,67 @@ $ xgen [<flag> ...] <XSD file or directory> ...
4343
-v 查看版本号并退出
4444
```
4545

46+
## 编程方式使用
47+
48+
您可以在 Go 代码中将 xgen 作为库使用,以便更好地控制解析和代码生成过程。
49+
50+
### 基本使用
51+
52+
```go
53+
import "github.com/xuri/xgen"
54+
55+
parser := xgen.NewParser(&xgen.Options{
56+
FilePath: "schema.xsd",
57+
OutputDir: "output",
58+
Lang: "Go",
59+
Package: "mypackage",
60+
IncludeMap: make(map[string]bool),
61+
LocalNameNSMap: make(map[string]string),
62+
NSSchemaLocationMap: make(map[string]string),
63+
ParseFileList: make(map[string]bool),
64+
ParseFileMap: make(map[string][]interface{}),
65+
ProtoTree: make([]interface{}, 0),
66+
})
67+
err := parser.Parse()
68+
```
69+
70+
### 通过 Hook 自定义
71+
72+
`Hook` 接口允许您通过拦截各个阶段的事件来自定义解析和代码生成过程:
73+
74+
```go
75+
type CustomHook struct{}
76+
77+
func (h *CustomHook) OnStartElement(opt *xgen.Options, ele xml.StartElement, protoTree []interface{}) (bool, error) {
78+
// 在解析期间拦截 XML 元素
79+
return true, nil
80+
}
81+
82+
func (h *CustomHook) OnGenerate(gen *xgen.CodeGenerator, protoName string, v interface{}) (bool, error) {
83+
// 拦截每个类型的代码生成
84+
return true, nil
85+
}
86+
87+
func (h *CustomHook) OnAddContent(gen *xgen.CodeGenerator, content *string) {
88+
// 在写入文件之前修改生成的代码
89+
}
90+
91+
// ... 实现其他 Hook 方法 ...
92+
93+
parser := xgen.NewParser(&xgen.Options{
94+
// ... 其他选项 ...
95+
Hook: &CustomHook{},
96+
})
97+
```
98+
99+
Hook 的使用场景包括:
100+
- 解析自定义 XSD 扩展或供应商特定的注释
101+
- 自定义 XSD 和目标语言类型之间的类型映射
102+
- 向生成的代码中注入额外的方法或文档
103+
- 在解析或代码生成过程中过滤元素
104+
105+
有关完整示例,请参阅 `Hook` 接口文档和 `parser_test.go` 中的 `TestParseGoWithAppinfoHook`
106+
46107
## XSD (XML Schema Definition)
47108

48109
XSD 是万维网联盟 ([W3C](https://www.w3.org)) 推荐的标准,它指定了在可扩展标记语言 ([XML](https://www.w3.org/TR/xml/)) 文档中描述元素的规范。开发者可以使用它来验证文档中的每个项目内容,并可以检查它是否符合放置元素的说明。

genGo.go

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type CodeGenerator struct {
2727
ImportEncodingXML bool // For Go language
2828
ProtoTree []interface{}
2929
StructAST map[string]string
30+
Hook Hook
3031
}
3132

3233
var goBuildinType = map[string]bool{
@@ -59,12 +60,28 @@ var goBuildinType = map[string]bool{
5960
// GenGo generate Go programming language source code for XML schema
6061
// definition files.
6162
func (gen *CodeGenerator) GenGo() error {
63+
err := error(nil)
6264
fieldNameCount = make(map[string]int)
6365
for _, ele := range gen.ProtoTree {
6466
if ele == nil {
6567
continue
6668
}
67-
funcName := fmt.Sprintf("Go%s", reflect.TypeOf(ele).String()[6:])
69+
70+
next := true
71+
protoName := reflect.TypeOf(ele).String()[6:]
72+
if gen.Hook != nil {
73+
next, err = gen.Hook.OnGenerate(gen, protoName, ele)
74+
if err != nil {
75+
return err
76+
}
77+
78+
// skip to next element (in tree)
79+
if !next {
80+
continue
81+
}
82+
}
83+
84+
funcName := fmt.Sprintf("Go%s", protoName)
6885
callFuncByName(gen, funcName, []reflect.Value{reflect.ValueOf(ele)})
6986
}
7087
f, err := os.Create(gen.FileWithExtension(".go"))
@@ -139,7 +156,12 @@ func (gen *CodeGenerator) GoSimpleType(v *SimpleType) {
139156
content := fmt.Sprintf(" []%s\n", genGoFieldType(fieldType))
140157
gen.StructAST[v.Name] = content
141158
fieldName := genGoFieldName(v.Name, true)
142-
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
159+
160+
output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
161+
if gen.Hook != nil {
162+
gen.Hook.OnAddContent(gen, &output)
163+
}
164+
gen.Field += output
143165
return
144166
}
145167
}
@@ -162,15 +184,25 @@ func (gen *CodeGenerator) GoSimpleType(v *SimpleType) {
162184
}
163185
content += "}\n"
164186
gen.StructAST[v.Name] = content
165-
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
187+
188+
output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
189+
if gen.Hook != nil {
190+
gen.Hook.OnAddContent(gen, &output)
191+
}
192+
gen.Field += output
166193
}
167194
return
168195
}
169196
if _, ok := gen.StructAST[v.Name]; !ok {
170197
content := fmt.Sprintf(" %s\n", genGoFieldType(getBasefromSimpleType(trimNSPrefix(v.Base), gen.ProtoTree)))
171198
gen.StructAST[v.Name] = content
172199
fieldName := genGoFieldName(v.Name, true)
173-
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
200+
201+
output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
202+
if gen.Hook != nil {
203+
gen.Hook.OnAddContent(gen, &output)
204+
}
205+
gen.Field += output
174206
}
175207
}
176208

@@ -244,7 +276,12 @@ func (gen *CodeGenerator) GoComplexType(v *ComplexType) {
244276
}
245277
content += "}\n"
246278
gen.StructAST[v.Name] = content
247-
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
279+
280+
output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
281+
if gen.Hook != nil {
282+
gen.Hook.OnAddContent(gen, &output)
283+
}
284+
gen.Field += output
248285
}
249286
}
250287

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

281318
content += "}\n"
282319
gen.StructAST[v.Name] = content
283-
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
320+
321+
output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
322+
if gen.Hook != nil {
323+
gen.Hook.OnAddContent(gen, &output)
324+
}
325+
gen.Field += output
284326
}
285327
}
286328

@@ -303,7 +345,12 @@ func (gen *CodeGenerator) GoAttributeGroup(v *AttributeGroup) {
303345
}
304346
content += "}\n"
305347
gen.StructAST[v.Name] = content
306-
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
348+
349+
output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
350+
if gen.Hook != nil {
351+
gen.Hook.OnAddContent(gen, &output)
352+
}
353+
gen.Field += output
307354
}
308355
}
309356

@@ -317,7 +364,12 @@ func (gen *CodeGenerator) GoElement(v *Element) {
317364
content := fmt.Sprintf("\t%s%s\n", plural, genGoFieldType(getBasefromSimpleType(trimNSPrefix(v.Type), gen.ProtoTree)))
318365
gen.StructAST[v.Name] = content
319366
fieldName := genGoFieldName(v.Name, false)
320-
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
367+
368+
output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
369+
if gen.Hook != nil {
370+
gen.Hook.OnAddContent(gen, &output)
371+
}
372+
gen.Field += output
321373
}
322374
}
323375

@@ -331,7 +383,12 @@ func (gen *CodeGenerator) GoAttribute(v *Attribute) {
331383
content := fmt.Sprintf("\t%s%s\n", plural, genGoFieldType(getBasefromSimpleType(trimNSPrefix(v.Type), gen.ProtoTree)))
332384
gen.StructAST[v.Name] = content
333385
fieldName := genGoFieldName(v.Name, true)
334-
gen.Field += fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
386+
387+
output := fmt.Sprintf("%stype %s%s", genFieldComment(fieldName, v.Doc, "//"), fieldName, gen.StructAST[v.Name])
388+
if gen.Hook != nil {
389+
gen.Hook.OnAddContent(gen, &output)
390+
}
391+
gen.Field += output
335392
}
336393
}
337394

hook.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package xgen
2+
3+
import "encoding/xml"
4+
5+
// Hook provides a mechanism for customizing the XSD parsing and code generation process
6+
// by intercepting events at various stages. Implementations can filter, modify, or extend
7+
// the default behavior without modifying xgen's core logic.
8+
//
9+
// Use cases include:
10+
// - Parsing custom XSD extensions or vendor-specific annotations
11+
// - Filtering elements to skip during parsing or code generation
12+
// - Customizing type mappings between XSD and target language types
13+
// - Injecting additional code, methods, or documentation into generated output
14+
// - Logging and debugging the parsing/generation flow
15+
//
16+
// Example:
17+
//
18+
// type CustomHook struct{}
19+
//
20+
// func (h *CustomHook) OnStartElement(opt *Options, ele xml.StartElement, protoTree []interface{}) (next bool, err error) {
21+
// if ele.Name.Local == "customElement" {
22+
// // Handle custom XSD element
23+
// return false, nil // Skip default processing
24+
// }
25+
// return true, nil // Continue with default processing
26+
// }
27+
//
28+
// // ... implement other Hook methods ...
29+
//
30+
// parser := NewParser(&Options{
31+
// FilePath: "schema.xsd",
32+
// OutputDir: "output",
33+
// Lang: "Go",
34+
// Hook: &CustomHook{},
35+
// })
36+
//
37+
// For a complete working example, see TestParseGoWithAppinfoHook in parser_test.go.
38+
type Hook interface {
39+
// OnStartElement is called when an XML start element is encountered during parsing.
40+
// Return next=false to skip xgen's default processing of this element.
41+
// Return an error to halt parsing.
42+
OnStartElement(opt *Options, ele xml.StartElement, protoTree []interface{}) (next bool, err error)
43+
44+
// OnEndElement is called when an XML end element is encountered during parsing.
45+
// Return next=false to skip xgen's default processing of this element.
46+
// Return an error to halt parsing.
47+
OnEndElement(opt *Options, ele xml.EndElement, protoTree []interface{}) (next bool, err error)
48+
49+
// OnCharData is called when XML character data (text content) is encountered during parsing.
50+
// Return next=false to skip xgen's default processing of this content.
51+
// Return an error to halt parsing.
52+
OnCharData(opt *Options, ele string, protoTree []interface{}) (next bool, err error)
53+
54+
// OnGenerate is called before generating code for each type (SimpleType, ComplexType, etc.).
55+
// The protoName identifies the type being generated (e.g., "SimpleType", "ComplexType").
56+
// Return next=false to skip code generation for this type.
57+
// Return an error to halt code generation.
58+
OnGenerate(gen *CodeGenerator, protoName string, v interface{}) (next bool, err error)
59+
60+
// OnAddContent is called after each code block is generated, allowing modification
61+
// of the generated code. The content parameter is a pointer to the generated code string
62+
// and can be modified directly.
63+
OnAddContent(gen *CodeGenerator, content *string)
64+
}

0 commit comments

Comments
 (0)