blob: 9302ef073d9f26fb170828ed6c5893bc51823d57 [file] [log] [blame]
package plist
import (
"encoding/base64"
"encoding/xml"
"io"
"math"
"time"
)
const xmlDOCTYPE = `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
`
type xmlPlistGenerator struct {
writer io.Writer
xmlEncoder *xml.Encoder
}
func (p *xmlPlistGenerator) generateDocument(root cfValue) {
io.WriteString(p.writer, xml.Header)
io.WriteString(p.writer, xmlDOCTYPE)
plistStartElement := xml.StartElement{
Name: xml.Name{
Space: "",
Local: "plist",
},
Attr: []xml.Attr{{
Name: xml.Name{
Space: "",
Local: "version"},
Value: "1.0"},
},
}
p.xmlEncoder.EncodeToken(plistStartElement)
p.writePlistValue(root)
p.xmlEncoder.EncodeToken(plistStartElement.End())
p.xmlEncoder.Flush()
}
func (p *xmlPlistGenerator) writeDictionary(dict *cfDictionary) {
dict.sort()
startElement := xml.StartElement{Name: xml.Name{Local: "dict"}}
p.xmlEncoder.EncodeToken(startElement)
for i, k := range dict.keys {
p.xmlEncoder.EncodeElement(k, xml.StartElement{Name: xml.Name{Local: "key"}})
p.writePlistValue(dict.values[i])
}
p.xmlEncoder.EncodeToken(startElement.End())
}
func (p *xmlPlistGenerator) writeArray(a *cfArray) {
startElement := xml.StartElement{Name: xml.Name{Local: "array"}}
p.xmlEncoder.EncodeToken(startElement)
for _, v := range a.values {
p.writePlistValue(v)
}
p.xmlEncoder.EncodeToken(startElement.End())
}
func (p *xmlPlistGenerator) writePlistValue(pval cfValue) {
if pval == nil {
return
}
defer p.xmlEncoder.Flush()
if dict, ok := pval.(*cfDictionary); ok {
p.writeDictionary(dict)
return
} else if a, ok := pval.(*cfArray); ok {
p.writeArray(a)
return
} else if uid, ok := pval.(cfUID); ok {
p.writeDictionary(&cfDictionary{
keys: []string{"CF$UID"},
values: []cfValue{
&cfNumber{
signed: false,
value: uint64(uid),
},
},
})
return
}
// Everything here and beyond is encoded the same way: <key>value</key>
key := ""
var encodedValue interface{} = pval
switch pval := pval.(type) {
case cfString:
key = "string"
case *cfNumber:
key = "integer"
if pval.signed {
encodedValue = int64(pval.value)
} else {
encodedValue = pval.value
}
case *cfReal:
key = "real"
encodedValue = pval.value
switch {
case math.IsInf(pval.value, 1):
encodedValue = "inf"
case math.IsInf(pval.value, -1):
encodedValue = "-inf"
case math.IsNaN(pval.value):
encodedValue = "nan"
}
case cfBoolean:
key = "false"
b := bool(pval)
if b {
key = "true"
}
encodedValue = ""
case cfData:
key = "data"
encodedValue = xml.CharData(base64.StdEncoding.EncodeToString([]byte(pval)))
case cfDate:
key = "date"
encodedValue = time.Time(pval).In(time.UTC).Format(time.RFC3339)
}
if key != "" {
err := p.xmlEncoder.EncodeElement(encodedValue, xml.StartElement{Name: xml.Name{Local: key}})
if err != nil {
panic(err)
}
}
}
func (p *xmlPlistGenerator) Indent(i string) {
p.xmlEncoder.Indent("", i)
}
func newXMLPlistGenerator(w io.Writer) *xmlPlistGenerator {
mw := mustWriter{w}
return &xmlPlistGenerator{mw, xml.NewEncoder(mw)}
}