value.go raw

   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  }
 303