Simples fixes to the parser, add is_valid_start_symbol and updated parse_json.
This commit is contained in:
@@ -1,434 +1,446 @@
|
|||||||
indexing
|
indexing
|
||||||
|
|
||||||
description: "Parse serialized JSON data"
|
description: "Parse serialized JSON data"
|
||||||
author: "jvelilla"
|
author: "jvelilla"
|
||||||
date: "2008/08/24"
|
date: "2008/08/24"
|
||||||
revision: "Revision 0.1"
|
revision: "Revision 0.1"
|
||||||
|
|
||||||
class
|
class
|
||||||
JSON_PARSER
|
JSON_PARSER
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
JSON_READER
|
JSON_READER
|
||||||
JSON_TOKENS
|
JSON_TOKENS
|
||||||
|
|
||||||
create
|
create
|
||||||
make_parser
|
make_parser
|
||||||
|
|
||||||
feature {NONE} -- Initialize
|
feature {NONE} -- Initialize
|
||||||
|
|
||||||
make_parser (a_json: STRING) is
|
make_parser (a_json: STRING) is
|
||||||
-- Initialize.
|
-- Initialize.
|
||||||
require
|
require
|
||||||
json_not_empty: a_json /= Void and then not a_json.is_empty
|
json_not_empty: a_json /= Void and then not a_json.is_empty
|
||||||
do
|
do
|
||||||
make (a_json)
|
make (a_json)
|
||||||
is_parsed := True
|
is_parsed := True
|
||||||
create errors.make
|
create errors.make
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Status report
|
feature -- Status report
|
||||||
|
|
||||||
is_parsed: BOOLEAN
|
is_parsed: BOOLEAN
|
||||||
-- Is parsed?
|
-- Is parsed?
|
||||||
|
|
||||||
errors: LINKED_LIST [STRING]
|
errors: LINKED_LIST [STRING]
|
||||||
-- Current errors
|
-- Current errors
|
||||||
|
|
||||||
current_errors: STRING
|
current_errors: STRING
|
||||||
-- Current errors as string
|
-- Current errors as string
|
||||||
do
|
do
|
||||||
create Result.make_empty
|
create Result.make_empty
|
||||||
from
|
from
|
||||||
errors.start
|
errors.start
|
||||||
until
|
until
|
||||||
errors.after
|
errors.after
|
||||||
loop
|
loop
|
||||||
Result.append_string (errors.item + "%N")
|
Result.append_string (errors.item + "%N")
|
||||||
errors.forth
|
errors.forth
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Element change
|
feature -- Element change
|
||||||
|
|
||||||
report_error (e: STRING) is
|
report_error (e: STRING) is
|
||||||
-- Report error `e'
|
-- Report error `e'
|
||||||
require
|
require
|
||||||
e_not_void: e /= Void
|
e_not_void: e /= Void
|
||||||
do
|
do
|
||||||
errors.force (e)
|
errors.force (e)
|
||||||
end
|
end
|
||||||
|
|
||||||
feature -- Commands
|
feature -- Commands
|
||||||
|
|
||||||
parse_json: ?JSON_VALUE is
|
parse_json: ?JSON_VALUE is
|
||||||
-- Parse JSON data `representation'
|
-- Parse JSON data `representation'
|
||||||
do
|
-- start ::= object | array
|
||||||
Result := parse
|
do
|
||||||
if extra_elements then
|
if is_valid_start_symbol then
|
||||||
is_parsed := False
|
Result := parse
|
||||||
end
|
if extra_elements then
|
||||||
end
|
is_parsed := False
|
||||||
|
end
|
||||||
parse: ?JSON_VALUE is
|
else
|
||||||
-- Parse JSON data `representation'
|
is_parsed := False
|
||||||
local
|
report_error ("Syntax error unexpected token, expecting `{' or `['")
|
||||||
c: CHARACTER
|
end
|
||||||
do
|
end
|
||||||
if is_parsed then
|
|
||||||
skip_white_spaces
|
parse: ?JSON_VALUE is
|
||||||
c := actual
|
-- Parse JSON data `representation'
|
||||||
inspect c
|
local
|
||||||
when j_OBJECT_OPEN then
|
c: CHARACTER
|
||||||
Result := parse_object
|
do
|
||||||
when j_STRING then
|
if is_parsed then
|
||||||
Result := parse_string
|
skip_white_spaces
|
||||||
when j_ARRAY_OPEN then
|
c := actual
|
||||||
Result := parse_array
|
inspect c
|
||||||
else
|
when j_OBJECT_OPEN then
|
||||||
if c.is_digit or c = j_MINUS then
|
Result := parse_object
|
||||||
Result := parse_number
|
when j_STRING then
|
||||||
elseif is_null then
|
Result := parse_string
|
||||||
Result := create {JSON_NULL}
|
when j_ARRAY_OPEN then
|
||||||
next
|
Result := parse_array
|
||||||
next
|
else
|
||||||
next
|
if c.is_digit or c = j_MINUS then
|
||||||
elseif is_true then
|
Result := parse_number
|
||||||
Result := create {JSON_BOOLEAN}.make_boolean (True)
|
elseif is_null then
|
||||||
next
|
Result := create {JSON_NULL}
|
||||||
next
|
next
|
||||||
next
|
next
|
||||||
elseif is_false then
|
next
|
||||||
Result := create {JSON_BOOLEAN}.make_boolean (False)
|
elseif is_true then
|
||||||
next
|
Result := create {JSON_BOOLEAN}.make_boolean (True)
|
||||||
next
|
next
|
||||||
next
|
next
|
||||||
next
|
next
|
||||||
else
|
elseif is_false then
|
||||||
is_parsed := False
|
Result := create {JSON_BOOLEAN}.make_boolean (False)
|
||||||
report_error ("JSON is not well formed in parse")
|
next
|
||||||
Result := Void
|
next
|
||||||
end
|
next
|
||||||
end
|
next
|
||||||
end
|
else
|
||||||
ensure
|
is_parsed := False
|
||||||
is_parsed_implies_result_not_void: is_parsed implies Result /= Void
|
report_error ("JSON is not well formed in parse")
|
||||||
end
|
Result := Void
|
||||||
|
end
|
||||||
parse_object: JSON_OBJECT is
|
end
|
||||||
-- object
|
end
|
||||||
-- {}
|
ensure
|
||||||
-- {"key" : "value" [,]}
|
is_parsed_implies_result_not_void: is_parsed implies Result /= Void
|
||||||
local
|
end
|
||||||
has_more: BOOLEAN
|
|
||||||
l_json_string: ?JSON_STRING
|
parse_object: JSON_OBJECT is
|
||||||
l_value: ?JSON_VALUE
|
-- object
|
||||||
do
|
-- {}
|
||||||
create Result.make
|
-- {"key" : "value" [,]}
|
||||||
-- check if is an empty object {}
|
local
|
||||||
next
|
has_more: BOOLEAN
|
||||||
skip_white_spaces
|
l_json_string: ?JSON_STRING
|
||||||
if actual = j_OBJECT_CLOSE then
|
l_value: ?JSON_VALUE
|
||||||
--is an empty object
|
do
|
||||||
else
|
create Result.make
|
||||||
-- a complex object {"key" : "value"}
|
-- check if is an empty object {}
|
||||||
previous
|
next
|
||||||
from has_more := True until not has_more loop
|
skip_white_spaces
|
||||||
next
|
if actual = j_OBJECT_CLOSE then
|
||||||
skip_white_spaces
|
--is an empty object
|
||||||
l_json_string := parse_string
|
else
|
||||||
next
|
-- a complex object {"key" : "value"}
|
||||||
skip_white_spaces
|
previous
|
||||||
if actual = ':' then
|
from has_more := True until not has_more loop
|
||||||
next
|
next
|
||||||
skip_white_spaces
|
skip_white_spaces
|
||||||
else
|
l_json_string := parse_string
|
||||||
is_parsed := False
|
next
|
||||||
report_error ("%N Input string is a not well formed JSON, expected: : found: " + actual.out)
|
skip_white_spaces
|
||||||
has_more := False
|
if actual = ':' then
|
||||||
end
|
next
|
||||||
|
skip_white_spaces
|
||||||
l_value := parse
|
else
|
||||||
if is_parsed and then (l_value /= Void and l_json_string /= Void) then
|
is_parsed := False
|
||||||
Result.put (l_value, l_json_string)
|
report_error ("%N Input string is a not well formed JSON, expected: : found: " + actual.out)
|
||||||
next
|
has_more := False
|
||||||
skip_white_spaces
|
end
|
||||||
if actual = j_OBJECT_CLOSE then
|
|
||||||
has_more := False
|
l_value := parse
|
||||||
elseif actual /= ',' then
|
if is_parsed and then (l_value /= Void and l_json_string /= Void) then
|
||||||
has_more := False
|
Result.put (l_value, l_json_string)
|
||||||
is_parsed := False
|
next
|
||||||
report_error ("JSON Object syntactically malformed expected , found: [" + actual.out + "]")
|
skip_white_spaces
|
||||||
end
|
if actual = j_OBJECT_CLOSE then
|
||||||
else
|
has_more := False
|
||||||
has_more := False
|
elseif actual /= ',' then
|
||||||
-- explain the error
|
has_more := False
|
||||||
end
|
is_parsed := False
|
||||||
end
|
report_error ("JSON Object syntactically malformed expected , found: [" + actual.out + "]")
|
||||||
end
|
end
|
||||||
end
|
else
|
||||||
|
has_more := False
|
||||||
parse_string: ?JSON_STRING is
|
-- explain the error
|
||||||
-- Parsed string
|
end
|
||||||
local
|
end
|
||||||
has_more: BOOLEAN
|
end
|
||||||
l_json_string: STRING
|
end
|
||||||
l_unicode: STRING
|
|
||||||
c: like actual
|
parse_string: ?JSON_STRING is
|
||||||
do
|
-- Parsed string
|
||||||
create l_json_string.make_empty
|
local
|
||||||
if actual = j_STRING then
|
has_more: BOOLEAN
|
||||||
from
|
l_json_string: STRING
|
||||||
has_more := True
|
l_unicode: STRING
|
||||||
until
|
c: like actual
|
||||||
not has_more
|
do
|
||||||
loop
|
create l_json_string.make_empty
|
||||||
next
|
if actual = j_STRING then
|
||||||
c := actual
|
from
|
||||||
if c = j_STRING then
|
has_more := True
|
||||||
has_more := False
|
until
|
||||||
elseif c = '%H' then
|
not has_more
|
||||||
next
|
loop
|
||||||
c := actual
|
next
|
||||||
if c = 'u' then
|
c := actual
|
||||||
create l_unicode.make_from_string ("\u")
|
if c = j_STRING then
|
||||||
l_unicode.append (read_unicode)
|
has_more := False
|
||||||
c := actual
|
elseif c = '%H' then
|
||||||
if is_a_valid_unicode (l_unicode) then
|
next
|
||||||
l_json_string.append (l_unicode)
|
c := actual
|
||||||
else
|
if c = 'u' then
|
||||||
has_more := False
|
create l_unicode.make_from_string ("\u")
|
||||||
is_parsed := False
|
l_unicode.append (read_unicode)
|
||||||
report_error ("Input String is not well formed JSON, expected a Unicode value, found [" + c.out + " ]")
|
c := actual
|
||||||
end
|
if is_a_valid_unicode (l_unicode) then
|
||||||
elseif (not is_special_character (c) and not is_special_control (c)) or c = '%N' then
|
l_json_string.append (l_unicode)
|
||||||
has_more := False
|
else
|
||||||
is_parsed := False
|
has_more := False
|
||||||
report_error ("Input String is not well formed JSON, found [" + c.out + " ]")
|
is_parsed := False
|
||||||
else
|
report_error ("Input String is not well formed JSON, expected a Unicode value, found [" + c.out + " ]")
|
||||||
l_json_string.append ("\")
|
end
|
||||||
l_json_string.append (c.out)
|
elseif (not is_special_character (c) and not is_special_control (c)) or c = '%N' then
|
||||||
end
|
has_more := False
|
||||||
else
|
is_parsed := False
|
||||||
if is_special_character (c) and c /= '/' then
|
report_error ("Input String is not well formed JSON, found [" + c.out + " ]")
|
||||||
has_more := False
|
else
|
||||||
is_parsed := False
|
l_json_string.append ("\")
|
||||||
report_error ("Input String is not well formed JSON, found [" + c.out + " ]")
|
l_json_string.append (c.out)
|
||||||
else
|
end
|
||||||
l_json_string.append_character (c)
|
else
|
||||||
end
|
if is_special_character (c) and c /= '/' then
|
||||||
end
|
has_more := False
|
||||||
end
|
is_parsed := False
|
||||||
create Result.make_json (l_json_string)
|
report_error ("Input String is not well formed JSON, found [" + c.out + " ]")
|
||||||
else
|
else
|
||||||
Result := Void
|
l_json_string.append_character (c)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
parse_array: JSON_ARRAY is
|
create Result.make_json (l_json_string)
|
||||||
-- array
|
else
|
||||||
-- []
|
Result := Void
|
||||||
-- [elements [,]]
|
end
|
||||||
local
|
end
|
||||||
flag: BOOLEAN
|
|
||||||
l_value: ?JSON_VALUE
|
parse_array: JSON_ARRAY is
|
||||||
c: like actual
|
-- array
|
||||||
do
|
-- []
|
||||||
create Result.make_array
|
-- [elements [,]]
|
||||||
--check if is an empty array []
|
local
|
||||||
next
|
flag: BOOLEAN
|
||||||
skip_white_spaces
|
l_value: ?JSON_VALUE
|
||||||
if actual = j_array_close then
|
c: like actual
|
||||||
--is an empty array
|
do
|
||||||
else
|
create Result.make_array
|
||||||
previous
|
--check if is an empty array []
|
||||||
from
|
next
|
||||||
flag := True
|
skip_white_spaces
|
||||||
until
|
if actual = j_array_close then
|
||||||
not flag
|
--is an empty array
|
||||||
loop
|
else
|
||||||
next
|
previous
|
||||||
skip_white_spaces
|
from
|
||||||
l_value := parse
|
flag := True
|
||||||
if is_parsed and then l_value /= Void then
|
until
|
||||||
Result.add (l_value)
|
not flag
|
||||||
next
|
loop
|
||||||
skip_white_spaces
|
next
|
||||||
c := actual
|
skip_white_spaces
|
||||||
if c = j_ARRAY_CLOSE then
|
l_value := parse
|
||||||
flag := False
|
if is_parsed and then l_value /= Void then
|
||||||
elseif c /= ',' then
|
Result.add (l_value)
|
||||||
flag := False
|
next
|
||||||
is_parsed := False
|
skip_white_spaces
|
||||||
report_error ("Array is not well formed JSON, found [" + c.out + " ]")
|
c := actual
|
||||||
end
|
if c = j_ARRAY_CLOSE then
|
||||||
else
|
flag := False
|
||||||
flag := False
|
elseif c /= ',' then
|
||||||
report_error ("Array is not well formed JSON, found [" + actual.out + " ]")
|
flag := False
|
||||||
end
|
is_parsed := False
|
||||||
end
|
report_error ("Array is not well formed JSON, found [" + c.out + " ]")
|
||||||
end
|
end
|
||||||
end
|
else
|
||||||
|
flag := False
|
||||||
parse_number: ?JSON_NUMBER is
|
report_error ("Array is not well formed JSON, found [" + actual.out + " ]")
|
||||||
-- Parsed number
|
end
|
||||||
local
|
end
|
||||||
sb: STRING
|
end
|
||||||
flag: BOOLEAN
|
end
|
||||||
is_integer: BOOLEAN
|
|
||||||
c: like actual
|
parse_number: ?JSON_NUMBER is
|
||||||
do
|
-- Parsed number
|
||||||
create sb.make_empty
|
local
|
||||||
sb.append_character (actual)
|
sb: STRING
|
||||||
|
flag: BOOLEAN
|
||||||
from
|
is_integer: BOOLEAN
|
||||||
flag := True
|
c: like actual
|
||||||
until
|
do
|
||||||
not flag
|
create sb.make_empty
|
||||||
loop
|
sb.append_character (actual)
|
||||||
next
|
|
||||||
c := actual
|
from
|
||||||
if not has_next or is_close_token (c)
|
flag := True
|
||||||
or c = ',' or c = '%N' or c = '%R'
|
until
|
||||||
then
|
not flag
|
||||||
flag := False
|
loop
|
||||||
previous
|
next
|
||||||
else
|
c := actual
|
||||||
sb.append_character (c)
|
if not has_next or is_close_token (c)
|
||||||
end
|
or c = ',' or c = '%N' or c = '%R'
|
||||||
end
|
then
|
||||||
|
flag := False
|
||||||
if is_a_valid_number (sb) then
|
previous
|
||||||
if sb.is_integer then
|
else
|
||||||
create Result.make_integer (sb.to_integer)
|
sb.append_character (c)
|
||||||
is_integer := True
|
end
|
||||||
elseif sb.is_double and not is_integer then
|
end
|
||||||
create Result.make_real (sb.to_double)
|
|
||||||
end
|
if is_a_valid_number (sb) then
|
||||||
else
|
if sb.is_integer then
|
||||||
is_parsed := False
|
create Result.make_integer (sb.to_integer)
|
||||||
report_error ("Expected a number, found: [ " + sb + " ]")
|
is_integer := True
|
||||||
end
|
elseif sb.is_double and not is_integer then
|
||||||
end
|
create Result.make_real (sb.to_double)
|
||||||
|
end
|
||||||
is_null: BOOLEAN is
|
else
|
||||||
-- Word at index represents null?
|
is_parsed := False
|
||||||
local
|
report_error ("Expected a number, found: [ " + sb + " ]")
|
||||||
l_null: STRING
|
end
|
||||||
l_string: STRING
|
end
|
||||||
do
|
|
||||||
l_null := null_id
|
is_null: BOOLEAN is
|
||||||
l_string := json_substring (index,index + l_null.count - 1)
|
-- Word at index represents null?
|
||||||
if l_string.is_equal (l_null) then
|
local
|
||||||
Result := True
|
l_null: STRING
|
||||||
end
|
l_string: STRING
|
||||||
end
|
do
|
||||||
|
l_null := null_id
|
||||||
is_false: BOOLEAN is
|
l_string := json_substring (index,index + l_null.count - 1)
|
||||||
-- Word at index represents false?
|
if l_string.is_equal (l_null) then
|
||||||
local
|
Result := True
|
||||||
l_false: STRING
|
end
|
||||||
l_string: STRING
|
end
|
||||||
do
|
|
||||||
l_false := false_id
|
is_false: BOOLEAN is
|
||||||
l_string := json_substring (index, index + l_false.count - 1)
|
-- Word at index represents false?
|
||||||
if l_string.is_equal (l_false) then
|
local
|
||||||
Result := True
|
l_false: STRING
|
||||||
end
|
l_string: STRING
|
||||||
end
|
do
|
||||||
|
l_false := false_id
|
||||||
is_true: BOOLEAN is
|
l_string := json_substring (index, index + l_false.count - 1)
|
||||||
-- Word at index represents true?
|
if l_string.is_equal (l_false) then
|
||||||
local
|
Result := True
|
||||||
l_true: STRING
|
end
|
||||||
l_string: STRING
|
end
|
||||||
do
|
|
||||||
l_true := true_id
|
is_true: BOOLEAN is
|
||||||
l_string := json_substring (index,index + l_true.count - 1)
|
-- Word at index represents true?
|
||||||
if l_string.is_equal (l_true) then
|
local
|
||||||
Result := True
|
l_true: STRING
|
||||||
end
|
l_string: STRING
|
||||||
end
|
do
|
||||||
|
l_true := true_id
|
||||||
read_unicode: STRING is
|
l_string := json_substring (index,index + l_true.count - 1)
|
||||||
-- Read unicode and return value
|
if l_string.is_equal (l_true) then
|
||||||
local
|
Result := True
|
||||||
i: INTEGER
|
end
|
||||||
do
|
end
|
||||||
create Result.make_empty
|
|
||||||
from
|
read_unicode: STRING is
|
||||||
i := 1
|
-- Read unicode and return value
|
||||||
until
|
local
|
||||||
i > 4 or not has_next
|
i: INTEGER
|
||||||
loop
|
do
|
||||||
next
|
create Result.make_empty
|
||||||
Result.append_character (actual)
|
from
|
||||||
i := i + 1
|
i := 1
|
||||||
end
|
until
|
||||||
end
|
i > 4 or not has_next
|
||||||
|
loop
|
||||||
feature {NONE} -- Implementation
|
next
|
||||||
|
Result.append_character (actual)
|
||||||
is_a_valid_number (a_number: STRING): BOOLEAN is
|
i := i + 1
|
||||||
-- is 'a_number' a valid number based on this regular expression
|
end
|
||||||
-- "-?(?: 0|[1-9]\d+)(?: \.\d+)?(?: [eE][+-]?\d+)?\b"?
|
end
|
||||||
do
|
|
||||||
Result := a_number.is_real_sequence
|
feature {NONE} -- Implementation
|
||||||
end
|
|
||||||
|
is_a_valid_number (a_number: STRING): BOOLEAN is
|
||||||
is_a_valid_unicode (a_unicode: STRING): BOOLEAN is
|
-- is 'a_number' a valid number based on this regular expression
|
||||||
-- is 'a_unicode' a valid unicode based on this regular expression
|
-- "-?(?: 0|[1-9]\d+)(?: \.\d+)?(?: [eE][+-]?\d+)?\b"?
|
||||||
-- "\\u[0-9a-fA-F]{4}"
|
do
|
||||||
local
|
Result := a_number.is_real_sequence
|
||||||
i: INTEGER
|
end
|
||||||
do
|
|
||||||
if
|
is_a_valid_unicode (a_unicode: STRING): BOOLEAN is
|
||||||
a_unicode.count = 6 and then
|
-- is 'a_unicode' a valid unicode based on this regular expression
|
||||||
a_unicode.item (1) = '\' and then
|
-- "\\u[0-9a-fA-F]{4}"
|
||||||
a_unicode.item (2) = 'u'
|
local
|
||||||
then
|
i: INTEGER
|
||||||
from
|
do
|
||||||
Result := True
|
if
|
||||||
i := 3
|
a_unicode.count = 6 and then
|
||||||
until
|
a_unicode.item (1) = '\' and then
|
||||||
i > 6
|
a_unicode.item (2) = 'u'
|
||||||
loop
|
then
|
||||||
inspect a_unicode.item (i)
|
from
|
||||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
Result := True
|
||||||
else
|
i := 3
|
||||||
Result := False
|
until
|
||||||
i := 6
|
i > 6
|
||||||
end
|
loop
|
||||||
i := i + 1
|
inspect a_unicode.item (i)
|
||||||
end
|
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||||
end
|
else
|
||||||
end
|
Result := False
|
||||||
|
i := 6
|
||||||
extra_elements: BOOLEAN is
|
end
|
||||||
-- has more elements?
|
i := i + 1
|
||||||
local
|
end
|
||||||
c: like actual
|
end
|
||||||
do
|
end
|
||||||
if has_next then
|
|
||||||
next
|
extra_elements: BOOLEAN is
|
||||||
end
|
-- has more elements?
|
||||||
from
|
local
|
||||||
c := actual
|
c: like actual
|
||||||
until
|
do
|
||||||
c /= ' ' or c /= '%R' or c /= '%U' or c /= '%T' or c /= '%N' or not has_next
|
if has_next then
|
||||||
loop
|
next
|
||||||
next
|
end
|
||||||
end
|
from
|
||||||
Result := has_next
|
c := actual
|
||||||
end
|
until
|
||||||
|
c /= ' ' or c /= '%R' or c /= '%U' or c /= '%T' or c /= '%N' or not has_next
|
||||||
feature {NONE} -- Constants
|
loop
|
||||||
|
next
|
||||||
false_id: STRING is "false"
|
end
|
||||||
|
Result := has_next
|
||||||
true_id: STRING is "true"
|
end
|
||||||
|
|
||||||
null_id: STRING is "null"
|
is_valid_start_symbol : BOOLEAN
|
||||||
|
-- expecting `{' or `[' as start symbol
|
||||||
|
do
|
||||||
end
|
Result := representation.starts_with ("{") or representation.starts_with ("[")
|
||||||
|
end
|
||||||
|
|
||||||
|
feature {NONE} -- Constants
|
||||||
|
|
||||||
|
false_id: STRING is "false"
|
||||||
|
|
||||||
|
true_id: STRING is "true"
|
||||||
|
|
||||||
|
null_id: STRING is "null"
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user