xml.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Copyright (c) 2021 Tulir Asokan
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package binary
  7. import (
  8. "encoding/hex"
  9. "fmt"
  10. "sort"
  11. "strings"
  12. "unicode"
  13. "unicode/utf8"
  14. )
  15. // Options to control how Node.XMLString behaves.
  16. var (
  17. IndentXML = false
  18. MaxBytesToPrintAsHex = 128
  19. )
  20. // XMLString converts the Node to its XML representation
  21. func (n *Node) XMLString() string {
  22. content := n.contentString()
  23. if len(content) == 0 {
  24. return fmt.Sprintf("<%[1]s%[2]s/>", n.Tag, n.attributeString())
  25. }
  26. newline := "\n"
  27. if len(content) == 1 || !IndentXML {
  28. newline = ""
  29. }
  30. return fmt.Sprintf("<%[1]s%[2]s>%[4]s%[3]s%[4]s</%[1]s>", n.Tag, n.attributeString(), strings.Join(content, newline), newline)
  31. }
  32. func (n *Node) attributeString() string {
  33. if len(n.Attrs) == 0 {
  34. return ""
  35. }
  36. stringAttrs := make([]string, len(n.Attrs)+1)
  37. i := 1
  38. for key, value := range n.Attrs {
  39. stringAttrs[i] = fmt.Sprintf(`%s="%v"`, key, value)
  40. i++
  41. }
  42. sort.Strings(stringAttrs)
  43. return strings.Join(stringAttrs, " ")
  44. }
  45. func printable(data []byte) string {
  46. if !utf8.Valid(data) {
  47. return ""
  48. }
  49. str := string(data)
  50. for _, c := range str {
  51. if !unicode.IsPrint(c) {
  52. return ""
  53. }
  54. }
  55. return str
  56. }
  57. func (n *Node) contentString() []string {
  58. split := make([]string, 0)
  59. switch content := n.Content.(type) {
  60. case []Node:
  61. for _, item := range content {
  62. split = append(split, strings.Split(item.XMLString(), "\n")...)
  63. }
  64. case []byte:
  65. if strContent := printable(content); len(strContent) > 0 {
  66. if IndentXML {
  67. split = append(split, strings.Split(string(content), "\n")...)
  68. } else {
  69. split = append(split, strings.ReplaceAll(string(content), "\n", "\\n"))
  70. }
  71. } else if len(content) > MaxBytesToPrintAsHex {
  72. split = append(split, fmt.Sprintf("<!-- %d bytes -->", len(content)))
  73. } else if !IndentXML {
  74. split = append(split, hex.EncodeToString(content))
  75. } else {
  76. hexData := hex.EncodeToString(content)
  77. for i := 0; i < len(hexData); i += 80 {
  78. if len(hexData) < i+80 {
  79. split = append(split, hexData[i:])
  80. } else {
  81. split = append(split, hexData[i:i+80])
  82. }
  83. }
  84. }
  85. case nil:
  86. // don't append anything
  87. default:
  88. strContent := fmt.Sprintf("%s", content)
  89. if IndentXML {
  90. split = append(split, strings.Split(strContent, "\n")...)
  91. } else {
  92. split = append(split, strings.ReplaceAll(strContent, "\n", "\\n"))
  93. }
  94. }
  95. if len(split) > 1 && IndentXML {
  96. for i, line := range split {
  97. split[i] = " " + line
  98. }
  99. }
  100. return split
  101. }