This commit is contained in:
Randy Reddig 2025-06-20 15:36:51 -04:00 committed by GitHub
commit 1aa6c7d7e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1182 additions and 190 deletions

View File

@ -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
}

View File

@ -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="&lt;unix&gt;"></port>`},
// Marshal is not symmetric to Unmarshal for these oddities because &apos is written as &#39
{Value: &Port{Type: "<un'ix>"}, ExpectXML: `<port type="&lt;un&apos;ix&gt;"></port>`, UnmarshalOnly: true},
{Value: &Port{Type: "<un\"ix>"}, ExpectXML: `<port type="&lt;un&quot;ix&gt;"></port>`, UnmarshalOnly: true},
{Value: &Port{Type: "<un&ix>"}, ExpectXML: `<port type="&lt;un&amp;ix&gt;"></port>`},
{Value: &Port{Type: "<unix>"}, ExpectXML: `<port type="&lt;unix&gt;"></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&amp;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">Youre welcome!</nested:value></wrapper></nested>`,
Value: &NamespacedNested{Value: "Youre 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
}
}

View File

@ -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))
}
}
}

View File

@ -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>`,

View File

@ -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")}

View File

@ -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
}

View File

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