blob: d71f02bbc84b3fdfbf64f96e70e9151945bba92a [file] [log] [blame]
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)))
}
case cfUID:
p.writePlistValue(pval.toDict())
}
}
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(`;`),
}
}