First version of URI Template library

as specified by http://tools.ietf.org/html/draft-gregorio-uritemplate-05
(it seems to contains some error in the spec .. or minor incoherences, to double check)
The matcher is basic, it does not handle all the details of the string builder, but that seems ok for now.
This commit is contained in:
Jocelyn Fiat
2011-07-20 12:11:05 +02:00
parent 917f80c0c8
commit 51b70a2490
9 changed files with 1764 additions and 2 deletions

View File

@@ -0,0 +1,332 @@
note
description: "[
Summary description for {URI_TEMPLATE}.
See http://tools.ietf.org/html/draft-gregorio-uritemplate-05
]"
legal: "See notice at end of class."
status: "See notice at end of class."
date: "$Date$"
revision: "$Revision$"
class
URI_TEMPLATE
create
make
feature {NONE} -- Initialization
make (s: STRING)
do
template := s
end
make_with_handler (s: STRING; a_handler: detachable URI_TEMPLATE_HANDLER)
do
make (s)
analyze (a_handler)
end
feature -- Access
template: STRING
-- URI string representation
path_variable_names: LIST [STRING]
do
analyze (Void)
if attached expansion_parts as l_x_parts then
create {ARRAYED_LIST [STRING]} Result.make (l_x_parts.count)
from
l_x_parts.start
until
l_x_parts.after
loop
if not l_x_parts.item.is_query then
Result.append (l_x_parts.item.variable_names)
end
l_x_parts.forth
end
else
create {ARRAYED_LIST [STRING]} Result.make (0)
end
end
query_variable_names: LIST [STRING]
do
analyze (Void)
if attached expansion_parts as l_x_parts then
create {ARRAYED_LIST [STRING]} Result.make (l_x_parts.count)
from
l_x_parts.start
until
l_x_parts.after
loop
if l_x_parts.item.is_query then
Result.append (l_x_parts.item.variable_names)
end
l_x_parts.forth
end
else
create {ARRAYED_LIST [STRING]} Result.make (0)
end
end
feature -- Builder
string (a_ht: HASH_TABLE [detachable ANY, STRING]): STRING
local
tpl: like template
exp: URI_TEMPLATE_EXPRESSION
p,q: INTEGER
do
analyze (Void)
tpl := template
if attached expansion_parts as l_x_parts then
create Result.make (tpl.count)
from
l_x_parts.start
p := 1
until
l_x_parts.after
loop
q := l_x_parts.item.position
--| Added inter variable text
Result.append (tpl.substring (p, q - 1))
--| Expand variables ...
exp := l_x_parts.item
exp.append_to_string (a_ht, Result)
p := q + l_x_parts.item.expression.count + 2
l_x_parts.forth
end
Result.append (tpl.substring (p, tpl.count))
else
create Result.make_from_string (tpl)
end
end
url_encoder: URL_ENCODER
once
create Result
end
feature -- Analyze
match (a_uri: STRING): detachable TUPLE [path_variables: HASH_TABLE [STRING, STRING]; query_variables: HASH_TABLE [STRING, STRING]]
local
b: BOOLEAN
tpl: like template
l_offset: INTEGER
p,q: INTEGER
exp: URI_TEMPLATE_EXPRESSION
vn, s,t: STRING
vv: STRING
l_vars, l_path_vars, l_query_vars: HASH_TABLE [STRING, STRING]
do
--| Extract expansion parts "\\{([^\\}]*)\\}"
analyze (Void)
if attached expansion_parts as l_x_parts then
create l_path_vars.make (l_x_parts.count)
create l_query_vars.make (l_x_parts.count)
l_vars := l_path_vars
tpl := template
b := True
from
l_x_parts.start
p := 1
l_offset := 0
until
l_x_parts.after or not b
loop
exp := l_x_parts.item
vn := exp.expression
q := exp.position
--| Check text between vars
if q > p then
t := tpl.substring (p, q - 1)
s := a_uri.substring (p + l_offset, q + l_offset - 1)
b := s.same_string (t)
p := q + vn.count + 2
end
--| Check related variable
if not vn.is_empty then
if exp.is_query then
l_vars := l_query_vars
else
l_vars := l_path_vars
end
inspect vn[1]
when '?' then
import_form_style_parameters_into (a_uri.substring (q + l_offset + 1, a_uri.count), l_vars)
when ';' then
import_path_style_parameters_into (a_uri.substring (q + l_offset, a_uri.count), l_vars)
else
vv := next_path_variable_value (a_uri, q + l_offset)
l_vars.force (vv, vn)
l_offset := l_offset + vv.count - (vn.count + 2)
end
end
l_x_parts.forth
end
if b then
Result := [l_path_vars, l_query_vars]
end
end
end
analyze (a_handler: detachable URI_TEMPLATE_HANDLER)
local
l_x_parts: like expansion_parts
c: CHARACTER
i,p,n: INTEGER
tpl: like template
in_x: BOOLEAN
in_query: BOOLEAN
x: STRING
exp: URI_TEMPLATE_EXPRESSION
do
l_x_parts := expansion_parts
if l_x_parts = Void then
tpl := template
--| Extract expansion parts "\\{([^\\}]*)\\}"
create {ARRAYED_LIST [like expansion_parts.item]} l_x_parts.make (tpl.occurrences ('{'))
from
i := 1
n := tpl.count
create x.make_empty
until
i > n
loop
c := tpl[i]
if in_x then
if c = '}' then
create exp.make (p, x.twin, in_query)
l_x_parts.force (exp)
x.wipe_out
in_x := False
else
x.extend (c)
end
else
inspect c
when '{' then
check x_is_empty: x.is_empty end
p := i
in_x := True
if not in_query then
in_query := tpl.valid_index (i+1) and then tpl[i+1] = '?'
end
when '?' then
in_query := True
else
end
end
i := i + 1
end
expansion_parts := l_x_parts
end
end
feature {NONE} -- Implementation
expansion_parts: detachable LIST [URI_TEMPLATE_EXPRESSION]
-- Expansion parts
import_path_style_parameters_into (a_content: STRING; res: HASH_TABLE [STRING, STRING])
require
a_content_attached: a_content /= Void
res_attached: res /= Void
do
import_custom_style_parameters_into (a_content, ';', res)
end
import_form_style_parameters_into (a_content: STRING; res: HASH_TABLE [STRING, STRING])
require
a_content_attached: a_content /= Void
res_attached: res /= Void
do
import_custom_style_parameters_into (a_content, '&', res)
end
import_custom_style_parameters_into (a_content: STRING; a_separator: CHARACTER; res: HASH_TABLE [STRING, STRING])
require
a_content_attached: a_content /= Void
res_attached: res /= Void
local
n, p, i, j: INTEGER
s: STRING
l_name,l_value: STRING
do
n := a_content.count
if n > 0 then
from
p := 1
until
p = 0
loop
i := a_content.index_of (a_separator, p)
if i = 0 then
s := a_content.substring (p, n)
p := 0
else
s := a_content.substring (p, i - 1)
p := i + 1
end
if not s.is_empty then
j := s.index_of ('=', 1)
if j > 0 then
l_name := s.substring (1, j - 1)
l_value := s.substring (j + 1, s.count)
res.force (l_value, l_name)
end
end
end
end
end
next_path_variable_value (a_uri: STRING; a_index: INTEGER): STRING
require
valid_index: a_index <= a_uri.count
local
i,n,p: INTEGER
do
from
i := a_index
n := a_uri.count
until
i > n
loop
inspect a_uri[i]
when '/', '?' then
i := n
else
p := i
end
i := i + 1
end
Result := a_uri.substring (a_index, p)
end
comma_separated_variable_names (s: STRING): LIST [STRING]
do
Result := s.split (',')
end
note
copyright: "2011-2011, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,286 @@
note
description: "Summary description for {URI_TEMPLATE_EXPRESSION}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
URI_TEMPLATE_EXPRESSION
inherit
DEBUG_OUTPUT
create
make
feature {NONE} -- Initialization
make (a_position: INTEGER; a_expression: STRING; a_is_query: BOOLEAN)
do
position := a_position
expression := a_expression
is_query := a_is_query
operator := '%U'
end
feature -- Processing
analyze
local
exp: like expression
s: detachable STRING
lst: LIST [STRING]
p: INTEGER
vars: like variables
vn: STRING
vmodifier: detachable STRING
i,n: INTEGER
do
if not is_analyzed then
exp := expression
if not exp.is_empty then
op_prefix := '%U'
op_delimiter := ','
inspect exp[1]
when '+' then
reserved := True
operator := '+'
when '.' then
operator := '.'
op_prefix := '.'
op_delimiter := '.'
when '/' then
operator := '/'
op_prefix := '/'
op_delimiter := '/'
when ';' then
operator := ';'
op_prefix := ';'
op_delimiter := ';'
when '?' then
operator := '?'
op_prefix := '?'
op_delimiter := '&'
when '|', '!', '@' then
operator := exp[1]
else
operator := '%U'
end
if operator /= '%U' then
s := exp.substring (2, exp.count)
else
s := exp
end
lst := s.split (',')
from
create {ARRAYED_LIST [like variables.item]} vars.make (lst.count)
lst.start
until
lst.after
loop
s := lst.item
vmodifier := Void
p := s.index_of ('|', 1)
if p > 0 then
vn := s.substring (1, p - 1)
s := s.substring (p + 1, s.count)
else
vn := s
s := Void
end
from
vmodifier := Void
i := 1
n := vn.count
until
i > n
loop
inspect vn[i]
when '*', '+', ':', '^' then
vmodifier := vn.substring (i, n)
vn := vn.substring (1, i - 1)
i := n + 1 --| exit
else
i := i + 1
end
end
vars.force (create {URI_TEMPLATE_EXPRESSION_VARIABLE}.make (operator, vn, s, vmodifier))
lst.forth
end
variables := vars
end
is_analyzed := True
end
end
feature -- Access
position: INTEGER
expression: STRING
is_query: BOOLEAN
feature -- Status
operator: CHARACTER
has_operator: BOOLEAN
do
Result := operator /= '%U'
end
reserved: BOOLEAN
has_op_prefix: BOOLEAN
do
Result := op_prefix /= '%U'
end
op_prefix: CHARACTER
op_delimiter: CHARACTER
variables: detachable LIST [URI_TEMPLATE_EXPRESSION_VARIABLE]
variable_names: LIST [STRING]
do
analyze
if attached variables as vars then
create {ARRAYED_LIST [STRING]} Result.make (vars.count)
from
vars.start
until
vars.after
loop
Result.force (vars.item.name)
vars.forth
end
else
create {ARRAYED_LIST [STRING]} Result.make (0)
end
end
feature -- Status report
is_analyzed: BOOLEAN
feature -- Report
append_to_string (a_ht: HASH_TABLE [detachable ANY, STRING]; a_buffer: STRING)
do
analyze
if attached variables as vars then
append_custom_variables_to_string (a_ht, vars, op_prefix, op_delimiter, True, a_buffer)
-- inspect operator
-- when '?' then
-- append_custom_variables_to_string (a_ht, vars, '?', '&', True, a_buffer)
-- when ';' then
-- append_custom_variables_to_string (a_ht, vars, ';', ';', False, a_buffer)
-- when '.' then
-- append_custom_variables_to_string (a_ht, vars, '.', ',', True, a_buffer)
-- when '/' then
-- append_custom_variables_to_string (a_ht, vars, '/', '/', True, a_buffer)
-- else
-- append_custom_variables_to_string (a_ht, vars, '%U', ',', False, a_buffer)
-- end
end
end
feature {NONE} -- Implementation
url_encoded_string (s: READABLE_STRING_GENERAL; a_encoded: BOOLEAN): STRING
do
if a_encoded then
Result := url_encoder.encoded_string (s.as_string_32)
else
Result := url_encoder.partial_encoded_string (s.as_string_32, <<
':', ',',
'+', '.', '/', ';', '?',
'|', '!', '@'
>>)
end
end
url_encoder: URL_ENCODER
once
create Result
end
append_custom_variables_to_string (a_ht: HASH_TABLE [detachable ANY, STRING]; vars: like variables; prefix_char, delimiter_char: CHARACTER; a_include_name: BOOLEAN; a_buffer: STRING)
-- If `first_char' is '%U' do not print any first character
local
vi: like variables.item
l_is_first: BOOLEAN
vdata: detachable ANY
vstr: detachable STRING
l_use_default: BOOLEAN
do
if vars /= Void then
from
vars.start
l_is_first := True
until
vars.after
loop
vi := vars.item
vdata := a_ht.item (vi.name)
vstr := Void
if vdata /= Void then
vstr := vi.string (vdata)
if vstr = Void and vi.has_explode_modifier then
--| Missing or list empty
vstr := vi.default_value
l_use_default := True
else
l_use_default := False
end
else
--| Missing
vstr := vi.default_value
l_use_default := True
end
if vstr /= Void then
if l_is_first then
if prefix_char /= '%U' then
a_buffer.append_character (prefix_char)
end
l_is_first := False
else
a_buffer.append_character (delimiter_char)
end
if l_use_default and (operator = '?') and not vi.has_explode_modifier_star then
a_buffer.append (vi.name)
if vi.has_explode_modifier_plus then
a_buffer.append_character ('.')
else
a_buffer.append_character ('=')
end
end
a_buffer.append (vstr)
end
vars.forth
end
end
end
feature -- Status report
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
Result := expression
end
;note
copyright: "2011-2011, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,398 @@
note
description: "Summary description for {URI_TEMPLATE_EXPRESSION_VARIABLE}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
URI_TEMPLATE_EXPRESSION_VARIABLE
create
make
feature {NONE} -- Initialization
make (op: like operator; n: like name; d: like default_value; m: like modifier)
do
operator := op
name := n
default_value := d
modifier := m
op_prefix := '%U'
op_separator := ','
inspect op
when '+' then
reserved := True
when '?' then
op_prefix := '?'
op_separator := '&'
when ';' then
op_prefix := ';'
op_separator := ';'
when '/' then
op_prefix := '/'
op_separator := '/'
when '.' then
op_prefix := '.'
op_separator := '.'
else
end
end
feature -- Access
operator: CHARACTER
name: STRING
default_value: detachable STRING
reserved: BOOLEAN
op_prefix: CHARACTER
op_separator: CHARACTER
modifier: detachable STRING
has_modifier: BOOLEAN
do
Result := modifier /= Void
end
modified (s: READABLE_STRING_GENERAL): READABLE_STRING_GENERAL
local
t: STRING
i,n: INTEGER
do
Result := s
if attached modifier as m and then m.count > 1 and then m[1] = ':' then
n := s.count
t := m.substring (2, m.count)
if t.is_integer then
i := t.to_integer
if i > 0 then
if i < n then
Result := s.substring (1, i)
end
elseif i < 0 then
Result := s.substring (n - i, n)
end
end
end
end
has_explode_modifier: BOOLEAN
do
Result := attached modifier as m and then m.count = 1 and then (
m[1] = '+' or m[1] = '*'
)
end
has_explode_modifier_plus: BOOLEAN
do
Result := attached modifier as m and then m.count = 1 and then
m[1] = '+'
end
has_explode_modifier_star: BOOLEAN
do
Result := attached modifier as m and then m.count = 1 and then
m[1] = '*'
end
feature -- Report
string (d: detachable ANY): detachable STRING
local
l_delimiter: CHARACTER
v_enc: detachable STRING
k_enc: STRING
l_obj: detachable ANY
i,n: INTEGER
modifier_is_plus: BOOLEAN
modifier_is_star: BOOLEAN
modifier_has_explode: BOOLEAN
dft: detachable ANY
has_list_op: BOOLEAN
do
modifier_has_explode := has_explode_modifier
if modifier_has_explode then
modifier_is_plus := has_explode_modifier_plus
modifier_is_star := has_explode_modifier_star
end
has_list_op := operator /= '%U' and operator /= '+'
dft := default_value
create Result.make (20)
if attached {READABLE_STRING_GENERAL} d as l_string then
v_enc := url_encoded_string (modified (l_string), not reserved)
if operator = '?' then
Result.append (name)
Result.append_character ('=')
elseif operator = ';' then
Result.append (name)
if not v_enc.is_empty then
Result.append_character ('=')
end
end
Result.append (v_enc)
elseif attached {ARRAY [detachable ANY]} d as l_array then
if l_array.is_empty then
if dft /= Void then
inspect operator
when '?',';' then
if not modifier_has_explode then
Result.append (name)
Result.append_character ('=')
Result.append (dft.out)
else
if modifier_is_plus then
Result.append (name)
Result.append_character ('.')
end
Result.append (dft.out)
end
when '/' then
if modifier_is_plus then
Result.append (name)
Result.append_character ('.')
end
Result.append (dft.out)
when '.' then
else
if modifier_has_explode then
if modifier_is_plus then
Result.append (name)
Result.append_character ('.')
end
Result.append (dft.out)
end
end
else
-- nothing ...
end
else
if modifier_has_explode then
l_delimiter := op_separator
else
l_delimiter := ','
inspect operator
when '?' then
Result.append (name)
Result.append_character ('=')
when ';' then
when '/' then
-- Result.append_character ('/')
else
end
end
from
i := l_array.lower
n := l_array.upper
until
i > n
loop
l_obj := l_array[i]
if l_obj /= Void then
v_enc := url_encoded_string (l_obj.out, not reserved)
else
v_enc := ""
end
if modifier_is_plus then
if
(operator = '?' and modifier_is_plus) or
(operator = ';' and modifier_has_explode)
then
Result.append (name)
Result.append_character ('=')
else
Result.append (name)
Result.append_character ('.')
end
elseif modifier_is_star and operator = '?' then
Result.append (name)
Result.append_character ('=')
end
Result.append (v_enc)
if i < n then
Result.append_character (l_delimiter)
end
i := i + 1
end
end
if Result.is_empty then
Result := Void
end
elseif attached {HASH_TABLE [detachable ANY, STRING]} d as l_table then
-- if operator = '?' and not modifier_has_explode and l_table.is_empty and dft = Void then
-- elseif operator = '?' and not modifier_has_explode then
-- Result.append (name)
-- Result.append_character ('=')
-- if l_table.is_empty and dft /= Void then
-- Result.append (dft.out)
-- end
-- elseif l_table.is_empty and dft /= Void then
-- if modifier_has_explode then
-- if modifier_is_plus then
-- Result.append (name)
-- Result.append_character ('.')
-- end
-- Result.append (dft.out)
-- end
-- end
if l_table.is_empty then
if dft /= Void then
inspect operator
when '?',';' then
if not modifier_has_explode then
Result.append (name)
Result.append_character ('=')
Result.append (dft.out)
else
if modifier_is_plus then
Result.append (name)
Result.append_character ('.')
end
Result.append (dft.out)
end
when '/' then
if modifier_is_plus then
Result.append (name)
Result.append_character ('.')
end
Result.append (dft.out)
when '.' then
else
if modifier_has_explode then
if modifier_is_plus then
Result.append (name)
Result.append_character ('.')
end
Result.append (dft.out)
end
end
else
-- nothing ...
end
else
if modifier_has_explode then
l_delimiter := op_separator
else
l_delimiter := ','
inspect operator
when '?' then
Result.append (name)
Result.append_character ('=')
when ';' then
when '/' then
else
end
end
from
l_table.start
until
l_table.after
loop
k_enc := url_encoded_string (l_table.key_for_iteration, not reserved)
l_obj := l_table.item_for_iteration
if l_obj /= Void then
v_enc := url_encoded_string (l_obj.out, not reserved)
else
v_enc := ""
end
if modifier_is_plus then
Result.append (name)
Result.append_character ('.')
end
if
modifier_has_explode and
(
operator = '%U' or
operator = '+' or
operator = '?' or
operator = '.' or
operator = ';' or
operator = '/'
)
then
Result.append (k_enc)
Result.append_character ('=')
else
Result.append (k_enc)
Result.append_character (l_delimiter)
end
Result.append (v_enc)
l_table.forth
if not l_table.after then
Result.append_character (l_delimiter)
end
end
end
if Result.is_empty then
Result := Void
end
else
if d /= Void then
v_enc := url_encoded_string (d.out, not reserved)
elseif dft /= Void then
v_enc := url_encoded_string (dft.out, not reserved)
else
v_enc := default_value
end
if operator = '?' then
Result.append (name)
if v_enc /= Void then
Result.append_character ('=')
end
elseif operator = ';' then
Result.append (name)
if v_enc /= Void and then not v_enc.is_empty then
Result.append_character ('=')
end
end
if v_enc /= Void then
Result.append (v_enc)
end
end
end
feature {NONE} -- Implementation
url_encoded_string (s: READABLE_STRING_GENERAL; a_encoded: BOOLEAN): STRING
do
if a_encoded then
Result := url_encoder.encoded_string (s.as_string_32)
else
Result := url_encoder.partial_encoded_string (s.as_string_32, <<
':', ',',
'+', '.', '/', ';', '?',
'|', '!', '@'
>>)
end
end
url_encoder: URL_ENCODER
once
create Result
end
;note
copyright: "2011-2011, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,25 @@
note
description: "Summary description for {URI_TEMPLATE_HANDLER}."
author: ""
date: "$Date$"
revision: "$Revision$"
deferred class
URI_TEMPLATE_HANDLER
feature -- Events
note
copyright: "2011-2011, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end