| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- // Package xpj reads and writes MPC Sample project files (.xpj).
- //
- // A project file is a gzip stream holding five header lines and one JSON
- // document. The JSON is held as an ordered tree: objects keep their key order
- // and numbers keep their original text, so a file that is read and written
- // back unchanged is byte-identical after decompression. The package knows
- // nothing about pads, samples or sequences.
- package xpj
- import (
- "errors"
- "fmt"
- "math"
- "strconv"
- "strings"
- )
- // Kind is the JSON type of a Node.
- type Kind int
- const (
- Null Kind = iota
- Bool
- Number
- String
- Array
- Object
- )
- func (k Kind) String() string {
- switch k {
- case Null:
- return "null"
- case Bool:
- return "bool"
- case Number:
- return "number"
- case String:
- return "string"
- case Array:
- return "array"
- case Object:
- return "object"
- }
- return fmt.Sprintf("Kind(%d)", int(k))
- }
- // Node is one JSON value.
- type Node struct {
- Kind Kind
- // Text is the decoded value of a String, or the literal text of a Number.
- Text string
- Bool bool
- Items []*Node // Array elements
- Members []Member // Object members, in file order
- }
- // Member is one key/value pair of an object.
- type Member struct {
- Key string
- Value *Node
- }
- // NewNull returns a null node.
- func NewNull() *Node { return &Node{Kind: Null} }
- // NewBool returns a boolean node.
- func NewBool(b bool) *Node { return &Node{Kind: Bool, Bool: b} }
- // NewString returns a string node.
- func NewString(s string) *Node { return &Node{Kind: String, Text: s} }
- // NewInt returns a number node written as an integer, e.g. 4.
- func NewInt(i int64) *Node { return &Node{Kind: Number, Text: strconv.FormatInt(i, 10)} }
- // NewFloat returns a number node for a float field. It is always written with
- // a decimal point or exponent (96.0, never 96), in the shortest form that
- // reads back as the same float64. It panics on NaN or infinity, which JSON
- // cannot hold.
- func NewFloat(f float64) *Node { return &Node{Kind: Number, Text: FormatFloat(f)} }
- // NewFloat32 is NewFloat for a field the device stores as a 32-bit float:
- // the value is rounded to float32 first, then printed as a float64
- // (12.4 is written as 12.399999618530273).
- func NewFloat32(f float64) *Node { return NewFloat(float64(float32(f))) }
- // NewArray returns an array node holding items.
- func NewArray(items ...*Node) *Node { return &Node{Kind: Array, Items: items} }
- // NewObject returns an empty object node.
- func NewObject() *Node { return &Node{Kind: Object} }
- // FormatFloat prints f the way the reference writer (Python's repr) does:
- // shortest round-trip digits, plain notation for exponents from -4 to 15
- // with at least one digit after the point, otherwise d.ddde±XX.
- func FormatFloat(f float64) string {
- if math.IsNaN(f) || math.IsInf(f, 0) {
- panic("xpj: cannot write NaN or infinity")
- }
- s := strconv.FormatFloat(f, 'e', -1, 64) // e.g. -1.2345e+01
- mant, expStr, _ := strings.Cut(s, "e")
- exp, _ := strconv.Atoi(expStr)
- neg := strings.HasPrefix(mant, "-")
- digits := strings.Replace(strings.TrimPrefix(mant, "-"), ".", "", 1)
- var out string
- if exp < -4 || exp >= 16 {
- out = digits[:1]
- if len(digits) > 1 {
- out += "." + digits[1:]
- }
- sign := "+"
- if exp < 0 {
- sign, exp = "-", -exp
- }
- out += fmt.Sprintf("e%s%02d", sign, exp)
- } else if exp < 0 {
- out = "0." + strings.Repeat("0", -exp-1) + digits
- } else if exp+1 >= len(digits) {
- out = digits + strings.Repeat("0", exp+1-len(digits)) + ".0"
- } else {
- out = digits[:exp+1] + "." + digits[exp+1:]
- }
- if neg {
- out = "-" + out
- }
- return out
- }
- // errMissing is returned by the value getters for a nil node.
- var errMissing = errors.New("xpj: value is missing")
- // Int returns the value of a Number node as an integer. A nil node, as
- // Lookup returns for a missing key, is an error.
- func (n *Node) Int() (int64, error) {
- if n == nil {
- return 0, errMissing
- }
- if n.Kind != Number {
- return 0, fmt.Errorf("xpj: %s is not a number", n.Kind)
- }
- if i, err := strconv.ParseInt(n.Text, 10, 64); err == nil {
- return i, nil
- }
- f, err := strconv.ParseFloat(n.Text, 64)
- if err != nil || f != math.Trunc(f) || math.Abs(f) > 1<<53 {
- return 0, fmt.Errorf("xpj: %s is not an integer", n.Text)
- }
- return int64(f), nil
- }
- // Float returns the value of a Number node. A nil node is an error.
- func (n *Node) Float() (float64, error) {
- if n == nil {
- return 0, errMissing
- }
- if n.Kind != Number {
- return 0, fmt.Errorf("xpj: %s is not a number", n.Kind)
- }
- return strconv.ParseFloat(n.Text, 64)
- }
- // Lookup returns the value of the first member called key, or nil if n is not
- // an object or has no such member.
- func (n *Node) Lookup(key string) *Node {
- if n == nil || n.Kind != Object {
- return nil
- }
- for _, m := range n.Members {
- if m.Key == key {
- return m.Value
- }
- }
- return nil
- }
- // SetKey replaces the value of the first member called key, keeping its
- // position, or appends a new member if there is none.
- func (n *Node) SetKey(key string, v *Node) error {
- if n.Kind != Object {
- return fmt.Errorf("xpj: cannot set key %q on %s", key, n.Kind)
- }
- for i := range n.Members {
- if n.Members[i].Key == key {
- n.Members[i].Value = v
- return nil
- }
- }
- n.Members = append(n.Members, Member{Key: key, Value: v})
- return nil
- }
- // DeleteKey removes the first member called key and reports whether it was there.
- func (n *Node) DeleteKey(key string) bool {
- if n.Kind != Object {
- return false
- }
- for i, m := range n.Members {
- if m.Key == key {
- n.Members = append(n.Members[:i], n.Members[i+1:]...)
- return true
- }
- }
- return false
- }
- // Insert puts v into an array at index i (0 <= i <= len), shifting later items.
- func (n *Node) Insert(i int, v *Node) error {
- if n.Kind != Array {
- return fmt.Errorf("xpj: cannot insert into %s", n.Kind)
- }
- if i < 0 || i > len(n.Items) {
- return fmt.Errorf("xpj: insert index %d out of range [0,%d]", i, len(n.Items))
- }
- n.Items = append(n.Items, nil)
- copy(n.Items[i+1:], n.Items[i:])
- n.Items[i] = v
- return nil
- }
- // Append adds v to the end of an array.
- func (n *Node) Append(v *Node) error { return n.Insert(len(n.Items), v) }
- // Remove deletes the array item at index i.
- func (n *Node) Remove(i int) error {
- if n.Kind != Array {
- return fmt.Errorf("xpj: cannot remove from %s", n.Kind)
- }
- if i < 0 || i >= len(n.Items) {
- return fmt.Errorf("xpj: remove index %d out of range [0,%d)", i, len(n.Items))
- }
- n.Items = append(n.Items[:i], n.Items[i+1:]...)
- return nil
- }
- // Clone returns a deep copy of n.
- func (n *Node) Clone() *Node {
- if n == nil {
- return nil
- }
- c := &Node{Kind: n.Kind, Text: n.Text, Bool: n.Bool}
- if n.Items != nil {
- c.Items = make([]*Node, len(n.Items))
- for i, it := range n.Items {
- c.Items[i] = it.Clone()
- }
- }
- if n.Members != nil {
- c.Members = make([]Member, len(n.Members))
- for i, m := range n.Members {
- c.Members[i] = Member{Key: m.Key, Value: m.Value.Clone()}
- }
- }
- return c
- }
|