mirror of https://github.com/golang/go.git
Merge a79fce7705 into 49cdf0c42e
This commit is contained in:
commit
1aa6c7d7e9
|
|
@ -317,6 +317,41 @@ func (enc *Encoder) Close() error {
|
|||
return enc.p.Close()
|
||||
}
|
||||
|
||||
// element represents a serialized XML element, in the form of either:
|
||||
// <$name>
|
||||
// <$name xmlns="$xmlns">
|
||||
// <$prefix:$name>
|
||||
// <$prefix:$name xmlns:$prefix="$xmlns">
|
||||
type element struct {
|
||||
xmlns string
|
||||
prefix string
|
||||
name string
|
||||
nsToPrefix map[string]string
|
||||
prefixToNS map[string]string
|
||||
}
|
||||
|
||||
func newElement(name Name) element {
|
||||
var e element
|
||||
e.xmlns = name.Space
|
||||
e.prefix, e.name = splitPrefixed(name.Local)
|
||||
return e
|
||||
}
|
||||
|
||||
func splitPrefixed(prefixed string) (prefix, name string) {
|
||||
i := strings.Index(prefixed, ":")
|
||||
if i < 1 || i > len(prefixed)-2 {
|
||||
return "", prefixed
|
||||
}
|
||||
return prefixed[:i], prefixed[i+1:]
|
||||
}
|
||||
|
||||
func joinPrefixed(prefix, name string) string {
|
||||
if prefix == "" {
|
||||
return name
|
||||
}
|
||||
return prefix + ":" + name
|
||||
}
|
||||
|
||||
type printer struct {
|
||||
w *bufio.Writer
|
||||
encoder *Encoder
|
||||
|
|
@ -326,38 +361,62 @@ type printer struct {
|
|||
depth int
|
||||
indentedIn bool
|
||||
putNewline bool
|
||||
attrNS map[string]string // map prefix -> name space
|
||||
attrPrefix map[string]string // map name space -> prefix
|
||||
prefixes []string
|
||||
tags []Name
|
||||
elements []element
|
||||
closed bool
|
||||
err error
|
||||
}
|
||||
|
||||
// createAttrPrefix finds the name space prefix attribute to use for the given name space,
|
||||
// defining a new prefix if necessary. It returns the prefix.
|
||||
func (p *printer) createAttrPrefix(url string) string {
|
||||
if prefix := p.attrPrefix[url]; prefix != "" {
|
||||
return prefix
|
||||
}
|
||||
|
||||
// The "http://www.w3.org/XML/1998/namespace" name space is predefined as "xml"
|
||||
// and must be referred to that way.
|
||||
// (The "http://www.w3.org/2000/xmlns/" name space is also predefined as "xmlns",
|
||||
// but users should not be trying to use that one directly - that's our job.)
|
||||
if url == xmlURL {
|
||||
// getPrefix finds the prefix to use for the given namespace URI, but does not create it.
|
||||
func (p *printer) getPrefix(uri string) string {
|
||||
switch uri {
|
||||
case xmlURL:
|
||||
// The "http://www.w3.org/XML/1998/namespace" namespace is predefined as "xml"
|
||||
// and must be referred to that way.
|
||||
return xmlPrefix
|
||||
case xmlnsURL:
|
||||
// (The "http://www.w3.org/2000/xmlns/" namespace is also predefined as "xmlns",
|
||||
// but users should not be trying to use that one directly - that's our job.)
|
||||
return xmlnsPrefix
|
||||
}
|
||||
for i := len(p.elements) - 1; i >= 0; i-- {
|
||||
prefix := p.elements[i].nsToPrefix[uri]
|
||||
if prefix != "" {
|
||||
// Look for prefix collision deeper in the tree
|
||||
for i++; i < len(p.elements); i++ {
|
||||
if p.elements[i].prefixToNS[prefix] != "" {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// createPrefix finds a prefix to use for the given namespace URI,
|
||||
// defining a new prefix if necessary. It returns the prefix.
|
||||
// If set, it will attempt to use preferred as the prefix.
|
||||
func (p *printer) createPrefix(uri, preferred string) (string, bool) {
|
||||
if prefix := p.getPrefix(uri); prefix != "" && (prefix == preferred || preferred == "") {
|
||||
return prefix, false
|
||||
}
|
||||
|
||||
// Need to define a new name space.
|
||||
if p.attrPrefix == nil {
|
||||
p.attrPrefix = make(map[string]string)
|
||||
p.attrNS = make(map[string]string)
|
||||
if len(p.elements) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Pick a name. We try to use the final element of the path
|
||||
// but fall back to _.
|
||||
prefix := strings.TrimRight(url, "/")
|
||||
// Need to define a new namespace prefix
|
||||
e := &p.elements[len(p.elements)-1]
|
||||
if e.nsToPrefix == nil {
|
||||
e.nsToPrefix = make(map[string]string)
|
||||
e.prefixToNS = make(map[string]string)
|
||||
}
|
||||
|
||||
// Pick a name. Try to use the final element of the path but fall back to _.
|
||||
prefix := preferred
|
||||
if prefix == "" || e.prefixToNS[prefix] != "" {
|
||||
prefix = strings.TrimRight(uri, "/")
|
||||
}
|
||||
if i := strings.LastIndex(prefix, "/"); i >= 0 {
|
||||
prefix = prefix[i+1:]
|
||||
}
|
||||
|
|
@ -371,49 +430,30 @@ func (p *printer) createAttrPrefix(url string) string {
|
|||
if len(prefix) >= 3 && strings.EqualFold(prefix[:3], "xml") {
|
||||
prefix = "_" + prefix
|
||||
}
|
||||
if p.attrNS[prefix] != "" {
|
||||
if e.prefixToNS[prefix] != "" {
|
||||
// Name is taken. Find a better one.
|
||||
for p.seq++; ; p.seq++ {
|
||||
if id := prefix + "_" + strconv.Itoa(p.seq); p.attrNS[id] == "" {
|
||||
if id := prefix + "_" + strconv.Itoa(p.seq); e.prefixToNS[id] == "" {
|
||||
prefix = id
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.attrPrefix[url] = prefix
|
||||
p.attrNS[prefix] = url
|
||||
if e.nsToPrefix[uri] == "" {
|
||||
e.nsToPrefix[uri] = prefix
|
||||
}
|
||||
e.prefixToNS[prefix] = uri
|
||||
|
||||
return prefix, true
|
||||
}
|
||||
|
||||
func (p *printer) writePrefixAttr(prefix, uri string) {
|
||||
p.WriteString(`xmlns:`)
|
||||
p.WriteString(prefix)
|
||||
p.WriteString(`="`)
|
||||
EscapeText(p, []byte(url))
|
||||
p.WriteString(`" `)
|
||||
|
||||
p.prefixes = append(p.prefixes, prefix)
|
||||
|
||||
return prefix
|
||||
}
|
||||
|
||||
// deleteAttrPrefix removes an attribute name space prefix.
|
||||
func (p *printer) deleteAttrPrefix(prefix string) {
|
||||
delete(p.attrPrefix, p.attrNS[prefix])
|
||||
delete(p.attrNS, prefix)
|
||||
}
|
||||
|
||||
func (p *printer) markPrefix() {
|
||||
p.prefixes = append(p.prefixes, "")
|
||||
}
|
||||
|
||||
func (p *printer) popPrefix() {
|
||||
for len(p.prefixes) > 0 {
|
||||
prefix := p.prefixes[len(p.prefixes)-1]
|
||||
p.prefixes = p.prefixes[:len(p.prefixes)-1]
|
||||
if prefix == "" {
|
||||
break
|
||||
}
|
||||
p.deleteAttrPrefix(prefix)
|
||||
}
|
||||
p.EscapeString(uri)
|
||||
p.WriteByte('"')
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -500,18 +540,20 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
|
|||
} else if tinfo.xmlname != nil {
|
||||
xmlname := tinfo.xmlname
|
||||
if xmlname.name != "" {
|
||||
start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name
|
||||
start.Name.Space, start.Name.Local = xmlname.xmlns, joinPrefixed(xmlname.prefix, xmlname.name)
|
||||
} else {
|
||||
fv := xmlname.value(val, dontInitNilPointers)
|
||||
if v, ok := fv.Interface().(Name); ok && v.Local != "" {
|
||||
start.Name = v
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No enforced namespace, i.e. the outer tag namespace remains valid
|
||||
}
|
||||
if start.Name.Local == "" && finfo != nil {
|
||||
start.Name.Space, start.Name.Local = finfo.xmlns, finfo.name
|
||||
if start.Name.Local == "" && finfo != nil { // XMLName overrides tag name - anonymous struct
|
||||
start.Name.Space, start.Name.Local = finfo.xmlns, joinPrefixed(finfo.prefix, finfo.name)
|
||||
}
|
||||
if start.Name.Local == "" {
|
||||
if start.Name.Local == "" { // No or empty XMLName and still no tag name
|
||||
name := typ.Name()
|
||||
if i := strings.IndexByte(name, '['); i >= 0 {
|
||||
// Truncate generic instantiation name. See issue 48318.
|
||||
|
|
@ -529,6 +571,7 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
|
|||
if finfo.flags&fAttr == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fv := finfo.value(val, dontInitNilPointers)
|
||||
|
||||
if finfo.flags&fOmitEmpty != 0 && (!fv.IsValid() || isEmptyValue(fv)) {
|
||||
|
|
@ -539,7 +582,7 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
|
|||
continue
|
||||
}
|
||||
|
||||
name := Name{Space: finfo.xmlns, Local: finfo.name}
|
||||
name := Name{Space: finfo.xmlns, Local: joinPrefixed(finfo.prefix, finfo.name)}
|
||||
if err := p.marshalAttr(&start, name, fv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -548,9 +591,10 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat
|
|||
// If an empty name was found, namespace is overridden with an empty space
|
||||
if tinfo.xmlname != nil && start.Name.Space == "" &&
|
||||
tinfo.xmlname.xmlns == "" && tinfo.xmlname.name == "" &&
|
||||
len(p.tags) != 0 && p.tags[len(p.tags)-1].Space != "" {
|
||||
start.Attr = append(start.Attr, Attr{Name{"", xmlnsPrefix}, ""})
|
||||
len(p.elements) != 0 && p.elements[len(p.elements)-1].xmlns != "" {
|
||||
start.Attr = append(start.Attr, Attr{Name{Space: "", Local: xmlnsPrefix}, ""})
|
||||
}
|
||||
|
||||
if err := p.writeStart(&start); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -672,7 +716,7 @@ func defaultStart(typ reflect.Type, finfo *fieldInfo, startTemplate *StartElemen
|
|||
start.Name = startTemplate.Name
|
||||
start.Attr = append(start.Attr, startTemplate.Attr...)
|
||||
} else if finfo != nil && finfo.name != "" {
|
||||
start.Name.Local = finfo.name
|
||||
start.Name.Local = joinPrefixed(finfo.prefix, finfo.name)
|
||||
start.Name.Space = finfo.xmlns
|
||||
} else if typ.Name() != "" {
|
||||
start.Name.Local = typ.Name()
|
||||
|
|
@ -686,21 +730,21 @@ func defaultStart(typ reflect.Type, finfo *fieldInfo, startTemplate *StartElemen
|
|||
|
||||
// marshalInterface marshals a Marshaler interface value.
|
||||
func (p *printer) marshalInterface(val Marshaler, start StartElement) error {
|
||||
// Push a marker onto the tag stack so that MarshalXML
|
||||
// Push a marker onto the element stack so that MarshalXML
|
||||
// cannot close the XML tags that it did not open.
|
||||
p.tags = append(p.tags, Name{})
|
||||
n := len(p.tags)
|
||||
p.elements = append(p.elements, element{})
|
||||
n := len(p.elements)
|
||||
|
||||
err := val.MarshalXML(p.encoder, start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure MarshalXML closed all its tags. p.tags[n-1] is the mark.
|
||||
if len(p.tags) > n {
|
||||
return fmt.Errorf("xml: %s.MarshalXML wrote invalid XML: <%s> not closed", receiverType(val), p.tags[len(p.tags)-1].Local)
|
||||
// Make sure MarshalXML closed all its tags. p.elements[n-1] is the mark.
|
||||
if len(p.elements) > n {
|
||||
return fmt.Errorf("xml: %s.MarshalXML wrote invalid XML: <%s> not closed", receiverType(val), p.elements[len(p.elements)-1].name)
|
||||
}
|
||||
p.tags = p.tags[:n-1]
|
||||
p.elements = p.elements[:n-1]
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -723,31 +767,95 @@ func (p *printer) writeStart(start *StartElement) error {
|
|||
return fmt.Errorf("xml: start tag with no name")
|
||||
}
|
||||
|
||||
p.tags = append(p.tags, start.Name)
|
||||
p.markPrefix()
|
||||
// Push a new XML element
|
||||
p.elements = append(p.elements, newElement(start.Name))
|
||||
e := &p.elements[len(p.elements)-1]
|
||||
|
||||
p.writeIndent(1)
|
||||
// Set the namespace prefix, if necessary
|
||||
var spaceDefined bool
|
||||
if e.xmlns != "" {
|
||||
// Try to avoid needing a prefix first.
|
||||
for _, attr := range start.Attr {
|
||||
if attr.Name.Space == "" && attr.Name.Local == xmlnsPrefix && attr.Value == e.xmlns {
|
||||
spaceDefined = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// Walk up the tree to see if a default namespace is already defined without a prefix.
|
||||
if !spaceDefined && e.prefix == "" {
|
||||
for i := len(p.elements) - 2; i >= 0; i-- {
|
||||
if p.elements[i].prefix == "" && p.elements[i].xmlns == e.xmlns {
|
||||
spaceDefined = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Locate an eventual prefix. If none, the XML is <tag name and no short notation is used
|
||||
if !spaceDefined && e.prefix == "" {
|
||||
// Look for an xmlns:prefix="uri" attribute that matches tag
|
||||
for _, attr := range start.Attr {
|
||||
if attr.Name.Space == xmlnsURL && attr.Name.Local != "" && attr.Value == e.xmlns {
|
||||
e.prefix, _ = p.createPrefix(attr.Value, attr.Name.Local)
|
||||
spaceDefined = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !spaceDefined && e.prefix == "" {
|
||||
e.prefix = p.getPrefix(e.xmlns)
|
||||
if e.prefix != "" {
|
||||
spaceDefined = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.writeIndent(1) // handling relative depth of a tag
|
||||
p.WriteByte('<')
|
||||
p.WriteString(start.Name.Local)
|
||||
|
||||
if start.Name.Space != "" {
|
||||
p.WriteString(` xmlns="`)
|
||||
p.EscapeString(start.Name.Space)
|
||||
p.WriteByte('"')
|
||||
if e.prefix != "" {
|
||||
var prefixCreated bool
|
||||
if e.xmlns != "" && !spaceDefined {
|
||||
e.prefix, prefixCreated = p.createPrefix(e.xmlns, e.prefix)
|
||||
}
|
||||
p.WriteString(e.prefix)
|
||||
p.WriteByte(':')
|
||||
p.WriteString(e.name)
|
||||
if prefixCreated {
|
||||
p.WriteByte(' ')
|
||||
p.writePrefixAttr(e.prefix, e.xmlns)
|
||||
}
|
||||
} else if e.xmlns != "" {
|
||||
p.WriteString(e.name)
|
||||
if !spaceDefined {
|
||||
p.WriteString(` xmlns="`)
|
||||
p.EscapeString(e.xmlns)
|
||||
p.WriteByte('"')
|
||||
}
|
||||
} else {
|
||||
p.WriteString(e.name)
|
||||
}
|
||||
|
||||
// Attributes
|
||||
for _, attr := range start.Attr {
|
||||
name := attr.Name
|
||||
if name.Local == "" {
|
||||
if attr.Name.Local == "" {
|
||||
continue
|
||||
}
|
||||
prefix, local := splitPrefixed(attr.Name.Local)
|
||||
p.WriteByte(' ')
|
||||
if name.Space != "" {
|
||||
p.WriteString(p.createAttrPrefix(name.Space))
|
||||
if attr.Name.Space == xmlnsURL {
|
||||
p.createPrefix(attr.Value, local)
|
||||
p.WriteString(xmlnsPrefix)
|
||||
p.WriteByte(':')
|
||||
} else if attr.Name.Space != "" {
|
||||
var prefixCreated bool
|
||||
prefix, prefixCreated = p.createPrefix(attr.Name.Space, prefix)
|
||||
if prefixCreated {
|
||||
p.writePrefixAttr(prefix, attr.Name.Space)
|
||||
p.WriteByte(' ')
|
||||
}
|
||||
p.WriteString(prefix)
|
||||
p.WriteByte(':')
|
||||
}
|
||||
p.WriteString(name.Local)
|
||||
p.WriteString(local)
|
||||
p.WriteString(`="`)
|
||||
p.EscapeString(attr.Value)
|
||||
p.WriteByte('"')
|
||||
|
|
@ -760,23 +868,34 @@ func (p *printer) writeEnd(name Name) error {
|
|||
if name.Local == "" {
|
||||
return fmt.Errorf("xml: end tag with no name")
|
||||
}
|
||||
if len(p.tags) == 0 || p.tags[len(p.tags)-1].Local == "" {
|
||||
if len(p.elements) == 0 || p.elements[len(p.elements)-1].name == "" {
|
||||
return fmt.Errorf("xml: end tag </%s> without start tag", name.Local)
|
||||
}
|
||||
if top := p.tags[len(p.tags)-1]; top != name {
|
||||
if top.Local != name.Local {
|
||||
return fmt.Errorf("xml: end tag </%s> does not match start tag <%s>", name.Local, top.Local)
|
||||
}
|
||||
return fmt.Errorf("xml: end tag </%s> in namespace %s does not match start tag <%s> in namespace %s", name.Local, name.Space, top.Local, top.Space)
|
||||
e := p.elements[len(p.elements)-1]
|
||||
prefix, local := splitPrefixed(name.Local)
|
||||
// Tag prefixes do not match
|
||||
if prefix != "" && e.prefix != "" && prefix != e.prefix {
|
||||
return fmt.Errorf("xml: end prefix </%s:%s> does not match start prefix <%s:%s>", prefix, local, e.prefix, e.name)
|
||||
}
|
||||
// Tag names do not match
|
||||
if local != e.name {
|
||||
return fmt.Errorf("xml: end tag </%s> does not match start tag <%s>", local, e.name)
|
||||
}
|
||||
// Namespaces do not match
|
||||
if name.Space != e.xmlns {
|
||||
return fmt.Errorf("xml: end namespace %q does not match start namespace %q", name.Space, e.xmlns)
|
||||
}
|
||||
p.tags = p.tags[:len(p.tags)-1]
|
||||
|
||||
p.writeIndent(-1)
|
||||
p.WriteByte('<')
|
||||
p.WriteByte('/')
|
||||
p.WriteString(name.Local)
|
||||
if e.prefix != "" {
|
||||
p.WriteString(e.prefix)
|
||||
p.WriteByte(':')
|
||||
}
|
||||
p.WriteString(e.name)
|
||||
p.WriteByte('>')
|
||||
p.popPrefix()
|
||||
// Pop elements stack
|
||||
p.elements = p.elements[:len(p.elements)-1]
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1030,8 +1149,8 @@ func (p *printer) Close() error {
|
|||
if err := p.w.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(p.tags) > 0 {
|
||||
return fmt.Errorf("unclosed tag <%s>", p.tags[len(p.tags)-1].Local)
|
||||
if len(p.elements) > 0 {
|
||||
return fmt.Errorf("unclosed tag <%s>", p.elements[len(p.elements)-1].name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -528,6 +528,38 @@ type Generic[T any] struct {
|
|||
X T
|
||||
}
|
||||
|
||||
type EPP struct {
|
||||
XMLName struct{} `xml:"urn:ietf:params:xml:ns:epp-1.0 epp"`
|
||||
Command *Command `xml:"command,omitempty"`
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Check *Check `xml:"urn:ietf:params:xml:ns:epp-1.0 check,omitempty"`
|
||||
}
|
||||
|
||||
type Check struct {
|
||||
DomainCheck *DomainCheck `xml:"urn:ietf:params:xml:ns:domain-1.0 domain:check,omitempty"`
|
||||
}
|
||||
|
||||
type DomainCheck struct {
|
||||
DomainNames []string `xml:"urn:ietf:params:xml:ns:domain-1.0 domain:name,omitempty"`
|
||||
}
|
||||
|
||||
type SecureEnvelope struct {
|
||||
XMLName struct{} `xml:"urn:test:secure-1.0 sec:envelope"`
|
||||
Message *SecureMessage `xml:"urn:test:message-1.0 msg,omitempty"`
|
||||
}
|
||||
|
||||
type SecureMessage struct {
|
||||
Body string `xml:"urn:test:message-1.0 body,omitempty"`
|
||||
Signer string `xml:"urn:test:secure-1.0 sec:signer,attr,omitempty"`
|
||||
}
|
||||
|
||||
type NamespacedNested struct {
|
||||
XMLName struct{} `xml:"urn:test:nested-1.0 nested"`
|
||||
Value string `xml:"urn:test:nested-1.0 nested:wrapper>nested:value"`
|
||||
}
|
||||
|
||||
var (
|
||||
nameAttr = "Sarah"
|
||||
ageAttr = uint(12)
|
||||
|
|
@ -633,6 +665,11 @@ var marshalTests = []struct {
|
|||
{Value: &Port{Type: "ssl", Number: "443"}, ExpectXML: `<port type="ssl">443</port>`},
|
||||
{Value: &Port{Number: "443"}, ExpectXML: `<port>443</port>`},
|
||||
{Value: &Port{Type: "<unix>"}, ExpectXML: `<port type="<unix>"></port>`},
|
||||
// Marshal is not symmetric to Unmarshal for these oddities because &apos is written as '
|
||||
{Value: &Port{Type: "<un'ix>"}, ExpectXML: `<port type="<un'ix>"></port>`, UnmarshalOnly: true},
|
||||
{Value: &Port{Type: "<un\"ix>"}, ExpectXML: `<port type="<un"ix>"></port>`, UnmarshalOnly: true},
|
||||
{Value: &Port{Type: "<un&ix>"}, ExpectXML: `<port type="<un&ix>"></port>`},
|
||||
{Value: &Port{Type: "<unix>"}, ExpectXML: `<port type="<unix>"></port>`, UnmarshalOnly: true},
|
||||
{Value: &Port{Number: "443", Comment: "https"}, ExpectXML: `<port><!--https-->443</port>`},
|
||||
{Value: &Port{Number: "443", Comment: "add space-"}, ExpectXML: `<port><!--add space- -->443</port>`, MarshalOnly: true},
|
||||
{Value: &Domain{Name: []byte("google.com&friends")}, ExpectXML: `<domain>google.com&friends</domain>`},
|
||||
|
|
@ -812,7 +849,7 @@ var marshalTests = []struct {
|
|||
D1: "d1",
|
||||
},
|
||||
ExpectXML: `<top xmlns="space">` +
|
||||
`<x><a>a</a><b>b</b><c xmlns="space">c</c>` +
|
||||
`<x><a>a</a><b>b</b><c>c</c>` +
|
||||
`<c xmlns="space1">c1</c>` +
|
||||
`<d xmlns="space1">d1</d>` +
|
||||
`</x>` +
|
||||
|
|
@ -1052,6 +1089,10 @@ var marshalTests = []struct {
|
|||
Value: &OmitAttrTest{},
|
||||
ExpectXML: `<OmitAttrTest></OmitAttrTest>`,
|
||||
},
|
||||
{
|
||||
Value: &OmitAttrTest{Str: "gopher@golang.org"},
|
||||
ExpectXML: `<OmitAttrTest Str="gopher@golang.org"></OmitAttrTest>`,
|
||||
},
|
||||
|
||||
// pointer fields
|
||||
{
|
||||
|
|
@ -1112,10 +1153,10 @@ var marshalTests = []struct {
|
|||
Value: &AnyTest{Nested: "known",
|
||||
AnyField: AnyHolder{
|
||||
XML: "<unknown/>",
|
||||
XMLName: Name{Local: "AnyField"},
|
||||
XMLName: Name{Local: "other"}, // Overriding the field name is the purpose of the test
|
||||
},
|
||||
},
|
||||
ExpectXML: `<a><nested><value>known</value></nested><AnyField><unknown/></AnyField></a>`,
|
||||
ExpectXML: `<a><nested><value>known</value></nested><other><unknown/></other></a>`,
|
||||
},
|
||||
{
|
||||
ExpectXML: `<a><nested><value>b</value></nested></a>`,
|
||||
|
|
@ -1656,6 +1697,59 @@ var marshalTests = []struct {
|
|||
Value: &DirectAny{Any: string("")},
|
||||
UnmarshalOnly: true,
|
||||
},
|
||||
|
||||
// Test namespace prefixes
|
||||
{
|
||||
ExpectXML: `<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"></epp>`,
|
||||
Value: &EPP{},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"><command></command></epp>`,
|
||||
Value: &EPP{Command: &Command{}},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"><command><check></check></command></epp>`,
|
||||
Value: &EPP{Command: &Command{Check: &Check{}}},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"><command><check><domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"></domain:check></check></command></epp>`,
|
||||
Value: &EPP{Command: &Command{Check: &Check{DomainCheck: &DomainCheck{}}}},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"><command><check><domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"><domain:name>golang.org</domain:name></domain:check></check></command></epp>`,
|
||||
Value: &EPP{Command: &Command{Check: &Check{DomainCheck: &DomainCheck{DomainNames: []string{"golang.org"}}}}},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"><command><check><domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0"><domain:name>golang.org</domain:name><domain:name>go.dev</domain:name></domain:check></check></command></epp>`,
|
||||
Value: &EPP{Command: &Command{Check: &Check{DomainCheck: &DomainCheck{DomainNames: []string{"golang.org", "go.dev"}}}}},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<sec:envelope xmlns:sec="urn:test:secure-1.0"></sec:envelope>`,
|
||||
Value: &SecureEnvelope{},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<sec:envelope xmlns:sec="urn:test:secure-1.0"><msg xmlns="urn:test:message-1.0"></msg></sec:envelope>`,
|
||||
Value: &SecureEnvelope{Message: &SecureMessage{}},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<sec:envelope xmlns:sec="urn:test:secure-1.0"><msg xmlns="urn:test:message-1.0"><body>Hello, world.</body></msg></sec:envelope>`,
|
||||
Value: &SecureEnvelope{Message: &SecureMessage{Body: "Hello, world."}},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<sec:envelope xmlns:sec="urn:test:secure-1.0"><msg xmlns="urn:test:message-1.0" sec:signer="gopher@golang.org"><body>Thanks</body></msg></sec:envelope>`,
|
||||
Value: &SecureEnvelope{Message: &SecureMessage{Body: "Thanks", Signer: "gopher@golang.org"}},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<nested xmlns="urn:test:nested-1.0"><wrapper><nested:value xmlns:nested="urn:test:nested-1.0">You’re welcome!</nested:value></wrapper></nested>`,
|
||||
Value: &NamespacedNested{Value: "You’re welcome!"},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<space:name><space:value>value</space:value></space:name>`,
|
||||
Value: &struct {
|
||||
XMLName struct{} `xml:"space:name"`
|
||||
Value string `xml:"space:value"`
|
||||
}{Value: "value"},
|
||||
},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
|
|
@ -1806,16 +1900,16 @@ func TestUnmarshal(t *testing.T) {
|
|||
|
||||
if err != nil {
|
||||
if test.UnmarshalError == "" {
|
||||
t.Errorf("unmarshal(%#v): %s", test.ExpectXML, err)
|
||||
t.Errorf("unmarshal(%s): %s", test.ExpectXML, err)
|
||||
return
|
||||
}
|
||||
if !strings.Contains(err.Error(), test.UnmarshalError) {
|
||||
t.Errorf("unmarshal(%#v): %s, want %q", test.ExpectXML, err, test.UnmarshalError)
|
||||
t.Errorf("unmarshal(%s): %s, want %q", test.ExpectXML, err, test.UnmarshalError)
|
||||
}
|
||||
return
|
||||
}
|
||||
if got, want := dest, test.Value; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unmarshal(%q):\nhave %#v\nwant %#v", test.ExpectXML, got, want)
|
||||
t.Errorf("unmarshal(%s):\nhave %#v\nwant %#v", test.ExpectXML, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1966,7 +2060,7 @@ var encodeTokenTests = []struct {
|
|||
want string
|
||||
err string
|
||||
}{{
|
||||
desc: "start element with name space",
|
||||
desc: "start element with namespace",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "local"}, nil},
|
||||
},
|
||||
|
|
@ -2063,7 +2157,7 @@ var encodeTokenTests = []struct {
|
|||
StartElement{Name{"space", "foo"}, nil},
|
||||
EndElement{Name{"another", "foo"}},
|
||||
},
|
||||
err: "xml: end tag </foo> in namespace another does not match start tag <foo> in namespace space",
|
||||
err: `xml: end namespace "another" does not match start namespace "space"`,
|
||||
want: `<foo xmlns="space">`,
|
||||
}, {
|
||||
desc: "start element with explicit namespace",
|
||||
|
|
@ -2074,6 +2168,23 @@ var encodeTokenTests = []struct {
|
|||
}},
|
||||
},
|
||||
want: `<local xmlns="space" xmlns:_xmlns="xmlns" _xmlns:x="space" xmlns:space="space" space:foo="value">`,
|
||||
}, {
|
||||
desc: "start element with explicit namespace prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "local"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "x"}, "space"},
|
||||
{Name{"space", "foo"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<x:local xmlns:x="space" x:foo="value">`,
|
||||
}, {
|
||||
desc: "start element with prefixed local name",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "x:local"}, []Attr{
|
||||
{Name{"space", "foo"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<x:local xmlns:x="space" x:foo="value">`,
|
||||
}, {
|
||||
desc: "start element with explicit namespace and colliding prefix",
|
||||
toks: []Token{
|
||||
|
|
@ -2084,6 +2195,25 @@ var encodeTokenTests = []struct {
|
|||
}},
|
||||
},
|
||||
want: `<local xmlns="space" xmlns:_xmlns="xmlns" _xmlns:x="space" xmlns:space="space" space:foo="value" xmlns:x="x" x:bar="other">`,
|
||||
}, {
|
||||
desc: "start element with explicit namespace prefix and colliding prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "local"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "x"}, "space"},
|
||||
{Name{"space", "foo"}, "value"},
|
||||
{Name{"x", "bar"}, "other"},
|
||||
}},
|
||||
},
|
||||
want: `<x:local xmlns:x="space" x:foo="value" xmlns:x_1="x" x_1:bar="other">`,
|
||||
}, {
|
||||
desc: "start element with prefixed local name and colliding prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "x:local"}, []Attr{
|
||||
{Name{"space", "foo"}, "value"},
|
||||
{Name{"x", "bar"}, "other"},
|
||||
}},
|
||||
},
|
||||
want: `<x:local xmlns:x="space" x:foo="value" xmlns:x_1="x" x_1:bar="other">`,
|
||||
}, {
|
||||
desc: "start element using previously defined namespace",
|
||||
toks: []Token{
|
||||
|
|
@ -2096,7 +2226,27 @@ var encodeTokenTests = []struct {
|
|||
},
|
||||
want: `<local xmlns:_xmlns="xmlns" _xmlns:x="space"><foo xmlns="space" xmlns:space="space" space:x="y">`,
|
||||
}, {
|
||||
desc: "nested name space with same prefix",
|
||||
desc: "start element using previously defined namespace prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "local"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "x"}, "space"},
|
||||
}},
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"space", "x"}, "y"},
|
||||
}},
|
||||
},
|
||||
want: `<local xmlns:x="space"><x:foo x:x="y">`,
|
||||
}, {
|
||||
desc: "start element using prefixed local name",
|
||||
toks: []Token{
|
||||
StartElement{Name: Name{"space", "x:local"}},
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"space", "x"}, "y"},
|
||||
}},
|
||||
},
|
||||
want: `<x:local xmlns:x="space"><x:foo x:x="y">`,
|
||||
}, {
|
||||
desc: "nested namespace with same prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"xmlns", "x"}, "space1"},
|
||||
|
|
@ -2117,7 +2267,28 @@ var encodeTokenTests = []struct {
|
|||
},
|
||||
want: `<foo xmlns:_xmlns="xmlns" _xmlns:x="space1"><foo _xmlns:x="space2"><foo xmlns:space1="space1" space1:a="space1 value" xmlns:space2="space2" space2:b="space2 value"></foo></foo><foo xmlns:space1="space1" space1:a="space1 value" xmlns:space2="space2" space2:b="space2 value">`,
|
||||
}, {
|
||||
desc: "start element defining several prefixes for the same name space",
|
||||
desc: "nested namespace with same prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "x"}, "space1"},
|
||||
}},
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "x"}, "space2"},
|
||||
}},
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"space1", "a"}, "space1 value"},
|
||||
{Name{"space2", "b"}, "space2 value"},
|
||||
}},
|
||||
EndElement{Name{"", "foo"}},
|
||||
EndElement{Name{"", "foo"}},
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"space1", "a"}, "space1 value"},
|
||||
{Name{"space2", "b"}, "space2 value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns:x="space1"><foo xmlns:x="space2"><foo xmlns:space1="space1" space1:a="space1 value" x:b="space2 value"></foo></foo><foo x:a="space1 value" xmlns:space2="space2" space2:b="space2 value">`,
|
||||
}, {
|
||||
desc: "start element defining several prefixes for the same namespace",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"xmlns", "a"}, "space"},
|
||||
|
|
@ -2127,7 +2298,17 @@ var encodeTokenTests = []struct {
|
|||
},
|
||||
want: `<foo xmlns="space" xmlns:_xmlns="xmlns" _xmlns:a="space" _xmlns:b="space" xmlns:space="space" space:x="value">`,
|
||||
}, {
|
||||
desc: "nested element redefines name space",
|
||||
desc: "start element explicitly defining several prefixes for the same namespace",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "a"}, "space"},
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "b"}, "space"},
|
||||
{Name{"space", "x"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<a:foo xmlns:a="space" xmlns:b="space" a:x="value">`,
|
||||
}, {
|
||||
desc: "nested element redefines namespace",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"xmlns", "x"}, "space"},
|
||||
|
|
@ -2139,7 +2320,30 @@ var encodeTokenTests = []struct {
|
|||
},
|
||||
want: `<foo xmlns:_xmlns="xmlns" _xmlns:x="space"><foo xmlns="space" _xmlns:y="space" xmlns:space="space" space:a="value">`,
|
||||
}, {
|
||||
desc: "nested element creates alias for default name space",
|
||||
desc: "nested element redefines namespace prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "x"}, "space"},
|
||||
}},
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "y"}, "space"},
|
||||
{Name{"space", "a"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns:x="space"><y:foo xmlns:y="space" y:a="value">`,
|
||||
}, {
|
||||
desc: "nested element explicitly redefines namespace prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "x"}, "space"},
|
||||
}},
|
||||
StartElement{Name{"space", "y:foo"}, []Attr{
|
||||
{Name{"space", "a"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns:x="space"><y:foo xmlns:y="space" y:a="value">`,
|
||||
}, {
|
||||
desc: "nested element creates alias for default namespace",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
|
|
@ -2149,9 +2353,32 @@ var encodeTokenTests = []struct {
|
|||
{Name{"space", "a"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns="space" xmlns="space"><foo xmlns="space" xmlns:_xmlns="xmlns" _xmlns:y="space" xmlns:space="space" space:a="value">`,
|
||||
want: `<foo xmlns="space"><foo xmlns:_xmlns="xmlns" _xmlns:y="space" xmlns:space="space" space:a="value">`,
|
||||
}, {
|
||||
desc: "nested element defines default name space with existing prefix",
|
||||
desc: "nested element creates alias prefix for default namespace",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
}},
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "y"}, "space"},
|
||||
{Name{"space", "a"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns="space"><foo xmlns:y="space" y:a="value">`,
|
||||
}, {
|
||||
desc: "nested element explicitly creates alias prefix for default namespace",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
}},
|
||||
StartElement{Name{"space", "y:foo"}, []Attr{
|
||||
{Name{"space", "a"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns="space"><y:foo xmlns:y="space" y:a="value">`,
|
||||
}, {
|
||||
desc: "nested element defines default namespace with existing prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"xmlns", "x"}, "space"},
|
||||
|
|
@ -2161,9 +2388,31 @@ var encodeTokenTests = []struct {
|
|||
{Name{"space", "a"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns:_xmlns="xmlns" _xmlns:x="space"><foo xmlns="space" xmlns="space" xmlns:space="space" space:a="value">`,
|
||||
want: `<foo xmlns:_xmlns="xmlns" _xmlns:x="space"><foo xmlns="space" xmlns:space="space" space:a="value">`,
|
||||
}, {
|
||||
desc: "nested element uses empty attribute name space when default ns defined",
|
||||
desc: "nested element defines default namespace prefix with existing prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "x"}, "space"},
|
||||
}},
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
{Name{"space", "a"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns:x="space"><foo xmlns="space" x:a="value">`,
|
||||
}, {
|
||||
desc: "nested element defines explicit attribute namespace prefix with existing prefix",
|
||||
toks: []Token{
|
||||
StartElement{Name: Name{"", "foo"}},
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
{Name{"space", "x:a"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo><foo xmlns="space" xmlns:x="space" x:a="value">`,
|
||||
}, {
|
||||
desc: "nested element uses empty attribute namespace when default ns defined",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
|
|
@ -2172,7 +2421,7 @@ var encodeTokenTests = []struct {
|
|||
{Name{"", "attr"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns="space" xmlns="space"><foo xmlns="space" attr="value">`,
|
||||
want: `<foo xmlns="space"><foo attr="value">`,
|
||||
}, {
|
||||
desc: "redefine xmlns",
|
||||
toks: []Token{
|
||||
|
|
@ -2182,7 +2431,7 @@ var encodeTokenTests = []struct {
|
|||
},
|
||||
want: `<foo xmlns:foo="foo" foo:xmlns="space">`,
|
||||
}, {
|
||||
desc: "xmlns with explicit name space #1",
|
||||
desc: "xmlns with explicit namespace #1",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"xml", "xmlns"}, "space"},
|
||||
|
|
@ -2190,7 +2439,7 @@ var encodeTokenTests = []struct {
|
|||
},
|
||||
want: `<foo xmlns="space" xmlns:_xml="xml" _xml:xmlns="space">`,
|
||||
}, {
|
||||
desc: "xmlns with explicit name space #2",
|
||||
desc: "xmlns with explicit namespace #2",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{xmlURL, "xmlns"}, "space"},
|
||||
|
|
@ -2198,13 +2447,21 @@ var encodeTokenTests = []struct {
|
|||
},
|
||||
want: `<foo xmlns="space" xml:xmlns="space">`,
|
||||
}, {
|
||||
desc: "empty name space declaration is ignored",
|
||||
desc: "empty namespace declaration is ignored",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"xmlns", "foo"}, ""},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns:_xmlns="xmlns" _xmlns:foo="">`,
|
||||
}, {
|
||||
desc: "empty namespace prefix declaration is ignored",
|
||||
toks: []Token{
|
||||
StartElement{Name{"", "foo"}, []Attr{
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "foo"}, ""},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns:foo="">`,
|
||||
}, {
|
||||
desc: "attribute with no name is ignored",
|
||||
toks: []Token{
|
||||
|
|
@ -2233,18 +2490,18 @@ var encodeTokenTests = []struct {
|
|||
{Name{"space", "x"}, "value"},
|
||||
}},
|
||||
},
|
||||
want: `<foo xmlns="space" xmlns="space"><foo xmlns="" x="value" xmlns:space="space" space:x="value">`,
|
||||
want: `<foo xmlns="space"><foo xmlns="" x="value" xmlns:space="space" space:x="value">`,
|
||||
}, {
|
||||
desc: "nested element requires empty default name space",
|
||||
desc: "nested element requires empty default namespace",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
}},
|
||||
StartElement{Name{"", "foo"}, nil},
|
||||
},
|
||||
want: `<foo xmlns="space" xmlns="space"><foo>`,
|
||||
want: `<foo xmlns="space"><foo>`,
|
||||
}, {
|
||||
desc: "attribute uses name space from xmlns",
|
||||
desc: "attribute uses namespace from xmlns",
|
||||
toks: []Token{
|
||||
StartElement{Name{"some/space", "foo"}, []Attr{
|
||||
{Name{"", "attr"}, "value"},
|
||||
|
|
@ -2253,7 +2510,7 @@ var encodeTokenTests = []struct {
|
|||
},
|
||||
want: `<foo xmlns="some/space" attr="value" xmlns:space="some/space" space:other="other value">`,
|
||||
}, {
|
||||
desc: "default name space should not be used by attributes",
|
||||
desc: "default namespace should not be used by attributes",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
|
|
@ -2264,9 +2521,22 @@ var encodeTokenTests = []struct {
|
|||
EndElement{Name{"space", "baz"}},
|
||||
EndElement{Name{"space", "foo"}},
|
||||
},
|
||||
want: `<foo xmlns="space" xmlns="space" xmlns:_xmlns="xmlns" _xmlns:bar="space" xmlns:space="space" space:baz="foo"><baz xmlns="space"></baz></foo>`,
|
||||
want: `<foo xmlns="space" xmlns:_xmlns="xmlns" _xmlns:bar="space" xmlns:space="space" space:baz="foo"><baz></baz></foo>`,
|
||||
}, {
|
||||
desc: "default name space not used by attributes, not explicitly defined",
|
||||
desc: "default namespace prefix should not be used by attributes",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
{Name{"http://www.w3.org/2000/xmlns/", "bar"}, "space"},
|
||||
{Name{"space", "baz"}, "foo"},
|
||||
}},
|
||||
StartElement{Name{"space", "baz"}, nil},
|
||||
EndElement{Name{"space", "baz"}},
|
||||
EndElement{Name{"space", "foo"}},
|
||||
},
|
||||
want: `<foo xmlns="space" xmlns:bar="space" bar:baz="foo"><baz></baz></foo>`,
|
||||
}, {
|
||||
desc: "default namespace not used by attributes, not explicitly defined",
|
||||
toks: []Token{
|
||||
StartElement{Name{"space", "foo"}, []Attr{
|
||||
{Name{"", "xmlns"}, "space"},
|
||||
|
|
@ -2276,7 +2546,7 @@ var encodeTokenTests = []struct {
|
|||
EndElement{Name{"space", "baz"}},
|
||||
EndElement{Name{"space", "foo"}},
|
||||
},
|
||||
want: `<foo xmlns="space" xmlns="space" xmlns:space="space" space:baz="foo"><baz xmlns="space"></baz></foo>`,
|
||||
want: `<foo xmlns="space" xmlns:space="space" space:baz="foo"><baz></baz></foo>`,
|
||||
}, {
|
||||
desc: "impossible xmlns declaration",
|
||||
toks: []Token{
|
||||
|
|
@ -2323,7 +2593,7 @@ loop:
|
|||
for j, tok := range tt.toks {
|
||||
err = enc.EncodeToken(tok)
|
||||
if err != nil && j < len(tt.toks)-1 {
|
||||
t.Errorf("#%d %s token #%d: %v", i, tt.desc, j, err)
|
||||
t.Errorf("#%d %s; token #%d: %v", i, tt.desc, j, err)
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,13 @@ import (
|
|||
//
|
||||
// - If the XMLName field has an associated tag of the form
|
||||
// "name" or "namespace-URL name", the XML element must have
|
||||
// the given name (and, optionally, name space) or else Unmarshal
|
||||
// the given name (and, optionally, namespace) or else Unmarshal
|
||||
// returns an error.
|
||||
//
|
||||
// - If the XMLName field contains an XML namespace, it may also
|
||||
// optionally specify a namespace prefix in the form of
|
||||
// "namespace-URL prefix:name".
|
||||
//
|
||||
// - If the XML element has an attribute whose name matches a
|
||||
// struct field name with an associated tag containing ",attr" or
|
||||
// the explicit name in a struct field tag of the form "name,attr",
|
||||
|
|
@ -444,17 +448,31 @@ func (d *Decoder) unmarshal(val reflect.Value, start *StartElement, depth int) e
|
|||
return UnmarshalError("expected element type <" + finfo.name + "> but have <" + start.Name.Local + ">")
|
||||
}
|
||||
if finfo.xmlns != "" && finfo.xmlns != start.Name.Space {
|
||||
e := "expected element <" + finfo.name + "> in name space " + finfo.xmlns + " but have "
|
||||
e := "expected element <" + finfo.name + "> in namespace " + finfo.xmlns + " but have "
|
||||
if start.Name.Space == "" {
|
||||
e += "no name space"
|
||||
e += "no namespace"
|
||||
} else {
|
||||
e += start.Name.Space
|
||||
}
|
||||
return UnmarshalError(e)
|
||||
}
|
||||
fv := finfo.value(sv, initNilPointers)
|
||||
if _, ok := fv.Interface().(Name); ok {
|
||||
fv.Set(reflect.ValueOf(start.Name))
|
||||
// Anonymous struct with no field or anonymous fields cannot get a value using the reflection
|
||||
// package and must be discarded.
|
||||
noValue := true
|
||||
if sv.Type().Name() == "" && sv.Type().Kind() == reflect.Struct {
|
||||
i := 0
|
||||
for i < sv.Type().NumField() && noValue {
|
||||
noValue = noValue && sv.Type().Field(i).Anonymous
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
noValue = false
|
||||
}
|
||||
if !noValue {
|
||||
fv := finfo.value(sv, initNilPointers)
|
||||
if _, ok := fv.Interface().(Name); ok {
|
||||
fv.Set(reflect.ValueOf(start.Name))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -538,14 +538,14 @@ var tableAttrs = []struct {
|
|||
tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
|
||||
},
|
||||
{
|
||||
// Default space does not apply to attribute names.
|
||||
// Default namespace does not apply to attribute names.
|
||||
xml: `<TableAttrs xmlns="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
|
||||
`h:table="hello" table="world" ` +
|
||||
`/></TableAttrs>`,
|
||||
tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}},
|
||||
},
|
||||
{
|
||||
// Default space does not apply to attribute names.
|
||||
// Default namespace does not apply to attribute names.
|
||||
xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr xmlns="http://www.w3.org/TR/html4/" ` +
|
||||
`table="hello" f:table="world" ` +
|
||||
`/></TableAttrs>`,
|
||||
|
|
@ -558,7 +558,7 @@ var tableAttrs = []struct {
|
|||
tab: TableAttrs{},
|
||||
},
|
||||
{
|
||||
// Default space does not apply to attribute names.
|
||||
// Default namespace does not apply to attribute names.
|
||||
xml: `<TableAttrs xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
|
||||
`h:table="hello" table="world" ` +
|
||||
`/></TableAttrs>`,
|
||||
|
|
@ -566,7 +566,7 @@ var tableAttrs = []struct {
|
|||
ns: "http://www.w3schools.com/furniture",
|
||||
},
|
||||
{
|
||||
// Default space does not apply to attribute names.
|
||||
// Default namespace does not apply to attribute names.
|
||||
xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr ` +
|
||||
`table="hello" f:table="world" ` +
|
||||
`/></TableAttrs>`,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ type typeInfo struct {
|
|||
// fieldInfo holds details for the xml representation of a single field.
|
||||
type fieldInfo struct {
|
||||
idx []int
|
||||
prefix string
|
||||
name string
|
||||
xmlns string
|
||||
flags fieldFlags
|
||||
|
|
@ -65,7 +66,7 @@ func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
|
|||
}
|
||||
|
||||
// For embedded structs, embed its fields.
|
||||
if f.Anonymous {
|
||||
if f.Anonymous { // i.e. reflect package will panic to get a Value
|
||||
t := f.Type
|
||||
if t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
|
|
@ -75,13 +76,14 @@ func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tinfo.xmlname == nil {
|
||||
if tinfo.xmlname == nil && inner.xmlname != nil && inner.xmlname.name != "" {
|
||||
// to leave xmlname to nil and to avoid assigning unexported xmlname field
|
||||
tinfo.xmlname = inner.xmlname
|
||||
}
|
||||
for _, finfo := range inner.fields {
|
||||
finfo.idx = append([]int{i}, finfo.idx...)
|
||||
if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
|
||||
return nil, err
|
||||
return nil, err // Any detected conflict returns an error
|
||||
}
|
||||
}
|
||||
continue
|
||||
|
|
@ -93,19 +95,16 @@ func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if f.Name == xmlName {
|
||||
if f.Name == xmlName { // copying the field for "easier" access when .FieldByName() is appropriate
|
||||
tinfo.xmlname = finfo
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the field if it doesn't conflict with other fields.
|
||||
if err := addFieldInfo(typ, tinfo, finfo); err != nil {
|
||||
} else if err := addFieldInfo(typ, tinfo, finfo); err != nil {
|
||||
// Add the field if it doesn't conflict with other fields.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ti, _ := tinfoMap.LoadOrStore(typ, tinfo)
|
||||
ti, _ := tinfoMap.LoadOrStore(typ, tinfo) // Returned bool is always false
|
||||
return ti.(*typeInfo), nil
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +121,7 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
|
|||
// Parse flags.
|
||||
tokens := strings.Split(tag, ",")
|
||||
if len(tokens) == 1 {
|
||||
finfo.flags = fElement
|
||||
finfo.flags = fElement // Nothing but the name of field
|
||||
} else {
|
||||
tag = tokens[0]
|
||||
for _, flag := range tokens[1:] {
|
||||
|
|
@ -179,7 +178,7 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
|
|||
// The XMLName field records the XML element name. Don't
|
||||
// process it as usual because its name should default to
|
||||
// empty rather than to the field name.
|
||||
finfo.name = tag
|
||||
finfo.prefix, finfo.name = splitPrefixed(tag)
|
||||
return finfo, nil
|
||||
}
|
||||
|
||||
|
|
@ -187,8 +186,9 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
|
|||
// If the name part of the tag is completely empty, get
|
||||
// default from XMLName of underlying struct if feasible,
|
||||
// or field name otherwise.
|
||||
// This is how an anonymous struct gets a value
|
||||
if xmlname := lookupXMLName(f.Type); xmlname != nil {
|
||||
finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
|
||||
finfo.xmlns, finfo.prefix, finfo.name = xmlname.xmlns, xmlname.prefix, xmlname.name
|
||||
} else {
|
||||
finfo.name = f.Name
|
||||
}
|
||||
|
|
@ -203,7 +203,11 @@ func structFieldInfo(typ reflect.Type, f *reflect.StructField) (*fieldInfo, erro
|
|||
if parents[len(parents)-1] == "" {
|
||||
return nil, fmt.Errorf("xml: trailing '>' in field %s of type %s", f.Name, typ)
|
||||
}
|
||||
finfo.name = parents[len(parents)-1]
|
||||
finfo.prefix, finfo.name = splitPrefixed(parents[len(parents)-1])
|
||||
// TODO(ydnar): should we allow prefixed parents, e.g.: space:a>space>b?
|
||||
for i := range parents {
|
||||
_, parents[i] = splitPrefixed(parents[i])
|
||||
}
|
||||
if len(parents) > 1 {
|
||||
if (finfo.flags & fElement) == 0 {
|
||||
return nil, fmt.Errorf("xml: %s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
|
||||
|
|
@ -240,7 +244,7 @@ func lookupXMLName(typ reflect.Type) (xmlname *fieldInfo) {
|
|||
if f.Name != xmlName {
|
||||
continue
|
||||
}
|
||||
finfo, err := structFieldInfo(typ, &f)
|
||||
finfo, err := structFieldInfo(typ, &f) // Recursive call
|
||||
if err == nil && finfo.name != "" {
|
||||
return finfo
|
||||
}
|
||||
|
|
@ -307,7 +311,7 @@ Loop:
|
|||
// Otherwise, if any of them is at the same depth level, it's an error.
|
||||
for _, i := range conflicts {
|
||||
oldf := &tinfo.fields[i]
|
||||
if len(oldf.idx) == len(newf.idx) {
|
||||
if len(oldf.idx) == len(newf.idx) { // Same depth
|
||||
f1 := typ.FieldByIndex(oldf.idx)
|
||||
f2 := typ.FieldByIndex(newf.idx)
|
||||
return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package xml implements a simple XML 1.0 parser that
|
||||
// understands XML name spaces.
|
||||
// understands XML namespaces.
|
||||
package xml
|
||||
|
||||
// References:
|
||||
// Annotated XML spec: https://www.xml.com/axml/testaxml.htm
|
||||
// XML name spaces: https://www.w3.org/TR/REC-xml-names/
|
||||
// XML namespaces: https://www.w3.org/TR/REC-xml-names/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
|
@ -33,10 +33,13 @@ func (e *SyntaxError) Error() string {
|
|||
}
|
||||
|
||||
// A Name represents an XML name (Local) annotated
|
||||
// with a name space identifier (Space).
|
||||
// with a namespace identifier (Space).
|
||||
// In tokens returned by [Decoder.Token], the Space identifier
|
||||
// is given as a canonical URL, not the short prefix used
|
||||
// in the document being parsed.
|
||||
// in the document being parsed. If Local is prefixed in
|
||||
// the form of "prefix:name", this package will attempt to
|
||||
// use the prefix instead of a fully-qualified namespace URL
|
||||
// when marshaling.
|
||||
type Name struct {
|
||||
Space, Local string
|
||||
}
|
||||
|
|
@ -164,9 +167,9 @@ type Decoder struct {
|
|||
//
|
||||
// creates a parser that can handle typical HTML.
|
||||
//
|
||||
// Strict mode does not enforce the requirements of the XML name spaces TR.
|
||||
// In particular it does not reject name space tags using undefined prefixes.
|
||||
// Such tags are recorded with the unknown prefix as the name space URL.
|
||||
// Strict mode does not enforce the requirements of the XML namespaces TR.
|
||||
// In particular it does not reject namespace tags using undefined prefixes.
|
||||
// Such tags are recorded with the unknown prefix as the namespace URL.
|
||||
Strict bool
|
||||
|
||||
// When Strict == false, AutoClose indicates a set of elements to
|
||||
|
|
@ -192,7 +195,7 @@ type Decoder struct {
|
|||
// CharsetReader's result values must be non-nil.
|
||||
CharsetReader func(charset string, input io.Reader) (io.Reader, error)
|
||||
|
||||
// DefaultSpace sets the default name space used for unadorned tags,
|
||||
// DefaultSpace sets the default namespace used for unadorned tags,
|
||||
// as if the entire XML stream were wrapped in an element containing
|
||||
// the attribute xmlns="DefaultSpace".
|
||||
DefaultSpace string
|
||||
|
|
@ -207,7 +210,7 @@ type Decoder struct {
|
|||
toClose Name
|
||||
nextToken Token
|
||||
nextByte int
|
||||
ns map[string]string
|
||||
ns map[string]string // url for a prefix ns[.Space]=.Value
|
||||
err error
|
||||
line int
|
||||
linestart int64
|
||||
|
|
@ -265,11 +268,11 @@ func NewTokenDecoder(t TokenReader) *Decoder {
|
|||
// If [Decoder.CharsetReader] is called and returns an error,
|
||||
// the error is wrapped and returned.
|
||||
//
|
||||
// Token implements XML name spaces as described by
|
||||
// Token implements XML namespaces as described by
|
||||
// https://www.w3.org/TR/REC-xml-names/. Each of the
|
||||
// [Name] structures contained in the Token has the Space
|
||||
// set to the URL identifying its name space when known.
|
||||
// If Token encounters an unrecognized name space prefix,
|
||||
// set to the URL identifying its namespace when known.
|
||||
// If Token encounters an unrecognized namespace prefix,
|
||||
// it uses the prefix as the Space rather than report an error.
|
||||
func (d *Decoder) Token() (Token, error) {
|
||||
var t Token
|
||||
|
|
@ -299,18 +302,27 @@ func (d *Decoder) Token() (Token, error) {
|
|||
}
|
||||
switch t1 := t.(type) {
|
||||
case StartElement:
|
||||
// In XML name spaces, the translations listed in the
|
||||
// In XML namespaces, the translations listed in the
|
||||
// attributes apply to the element name and
|
||||
// to the other attribute names, so process
|
||||
// the translations first.
|
||||
for _, a := range t1.Attr {
|
||||
if a.Name.Space == xmlnsPrefix {
|
||||
v, ok := d.ns[a.Name.Local]
|
||||
d.pushNs(a.Name.Local, v, ok)
|
||||
if a.Name.Space == xmlnsPrefix { // namespace attribute {.Space}xmlns:{.Local}={.Value}
|
||||
if a.Value == "" {
|
||||
d.err = d.syntaxError("empty namespace without prefix")
|
||||
return nil, d.err
|
||||
}
|
||||
if a.Name.Local == "" {
|
||||
d.err = d.syntaxError("empty prefix")
|
||||
return nil, d.err
|
||||
}
|
||||
v, ok := d.ns[a.Name.Local] // Checking existence
|
||||
// Recording the level of the namespace by recording tag name
|
||||
d.pushNs(a.Name.Local, v, ok) // Pushing tag, eventual value, and existence of namespace
|
||||
d.ns[a.Name.Local] = a.Value
|
||||
}
|
||||
if a.Name.Space == "" && a.Name.Local == xmlnsPrefix {
|
||||
// Default space for untagged names
|
||||
if a.Name.Space == "" && a.Name.Local == xmlnsPrefix { // xmlns=".Value"
|
||||
// Default namespace for non-prefixed names
|
||||
v, ok := d.ns[""]
|
||||
d.pushNs("", v, ok)
|
||||
d.ns[""] = a.Value
|
||||
|
|
@ -335,16 +347,18 @@ func (d *Decoder) Token() (Token, error) {
|
|||
|
||||
const (
|
||||
xmlURL = "http://www.w3.org/XML/1998/namespace"
|
||||
xmlnsPrefix = "xmlns"
|
||||
xmlPrefix = "xml"
|
||||
xmlnsURL = "http://www.w3.org/2000/xmlns/"
|
||||
xmlnsPrefix = "xmlns"
|
||||
)
|
||||
|
||||
// Apply name space translation to name n.
|
||||
// The default name space (for Space=="")
|
||||
// Apply namespace translation to name n.
|
||||
// The default namespace (for Space=="")
|
||||
// applies only to element names, not to attribute names.
|
||||
func (d *Decoder) translate(n *Name, isElementName bool) {
|
||||
switch {
|
||||
case n.Space == xmlnsPrefix:
|
||||
n.Space = xmlnsURL
|
||||
return
|
||||
case n.Space == "" && !isElementName:
|
||||
return
|
||||
|
|
@ -372,7 +386,7 @@ func (d *Decoder) switchToReader(r io.Reader) {
|
|||
}
|
||||
}
|
||||
|
||||
// Parsing state - stack holds old name space translations
|
||||
// Parsing state - stack holds old namespace translations
|
||||
// and the current set of open elements. The translations to pop when
|
||||
// ending a given tag are *below* it on the stack, which is
|
||||
// more work but forced on us by XML.
|
||||
|
|
@ -496,8 +510,8 @@ func (d *Decoder) popElement(t *EndElement) bool {
|
|||
if name.Space == "" {
|
||||
ns = `""`
|
||||
}
|
||||
d.err = d.syntaxError("element <" + s.name.Local + "> in space " + s.name.Space +
|
||||
" closed by </" + name.Local + "> in space " + ns)
|
||||
d.err = d.syntaxError("element <" + s.name.Local + "> in namespace " + s.name.Space +
|
||||
" closed by </" + name.Local + "> in namespace " + ns)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +554,7 @@ var errRawToken = errors.New("xml: cannot use RawToken from UnmarshalXML method"
|
|||
|
||||
// RawToken is like [Decoder.Token] but does not verify that
|
||||
// start and end elements match and does not translate
|
||||
// name space prefixes to their corresponding URLs.
|
||||
// namespace prefixes to their corresponding URLs.
|
||||
func (d *Decoder) RawToken() (Token, error) {
|
||||
if d.unmarshalDepth > 0 {
|
||||
return nil, errRawToken
|
||||
|
|
@ -797,6 +811,9 @@ func (d *Decoder) rawToken() (Token, error) {
|
|||
for {
|
||||
d.space()
|
||||
if b, ok = d.mustgetc(); !ok {
|
||||
if len(attr) > 0 && !d.Strict {
|
||||
break // When not strict, an attribute might end with EOF
|
||||
}
|
||||
return nil, d.err
|
||||
}
|
||||
if b == '/' {
|
||||
|
|
@ -831,7 +848,6 @@ func (d *Decoder) rawToken() (Token, error) {
|
|||
d.err = d.syntaxError("attribute name without = in element")
|
||||
return nil, d.err
|
||||
}
|
||||
d.ungetc(b)
|
||||
a.Value = a.Name.Local
|
||||
} else {
|
||||
d.space()
|
||||
|
|
@ -1024,6 +1040,11 @@ Input:
|
|||
d.ungetc('<')
|
||||
break Input
|
||||
}
|
||||
// This occurs only for an unquoted attr name.
|
||||
if b == '>' && !cdata && quote < 0 { // Possible end of tag reached
|
||||
d.ungetc('>') // Leaving end of tag available
|
||||
break // returning text
|
||||
}
|
||||
if quote >= 0 && b == byte(quote) {
|
||||
break Input
|
||||
}
|
||||
|
|
@ -1120,6 +1141,7 @@ Input:
|
|||
}
|
||||
|
||||
// We must rewrite unescaped \r and \r\n into \n.
|
||||
// End of line handling https://www.w3.org/TR/xml/#sec-line-ends
|
||||
if b == '\r' {
|
||||
d.buf.WriteByte('\n')
|
||||
} else if b1 == '\r' && b == '\n' {
|
||||
|
|
@ -1163,20 +1185,25 @@ func isInCharacterRange(r rune) (inrange bool) {
|
|||
r >= 0x10000 && r <= 0x10FFFF
|
||||
}
|
||||
|
||||
// Get name space name: name with a : stuck in the middle.
|
||||
// The part before the : is the name space identifier.
|
||||
// Get namespace name: name with a : stuck in the middle.
|
||||
// The part before the : is the namespace identifier.
|
||||
func (d *Decoder) nsname() (name Name, ok bool) {
|
||||
s, ok := d.name()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if strings.Count(s, ":") > 1 {
|
||||
return name, false
|
||||
} else if space, local, ok := strings.Cut(s, ":"); !ok || space == "" || local == "" {
|
||||
n := strings.Count(s, ":")
|
||||
if n == 0 { // No colons, no namespace. OK.
|
||||
name.Local = s
|
||||
} else if n > 1 { // More than one colon, not OK.
|
||||
name.Local = s
|
||||
return name, false
|
||||
} else if i := strings.Index(s, ":"); i < 1 || i > len(s)-2 { // Leading or trailing colon, not OK.
|
||||
name.Local = s
|
||||
return name, false
|
||||
} else {
|
||||
name.Space = space
|
||||
name.Local = local
|
||||
name.Space = s[0:i]
|
||||
name.Local = s[i+1:]
|
||||
}
|
||||
return name, true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ var cookedTokens = []Token{
|
|||
Directive(`DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"`),
|
||||
CharData("\n"),
|
||||
StartElement{Name{"ns2", "body"}, []Attr{{Name{"xmlns", "foo"}, "ns1"}, {Name{"", "xmlns"}, "ns2"}, {Name{"xmlns", "tag"}, "ns3"}}},
|
||||
StartElement{Name{"ns2", "body"}, []Attr{{Name{"http://www.w3.org/2000/xmlns/", "foo"}, "ns1"}, {Name{"", "xmlns"}, "ns2"}, {Name{"http://www.w3.org/2000/xmlns/", "tag"}, "ns3"}}},
|
||||
CharData("\n "),
|
||||
StartElement{Name{"ns2", "hello"}, []Attr{{Name{"", "lang"}, "en"}}},
|
||||
CharData("World <>'\" 白鵬翔"),
|
||||
|
|
@ -195,7 +195,7 @@ var cookedTokens = []Token{
|
|||
StartElement{Name{"ns2", "goodbye"}, []Attr{}},
|
||||
EndElement{Name{"ns2", "goodbye"}},
|
||||
CharData("\n "),
|
||||
StartElement{Name{"ns2", "outer"}, []Attr{{Name{"ns1", "attr"}, "value"}, {Name{"xmlns", "tag"}, "ns4"}}},
|
||||
StartElement{Name{"ns2", "outer"}, []Attr{{Name{"ns1", "attr"}, "value"}, {Name{"http://www.w3.org/2000/xmlns/", "tag"}, "ns4"}}},
|
||||
CharData("\n "),
|
||||
StartElement{Name{"ns2", "inner"}, []Attr{}},
|
||||
EndElement{Name{"ns2", "inner"}},
|
||||
|
|
@ -952,7 +952,7 @@ func TestIssue8535(t *testing.T) {
|
|||
type ExampleConflict struct {
|
||||
XMLName Name `xml:"example"`
|
||||
Link string `xml:"link"`
|
||||
AtomLink string `xml:"http://www.w3.org/2005/Atom link"` // Same name in a different name space
|
||||
AtomLink string `xml:"http://www.w3.org/2005/Atom link"` // Same name in a different namespace
|
||||
}
|
||||
testCase := `<example>
|
||||
<title>Example</title>
|
||||
|
|
@ -1037,6 +1037,316 @@ func encodeXMLNS4() ([]byte, error) {
|
|||
return Marshal(s)
|
||||
}
|
||||
|
||||
func TestIssue11431(t *testing.T) {
|
||||
type Test struct {
|
||||
XMLName Name `xml:"Test"`
|
||||
Ns string `xml:"xmlns,attr"`
|
||||
Body string
|
||||
}
|
||||
|
||||
s := &Test{Ns: "http://example.com/ns", Body: "hello world"}
|
||||
b, err := Marshal(s)
|
||||
if err != nil {
|
||||
t.Errorf("namespace handling: expected no error, got %s", err)
|
||||
}
|
||||
|
||||
want := `<Test xmlns="http://example.com/ns"><Body>hello world</Body></Test>`
|
||||
if string(b) != want {
|
||||
t.Errorf("namespace handling: got %s, want %s \n", string(b), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue11431NsWoAttr(t *testing.T) {
|
||||
type Test struct {
|
||||
Body string `xml:"http://example.com/ns body"`
|
||||
}
|
||||
|
||||
s := &Test{Body: "hello world"}
|
||||
b, err := Marshal(s)
|
||||
if err != nil {
|
||||
t.Errorf("namespace handling: expected no error, got %s", err)
|
||||
}
|
||||
|
||||
want := `<Test><body xmlns="http://example.com/ns">hello world</body></Test>`
|
||||
if string(b) != want {
|
||||
t.Errorf("namespace handling: got %s, want %s \n", string(b), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue11431XMLName(t *testing.T) {
|
||||
type Test struct {
|
||||
XMLName Name `xml:"http://example.com/ns Test"`
|
||||
Body string
|
||||
}
|
||||
|
||||
//s := &Test{XMLName: Name{"http://example.com/ns",""}, Body: "hello world"} is unusable as the "-" is missing
|
||||
// as documentation states
|
||||
s := &Test{Body: "hello world"}
|
||||
b, err := Marshal(s)
|
||||
if err != nil {
|
||||
t.Errorf("namespace handling: expected no error, got %s", err)
|
||||
}
|
||||
|
||||
want := `<Test xmlns="http://example.com/ns"><Body>hello world</Body></Test>`
|
||||
if string(b) != want {
|
||||
t.Errorf("namespace handling: got %s, want %s \n", string(b), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue11431UsingAttr(t *testing.T) {
|
||||
type T struct {
|
||||
Ns string `xml:"xmlns,attr"`
|
||||
Body string
|
||||
}
|
||||
|
||||
//s := &Test{XMLName: Name{"http://example.com/ns",""}, Body: "hello world"} is unusable as the "-" is missing
|
||||
// as documentation states
|
||||
s := &T{Ns: "http://example.com/ns", Body: "hello world"}
|
||||
b, err := Marshal(s)
|
||||
if err != nil {
|
||||
t.Errorf("namespace handling: expected no error, got %s", err)
|
||||
}
|
||||
|
||||
want := `<T xmlns="http://example.com/ns"><Body>hello world</Body></T>`
|
||||
if string(b) != want {
|
||||
t.Errorf("namespace handling: got %s, want %s \n", string(b), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue11496(t *testing.T) { // Issue answered
|
||||
type Person struct {
|
||||
XMLName Name `xml:"ns1 person"`
|
||||
Name string `xml:"name"`
|
||||
Phone string `xml:"ns2 phone,omitempty"`
|
||||
}
|
||||
|
||||
p := &Person{
|
||||
Name: "Oliver",
|
||||
Phone: "110",
|
||||
}
|
||||
|
||||
got, err := Marshal(p)
|
||||
if err != nil {
|
||||
t.Errorf("namespace assignment: marshal error returned is %s", err)
|
||||
}
|
||||
|
||||
want := `<person xmlns="ns1"><name>Oliver</name><phone xmlns="ns2">110</phone></person>`
|
||||
if string(got) != want {
|
||||
t.Errorf("namespace assignment:\ngot: %s\nwant: %s", string(got), want)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// <person xmlns="ns1">
|
||||
// <name>Oliver</name>
|
||||
// <phone xmlns="ns2">110</phone>
|
||||
// </person>
|
||||
//
|
||||
// Want:
|
||||
// <person xmlns="ns1" xmlns:ns2="ns2">
|
||||
// <name>Oliver</name>
|
||||
// <ns2:phone>110</ns2:phone>
|
||||
// </person>
|
||||
|
||||
}
|
||||
|
||||
func TestIssue8068(t *testing.T) {
|
||||
testCases := []struct {
|
||||
s string
|
||||
ok bool
|
||||
}{ // Empty prefixed namespace is not allowed
|
||||
{`<foo xmlns:bar="a"></foo>`, true},
|
||||
{`<foo xmlns:bar=""></foo>`, false},
|
||||
{`<foo xmlns:="a"></foo>`, false},
|
||||
{`<foo xmlns:""></foo>`, false},
|
||||
{`<foo xmlns:"a"></foo>`, false},
|
||||
}
|
||||
|
||||
var dest string // type does not matter as tested tags are empty
|
||||
var err error
|
||||
for _, tc := range testCases {
|
||||
err = Unmarshal([]byte(tc.s), &dest)
|
||||
|
||||
if err != nil && tc.ok {
|
||||
t.Errorf("%s: Empty prefixed namespace : expected no error, got %s", tc.s, err)
|
||||
continue
|
||||
}
|
||||
if err == nil && !tc.ok {
|
||||
t.Errorf("%s: Empty prefixed namespace : expected error, got nil", tc.s)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIssue10538(t *testing.T) {
|
||||
// There is no restriction of the placement of XMLName in embedded structs
|
||||
// If the field is unexported, reflect package will panic in the documented cases
|
||||
// Purpose of the test is to show that no panic occurs with multiple set ups of embedded structs using XMLName
|
||||
type elementNoXMLName struct {
|
||||
Children []interface{}
|
||||
}
|
||||
|
||||
type element struct {
|
||||
XMLName Name
|
||||
Children []interface{}
|
||||
}
|
||||
|
||||
type Element struct {
|
||||
XMLName Name
|
||||
Children []interface{}
|
||||
}
|
||||
|
||||
type svgstrEmptyStruct struct {
|
||||
elementNoXMLName //is not exported and empty
|
||||
Height string `xml:"height,attr,omitempty"`
|
||||
Width string `xml:"width,attr,omitempty"`
|
||||
}
|
||||
|
||||
type svgstr struct {
|
||||
element // not exported and .Value panics
|
||||
Height string `xml:"height,attr,omitempty"`
|
||||
Width string `xml:"width,attr,omitempty"`
|
||||
}
|
||||
|
||||
type svgstrExp struct {
|
||||
Element element // exported and .Value does not panic
|
||||
Height string `xml:"height,attr,omitempty"`
|
||||
Width string `xml:"width,attr,omitempty"`
|
||||
}
|
||||
|
||||
type svgstrExpType struct {
|
||||
Element // exported and .Value does not panic
|
||||
Height string `xml:"height,attr,omitempty"`
|
||||
Width string `xml:"width,attr,omitempty"`
|
||||
}
|
||||
|
||||
type svgstr2 struct {
|
||||
XMLName Name
|
||||
Children []interface{}
|
||||
Height string `xml:"height,attr,omitempty"`
|
||||
Width string `xml:"width,attr,omitempty"`
|
||||
}
|
||||
|
||||
/* No embedded XMLName */
|
||||
result := `<svgstrEmptyStruct height="200" width="400"></svgstrEmptyStruct>`
|
||||
sE := svgstrEmptyStruct{
|
||||
Width: "400",
|
||||
Height: "200",
|
||||
}
|
||||
a, err := Marshal(sE)
|
||||
if err != nil {
|
||||
t.Errorf("xmlname handling : marshaling failed with %s \n", err)
|
||||
}
|
||||
if string(a) != result {
|
||||
t.Errorf("xmlname handling : got %s, want %s \n", string(a), result)
|
||||
}
|
||||
/* XMLName in a unexported field is not assigned */
|
||||
result = `<svgstr height="200" width="400"></svgstr>`
|
||||
s := svgstr{
|
||||
element: element{XMLName: Name{Local: "svg", Space: "www.etc"}, Children: nil},
|
||||
Width: "400",
|
||||
Height: "200",
|
||||
}
|
||||
|
||||
f, err := Marshal(s)
|
||||
if err != nil {
|
||||
t.Errorf("xmlname handling : marshaling failed with %s \n", err)
|
||||
}
|
||||
if string(f) != result {
|
||||
t.Errorf("xmlname handling : got %s, want %s \n", string(f), result)
|
||||
}
|
||||
/* Embedding the XMLName gets it assigned to the inner struct */
|
||||
result = `<svgstrExp height="200" width="400"><svg xmlns="www.etc"></svg></svgstrExp>`
|
||||
sExp := svgstrExp{
|
||||
Element: element{XMLName: Name{Local: "svg", Space: "www.etc"}, Children: nil},
|
||||
Width: "400",
|
||||
Height: "200",
|
||||
}
|
||||
|
||||
b, err := Marshal(sExp)
|
||||
if err != nil {
|
||||
t.Errorf("xmlname handling : marshaling failed with %s \n", err)
|
||||
}
|
||||
if string(b) != result {
|
||||
t.Errorf("xmlname handling : got %s, want %s \n", string(b), result)
|
||||
}
|
||||
/* XMLName is not assigned to outer tag but to inner tag. Not working due to other issues */
|
||||
result = `<svgstrExpType height="200" width="400"><Children></Children></svgstrExpType>`
|
||||
sExpType := svgstrExpType{
|
||||
Element: Element{XMLName: Name{Local: "svg", Space: "www.etc"}, Children: []interface{}{""}},
|
||||
Width: "400",
|
||||
Height: "200",
|
||||
}
|
||||
|
||||
d, err := Marshal(sExpType)
|
||||
if err != nil {
|
||||
t.Errorf("xmlname handling : marshaling failed with %s \n", err)
|
||||
}
|
||||
if string(d) != result {
|
||||
t.Errorf("xmlname handling : got %s, want %s \n", string(d), result)
|
||||
}
|
||||
/* No inner struct. XMLName is assigned as usual */
|
||||
result = `<svg xmlns="www.etc" height="200" width="400"></svg>`
|
||||
s2 := svgstr2{
|
||||
XMLName: Name{Local: "svg", Space: "www.etc"},
|
||||
Width: "400",
|
||||
Height: "200",
|
||||
}
|
||||
|
||||
c, err := Marshal(s2)
|
||||
if err != nil {
|
||||
t.Errorf("xmlname handling : marshaling failed with %s \n", err)
|
||||
}
|
||||
if string(c) != result {
|
||||
t.Errorf("xmlname handling : got %s, want %s \n", string(c), result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue7535(t *testing.T) {
|
||||
source := `<ex:element xmlns:ex="http://example.com/schema"></ex:element>`
|
||||
result := `<ex:element xmlns:ex="http://example.com/schema"></ex:element>`
|
||||
// A prefix is the namespace known from the tag where it is declared and not the default namespace.
|
||||
// But in a well-formed xml, it is useless as the prefix is bound and recorded as an attribute
|
||||
in := strings.NewReader(source)
|
||||
var errl, err error
|
||||
var token Token
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
out := &bytes.Buffer{}
|
||||
d := NewDecoder(in)
|
||||
e := NewEncoder(out)
|
||||
errl = nil
|
||||
for errl == nil {
|
||||
token, err = d.Token()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
errl = err
|
||||
} else {
|
||||
t.Errorf("read token failed:%s", err)
|
||||
return
|
||||
}
|
||||
} else { // err is nil
|
||||
// end token contains now the URL which can be encoded only if the NS if available
|
||||
// from the start token
|
||||
err = e.EncodeToken(token)
|
||||
if err != nil {
|
||||
t.Errorf("encode token failed : %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
e.Flush()
|
||||
if out.String() != result {
|
||||
t.Errorf("duplicating namespace : got %s, want %s \n", out.String(), result)
|
||||
return
|
||||
}
|
||||
in.Reset(out.String())
|
||||
}
|
||||
|
||||
if errl != nil && errl != io.EOF {
|
||||
t.Errorf("%s \n: duplicating namespace : got error %v, want no fail \n", source, errl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue11405(t *testing.T) {
|
||||
testCases := []string{
|
||||
"<root>",
|
||||
|
|
@ -1138,7 +1448,7 @@ func TestIssue7113(t *testing.T) {
|
|||
t.Fatalf("second Unmarshal failed: %s", err)
|
||||
}
|
||||
if c.XMLName.Space != "b" {
|
||||
t.Errorf("overidding with empty namespace: after marshaling & unmarshaling, XML name space: got %s, want %s\n", a.XMLName.Space, structSpace)
|
||||
t.Errorf("overidding with empty namespace: after marshaling & unmarshaling, XML namespace: got %s, want %s\n", a.XMLName.Space, structSpace)
|
||||
}
|
||||
if len(c.C.XMLName.Space) != 0 {
|
||||
t.Errorf("overidding with empty namespace: after marshaling & unmarshaling, got %s, want empty\n", a.C.XMLName.Space)
|
||||
|
|
@ -1214,6 +1524,246 @@ func TestIssue20685(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIssue16497(t *testing.T) {
|
||||
|
||||
type IQ struct {
|
||||
Type string `xml:"type,attr"`
|
||||
XMLName Name `xml:"iq"`
|
||||
}
|
||||
|
||||
type embedIQ struct {
|
||||
IQ IQ
|
||||
}
|
||||
|
||||
/* Anonymous struct */
|
||||
resp := struct {
|
||||
IQ
|
||||
}{} /* */
|
||||
|
||||
var err error
|
||||
err = Unmarshal([]byte(`<iq/>`), &resp)
|
||||
if err != nil {
|
||||
t.Errorf("unmarshal anonymous struct failed with %s", err)
|
||||
return
|
||||
}
|
||||
// assigning values or not does not change anything
|
||||
var respEmbed embedIQ
|
||||
err = Unmarshal([]byte(`<iq/>`), &respEmbed)
|
||||
if err != nil {
|
||||
t.Errorf("unmarshal anonymous struct failed with %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue9519(t *testing.T) {
|
||||
// Expects prefixed notation prefix:tag name iso xmlns:
|
||||
type HouseType struct {
|
||||
XMLName Name `xml:"prefix11 House"`
|
||||
MessageId string `xml:"message_id,attr"`
|
||||
}
|
||||
|
||||
var tm HouseType
|
||||
var err error
|
||||
|
||||
tm.MessageId = "test1234"
|
||||
|
||||
var data1 []byte
|
||||
data1, err = Marshal(tm)
|
||||
if err != nil {
|
||||
t.Errorf("%s : handling namespace : got error %v, want no fail \n", data1, err)
|
||||
}
|
||||
|
||||
result := `<House xmlns="prefix11" message_id="` + tm.MessageId + `"></House>`
|
||||
if string(data1) != result {
|
||||
t.Errorf("handling namespace : got %v, want %s \n", string(data1), result)
|
||||
}
|
||||
|
||||
var tm2 HouseType
|
||||
err = Unmarshal([]byte(data1), &tm2)
|
||||
if err != nil {
|
||||
t.Errorf("%s : handling namespace : got error %v, want no fail \n", data1, err)
|
||||
}
|
||||
|
||||
if tm.MessageId != tm2.MessageId {
|
||||
t.Errorf("handling namespace : got %s, want %s \n", tm.MessageId, tm2.MessageId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalXMLName(t *testing.T) {
|
||||
|
||||
type InnerStruct struct {
|
||||
XMLName Name `xml:"testns outer"`
|
||||
}
|
||||
|
||||
type OuterStruct struct {
|
||||
InnerStruct
|
||||
IntAttr int `xml:"int,attr"`
|
||||
}
|
||||
|
||||
type OuterNamedStruct struct {
|
||||
InnerStruct
|
||||
IntAttr int `xml:"int,attr"`
|
||||
XMLName Name `xml:"outerns test"`
|
||||
}
|
||||
|
||||
type OuterNamedOrderedStruct struct {
|
||||
XMLName Name `xml:"outerns test"`
|
||||
InnerStruct
|
||||
IntAttr int `xml:"int,attr"`
|
||||
}
|
||||
|
||||
var unMarshalTestsXMLName = []struct {
|
||||
Value interface{}
|
||||
ExpectXML string
|
||||
MarshalOnly bool
|
||||
MarshalError string
|
||||
UnmarshalOnly bool
|
||||
UnmarshalError string
|
||||
}{
|
||||
{
|
||||
ExpectXML: `<outer xmlns="testns" int="10"></outer>`,
|
||||
Value: &OuterStruct{IntAttr: 10},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<outer xmlns="testns" int="10"></outer>`,
|
||||
Value: &OuterStruct{IntAttr: 10},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<test xmlns="outerns" int="10"></test>`,
|
||||
Value: &OuterNamedStruct{XMLName: Name{Space: "outerns", Local: "test"}, IntAttr: 10},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<test xmlns="outerns" int="10"></test>`,
|
||||
Value: &OuterNamedOrderedStruct{XMLName: Name{Space: "outerns", Local: "test"}, IntAttr: 10},
|
||||
},
|
||||
}
|
||||
for i, test := range unMarshalTestsXMLName {
|
||||
if test.MarshalOnly {
|
||||
continue
|
||||
}
|
||||
if _, ok := test.Value.(*Plain); ok {
|
||||
continue
|
||||
}
|
||||
if test.ExpectXML == `<top>`+
|
||||
`<x><b xmlns="space">b</b>`+
|
||||
`<b xmlns="space1">b1</b></x>`+
|
||||
`</top>` {
|
||||
// TODO(rogpeppe): re-enable this test in
|
||||
// https://go-review.googlesource.com/#/c/5910/
|
||||
continue
|
||||
}
|
||||
|
||||
vt := reflect.TypeOf(test.Value)
|
||||
dest := reflect.New(vt.Elem()).Interface()
|
||||
err := Unmarshal([]byte(test.ExpectXML), dest)
|
||||
|
||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
switch fix := dest.(type) {
|
||||
case *Feed:
|
||||
fix.Author.InnerXML = ""
|
||||
for i := range fix.Entry {
|
||||
fix.Entry[i].Author.InnerXML = ""
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if test.UnmarshalError == "" {
|
||||
t.Errorf("unmarshal(%#v): %s", test.ExpectXML, err)
|
||||
return
|
||||
}
|
||||
if !strings.Contains(err.Error(), test.UnmarshalError) {
|
||||
t.Errorf("unmarshal(%#v): %s, want %q", test.ExpectXML, err, test.UnmarshalError)
|
||||
}
|
||||
return
|
||||
}
|
||||
if got, want := dest, test.Value; !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("unmarshal(%q):\nhave %#v\nwant %#v", test.ExpectXML, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalXMLName(t *testing.T) {
|
||||
|
||||
type InnerStruct struct {
|
||||
XMLName Name `xml:"testns outer"`
|
||||
}
|
||||
|
||||
type OuterStruct struct {
|
||||
InnerStruct
|
||||
IntAttr int `xml:"int,attr"`
|
||||
}
|
||||
|
||||
type OuterNamedStruct struct {
|
||||
InnerStruct
|
||||
IntAttr int `xml:"int,attr"`
|
||||
XMLName Name `xml:"outerns test"`
|
||||
}
|
||||
|
||||
type OuterNamedOrderedStruct struct {
|
||||
XMLName Name `xml:"outerns test"`
|
||||
InnerStruct
|
||||
IntAttr int `xml:"int,attr"`
|
||||
}
|
||||
|
||||
var marshalTestsXMLName = []struct {
|
||||
Value interface{}
|
||||
ExpectXML string
|
||||
MarshalOnly bool
|
||||
MarshalError string
|
||||
UnmarshalOnly bool
|
||||
UnmarshalError string
|
||||
}{
|
||||
{
|
||||
ExpectXML: `<outer xmlns="testns" int="10"></outer>`,
|
||||
Value: &OuterStruct{IntAttr: 10},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<outer xmlns="testns" int="10"></outer>`,
|
||||
Value: &OuterStruct{IntAttr: 10},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<test xmlns="outerns" int="10"></test>`,
|
||||
Value: &OuterNamedStruct{XMLName: Name{Space: "outerns", Local: "test"}, IntAttr: 10},
|
||||
},
|
||||
{
|
||||
ExpectXML: `<test xmlns="outerns" int="10"></test>`,
|
||||
Value: &OuterNamedOrderedStruct{XMLName: Name{Space: "outerns", Local: "test"}, IntAttr: 10},
|
||||
},
|
||||
}
|
||||
|
||||
for idx, test := range marshalTestsXMLName {
|
||||
if test.UnmarshalOnly {
|
||||
continue
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
|
||||
data, err := Marshal(test.Value)
|
||||
if err != nil {
|
||||
if test.MarshalError == "" {
|
||||
t.Errorf("marshal(%#v): %s", test.Value, err)
|
||||
return
|
||||
}
|
||||
if !strings.Contains(err.Error(), test.MarshalError) {
|
||||
t.Errorf("marshal(%#v): %s, want %q", test.Value, err, test.MarshalError)
|
||||
}
|
||||
return
|
||||
}
|
||||
if test.MarshalError != "" {
|
||||
t.Errorf("Marshal succeeded, want error %q", test.MarshalError)
|
||||
return
|
||||
}
|
||||
if got, want := string(data), test.ExpectXML; got != want {
|
||||
if strings.Contains(want, "\n") {
|
||||
t.Errorf("marshal(%#v):\nHAVE:\n%s\nWANT:\n%s", test.Value, got, want)
|
||||
} else {
|
||||
t.Errorf("marshal(%#v):\nhave %#q\nwant %#q", test.Value, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func tokenMap(mapping func(t Token) Token) func(TokenReader) TokenReader {
|
||||
return func(src TokenReader) TokenReader {
|
||||
return mapper{
|
||||
|
|
@ -1348,7 +1898,11 @@ func testRoundTrip(t *testing.T, input string) {
|
|||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"trailing colon": `<foo abc:="x"></foo>`,
|
||||
// Disabling these tests because the parser now treats malformed namespaces as an error.
|
||||
// See https://github.com/golang/go/issues/43168.
|
||||
// "leading colon": `<::Test ::foo="bar"><:::Hello></:::Hello><Hello></Hello></::Test>`,
|
||||
// "trailing colon": `<foo abc:="x"></foo>`,
|
||||
// "double colon": `<x:y:foo></x:y:foo>`,
|
||||
"comments in directives": `<!ENTITY x<!<!-- c1 [ " -->--x --> > <e></e> <!DOCTYPE xxx [ x<!-- c2 " -->--x ]>`,
|
||||
}
|
||||
for name, input := range tests {
|
||||
|
|
@ -1365,13 +1919,13 @@ func TestParseErrors(t *testing.T) {
|
|||
err string
|
||||
}{
|
||||
{withDefaultHeader(`</foo>`), `unexpected end element </foo>`},
|
||||
{withDefaultHeader(`<x:foo></y:foo>`), `element <foo> in space x closed by </foo> in space y`},
|
||||
{withDefaultHeader(`<x:foo></y:foo>`), `element <foo> in namespace x closed by </foo> in namespace y`},
|
||||
{withDefaultHeader(`<? not ok ?>`), `expected target name after <?`},
|
||||
{withDefaultHeader(`<!- not ok -->`), `invalid sequence <!- not part of <!--`},
|
||||
{withDefaultHeader(`<!-? not ok -->`), `invalid sequence <!- not part of <!--`},
|
||||
{withDefaultHeader(`<![not ok]>`), `invalid <![ sequence`},
|
||||
{withDefaultHeader(`<zzz:foo xmlns:zzz="http://example.com"><bar>baz</bar></foo>`),
|
||||
`element <foo> in space zzz closed by </foo> in space ""`},
|
||||
`element <foo> in namespace zzz closed by </foo> in namespace ""`},
|
||||
{withDefaultHeader("\xf1"), `invalid UTF-8`},
|
||||
|
||||
// Header-related errors.
|
||||
|
|
|
|||
Loading…
Reference in New Issue