From bdf8bf6adc956719a3a224f32c1ca6e6df77dbac Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Wed, 13 Mar 2013 14:36:42 -0400 Subject: [PATCH] encoding/xml: predefine xml name space prefix Also change prefix generation to use more human-friendly prefixes. Fixes #5040. R=golang-dev, r, bradfitz CC=golang-dev https://golang.org/cl/7777047 --- src/pkg/encoding/xml/marshal.go | 78 +++++++++++++++++++++++++++---- src/pkg/encoding/xml/read_test.go | 36 +++++++++----- src/pkg/encoding/xml/xml.go | 4 ++ 3 files changed, 97 insertions(+), 21 deletions(-) diff --git a/src/pkg/encoding/xml/marshal.go b/src/pkg/encoding/xml/marshal.go index 052e10125f8..47b00176342 100644 --- a/src/pkg/encoding/xml/marshal.go +++ b/src/pkg/encoding/xml/marshal.go @@ -126,6 +126,70 @@ 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 +} + +// createAttrPrefix finds the name space prefix attribute to use for the given name space, +// defining a new prefix if necessary. It returns the prefix and whether it is new. +func (p *printer) createAttrPrefix(url string) (prefix string, isNew bool) { + if prefix = p.attrPrefix[url]; prefix != "" { + return prefix, false + } + + // 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 { + return "xml", false + } + + // Need to define a new name space. + if p.attrPrefix == nil { + p.attrPrefix = make(map[string]string) + p.attrNS = make(map[string]string) + } + + // Pick a name. We try to use the final element of the path + // but fall back to _. + prefix = strings.TrimRight(url, "/") + if i := strings.LastIndex(prefix, "/"); i >= 0 { + prefix = prefix[i+1:] + } + if prefix == "" || !isName([]byte(prefix)) || strings.Contains(prefix, ":") { + prefix = "_" + } + if strings.HasPrefix(prefix, "xml") { + // xmlanything is reserved. + prefix = "_" + prefix + } + if p.attrNS[prefix] != "" { + // Name is taken. Find a better one. + for p.seq++; ; p.seq++ { + if id := prefix + "_" + strconv.Itoa(p.seq); p.attrNS[id] == "" { + prefix = id + break + } + } + } + + p.attrPrefix[url] = prefix + p.attrNS[prefix] = url + + p.WriteString(`xmlns:`) + p.WriteString(prefix) + p.WriteString(`="`) + EscapeText(p, []byte(url)) + p.WriteString(`" `) + + return prefix, true +} + +// deleteAttrPrefix removes an attribute name space prefix. +func (p *printer) deleteAttrPrefix(prefix string) { + delete(p.attrPrefix, p.attrNS[prefix]) + delete(p.attrNS, prefix) } // marshalValue writes one or more XML elements representing val. @@ -212,17 +276,11 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error { } p.WriteByte(' ') if finfo.xmlns != "" { - p.WriteString("xmlns:") - p.seq++ - id := "_" + strconv.Itoa(p.seq) - p.WriteString(id) - p.WriteString(`="`) - // TODO: EscapeString, to avoid the allocation. - if err := EscapeText(p, []byte(finfo.xmlns)); err != nil { - return err + prefix, created := p.createAttrPrefix(finfo.xmlns) + if created { + defer p.deleteAttrPrefix(prefix) } - p.WriteString(`" `) - p.WriteString(id) + p.WriteString(prefix) p.WriteByte(':') } p.WriteString(finfo.name) diff --git a/src/pkg/encoding/xml/read_test.go b/src/pkg/encoding/xml/read_test.go index c0b1b215ac9..7d28c5d7d6c 100644 --- a/src/pkg/encoding/xml/read_test.go +++ b/src/pkg/encoding/xml/read_test.go @@ -503,6 +503,11 @@ type TableAttrs struct { type TAttr struct { HTable string `xml:"http://www.w3.org/TR/html4/ table,attr"` FTable string `xml:"http://www.w3schools.com/furniture table,attr"` + Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"` + Other1 string `xml:"http://golang.org/xml/ other,attr,omitempty"` + Other2 string `xml:"http://golang.org/xmlfoo/ other,attr,omitempty"` + Other3 string `xml:"http://golang.org/json/ other,attr,omitempty"` + Other4 string `xml:"http://golang.org/2/json/ other,attr,omitempty"` } var tableAttrs = []struct { @@ -514,33 +519,33 @@ var tableAttrs = []struct { xml: ``, - tab: TableAttrs{TAttr{"hello", "world"}}, + tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}}, }, { xml: ``, - tab: TableAttrs{TAttr{"hello", "world"}}, + tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}}, }, { xml: ``, - tab: TableAttrs{TAttr{"hello", "world"}}, + tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}}, }, { // Default space does not apply to attribute names. xml: ``, - tab: TableAttrs{TAttr{"hello", ""}}, + tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}}, }, { // Default space does not apply to attribute names. xml: ``, - tab: TableAttrs{TAttr{"", "world"}}, + tab: TableAttrs{TAttr{HTable: "", FTable: "world"}}, }, { xml: ``, - tab: TableAttrs{TAttr{"hello", ""}}, + tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}}, ns: "http://www.w3schools.com/furniture", }, { @@ -561,7 +566,7 @@ var tableAttrs = []struct { xml: ``, - tab: TableAttrs{TAttr{"", "world"}}, + tab: TableAttrs{TAttr{HTable: "", FTable: "world"}}, ns: "http://www.w3.org/TR/html4/", }, { @@ -596,14 +601,23 @@ func TestUnmarshalNSAttr(t *testing.T) { } func TestMarshalNSAttr(t *testing.T) { - dst := TableAttrs{TAttr{"hello", "world"}} - data, err := Marshal(&dst) + src := TableAttrs{TAttr{"hello", "world", "en_US", "other1", "other2", "other3", "other4"}} + data, err := Marshal(&src) if err != nil { t.Fatalf("Marshal: %v", err) } - want := `` + want := `` str := string(data) if str != want { - t.Errorf("have: %q\nwant: %q\n", str, want) + t.Errorf("Marshal:\nhave: %#q\nwant: %#q\n", str, want) + } + + var dst TableAttrs + if err := Unmarshal(data, &dst); err != nil { + t.Errorf("Unmarshal: %v", err) + } + + if dst != src { + t.Errorf("Unmarshal = %q, want %q", dst, src) } } diff --git a/src/pkg/encoding/xml/xml.go b/src/pkg/encoding/xml/xml.go index e8417cc6393..96d97dbe2a0 100644 --- a/src/pkg/encoding/xml/xml.go +++ b/src/pkg/encoding/xml/xml.go @@ -273,6 +273,8 @@ func (d *Decoder) Token() (t Token, err error) { return } +const xmlURL = "http://www.w3.org/XML/1998/namespace" + // Apply name space translation to name n. // The default name space (for Space=="") // applies only to element names, not to attribute names. @@ -282,6 +284,8 @@ func (d *Decoder) translate(n *Name, isElementName bool) { return case n.Space == "" && !isElementName: return + case n.Space == "xml": + n.Space = xmlURL case n.Space == "" && n.Local == "xmlns": return }