You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
226 lines
4.7 KiB
226 lines
4.7 KiB
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(`;`), |
|
} |
|
}
|
|
|