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.
119 lines
3.9 KiB
119 lines
3.9 KiB
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 |
|
}
|
|
|