123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- package plist
- import (
- "encoding/hex"
- "io"
- "strconv"
- "time"
- )
- type textPlistGenerator struct {
- writer io.Writer
- format int
- quotableTable *characterSet
- indent string
- depth int
- dictKvDelimiter, dictEntryDelimiter, arrayDelimiter []byte
- }
- var (
- textPlistTimeLayout = "2006-01-02 15:04:05 -0700"
- padding = "0000"
- )
- func (p *textPlistGenerator) generateDocument(pval cfValue) {
- p.writePlistValue(pval)
- }
- func (p *textPlistGenerator) plistQuotedString(str string) string {
- if str == "" {
- return `""`
- }
- s := ""
- quot := false
- for _, r := range str {
- if r > 0xFF {
- quot = true
- s += `\U`
- us := strconv.FormatInt(int64(r), 16)
- s += padding[len(us):]
- s += us
- } else if r > 0x7F {
- quot = true
- s += `\`
- us := strconv.FormatInt(int64(r), 8)
- s += padding[1+len(us):]
- s += us
- } else {
- c := uint8(r)
- if p.quotableTable.ContainsByte(c) {
- quot = true
- }
- switch c {
- case '\a':
- s += `\a`
- case '\b':
- s += `\b`
- case '\v':
- s += `\v`
- case '\f':
- s += `\f`
- case '\\':
- s += `\\`
- case '"':
- s += `\"`
- case '\t', '\r', '\n':
- fallthrough
- default:
- s += string(c)
- }
- }
- }
- if quot {
- s = `"` + s + `"`
- }
- return s
- }
- func (p *textPlistGenerator) deltaIndent(depthDelta int) {
- if depthDelta < 0 {
- p.depth--
- } else if depthDelta > 0 {
- p.depth++
- }
- }
- func (p *textPlistGenerator) writeIndent() {
- if len(p.indent) == 0 {
- return
- }
- if len(p.indent) > 0 {
- p.writer.Write([]byte("\n"))
- for i := 0; i < p.depth; i++ {
- io.WriteString(p.writer, p.indent)
- }
- }
- }
- func (p *textPlistGenerator) writePlistValue(pval cfValue) {
- if pval == nil {
- return
- }
- switch pval := pval.(type) {
- case *cfDictionary:
- pval.sort()
- p.writer.Write([]byte(`{`))
- p.deltaIndent(1)
- for i, k := range pval.keys {
- p.writeIndent()
- io.WriteString(p.writer, p.plistQuotedString(k))
- p.writer.Write(p.dictKvDelimiter)
- p.writePlistValue(pval.values[i])
- p.writer.Write(p.dictEntryDelimiter)
- }
- p.deltaIndent(-1)
- p.writeIndent()
- p.writer.Write([]byte(`}`))
- case *cfArray:
- p.writer.Write([]byte(`(`))
- p.deltaIndent(1)
- for _, v := range pval.values {
- p.writeIndent()
- p.writePlistValue(v)
- p.writer.Write(p.arrayDelimiter)
- }
- p.deltaIndent(-1)
- p.writeIndent()
- p.writer.Write([]byte(`)`))
- case cfString:
- io.WriteString(p.writer, p.plistQuotedString(string(pval)))
- case *cfNumber:
- if p.format == GNUStepFormat {
- p.writer.Write([]byte(`<*I`))
- }
- if pval.signed {
- io.WriteString(p.writer, strconv.FormatInt(int64(pval.value), 10))
- } else {
- io.WriteString(p.writer, strconv.FormatUint(pval.value, 10))
- }
- if p.format == GNUStepFormat {
- p.writer.Write([]byte(`>`))
- }
- case *cfReal:
- if p.format == GNUStepFormat {
- p.writer.Write([]byte(`<*R`))
- }
- // GNUstep does not differentiate between 32/64-bit floats.
- io.WriteString(p.writer, strconv.FormatFloat(pval.value, 'g', -1, 64))
- if p.format == GNUStepFormat {
- p.writer.Write([]byte(`>`))
- }
- case cfBoolean:
- if p.format == GNUStepFormat {
- if pval {
- p.writer.Write([]byte(`<*BY>`))
- } else {
- p.writer.Write([]byte(`<*BN>`))
- }
- } else {
- if pval {
- p.writer.Write([]byte(`1`))
- } else {
- p.writer.Write([]byte(`0`))
- }
- }
- case cfData:
- var hexencoded [9]byte
- var l int
- var asc = 9
- hexencoded[8] = ' '
- p.writer.Write([]byte(`<`))
- b := []byte(pval)
- for i := 0; i < len(b); i += 4 {
- l = i + 4
- if l >= len(b) {
- l = len(b)
- // We no longer need the space - or the rest of the buffer.
- // (we used >= above to get this part without another conditional :P)
- asc = (l - i) * 2
- }
- // Fill the buffer (only up to 8 characters, to preserve the space we implicitly include
- // at the end of every encode)
- hex.Encode(hexencoded[:8], b[i:l])
- io.WriteString(p.writer, string(hexencoded[:asc]))
- }
- p.writer.Write([]byte(`>`))
- case cfDate:
- if p.format == GNUStepFormat {
- p.writer.Write([]byte(`<*D`))
- io.WriteString(p.writer, time.Time(pval).In(time.UTC).Format(textPlistTimeLayout))
- p.writer.Write([]byte(`>`))
- } else {
- io.WriteString(p.writer, p.plistQuotedString(time.Time(pval).In(time.UTC).Format(textPlistTimeLayout)))
- }
- }
- }
- func (p *textPlistGenerator) Indent(i string) {
- p.indent = i
- if i == "" {
- p.dictKvDelimiter = []byte(`=`)
- } else {
- // For pretty-printing
- p.dictKvDelimiter = []byte(` = `)
- }
- }
- func newTextPlistGenerator(w io.Writer, format int) *textPlistGenerator {
- table := &osQuotable
- if format == GNUStepFormat {
- table = &gsQuotable
- }
- return &textPlistGenerator{
- writer: mustWriter{w},
- format: format,
- quotableTable: table,
- dictKvDelimiter: []byte(`=`),
- arrayDelimiter: []byte(`,`),
- dictEntryDelimiter: []byte(`;`),
- }
- }
|