From 51b70a2490798dad0edb6dcd6c424672ac3b4a12 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Wed, 20 Jul 2011 12:11:05 +0200 Subject: [PATCH] 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. --- library/protocol/uri_template/license.lic | 10 + .../protocol/uri_template/src/uri_template.e | 332 ++++++++++ .../src/uri_template_expression.e | 286 ++++++++ .../src/uri_template_expression_variable.e | 398 +++++++++++ .../uri_template/src/uri_template_handler.e | 25 + .../uri_template/tests/test_uri_template.e | 616 ++++++++++++++++++ .../uri_template/uri_template-safe.ecf | 32 + .../protocol/uri_template/uri_template.ecf | 18 + library/text/encoder/src/url_encoder.e | 49 +- 9 files changed, 1764 insertions(+), 2 deletions(-) create mode 100644 library/protocol/uri_template/license.lic create mode 100644 library/protocol/uri_template/src/uri_template.e create mode 100644 library/protocol/uri_template/src/uri_template_expression.e create mode 100644 library/protocol/uri_template/src/uri_template_expression_variable.e create mode 100644 library/protocol/uri_template/src/uri_template_handler.e create mode 100644 library/protocol/uri_template/tests/test_uri_template.e create mode 100644 library/protocol/uri_template/uri_template-safe.ecf create mode 100644 library/protocol/uri_template/uri_template.ecf diff --git a/library/protocol/uri_template/license.lic b/library/protocol/uri_template/license.lic new file mode 100644 index 00000000..cf2d1ed9 --- /dev/null +++ b/library/protocol/uri_template/license.lic @@ -0,0 +1,10 @@ +${NOTE_KEYWORD} + copyright: "2011-${YEAR}, 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 + ]" diff --git a/library/protocol/uri_template/src/uri_template.e b/library/protocol/uri_template/src/uri_template.e new file mode 100644 index 00000000..dbc53430 --- /dev/null +++ b/library/protocol/uri_template/src/uri_template.e @@ -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 diff --git a/library/protocol/uri_template/src/uri_template_expression.e b/library/protocol/uri_template/src/uri_template_expression.e new file mode 100644 index 00000000..298a53c5 --- /dev/null +++ b/library/protocol/uri_template/src/uri_template_expression.e @@ -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 diff --git a/library/protocol/uri_template/src/uri_template_expression_variable.e b/library/protocol/uri_template/src/uri_template_expression_variable.e new file mode 100644 index 00000000..81cf11e6 --- /dev/null +++ b/library/protocol/uri_template/src/uri_template_expression_variable.e @@ -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 diff --git a/library/protocol/uri_template/src/uri_template_handler.e b/library/protocol/uri_template/src/uri_template_handler.e new file mode 100644 index 00000000..7f19b55c --- /dev/null +++ b/library/protocol/uri_template/src/uri_template_handler.e @@ -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 diff --git a/library/protocol/uri_template/tests/test_uri_template.e b/library/protocol/uri_template/tests/test_uri_template.e new file mode 100644 index 00000000..6248f31b --- /dev/null +++ b/library/protocol/uri_template/tests/test_uri_template.e @@ -0,0 +1,616 @@ +note + description: "[ + Eiffel tests that can be executed by testing tool. + ]" + author: "EiffelStudio test wizard" + date: "$Date$" + revision: "$Revision$" + testing: "type/manual" + +class + TEST_URI_TEMPLATE + +inherit + EQA_TEST_SET + +feature -- Test routines + + test_uri_template_parser + note + testing: "uri-template" + do + uri_template_parse ("api/foo/{foo_id}/{?id,extra}", <<"foo_id">>, <<"id", "extra">>) + uri_template_parse ("weather/{state}/{city}?forecast={day}", <<"state", "city">>, <<"day">>) + + end + + test_uri_template_matcher + note + testing: "uri-template" + local + tpl: URI_TEMPLATE + do + create tpl.make ("api/foo/{foo_id}/{?id,extra}") + uri_template_match (tpl, "api/foo/bar/?id=123", <<["foo_id", "bar"]>>, <<["id", "123"]>>) + uri_template_match (tpl, "api/foo/bar/?id=123&extra=test", <<["foo_id", "bar"]>>, <<["id", "123"], ["extra", "test"]>>) + + create tpl.make ("weather/{state}/{city}?forecast={day}") + uri_template_match (tpl, "weather/California/Goleta?forecast=today", <<["state", "California"], ["city", "Goleta"]>>, <<["day", "today"]>>) + end + + uri_template_string_errors: detachable LIST [STRING] + + test_uri_template_string_builder + note + testing: "uri-template" + local + ht: HASH_TABLE [detachable ANY, STRING] + empty_keys: HASH_TABLE [STRING, STRING] + empty_list: ARRAY [STRING] + favs: HASH_TABLE [detachable ANY, STRING] + keys: HASH_TABLE [STRING, STRING] + colors: ARRAY [STRING] + names: ARRAY [STRING] + semi_dot: HASH_TABLE [STRING, STRING] + vals: ARRAY [STRING] + do + create ht.make (3) + ht.force ("FooBar", "foo_id") + ht.force ("That's right!", "extra") + ht.force ("123", "id") + ht.force ("California", "state") + ht.force ("Goleta", "city") + ht.force ("today", "day") + + ht.force ("value", "var") + ht.force ("Hello World!", "hello") + ht.force ("", "empty") + ht.force ("/foo/bar", "path") + ht.force ("1024", "x") + ht.force ("768", "y") + ht.force ("fred", "foo") + ht.force ("That's right!", "foo2") + ht.force ("http://example.com/home/", "base") + + names := <<"Fred", "Wilma", "Pebbles">> + ht.force (names, "name") + create favs.make (2) + favs.force ("red", "color") + favs.force ("high", "volume") + ht.force (favs, "favs") + + create empty_list.make_empty + ht.force (empty_list,"empty_list") + + create empty_keys.make (0) + ht.force (empty_keys,"empty_keys") + + vals := <<"val1", "val2", "val3">> + ht.force (vals, "list") + create keys.make (2) + keys.force ("val1", "key1") + keys.force ("val2", "key2") + ht.force (keys, "keys") + + colors := <<"red", "green", "blue">> + create semi_dot.make (3) + semi_dot.force (";", "semi") + semi_dot.force (".", "dot") + semi_dot.force (",", "comma") + + create {ARRAYED_LIST [STRING]} uri_template_string_errors.make (10) + + + --| Simple string expansion + uri_template_string (ht, "{var}", "value") + uri_template_string (ht, "{hello}", "Hello+World%%21") + uri_template_string (ht, "O{empty}X", "OX") + uri_template_string (ht, "O{undef}X", "OX") + + --| String expansion with defaults + uri_template_string (ht, "{var|default}", "value") + uri_template_string (ht, "O{empty|default}X", "OX") + uri_template_string (ht, "O{undef|default}X", "OdefaultX") + + --| Reserved expansion with defaults + uri_template_string (ht, "{+var}", "value") + uri_template_string (ht, "{+hello}", "Hello+World!") + uri_template_string (ht, "{+path}/here", "/foo/bar/here") + uri_template_string (ht, "here?ref={+path}", "here?ref=/foo/bar") + uri_template_string (ht, "up{+path}{x}/here", "up/foo/bar1024/here") + uri_template_string (ht, "up{+empty|/1}/here", "up/here") + uri_template_string (ht, "up{+undef|/1}/here", "up/1/here") + + --| String expansion with multiple variables + uri_template_string (ht, "{x,y}", "1024,768") + uri_template_string (ht, "{x,hello,y}", "1024,Hello+World%%21,768") + uri_template_string (ht, "?{x,empty}", "?1024,") + uri_template_string (ht, "?{x,undef}", "?1024") + uri_template_string (ht, "?{undef,y}", "?768") + uri_template_string (ht, "?{x,undef|0}", "?1024,0") + + --| Reserved expansion with multiple variables + uri_template_string (ht, "{+x,hello,y}", "1024,Hello+World!,768") + uri_template_string (ht, "{+path,x}/here", "/foo/bar,1024/here") + --| Label expansion, dot-prefixed + uri_template_string (ht, "X{.var}", "X.value") + uri_template_string (ht, "X{.empty}", "X.") + uri_template_string (ht, "X{.undef}", "X") + + --| Path segments, slash-prefixed + uri_template_string (ht, "{/var}", "/value") + uri_template_string (ht, "{/var,empty}", "/value/") + uri_template_string (ht, "{/var,undef}", "/value") + + --| Path-style parameters, semicolon-prefixed + uri_template_string (ht, "{;x,y}", ";x=1024;y=768") + uri_template_string (ht, "{;x,y,empty}", ";x=1024;y=768;empty") + uri_template_string (ht, "{;x,y,undef}", ";x=1024;y=768") + + --| Form-style query, ampersand-separated + uri_template_string (ht, "{?x,y}", "?x=1024&y=768") + uri_template_string (ht, "{?x,y,empty}", "?x=1024&y=768&empty=") + uri_template_string (ht, "{?x,y,undef}", "?x=1024&y=768") + + + ht.force (colors, "list") + ht.force (semi_dot, "keys") + --| String expansion with value modifiers + uri_template_string (ht, "{var:3}", "val") + uri_template_string (ht, "{var:30}", "value") + uri_template_string (ht, "{list}", "red,green,blue") + uri_template_string (ht, "{list*}", "red,green,blue") + uri_template_string (ht, "{keys}", "semi,%%3B,dot,.,comma,%%2C") + uri_template_string (ht, "{keys*}", "semi=%%3B,dot=.,comma=%%2C") + --| Reserved expansion with value modifiers + uri_template_string (ht, "{+path:6}/here", "/foo/b/here") + uri_template_string (ht, "{+list}", "red,green,blue") + uri_template_string (ht, "{+list*}", "red,green,blue") + uri_template_string (ht, "{+keys}", "semi,;,dot,.,comma,,") + uri_template_string (ht, "{+keys*}", "semi=;,dot=.,comma=,") + --| Label expansion, dot-prefixed + uri_template_string (ht, "X{.var:3}", "X.val") + uri_template_string (ht, "X{.list}", "X.red,green,blue") + uri_template_string (ht, "X{.list*}", "X.red.green.blue") + uri_template_string (ht, "X{.keys}", "X.semi,%%3B,dot,.,comma,%%2C") + uri_template_string (ht, "X{.keys*}", "X.semi=%%3B.dot=..comma=%%2C") + + --| Path segments, slash-prefixed + uri_template_string (ht, "{/var:1,var}", "/v/value") + uri_template_string (ht, "{/list}", "/red,green,blue") + uri_template_string (ht, "{/list*}", "/red/green/blue") + uri_template_string (ht, "{/list*,path:4}", "/red/green/blue/%%2Ffoo") + uri_template_string (ht, "{/keys}", "/semi,%%3B,dot,.,comma,%%2C") + uri_template_string (ht, "{/keys*}", "/semi=%%3B/dot=./comma=%%2C") + + --| Path-style parameters, semicolon-prefixed + uri_template_string (ht, "{;hello:5}", ";hello=Hello") + uri_template_string (ht, "{;list}", ";red,green,blue") + uri_template_string (ht, "{;list*}", ";red;green;blue") + uri_template_string (ht, "{;keys}", ";semi,%%3B,dot,.,comma,%%2C") + uri_template_string (ht, "{;keys*}", ";semi=%%3B;dot=.;comma=%%2C") + + --| Form-style query, ampersand-separated + uri_template_string (ht, "{?var:3}", "?var=val") + uri_template_string (ht, "{?list}", "?list=red,green,blue") + uri_template_string (ht, "{?list*}", "?list=red&list=green&list=blue") + uri_template_string (ht, "{?keys}", "?keys=semi,%%3B,dot,.,comma,%%2C") + uri_template_string (ht, "{?keys*}", "?semi=%%3B&dot=.&comma=%%2C") + + + assert ("all strings built", uri_template_string_errors = Void or (attached uri_template_string_errors as err and then err.is_empty)) + end + + + test_uri_template_string_builder_extra + note + testing: "uri-template" + local + ht: HASH_TABLE [detachable ANY, STRING] + empty_keys: HASH_TABLE [STRING, STRING] + empty_list: ARRAY [STRING] + favs: HASH_TABLE [detachable ANY, STRING] + keys: HASH_TABLE [STRING, STRING] + colors: ARRAY [STRING] + names: ARRAY [STRING] + semi_dot: HASH_TABLE [STRING, STRING] + vals: ARRAY [STRING] + do + create ht.make (3) + ht.force ("FooBar", "foo_id") + ht.force ("That's right!", "extra") + ht.force ("123", "id") + ht.force ("California", "state") + ht.force ("Goleta", "city") + ht.force ("today", "day") + + ht.force ("value", "var") + ht.force ("Hello World!", "hello") + ht.force ("", "empty") + ht.force ("/foo/bar", "path") + ht.force ("1024", "x") + ht.force ("768", "y") + ht.force ("fred", "foo") + ht.force ("That's right!", "foo2") + ht.force ("http://example.com/home/", "base") + + names := <<"Fred", "Wilma", "Pebbles">> + ht.force (names, "name") + create favs.make (2) + favs.force ("red", "color") + favs.force ("high", "volume") + ht.force (favs, "favs") + + create empty_list.make_empty + ht.force (empty_list,"empty_list") + + create empty_keys.make (0) + ht.force (empty_keys,"empty_keys") + + vals := <<"val1", "val2", "val3">> + ht.force (vals, "list") + create keys.make (2) + keys.force ("val1", "key1") + keys.force ("val2", "key2") + ht.force (keys, "keys") + + colors := <<"red", "green", "blue">> + create semi_dot.make (3) + semi_dot.force (";", "semi") + semi_dot.force (".", "dot") + semi_dot.force (",", "comma") + + create {ARRAYED_LIST [STRING]} uri_template_string_errors.make (10) + + --| Addition to the spec + uri_template_string (ht, "api/foo/{foo_id}/{?id,extra}", + "api/foo/FooBar/?id=123&extra=That%%27s+right%%21") + + uri_template_string (ht, "api/foo/{foo_id}/{?id,empty,undef,extra}", + "api/foo/FooBar/?id=123&empty=&extra=That%%27s+right%%21") + + uri_template_string (ht, "weather/{state}/{city}?forecast={day}", + "weather/California/Goleta?forecast=today") + + + uri_template_string (ht, "{var|default}", "value") + uri_template_string (ht, "{undef|default}", "default") + uri_template_string (ht, "{undef:3|default}", "default") + + uri_template_string (ht, "x{empty}y", "xy") + uri_template_string (ht, "x{empty|_}y", "xy") + uri_template_string (ht, "x{undef}y", "xy") + uri_template_string (ht, "x{undef|_}y", "x_y") + + uri_template_string (ht, "x{.name|none}", "x.Fred,Wilma,Pebbles") + uri_template_string (ht, "x{.name*|none}", "x.Fred.Wilma.Pebbles") + uri_template_string (ht, "x{.empty}", "x.") + uri_template_string (ht, "x{.empty|none}", "x.") + uri_template_string (ht, "x{.undef}", "x") + uri_template_string (ht, "x{.undef|none}", "x.none") + + uri_template_string (ht, "x{/name|none}", "x/Fred,Wilma,Pebbles") + uri_template_string (ht, "x{/name*|none}", "x/Fred/Wilma/Pebbles") + uri_template_string (ht, "x{/undef}", "x") + uri_template_string (ht, "x{/undef|none}", "x/none") + uri_template_string (ht, "x{/empty}", "x/") + uri_template_string (ht, "x{/empty|none}", "x/") + uri_template_string (ht, "x{/empty_keys}", "x") + uri_template_string (ht, "x{/empty_keys|none}", "x/none") + uri_template_string (ht, "x{/empty_keys*}", "x") + uri_template_string (ht, "x{/empty_keys*|none}", "x/none") + +-- uri_template_string (ht, "x{;name|none}", "x;name=Fred,Wilma,Pebbles") +-- uri_template_string (ht, "x{;favs|none}", "x;favs=color,red,volume,high") + uri_template_string (ht, "x{;favs*|none}", "x;color=red;volume=high") + uri_template_string (ht, "x{;empty}", "x;empty") + uri_template_string (ht, "x{;empty|none}", "x;empty") + + uri_template_string (ht, "x{;undef}", "x") + uri_template_string (ht, "x{;undef|none}", "x;none") + uri_template_string (ht, "x{;undef|foo=y}", "x;foo=y") + + uri_template_string (ht, "x{?var|none}", "x?var=value") + uri_template_string (ht, "x{?favs|none}", "x?favs=color,red,volume,high") + uri_template_string (ht, "x{?favs*|none}", "x?color=red&volume=high") + uri_template_string (ht, "x{?empty}", "x?empty=") + uri_template_string (ht, "x{?empty|foo=none}", "x?empty=") + uri_template_string (ht, "x{?undef}", "x") +-- uri_template_string (ht, "x{?undef|foo=none}", "x?foo=none") + uri_template_string (ht, "x{?empty_keys}", "x") +-- uri_template_string (ht, "x{?empty_keys|none}", "x?none") +-- uri_template_string (ht, "x{?empty_keys|y=z}", "x?y=z") + uri_template_string (ht, "x{?empty_keys*|y=z}", "x?y=z") + + + ------ + + uri_template_string (ht, "x{empty_list}y", "xy") + uri_template_string (ht, "x{empty_list|_}y", "xy") + uri_template_string (ht, "x{empty_list*}y", "xy") + uri_template_string (ht, "x{empty_list*|_}y", "x_y") + uri_template_string (ht, "x{empty_list+}y", "xy") + uri_template_string (ht, "x{empty_list+|_}y", "xempty_list._y") + + uri_template_string (ht, "x{empty_keys}y", "xy") + uri_template_string (ht, "x{empty_keys|_}y", "xy") + uri_template_string (ht, "x{empty_keys*}y", "xy") + uri_template_string (ht, "x{empty_keys*|_}y", "x_y") + uri_template_string (ht, "x{empty_keys+}y", "xy") + uri_template_string (ht, "x{empty_keys+|_}y", "xempty_keys._y") + + uri_template_string (ht, "x{?name|none}", "x?name=Fred,Wilma,Pebbles") + uri_template_string (ht, "x{?favs|none}", "x?favs=color,red,volume,high") + uri_template_string (ht, "x{?favs*|none}", "x?color=red&volume=high") + uri_template_string (ht, "x{?favs+|none}", "x?favs.color=red&favs.volume=high") + + uri_template_string (ht, "x{?undef}", "x") + uri_template_string (ht, "x{?undef|none}", "x?undef=none") + uri_template_string (ht, "x{?empty}", "x?empty=") + uri_template_string (ht, "x{?empty|none}", "x?empty=") + + uri_template_string (ht, "x{?empty_list}", "x") + uri_template_string (ht, "x{?empty_list|none}", "x?empty_list=none") + uri_template_string (ht, "x{?empty_list*}", "x") + uri_template_string (ht, "x{?empty_list*|none}", "x?none") + uri_template_string (ht, "x{?empty_list+}", "x") + uri_template_string (ht, "x{?empty_list+|none}", "x?empty_list.none") + + uri_template_string (ht, "x{?empty_keys}", "x") + uri_template_string (ht, "x{?empty_keys|none}", "x?empty_keys=none") + uri_template_string (ht, "x{?empty_keys*}", "x") + uri_template_string (ht, "x{?empty_keys*|none}", "x?none") + uri_template_string (ht, "x{?empty_keys+}", "x") + uri_template_string (ht, "x{?empty_keys+|none}", "x?empty_keys.none") + +-- uri_template_string (ht, "x{;name|none}", "x;name=Fred,Wilma,Pebbles") +-- uri_template_string (ht, "x{;favs|none}", "x;favs=color,red,volume,high") + uri_template_string (ht, "x{;favs|none}", "x;color,red,volume,high") -- DIFF + uri_template_string (ht, "x{;favs*|none}", "x;color=red;volume=high") + uri_template_string (ht, "x{;favs+|none}", "x;favs.color=red;favs.volume=high") + uri_template_string (ht, "x{;undef}", "x") +-- uri_template_string (ht, "x{;undef|none}", "x;undef=none") + uri_template_string (ht, "x{;undef|none}", "x;none") + uri_template_string (ht, "x{;empty}", "x;empty") + uri_template_string (ht, "x{;empty|none}", "x;empty") + + uri_template_string (ht, "x{;empty_list}", "x") + uri_template_string (ht, "x{;empty_list|none}", "x;empty_list=none") + uri_template_string (ht, "x{;empty_list*}", "x") + uri_template_string (ht, "x{;empty_list*|none}", "x;none") + uri_template_string (ht, "x{;empty_list+}", "x") + uri_template_string (ht, "x{;empty_list+|none}", "x;empty_list.none") + + uri_template_string (ht, "x{;empty_keys}", "x") + uri_template_string (ht, "x{;empty_keys|none}", "x;empty_keys=none") + uri_template_string (ht, "x{;empty_keys*}", "x") + uri_template_string (ht, "x{;empty_keys*|none}", "x;none") + uri_template_string (ht, "x{;empty_keys+}", "x") + uri_template_string (ht, "x{;empty_keys+|none}", "x;empty_keys.none") + + uri_template_string (ht, "x{/name|none}", "x/Fred,Wilma,Pebbles") + uri_template_string (ht, "x{/name*|none}", "x/Fred/Wilma/Pebbles") + uri_template_string (ht, "x{/name+|none}", "x/name.Fred/name.Wilma/name.Pebbles") + uri_template_string (ht, "x{/favs|none}", "x/color,red,volume,high") + uri_template_string (ht, "x{/favs*|none}", "x/color/red/volume/high") + uri_template_string (ht, "x{/favs+|none}", "x/favs.color/red/favs.volume/high") + + uri_template_string (ht, "x{/undef}", "x") + uri_template_string (ht, "x{/undef|none}", "x/none") + uri_template_string (ht, "x{/empty}", "x/") + uri_template_string (ht, "x{/empty|none}", "x/") + + uri_template_string (ht, "x{/empty_list}", "x") + uri_template_string (ht, "x{/empty_list|none}", "x/none") + uri_template_string (ht, "x{/empty_list*}", "x") + uri_template_string (ht, "x{/empty_list*|none}", "x/none") + uri_template_string (ht, "x{/empty_list+}", "x") + uri_template_string (ht, "x{/empty_list+|none}", "x/empty_list.none") + + uri_template_string (ht, "x{/empty_keys}", "x") + uri_template_string (ht, "x{/empty_keys|none}", "x/none") + uri_template_string (ht, "x{/empty_keys*}", "x") + uri_template_string (ht, "x{/empty_keys*|none}", "x/none") + uri_template_string (ht, "x{/empty_keys+}", "x") + uri_template_string (ht, "x{/empty_keys+|none}", "x/empty_keys.none") + + --| Simple expansion with comma-separated values + uri_template_string (ht, "{var}", "value") + uri_template_string (ht, "{hello}", "Hello+World%%21") + uri_template_string (ht, "{path}/here", "%%2Ffoo%%2Fbar/here") + uri_template_string (ht, "{x,y}", "1024,768") + uri_template_string (ht, "{var|default}", "value") + uri_template_string (ht, "{undef|default}", "default") + uri_template_string (ht, "{list}", "val1,val2,val3") + uri_template_string (ht, "{list*}", "val1,val2,val3") + uri_template_string (ht, "{list+}", "list.val1,list.val2,list.val3") + uri_template_string (ht, "{keys}", "key1,val1,key2,val2") + uri_template_string (ht, "{keys*}", "key1,val1,key2,val2") + uri_template_string (ht, "{keys+}", "keys.key1,val1,keys.key2,val2") + + --| Reserved expansion with comma-separated values + uri_template_string (ht, "{+var}", "value") + uri_template_string (ht, "{+hello}", "Hello+World!") + uri_template_string (ht, "{+path}/here", "/foo/bar/here") + uri_template_string (ht, "{+path,x}/here", "/foo/bar,1024/here") + uri_template_string (ht, "{+path}{x}/here", "/foo/bar1024/here") + uri_template_string (ht, "{+empty}/here", "/here") + uri_template_string (ht, "{+undef}/here", "/here") + uri_template_string (ht, "{+list}", "val1,val2,val3") + uri_template_string (ht, "{+list*}", "val1,val2,val3") + uri_template_string (ht, "{+list+}", "list.val1,list.val2,list.val3") + uri_template_string (ht, "{+keys}", "key1,val1,key2,val2") + uri_template_string (ht, "{+keys*}", "key1,val1,key2,val2") + uri_template_string (ht, "{+keys+}", "keys.key1,val1,keys.key2,val2") + + --| Path-style parameters, semicolon-prefixed + uri_template_string (ht, "{;x,y}", ";x=1024;y=768") + uri_template_string (ht, "{;x,y,empty}", ";x=1024;y=768;empty") + uri_template_string (ht, "{;x,y,undef}", ";x=1024;y=768") +-- uri_template_string (ht, "{;list}", ";val1,val2,val3") -- DIFF + uri_template_string (ht, "{;list}", ";list=val1,val2,val3") -- DIFF + uri_template_string (ht, "{;list*}", ";val1;val2;val3") + uri_template_string (ht, "{;list+}", ";list=val1;list=val2;list=val3") + uri_template_string (ht, "{;keys}", ";key1,val1,key2,val2") + uri_template_string (ht, "{;keys*}", ";key1=val1;key2=val2") + uri_template_string (ht, "{;keys+}", ";keys.key1=val1;keys.key2=val2") + + --| Form-style parameters, ampersand-separated + uri_template_string (ht, "{?x,y}", "?x=1024&y=768") + uri_template_string (ht, "{?x,y,empty}", "?x=1024&y=768&empty=") + uri_template_string (ht, "{?x,y,undef}", "?x=1024&y=768") + uri_template_string (ht, "{?list}", "?list=val1,val2,val3") + uri_template_string (ht, "{?list*}", "?val1&val2&val3") + uri_template_string (ht, "{?list+}", "?list=val1&list=val2&list=val3") + uri_template_string (ht, "{?keys}", "?keys=key1,val1,key2,val2") + uri_template_string (ht, "{?keys*}", "?key1=val1&key2=val2") + uri_template_string (ht, "{?keys+}", "?keys.key1=val1&keys.key2=val2") + + --| Hierarchical path segments, slash-separated + uri_template_string (ht, "{/var}", "/value") + uri_template_string (ht, "{/var,empty}", "/value/") + uri_template_string (ht, "{/var,undef}", "/value") + uri_template_string (ht, "{/list}", "/val1,val2,val3") + uri_template_string (ht, "{/list*}", "/val1/val2/val3") + uri_template_string (ht, "{/list*,x}", "/val1/val2/val3/1024") + uri_template_string (ht, "{/list+}", "/list.val1/list.val2/list.val3") + uri_template_string (ht, "{/keys}", "/key1,val1,key2,val2") + uri_template_string (ht, "{/keys*}", "/key1/val1/key2/val2") + uri_template_string (ht, "{/keys+}", "/keys.key1/val1/keys.key2/val2") + + --| Label expansion, dot-prefixed + uri_template_string (ht, "X{.var}", "X.value") + uri_template_string (ht, "X{.empty}", "X.") + uri_template_string (ht, "X{.undef}", "X") + uri_template_string (ht, "X{.list}", "X.val1,val2,val3") + uri_template_string (ht, "X{.list*}", "X.val1.val2.val3") + uri_template_string (ht, "X{.list*,x}", "X.val1.val2.val3.1024") + uri_template_string (ht, "X{.list+}", "X.list.val1.list.val2.list.val3") + uri_template_string (ht, "X{.keys}", "X.key1,val1,key2,val2") + uri_template_string (ht, "X{.keys*}", "X.key1.val1.key2.val2") + uri_template_string (ht, "X{.keys+}", "X.keys.key1.val1.keys.key2.val2") + + --| Simple Expansion + uri_template_string (ht, "{foo}", "fred") + uri_template_string (ht, "{foo,foo}", "fred,fred") + uri_template_string (ht, "{bar,foo}", "fred") + uri_template_string (ht, "{bar|wilma}", "wilma") + + --| Reserved Expansion + uri_template_string (ht, "{foo2}", "That%%27s+right%%21") + uri_template_string (ht, "{+foo2}", "That%%27s+right!") + uri_template_string (ht, "{base}index", "http%%3A%%2F%%2Fexample.com%%2Fhome%%2Findex") + uri_template_string (ht, "{+base}index", "http://example.com/home/index") + + assert ("all strings built", uri_template_string_errors = Void or (attached uri_template_string_errors as err and then err.is_empty)) + end + + uri_template_string (a_ht: HASH_TABLE [detachable ANY, STRING]; a_expression: STRING; a_expected: STRING) + local + tpl: URI_TEMPLATE + s: STRING + m: STRING + do + create tpl.make (a_expression) + s := tpl.string (a_ht) + if not s.same_string (a_expected) then + m := "Expected string for %"" + a_expression + "%" expected=%""+ a_expected +"%" but got %"" + s + "%"%N" + if attached uri_template_string_errors as err then + print (m) + err.force (m) + else + assert (m, False) + end + end + end + + uri_template_parse (s: STRING_8; path_vars: ARRAY [STRING]; query_vars: ARRAY [STRING]) + local + u: URI_TEMPLATE + matched: BOOLEAN + i: INTEGER + do + create u.make (s) + if attached u.path_variable_names as vars then + matched := vars.count = path_vars.count + from + i := path_vars.lower + vars.start + until + not matched or i > path_vars.upper + loop + matched := vars.item.same_string (path_vars[i]) + vars.forth + i := i + 1 + end + else + matched := path_vars.is_empty + end + assert ("path variables matched", matched) + + if attached u.query_variable_names as vars then + matched := vars.count = query_vars.count + from + i := query_vars.lower + vars.start + until + not matched or i > query_vars.upper + loop + matched := vars.item.same_string (query_vars[i]) + vars.forth + i := i + 1 + end + else + matched := query_vars.is_empty + end + assert ("query variables matched", matched) + end + + uri_template_match (a_uri_template: URI_TEMPLATE; a_uri: STRING; path_res: ARRAY [TUPLE [name: STRING; value: STRING]]; query_res: ARRAY [TUPLE [name: STRING; value: STRING]]) + local + b: BOOLEAN + i: INTEGER + do + if attached a_uri_template.match (a_uri) as l_match then + if attached l_match.path_variables as path_ht then + b := path_ht.count = path_res.count + from + i := path_res.lower + until + not b or i > path_res.upper + loop + b := attached path_ht.item (path_res[i].name) as s and then s.same_string (path_res[i].value) + i := i + 1 + end + assert ("uri matched path variables", b) + end + if attached l_match.query_variables as query_ht then + b := query_ht.count = query_res.count + from + i := query_res.lower + until + not b or i > query_res.upper + loop + b := attached query_ht.item (query_res[i].name) as s and then s.same_string (query_res[i].value) + i := i + 1 + end + assert ("uri matched query variables", b) + end + else + assert ("uri matched", False) + end + 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 + + diff --git a/library/protocol/uri_template/uri_template-safe.ecf b/library/protocol/uri_template/uri_template-safe.ecf new file mode 100644 index 00000000..ed5b9178 --- /dev/null +++ b/library/protocol/uri_template/uri_template-safe.ecf @@ -0,0 +1,32 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + diff --git a/library/protocol/uri_template/uri_template.ecf b/library/protocol/uri_template/uri_template.ecf new file mode 100644 index 00000000..8d31c66c --- /dev/null +++ b/library/protocol/uri_template/uri_template.ecf @@ -0,0 +1,18 @@ + + + + + + /.git$ + /EIFGENs$ + /.svn$ + + + + + + + + diff --git a/library/text/encoder/src/url_encoder.e b/library/text/encoder/src/url_encoder.e index 63b1af79..b5370e49 100644 --- a/library/text/encoder/src/url_encoder.e +++ b/library/text/encoder/src/url_encoder.e @@ -1,5 +1,9 @@ note - description: "Summary description for {URL_ENCODER}." + description: "[ + Summary description for {URL_ENCODER}. + + See: http://www.faqs.org/rfcs/rfc3986.html + ]" legal: "See notice at end of class." status: "See notice at end of class." date: "$Date$" @@ -59,6 +63,47 @@ feature -- Encoder end end + partial_encoded_string (s: STRING_32; a_ignore: ARRAY [CHARACTER]): STRING_8 + -- URL-encoded value of `s'. + local + i, n: INTEGER + uc: CHARACTER_32 + c: CHARACTER_8 + do + has_error := False + create Result.make (s.count + s.count // 10) + n := s.count + 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 + 'A' .. 'Z', + 'a' .. 'z', '0' .. '9', + '.', '-', '~', '_' + then + Result.extend (c) + when ' ' then + Result.extend ('+') + else + if a_ignore.has (c) then + Result.extend (c) + else + Result.append (url_encoded_char (uc)) + end + end + else + if a_ignore.has (c) then + Result.extend (c) + else + Result.append (url_encoded_char (uc)) + end + end + i := i + 1 + end + end + feature {NONE} -- encoder character url_encoded_char (uc: CHARACTER_32): STRING_8 @@ -309,7 +354,7 @@ feature {NONE} -- Hexadecimal and strings end note - copyright: "Copyright (c) 1984-2011, Eiffel Software and others" + copyright: "2011-2011, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software