xml.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. // Copyright 2012-2016 xiaolipeng. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file
  4. // xml.go - basically the core of X2j for map[string]interface{} values.
  5. // NewMapXml, NewMapXmlReader, mv.Xml, mv.XmlWriter
  6. // see x2j and j2x for wrappers to provide end-to-end transformation of XML and JSON messages.
  7. package anyxml
  8. import (
  9. "encoding/xml"
  10. "errors"
  11. "fmt"
  12. "html/template"
  13. "time"
  14. )
  15. // --------------------------------- Xml, XmlIndent - from mxj -------------------------------
  16. const (
  17. DefaultRootTag = "doc"
  18. UseGoEmptyElementSyntax = false // if 'true' encode empty element as "<tag></tag>" instead of "<tag/>
  19. )
  20. // From: github.com/clbanning/mxj/xml.go with functions relabled: Xml() --> anyxml().
  21. // Encode a Map as XML. The companion of NewMapXml().
  22. // The following rules apply.
  23. // - The key label "#text" is treated as the value for a simple element with attributes.
  24. // - Map keys that begin with a hyphen, '-', are interpreted as attributes.
  25. // It is an error if the attribute doesn't have a []byte, string, number, or boolean value.
  26. // - Map value type encoding:
  27. // > string, bool, float64, int, int32, int64, float32: per "%v" formating
  28. // > []bool, []uint8: by casting to string
  29. // > structures, etc.: handed to xml.Marshal() - if there is an error, the element
  30. // value is "UNKNOWN"
  31. // - Elements with only attribute values or are null are terminated using "/>".
  32. // - If len(mv) == 1 and no rootTag is provided, then the map key is used as the root tag, possible.
  33. // Thus, `{ "key":"value" }` encodes as "<key>value</key>".
  34. // - To encode empty elements in a syntax consistent with encoding/xml call UseGoXmlEmptyElementSyntax().
  35. func anyxml(m map[string]interface{}, rootTag ...string) ([]byte, error) {
  36. var err error
  37. s := new(string)
  38. p := new(pretty) // just a stub
  39. if len(m) == 1 && len(rootTag) == 0 {
  40. for key, value := range m {
  41. // if it an array, see if all values are map[string]interface{}
  42. // we force a new root tag if we'll end up with no key:value in the list
  43. // so: key:[string_val, bool:true] --> <doc><key>string_val</key><bool>true</bool></key></doc>
  44. switch value.(type) {
  45. case []interface{}:
  46. for _, v := range value.([]interface{}) {
  47. switch v.(type) {
  48. case map[string]interface{}: // noop
  49. default: // anything else
  50. err = mapToXmlIndent(false, s, DefaultRootTag, m, p)
  51. goto done
  52. }
  53. }
  54. }
  55. err = mapToXmlIndent(false, s, key, value, p)
  56. }
  57. } else if len(rootTag) == 1 {
  58. err = mapToXmlIndent(false, s, rootTag[0], m, p)
  59. } else {
  60. err = mapToXmlIndent(false, s, DefaultRootTag, m, p)
  61. }
  62. done:
  63. return []byte(*s), err
  64. }
  65. func anyxmlWithDateFormat(dateFormat string, m map[string]interface{}, rootTag ...string) ([]byte, error) {
  66. var err error
  67. s := new(string)
  68. p := new(pretty) // just a stub
  69. if len(m) == 1 && len(rootTag) == 0 {
  70. for key, value := range m {
  71. // if it an array, see if all values are map[string]interface{}
  72. // we force a new root tag if we'll end up with no key:value in the list
  73. // so: key:[string_val, bool:true] --> <doc><key>string_val</key><bool>true</bool></key></doc>
  74. switch value.(type) {
  75. case []interface{}:
  76. for _, v := range value.([]interface{}) {
  77. switch v.(type) {
  78. case map[string]interface{}: // noop
  79. default: // anything else
  80. err = mapToXmlIndentWithDateFormat(dateFormat, false, s, DefaultRootTag, m, p)
  81. goto done
  82. }
  83. }
  84. }
  85. err = mapToXmlIndentWithDateFormat(dateFormat, false, s, key, value, p)
  86. }
  87. } else if len(rootTag) == 1 {
  88. err = mapToXmlIndentWithDateFormat(dateFormat, false, s, rootTag[0], m, p)
  89. } else {
  90. err = mapToXmlIndentWithDateFormat(dateFormat, false, s, DefaultRootTag, m, p)
  91. }
  92. done:
  93. return []byte(*s), err
  94. }
  95. func anyxmlIndentWithDateFormat(dateFormat string, m map[string]interface{}, prefix string, indent string, rootTag ...string) ([]byte, error) {
  96. var err error
  97. s := new(string)
  98. p := new(pretty) // just a stub
  99. if len(m) == 1 && len(rootTag) == 0 {
  100. for key, value := range m {
  101. // if it an array, see if all values are map[string]interface{}
  102. // we force a new root tag if we'll end up with no key:value in the list
  103. // so: key:[string_val, bool:true] --> <doc><key>string_val</key><bool>true</bool></key></doc>
  104. switch value.(type) {
  105. case []interface{}:
  106. for _, v := range value.([]interface{}) {
  107. switch v.(type) {
  108. case map[string]interface{}: // noop
  109. default: // anything else
  110. err = mapToXmlIndentWithDateFormat(dateFormat, true, s, DefaultRootTag, m, p)
  111. goto done
  112. }
  113. }
  114. }
  115. err = mapToXmlIndentWithDateFormat(dateFormat, true, s, key, value, p)
  116. }
  117. } else if len(rootTag) == 1 {
  118. err = mapToXmlIndentWithDateFormat(dateFormat, true, s, rootTag[0], m, p)
  119. } else {
  120. err = mapToXmlIndentWithDateFormat(dateFormat, true, s, DefaultRootTag, m, p)
  121. }
  122. done:
  123. return []byte(*s), err
  124. }
  125. // Encode a map[string]interface{} as a pretty XML string.
  126. // See Xml for encoding rules.
  127. func anyxmlIndent(m map[string]interface{}, prefix string, indent string, rootTag ...string) ([]byte, error) {
  128. var err error
  129. s := new(string)
  130. p := new(pretty)
  131. p.indent = indent
  132. p.padding = prefix
  133. if len(m) == 1 && len(rootTag) == 0 {
  134. // this can extract the key for the single map element
  135. // use it if it isn't a key for a list
  136. for key, value := range m {
  137. if _, ok := value.([]interface{}); ok {
  138. err = mapToXmlIndent(true, s, DefaultRootTag, m, p)
  139. } else {
  140. err = mapToXmlIndent(true, s, key, value, p)
  141. }
  142. }
  143. } else if len(rootTag) == 1 {
  144. err = mapToXmlIndent(true, s, rootTag[0], m, p)
  145. } else {
  146. err = mapToXmlIndent(true, s, DefaultRootTag, m, p)
  147. }
  148. return []byte(*s), err
  149. }
  150. type pretty struct {
  151. indent string
  152. cnt int
  153. padding string
  154. mapDepth int
  155. start int
  156. }
  157. func (p *pretty) Indent() {
  158. p.padding += p.indent
  159. p.cnt++
  160. }
  161. func (p *pretty) Outdent() {
  162. if p.cnt > 0 {
  163. p.padding = p.padding[:len(p.padding)-len(p.indent)]
  164. p.cnt--
  165. }
  166. }
  167. // where the work actually happens
  168. // returns an error if an attribute is not atomic
  169. func mapToXmlIndent(doIndent bool, s *string, key string, value interface{}, pp *pretty) error {
  170. var endTag bool
  171. var isSimple bool
  172. p := &pretty{pp.indent, pp.cnt, pp.padding, pp.mapDepth, pp.start}
  173. key = template.HTMLEscapeString(key)
  174. switch value.(type) {
  175. case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32:
  176. if doIndent {
  177. *s += p.padding
  178. }
  179. *s += `<` + key
  180. }
  181. switch value.(type) {
  182. case map[string]interface{}:
  183. vv := value.(map[string]interface{})
  184. lenvv := len(vv)
  185. // scan out attributes - keys have prepended hyphen, '-'
  186. var cntAttr int
  187. for k, v := range vv {
  188. if k[:1] == "-" {
  189. switch v.(type) {
  190. case string:
  191. *s += ` ` + k[1:] + `="` + fmt.Sprintf("%v", template.HTMLEscapeString(v.(string))) + `"`
  192. cntAttr++
  193. case float64, bool, int, int32, int64, float32:
  194. *s += ` ` + k[1:] + `="` + fmt.Sprintf("%v", v) + `"`
  195. cntAttr++
  196. case []byte: // allow standard xml pkg []byte transform, as below
  197. *s += ` ` + k[1:] + `="` + fmt.Sprintf("%v", string(v.([]byte))) + `"`
  198. cntAttr++
  199. default:
  200. return fmt.Errorf("invalid attribute value for: %s", k)
  201. }
  202. }
  203. }
  204. // only attributes?
  205. if cntAttr == lenvv {
  206. break
  207. }
  208. // simple element? Note: '#text" is an invalid XML tag.
  209. if v, ok := vv["#text"]; ok {
  210. if cntAttr+1 < lenvv {
  211. return errors.New("#text key occurs with other non-attribute keys")
  212. }
  213. *s += ">" + fmt.Sprintf("%v", v)
  214. endTag = true
  215. break
  216. }
  217. // close tag with possible attributes
  218. *s += ">"
  219. if doIndent {
  220. *s += "\n"
  221. }
  222. // something more complex
  223. p.mapDepth++
  224. var i int
  225. for k, v := range vv {
  226. if k[:1] == "-" {
  227. continue
  228. }
  229. switch v.(type) {
  230. case []interface{}:
  231. default:
  232. if i == 0 && doIndent {
  233. p.Indent()
  234. }
  235. }
  236. i++
  237. mapToXmlIndent(doIndent, s, k, v, p)
  238. switch v.(type) {
  239. case []interface{}: // handled in []interface{} case
  240. default:
  241. if doIndent {
  242. p.Outdent()
  243. }
  244. }
  245. i--
  246. }
  247. p.mapDepth--
  248. endTag = true
  249. case []interface{}:
  250. for _, v := range value.([]interface{}) {
  251. if doIndent {
  252. p.Indent()
  253. }
  254. mapToXmlIndent(doIndent, s, key, v, p)
  255. if doIndent {
  256. p.Outdent()
  257. }
  258. }
  259. return nil
  260. case nil:
  261. // terminate the tag
  262. *s += "<" + key
  263. break
  264. default: // handle anything - even goofy stuff
  265. switch value.(type) {
  266. case string:
  267. *s += ">" + fmt.Sprintf("%v", template.HTMLEscapeString(value.(string)))
  268. case float64, bool, int, int32, int64, float32:
  269. *s += ">" + fmt.Sprintf("%v", value)
  270. case []byte: // NOTE: byte is just an alias for uint8
  271. // similar to how xml.Marshal handles []byte structure members
  272. *s += ">" + string(value.([]byte))
  273. default:
  274. var v []byte
  275. var err error
  276. if doIndent {
  277. v, err = xml.MarshalIndent(value, p.padding, p.indent)
  278. } else {
  279. v, err = xml.Marshal(value)
  280. }
  281. if err != nil {
  282. *s += ">UNKNOWN"
  283. } else {
  284. *s += string(v)
  285. }
  286. }
  287. isSimple = true
  288. endTag = true
  289. }
  290. if endTag {
  291. if doIndent {
  292. if !isSimple {
  293. // if p.mapDepth == 0 {
  294. // p.Outdent()
  295. // }
  296. *s += p.padding
  297. }
  298. }
  299. switch value.(type) {
  300. case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32:
  301. *s += `</` + key + ">"
  302. }
  303. } else if UseGoEmptyElementSyntax {
  304. *s += "></" + key + ">"
  305. } else {
  306. *s += "/>"
  307. }
  308. if doIndent {
  309. if p.cnt > p.start {
  310. *s += "\n"
  311. }
  312. p.Outdent()
  313. }
  314. return nil
  315. }
  316. // where the work actually happens
  317. // returns an error if an attribute is not atomic
  318. func mapToXmlIndentWithDateFormat(dateFormat string, doIndent bool, s *string, key string, value interface{}, pp *pretty) error {
  319. var endTag bool
  320. var isSimple bool
  321. p := &pretty{pp.indent, pp.cnt, pp.padding, pp.mapDepth, pp.start}
  322. key = template.HTMLEscapeString(key)
  323. //start tag
  324. switch value.(type) {
  325. case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32, time.Time:
  326. if doIndent {
  327. *s += p.padding
  328. }
  329. *s += `<` + key
  330. }
  331. switch value.(type) {
  332. case map[string]interface{}:
  333. vv := value.(map[string]interface{})
  334. lenvv := len(vv)
  335. var cntAttr int
  336. for k, v := range vv {
  337. if k[:1] == "-" {
  338. switch v.(type) {
  339. case string:
  340. *s += ` ` + k[1:] + `="` + fmt.Sprintf("%v", template.HTMLEscapeString(v.(string))) + `"`
  341. cntAttr++
  342. case float64, bool, int, int32, int64, float32:
  343. *s += ` ` + k[1:] + `="` + fmt.Sprintf("%v", v) + `"`
  344. cntAttr++
  345. case []byte: // allow standard xml pkg []byte transform, as below
  346. *s += ` ` + k[1:] + `="` + fmt.Sprintf("%v", string(v.([]byte))) + `"`
  347. cntAttr++
  348. default:
  349. return fmt.Errorf("invalid attribute value for: %s", k)
  350. }
  351. }
  352. }
  353. // only attributes?
  354. if cntAttr == lenvv {
  355. break
  356. }
  357. // simple element? Note: '#text" is an invalid XML tag.
  358. if v, ok := vv["#text"]; ok {
  359. if cntAttr+1 < lenvv {
  360. return errors.New("#text key occurs with other non-attribute keys")
  361. }
  362. *s += ">" + fmt.Sprintf("%v", v)
  363. endTag = true
  364. break
  365. }
  366. // close tag with possible attributes
  367. *s += ">"
  368. if doIndent {
  369. *s += "\n"
  370. }
  371. // something more complex
  372. p.mapDepth++
  373. var i int
  374. for k, v := range vv {
  375. if k[:1] == "-" {
  376. continue
  377. }
  378. switch v.(type) {
  379. case []interface{}:
  380. default:
  381. if i == 0 && doIndent {
  382. p.Indent()
  383. }
  384. }
  385. i++
  386. mapToXmlIndentWithDateFormat(dateFormat, doIndent, s, k, v, p)
  387. switch v.(type) {
  388. case []interface{}: // handled in []interface{} case
  389. default:
  390. if doIndent {
  391. p.Outdent()
  392. }
  393. }
  394. i--
  395. }
  396. p.mapDepth--
  397. endTag = true
  398. case []interface{}:
  399. for _, v := range value.([]interface{}) {
  400. if doIndent {
  401. p.Indent()
  402. }
  403. mapToXmlIndentWithDateFormat(dateFormat, doIndent, s, key, v, p)
  404. if doIndent {
  405. p.Outdent()
  406. }
  407. }
  408. return nil
  409. case nil:
  410. *s += "<" + key
  411. break
  412. default: // handle anything - even goofy stuff
  413. switch value.(type) {
  414. case string:
  415. *s += ">" + fmt.Sprintf("%v", template.HTMLEscapeString(value.(string)))
  416. case float64, bool, int, int32, int64, float32:
  417. *s += ">" + fmt.Sprintf("%v", value)
  418. case []byte: // NOTE: byte is just an alias for uint8
  419. // similar to how xml.Marshal handles []byte structure members
  420. *s += ">" + string(value.([]byte))
  421. case time.Time:
  422. *s += ">" + (value.(time.Time)).Format(dateFormat)
  423. default:
  424. var v []byte
  425. var err error
  426. if doIndent {
  427. v, err = xml.MarshalIndent(value, p.padding, p.indent)
  428. } else {
  429. v, err = xml.Marshal(value)
  430. }
  431. if err != nil {
  432. *s += ">UNKNOWN"
  433. } else {
  434. *s += string(v)
  435. }
  436. }
  437. isSimple = true
  438. endTag = true
  439. }
  440. if endTag {
  441. if doIndent {
  442. if !isSimple {
  443. // if p.mapDepth == 0 {
  444. // p.Outdent()
  445. // }
  446. *s += p.padding
  447. }
  448. }
  449. switch value.(type) {
  450. case map[string]interface{}, []byte, string, float64, bool, int, int32, int64, float32, time.Time:
  451. *s += `</` + key + ">"
  452. }
  453. } else if UseGoEmptyElementSyntax {
  454. *s += "></" + key + ">"
  455. } else {
  456. *s += "/>"
  457. }
  458. if doIndent {
  459. if p.cnt > p.start {
  460. *s += "\n"
  461. }
  462. p.Outdent()
  463. }
  464. return nil
  465. }