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
This commit is contained in:
jfiat
2012-01-13 17:26:23 +00:00
parent 1b446caa27
commit d67e01eea6
4 changed files with 783 additions and 533 deletions

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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