Commit 3be158d6 authored by Roger Peppe's avatar Roger Peppe Committed by roger peppe

encoding/xml: encoding name spaces correctly

The current XML printer does not understand the xmlns
attribute. This change changes it so that it interprets the
xmlns attributes in the tokens being printed, and uses
appropriate prefixes.

Fixes #7535.

Change-Id: I20fae291d20602d37deb41ed42fab4c9a50ec85d
Reviewed-on: https://go-review.googlesource.com/2660Reviewed-by: default avatarNigel Tao <nigeltao@golang.org>
parent f59f9b85
This diff is collapsed.
...@@ -1203,7 +1203,7 @@ var encodeTokenTests = []struct { ...@@ -1203,7 +1203,7 @@ var encodeTokenTests = []struct {
toks: []Token{ toks: []Token{
StartElement{Name{"space", "local"}, nil}, StartElement{Name{"space", "local"}, nil},
}, },
want: `<local xmlns="space">`, want: `<space:local xmlns:space="space">`,
}, { }, {
desc: "start element with no name", desc: "start element with no name",
toks: []Token{ toks: []Token{
...@@ -1291,7 +1291,7 @@ var encodeTokenTests = []struct { ...@@ -1291,7 +1291,7 @@ var encodeTokenTests = []struct {
EndElement{Name{"another", "foo"}}, EndElement{Name{"another", "foo"}},
}, },
err: "xml: end tag </foo> in namespace another does not match start tag <foo> in namespace space", err: "xml: end tag </foo> in namespace another does not match start tag <foo> in namespace space",
want: `<foo xmlns="space">`, want: `<space:foo xmlns:space="space">`,
}, { }, {
desc: "start element with explicit namespace", desc: "start element with explicit namespace",
toks: []Token{ toks: []Token{
...@@ -1300,7 +1300,7 @@ var encodeTokenTests = []struct { ...@@ -1300,7 +1300,7 @@ var encodeTokenTests = []struct {
{Name{"space", "foo"}, "value"}, {Name{"space", "foo"}, "value"},
}}, }},
}, },
want: `<local xmlns="space" xmlns:_xmlns="xmlns" _xmlns:x="space" xmlns:space="space" space:foo="value">`, want: `<x:local xmlns:x="space" x:foo="value">`,
}, { }, {
desc: "start element with explicit namespace and colliding prefix", desc: "start element with explicit namespace and colliding prefix",
toks: []Token{ toks: []Token{
...@@ -1310,7 +1310,7 @@ var encodeTokenTests = []struct { ...@@ -1310,7 +1310,7 @@ var encodeTokenTests = []struct {
{Name{"x", "bar"}, "other"}, {Name{"x", "bar"}, "other"},
}}, }},
}, },
want: `<local xmlns="space" xmlns:_xmlns="xmlns" _xmlns:x="space" xmlns:space="space" space:foo="value" xmlns:x="x" x:bar="other">`, want: `<x:local xmlns:x_1="x" xmlns:x="space" x:foo="value" x_1:bar="other">`,
}, { }, {
desc: "start element using previously defined namespace", desc: "start element using previously defined namespace",
toks: []Token{ toks: []Token{
...@@ -1321,7 +1321,7 @@ var encodeTokenTests = []struct { ...@@ -1321,7 +1321,7 @@ var encodeTokenTests = []struct {
{Name{"space", "x"}, "y"}, {Name{"space", "x"}, "y"},
}}, }},
}, },
want: `<local xmlns:_xmlns="xmlns" _xmlns:x="space"><foo xmlns="space" xmlns:space="space" space:x="y">`, want: `<local xmlns:x="space"><x:foo x:x="y">`,
}, { }, {
desc: "nested name space with same prefix", desc: "nested name space with same prefix",
toks: []Token{ toks: []Token{
...@@ -1342,7 +1342,7 @@ var encodeTokenTests = []struct { ...@@ -1342,7 +1342,7 @@ var encodeTokenTests = []struct {
{Name{"space2", "b"}, "space2 value"}, {Name{"space2", "b"}, "space2 value"},
}}, }},
}, },
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">`, want: `<foo xmlns:x="space1"><foo xmlns:x="space2"><foo xmlns:space1="space1" space1:a="space1 value" x:b="space2 value"></foo></foo><foo xmlns:space2="space2" x:a="space1 value" space2:b="space2 value">`,
}, { }, {
desc: "start element defining several prefixes for the same name space", desc: "start element defining several prefixes for the same name space",
toks: []Token{ toks: []Token{
...@@ -1352,7 +1352,7 @@ var encodeTokenTests = []struct { ...@@ -1352,7 +1352,7 @@ var encodeTokenTests = []struct {
{Name{"space", "x"}, "value"}, {Name{"space", "x"}, "value"},
}}, }},
}, },
want: `<foo xmlns="space" xmlns:_xmlns="xmlns" _xmlns:a="space" _xmlns:b="space" xmlns:space="space" space:x="value">`, want: `<a:foo xmlns:a="space" a:x="value">`,
}, { }, {
desc: "nested element redefines name space", desc: "nested element redefines name space",
toks: []Token{ toks: []Token{
...@@ -1364,7 +1364,7 @@ var encodeTokenTests = []struct { ...@@ -1364,7 +1364,7 @@ var encodeTokenTests = []struct {
{Name{"space", "a"}, "value"}, {Name{"space", "a"}, "value"},
}}, }},
}, },
want: `<foo xmlns:_xmlns="xmlns" _xmlns:x="space"><foo xmlns="space" _xmlns:y="space" xmlns:space="space" space:a="value">`, want: `<foo xmlns:x="space"><x:foo x:a="value">`,
}, { }, {
desc: "nested element creates alias for default name space", desc: "nested element creates alias for default name space",
toks: []Token{ toks: []Token{
...@@ -1376,7 +1376,7 @@ var encodeTokenTests = []struct { ...@@ -1376,7 +1376,7 @@ var encodeTokenTests = []struct {
{Name{"space", "a"}, "value"}, {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:y="space" y:a="value">`,
}, { }, {
desc: "nested element defines default name space with existing prefix", desc: "nested element defines default name space with existing prefix",
toks: []Token{ toks: []Token{
...@@ -1388,7 +1388,7 @@ var encodeTokenTests = []struct { ...@@ -1388,7 +1388,7 @@ var encodeTokenTests = []struct {
{Name{"space", "a"}, "value"}, {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:x="space"><foo xmlns="space" x:a="value">`,
}, { }, {
desc: "nested element uses empty attribute name space when default ns defined", desc: "nested element uses empty attribute name space when default ns defined",
toks: []Token{ toks: []Token{
...@@ -1399,7 +1399,7 @@ var encodeTokenTests = []struct { ...@@ -1399,7 +1399,7 @@ var encodeTokenTests = []struct {
{Name{"", "attr"}, "value"}, {Name{"", "attr"}, "value"},
}}, }},
}, },
want: `<foo xmlns="space" xmlns="space"><foo xmlns="space" attr="value">`, want: `<foo xmlns="space"><foo attr="value">`,
}, { }, {
desc: "redefine xmlns", desc: "redefine xmlns",
toks: []Token{ toks: []Token{
...@@ -1407,7 +1407,7 @@ var encodeTokenTests = []struct { ...@@ -1407,7 +1407,7 @@ var encodeTokenTests = []struct {
{Name{"foo", "xmlns"}, "space"}, {Name{"foo", "xmlns"}, "space"},
}}, }},
}, },
want: `<foo xmlns:foo="foo" foo:xmlns="space">`, err: `xml: cannot redefine xmlns attribute prefix`,
}, { }, {
desc: "xmlns with explicit name space #1", desc: "xmlns with explicit name space #1",
toks: []Token{ toks: []Token{
...@@ -1415,7 +1415,7 @@ var encodeTokenTests = []struct { ...@@ -1415,7 +1415,7 @@ var encodeTokenTests = []struct {
{Name{"xml", "xmlns"}, "space"}, {Name{"xml", "xmlns"}, "space"},
}}, }},
}, },
want: `<foo xmlns="space" xmlns:_xml="xml" _xml:xmlns="space">`, want: `<foo xmlns="space">`,
}, { }, {
desc: "xmlns with explicit name space #2", desc: "xmlns with explicit name space #2",
toks: []Token{ toks: []Token{
...@@ -1423,7 +1423,7 @@ var encodeTokenTests = []struct { ...@@ -1423,7 +1423,7 @@ var encodeTokenTests = []struct {
{Name{xmlURL, "xmlns"}, "space"}, {Name{xmlURL, "xmlns"}, "space"},
}}, }},
}, },
want: `<foo xmlns="space" xml:xmlns="space">`, want: `<foo xmlns="space">`,
}, { }, {
desc: "empty name space declaration is ignored", desc: "empty name space declaration is ignored",
toks: []Token{ toks: []Token{
...@@ -1431,7 +1431,7 @@ var encodeTokenTests = []struct { ...@@ -1431,7 +1431,7 @@ var encodeTokenTests = []struct {
{Name{"xmlns", "foo"}, ""}, {Name{"xmlns", "foo"}, ""},
}}, }},
}, },
want: `<foo xmlns:_xmlns="xmlns" _xmlns:foo="">`, want: `<foo>`,
}, { }, {
desc: "attribute with no name is ignored", desc: "attribute with no name is ignored",
toks: []Token{ toks: []Token{
...@@ -1447,7 +1447,7 @@ var encodeTokenTests = []struct { ...@@ -1447,7 +1447,7 @@ var encodeTokenTests = []struct {
{Name{"/34", "x"}, "value"}, {Name{"/34", "x"}, "value"},
}}, }},
}, },
want: `<foo xmlns="/34" xmlns:_="/34" _:x="value">`, want: `<_:foo xmlns:_="/34" _:x="value">`,
}, { }, {
desc: "nested element resets default namespace to empty", desc: "nested element resets default namespace to empty",
toks: []Token{ toks: []Token{
...@@ -1460,7 +1460,7 @@ var encodeTokenTests = []struct { ...@@ -1460,7 +1460,7 @@ var encodeTokenTests = []struct {
{Name{"space", "x"}, "value"}, {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:space="space" xmlns="" x="value" space:x="value">`,
}, { }, {
desc: "nested element requires empty default name space", desc: "nested element requires empty default name space",
toks: []Token{ toks: []Token{
...@@ -1469,7 +1469,7 @@ var encodeTokenTests = []struct { ...@@ -1469,7 +1469,7 @@ var encodeTokenTests = []struct {
}}, }},
StartElement{Name{"", "foo"}, nil}, StartElement{Name{"", "foo"}, nil},
}, },
want: `<foo xmlns="space" xmlns="space"><foo>`, want: `<foo xmlns="space"><foo xmlns="">`,
}, { }, {
desc: "attribute uses name space from xmlns", desc: "attribute uses name space from xmlns",
toks: []Token{ toks: []Token{
...@@ -1478,7 +1478,7 @@ var encodeTokenTests = []struct { ...@@ -1478,7 +1478,7 @@ var encodeTokenTests = []struct {
{Name{"some/space", "other"}, "other value"}, {Name{"some/space", "other"}, "other value"},
}}, }},
}, },
want: `<foo xmlns="some/space" attr="value" xmlns:space="some/space" space:other="other value">`, want: `<space:foo xmlns:space="some/space" attr="value" space:other="other value">`,
}, { }, {
desc: "default name space should not be used by attributes", desc: "default name space should not be used by attributes",
toks: []Token{ toks: []Token{
...@@ -1491,7 +1491,7 @@ var encodeTokenTests = []struct { ...@@ -1491,7 +1491,7 @@ var encodeTokenTests = []struct {
EndElement{Name{"space", "baz"}}, EndElement{Name{"space", "baz"}},
EndElement{Name{"space", "foo"}}, 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:bar="space" xmlns="space" bar:baz="foo"><baz></baz></foo>`,
}, { }, {
desc: "default name space not used by attributes, not explicitly defined", desc: "default name space not used by attributes, not explicitly defined",
toks: []Token{ toks: []Token{
...@@ -1503,7 +1503,7 @@ var encodeTokenTests = []struct { ...@@ -1503,7 +1503,7 @@ var encodeTokenTests = []struct {
EndElement{Name{"space", "baz"}}, EndElement{Name{"space", "baz"}},
EndElement{Name{"space", "foo"}}, 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="space" xmlns="space" space:baz="foo"><baz></baz></foo>`,
}, { }, {
desc: "impossible xmlns declaration", desc: "impossible xmlns declaration",
toks: []Token{ toks: []Token{
...@@ -1514,7 +1514,7 @@ var encodeTokenTests = []struct { ...@@ -1514,7 +1514,7 @@ var encodeTokenTests = []struct {
{Name{"space", "attr"}, "value"}, {Name{"space", "attr"}, "value"},
}}, }},
}, },
want: `<foo xmlns="space"><bar xmlns="space" xmlns:space="space" space:attr="value">`, want: `<foo><space:bar xmlns:space="space" space:attr="value">`,
}} }}
func TestEncodeToken(t *testing.T) { func TestEncodeToken(t *testing.T) {
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
package xml package xml
import ( import (
"bytes"
"fmt"
"io" "io"
"reflect" "reflect"
"strings" "strings"
...@@ -484,6 +486,34 @@ func TestUnmarshalNS(t *testing.T) { ...@@ -484,6 +486,34 @@ func TestUnmarshalNS(t *testing.T) {
} }
} }
func TestRoundTrip(t *testing.T) {
// From issue 7535
const s = `<ex:element xmlns:ex="http://example.com/schema"></ex:element>`
in := bytes.NewBufferString(s)
for i := 0; i < 10; i++ {
out := &bytes.Buffer{}
d := NewDecoder(in)
e := NewEncoder(out)
for {
t, err := d.Token()
if err == io.EOF {
break
}
if err != nil {
fmt.Println("failed:", err)
return
}
e.EncodeToken(t)
}
e.Flush()
in = out
}
if got := in.String(); got != s {
t.Errorf("have: %q\nwant: %q\n", got, s)
}
}
func TestMarshalNS(t *testing.T) { func TestMarshalNS(t *testing.T) {
dst := Tables{"hello", "world"} dst := Tables{"hello", "world"}
data, err := Marshal(&dst) data, err := Marshal(&dst)
...@@ -607,7 +637,7 @@ func TestMarshalNSAttr(t *testing.T) { ...@@ -607,7 +637,7 @@ func TestMarshalNSAttr(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Marshal: %v", err) t.Fatalf("Marshal: %v", err)
} }
want := `<TableAttrs><TAttr xmlns:html4="http://www.w3.org/TR/html4/" html4:table="hello" xmlns:furniture="http://www.w3schools.com/furniture" furniture:table="world" xml:lang="en_US" xmlns:_xml="http://golang.org/xml/" _xml:other="other1" xmlns:_xmlfoo="http://golang.org/xmlfoo/" _xmlfoo:other="other2" xmlns:json="http://golang.org/json/" json:other="other3" xmlns:json_1="http://golang.org/2/json/" json_1:other="other4"></TAttr></TableAttrs>` want := `<TableAttrs><TAttr xmlns:json_1="http://golang.org/2/json/" xmlns:json="http://golang.org/json/" xmlns:_xmlfoo="http://golang.org/xmlfoo/" xmlns:_xml="http://golang.org/xml/" xmlns:furniture="http://www.w3schools.com/furniture" xmlns:html4="http://www.w3.org/TR/html4/" html4:table="hello" furniture:table="world" xml:lang="en_US" _xml:other="other1" _xmlfoo:other="other2" json:other="other3" json_1:other="other4"></TAttr></TableAttrs>`
str := string(data) str := string(data)
if str != want { if str != want {
t.Errorf("Marshal:\nhave: %#q\nwant: %#q\n", str, want) t.Errorf("Marshal:\nhave: %#q\nwant: %#q\n", str, want)
......
...@@ -35,15 +35,24 @@ func (e *SyntaxError) Error() string { ...@@ -35,15 +35,24 @@ func (e *SyntaxError) Error() string {
return "XML syntax error on line " + strconv.Itoa(e.Line) + ": " + e.Msg return "XML syntax error on line " + strconv.Itoa(e.Line) + ": " + e.Msg
} }
// A Name represents an XML name (Local) annotated // A Name represents an XML name (Local) annotated with a name space
// with a name space identifier (Space). // identifier (Space). In tokens returned by Decoder.Token, the Space
// In tokens returned by Decoder.Token, the Space identifier // identifier is given as a canonical URL, not the short prefix used in
// is given as a canonical URL, not the short prefix used // the document being parsed.
// in the document being parsed. //
// As a special case, XML namespace declarations will use the literal
// string "xmlns" for the Space field instead of the fully resolved URL.
// See Encoder.EncodeToken for more information on namespace encoding
// behaviour.
type Name struct { type Name struct {
Space, Local string Space, Local string
} }
// isNamespace reports whether the name is a namespace-defining name.
func (name Name) isNamespace() bool {
return name.Local == "xmlns" || name.Space == "xmlns"
}
// An Attr represents an attribute in an XML element (Name=Value). // An Attr represents an attribute in an XML element (Name=Value).
type Attr struct { type Attr struct {
Name Name Name Name
...@@ -72,6 +81,24 @@ func (e StartElement) End() EndElement { ...@@ -72,6 +81,24 @@ func (e StartElement) End() EndElement {
return EndElement{e.Name} return EndElement{e.Name}
} }
// setDefaultNamespace sets the namespace of the element
// as the default for all elements contained within it.
func (e *StartElement) setDefaultNamespace() {
if e.Name.Space == "" {
// If there's no namespace on the element, don't
// set the default. Strictly speaking this might be wrong, as
// we can't tell if the element had no namespace set
// or was just using the default namespace.
return
}
e.Attr = append(e.Attr, Attr{
Name: Name{
Local: "xmlns",
},
Value: e.Name.Space,
})
}
// An EndElement represents an XML end element. // An EndElement represents an XML end element.
type EndElement struct { type EndElement struct {
Name Name Name Name
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment