blob: 30597c160165885d93c441569fa466c4b094fdf4 [file] [log] [blame]
package plist
import (
"bufio"
"encoding/base64"
"encoding/xml"
"io"
"math"
"strconv"
"time"
)
const (
xmlHEADER string = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
xmlDOCTYPE = `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">` + "\n"
xmlArrayTag = "array"
xmlDataTag = "data"
xmlDateTag = "date"
xmlDictTag = "dict"
xmlFalseTag = "false"
xmlIntegerTag = "integer"
xmlKeyTag = "key"
xmlPlistTag = "plist"
xmlRealTag = "real"
xmlStringTag = "string"
xmlTrueTag = "true"
)
func formatXMLFloat(f float64) string {
switch {
case math.IsInf(f, 1):
return "inf"
case math.IsInf(f, -1):
return "-inf"
case math.IsNaN(f):
return "nan"
}
return strconv.FormatFloat(f, 'g', -1, 64)
}
type xmlPlistGenerator struct {
*bufio.Writer
indent string
depth int
putNewline bool
}
func (p *xmlPlistGenerator) generateDocument(root cfValue) {
p.WriteString(xmlHEADER)
p.WriteString(xmlDOCTYPE)
p.openTag(`plist version="1.0"`)
p.writePlistValue(root)
p.closeTag(xmlPlistTag)
p.Flush()
}
func (p *xmlPlistGenerator) openTag(n string) {
p.writeIndent(1)
p.WriteByte('<')
p.WriteString(n)
p.WriteByte('>')
}
func (p *xmlPlistGenerator) closeTag(n string) {
p.writeIndent(-1)
p.WriteString("</")
p.WriteString(n)
p.WriteByte('>')
}
func (p *xmlPlistGenerator) element(n string, v string) {
p.writeIndent(0)
if len(v) == 0 {
p.WriteByte('<')
p.WriteString(n)
p.WriteString("/>")
} else {
p.WriteByte('<')
p.WriteString(n)
p.WriteByte('>')
err := xml.EscapeText(p.Writer, []byte(v))
if err != nil {
panic(err)
}
p.WriteString("</")
p.WriteString(n)
p.WriteByte('>')
}
}
func (p *xmlPlistGenerator) writeDictionary(dict *cfDictionary) {
dict.sort()
p.openTag(xmlDictTag)
for i, k := range dict.keys {
p.element(xmlKeyTag, k)
p.writePlistValue(dict.values[i])
}
p.closeTag(xmlDictTag)
}
func (p *xmlPlistGenerator) writeArray(a *cfArray) {
p.openTag(xmlArrayTag)
for _, v := range a.values {
p.writePlistValue(v)
}
p.closeTag(xmlArrayTag)
}
func (p *xmlPlistGenerator) writePlistValue(pval cfValue) {
if pval == nil {
return
}
switch pval := pval.(type) {
case cfString:
p.element(xmlStringTag, string(pval))
case *cfNumber:
if pval.signed {
p.element(xmlIntegerTag, strconv.FormatInt(int64(pval.value), 10))
} else {
p.element(xmlIntegerTag, strconv.FormatUint(pval.value, 10))
}
case *cfReal:
p.element(xmlRealTag, formatXMLFloat(pval.value))
case cfBoolean:
if bool(pval) {
p.element(xmlTrueTag, "")
} else {
p.element(xmlFalseTag, "")
}
case cfData:
p.element(xmlDataTag, base64.StdEncoding.EncodeToString([]byte(pval)))
case cfDate:
p.element(xmlDateTag, time.Time(pval).In(time.UTC).Format(time.RFC3339))
case *cfDictionary:
p.writeDictionary(pval)
case *cfArray:
p.writeArray(pval)
case cfUID:
p.writePlistValue(pval.toDict())
}
}
func (p *xmlPlistGenerator) writeIndent(delta int) {
if len(p.indent) == 0 {
return
}
if delta < 0 {
p.depth--
}
if p.putNewline {
// from encoding/xml/marshal.go; it seems to be intended
// to suppress the first newline.
p.WriteByte('\n')
} else {
p.putNewline = true
}
for i := 0; i < p.depth; i++ {
p.WriteString(p.indent)
}
if delta > 0 {
p.depth++
}
}
func (p *xmlPlistGenerator) Indent(i string) {
p.indent = i
}
func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator {
return &xmlPlistGenerator{Writer: bufio.NewWriter(w)}
}