Skip to content

Commit 8812191

Browse files
committed
Add namespace prefix support
1 parent 669d87a commit 8812191

File tree

1 file changed

+122
-64
lines changed

1 file changed

+122
-64
lines changed

src/encoding/xml/marshal.go

Lines changed: 122 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,14 @@ const (
8080
//
8181
// Marshal will return an error if asked to marshal a channel, function, or map.
8282
func Marshal(v any) ([]byte, error) {
83+
nametable := make(map[string]string)
84+
return MarshalWithNametable(v, nametable)
85+
}
86+
87+
func MarshalWithNametable(v any, nametable map[string]string) ([]byte, error) {
8388
var b bytes.Buffer
8489
enc := NewEncoder(&b)
90+
enc.p.nametable = nametable
8591
if err := enc.Encode(v); err != nil {
8692
return nil, err
8793
}
@@ -130,9 +136,15 @@ type MarshalerAttr interface {
130136
// indented line that starts with prefix and is followed by one or more
131137
// copies of indent according to the nesting depth.
132138
func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
139+
nametable := make(map[string]string)
140+
return MarshalIndentWithNametable(v, nametable, prefix, indent)
141+
}
142+
143+
func MarshalIndentWithNametable(v any, nametable map[string]string, prefix, indent string) ([]byte, error) {
133144
var b bytes.Buffer
134145
enc := NewEncoder(&b)
135146
enc.Indent(prefix, indent)
147+
enc.p.nametable = nametable
136148
if err := enc.Encode(v); err != nil {
137149
return nil, err
138150
}
@@ -264,6 +276,17 @@ func (enc *Encoder) EncodeToken(t Token) error {
264276
return p.cachedWriteError()
265277
}
266278

279+
func (enc *Encoder) SetNamespace(prefix, url string) {
280+
if enc.p.nametable == nil {
281+
enc.p.nametable = make(map[string]string)
282+
}
283+
enc.p.nametable[url] = prefix
284+
}
285+
286+
func (enc *Encoder) SetNametable(nametable map[string]string) {
287+
enc.p.nametable = nametable
288+
}
289+
267290
// isValidDirective reports whether dir is a valid directive text,
268291
// meaning angle brackets are matched, ignoring comments and strings.
269292
func isValidDirective(dir Directive) bool {
@@ -318,87 +341,101 @@ func (enc *Encoder) Close() error {
318341
}
319342

320343
type printer struct {
321-
w *bufio.Writer
322-
encoder *Encoder
323-
seq int
324-
indent string
325-
prefix string
326-
depth int
327-
indentedIn bool
328-
putNewline bool
329-
attrNS map[string]string // map prefix -> name space
330-
attrPrefix map[string]string // map name space -> prefix
331-
prefixes []string
332-
tags []Name
333-
closed bool
334-
err error
344+
w *bufio.Writer
345+
encoder *Encoder
346+
seq int
347+
indent string
348+
prefix string
349+
depth int
350+
indentedIn bool
351+
putNewline bool
352+
prefixes []string
353+
tags []Name
354+
closed bool
355+
err error
356+
elementNS map[string]string // map prefix -> name space
357+
elementPrefix map[string]string // map name space -> prefix
358+
nametable map[string]string
335359
}
336360

337-
// createAttrPrefix finds the name space prefix attribute to use for the given name space,
338-
// defining a new prefix if necessary. It returns the prefix.
339-
func (p *printer) createAttrPrefix(url string) string {
340-
if prefix := p.attrPrefix[url]; prefix != "" {
341-
return prefix
361+
func (p *printer) createElementPrefix(url string) (string, bool) {
362+
if url == "" {
363+
return "", true
364+
}
365+
if prefix := p.elementPrefix[url]; prefix != "" {
366+
return prefix, true
342367
}
343368

344369
// The "http://www.w3.org/XML/1998/namespace" name space is predefined as "xml"
345370
// and must be referred to that way.
346371
// (The "http://www.w3.org/2000/xmlns/" name space is also predefined as "xmlns",
347372
// but users should not be trying to use that one directly - that's our job.)
348373
if url == xmlURL {
349-
return xmlPrefix
374+
return xmlPrefix, true
350375
}
351376

352377
// Need to define a new name space.
353-
if p.attrPrefix == nil {
354-
p.attrPrefix = make(map[string]string)
355-
p.attrNS = make(map[string]string)
356-
}
357-
358-
// Pick a name. We try to use the final element of the path
359-
// but fall back to _.
360-
prefix := strings.TrimRight(url, "/")
361-
if i := strings.LastIndex(prefix, "/"); i >= 0 {
362-
prefix = prefix[i+1:]
363-
}
364-
if prefix == "" || !isName([]byte(prefix)) || strings.Contains(prefix, ":") {
365-
prefix = "_"
366-
}
367-
// xmlanything is reserved and any variant of it regardless of
368-
// case should be matched, so:
369-
// (('X'|'x') ('M'|'m') ('L'|'l'))
370-
// See Section 2.3 of https://www.w3.org/TR/REC-xml/
371-
if len(prefix) >= 3 && strings.EqualFold(prefix[:3], "xml") {
372-
prefix = "_" + prefix
373-
}
374-
if p.attrNS[prefix] != "" {
375-
// Name is taken. Find a better one.
376-
for p.seq++; ; p.seq++ {
377-
if id := prefix + "_" + strconv.Itoa(p.seq); p.attrNS[id] == "" {
378-
prefix = id
379-
break
378+
if p.elementPrefix == nil {
379+
p.elementPrefix = make(map[string]string)
380+
p.elementNS = make(map[string]string)
381+
}
382+
383+
// Get the prefix from the nametable
384+
prefix := p.nametable[url]
385+
if prefix == "" {
386+
// Pick a name. We try to use the final element of the path
387+
// but fall back to _.
388+
prefix = strings.TrimRight(url, "/")
389+
if i := strings.LastIndex(prefix, "/"); i >= 0 {
390+
prefix = prefix[i+1:]
391+
}
392+
if prefix == "" || !isName([]byte(prefix)) || strings.Contains(prefix, ":") {
393+
prefix = "_"
394+
}
395+
396+
// xmlanything is reserved and any variant of it regardless of
397+
// case should be matched, so:
398+
// (('X'|'x') ('M'|'m') ('L'|'l'))
399+
// See Section 2.3 of https://www.w3.org/TR/REC-xml/
400+
if len(prefix) >= 3 && strings.EqualFold(prefix[:3], "xml") {
401+
prefix = "_" + prefix
402+
}
403+
if p.elementNS[prefix] != "" {
404+
for p.seq++; ; p.seq++ {
405+
if id := prefix + "_" + strconv.Itoa(p.seq); p.elementNS[id] == "" {
406+
prefix = id
407+
break
408+
}
380409
}
381410
}
382411
}
383412

384-
p.attrPrefix[url] = prefix
385-
p.attrNS[prefix] = url
386-
387-
p.WriteString(`xmlns:`)
388-
p.WriteString(prefix)
389-
p.WriteString(`="`)
390-
EscapeText(p, []byte(url))
391-
p.WriteString(`" `)
392-
413+
p.elementPrefix[url] = prefix
414+
p.elementNS[prefix] = url
393415
p.prefixes = append(p.prefixes, prefix)
394416

395-
return prefix
417+
return prefix, false
396418
}
397419

398-
// deleteAttrPrefix removes an attribute name space prefix.
399-
func (p *printer) deleteAttrPrefix(prefix string) {
400-
delete(p.attrPrefix, p.attrNS[prefix])
401-
delete(p.attrNS, prefix)
420+
func (p *printer) deleteElementPrefix(prefix string) {
421+
delete(p.elementPrefix, p.elementNS[prefix])
422+
delete(p.elementNS, prefix)
423+
}
424+
425+
// createAttrPrefix finds the name space prefix attribute to use for the given name space,
426+
// defining a new prefix if necessary. It returns the prefix.
427+
func (p *printer) createAttrPrefix(url string) string {
428+
prefix, found := p.createElementPrefix(url)
429+
430+
if !found {
431+
p.WriteString(`xmlns:`)
432+
p.WriteString(prefix)
433+
p.WriteString(`="`)
434+
EscapeText(p, []byte(url))
435+
p.WriteString(`" `)
436+
}
437+
438+
return prefix
402439
}
403440

404441
func (p *printer) markPrefix() {
@@ -412,7 +449,7 @@ func (p *printer) popPrefix() {
412449
if prefix == "" {
413450
break
414451
}
415-
p.deleteAttrPrefix(prefix)
452+
p.deleteElementPrefix(prefix)
416453
}
417454
}
418455

@@ -726,12 +763,24 @@ func (p *printer) writeStart(start *StartElement) error {
726763
p.tags = append(p.tags, start.Name)
727764
p.markPrefix()
728765

766+
prefix, prefixFound := p.createElementPrefix(start.Name.Space)
767+
729768
p.writeIndent(1)
730769
p.WriteByte('<')
770+
if prefix != "" {
771+
p.WriteString(prefix)
772+
p.WriteByte(':')
773+
}
731774
p.WriteString(start.Name.Local)
732775

733-
if start.Name.Space != "" {
734-
p.WriteString(` xmlns="`)
776+
if start.Name.Space != "" && !prefixFound {
777+
p.WriteString(" xmlns")
778+
if prefix != "" {
779+
p.WriteByte(':')
780+
p.WriteString(prefix)
781+
}
782+
p.WriteByte('=')
783+
p.WriteByte('"')
735784
p.EscapeString(start.Name.Space)
736785
p.WriteByte('"')
737786
}
@@ -771,9 +820,18 @@ func (p *printer) writeEnd(name Name) error {
771820
}
772821
p.tags = p.tags[:len(p.tags)-1]
773822

823+
prefix := ""
824+
if name.Space != "" {
825+
prefix = p.elementPrefix[name.Space]
826+
}
827+
774828
p.writeIndent(-1)
775829
p.WriteByte('<')
776830
p.WriteByte('/')
831+
if prefix != "" {
832+
p.WriteString(prefix)
833+
p.WriteByte(':')
834+
}
777835
p.WriteString(name.Local)
778836
p.WriteByte('>')
779837
p.popPrefix()

0 commit comments

Comments
 (0)