Files
EWF/library/kernel/json_string.e
Jocelyn Fiat 19dbbf89e7 Fixed various issue with parsing string (such as \t and related),
Implemented escaping of slash '/' only in case of '</' to avoid potential issue with javascript and </script>
Many feature renaming to match Eiffel style and naming convention, kept previous feature as obsolete.
Restructured the library to make easy extraction of "converter" classes if needed in the future.
Updated part of the code to use new feature names.
2014-09-24 20:08:12 +02:00

504 lines
11 KiB
Plaintext

note
description: "[
A JSON_STRING represent a string in JSON.
A string is a collection of zero or more Unicodes characters, wrapped in double
quotes, using blackslash espaces.
]"
author: "$Author$"
date: "$Date$"
revision: "$Revision$"
license: "MIT (see http://www.opensource.org/licenses/mit-license.php)"
class
JSON_STRING
inherit
JSON_VALUE
redefine
is_equal
end
create
make_from_string, make_from_string_32, make_from_string_general,
make_from_escaped_json_string,
make_with_escaped_json, make_json, make_json_from_string_32
convert
make_from_string ({READABLE_STRING_8, STRING_8, IMMUTABLE_STRING_8}),
make_from_string_32 ({READABLE_STRING_32, STRING_32, IMMUTABLE_STRING_32}),
make_from_string_general ({READABLE_STRING_GENERAL, STRING_GENERAL, IMMUTABLE_STRING_GENERAL})
feature {NONE} -- Initialization
make_from_string (s: READABLE_STRING_8)
-- Initialize from ascii string `s'.
require
s_not_void: s /= Void
do
make_from_escaped_json_string (escaped_json_string (s))
end
make_from_string_32 (s: READABLE_STRING_32)
-- Initialize from unicode string `s'.
require
s_not_void: s /= Void
do
make_from_escaped_json_string (escaped_json_string (s))
end
make_from_string_general (s: READABLE_STRING_GENERAL)
-- Initialize from string `s'.
require
s_not_void: s /= Void
do
if attached {READABLE_STRING_8} s as s8 then
make_from_string (s8)
else
make_from_string_32 (s.as_string_32)
end
end
make_from_escaped_json_string (a_escaped_string: READABLE_STRING_8)
-- Initialize with `a_escaped_string' already JSON escaped.
require
a_escaped_string_not_void: a_escaped_string /= Void
do
item := a_escaped_string
end
make_with_escaped_json (a_escaped_string: READABLE_STRING_8)
-- Initialize with `a_escaped_string' already JSON escaped.
obsolete
"Use `make_from_escaped_json_string' Sept/2014"
require
a_escaped_string_not_void: a_escaped_string /= Void
do
make_from_escaped_json_string (a_escaped_string)
end
make_from_json_string (a_json: JSON_STRING)
-- Initialize with `a_json' string value.
do
make_from_escaped_json_string (a_json.item)
end
make_json (s: READABLE_STRING_8)
-- Initialize.
obsolete
"Use `make_from_string' Sept/2014"
require
item_not_void: s /= Void
do
make_with_escaped_json (escaped_json_string (s))
end
make_json_from_string_32 (s: READABLE_STRING_32)
-- Initialize from STRING_32 `s'.
obsolete
"Use `make_from_string_32' Sept/2014"
require
item_not_void: s /= Void
do
make_with_escaped_json (escaped_json_string (s))
end
feature -- Access
item: STRING
-- Contents with escaped entities if any
feature -- Conversion
unescaped_string_8: STRING_8
-- Unescaped ascii string from `item'.
--| note: valid only if `item' does not encode any unicode character.
local
s: like item
do
s := item
create Result.make (s.count)
unescape_to_string_8 (Result)
end
unescaped_string_32: STRING_32
-- Unescaped uncode string from `item'
--| some encoders uses UTF-8 , and not the recommended pure json encoding
--| thus, let's support the UTF-8 encoding during decoding.
local
s: READABLE_STRING_8
do
s := item
create Result.make (s.count)
unescape_to_string_32 (Result)
end
representation: STRING
-- String representation of `item' with escaped entities if any.
do
create Result.make (item.count + 2)
Result.append_character ('%"')
Result.append (item)
Result.append_character ('%"')
end
unescape_to_string_8 (a_output: STRING_8)
-- Unescape string `item' into `a_output'.
--| note: valid only if `item' does not encode any unicode character.
local
s: like item
i, n: INTEGER
c: CHARACTER
do
s := item
n := s.count
from
i := 1
until
i > n
loop
c := s [i]
if c = '\' then
if i < n then
inspect s [i + 1]
when '%"' then
a_output.append_character ('%"')
i := i + 2
when '\' then
a_output.append_character ('\')
i := i + 2
when '/' then
a_output.append_character ('/')
i := i + 2
when 'b' then
a_output.append_character ('%B')
i := i + 2
when 'f' then
a_output.append_character ('%F')
i := i + 2
when 'n' then
a_output.append_character ('%N')
i := i + 2
when 'r' then
a_output.append_character ('%R')
i := i + 2
when 't' then
a_output.append_character ('%T')
i := i + 2
when 'u' then
--| Leave unicode \uXXXX unescaped
a_output.append_character (c) -- '\'
i := i + 1
else
a_output.append_character (c) -- '\'
i := i + 1
end
else
a_output.append_character (c) -- '\'
i := i + 1
end
else
a_output.append_character (c)
i := i + 1
end
end
end
unescape_to_string_32 (a_output: STRING_32)
-- Unescape string `item' into `a_output' string 32.
--| some encoders uses UTF-8 , and not the recommended pure json encoding
--| thus, let's support the UTF-8 encoding during decoding.
local
s: READABLE_STRING_8
i, n: INTEGER
c: NATURAL_32
ch: CHARACTER_8
hex: READABLE_STRING_8
do
s := item
n := s.count
from
i := 1
until
i > n
loop
ch := s.item (i)
if ch = '\' then
if i < n then
inspect s [i + 1]
when '%"' then
a_output.append_character ('%"')
i := i + 2
when '\' then
a_output.append_character ('\')
i := i + 2
when '/' then
a_output.append_character ('/')
i := i + 2
when 'b' then
a_output.append_character ('%B')
i := i + 2
when 'f' then
a_output.append_character ('%F')
i := i + 2
when 'n' then
a_output.append_character ('%N')
i := i + 2
when 'r' then
a_output.append_character ('%R')
i := i + 2
when 't' then
a_output.append_character ('%T')
i := i + 2
when 'u' then
hex := s.substring (i + 2, i + 5) -- i+2 , i+2+4-1
if hex.count = 4 then
a_output.append_code (hexadecimal_to_natural_32 (hex))
end
i := i + 6 -- i+2+4
else
a_output.append_character (ch) -- '\'
i := i + 1
end
else
a_output.append_character (ch) -- '\'
i := i + 1
end
else
c := ch.natural_32_code
if c <= 0x7F then
-- 0xxxxxxx
check
ch = c.to_character_32
end
a_output.append_character (ch)
elseif c <= 0xDF then
-- 110xxxxx 10xxxxxx
i := i + 1
if i <= n then
a_output.append_code (((c & 0x1F) |<< 6) | (s.code (i) & 0x3F))
end
elseif c <= 0xEF then
-- 1110xxxx 10xxxxxx 10xxxxxx
i := i + 2
if i <= n then
a_output.append_code (((c & 0xF) |<< 12) | ((s.code (i - 1) & 0x3F) |<< 6) | (s.code (i) & 0x3F))
end
elseif c <= 0xF7 then
-- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
i := i + 3
if i <= n then
a_output.append_code (((c & 0x7) |<< 18) | ((s.code (i - 2) & 0x3F) |<< 12) | ((s.code (i - 1) & 0x3F) |<< 6) | (s.code (i) & 0x3F))
end
end
i := i + 1
end
end
end
feature -- Visitor pattern
accept (a_visitor: JSON_VISITOR)
-- Accept `a_visitor'.
-- (Call `visit_json_string' procedure on `a_visitor'.)
do
a_visitor.visit_json_string (Current)
end
feature -- Comparison
is_equal (other: like Current): BOOLEAN
-- Is JSON_STRING made of same character sequence as `other'
-- (possibly with a different capacity)?
do
Result := item.same_string (other.item)
end
feature -- Change Element
append (a_escaped_string: READABLE_STRING_8)
-- Add JSON escaped string `a_escaped_string'
require
a_escaped_string_not_void: a_escaped_string /= Void
do
item.append_string (a_escaped_string)
end
append_json_string (a_json_string: JSON_STRING)
-- Add JSON string `a_json_string'
require
a_json_string_not_void: a_json_string /= Void
do
append (a_json_string.item)
end
append_string (s: READABLE_STRING_8)
-- Add ascii string `s'
require
s_not_void: s /= Void
do
append (escaped_json_string (s))
end
append_string_32 (s: READABLE_STRING_32)
-- Add unicode string `s'
require
s_not_void: s /= Void
do
append (escaped_json_string (s))
end
append_string_general (s: READABLE_STRING_GENERAL)
-- Add unicode string `s'
require
s_not_void: s /= Void
do
if attached {READABLE_STRING_8} s as s8 then
append_string (s.as_string_8)
else
append_string_32 (s.as_string_32)
end
end
feature -- Status report
hash_code: INTEGER
-- Hash code value
do
Result := item.hash_code
end
feature -- Status report
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
Result := item
end
feature {NONE} -- Implementation
is_hexadecimal (s: READABLE_STRING_8): BOOLEAN
-- Is `s' an hexadecimal value?
local
i: INTEGER
do
from
Result := True
i := 1
until
i > s.count or not Result
loop
Result := s [i].is_hexa_digit
i := i + 1
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_GENERAL): 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 '/' then
-- To avoid issue with Javascript </script> ...
-- escape only </ to <\/
if s.valid_index (i - 1) and then s.item (i - 1) = '<' then
Result.append_string ("\/")
else
Result.append_string ("/")
end
when '%B' then
Result.append_string ("\b")
when '%F' then
Result.append_string ("\f")
when '%N' then
Result.append_string ("\n")
when '%R' then
Result.append_string ("\r")
when '%T' then
Result.append_string ("\t")
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
hexastring_has_4_chars: h.count = 4
end
Result.append (h)
end
i := i + 1
end
end
invariant
item_not_void: item /= Void
end