text_generator.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package plist
  2. import (
  3. "encoding/hex"
  4. "io"
  5. "strconv"
  6. "time"
  7. )
  8. type textPlistGenerator struct {
  9. writer io.Writer
  10. format int
  11. quotableTable *characterSet
  12. indent string
  13. depth int
  14. dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte
  15. }
  16. var (
  17. textPlistTimeLayout = "2006-01-02 15:04:05 -0700"
  18. padding = "0000"
  19. )
  20. func (p *textPlistGenerator) generateDocument(pval cfValue) {
  21. p.writePlistValue(pval)
  22. }
  23. func (p *textPlistGenerator) plistQuotedString(str string) string {
  24. if str == "" {
  25. return `""`
  26. }
  27. s := ""
  28. quot := false
  29. for _, r := range str {
  30. if r > 0xFF {
  31. quot = true
  32. s += `\U`
  33. us := strconv.FormatInt(int64(r), 16)
  34. s += padding[len(us):]
  35. s += us
  36. } else if r > 0x7F {
  37. quot = true
  38. s += `\`
  39. us := strconv.FormatInt(int64(r), 8)
  40. s += padding[1+len(us):]
  41. s += us
  42. } else {
  43. c := uint8(r)
  44. if p.quotableTable.ContainsByte(c) {
  45. quot = true
  46. }
  47. switch c {
  48. case '\a':
  49. s += `\a`
  50. case '\b':
  51. s += `\b`
  52. case '\v':
  53. s += `\v`
  54. case '\f':
  55. s += `\f`
  56. case '\\':
  57. s += `\\`
  58. case '"':
  59. s += `\"`
  60. case '\t', '\r', '\n':
  61. fallthrough
  62. default:
  63. s += string(c)
  64. }
  65. }
  66. }
  67. if quot {
  68. s = `"` + s + `"`
  69. }
  70. return s
  71. }
  72. func (p *textPlistGenerator) deltaIndent(depthDelta int) {
  73. if depthDelta < 0 {
  74. p.depth--
  75. } else if depthDelta > 0 {
  76. p.depth++
  77. }
  78. }
  79. func (p *textPlistGenerator) writeIndent() {
  80. if len(p.indent) == 0 {
  81. return
  82. }
  83. if len(p.indent) > 0 {
  84. p.writer.Write([]byte("\n"))
  85. for i := 0; i < p.depth; i++ {
  86. io.WriteString(p.writer, p.indent)
  87. }
  88. }
  89. }
  90. func (p *textPlistGenerator) writePlistValue(pval cfValue) {
  91. if pval == nil {
  92. return
  93. }
  94. switch pval := pval.(type) {
  95. case *cfDictionary:
  96. pval.sort()
  97. p.writer.Write([]byte(`{`))
  98. p.deltaIndent(1)
  99. for i, k := range pval.keys {
  100. p.writeIndent()
  101. io.WriteString(p.writer, p.plistQuotedString(k))
  102. p.writer.Write(p.dictKvDelimiter)
  103. p.writePlistValue(pval.values[i])
  104. p.writer.Write(p.dictEntryDelimiter)
  105. }
  106. p.deltaIndent(-1)
  107. p.writeIndent()
  108. p.writer.Write([]byte(`}`))
  109. case *cfArray:
  110. p.writer.Write([]byte(`(`))
  111. p.deltaIndent(1)
  112. for _, v := range pval.values {
  113. p.writeIndent()
  114. p.writePlistValue(v)
  115. p.writer.Write(p.arrayDelimiter)
  116. }
  117. p.deltaIndent(-1)
  118. p.writeIndent()
  119. p.writer.Write([]byte(`)`))
  120. case cfString:
  121. io.WriteString(p.writer, p.plistQuotedString(string(pval)))
  122. case *cfNumber:
  123. if p.format == GNUStepFormat {
  124. p.writer.Write([]byte(`<*I`))
  125. }
  126. if pval.signed {
  127. io.WriteString(p.writer, strconv.FormatInt(int64(pval.value), 10))
  128. } else {
  129. io.WriteString(p.writer, strconv.FormatUint(pval.value, 10))
  130. }
  131. if p.format == GNUStepFormat {
  132. p.writer.Write([]byte(`>`))
  133. }
  134. case *cfReal:
  135. if p.format == GNUStepFormat {
  136. p.writer.Write([]byte(`<*R`))
  137. }
  138. // GNUstep does not differentiate between 32/64-bit floats.
  139. io.WriteString(p.writer, strconv.FormatFloat(pval.value, 'g', -1, 64))
  140. if p.format == GNUStepFormat {
  141. p.writer.Write([]byte(`>`))
  142. }
  143. case cfBoolean:
  144. if p.format == GNUStepFormat {
  145. if pval {
  146. p.writer.Write([]byte(`<*BY>`))
  147. } else {
  148. p.writer.Write([]byte(`<*BN>`))
  149. }
  150. } else {
  151. if pval {
  152. p.writer.Write([]byte(`1`))
  153. } else {
  154. p.writer.Write([]byte(`0`))
  155. }
  156. }
  157. case cfData:
  158. var hexencoded [9]byte
  159. var l int
  160. var asc = 9
  161. hexencoded[8] = ' '
  162. p.writer.Write([]byte(`<`))
  163. b := []byte(pval)
  164. for i := 0; i < len(b); i += 4 {
  165. l = i + 4
  166. if l >= len(b) {
  167. l = len(b)
  168. // We no longer need the space - or the rest of the buffer.
  169. // (we used >= above to get this part without another conditional :P)
  170. asc = (l - i) * 2
  171. }
  172. // Fill the buffer (only up to 8 characters, to preserve the space we implicitly include
  173. // at the end of every encode)
  174. hex.Encode(hexencoded[:8], b[i:l])
  175. io.WriteString(p.writer, string(hexencoded[:asc]))
  176. }
  177. p.writer.Write([]byte(`>`))
  178. case cfDate:
  179. if p.format == GNUStepFormat {
  180. p.writer.Write([]byte(`<*D`))
  181. io.WriteString(p.writer, time.Time(pval).In(time.UTC).Format(textPlistTimeLayout))
  182. p.writer.Write([]byte(`>`))
  183. } else {
  184. io.WriteString(p.writer, p.plistQuotedString(time.Time(pval).In(time.UTC).Format(textPlistTimeLayout)))
  185. }
  186. }
  187. }
  188. func (p *textPlistGenerator) Indent(i string) {
  189. p.indent = i
  190. if i == "" {
  191. p.dictKvDelimiter = []byte(`=`)
  192. } else {
  193. // For pretty-printing
  194. p.dictKvDelimiter = []byte(` = `)
  195. }
  196. }
  197. func newTextPlistGenerator(w io.Writer, format int) *textPlistGenerator {
  198. table := &osQuotable
  199. if format == GNUStepFormat {
  200. table = &gsQuotable
  201. }
  202. return &textPlistGenerator{
  203. writer: mustWriter{w},
  204. format: format,
  205. quotableTable: table,
  206. dictKvDelimiter: []byte(`=`),
  207. arrayDelimiter: []byte(`,`),
  208. dictEntryDelimiter: []byte(`;`),
  209. }
  210. }