xgbgen: process <doc> elements
Most of XCB documentation now ends up in Go sources, although the end result is of mixed quality.
This commit is contained in:
parent
0056720d05
commit
3e9ed4eac6
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Context represents the protocol we're converting to Go, and a writer
|
// Context represents the protocol we're converting to Go, and a writer
|
||||||
|
@ -34,6 +35,13 @@ func (c *Context) Put(format string, v ...interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PutComment writes each line of comment commented out to 'out'.
|
||||||
|
func (c *Context) PutComment(comment string) {
|
||||||
|
for _, line := range strings.Split(comment, "\n") {
|
||||||
|
c.Putln("// %s", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Morph is the big daddy of them all. It takes in an XML byte slice,
|
// Morph is the big daddy of them all. It takes in an XML byte slice,
|
||||||
// parse it, transforms the XML types into more usable types,
|
// parse it, transforms the XML types into more usable types,
|
||||||
// and writes Go code to the 'out' buffer.
|
// and writes Go code to the 'out' buffer.
|
||||||
|
|
|
@ -105,6 +105,7 @@ type SingleField struct {
|
||||||
srcName string
|
srcName string
|
||||||
xmlName string
|
xmlName string
|
||||||
Type Type
|
Type Type
|
||||||
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *SingleField) Initialize(p *Protocol) {
|
func (f *SingleField) Initialize(p *Protocol) {
|
||||||
|
@ -259,6 +260,8 @@ type ValueField struct {
|
||||||
MaskType Type
|
MaskType Type
|
||||||
MaskName string
|
MaskName string
|
||||||
ListName string
|
ListName string
|
||||||
|
MaskComment string
|
||||||
|
ListComment string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *ValueField) SrcName() string {
|
func (f *ValueField) SrcName() string {
|
||||||
|
@ -324,6 +327,7 @@ type SwitchField struct {
|
||||||
MaskName string
|
MaskName string
|
||||||
Expr Expression
|
Expr Expression
|
||||||
Bitcases []*Bitcase
|
Bitcases []*Bitcase
|
||||||
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *SwitchField) SrcName() string {
|
func (f *SwitchField) SrcName() string {
|
||||||
|
|
|
@ -168,7 +168,13 @@ func (f *ExprField) Write(c *Context, prefix string) {
|
||||||
|
|
||||||
// Value field
|
// Value field
|
||||||
func (f *ValueField) Define(c *Context) {
|
func (f *ValueField) Define(c *Context) {
|
||||||
|
if f.MaskComment != "" {
|
||||||
|
c.PutComment(f.MaskComment)
|
||||||
|
}
|
||||||
c.Putln("%s %s", f.MaskName, f.SrcType())
|
c.Putln("%s %s", f.MaskName, f.SrcType())
|
||||||
|
if f.ListComment != "" {
|
||||||
|
c.PutComment(f.ListComment)
|
||||||
|
}
|
||||||
c.Putln("%s []uint32", f.ListName)
|
c.Putln("%s []uint32", f.ListName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,6 +203,9 @@ func (f *ValueField) Write(c *Context, prefix string) {
|
||||||
|
|
||||||
// Switch field
|
// Switch field
|
||||||
func (f *SwitchField) Define(c *Context) {
|
func (f *SwitchField) Define(c *Context) {
|
||||||
|
if f.Comment != "" {
|
||||||
|
c.PutComment(f.Comment)
|
||||||
|
}
|
||||||
c.Putln("%s []uint32", f.Name)
|
c.Putln("%s []uint32", f.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,6 +13,33 @@ func (r *Request) Define(c *Context) {
|
||||||
c.Putln("*xgb.Cookie")
|
c.Putln("*xgb.Cookie")
|
||||||
c.Putln("}")
|
c.Putln("}")
|
||||||
c.Putln("")
|
c.Putln("")
|
||||||
|
|
||||||
|
if r.Doc.Description != "" {
|
||||||
|
c.PutComment(r.Doc.Description)
|
||||||
|
c.Putln("//")
|
||||||
|
}
|
||||||
|
|
||||||
|
allErrors := make([]string, 0, len(r.Doc.Errors))
|
||||||
|
for kind := range r.Doc.Errors {
|
||||||
|
allErrors = append(allErrors, kind)
|
||||||
|
}
|
||||||
|
sort.Strings(allErrors)
|
||||||
|
|
||||||
|
undocErrors := make([]string, 0)
|
||||||
|
for _, kind := range allErrors {
|
||||||
|
if desc := r.Doc.Errors[kind]; desc == "" {
|
||||||
|
undocErrors = append(undocErrors, kind)
|
||||||
|
} else {
|
||||||
|
c.PutComment(fmt.Sprintf("May return a %s error if %s%s", kind,
|
||||||
|
strings.ToLower(desc[:1]), desc[1:]))
|
||||||
|
c.Putln("//")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(undocErrors) > 0 {
|
||||||
|
c.Putln("// May return %s errors.", strings.Join(undocErrors, ", "))
|
||||||
|
c.Putln("//")
|
||||||
|
}
|
||||||
|
|
||||||
if r.Reply != nil {
|
if r.Reply != nil {
|
||||||
c.Putln("// %s sends a checked request.", r.SrcName())
|
c.Putln("// %s sends a checked request.", r.SrcName())
|
||||||
c.Putln("// If an error occurs, it will be returned with the reply "+
|
c.Putln("// If an error occurs, it will be returned with the reply "+
|
||||||
|
|
|
@ -6,6 +6,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *SingleField) Define(c *Context) {
|
func (f *SingleField) Define(c *Context) {
|
||||||
|
if f.Comment != "" {
|
||||||
|
c.PutComment(f.Comment)
|
||||||
|
}
|
||||||
c.Putln("%s %s", f.SrcName(), f.Type.SrcName())
|
c.Putln("%s %s", f.SrcName(), f.Type.SrcName())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,22 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Doc contains any documentation, if present. Example C code is excluded.
|
||||||
|
type Doc struct {
|
||||||
|
Brief string // short description
|
||||||
|
Description string // long description
|
||||||
|
Fields map[string]string // from field name to description
|
||||||
|
Errors map[string]string // from error type to description
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescribeField is an accessor that supports nil receivers.
|
||||||
|
func (d *Doc) DescribeField(name string) string {
|
||||||
|
if d == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return d.Fields[name]
|
||||||
|
}
|
||||||
|
|
||||||
// Request represents all XML 'request' nodes.
|
// Request represents all XML 'request' nodes.
|
||||||
// If the request doesn't have a reply, Reply is nil.
|
// If the request doesn't have a reply, Reply is nil.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
|
@ -15,6 +31,7 @@ type Request struct {
|
||||||
Combine bool // Not currently used.
|
Combine bool // Not currently used.
|
||||||
Fields []Field // All fields in the request.
|
Fields []Field // All fields in the request.
|
||||||
Reply *Reply // A reply, if one exists for this request.
|
Reply *Reply // A reply, if one exists for this request.
|
||||||
|
Doc Doc // Documentation.
|
||||||
}
|
}
|
||||||
|
|
||||||
type Requests []*Request
|
type Requests []*Request
|
||||||
|
@ -126,6 +143,7 @@ func (r *Request) Size(c *Context) Size {
|
||||||
// Reply encapsulates the fields associated with a 'reply' element.
|
// Reply encapsulates the fields associated with a 'reply' element.
|
||||||
type Reply struct {
|
type Reply struct {
|
||||||
Fields []Field
|
Fields []Field
|
||||||
|
Doc Doc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size gets the number of bytes in this request's reply.
|
// Size gets the number of bytes in this request's reply.
|
||||||
|
|
|
@ -7,13 +7,11 @@ the XML type into our "better" representation.
|
||||||
i.e., the representation of Fields and Expressions is just too general.
|
i.e., the representation of Fields and Expressions is just too general.
|
||||||
We end up losing a lot of the advantages of static typing if we keep
|
We end up losing a lot of the advantages of static typing if we keep
|
||||||
the types that encoding/xml forces us into.
|
the types that encoding/xml forces us into.
|
||||||
|
|
||||||
Please see 'representation.go' for the type definitions that we're
|
|
||||||
translating to.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -138,12 +136,10 @@ func (x *XMLEvent) Translate() *Event {
|
||||||
Number: x.Number,
|
Number: x.Number,
|
||||||
NoSequence: x.NoSequence,
|
NoSequence: x.NoSequence,
|
||||||
Fields: make([]Field, 0, len(x.Fields)),
|
Fields: make([]Field, 0, len(x.Fields)),
|
||||||
|
Doc: x.Doc.Translate(),
|
||||||
}
|
}
|
||||||
for _, field := range x.Fields {
|
for _, field := range x.Fields {
|
||||||
if field.XMLName.Local == "doc" {
|
ev.Fields = append(ev.Fields, field.Translate(ev, &ev.Doc))
|
||||||
continue
|
|
||||||
}
|
|
||||||
ev.Fields = append(ev.Fields, field.Translate(ev))
|
|
||||||
}
|
}
|
||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
@ -163,7 +159,7 @@ func (x *XMLError) Translate() *Error {
|
||||||
Fields: make([]Field, len(x.Fields)),
|
Fields: make([]Field, len(x.Fields)),
|
||||||
}
|
}
|
||||||
for i, field := range x.Fields {
|
for i, field := range x.Fields {
|
||||||
err.Fields[i] = field.Translate(err)
|
err.Fields[i] = field.Translate(err, nil)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -176,13 +172,82 @@ func (x *XMLErrorCopy) Translate() *ErrorCopy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XCB documentation is positively stuffed with TODOs. We'd like to make it
|
||||||
|
// look a bit less shitty, so remove those as they don't convey information.
|
||||||
|
//
|
||||||
|
// ^TODO
|
||||||
|
// ^TODO:
|
||||||
|
// ^TODO: question?
|
||||||
|
// ^TODO: Some words
|
||||||
|
// ^TODO: some words
|
||||||
|
// ^TODO: some words with full stop.
|
||||||
|
// ^TODO: some words with full stop. and a question?
|
||||||
|
// ... (TODO),
|
||||||
|
// ... (TODO).
|
||||||
|
// ... (TODO: a question?).
|
||||||
|
// ... TODO: a question?
|
||||||
|
// ... (word TODO) ...
|
||||||
|
var todoRE = regexp.MustCompile(`(?m:^TODO.*| \([^)]*TODO[^)]*\)| TODO:.*)`)
|
||||||
|
var paraRE = regexp.MustCompile(`\n{3,}`)
|
||||||
|
|
||||||
|
var backticksRE = regexp.MustCompile("`(?:xcb_|XCB_)?(.*?)(?:_t)?`")
|
||||||
|
|
||||||
|
// fixDocumentation tries to translate XCB documentation to match XGB.
|
||||||
|
// Note that <doc> blocks only appear in xproto, so this doesn't have that much
|
||||||
|
// of a value--users still need to read Xlib or X11 protocol docs.
|
||||||
|
// Despite that, it's better to have something than nothing.
|
||||||
|
//
|
||||||
|
// We don't attempt to add proper prefixes to enum values or guess at
|
||||||
|
// specific XCB_NONE types (the latter is undecidable).
|
||||||
|
//
|
||||||
|
// We can't decide whether `fields_len` should be converted to len(Fields)
|
||||||
|
// or FieldsLen because e.g. StringLen is retained in ImageText8/16 but
|
||||||
|
// PointsLen is implied by the length of the Points slice in PolyLine.
|
||||||
|
func fixDocumentation(xcb string) string {
|
||||||
|
last, result := 0, make([]byte, 0, len(xcb))
|
||||||
|
for _, m := range backticksRE.FindAllStringSubmatchIndex(xcb, -1) {
|
||||||
|
result = append(result, xcb[last:m[0]]...)
|
||||||
|
inner := xcb[m[2]:m[3]]
|
||||||
|
last = m[1]
|
||||||
|
|
||||||
|
// Do not convert atom names to identifiers, mainly _NET_WM_*.
|
||||||
|
if strings.Contains(inner, "WM_") {
|
||||||
|
result = append(result, inner...)
|
||||||
|
} else {
|
||||||
|
result = append(result, splitAndTitle(inner)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = todoRE.ReplaceAllLiteral(append(result, xcb[last:]...), nil)
|
||||||
|
result = paraRE.ReplaceAllLiteral(result, []byte("\n\n"))
|
||||||
|
return strings.TrimSpace(string(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XMLDoc) Translate() Doc {
|
||||||
|
if x == nil {
|
||||||
|
return Doc{}
|
||||||
|
}
|
||||||
|
d := Doc{
|
||||||
|
Brief: fixDocumentation(x.Brief),
|
||||||
|
Description: fixDocumentation(x.Description),
|
||||||
|
Fields: make(map[string]string),
|
||||||
|
Errors: make(map[string]string),
|
||||||
|
}
|
||||||
|
for _, x := range x.Fields {
|
||||||
|
d.Fields[x.Name] = fixDocumentation(x.Description)
|
||||||
|
}
|
||||||
|
for _, x := range x.Errors {
|
||||||
|
d.Errors[x.Type] = fixDocumentation(x.Description)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
func (x *XMLStruct) Translate() *Struct {
|
func (x *XMLStruct) Translate() *Struct {
|
||||||
s := &Struct{
|
s := &Struct{
|
||||||
xmlName: x.Name,
|
xmlName: x.Name,
|
||||||
Fields: make([]Field, len(x.Fields)),
|
Fields: make([]Field, len(x.Fields)),
|
||||||
}
|
}
|
||||||
for i, field := range x.Fields {
|
for i, field := range x.Fields {
|
||||||
s.Fields[i] = field.Translate(s)
|
s.Fields[i] = field.Translate(s, nil)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
@ -193,7 +258,7 @@ func (x *XMLUnion) Translate() *Union {
|
||||||
Fields: make([]Field, len(x.Fields)),
|
Fields: make([]Field, len(x.Fields)),
|
||||||
}
|
}
|
||||||
for i, field := range x.Fields {
|
for i, field := range x.Fields {
|
||||||
u.Fields[i] = field.Translate(u)
|
u.Fields[i] = field.Translate(u, nil)
|
||||||
}
|
}
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
@ -205,12 +270,13 @@ func (x *XMLRequest) Translate() *Request {
|
||||||
Combine: x.Combine,
|
Combine: x.Combine,
|
||||||
Fields: make([]Field, 0, len(x.Fields)),
|
Fields: make([]Field, 0, len(x.Fields)),
|
||||||
Reply: x.Reply.Translate(),
|
Reply: x.Reply.Translate(),
|
||||||
|
Doc: x.Doc.Translate(),
|
||||||
}
|
}
|
||||||
for _, field := range x.Fields {
|
for _, field := range x.Fields {
|
||||||
if field.XMLName.Local == "doc" || field.XMLName.Local == "fd" {
|
if field.XMLName.Local == "fd" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.Fields = append(r.Fields, field.Translate(r))
|
r.Fields = append(r.Fields, field.Translate(r, &r.Doc))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address bug (or legacy code) in QueryTextExtents.
|
// Address bug (or legacy code) in QueryTextExtents.
|
||||||
|
@ -236,12 +302,13 @@ func (x *XMLReply) Translate() *Reply {
|
||||||
|
|
||||||
r := &Reply{
|
r := &Reply{
|
||||||
Fields: make([]Field, 0, len(x.Fields)),
|
Fields: make([]Field, 0, len(x.Fields)),
|
||||||
|
Doc: x.Doc.Translate(),
|
||||||
}
|
}
|
||||||
for _, field := range x.Fields {
|
for _, field := range x.Fields {
|
||||||
if field.XMLName.Local == "doc" || field.XMLName.Local == "fd" {
|
if field.XMLName.Local == "fd" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.Fields = append(r.Fields, field.Translate(r))
|
r.Fields = append(r.Fields, field.Translate(r, &r.Doc))
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -320,7 +387,7 @@ func (x *XMLExpression) Translate() Expression {
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *XMLField) Translate(parent interface{}) Field {
|
func (x *XMLField) Translate(parent interface{}, doc *Doc) Field {
|
||||||
switch x.XMLName.Local {
|
switch x.XMLName.Local {
|
||||||
case "pad":
|
case "pad":
|
||||||
return &PadField{
|
return &PadField{
|
||||||
|
@ -331,6 +398,7 @@ func (x *XMLField) Translate(parent interface{}) Field {
|
||||||
s := &SingleField{
|
s := &SingleField{
|
||||||
xmlName: x.Name,
|
xmlName: x.Name,
|
||||||
Type: newTranslation(x.Type),
|
Type: newTranslation(x.Type),
|
||||||
|
Comment: doc.DescribeField(x.Name),
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
case "list":
|
case "list":
|
||||||
|
@ -356,12 +424,15 @@ func (x *XMLField) Translate(parent interface{}) Field {
|
||||||
MaskType: newTranslation(x.ValueMaskType),
|
MaskType: newTranslation(x.ValueMaskType),
|
||||||
MaskName: x.ValueMaskName,
|
MaskName: x.ValueMaskName,
|
||||||
ListName: x.ValueListName,
|
ListName: x.ValueListName,
|
||||||
|
MaskComment: doc.DescribeField(x.ValueMaskName),
|
||||||
|
ListComment: doc.DescribeField(x.ValueListName),
|
||||||
}
|
}
|
||||||
case "switch":
|
case "switch":
|
||||||
swtch := &SwitchField{
|
swtch := &SwitchField{
|
||||||
Name: x.Name,
|
Name: x.Name,
|
||||||
Expr: x.Expr.Translate(),
|
Expr: x.Expr.Translate(),
|
||||||
Bitcases: make([]*Bitcase, len(x.Bitcases)),
|
Bitcases: make([]*Bitcase, len(x.Bitcases)),
|
||||||
|
Comment: doc.DescribeField(x.Name),
|
||||||
}
|
}
|
||||||
for i, bitcase := range x.Bitcases {
|
for i, bitcase := range x.Bitcases {
|
||||||
swtch.Bitcases[i] = bitcase.Translate()
|
swtch.Bitcases[i] = bitcase.Translate()
|
||||||
|
@ -381,7 +452,7 @@ func (x *XMLBitcase) Translate() *Bitcase {
|
||||||
Fields: make([]Field, len(x.Fields)),
|
Fields: make([]Field, len(x.Fields)),
|
||||||
}
|
}
|
||||||
for i, field := range x.Fields {
|
for i, field := range x.Fields {
|
||||||
b.Fields[i] = field.Translate(b)
|
b.Fields[i] = field.Translate(b, nil)
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,6 +190,7 @@ type Event struct {
|
||||||
Number int
|
Number int
|
||||||
NoSequence bool
|
NoSequence bool
|
||||||
Fields []Field
|
Fields []Field
|
||||||
|
Doc Doc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Event) SrcName() string {
|
func (e *Event) SrcName() string {
|
||||||
|
|
|
@ -92,6 +92,24 @@ type XMLErrorCopy struct {
|
||||||
Ref string `xml:"ref,attr"`
|
Ref string `xml:"ref,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type XMLDocField struct {
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Description string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type XMLDocError struct {
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
Description string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type XMLDoc struct {
|
||||||
|
Brief string `xml:"brief"`
|
||||||
|
Description string `xml:"description"`
|
||||||
|
Example string `xml:"example"`
|
||||||
|
Fields []*XMLDocField `xml:"field"`
|
||||||
|
Errors []*XMLDocError `xml:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
type XMLStruct struct {
|
type XMLStruct struct {
|
||||||
Name string `xml:"name,attr"`
|
Name string `xml:"name,attr"`
|
||||||
Fields []*XMLField `xml:",any"`
|
Fields []*XMLField `xml:",any"`
|
||||||
|
@ -108,10 +126,12 @@ type XMLRequest struct {
|
||||||
Combine bool `xml:"combine-adjacent,attr"`
|
Combine bool `xml:"combine-adjacent,attr"`
|
||||||
Fields []*XMLField `xml:",any"`
|
Fields []*XMLField `xml:",any"`
|
||||||
Reply *XMLReply `xml:"reply"`
|
Reply *XMLReply `xml:"reply"`
|
||||||
|
Doc *XMLDoc `xml:"doc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type XMLReply struct {
|
type XMLReply struct {
|
||||||
Fields []*XMLField `xml:",any"`
|
Fields []*XMLField `xml:",any"`
|
||||||
|
Doc *XMLDoc `xml:"doc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type XMLEvent struct {
|
type XMLEvent struct {
|
||||||
|
@ -119,6 +139,7 @@ type XMLEvent struct {
|
||||||
Number int `xml:"number,attr"`
|
Number int `xml:"number,attr"`
|
||||||
NoSequence bool `xml:"no-sequence-number,attr"`
|
NoSequence bool `xml:"no-sequence-number,attr"`
|
||||||
Fields []*XMLField `xml:",any"`
|
Fields []*XMLField `xml:",any"`
|
||||||
|
Doc *XMLDoc `xml:"doc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type XMLError struct {
|
type XMLError struct {
|
||||||
|
|
Loading…
Reference in New Issue