From d67e01eea6c9028634edf5a04657edcbca291e89 Mon Sep 17 00:00:00 2001 From: jfiat Date: Fri, 13 Jan 2012 17:26:23 +0000 Subject: [PATCH] Better support for special character and unicode (\n \r \" ... and \uXXXX where XXXX is an hexadecimal value) Added features to JSON_STRING - make_json_from_string_32 (READABLE_STRING_32) - escaped_string_8: STRING_8 - escaped_string_32: STRING_32 Added associated autotests --- library/kernel/ejson.e | 2 +- library/kernel/json_string.e | 228 ++++- library/kernel/scanner/json_parser.e | 1028 ++++++++++----------- test/autotest/test_suite/test_json_core.e | 58 +- 4 files changed, 783 insertions(+), 533 deletions(-) diff --git a/library/kernel/ejson.e b/library/kernel/ejson.e index 9a828b17..de9058d5 100644 --- a/library/kernel/ejson.e +++ b/library/kernel/ejson.e @@ -117,7 +117,7 @@ feature -- Access Result := jn.item.to_double end elseif attached {JSON_STRING} a_value as js then - create {STRING_32} Result.make_from_string (js.item) + create {STRING_32} Result.make_from_string (js.unescaped_string_32) elseif attached {JSON_ARRAY} a_value as ja then from create ll.make diff --git a/library/kernel/json_string.e b/library/kernel/json_string.e index 4df0a1e3..61115f3c 100644 --- a/library/kernel/json_string.e +++ b/library/kernel/json_string.e @@ -22,7 +22,9 @@ inherit end create - make_json + make_json, + make_json_from_string_32, + make_with_escaped_json convert make_json ({READABLE_STRING_8, STRING_8, IMMUTABLE_STRING_8}) @@ -34,7 +36,23 @@ feature {NONE} -- Initialization require item_not_void: an_item /= Void do - item := escaped_json_string (an_item) + make_with_escaped_json (escaped_json_string (an_item)) + end + + make_json_from_string_32 (an_item: READABLE_STRING_32) + -- Initialize. + require + item_not_void: an_item /= Void + do + make_with_escaped_json (escaped_json_string_32 (an_item)) + end + + make_with_escaped_json (an_item: READABLE_STRING_8) + -- Initialize with an_item already escaped + require + item_not_void: an_item /= Void + do + item := an_item end feature -- Access @@ -42,6 +60,99 @@ feature -- Access item: STRING -- Contents + unescaped_string: STRING_8 + local + s: like item + i, n: INTEGER + c: CHARACTER + do + s := item + n := s.count + create Result.make (n) + from i := 1 until i > n loop + c := s[i] + if c = '\' then + if i < n then + inspect s[i+1] + when '\' then + Result.append_character ('\') + i := i + 2 + when '%"' then + Result.append_character ('%"') + i := i + 2 + when 'n' then + Result.append_character ('%N') + i := i + 2 + when 'r' then + Result.append_character ('%R') + i := i + 2 + when 'u' then + --| Leave unicode \uXXXX unescaped + Result.append_character ('\') + i := i + 1 + else + Result.append_character ('\') + i := i + 1 + end + else + Result.append_character ('\') + i := i + 1 + end + else + Result.append_character (c) + i := i + 1 + end + end + end + + unescaped_string_32: STRING_32 + local + s: like item + i, n: INTEGER + c: CHARACTER + hex: STRING + do + s := item + n := s.count + create Result.make (n) + from i := 1 until i > n loop + c := s[i] + if c = '\' then + if i < n then + inspect s[i+1] + when '\' then + Result.append_character ('\') + i := i + 2 + when '%"' then + Result.append_character ('%"') + i := i + 2 + when 'n' then + Result.append_character ('%N') + i := i + 2 + when 'r' then + Result.append_character ('%R') + i := i + 2 + when 'u' then + hex := s.substring (i+2, i+2+4 - 1) + if hex.count = 4 then + Result.append_code (hexadecimal_to_natural_32 (hex)) + end + i := i + 2 + 4 + else + Result.append_character ('\') + i := i + 1 + end + else + Result.append_character ('\') + i := i + 1 + end + else + Result.append_character (c.to_character_32) + i := i + 1 + end + end + end + representation: STRING do Result := "%"" @@ -95,15 +206,116 @@ feature -- Status report feature {NONE} -- Implementation - escaped_json_string (s: READABLE_STRING_8): STRING + is_hexadecimal (s: READABLE_STRING_8): BOOLEAN + do + Result := across s as scur all scur.item.is_hexa_digit end + end + + hexadecimal_to_natural_32 (s: READABLE_STRING_8): NATURAL_32 + -- Hexadecimal string `s' converted to NATURAL_32 value + require + s_not_void: s /= Void + is_hexadecimal: is_hexadecimal (s) + local + i, nb: INTEGER + char: CHARACTER + do + nb := s.count + + if nb >= 2 and then s.item (2) = 'x' then + i := 3 + else + i := 1 + end + + from + until + i > nb + loop + Result := Result * 16 + char := s.item (i) + if char >= '0' and then char <= '9' then + Result := Result + (char |-| '0').to_natural_32 + else + Result := Result + (char.lower |-| 'a' + 10).to_natural_32 + end + i := i + 1 + end + end + + escaped_json_string (s: READABLE_STRING_8): STRING_8 -- JSON string with '"' and '\' characters escaped require s_not_void: s /= Void - do - Result := s.twin - Result.replace_substring_all ("\", "\\") - Result.replace_substring_all ("%"", "\%"") - end + local + i, n: INTEGER + c: CHARACTER_8 + do + n := s.count + create Result.make (n + n // 10) + from i := 1 until i > n loop + c := s.item (i) + inspect c + when '%"' then Result.append_string ("\%"") + when '\' then Result.append_string ("\\") + when '%R' then Result.append_string ("\r") + when '%N' then Result.append_string ("\n") + else + Result.extend (c) + end + i := i + 1 + end + end + + escaped_json_string_32 (s: READABLE_STRING_32): STRING_8 + -- JSON string with '"' and '\' characters and unicode escaped + require + s_not_void: s /= Void + local + i, j, n: INTEGER + uc: CHARACTER_32 + c: CHARACTER_8 + h: STRING_8 + do + n := s.count + create Result.make (n + n // 10) + from i := 1 until i > n loop + uc := s.item (i) + if uc.is_character_8 then + c := uc.to_character_8 + inspect c + when '%"' then Result.append_string ("\%"") + when '\' then Result.append_string ("\\") + when '%R' then Result.append_string ("\r") + when '%N' then Result.append_string ("\n") + else + Result.extend (c) + end + else + Result.append ("\u") + h := uc.code.to_hex_string + -- Remove first 0 and keep 4 hexa digit + from + j := 1 + until + h.count = 4 or (j <= h.count and then h.item (j) /= '0') + loop + j := j + 1 + end + h := h.substring (j, h.count) + + from + until + h.count >= 4 + loop + h.prepend_integer (0) + end + check h.count = 4 end + Result.append (h) + end + i := i + 1 + end + end invariant value_not_void: item /= Void diff --git a/library/kernel/scanner/json_parser.e b/library/kernel/scanner/json_parser.e index a53e5a32..3eaa7388 100644 --- a/library/kernel/scanner/json_parser.e +++ b/library/kernel/scanner/json_parser.e @@ -1,515 +1,513 @@ -note - - description: "Parse serialized JSON data" - author: "jvelilla" - date: "2008/08/24" - revision: "Revision 0.1" - -class - JSON_PARSER - -inherit - JSON_READER - JSON_TOKENS - -create - make_parser - -feature {NONE} -- Initialize - - make_parser (a_json: STRING) - -- Initialize. - require - json_not_empty: a_json /= Void and then not a_json.is_empty - do - make (a_json) - is_parsed := True - create errors.make - end - -feature -- Status report - - is_parsed: BOOLEAN - -- Is parsed? - - errors: LINKED_LIST [STRING] - -- Current errors - - current_errors: STRING - -- Current errors as string - do - create Result.make_empty - from - errors.start - until - errors.after - loop - Result.append_string (errors.item + "%N") - errors.forth - end - end - -feature -- Element change - - report_error (e: STRING) - -- Report error `e' - require - e_not_void: e /= Void - do - errors.force (e) - end - -feature -- Commands - - parse_json: detachable JSON_VALUE - -- Parse JSON data `representation' - -- start ::= object | array - do - if is_valid_start_symbol then - Result := parse - if extra_elements then - is_parsed := False - end - else - is_parsed := False - report_error ("Syntax error unexpected token, expecting `{' or `['") - end - end - - parse: detachable JSON_VALUE - -- Parse JSON data `representation' - local - c: CHARACTER - do - if is_parsed then - skip_white_spaces - c := actual - inspect c - when j_OBJECT_OPEN then - Result := parse_object - when j_STRING then - Result := parse_string - when j_ARRAY_OPEN then - Result := parse_array - else - if c.is_digit or c = j_MINUS then - Result := parse_number - elseif is_null then - Result := create {JSON_NULL} - next - next - next - elseif is_true then - Result := create {JSON_BOOLEAN}.make_boolean (True) - next - next - next - elseif is_false then - Result := create {JSON_BOOLEAN}.make_boolean (False) - next - next - next - next - else - is_parsed := False - report_error ("JSON is not well formed in parse") - Result := Void - end - end - end - ensure - is_parsed_implies_result_not_void: is_parsed implies Result /= Void - end - - parse_object: JSON_OBJECT - -- object - -- {} - -- {"key" : "value" [,]} - local - has_more: BOOLEAN - l_json_string: detachable JSON_STRING - l_value: detachable JSON_VALUE - do - create Result.make - -- check if is an empty object {} - next - skip_white_spaces - if actual = j_OBJECT_CLOSE then - --is an empty object - else - -- a complex object {"key" : "value"} - previous - from has_more := True until not has_more loop - next - skip_white_spaces - l_json_string := parse_string - next - skip_white_spaces - if actual = ':' then - next - skip_white_spaces - else - is_parsed := False - report_error ("%N Input string is a not well formed JSON, expected: : found: " + actual.out) - has_more := False - end - - l_value := parse - if is_parsed and then (l_value /= Void and l_json_string /= Void) then - Result.put (l_value, l_json_string) - next - skip_white_spaces - if actual = j_OBJECT_CLOSE then - has_more := False - elseif actual /= ',' then - has_more := False - is_parsed := False - report_error ("JSON Object syntactically malformed expected , found: [" + actual.out + "]") - end - else - has_more := False - -- explain the error - end - end - end - end - - parse_string: detachable JSON_STRING - -- Parsed string - local - has_more: BOOLEAN - l_json_string: STRING - l_unicode: STRING - c: like actual - do - create l_json_string.make_empty - if actual = j_STRING then - from - has_more := True - until - not has_more - loop - next - c := actual - if c = j_STRING then - has_more := False - elseif c = '%H' then - next - c := actual - if c = 'u' then - create l_unicode.make_from_string ("\u") - l_unicode.append (read_unicode) - c := actual - if is_valid_unicode (l_unicode) then - l_json_string.append (l_unicode) - else - has_more := False - is_parsed := False - report_error ("Input String is not well formed JSON, expected a Unicode value, found [" + c.out + " ]") - end - elseif (not is_special_character (c) and not is_special_control (c)) or c = '%N' then - has_more := False - is_parsed := False - report_error ("Input String is not well formed JSON, found [" + c.out + " ]") - else - l_json_string.append ("\") - l_json_string.append (c.out) - end - else - if is_special_character (c) and c /= '/' then - has_more := False - is_parsed := False - report_error ("Input String is not well formed JSON, found [" + c.out + " ]") - else - l_json_string.append_character (c) - end - end - end - create Result.make_json (l_json_string) - else - Result := Void - end - end - - parse_array: JSON_ARRAY - -- array - -- [] - -- [elements [,]] - local - flag: BOOLEAN - l_value: detachable JSON_VALUE - c: like actual - do - create Result.make_array - --check if is an empty array [] - next - skip_white_spaces - if actual = j_array_close then - --is an empty array - else - previous - from - flag := True - until - not flag - loop - next - skip_white_spaces - l_value := parse - if is_parsed and then l_value /= Void then - Result.add (l_value) - next - skip_white_spaces - c := actual - if c = j_ARRAY_CLOSE then - flag := False - elseif c /= ',' then - flag := False - is_parsed := False - report_error ("Array is not well formed JSON, found [" + c.out + " ]") - end - else - flag := False - report_error ("Array is not well formed JSON, found [" + actual.out + " ]") - end - end - end - end - - parse_number: detachable JSON_NUMBER - -- Parsed number - local - sb: STRING - flag: BOOLEAN - is_integer: BOOLEAN - c: like actual - do - create sb.make_empty - sb.append_character (actual) - - from - flag := True - until - not flag - loop - next - c := actual - if not has_next or is_close_token (c) - or c = ',' or c = '%N' or c = '%R' - then - flag := False - previous - else - sb.append_character (c) - end - end - - if is_valid_number (sb) then - if sb.is_integer then - create Result.make_integer (sb.to_integer) - is_integer := True - elseif sb.is_double and not is_integer then - create Result.make_real (sb.to_double) - end - else - is_parsed := False - report_error ("Expected a number, found: [ " + sb + " ]") - end - end - - is_null: BOOLEAN - -- Word at index represents null? - local - l_null: STRING - l_string: STRING - do - l_null := null_id - l_string := json_substring (index,index + l_null.count - 1) - if l_string.is_equal (l_null) then - Result := True - end - end - - is_false: BOOLEAN - -- Word at index represents false? - local - l_false: STRING - l_string: STRING - do - l_false := false_id - l_string := json_substring (index, index + l_false.count - 1) - if l_string.is_equal (l_false) then - Result := True - end - end - - is_true: BOOLEAN - -- Word at index represents true? - local - l_true: STRING - l_string: STRING - do - l_true := true_id - l_string := json_substring (index,index + l_true.count - 1) - if l_string.is_equal (l_true) then - Result := True - end - end - - read_unicode: STRING - -- Read unicode and return value - local - i: INTEGER - do - create Result.make_empty - from - i := 1 - until - i > 4 or not has_next - loop - next - Result.append_character (actual) - i := i + 1 - end - end - -feature {NONE} -- Implementation - - is_valid_number (a_number: STRING): BOOLEAN - -- is 'a_number' a valid number based on this regular expression - -- "-?(?: 0|[1-9]\d+)(?: \.\d+)?(?: [eE][+-]?\d+)?\b"? - local - s: detachable STRING - c: CHARACTER - i,n: INTEGER - do - create s.make_empty - n := a_number.count - if n = 0 then - Result := False - else - Result := True - i := 1 - --| "-?" - c := a_number[i] - if c = '-' then - s.extend (c); i := i + 1; c := a_number[i] - end - --| "0|[1-9]\d* - if c.is_digit then - if c = '0' then - --| "0" - s.extend (c); i := i + 1; c := a_number[i] - else - --| "[1-9]" - s.extend (c); i := i + 1; c := a_number[i] - --| "\d*" - from until i > n or not c.is_digit loop - s.extend (c); i := i + 1; c := a_number[i] - end - end - end - end - if Result then - --| "(\.\d+)?" - if c = '.' then - --| "\.\d+" = "\.\d\d*" - s.extend (c); i := i + 1; c := a_number[i] - if c.is_digit then - from until i > n or not c.is_digit loop - s.extend (c); i := i + 1; c := a_number[i] - end - else - Result := False --| expecting digit - end - end - end - if Result then --| "(?:[eE][+-]?\d+)?\b" - if c = 'e' or c = 'E' then - --| "[eE][+-]?\d+" - s.extend (c); i := i + 1; c := a_number[i] - if c = '+' or c = '-' then - s.extend (c); i := i + 1; c := a_number[i] - end - if c.is_digit then - from until i > n or not c.is_digit loop - s.extend (c); i := i + 1; c := a_number[i] - end - else - Result := False --| expecting digit - end - end - end - if Result then --| "\b" - from until i > n or not c.is_space loop - s.extend (c); i := i + 1; c := a_number[i] - end - Result := i > n - if Result then - Result := s.same_string (a_number) - end - end - end - - is_valid_unicode (a_unicode: STRING): BOOLEAN - -- is 'a_unicode' a valid unicode based on this regular expression - -- "\\u[0-9a-fA-F]{4}" - local - i: INTEGER - do - if - a_unicode.count = 6 and then - a_unicode.item (1) = '\' and then - a_unicode.item (2) = 'u' - then - from - Result := True - i := 3 - until - i > 6 - loop - inspect a_unicode.item (i) - when '0'..'9', 'a'..'f', 'A'..'F' then - else - Result := False - i := 6 - end - i := i + 1 - end - end - end - - extra_elements: BOOLEAN - -- has more elements? - local - c: like actual - do - if has_next then - next - end - from - c := actual - until - c /= ' ' or c /= '%R' or c /= '%U' or c /= '%T' or c /= '%N' or not has_next - loop - next - end - Result := has_next - end - - is_valid_start_symbol : BOOLEAN - -- expecting `{' or `[' as start symbol - do - Result := representation.starts_with ("{") or representation.starts_with ("[") - end - -feature {NONE} -- Constants - - false_id: STRING = "false" - - true_id: STRING = "true" - - null_id: STRING = "null" - - -end +note + + description: "Parse serialized JSON data" + author: "jvelilla" + date: "2008/08/24" + revision: "Revision 0.1" + +class + JSON_PARSER + +inherit + JSON_READER + JSON_TOKENS + +create + make_parser + +feature {NONE} -- Initialize + + make_parser (a_json: STRING) + -- Initialize. + require + json_not_empty: a_json /= Void and then not a_json.is_empty + do + make (a_json) + is_parsed := True + create errors.make + end + +feature -- Status report + + is_parsed: BOOLEAN + -- Is parsed? + + errors: LINKED_LIST [STRING] + -- Current errors + + current_errors: STRING + -- Current errors as string + do + create Result.make_empty + from + errors.start + until + errors.after + loop + Result.append_string (errors.item + "%N") + errors.forth + end + end + +feature -- Element change + + report_error (e: STRING) + -- Report error `e' + require + e_not_void: e /= Void + do + errors.force (e) + end + +feature -- Commands + + parse_json: detachable JSON_VALUE + -- Parse JSON data `representation' + -- start ::= object | array + do + if is_valid_start_symbol then + Result := parse + if extra_elements then + is_parsed := False + end + else + is_parsed := False + report_error ("Syntax error unexpected token, expecting `{' or `['") + end + end + + parse: detachable JSON_VALUE + -- Parse JSON data `representation' + local + c: CHARACTER + do + if is_parsed then + skip_white_spaces + c := actual + inspect c + when j_OBJECT_OPEN then + Result := parse_object + when j_STRING then + Result := parse_string + when j_ARRAY_OPEN then + Result := parse_array + else + if c.is_digit or c = j_MINUS then + Result := parse_number + elseif is_null then + Result := create {JSON_NULL} + next + next + next + elseif is_true then + Result := create {JSON_BOOLEAN}.make_boolean (True) + next + next + next + elseif is_false then + Result := create {JSON_BOOLEAN}.make_boolean (False) + next + next + next + next + else + is_parsed := False + report_error ("JSON is not well formed in parse") + Result := Void + end + end + end + ensure + is_parsed_implies_result_not_void: is_parsed implies Result /= Void + end + + parse_object: JSON_OBJECT + -- object + -- {} + -- {"key" : "value" [,]} + local + has_more: BOOLEAN + l_json_string: detachable JSON_STRING + l_value: detachable JSON_VALUE + do + create Result.make + -- check if is an empty object {} + next + skip_white_spaces + if actual = j_OBJECT_CLOSE then + --is an empty object + else + -- a complex object {"key" : "value"} + previous + from has_more := True until not has_more loop + next + skip_white_spaces + l_json_string := parse_string + next + skip_white_spaces + if actual = ':' then + next + skip_white_spaces + else + is_parsed := False + report_error ("%N Input string is a not well formed JSON, expected: : found: " + actual.out) + has_more := False + end + + l_value := parse + if is_parsed and then (l_value /= Void and l_json_string /= Void) then + Result.put (l_value, l_json_string) + next + skip_white_spaces + if actual = j_OBJECT_CLOSE then + has_more := False + elseif actual /= ',' then + has_more := False + is_parsed := False + report_error ("JSON Object syntactically malformed expected , found: [" + actual.out + "]") + end + else + has_more := False + -- explain the error + end + end + end + end + + parse_string: detachable JSON_STRING + -- Parsed string + local + has_more: BOOLEAN + l_json_string: STRING + l_unicode: STRING + c: like actual + do + create l_json_string.make_empty + if actual = j_STRING then + from + has_more := True + until + not has_more + loop + next + c := actual + if c = j_STRING then + has_more := False + elseif c = '%H' then + next + c := actual + if c = 'u' then + create l_unicode.make_from_string ("\u") + l_unicode.append (read_unicode) + c := actual + if is_valid_unicode (l_unicode) then + l_json_string.append (l_unicode) + else + has_more := False + is_parsed := False + report_error ("Input String is not well formed JSON, expected a Unicode value, found [" + c.out + " ]") + end + elseif (not is_special_character (c) and not is_special_control (c)) or c = '%N' then + has_more := False + is_parsed := False + report_error ("Input String is not well formed JSON, found [" + c.out + " ]") + else + l_json_string.append_character ('\') + l_json_string.append_character (c) + end + else + if is_special_character (c) and c /= '/' then + has_more := False + is_parsed := False + report_error ("Input String is not well formed JSON, found [" + c.out + " ]") + else + l_json_string.append_character (c) + end + end + end + create Result.make_with_escaped_json (l_json_string) + else + Result := Void + end + end + + parse_array: JSON_ARRAY + -- array + -- [] + -- [elements [,]] + local + flag: BOOLEAN + l_value: detachable JSON_VALUE + c: like actual + do + create Result.make_array + --check if is an empty array [] + next + skip_white_spaces + if actual = j_array_close then + --is an empty array + else + previous + from + flag := True + until + not flag + loop + next + skip_white_spaces + l_value := parse + if is_parsed and then l_value /= Void then + Result.add (l_value) + next + skip_white_spaces + c := actual + if c = j_ARRAY_CLOSE then + flag := False + elseif c /= ',' then + flag := False + is_parsed := False + report_error ("Array is not well formed JSON, found [" + c.out + " ]") + end + else + flag := False + report_error ("Array is not well formed JSON, found [" + actual.out + " ]") + end + end + end + end + + parse_number: detachable JSON_NUMBER + -- Parsed number + local + sb: STRING + flag: BOOLEAN + is_integer: BOOLEAN + c: like actual + do + create sb.make_empty + sb.append_character (actual) + + from + flag := True + until + not flag + loop + next + c := actual + if not has_next or is_close_token (c) + or c = ',' or c = '%N' or c = '%R' + then + flag := False + previous + else + sb.append_character (c) + end + end + + if is_valid_number (sb) then + if sb.is_integer then + create Result.make_integer (sb.to_integer) + is_integer := True + elseif sb.is_double and not is_integer then + create Result.make_real (sb.to_double) + end + else + is_parsed := False + report_error ("Expected a number, found: [ " + sb + " ]") + end + end + + is_null: BOOLEAN + -- Word at index represents null? + local + l_null: STRING + l_string: STRING + do + l_null := null_id + l_string := json_substring (index,index + l_null.count - 1) + if l_string.is_equal (l_null) then + Result := True + end + end + + is_false: BOOLEAN + -- Word at index represents false? + local + l_false: STRING + l_string: STRING + do + l_false := false_id + l_string := json_substring (index, index + l_false.count - 1) + if l_string.is_equal (l_false) then + Result := True + end + end + + is_true: BOOLEAN + -- Word at index represents true? + local + l_true: STRING + l_string: STRING + do + l_true := true_id + l_string := json_substring (index,index + l_true.count - 1) + if l_string.is_equal (l_true) then + Result := True + end + end + + read_unicode: STRING + -- Read unicode and return value + local + i: INTEGER + do + create Result.make_empty + from + i := 1 + until + i > 4 or not has_next + loop + next + Result.append_character (actual) + i := i + 1 + end + end + +feature {NONE} -- Implementation + + is_valid_number (a_number: STRING): BOOLEAN + -- is 'a_number' a valid number based on this regular expression + -- "-?(?: 0|[1-9]\d+)(?: \.\d+)?(?: [eE][+-]?\d+)?\b"? + local + s: detachable STRING + c: CHARACTER + i,n: INTEGER + do + create s.make_empty + n := a_number.count + if n = 0 then + Result := False + else + Result := True + i := 1 + --| "-?" + c := a_number[i] + if c = '-' then + s.extend (c); i := i + 1; c := a_number[i] + end + --| "0|[1-9]\d* + if c.is_digit then + if c = '0' then + --| "0" + s.extend (c); i := i + 1; c := a_number[i] + else + --| "[1-9]" + s.extend (c); i := i + 1; c := a_number[i] + --| "\d*" + from until i > n or not c.is_digit loop + s.extend (c); i := i + 1; c := a_number[i] + end + end + end + end + if Result then + --| "(\.\d+)?" + if c = '.' then + --| "\.\d+" = "\.\d\d*" + s.extend (c); i := i + 1; c := a_number[i] + if c.is_digit then + from until i > n or not c.is_digit loop + s.extend (c); i := i + 1; c := a_number[i] + end + else + Result := False --| expecting digit + end + end + end + if Result then --| "(?:[eE][+-]?\d+)?\b" + if c = 'e' or c = 'E' then + --| "[eE][+-]?\d+" + s.extend (c); i := i + 1; c := a_number[i] + if c = '+' or c = '-' then + s.extend (c); i := i + 1; c := a_number[i] + end + if c.is_digit then + from until i > n or not c.is_digit loop + s.extend (c); i := i + 1; c := a_number[i] + end + else + Result := False --| expecting digit + end + end + end + if Result then --| "\b" + from until i > n or not c.is_space loop + s.extend (c); i := i + 1; c := a_number[i] + end + Result := i > n and then s.same_string (a_number) + end + end + + is_valid_unicode (a_unicode: STRING): BOOLEAN + -- is 'a_unicode' a valid unicode based on this regular expression + -- "\\u[0-9a-fA-F]{4}" + local + i: INTEGER + do + if + a_unicode.count = 6 and then + a_unicode[1] = '\' and then + a_unicode[2] = 'u' + then + from + Result := True + i := 3 + until + i > 6 or Result = False + loop + inspect a_unicode[i] + when '0'..'9', 'a'..'f', 'A'..'F' then + else + Result := False + end + i := i + 1 + end + end + end + + extra_elements: BOOLEAN + -- has more elements? + local + c: like actual + do + if has_next then + next + end + from + c := actual + until + c /= ' ' or c /= '%R' or c /= '%U' or c /= '%T' or c /= '%N' or not has_next + loop + next + end + Result := has_next + end + + is_valid_start_symbol : BOOLEAN + -- expecting `{' or `[' as start symbol + do + if attached representation as s and then s.count > 0 then + Result := s[1] = '{' or s[1] = '[' + end + end + +feature {NONE} -- Constants + + false_id: STRING = "false" + + true_id: STRING = "true" + + null_id: STRING = "null" + + +end diff --git a/test/autotest/test_suite/test_json_core.e b/test/autotest/test_suite/test_json_core.e index ca8434f5..ad8326b9 100644 --- a/test/autotest/test_suite/test_json_core.e +++ b/test/autotest/test_suite/test_json_core.e @@ -510,7 +510,6 @@ feature -- Test local c: CHARACTER js: detachable JSON_STRING - ucs: detachable STRING_32 jrep: STRING parser: JSON_PARSER do @@ -532,9 +531,8 @@ feature -- Test js := Void js ?= parser.parse assert ("js /= Void", js /= Void) - ucs ?= json.object (js, Void) - if attached ucs as l_ucs then - assert ("ucs.string.is_equal (%"a%")", l_ucs.string.is_equal ("a")) + if attached {STRING_32} json.object (js, Void) as ucs then + assert ("ucs.string.is_equal (%"a%")", ucs.string.is_equal ("a")) end end @@ -543,7 +541,6 @@ feature -- Test local s: STRING js: detachable JSON_STRING - ucs: detachable STRING_32 jrep: STRING parser: JSON_PARSER do @@ -565,11 +562,9 @@ feature -- Test js := Void js ?= parser.parse assert ("js /= Void", js /= Void) - ucs ?= json.object (js, Void) - if attached ucs as l_ucs then - assert ("ucs.string.is_equal (%"foobar%")", ucs.string.is_equal ("foobar")) + if attached {STRING_32} json.object (js, Void) as l_ucs then + assert ("ucs.string.is_equal (%"foobar%")", l_ucs.string.is_equal ("foobar")) end - end test_json_string_and_uc_string @@ -602,7 +597,52 @@ feature -- Test if attached ucs as l_ucs then assert ("ucs.string.is_equal (%"foobar%")", l_ucs.string.is_equal ("foobar")) end + end + test_json_string_and_special_characters + local + js: detachable JSON_STRING + s: detachable STRING_8 + ucs: detachable STRING_32 + jrep: STRING + parser: JSON_PARSER + do + create s.make_from_string ("foo\bar") + create js.make_json (s) + + assert ("js.representation.same_string (%"%"foo\\bar%"%")", js.representation.same_string ("%"foo\\bar%"")) + + -- Eiffel value -> JSON value -> JSON representation with factory + js ?= json.value (s) + assert ("js /= Void", js /= Void) + if js /= Void then + assert ("js.representation.is_equal (%"%"foobar%"%")", js.representation.same_string ("%"foo\\bar%"")) + end + + -- JSON representation -> JSON value -> Eiffel value + jrep := "%"foo\\bar%"" + create parser.make_parser (jrep) + js ?= parser.parse + assert ("js /= Void", js /= Void) + ucs ?= json.object (js, Void) + if ucs /= Void then + assert ("ucs.same_string (%"foo\bar%")", ucs.same_string ("foo\bar")) + end + + jrep := "%"foo\\bar%"" + create parser.make_parser (jrep) + if attached {JSON_STRING} parser.parse as jstring then + assert ("unescaped string %"foo\\bar%" to %"foo\bar%"", jstring.unescaped_string.same_string ("foo\bar")) + end + + create js.make_json_from_string_32 ({STRING_32}"%/20320/%/22909/") + assert ("escaping unicode string32 %"%%/20320/%%/22909/%" %"\u4F60\u597D%"", js.item.same_string ("\u4F60\u597D")) + + jrep := "%"\u4F60\u597D%"" --| Ni hao + create parser.make_parser (jrep) + if attached {JSON_STRING} parser.parse as jstring then + assert ("same unicode string32 %"%%/20320/%%/22909/%"", jstring.unescaped_string_32.same_string ({STRING_32}"%/20320/%/22909/")) + end end test_json_array