122 lines
3.7 KiB
JavaScript
122 lines
3.7 KiB
JavaScript
'use strict'
|
|
|
|
const BACKSLASH = '\\'
|
|
const DQUOT = '"'
|
|
const LBRACE = '{'
|
|
const RBRACE = '}'
|
|
const LBRACKET = '['
|
|
const EQUALS = '='
|
|
const COMMA = ','
|
|
|
|
/** When the raw value is this, it means a literal `null` */
|
|
const NULL_STRING = 'NULL'
|
|
|
|
/**
|
|
* Parses an array according to
|
|
* https://www.postgresql.org/docs/17/arrays.html#ARRAYS-IO
|
|
*
|
|
* Trusts the data (mostly), so only hook up to trusted Postgres servers.
|
|
*/
|
|
function makeParseArrayWithTransform (transform) {
|
|
const haveTransform = transform != null
|
|
return function parseArray (str) {
|
|
const rbraceIndex = str.length - 1
|
|
if (rbraceIndex === 1) {
|
|
return []
|
|
}
|
|
if (str[rbraceIndex] !== RBRACE) {
|
|
throw new Error('Invalid array text - must end with }')
|
|
}
|
|
|
|
// If starts with `[`, it is specifying the index boundas. Skip past first `=`.
|
|
let position = 0
|
|
if (str[position] === LBRACKET) {
|
|
position = str.indexOf(EQUALS) + 1
|
|
}
|
|
|
|
if (str[position++] !== LBRACE) {
|
|
throw new Error('Invalid array text - must start with {')
|
|
}
|
|
const output = []
|
|
let current = output
|
|
const stack = []
|
|
|
|
let currentStringStart = position
|
|
let currentString = ''
|
|
let expectValue = true
|
|
|
|
for (; position < rbraceIndex; ++position) {
|
|
let char = str[position]
|
|
// > The array output routine will put double quotes around element values if
|
|
// > they are empty strings, contain curly braces, delimiter characters, double
|
|
// > quotes, backslashes, or white space, or match the word NULL. Double quotes
|
|
// > and backslashes embedded in element values will be backslash-escaped.
|
|
if (char === DQUOT) {
|
|
// It's escaped
|
|
currentStringStart = ++position
|
|
let dquot = str.indexOf(DQUOT, currentStringStart)
|
|
let backSlash = str.indexOf(BACKSLASH, currentStringStart)
|
|
while (backSlash !== -1 && backSlash < dquot) {
|
|
position = backSlash
|
|
const part = str.slice(currentStringStart, position)
|
|
currentString += part
|
|
currentStringStart = ++position
|
|
if (dquot === position++) {
|
|
// This was an escaped doublequote; find the next one!
|
|
dquot = str.indexOf(DQUOT, position)
|
|
}
|
|
// Either way, find the next backslash
|
|
backSlash = str.indexOf(BACKSLASH, position)
|
|
}
|
|
position = dquot
|
|
const part = str.slice(currentStringStart, position)
|
|
currentString += part
|
|
current.push(haveTransform ? transform(currentString) : currentString)
|
|
currentString = ''
|
|
expectValue = false
|
|
} else if (char === LBRACE) {
|
|
const newArray = []
|
|
current.push(newArray)
|
|
stack.push(current)
|
|
current = newArray
|
|
currentStringStart = position + 1
|
|
expectValue = true
|
|
} else if (char === COMMA) {
|
|
expectValue = true
|
|
} else if (char === RBRACE) {
|
|
expectValue = false
|
|
const arr = stack.pop()
|
|
if (arr === undefined) {
|
|
throw new Error("Invalid array text - too many '}'")
|
|
}
|
|
current = arr
|
|
} else if (expectValue) {
|
|
currentStringStart = position
|
|
while (
|
|
(char = str[position]) !== COMMA &&
|
|
char !== RBRACE &&
|
|
position < rbraceIndex
|
|
) {
|
|
++position
|
|
}
|
|
const part = str.slice(currentStringStart, position--)
|
|
current.push(
|
|
part === NULL_STRING ? null : haveTransform ? transform(part) : part
|
|
)
|
|
expectValue = false
|
|
} else {
|
|
throw new Error('Was expecting delimeter')
|
|
}
|
|
}
|
|
|
|
return output
|
|
}
|
|
}
|
|
|
|
const parseArray = makeParseArrayWithTransform()
|
|
|
|
exports.parse = (source, transform) =>
|
|
transform != null
|
|
? makeParseArrayWithTransform(transform)(source)
|
|
: parseArray(source)
|