value.go (8320B)
1 package xml 2 3 import ( 4 "encoding/base64" 5 "fmt" 6 "math/big" 7 "strconv" 8 9 "github.com/aws/smithy-go/encoding" 10 ) 11 12 // Value represents an XML Value type 13 // XML Value types: Object, Array, Map, String, Number, Boolean. 14 type Value struct { 15 w writer 16 scratch *[]byte 17 18 // xml start element is the associated start element for the Value 19 startElement StartElement 20 21 // indicates if the Value represents a flattened shape 22 isFlattened bool 23 } 24 25 // newFlattenedValue returns a Value encoder. newFlattenedValue does NOT write the start element tag 26 func newFlattenedValue(w writer, scratch *[]byte, startElement StartElement) Value { 27 return Value{ 28 w: w, 29 scratch: scratch, 30 startElement: startElement, 31 } 32 } 33 34 // newValue writes the start element xml tag and returns a Value 35 func newValue(w writer, scratch *[]byte, startElement StartElement) Value { 36 writeStartElement(w, startElement) 37 return Value{w: w, scratch: scratch, startElement: startElement} 38 } 39 40 // writeStartElement takes in a start element and writes it. 41 // It handles namespace, attributes in start element. 42 func writeStartElement(w writer, el StartElement) error { 43 if el.isZero() { 44 return fmt.Errorf("xml start element cannot be nil") 45 } 46 47 w.WriteRune(leftAngleBracket) 48 49 if len(el.Name.Space) != 0 { 50 escapeString(w, el.Name.Space) 51 w.WriteRune(colon) 52 } 53 escapeString(w, el.Name.Local) 54 for _, attr := range el.Attr { 55 w.WriteRune(' ') 56 writeAttribute(w, &attr) 57 } 58 59 w.WriteRune(rightAngleBracket) 60 return nil 61 } 62 63 // writeAttribute writes an attribute from a provided Attribute 64 // For a namespace attribute, the attr.Name.Space must be defined as "xmlns". 65 // https://www.w3.org/TR/REC-xml-names/#NT-DefaultAttName 66 func writeAttribute(w writer, attr *Attr) { 67 // if local, space both are not empty 68 if len(attr.Name.Space) != 0 && len(attr.Name.Local) != 0 { 69 escapeString(w, attr.Name.Space) 70 w.WriteRune(colon) 71 } 72 73 // if prefix is empty, the default `xmlns` space should be used as prefix. 74 if len(attr.Name.Local) == 0 { 75 attr.Name.Local = attr.Name.Space 76 } 77 78 escapeString(w, attr.Name.Local) 79 w.WriteRune(equals) 80 w.WriteRune(quote) 81 escapeString(w, attr.Value) 82 w.WriteRune(quote) 83 } 84 85 // writeEndElement takes in a end element and writes it. 86 func writeEndElement(w writer, el EndElement) error { 87 if el.isZero() { 88 return fmt.Errorf("xml end element cannot be nil") 89 } 90 91 w.WriteRune(leftAngleBracket) 92 w.WriteRune(forwardSlash) 93 94 if len(el.Name.Space) != 0 { 95 escapeString(w, el.Name.Space) 96 w.WriteRune(colon) 97 } 98 escapeString(w, el.Name.Local) 99 w.WriteRune(rightAngleBracket) 100 101 return nil 102 } 103 104 // String encodes v as a XML string. 105 // It will auto close the parent xml element tag. 106 func (xv Value) String(v string) { 107 escapeString(xv.w, v) 108 xv.Close() 109 } 110 111 // Byte encodes v as a XML number. 112 // It will auto close the parent xml element tag. 113 func (xv Value) Byte(v int8) { 114 xv.Long(int64(v)) 115 } 116 117 // Short encodes v as a XML number. 118 // It will auto close the parent xml element tag. 119 func (xv Value) Short(v int16) { 120 xv.Long(int64(v)) 121 } 122 123 // Integer encodes v as a XML number. 124 // It will auto close the parent xml element tag. 125 func (xv Value) Integer(v int32) { 126 xv.Long(int64(v)) 127 } 128 129 // Long encodes v as a XML number. 130 // It will auto close the parent xml element tag. 131 func (xv Value) Long(v int64) { 132 *xv.scratch = strconv.AppendInt((*xv.scratch)[:0], v, 10) 133 xv.w.Write(*xv.scratch) 134 135 xv.Close() 136 } 137 138 // Float encodes v as a XML number. 139 // It will auto close the parent xml element tag. 140 func (xv Value) Float(v float32) { 141 xv.float(float64(v), 32) 142 xv.Close() 143 } 144 145 // Double encodes v as a XML number. 146 // It will auto close the parent xml element tag. 147 func (xv Value) Double(v float64) { 148 xv.float(v, 64) 149 xv.Close() 150 } 151 152 func (xv Value) float(v float64, bits int) { 153 *xv.scratch = encoding.EncodeFloat((*xv.scratch)[:0], v, bits) 154 xv.w.Write(*xv.scratch) 155 } 156 157 // Boolean encodes v as a XML boolean. 158 // It will auto close the parent xml element tag. 159 func (xv Value) Boolean(v bool) { 160 *xv.scratch = strconv.AppendBool((*xv.scratch)[:0], v) 161 xv.w.Write(*xv.scratch) 162 163 xv.Close() 164 } 165 166 // Base64EncodeBytes writes v as a base64 value in XML string. 167 // It will auto close the parent xml element tag. 168 func (xv Value) Base64EncodeBytes(v []byte) { 169 encodeByteSlice(xv.w, (*xv.scratch)[:0], v) 170 xv.Close() 171 } 172 173 // BigInteger encodes v big.Int as XML value. 174 // It will auto close the parent xml element tag. 175 func (xv Value) BigInteger(v *big.Int) { 176 xv.w.Write([]byte(v.Text(10))) 177 xv.Close() 178 } 179 180 // BigDecimal encodes v big.Float as XML value. 181 // It will auto close the parent xml element tag. 182 func (xv Value) BigDecimal(v *big.Float) { 183 if i, accuracy := v.Int64(); accuracy == big.Exact { 184 xv.Long(i) 185 return 186 } 187 188 xv.w.Write([]byte(v.Text('e', -1))) 189 xv.Close() 190 } 191 192 // Write writes v directly to the xml document 193 // if escapeXMLText is set to true, write will escape text. 194 // It will auto close the parent xml element tag. 195 func (xv Value) Write(v []byte, escapeXMLText bool) { 196 // escape and write xml text 197 if escapeXMLText { 198 escapeText(xv.w, v) 199 } else { 200 // write xml directly 201 xv.w.Write(v) 202 } 203 204 xv.Close() 205 } 206 207 // MemberElement does member element encoding. It returns a Value. 208 // Member Element method should be used for all shapes except flattened shapes. 209 // 210 // A call to MemberElement will write nested element tags directly using the 211 // provided start element. The value returned by MemberElement should be closed. 212 func (xv Value) MemberElement(element StartElement) Value { 213 return newValue(xv.w, xv.scratch, element) 214 } 215 216 // FlattenedElement returns flattened element encoding. It returns a Value. 217 // This method should be used for flattened shapes. 218 // 219 // Unlike MemberElement, flattened element will NOT write element tags 220 // directly for the associated start element. 221 // 222 // The value returned by the FlattenedElement does not need to be closed. 223 func (xv Value) FlattenedElement(element StartElement) Value { 224 v := newFlattenedValue(xv.w, xv.scratch, element) 225 v.isFlattened = true 226 return v 227 } 228 229 // Array returns an array encoder. By default, the members of array are 230 // wrapped with `<member>` element tag. 231 // If value is marked as flattened, the start element is used to wrap the members instead of 232 // the `<member>` element. 233 func (xv Value) Array() *Array { 234 return newArray(xv.w, xv.scratch, arrayMemberWrapper, xv.startElement, xv.isFlattened) 235 } 236 237 /* 238 ArrayWithCustomName returns an array encoder. 239 240 It takes named start element as an argument, the named start element will used to wrap xml array entries. 241 for eg, `<someList><customName>entry1</customName></someList>` 242 Here `customName` named start element will be wrapped on each array member. 243 */ 244 func (xv Value) ArrayWithCustomName(element StartElement) *Array { 245 return newArray(xv.w, xv.scratch, element, xv.startElement, xv.isFlattened) 246 } 247 248 /* 249 Map returns a map encoder. By default, the map entries are 250 wrapped with `<entry>` element tag. 251 252 If value is marked as flattened, the start element is used to wrap the entry instead of 253 the `<member>` element. 254 */ 255 func (xv Value) Map() *Map { 256 // flattened map 257 if xv.isFlattened { 258 return newFlattenedMap(xv.w, xv.scratch, xv.startElement) 259 } 260 261 // un-flattened map 262 return newMap(xv.w, xv.scratch) 263 } 264 265 // encodeByteSlice is modified copy of json encoder's encodeByteSlice. 266 // It is used to base64 encode a byte slice. 267 func encodeByteSlice(w writer, scratch []byte, v []byte) { 268 if v == nil { 269 return 270 } 271 272 encodedLen := base64.StdEncoding.EncodedLen(len(v)) 273 if encodedLen <= len(scratch) { 274 // If the encoded bytes fit in e.scratch, avoid an extra 275 // allocation and use the cheaper Encoding.Encode. 276 dst := scratch[:encodedLen] 277 base64.StdEncoding.Encode(dst, v) 278 w.Write(dst) 279 } else if encodedLen <= 1024 { 280 // The encoded bytes are short enough to allocate for, and 281 // Encoding.Encode is still cheaper. 282 dst := make([]byte, encodedLen) 283 base64.StdEncoding.Encode(dst, v) 284 w.Write(dst) 285 } else { 286 // The encoded bytes are too long to cheaply allocate, and 287 // Encoding.Encode is no longer noticeably cheaper. 288 enc := base64.NewEncoder(base64.StdEncoding, w) 289 enc.Write(v) 290 enc.Close() 291 } 292 } 293 294 // IsFlattened returns true if value is for flattened shape. 295 func (xv Value) IsFlattened() bool { 296 return xv.isFlattened 297 } 298 299 // Close closes the value. 300 func (xv Value) Close() { 301 writeEndElement(xv.w, xv.startElement.End()) 302 }