1
0

path.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package xpj
  2. import (
  3. "fmt"
  4. "strconv"
  5. "strings"
  6. )
  7. // A path names a node below another, in the form used by the format doc:
  8. // object keys separated by dots, array indexes in brackets, e.g.
  9. // "tracks[0].program.drum.instruments[4].layersv[0].sliceInfo.End".
  10. // Keys containing '.', '[' or ']' cannot be addressed this way; use Lookup.
  11. type step struct {
  12. key string
  13. index int // -1 for a key step
  14. }
  15. func parsePath(path string) ([]step, error) {
  16. var steps []step
  17. rest := path
  18. for rest != "" {
  19. if rest[0] == '[' {
  20. end := strings.IndexByte(rest, ']')
  21. if end < 0 {
  22. return nil, fmt.Errorf("xpj: path %q: missing ]", path)
  23. }
  24. i, err := strconv.Atoi(rest[1:end])
  25. if err != nil || i < 0 {
  26. return nil, fmt.Errorf("xpj: path %q: bad index %q", path, rest[1:end])
  27. }
  28. steps = append(steps, step{index: i})
  29. rest = rest[end+1:]
  30. if strings.HasPrefix(rest, ".") {
  31. rest = rest[1:]
  32. if rest == "" || rest[0] == '[' {
  33. return nil, fmt.Errorf("xpj: path %q: empty key", path)
  34. }
  35. }
  36. continue
  37. }
  38. end := strings.IndexAny(rest, ".[]")
  39. if end < 0 {
  40. end = len(rest)
  41. }
  42. if end == 0 {
  43. return nil, fmt.Errorf("xpj: path %q: empty key", path)
  44. }
  45. steps = append(steps, step{key: rest[:end], index: -1})
  46. rest = rest[end:]
  47. if strings.HasPrefix(rest, "]") {
  48. return nil, fmt.Errorf("xpj: path %q: unexpected ]", path)
  49. }
  50. if strings.HasPrefix(rest, ".") {
  51. rest = rest[1:]
  52. if rest == "" || rest[0] == '[' {
  53. return nil, fmt.Errorf("xpj: path %q: empty key", path)
  54. }
  55. }
  56. }
  57. return steps, nil
  58. }
  59. func (n *Node) walk(steps []step, path string) (*Node, error) {
  60. cur := n
  61. if cur == nil {
  62. return nil, fmt.Errorf("xpj: path %q: node is missing", path)
  63. }
  64. for i, s := range steps {
  65. if s.index < 0 {
  66. next := cur.Lookup(s.key)
  67. if next == nil {
  68. return nil, fmt.Errorf("xpj: path %q: no key %q at %s", path, s.key, describe(steps[:i]))
  69. }
  70. cur = next
  71. continue
  72. }
  73. if cur.Kind != Array {
  74. return nil, fmt.Errorf("xpj: path %q: %s is %s, not an array", path, describe(steps[:i]), cur.Kind)
  75. }
  76. if s.index >= len(cur.Items) {
  77. return nil, fmt.Errorf("xpj: path %q: index %d out of range at %s (length %d)", path, s.index, describe(steps[:i]), len(cur.Items))
  78. }
  79. cur = cur.Items[s.index]
  80. }
  81. return cur, nil
  82. }
  83. func describe(steps []step) string {
  84. if len(steps) == 0 {
  85. return "root"
  86. }
  87. var b strings.Builder
  88. for i, s := range steps {
  89. if s.index >= 0 {
  90. fmt.Fprintf(&b, "[%d]", s.index)
  91. continue
  92. }
  93. if i > 0 {
  94. b.WriteByte('.')
  95. }
  96. b.WriteString(s.key)
  97. }
  98. return b.String()
  99. }
  100. // Get returns the node at path below n. An empty path returns n.
  101. func (n *Node) Get(path string) (*Node, error) {
  102. steps, err := parsePath(path)
  103. if err != nil {
  104. return nil, err
  105. }
  106. return n.walk(steps, path)
  107. }
  108. // Set replaces the node at path below n. The parent must exist; a missing
  109. // final key is appended to its object, but array indexes must already exist.
  110. func (n *Node) Set(path string, v *Node) error {
  111. steps, err := parsePath(path)
  112. if err != nil {
  113. return err
  114. }
  115. if len(steps) == 0 {
  116. return fmt.Errorf("xpj: cannot Set the root")
  117. }
  118. parent, err := n.walk(steps[:len(steps)-1], path)
  119. if err != nil {
  120. return err
  121. }
  122. last := steps[len(steps)-1]
  123. if last.index < 0 {
  124. return parent.SetKey(last.key, v)
  125. }
  126. if parent.Kind != Array {
  127. return fmt.Errorf("xpj: path %q: parent is %s, not an array", path, parent.Kind)
  128. }
  129. if last.index >= len(parent.Items) {
  130. return fmt.Errorf("xpj: path %q: index %d out of range (length %d)", path, last.index, len(parent.Items))
  131. }
  132. parent.Items[last.index] = v
  133. return nil
  134. }