Skip to content

Commit 7f632f7

Browse files
Meroviusgopherbot
authored andcommitted
encoding/xml: add (*Encoder).Close
Flush can not check for unclosed elements, as more data might be encoded after Flush is called. Close implicitly calls Flush and also checks that all opened elements are closed as well. Fixes #53346 Change-Id: I889b9f5ae54e5dfabb9e6948d96c5ed7bc1110f9 Reviewed-on: https://go-review.googlesource.com/c/go/+/424777 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: David Chase <[email protected]>
1 parent be9e244 commit 7f632f7

File tree

3 files changed

+133
-7
lines changed

3 files changed

+133
-7
lines changed

api/next/53346.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pkg encoding/xml, method (*Encoder) Close() error #53346

src/encoding/xml/marshal.go

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"bufio"
99
"bytes"
1010
"encoding"
11+
"errors"
1112
"fmt"
1213
"io"
1314
"reflect"
@@ -78,7 +79,11 @@ const (
7879
// Marshal will return an error if asked to marshal a channel, function, or map.
7980
func Marshal(v any) ([]byte, error) {
8081
var b bytes.Buffer
81-
if err := NewEncoder(&b).Encode(v); err != nil {
82+
enc := NewEncoder(&b)
83+
if err := enc.Encode(v); err != nil {
84+
return nil, err
85+
}
86+
if err := enc.Close(); err != nil {
8287
return nil, err
8388
}
8489
return b.Bytes(), nil
@@ -129,6 +134,9 @@ func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
129134
if err := enc.Encode(v); err != nil {
130135
return nil, err
131136
}
137+
if err := enc.Close(); err != nil {
138+
return nil, err
139+
}
132140
return b.Bytes(), nil
133141
}
134142

@@ -139,7 +147,7 @@ type Encoder struct {
139147

140148
// NewEncoder returns a new encoder that writes to w.
141149
func NewEncoder(w io.Writer) *Encoder {
142-
e := &Encoder{printer{Writer: bufio.NewWriter(w)}}
150+
e := &Encoder{printer{w: bufio.NewWriter(w)}}
143151
e.p.encoder = e
144152
return e
145153
}
@@ -163,7 +171,7 @@ func (enc *Encoder) Encode(v any) error {
163171
if err != nil {
164172
return err
165173
}
166-
return enc.p.Flush()
174+
return enc.p.w.Flush()
167175
}
168176

169177
// EncodeElement writes the XML encoding of v to the stream,
@@ -178,7 +186,7 @@ func (enc *Encoder) EncodeElement(v any, start StartElement) error {
178186
if err != nil {
179187
return err
180188
}
181-
return enc.p.Flush()
189+
return enc.p.w.Flush()
182190
}
183191

184192
var (
@@ -224,7 +232,7 @@ func (enc *Encoder) EncodeToken(t Token) error {
224232
case ProcInst:
225233
// First token to be encoded which is also a ProcInst with target of xml
226234
// is the xml declaration. The only ProcInst where target of xml is allowed.
227-
if t.Target == "xml" && p.Buffered() != 0 {
235+
if t.Target == "xml" && p.w.Buffered() != 0 {
228236
return fmt.Errorf("xml: EncodeToken of ProcInst xml target only valid for xml declaration, first token encoded")
229237
}
230238
if !isNameString(t.Target) {
@@ -297,11 +305,18 @@ func isValidDirective(dir Directive) bool {
297305
// Flush flushes any buffered XML to the underlying writer.
298306
// See the EncodeToken documentation for details about when it is necessary.
299307
func (enc *Encoder) Flush() error {
300-
return enc.p.Flush()
308+
return enc.p.w.Flush()
309+
}
310+
311+
// Close the Encoder, indicating that no more data will be written. It flushes
312+
// any buffered XML to the underlying writer and returns an error if the
313+
// written XML is invalid (e.g. by containing unclosed elements).
314+
func (enc *Encoder) Close() error {
315+
return enc.p.Close()
301316
}
302317

303318
type printer struct {
304-
*bufio.Writer
319+
w *bufio.Writer
305320
encoder *Encoder
306321
seq int
307322
indent string
@@ -313,6 +328,8 @@ type printer struct {
313328
attrPrefix map[string]string // map name space -> prefix
314329
prefixes []string
315330
tags []Name
331+
closed bool
332+
err error
316333
}
317334

318335
// createAttrPrefix finds the name space prefix attribute to use for the given name space,
@@ -961,6 +978,56 @@ func (p *printer) marshalStruct(tinfo *typeInfo, val reflect.Value) error {
961978
return p.cachedWriteError()
962979
}
963980

981+
// Write implements io.Writer
982+
func (p *printer) Write(b []byte) (n int, err error) {
983+
if p.closed && p.err == nil {
984+
p.err = errors.New("use of closed Encoder")
985+
}
986+
if p.err == nil {
987+
n, p.err = p.w.Write(b)
988+
}
989+
return n, p.err
990+
}
991+
992+
// WriteString implements io.StringWriter
993+
func (p *printer) WriteString(s string) (n int, err error) {
994+
if p.closed && p.err == nil {
995+
p.err = errors.New("use of closed Encoder")
996+
}
997+
if p.err == nil {
998+
n, p.err = p.w.WriteString(s)
999+
}
1000+
return n, p.err
1001+
}
1002+
1003+
// WriteByte implements io.ByteWriter
1004+
func (p *printer) WriteByte(c byte) error {
1005+
if p.closed && p.err == nil {
1006+
p.err = errors.New("use of closed Encoder")
1007+
}
1008+
if p.err == nil {
1009+
p.err = p.w.WriteByte(c)
1010+
}
1011+
return p.err
1012+
}
1013+
1014+
// Close the Encoder, indicating that no more data will be written. It flushes
1015+
// any buffered XML to the underlying writer and returns an error if the
1016+
// written XML is invalid (e.g. by containing unclosed elements).
1017+
func (p *printer) Close() error {
1018+
if p.closed {
1019+
return nil
1020+
}
1021+
p.closed = true
1022+
if err := p.w.Flush(); err != nil {
1023+
return err
1024+
}
1025+
if len(p.tags) > 0 {
1026+
return fmt.Errorf("unclosed tag <%s>", p.tags[len(p.tags)-1].Local)
1027+
}
1028+
return nil
1029+
}
1030+
9641031
// return the bufio Writer's cached write error
9651032
func (p *printer) cachedWriteError() error {
9661033
_, err := p.Write(nil)

src/encoding/xml/marshal_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2531,3 +2531,61 @@ func TestMarshalZeroValue(t *testing.T) {
25312531
t.Fatalf("unexpected unmarshal result, want %q but got %q", proofXml, anotherXML)
25322532
}
25332533
}
2534+
2535+
var closeTests = []struct {
2536+
desc string
2537+
toks []Token
2538+
want string
2539+
err string
2540+
}{{
2541+
desc: "unclosed start element",
2542+
toks: []Token{
2543+
StartElement{Name{"", "foo"}, nil},
2544+
},
2545+
want: `<foo>`,
2546+
err: "unclosed tag <foo>",
2547+
}, {
2548+
desc: "closed element",
2549+
toks: []Token{
2550+
StartElement{Name{"", "foo"}, nil},
2551+
EndElement{Name{"", "foo"}},
2552+
},
2553+
want: `<foo></foo>`,
2554+
}, {
2555+
desc: "directive",
2556+
toks: []Token{
2557+
Directive("foo"),
2558+
},
2559+
want: `<!foo>`,
2560+
}}
2561+
2562+
func TestClose(t *testing.T) {
2563+
for _, tt := range closeTests {
2564+
tt := tt
2565+
t.Run(tt.desc, func(t *testing.T) {
2566+
var out strings.Builder
2567+
enc := NewEncoder(&out)
2568+
for j, tok := range tt.toks {
2569+
if err := enc.EncodeToken(tok); err != nil {
2570+
t.Fatalf("token #%d: %v", j, err)
2571+
}
2572+
}
2573+
err := enc.Close()
2574+
switch {
2575+
case tt.err != "" && err == nil:
2576+
t.Error(" expected error; got none")
2577+
case tt.err == "" && err != nil:
2578+
t.Errorf(" got error: %v", err)
2579+
case tt.err != "" && err != nil && tt.err != err.Error():
2580+
t.Errorf(" error mismatch; got %v, want %v", err, tt.err)
2581+
}
2582+
if got := out.String(); got != tt.want {
2583+
t.Errorf("\ngot %v\nwant %v", got, tt.want)
2584+
}
2585+
t.Log(enc.p.closed)
2586+
if err := enc.EncodeToken(Directive("foo")); err == nil {
2587+
t.Errorf("unexpected success when encoding after Close")
2588+
}
2589+
})
2590+
}
2591+
}

0 commit comments

Comments
 (0)