227 lines
4.7 KiB
Go
227 lines
4.7 KiB
Go
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(`;`),
|
|
}
|
|
}
|