// Field access paths as understood by vega-util `field()` / `splitAccessPath`:
// `a.b`, `a[0]`, `a["b.c"]` and backslash-escaped dots (`a\.b`).
// A data row is a `Map[String, Value]` in insertion order.
///|
/// Split an access path into its property names.
pub fn split_access_path(path : String) -> Array[String] {
let cs = path.to_array()
let n = cs.length()
let parts : Array[String] = []
let cur = StringBuilder()
let mut have = false
let mut i = 0
while i < n {
let c = cs[i]
if c == '\\' && i + 1 < n {
cur.write_char(cs[i + 1])
have = true
i = i + 2
} else if c == '.' {
parts.push(cur.to_string())
cur.reset()
have = false
i = i + 1
} else if c == '[' {
if have {
parts.push(cur.to_string())
cur.reset()
have = false
}
i = i + 1
if i < n && c_is_quote(cs[i]) {
let q = cs[i]
i = i + 1
while i < n && cs[i] != q {
if cs[i] == '\\' && i + 1 < n {
i = i + 1
}
cur.write_char(cs[i])
i = i + 1
}
i = i + 1
} else {
while i < n && cs[i] != ']' {
cur.write_char(cs[i])
i = i + 1
}
}
parts.push(cur.to_string())
cur.reset()
have = false
if i < n && cs[i] == ']' {
i = i + 1
}
// a following '.' only separates
if i < n && cs[i] == '.' {
i = i + 1
}
} else {
cur.write_char(c)
have = true
i = i + 1
}
}
if have || parts.length() == 0 {
parts.push(cur.to_string())
}
parts
}
///|
fn c_is_quote(c : Char) -> Bool {
c == '"' || c == '\''
}
///|
/// Read a (possibly nested) field of a row; `Undef` when any step is missing.
pub fn get_field(row : Map[String, Value], path : String) -> Value {
let parts = split_access_path(path)
let mut cur : Value = match row.get(parts[0]) {
Some(v) => v
None => Undef
}
for k in 1..
match m.get(parts[k]) {
Some(v) => v
None => Undef
}
Arr(items) => {
let idx = string_to_number(parts[k])
if idx >= 0.0 && idx == idx.trunc() && idx < items.length().to_double() {
items[idx.to_int()]
} else {
Undef
}
}
_ => Undef
}
}
cur
}