value.go 8.1 KB

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