120 lines
3.9 KiB
Go
120 lines
3.9 KiB
Go
|
package plist
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"io"
|
||
|
"reflect"
|
||
|
"runtime"
|
||
|
)
|
||
|
|
||
|
type parser interface {
|
||
|
parseDocument() (cfValue, error)
|
||
|
}
|
||
|
|
||
|
// A Decoder reads a property list from an input stream.
|
||
|
type Decoder struct {
|
||
|
// the format of the most-recently-decoded property list
|
||
|
Format int
|
||
|
|
||
|
reader io.ReadSeeker
|
||
|
lax bool
|
||
|
}
|
||
|
|
||
|
// Decode works like Unmarshal, except it reads the decoder stream to find property list elements.
|
||
|
//
|
||
|
// After Decoding, the Decoder's Format field will be set to one of the plist format constants.
|
||
|
func (p *Decoder) Decode(v interface{}) (err error) {
|
||
|
defer func() {
|
||
|
if r := recover(); r != nil {
|
||
|
if _, ok := r.(runtime.Error); ok {
|
||
|
panic(r)
|
||
|
}
|
||
|
err = r.(error)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
header := make([]byte, 6)
|
||
|
p.reader.Read(header)
|
||
|
p.reader.Seek(0, 0)
|
||
|
|
||
|
var ps parser
|
||
|
var pval cfValue
|
||
|
if bytes.Equal(header, []byte("bplist")) {
|
||
|
ps = newBplistParser(p.reader)
|
||
|
pval, err = ps.parseDocument()
|
||
|
if err != nil {
|
||
|
// Had a bplist header, but still got an error: we have to die here.
|
||
|
return err
|
||
|
}
|
||
|
p.Format = BinaryFormat
|
||
|
} else {
|
||
|
ps = newXMLPlistParser(p.reader)
|
||
|
pval, err = ps.parseDocument()
|
||
|
if _, ok := err.(invalidPlistError); ok {
|
||
|
// Rewind: the XML ps might have exhausted the file.
|
||
|
p.reader.Seek(0, 0)
|
||
|
// We don't use ps here because we want the textPlistParser type
|
||
|
tp := newTextPlistParser(p.reader)
|
||
|
pval, err = tp.parseDocument()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
p.Format = tp.format
|
||
|
if p.Format == OpenStepFormat {
|
||
|
// OpenStep property lists can only store strings,
|
||
|
// so we have to turn on lax mode here for the unmarshal step later.
|
||
|
p.lax = true
|
||
|
}
|
||
|
} else {
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
p.Format = XMLFormat
|
||
|
}
|
||
|
}
|
||
|
|
||
|
p.unmarshal(pval, reflect.ValueOf(v))
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// NewDecoder returns a Decoder that reads property list elements from a stream reader, r.
|
||
|
// NewDecoder requires a Seekable stream for the purposes of file type detection.
|
||
|
func NewDecoder(r io.ReadSeeker) *Decoder {
|
||
|
return &Decoder{Format: InvalidFormat, reader: r, lax: false}
|
||
|
}
|
||
|
|
||
|
// Unmarshal parses a property list document and stores the result in the value pointed to by v.
|
||
|
//
|
||
|
// Unmarshal uses the inverse of the type encodings that Marshal uses, allocating heap-borne types as necessary.
|
||
|
//
|
||
|
// When given a nil pointer, Unmarshal allocates a new value for it to point to.
|
||
|
//
|
||
|
// To decode property list values into an interface value, Unmarshal decodes the property list into the concrete value contained
|
||
|
// in the interface value. If the interface value is nil, Unmarshal stores one of the following in the interface value:
|
||
|
//
|
||
|
// string, bool, uint64, float64
|
||
|
// plist.UID for "CoreFoundation Keyed Archiver UIDs" (convertible to uint64)
|
||
|
// []byte, for plist data
|
||
|
// []interface{}, for plist arrays
|
||
|
// map[string]interface{}, for plist dictionaries
|
||
|
//
|
||
|
// If a property list value is not appropriate for a given value type, Unmarshal aborts immediately and returns an error.
|
||
|
//
|
||
|
// As Go does not support 128-bit types, and we don't want to pretend we're giving the user integer types (as opposed to
|
||
|
// secretly passing them structs), Unmarshal will drop the high 64 bits of any 128-bit integers encoded in binary property lists.
|
||
|
// (This is important because CoreFoundation serializes some large 64-bit values as 128-bit values with an empty high half.)
|
||
|
//
|
||
|
// When Unmarshal encounters an OpenStep property list, it will enter a relaxed parsing mode: OpenStep property lists can only store
|
||
|
// plain old data as strings, so we will attempt to recover integer, floating-point, boolean and date values wherever they are necessary.
|
||
|
// (for example, if Unmarshal attempts to unmarshal an OpenStep property list into a time.Time, it will try to parse the string it
|
||
|
// receives as a time.)
|
||
|
//
|
||
|
// Unmarshal returns the detected property list format and an error, if any.
|
||
|
func Unmarshal(data []byte, v interface{}) (format int, err error) {
|
||
|
r := bytes.NewReader(data)
|
||
|
dec := NewDecoder(r)
|
||
|
err = dec.Decode(v)
|
||
|
format = dec.Format
|
||
|
return
|
||
|
}
|