Skip to content

Commit bdf8bf6

Browse files
committed
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
1 parent 8c2b622 commit bdf8bf6

File tree

3 files changed

+97
-21
lines changed

3 files changed

+97
-21
lines changed

src/pkg/encoding/xml/marshal.go

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,70 @@ type printer struct {
126126
depth int
127127
indentedIn bool
128128
putNewline bool
129+
attrNS map[string]string // map prefix -> name space
130+
attrPrefix map[string]string // map name space -> prefix
131+
}
132+
133+
// createAttrPrefix finds the name space prefix attribute to use for the given name space,
134+
// defining a new prefix if necessary. It returns the prefix and whether it is new.
135+
func (p *printer) createAttrPrefix(url string) (prefix string, isNew bool) {
136+
if prefix = p.attrPrefix[url]; prefix != "" {
137+
return prefix, false
138+
}
139+
140+
// The "http://www.w3.org/XML/1998/namespace" name space is predefined as "xml"
141+
// and must be referred to that way.
142+
// (The "http://www.w3.org/2000/xmlns/" name space is also predefined as "xmlns",
143+
// but users should not be trying to use that one directly - that's our job.)
144+
if url == xmlURL {
145+
return "xml", false
146+
}
147+
148+
// Need to define a new name space.
149+
if p.attrPrefix == nil {
150+
p.attrPrefix = make(map[string]string)
151+
p.attrNS = make(map[string]string)
152+
}
153+
154+
// Pick a name. We try to use the final element of the path
155+
// but fall back to _.
156+
prefix = strings.TrimRight(url, "/")
157+
if i := strings.LastIndex(prefix, "/"); i >= 0 {
158+
prefix = prefix[i+1:]
159+
}
160+
if prefix == "" || !isName([]byte(prefix)) || strings.Contains(prefix, ":") {
161+
prefix = "_"
162+
}
163+
if strings.HasPrefix(prefix, "xml") {
164+
// xmlanything is reserved.
165+
prefix = "_" + prefix
166+
}
167+
if p.attrNS[prefix] != "" {
168+
// Name is taken. Find a better one.
169+
for p.seq++; ; p.seq++ {
170+
if id := prefix + "_" + strconv.Itoa(p.seq); p.attrNS[id] == "" {
171+
prefix = id
172+
break
173+
}
174+
}
175+
}
176+
177+
p.attrPrefix[url] = prefix
178+
p.attrNS[prefix] = url
179+
180+
p.WriteString(`xmlns:`)
181+
p.WriteString(prefix)
182+
p.WriteString(`="`)
183+
EscapeText(p, []byte(url))
184+
p.WriteString(`" `)
185+
186+
return prefix, true
187+
}
188+
189+
// deleteAttrPrefix removes an attribute name space prefix.
190+
func (p *printer) deleteAttrPrefix(prefix string) {
191+
delete(p.attrPrefix, p.attrNS[prefix])
192+
delete(p.attrNS, prefix)
129193
}
130194

131195
// marshalValue writes one or more XML elements representing val.
@@ -212,17 +276,11 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo) error {
212276
}
213277
p.WriteByte(' ')
214278
if finfo.xmlns != "" {
215-
p.WriteString("xmlns:")
216-
p.seq++
217-
id := "_" + strconv.Itoa(p.seq)
218-
p.WriteString(id)
219-
p.WriteString(`="`)
220-
// TODO: EscapeString, to avoid the allocation.
221-
if err := EscapeText(p, []byte(finfo.xmlns)); err != nil {
222-
return err
279+
prefix, created := p.createAttrPrefix(finfo.xmlns)
280+
if created {
281+
defer p.deleteAttrPrefix(prefix)
223282
}
224-
p.WriteString(`" `)
225-
p.WriteString(id)
283+
p.WriteString(prefix)
226284
p.WriteByte(':')
227285
}
228286
p.WriteString(finfo.name)

src/pkg/encoding/xml/read_test.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,11 @@ type TableAttrs struct {
503503
type TAttr struct {
504504
HTable string `xml:"http://www.w3.org/TR/html4/ table,attr"`
505505
FTable string `xml:"http://www.w3schools.com/furniture table,attr"`
506+
Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
507+
Other1 string `xml:"http://golang.org/xml/ other,attr,omitempty"`
508+
Other2 string `xml:"http://golang.org/xmlfoo/ other,attr,omitempty"`
509+
Other3 string `xml:"http://golang.org/json/ other,attr,omitempty"`
510+
Other4 string `xml:"http://golang.org/2/json/ other,attr,omitempty"`
506511
}
507512

508513
var tableAttrs = []struct {
@@ -514,33 +519,33 @@ var tableAttrs = []struct {
514519
xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
515520
`h:table="hello" f:table="world" ` +
516521
`/></TableAttrs>`,
517-
tab: TableAttrs{TAttr{"hello", "world"}},
522+
tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
518523
},
519524
{
520525
xml: `<TableAttrs><TAttr xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/" ` +
521526
`h:table="hello" f:table="world" ` +
522527
`/></TableAttrs>`,
523-
tab: TableAttrs{TAttr{"hello", "world"}},
528+
tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
524529
},
525530
{
526531
xml: `<TableAttrs><TAttr ` +
527532
`h:table="hello" f:table="world" xmlns:f="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/" ` +
528533
`/></TableAttrs>`,
529-
tab: TableAttrs{TAttr{"hello", "world"}},
534+
tab: TableAttrs{TAttr{HTable: "hello", FTable: "world"}},
530535
},
531536
{
532537
// Default space does not apply to attribute names.
533538
xml: `<TableAttrs xmlns="http://www.w3schools.com/furniture" xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
534539
`h:table="hello" table="world" ` +
535540
`/></TableAttrs>`,
536-
tab: TableAttrs{TAttr{"hello", ""}},
541+
tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}},
537542
},
538543
{
539544
// Default space does not apply to attribute names.
540545
xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr xmlns="http://www.w3.org/TR/html4/" ` +
541546
`table="hello" f:table="world" ` +
542547
`/></TableAttrs>`,
543-
tab: TableAttrs{TAttr{"", "world"}},
548+
tab: TableAttrs{TAttr{HTable: "", FTable: "world"}},
544549
},
545550
{
546551
xml: `<TableAttrs><TAttr ` +
@@ -553,15 +558,15 @@ var tableAttrs = []struct {
553558
xml: `<TableAttrs xmlns:h="http://www.w3.org/TR/html4/"><TAttr ` +
554559
`h:table="hello" table="world" ` +
555560
`/></TableAttrs>`,
556-
tab: TableAttrs{TAttr{"hello", ""}},
561+
tab: TableAttrs{TAttr{HTable: "hello", FTable: ""}},
557562
ns: "http://www.w3schools.com/furniture",
558563
},
559564
{
560565
// Default space does not apply to attribute names.
561566
xml: `<TableAttrs xmlns:f="http://www.w3schools.com/furniture"><TAttr ` +
562567
`table="hello" f:table="world" ` +
563568
`/></TableAttrs>`,
564-
tab: TableAttrs{TAttr{"", "world"}},
569+
tab: TableAttrs{TAttr{HTable: "", FTable: "world"}},
565570
ns: "http://www.w3.org/TR/html4/",
566571
},
567572
{
@@ -596,14 +601,23 @@ func TestUnmarshalNSAttr(t *testing.T) {
596601
}
597602

598603
func TestMarshalNSAttr(t *testing.T) {
599-
dst := TableAttrs{TAttr{"hello", "world"}}
600-
data, err := Marshal(&dst)
604+
src := TableAttrs{TAttr{"hello", "world", "en_US", "other1", "other2", "other3", "other4"}}
605+
data, err := Marshal(&src)
601606
if err != nil {
602607
t.Fatalf("Marshal: %v", err)
603608
}
604-
want := `<TableAttrs><TAttr xmlns:_1="http://www.w3.org/TR/html4/" _1:table="hello" xmlns:_2="http://www.w3schools.com/furniture" _2:table="world"></TAttr></TableAttrs>`
609+
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>`
605610
str := string(data)
606611
if str != want {
607-
t.Errorf("have: %q\nwant: %q\n", str, want)
612+
t.Errorf("Marshal:\nhave: %#q\nwant: %#q\n", str, want)
613+
}
614+
615+
var dst TableAttrs
616+
if err := Unmarshal(data, &dst); err != nil {
617+
t.Errorf("Unmarshal: %v", err)
618+
}
619+
620+
if dst != src {
621+
t.Errorf("Unmarshal = %q, want %q", dst, src)
608622
}
609623
}

src/pkg/encoding/xml/xml.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ func (d *Decoder) Token() (t Token, err error) {
273273
return
274274
}
275275

276+
const xmlURL = "http://www.w3.org/XML/1998/namespace"
277+
276278
// Apply name space translation to name n.
277279
// The default name space (for Space=="")
278280
// applies only to element names, not to attribute names.
@@ -282,6 +284,8 @@ func (d *Decoder) translate(n *Name, isElementName bool) {
282284
return
283285
case n.Space == "" && !isElementName:
284286
return
287+
case n.Space == "xml":
288+
n.Space = xmlURL
285289
case n.Space == "" && n.Local == "xmlns":
286290
return
287291
}

0 commit comments

Comments
 (0)