1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2025-04-21 03:02:09 +00:00
mailinabox/json-parser
2018-11-25 11:37:19 -07:00

190 lines
5.3 KiB
Bash

#!/usr/bin/env bash
function output_entry() {
echo "$1=$2"
}
function parse_array() {
local current_path="${1:+$1.}$2"
local current_scope="root"
local current_index=0
while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
[ "$preserve_current_char" == "0" ] && chars_read=$((chars_read+1)) && read -r -s -n 1 c
preserve_current_char=0
c=${c:-' '}
case "$current_scope" in
"root") # Waiting for new object or value
case "$c" in
'{')
parse_object "$current_path" "$current_index"
current_scope="entry_separator"
;;
']')
return
;;
[\"tfTF\-0-9])
preserve_current_char=1 # Let the parse value function decide what kind of value this is
parse_value "$current_path" "$current_index"
preserve_current_char=1 # Parse value has terminated with a separator or an array end, but we can handle this only in the next while iteration
current_scope="entry_separator"
;;
esac
;;
"entry_separator")
[ "$c" == "," ] && current_index=$((current_index+1)) && current_scope="root"
[ "$c" == "]" ] && return
;;
esac
done
}
function parse_value() {
local current_path="${1:+$1.}$2"
local current_scope="root"
while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
[ "$preserve_current_char" == "0" ] && chars_read=$((chars_read+1)) && read -r -s -n 1 c
preserve_current_char=0
c=${c:-' '}
case "$current_scope" in
"root") # Waiting for new string, number or boolean
case "$c" in
'"') # String begin
current_scope="string"
current_varvalue=""
;;
[\-0-9]) # Number begin
current_scope="number"
current_varvalue="$c"
;;
[tfTF]) # True or false begin
current_scope="boolean"
current_varvalue="$c"
;;
"[") # Array begin
parse_array "" "$current_path"
return
;;
"{") # Object begin
parse_object "" "$current_path"
return
esac
;;
"string") # Waiting for string end
case "$c" in
'"') # String end if not in escape mode, normal character otherwise
[ "$current_escaping" == "0" ] && output_entry "$current_path" "$current_varvalue" && return
[ "$current_escaping" == "1" ] && current_varvalue="$current_varvalue$c" && current_escaping=0
;;
'\') # Escape character, entering or leaving escape mode
current_escaping=$((1-current_escaping))
current_varvalue="$current_varvalue$c"
;;
*) # Any other string character
current_escaping=0
current_varvalue="$current_varvalue$c"
;;
esac
;;
"number") # Waiting for number end
case "$c" in
[,\]}]) # Separator or array end or object end
output_entry "$current_path" "$current_varvalue"
preserve_current_char=1 # The caller needs to handle this char
return
;;
[\-0-9.]) # Number can only contain digits, dots and a sign
current_varvalue="$current_varvalue$c"
;;
# Ignore everything else
esac
;;
"boolean") # Waiting for boolean to end
case "$c" in
[,\]}]) # Separator or array end or object end
output_entry "$current_path" "$current_varvalue"
preserve_current_char=1 # The caller needs to handle this char
return
;;
[a-zA-Z]) # No need to do some strict checking, we do not want to validate the incoming json data
current_varvalue="$current_varvalue$c"
;;
# Ignore everything else
esac
;;
esac
done
}
function parse_object() {
local current_path="${1:+$1.}$2"
local current_scope="root"
while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
[ "$preserve_current_char" == "0" ] && chars_read=$((chars_read+1)) && read -r -s -n 1 c
preserve_current_char=0
c=${c:-' '}
case "$current_scope" in
"root") # Waiting for new field or object end
[ "$c" == "}" ] && return
[ "$c" == "\"" ] && current_scope="varname" && current_varname="" && current_escaping=0
;;
"varname") # Reading the field name
case "$c" in
'"') # String end if not in escape mode, normal character otherwise
[ "$current_escaping" == "0" ] && current_scope="key_value_separator"
[ "$current_escaping" == "1" ] && current_varname="$current_varname$c"
;;
'\') # Escape character, entering or leaving escape mode
current_escaping=$((1-current_escaping))
current_varname="$current_varname$c"
;;
*) # Any other string character
current_escaping=0
current_varname="$current_varname$c"
;;
esac
;;
"key_value_separator") # Waiting for the key value separator (:)
[ "$c" == ":" ] && parse_value "$current_path" "$current_varname" && current_scope="field_separator"
;;
"field_separator") # Waiting for the field separator (,)
[ "$c" == ',' ] && current_scope="root"
[ "$c" == '}' ] && return
;;
esac
done
}
function parse() {
chars_read=0
preserve_current_char=0
while [ "$chars_read" -lt "$INPUT_LENGTH" ]; do
read -r -s -n 1 c
c=${c:-' '}
chars_read=$((chars_read+1))
# A valid JSON string consists of exactly one object
[ "$c" == "{" ] && parse_object "" "" && return
# ... or one array
[ "$c" == "[" ] && parse_array "" "" && return
done
}
if [ -z "$@" ]; then
INPUT=$(cat -)
else
INPUT=$(echo "$@")
fi
INPUT_LENGTH="${#INPUT}"
parse "" "" <<< "${INPUT}"