xml_generator.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. package plist
  2. import (
  3. "bufio"
  4. "encoding/base64"
  5. "encoding/xml"
  6. "io"
  7. "math"
  8. "strconv"
  9. "time"
  10. )
  11. const (
  12. xmlHEADER string = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
  13. xmlDOCTYPE = `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">` + "\n"
  14. xmlArrayTag = "array"
  15. xmlDataTag = "data"
  16. xmlDateTag = "date"
  17. xmlDictTag = "dict"
  18. xmlFalseTag = "false"
  19. xmlIntegerTag = "integer"
  20. xmlKeyTag = "key"
  21. xmlPlistTag = "plist"
  22. xmlRealTag = "real"
  23. xmlStringTag = "string"
  24. xmlTrueTag = "true"
  25. // magic value used in the XML encoding of UIDs
  26. // (stored as a dictionary mapping CF$UID->integer)
  27. xmlCFUIDMagic = "CF$UID"
  28. )
  29. func formatXMLFloat(f float64) string {
  30. switch {
  31. case math.IsInf(f, 1):
  32. return "inf"
  33. case math.IsInf(f, -1):
  34. return "-inf"
  35. case math.IsNaN(f):
  36. return "nan"
  37. }
  38. return strconv.FormatFloat(f, 'g', -1, 64)
  39. }
  40. type xmlPlistGenerator struct {
  41. *bufio.Writer
  42. indent string
  43. depth int
  44. putNewline bool
  45. }
  46. func (p *xmlPlistGenerator) generateDocument(root cfValue) {
  47. p.WriteString(xmlHEADER)
  48. p.WriteString(xmlDOCTYPE)
  49. p.openTag(`plist version="1.0"`)
  50. p.writePlistValue(root)
  51. p.closeTag(xmlPlistTag)
  52. p.Flush()
  53. }
  54. func (p *xmlPlistGenerator) openTag(n string) {
  55. p.writeIndent(1)
  56. p.WriteByte('<')
  57. p.WriteString(n)
  58. p.WriteByte('>')
  59. }
  60. func (p *xmlPlistGenerator) closeTag(n string) {
  61. p.writeIndent(-1)
  62. p.WriteString("</")
  63. p.WriteString(n)
  64. p.WriteByte('>')
  65. }
  66. func (p *xmlPlistGenerator) element(n string, v string) {
  67. p.writeIndent(0)
  68. if len(v) == 0 {
  69. p.WriteByte('<')
  70. p.WriteString(n)
  71. p.WriteString("/>")
  72. } else {
  73. p.WriteByte('<')
  74. p.WriteString(n)
  75. p.WriteByte('>')
  76. err := xml.EscapeText(p.Writer, []byte(v))
  77. if err != nil {
  78. panic(err)
  79. }
  80. p.WriteString("</")
  81. p.WriteString(n)
  82. p.WriteByte('>')
  83. }
  84. }
  85. func (p *xmlPlistGenerator) writeDictionary(dict *cfDictionary) {
  86. dict.sort()
  87. p.openTag(xmlDictTag)
  88. for i, k := range dict.keys {
  89. p.element(xmlKeyTag, k)
  90. p.writePlistValue(dict.values[i])
  91. }
  92. p.closeTag(xmlDictTag)
  93. }
  94. func (p *xmlPlistGenerator) writeArray(a *cfArray) {
  95. p.openTag(xmlArrayTag)
  96. for _, v := range a.values {
  97. p.writePlistValue(v)
  98. }
  99. p.closeTag(xmlArrayTag)
  100. }
  101. func (p *xmlPlistGenerator) writePlistValue(pval cfValue) {
  102. if pval == nil {
  103. return
  104. }
  105. switch pval := pval.(type) {
  106. case cfString:
  107. p.element(xmlStringTag, string(pval))
  108. case *cfNumber:
  109. if pval.signed {
  110. p.element(xmlIntegerTag, strconv.FormatInt(int64(pval.value), 10))
  111. } else {
  112. p.element(xmlIntegerTag, strconv.FormatUint(pval.value, 10))
  113. }
  114. case *cfReal:
  115. p.element(xmlRealTag, formatXMLFloat(pval.value))
  116. case cfBoolean:
  117. if bool(pval) {
  118. p.element(xmlTrueTag, "")
  119. } else {
  120. p.element(xmlFalseTag, "")
  121. }
  122. case cfData:
  123. p.element(xmlDataTag, base64.StdEncoding.EncodeToString([]byte(pval)))
  124. case cfDate:
  125. p.element(xmlDateTag, time.Time(pval).In(time.UTC).Format(time.RFC3339))
  126. case *cfDictionary:
  127. p.writeDictionary(pval)
  128. case *cfArray:
  129. p.writeArray(pval)
  130. case cfUID:
  131. p.openTag(xmlDictTag)
  132. p.element(xmlKeyTag, xmlCFUIDMagic)
  133. p.element(xmlIntegerTag, strconv.FormatUint(uint64(pval), 10))
  134. p.closeTag(xmlDictTag)
  135. }
  136. }
  137. func (p *xmlPlistGenerator) writeIndent(delta int) {
  138. if len(p.indent) == 0 {
  139. return
  140. }
  141. if delta < 0 {
  142. p.depth--
  143. }
  144. if p.putNewline {
  145. // from encoding/xml/marshal.go; it seems to be intended
  146. // to suppress the first newline.
  147. p.WriteByte('\n')
  148. } else {
  149. p.putNewline = true
  150. }
  151. for i := 0; i < p.depth; i++ {
  152. p.WriteString(p.indent)
  153. }
  154. if delta > 0 {
  155. p.depth++
  156. }
  157. }
  158. func (p *xmlPlistGenerator) Indent(i string) {
  159. p.indent = i
  160. }
  161. func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator {
  162. return &xmlPlistGenerator{Writer: bufio.NewWriter(w)}
  163. }