aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/aws/smithy-go/encoding/xml/value.go
blob: 09434b2c0b55f7f2c710873ce1d8cdb3954ada7d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
package xml

import (
	"encoding/base64"
	"fmt"
	"math/big"
	"strconv"

	"github.com/aws/smithy-go/encoding"
)

// Value represents an XML Value type
// XML Value types: Object, Array, Map, String, Number, Boolean.
type Value struct {
	w       writer
	scratch *[]byte

	// xml start element is the associated start element for the Value
	startElement StartElement

	// indicates if the Value represents a flattened shape
	isFlattened bool
}

// newFlattenedValue returns a Value encoder. newFlattenedValue does NOT write the start element tag
func newFlattenedValue(w writer, scratch *[]byte, startElement StartElement) Value {
	return Value{
		w:            w,
		scratch:      scratch,
		startElement: startElement,
	}
}

// newValue writes the start element xml tag and returns a Value
func newValue(w writer, scratch *[]byte, startElement StartElement) Value {
	writeStartElement(w, startElement)
	return Value{w: w, scratch: scratch, startElement: startElement}
}

// writeStartElement takes in a start element and writes it.
// It handles namespace, attributes in start element.
func writeStartElement(w writer, el StartElement) error {
	if el.isZero() {
		return fmt.Errorf("xml start element cannot be nil")
	}

	w.WriteRune(leftAngleBracket)

	if len(el.Name.Space) != 0 {
		escapeString(w, el.Name.Space)
		w.WriteRune(colon)
	}
	escapeString(w, el.Name.Local)
	for _, attr := range el.Attr {
		w.WriteRune(' ')
		writeAttribute(w, &attr)
	}

	w.WriteRune(rightAngleBracket)
	return nil
}

// writeAttribute writes an attribute from a provided Attribute
// For a namespace attribute, the attr.Name.Space must be defined as "xmlns".
// https://www.w3.org/TR/REC-xml-names/#NT-DefaultAttName
func writeAttribute(w writer, attr *Attr) {
	// if local, space both are not empty
	if len(attr.Name.Space) != 0 && len(attr.Name.Local) != 0 {
		escapeString(w, attr.Name.Space)
		w.WriteRune(colon)
	}

	// if prefix is empty, the default `xmlns` space should be used as prefix.
	if len(attr.Name.Local) == 0 {
		attr.Name.Local = attr.Name.Space
	}

	escapeString(w, attr.Name.Local)
	w.WriteRune(equals)
	w.WriteRune(quote)
	escapeString(w, attr.Value)
	w.WriteRune(quote)
}

// writeEndElement takes in a end element and writes it.
func writeEndElement(w writer, el EndElement) error {
	if el.isZero() {
		return fmt.Errorf("xml end element cannot be nil")
	}

	w.WriteRune(leftAngleBracket)
	w.WriteRune(forwardSlash)

	if len(el.Name.Space) != 0 {
		escapeString(w, el.Name.Space)
		w.WriteRune(colon)
	}
	escapeString(w, el.Name.Local)
	w.WriteRune(rightAngleBracket)

	return nil
}

// String encodes v as a XML string.
// It will auto close the parent xml element tag.
func (xv Value) String(v string) {
	escapeString(xv.w, v)
	xv.Close()
}

// Byte encodes v as a XML number.
// It will auto close the parent xml element tag.
func (xv Value) Byte(v int8) {
	xv.Long(int64(v))
}

// Short encodes v as a XML number.
// It will auto close the parent xml element tag.
func (xv Value) Short(v int16) {
	xv.Long(int64(v))
}

// Integer encodes v as a XML number.
// It will auto close the parent xml element tag.
func (xv Value) Integer(v int32) {
	xv.Long(int64(v))
}

// Long encodes v as a XML number.
// It will auto close the parent xml element tag.
func (xv Value) Long(v int64) {
	*xv.scratch = strconv.AppendInt((*xv.scratch)[:0], v, 10)
	xv.w.Write(*xv.scratch)

	xv.Close()
}

// Float encodes v as a XML number.
// It will auto close the parent xml element tag.
func (xv Value) Float(v float32) {
	xv.float(float64(v), 32)
	xv.Close()
}

// Double encodes v as a XML number.
// It will auto close the parent xml element tag.
func (xv Value) Double(v float64) {
	xv.float(v, 64)
	xv.Close()
}

func (xv Value) float(v float64, bits int) {
	*xv.scratch = encoding.EncodeFloat((*xv.scratch)[:0], v, bits)
	xv.w.Write(*xv.scratch)
}

// Boolean encodes v as a XML boolean.
// It will auto close the parent xml element tag.
func (xv Value) Boolean(v bool) {
	*xv.scratch = strconv.AppendBool((*xv.scratch)[:0], v)
	xv.w.Write(*xv.scratch)

	xv.Close()
}

// Base64EncodeBytes writes v as a base64 value in XML string.
// It will auto close the parent xml element tag.
func (xv Value) Base64EncodeBytes(v []byte) {
	encodeByteSlice(xv.w, (*xv.scratch)[:0], v)
	xv.Close()
}

// BigInteger encodes v big.Int as XML value.
// It will auto close the parent xml element tag.
func (xv Value) BigInteger(v *big.Int) {
	xv.w.Write([]byte(v.Text(10)))
	xv.Close()
}

// BigDecimal encodes v big.Float as XML value.
// It will auto close the parent xml element tag.
func (xv Value) BigDecimal(v *big.Float) {
	if i, accuracy := v.Int64(); accuracy == big.Exact {
		xv.Long(i)
		return
	}

	xv.w.Write([]byte(v.Text('e', -1)))
	xv.Close()
}

// Write writes v directly to the xml document
// if escapeXMLText is set to true, write will escape text.
// It will auto close the parent xml element tag.
func (xv Value) Write(v []byte, escapeXMLText bool) {
	// escape and write xml text
	if escapeXMLText {
		escapeText(xv.w, v)
	} else {
		// write xml directly
		xv.w.Write(v)
	}

	xv.Close()
}

// MemberElement does member element encoding. It returns a Value.
// Member Element method should be used for all shapes except flattened shapes.
//
// A call to MemberElement will write nested element tags directly using the
// provided start element. The value returned by MemberElement should be closed.
func (xv Value) MemberElement(element StartElement) Value {
	return newValue(xv.w, xv.scratch, element)
}

// FlattenedElement returns flattened element encoding. It returns a Value.
// This method should be used for flattened shapes.
//
// Unlike MemberElement, flattened element will NOT write element tags
// directly for the associated start element.
//
// The value returned by the FlattenedElement does not need to be closed.
func (xv Value) FlattenedElement(element StartElement) Value {
	v := newFlattenedValue(xv.w, xv.scratch, element)
	v.isFlattened = true
	return v
}

// Array returns an array encoder. By default, the members of array are
// wrapped with `<member>` element tag.
// If value is marked as flattened, the start element is used to wrap the members instead of
// the `<member>` element.
func (xv Value) Array() *Array {
	return newArray(xv.w, xv.scratch, arrayMemberWrapper, xv.startElement, xv.isFlattened)
}

/*
ArrayWithCustomName returns an array encoder.

It takes named start element as an argument, the named start element will used to wrap xml array entries.
for eg, `<someList><customName>entry1</customName></someList>`
Here `customName` named start element will be wrapped on each array member.
*/
func (xv Value) ArrayWithCustomName(element StartElement) *Array {
	return newArray(xv.w, xv.scratch, element, xv.startElement, xv.isFlattened)
}

/*
Map returns a map encoder. By default, the map entries are
wrapped with `<entry>` element tag.

If value is marked as flattened, the start element is used to wrap the entry instead of
the `<member>` element.
*/
func (xv Value) Map() *Map {
	// flattened map
	if xv.isFlattened {
		return newFlattenedMap(xv.w, xv.scratch, xv.startElement)
	}

	// un-flattened map
	return newMap(xv.w, xv.scratch)
}

// encodeByteSlice is modified copy of json encoder's encodeByteSlice.
// It is used to base64 encode a byte slice.
func encodeByteSlice(w writer, scratch []byte, v []byte) {
	if v == nil {
		return
	}

	encodedLen := base64.StdEncoding.EncodedLen(len(v))
	if encodedLen <= len(scratch) {
		// If the encoded bytes fit in e.scratch, avoid an extra
		// allocation and use the cheaper Encoding.Encode.
		dst := scratch[:encodedLen]
		base64.StdEncoding.Encode(dst, v)
		w.Write(dst)
	} else if encodedLen <= 1024 {
		// The encoded bytes are short enough to allocate for, and
		// Encoding.Encode is still cheaper.
		dst := make([]byte, encodedLen)
		base64.StdEncoding.Encode(dst, v)
		w.Write(dst)
	} else {
		// The encoded bytes are too long to cheaply allocate, and
		// Encoding.Encode is no longer noticeably cheaper.
		enc := base64.NewEncoder(base64.StdEncoding, w)
		enc.Write(v)
		enc.Close()
	}
}

// IsFlattened returns true if value is for flattened shape.
func (xv Value) IsFlattened() bool {
	return xv.isFlattened
}

// Close closes the value.
func (xv Value) Close() {
	writeEndElement(xv.w, xv.startElement.End())
}