code.dwrz.net

Go monorepo.
Log | Files | Refs

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 }