@@ -80,8 +80,14 @@ const (
80
80
//
81
81
// Marshal will return an error if asked to marshal a channel, function, or map.
82
82
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 ) {
83
88
var b bytes.Buffer
84
89
enc := NewEncoder (& b )
90
+ enc .p .nametable = nametable
85
91
if err := enc .Encode (v ); err != nil {
86
92
return nil , err
87
93
}
@@ -130,9 +136,15 @@ type MarshalerAttr interface {
130
136
// indented line that starts with prefix and is followed by one or more
131
137
// copies of indent according to the nesting depth.
132
138
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 ) {
133
144
var b bytes.Buffer
134
145
enc := NewEncoder (& b )
135
146
enc .Indent (prefix , indent )
147
+ enc .p .nametable = nametable
136
148
if err := enc .Encode (v ); err != nil {
137
149
return nil , err
138
150
}
@@ -264,6 +276,17 @@ func (enc *Encoder) EncodeToken(t Token) error {
264
276
return p .cachedWriteError ()
265
277
}
266
278
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
+
267
290
// isValidDirective reports whether dir is a valid directive text,
268
291
// meaning angle brackets are matched, ignoring comments and strings.
269
292
func isValidDirective (dir Directive ) bool {
@@ -318,87 +341,101 @@ func (enc *Encoder) Close() error {
318
341
}
319
342
320
343
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
335
359
}
336
360
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
342
367
}
343
368
344
369
// The "http://www.w3.org/XML/1998/namespace" name space is predefined as "xml"
345
370
// and must be referred to that way.
346
371
// (The "http://www.w3.org/2000/xmlns/" name space is also predefined as "xmlns",
347
372
// but users should not be trying to use that one directly - that's our job.)
348
373
if url == xmlURL {
349
- return xmlPrefix
374
+ return xmlPrefix , true
350
375
}
351
376
352
377
// 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
+ }
380
409
}
381
410
}
382
411
}
383
412
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
393
415
p .prefixes = append (p .prefixes , prefix )
394
416
395
- return prefix
417
+ return prefix , false
396
418
}
397
419
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
402
439
}
403
440
404
441
func (p * printer ) markPrefix () {
@@ -412,7 +449,7 @@ func (p *printer) popPrefix() {
412
449
if prefix == "" {
413
450
break
414
451
}
415
- p .deleteAttrPrefix (prefix )
452
+ p .deleteElementPrefix (prefix )
416
453
}
417
454
}
418
455
@@ -726,12 +763,24 @@ func (p *printer) writeStart(start *StartElement) error {
726
763
p .tags = append (p .tags , start .Name )
727
764
p .markPrefix ()
728
765
766
+ prefix , prefixFound := p .createElementPrefix (start .Name .Space )
767
+
729
768
p .writeIndent (1 )
730
769
p .WriteByte ('<' )
770
+ if prefix != "" {
771
+ p .WriteString (prefix )
772
+ p .WriteByte (':' )
773
+ }
731
774
p .WriteString (start .Name .Local )
732
775
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 ('"' )
735
784
p .EscapeString (start .Name .Space )
736
785
p .WriteByte ('"' )
737
786
}
@@ -771,9 +820,18 @@ func (p *printer) writeEnd(name Name) error {
771
820
}
772
821
p .tags = p .tags [:len (p .tags )- 1 ]
773
822
823
+ prefix := ""
824
+ if name .Space != "" {
825
+ prefix = p .elementPrefix [name .Space ]
826
+ }
827
+
774
828
p .writeIndent (- 1 )
775
829
p .WriteByte ('<' )
776
830
p .WriteByte ('/' )
831
+ if prefix != "" {
832
+ p .WriteString (prefix )
833
+ p .WriteByte (':' )
834
+ }
777
835
p .WriteString (name .Local )
778
836
p .WriteByte ('>' )
779
837
p .popPrefix ()
0 commit comments