Updated content_negotiation with better class names and feature names.

Minor semantic changes in VARIANTS classes
Factorized some code in HTTP_ACCEPT_LANGUAGE
This commit is contained in:
2013-10-15 23:19:12 +02:00
parent 3072ce7dec
commit d376f99832
26 changed files with 1185 additions and 1052 deletions
@@ -1,81 +0,0 @@
note
description: "Summary description for {FITNESS_AND_QUALITY}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
FITNESS_AND_QUALITY
inherit
COMPARABLE
DEBUG_OUTPUT
undefine
is_equal
end
create
make
feature -- Initialization
make (a_fitness: INTEGER; a_quality: REAL_64)
do
fitness := a_fitness
quality := a_quality
create mime_type.make_empty
ensure
fitness_assigned : fitness = a_fitness
quality_assigned : quality = a_quality
end
feature -- Access
fitness: INTEGER
quality: REAL_64
mime_type: STRING
-- optionally used
-- empty by default
feature -- Status report
debug_output: STRING
-- String that should be displayed in debugger to represent `Current'.
do
create Result.make_from_string (mime_type)
Result.append (" (")
Result.append ("quality=" + quality.out)
Result.append (" ; fitness=" + fitness.out)
Result.append (" )")
end
feature -- Element Change
set_mime_type (a_mime_type: STRING)
-- set mime_type with `a_mime_type'
do
mime_type := a_mime_type
ensure
mime_type_assigned : mime_type.same_string (a_mime_type)
end
feature -- Comparision
is_less alias "<" (other: like Current): BOOLEAN
-- Is current object less than `other'?
do
if fitness = other.fitness then
Result := quality < other.quality
else
Result := fitness < other.fitness
end
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end
@@ -0,0 +1,269 @@
note
description: "[
{HTTP_ACCEPT_LANGUAGE_PARSER} is encharge to parse language tags defined as follow:
Accept-Language = "Accept-Language" ":"
1#( language-l_range [ ";" "q" "=" qvalue ] )
language-l_range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
Example:
Accept-Language: da, en-gb;q=0.8, en;q=0.7
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Accept-Language", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4", "protocol=uri"
class
HTTP_ACCEPT_LANGUAGE_PARSER
inherit
HTTP_HEADER_PARSER
REFACTORING_HELPER
feature -- Parser
accept_language_list (a_header_value: READABLE_STRING_8): LIST [HTTP_ACCEPT_LANGUAGE]
-- Languages-ranges are languages with wild-cards and a 'q' quality parameter.
-- For example, the language l_range ('en-* ;q=0.5') would get parsed into:
-- ('en', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
local
l_res: LIST [READABLE_STRING_8]
l_lang: HTTP_ACCEPT_LANGUAGE
do
l_res := a_header_value.split (',')
create {ARRAYED_LIST [HTTP_ACCEPT_LANGUAGE]} Result.make (l_res.count)
from
l_res.start
until
l_res.after
loop
create l_lang.make_from_string (l_res.item_for_iteration)
Result.force (l_lang)
l_res.forth
end
end
accept_language (a_accept_language_item: READABLE_STRING_8): HTTP_ACCEPT_LANGUAGE
-- Languages-ranges are languages with wild-cards and a 'q' quality parameter.
-- For example, the language l_range ('en-* ;q=0.5') would get parsed into:
-- ('en', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
do
create Result.make_from_string (a_accept_language_item)
end
quality (a_language: READABLE_STRING_8; a_ranges: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a `a_language' when compared against the
-- language l_range in `a_ranges'.
do
Result := quality_from_list (a_language, accept_language_list (a_ranges))
end
best_match (a_supported: LIST [READABLE_STRING_8]; a_header_value: READABLE_STRING_8): READABLE_STRING_8
-- Choose the `parse_language' with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [HTTP_ACCEPT_LANGUAGE]
l_weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [READABLE_STRING_8]
p_res: HTTP_ACCEPT_LANGUAGE
l_fitness_and_quality, l_first_one: detachable FITNESS_AND_QUALITY
s: READABLE_STRING_8
do
l_header_results := accept_language_list (a_header_value)
--| weighted matches
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} l_weighted_matches.make (a_supported.count)
from
a_supported.start
until
a_supported.after
loop
l_fitness_and_quality := fitness_and_quality_from_list (a_supported.item_for_iteration, l_header_results)
l_fitness_and_quality.set_entity (entity_value (a_supported.item_for_iteration))
l_weighted_matches.force (l_fitness_and_quality)
a_supported.forth
end
--| Keep only top quality+fitness types
from
l_weighted_matches.start
l_first_one := l_weighted_matches.item
l_weighted_matches.forth
until
l_weighted_matches.after
loop
l_fitness_and_quality := l_weighted_matches.item
if l_first_one < l_fitness_and_quality then
l_first_one := l_fitness_and_quality
if not l_weighted_matches.isfirst then
from
l_weighted_matches.back
until
l_weighted_matches.before
loop
l_weighted_matches.remove
l_weighted_matches.back
end
l_weighted_matches.forth
end
check
l_weighted_matches.item = l_fitness_and_quality
end
l_weighted_matches.forth
elseif l_first_one ~ l_fitness_and_quality then
l_weighted_matches.forth
else
check
l_first_one > l_fitness_and_quality
end
l_weighted_matches.remove
end
end
if l_first_one /= Void and then l_first_one.quality /= 0.0 then
if l_weighted_matches.count = 1 then
Result := l_first_one.entity
else
from
l_fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or l_fitness_and_quality /= Void
loop
s := l_header_results.item.language_range
from
l_weighted_matches.start
until
l_weighted_matches.after or l_fitness_and_quality /= Void
loop
l_fitness_and_quality := l_weighted_matches.item
if l_fitness_and_quality.entity.same_string (s) then
--| Found
else
l_fitness_and_quality := Void
l_weighted_matches.forth
end
end
l_header_results.forth
end
if l_fitness_and_quality /= Void then
Result := l_fitness_and_quality.entity
else
Result := l_first_one.entity
end
end
else
Result := ""
end
end
feature {NONE} -- Implementation
fitness_and_quality_from_list (a_language: READABLE_STRING_8; a_parsed_ranges: LIST [HTTP_ACCEPT_LANGUAGE]): FITNESS_AND_QUALITY
-- Find the best match for a given `a_language' against a list of language ranges `a_parsed_ranges'
-- that have already been parsed by parse_language_range.
local
l_best_fitness: INTEGER
l_target_q: REAL_64
l_best_fit_q: REAL_64
l_target: HTTP_ACCEPT_LANGUAGE
l_target_type: READABLE_STRING_8
l_range: HTTP_ACCEPT_LANGUAGE
l_keys: LIST [READABLE_STRING_8]
l_param_matches: INTEGER
l_element: detachable READABLE_STRING_8
l_fitness: INTEGER
do
l_best_fitness := -1
l_best_fit_q := 0.0
create l_target.make_from_string (a_language)
l_target_q := l_target.quality
l_target_type := l_target.language
from
a_parsed_ranges.start
until
a_parsed_ranges.after
loop
l_range := a_parsed_ranges.item_for_iteration
if
attached l_range.language as l_range_type and then
( l_target_type.same_string (l_range_type)
or l_range_type.same_string ("*")
or l_target_type.same_string ("*")
)
then
from
l_param_matches := 0
l_keys := l_target.keys
l_keys.start
until
l_keys.after
loop
l_element := l_keys.item_for_iteration
if
not l_element.same_string ("q") and then
l_range.has_key (l_element) and then
(attached l_target.item (l_element) as t_item and attached l_range.item (l_element) as r_item) and then
t_item.same_string (r_item)
then
l_param_matches := l_param_matches + 1
end
l_keys.forth
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if
attached l_range.specialization as l_range_sub_type and then
attached l_target.specialization as l_target_sub_type and then
( l_target_sub_type.same_string (l_range_sub_type)
or l_range_sub_type.same_string ("*")
or l_target_sub_type.same_string ("*")
)
then
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
end
l_fitness := l_fitness + l_param_matches
if l_fitness > l_best_fitness then
l_best_fitness := l_fitness
l_element := l_range.item ("q")
if l_element /= Void then
l_best_fit_q := l_element.to_real_64.min (l_target_q)
else
l_best_fit_q := 0.0
end
end
end
a_parsed_ranges.forth
end
create Result.make (l_best_fitness, l_best_fit_q)
end
quality_from_list (a_language: READABLE_STRING_8; a_parsed_ranges: LIST [HTTP_ACCEPT_LANGUAGE]): REAL_64
-- Find the best match for a given `a_language' against a list of ranges `parsed_ranges' that
-- have already been parsed by parse_language_range. Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality except that 'a_parsed_ranges' must be a list
-- of parsed language ranges.
do
Result := fitness_and_quality_from_list (a_language, a_parsed_ranges).quality
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end
@@ -1,6 +1,6 @@
note
description: "[
{MIME_PARSE}. is encharge to parse Accept request-header field defined as follow:
{HTTP_ACCEPT_MEDIA_TYPE_PARSER}. is encharge to parse Accept request-header field defined as follow:
Accept = "Accept" ":"
#( media-range [ accept-params ] )
@@ -20,26 +20,17 @@
EIS: "name=Accept", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1", "protocol=uri"
class
MIME_PARSE
HTTP_ACCEPT_MEDIA_TYPE_PARSER
inherit {NONE}
inherit
MIME_TYPE_PARSER_UTILITIES
HTTP_HEADER_PARSER
REFACTORING_HELPER
feature -- Parser
parse_mime_type (a_mime_type: READABLE_STRING_8): HTTP_MEDIA_TYPE
-- Parses a mime-type into its component parts.
-- For example, the media range 'application/xhtml;q=0.5' would get parsed
-- into:
-- ('application', 'xhtml', {'q', '0.5'})
do
create Result.make_from_string (a_mime_type)
end
parse_media_range (a_range: READABLE_STRING_8): HTTP_MEDIA_TYPE
media_type (a_range: READABLE_STRING_8): HTTP_MEDIA_TYPE
-- Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
-- For example, the media range 'application/*;q=0.5' would get parsed into:
-- ('application', '*', {'q', '0.5'})
@@ -48,11 +39,11 @@ feature -- Parser
-- necessary.
do
fixme ("Improve the code!!!")
Result := parse_mime_type (a_range)
create Result.make_from_string (a_range)
if attached Result.parameter ("q") as q then
if
q.is_double and then
attached {REAL_64} q.to_double as r and then
attached {REAL_64} q.to_real_64 as r and then
(r >= 0.0 and r <= 1.0)
then
--| Keep current value
@@ -68,8 +59,136 @@ feature -- Parser
end
end
quality (a_mime_type: READABLE_STRING_8; ranges: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a mime-type when compared against the
-- mediaRanges in ranges.
local
l_ranges : LIST [READABLE_STRING_8]
res : ARRAYED_LIST [HTTP_MEDIA_TYPE]
p_res : HTTP_MEDIA_TYPE
do
l_ranges := ranges.split (',')
from
create res.make (10);
l_ranges.start
until
l_ranges.after
loop
p_res := media_type (l_ranges.item_for_iteration)
res.force (p_res)
l_ranges.forth
end
Result := quality_from_list (a_mime_type, res)
end
fitness_and_quality_parsed (a_mime_type: READABLE_STRING_8; parsed_ranges: LIST [HTTP_MEDIA_TYPE]): FITNESS_AND_QUALITY
best_match (supported: LIST [READABLE_STRING_8]; header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the mime-type with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [HTTP_MEDIA_TYPE]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [READABLE_STRING_8]
p_res: HTTP_MEDIA_TYPE
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: READABLE_STRING_8
do
l_res := header.split (',')
create {ARRAYED_LIST [HTTP_MEDIA_TYPE]} l_header_results.make (l_res.count)
fixme("Extract method!!!")
from
l_res.start
until
l_res.after
loop
p_res := media_type (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_from_list (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_entity (entity_value (supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check weighted_matches.item = fitness_and_quality end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check first_one > fitness_and_quality end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.entity
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.simple_type
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.entity.same_string (s) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.entity
else
Result := first_one.entity
end
end
else
Result := ""
end
end
feature {NONE} -- Implementation
fitness_and_quality_from_list (a_mime_type: READABLE_STRING_8; parsed_ranges: LIST [HTTP_MEDIA_TYPE]): FITNESS_AND_QUALITY
-- Find the best match for a given mimeType against a list of media_ranges
-- that have already been parsed by parse_media_range.
local
@@ -84,7 +203,7 @@ feature -- Parser
do
best_fitness := -1
best_fit_q := 0.0
target := parse_media_range (a_mime_type)
target := media_type (a_mime_type)
if attached target.parameter ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
@@ -163,142 +282,16 @@ feature -- Parser
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_mime_type: READABLE_STRING_8; parsed_ranges: LIST [HTTP_MEDIA_TYPE]): REAL_64
quality_from_list (a_mime_type: READABLE_STRING_8; parsed_ranges: LIST [HTTP_MEDIA_TYPE]): REAL_64
-- Find the best match for a given mime-type against a list of ranges that
-- have already been parsed by parse_media_range. Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality except that 'parsed_ranges' must be a list
-- of parsed media ranges.
do
Result := fitness_and_quality_parsed (a_mime_type, parsed_ranges).quality
Result := fitness_and_quality_from_list (a_mime_type, parsed_ranges).quality
end
quality (a_mime_type: READABLE_STRING_8; ranges: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a mime-type when compared against the
-- mediaRanges in ranges.
local
l_ranges : LIST [READABLE_STRING_8]
res : ARRAYED_LIST [HTTP_MEDIA_TYPE]
p_res : HTTP_MEDIA_TYPE
do
l_ranges := ranges.split (',')
from
create res.make (10);
l_ranges.start
until
l_ranges.after
loop
p_res := parse_media_range (l_ranges.item_for_iteration)
res.force (p_res)
l_ranges.forth
end
Result := quality_parsed (a_mime_type, res)
end
best_match (supported: LIST [READABLE_STRING_8]; header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the mime-type with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [HTTP_MEDIA_TYPE]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [READABLE_STRING_8]
p_res: HTTP_MEDIA_TYPE
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: READABLE_STRING_8
do
l_res := header.split (',')
create {ARRAYED_LIST [HTTP_MEDIA_TYPE]} l_header_results.make (l_res.count)
fixme("Extract method!!!")
from
l_res.start
until
l_res.after
loop
p_res := parse_media_range (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check weighted_matches.item = fitness_and_quality end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check first_one > fitness_and_quality end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.simple_type
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (s) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
@@ -1,6 +1,6 @@
note
description: "[
COMMON_ACCEPT_HEADER_PARSER, this class allows to parse Accept-Charset and Accept-Encoding headers
HTTP_ANY_ACCEPT_HEADER_PARSER, this class allows to parse Accept-* headers
]"
date: "$Date$"
@@ -9,17 +9,15 @@ note
EIS: "name=Encoding", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3", "protocol=uri"
class
COMMON_ACCEPT_HEADER_PARSER
inherit {NONE}
MIME_TYPE_PARSER_UTILITIES
HTTP_ANY_ACCEPT_HEADER_PARSER
inherit
HTTP_HEADER_PARSER
feature -- Parser
parse_common (header: READABLE_STRING_8): COMMON_RESULTS
-- Parses `header' charset/encoding into its component parts.
header (a_header: READABLE_STRING_8): HTTP_ANY_ACCEPT_HEADER
-- Parses `a_header' charset/encoding into its component parts.
-- For example, the charset 'iso-8889-5' would get parsed
-- into:
-- ('iso-8889-5', {'q':'1.0'})
@@ -31,7 +29,7 @@ feature -- Parser
l_header: READABLE_STRING_8
do
create Result.make
l_parts := header.split (';')
l_parts := a_header.split (';')
if l_parts.count = 1 then
Result.put ("1.0", "q")
else
@@ -40,7 +38,7 @@ feature -- Parser
until
i > l_parts.count
loop
p := l_parts.at (i)
p := l_parts [i]
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts [2]), trim (sub_parts [1]))
@@ -52,7 +50,141 @@ feature -- Parser
Result.set_field (trim (l_header))
end
fitness_and_quality_parsed (a_field: READABLE_STRING_8; a_parsed_charsets: LIST [COMMON_RESULTS]): FITNESS_AND_QUALITY
quality (a_field: READABLE_STRING_8; a_commons: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a charset/encoding when compared against the
-- a list of charsets/encodings/
local
l_commons: LIST [READABLE_STRING_8]
res: ARRAYED_LIST [HTTP_ANY_ACCEPT_HEADER]
p_res: HTTP_ANY_ACCEPT_HEADER
do
l_commons := a_commons.split (',')
from
create res.make (10)
l_commons.start
until
l_commons.after
loop
p_res := header (l_commons.item_for_iteration)
res.force (p_res)
l_commons.forth
end
Result := quality_from_list (a_field, res)
end
best_match (a_supported: LIST [READABLE_STRING_8]; a_header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the accept with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [HTTP_ANY_ACCEPT_HEADER]
l_weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [READABLE_STRING_8]
p_res: HTTP_ANY_ACCEPT_HEADER
l_fitness_and_quality, l_first_one: detachable FITNESS_AND_QUALITY
do
l_res := a_header.split (',')
create {ARRAYED_LIST [HTTP_ANY_ACCEPT_HEADER]} l_header_results.make (l_res.count)
from
l_res.start
until
l_res.after
loop
p_res := header (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} l_weighted_matches.make (a_supported.count)
from
a_supported.start
until
a_supported.after
loop
l_fitness_and_quality := fitness_and_quality_from_list (a_supported.item_for_iteration, l_header_results)
l_fitness_and_quality.set_entity (entity_value (a_supported.item_for_iteration))
l_weighted_matches.force (l_fitness_and_quality)
a_supported.forth
end
--| Keep only top quality+fitness types
--| TODO extract method
from
l_weighted_matches.start
l_first_one := l_weighted_matches.item
l_weighted_matches.forth
until
l_weighted_matches.after
loop
l_fitness_and_quality := l_weighted_matches.item
if l_first_one < l_fitness_and_quality then
l_first_one := l_fitness_and_quality
if not l_weighted_matches.isfirst then
from
l_weighted_matches.back
until
l_weighted_matches.before
loop
l_weighted_matches.remove
l_weighted_matches.back
end
l_weighted_matches.forth
end
check
l_weighted_matches.item = l_fitness_and_quality
end
l_weighted_matches.forth
elseif l_first_one.is_equal (l_fitness_and_quality) then
l_weighted_matches.forth
else
check
l_first_one > l_fitness_and_quality
end
l_weighted_matches.remove
end
end
if l_first_one /= Void and then l_first_one.quality /= 0.0 then
if l_weighted_matches.count = 1 then
Result := l_first_one.entity
else
from
l_fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or l_fitness_and_quality /= Void
loop
if attached l_header_results.item.field as l_field then
from
l_weighted_matches.start
until
l_weighted_matches.after or l_fitness_and_quality /= Void
loop
l_fitness_and_quality := l_weighted_matches.item
if l_fitness_and_quality.entity.same_string (l_field) then
--| Found
else
l_fitness_and_quality := Void
l_weighted_matches.forth
end
end
else
check
has_field: False
end
end
l_header_results.forth
end
if l_fitness_and_quality /= Void then
Result := l_fitness_and_quality.entity
else
Result := l_first_one.entity
end
end
else
Result := ""
end
end
feature {NONE} -- Implementation
fitness_and_quality_from_list (a_field: READABLE_STRING_8; a_parsed_charsets: LIST [HTTP_ANY_ACCEPT_HEADER]): FITNESS_AND_QUALITY
-- Find the best match for a given charset/encoding against a list of charsets/encodings
-- that have already been parsed by parse_common. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
@@ -62,14 +194,14 @@ feature -- Parser
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: COMMON_RESULTS
range: COMMON_RESULTS
target: HTTP_ANY_ACCEPT_HEADER
range: HTTP_ANY_ACCEPT_HEADER
element: detachable READABLE_STRING_8
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_common (a_field)
target := header (a_field)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
@@ -111,146 +243,15 @@ feature -- Parser
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_field: READABLE_STRING_8; parsed_common: LIST [COMMON_RESULTS]): REAL_64
quality_from_list (a_field: READABLE_STRING_8; a_parsed_common: LIST [HTTP_ANY_ACCEPT_HEADER]): REAL_64
-- Find the best match for a given charset/encoding against a list of charsets/encodings that
-- have already been parsed by parse_charsets(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality()
do
Result := fitness_and_quality_parsed (a_field, parsed_common).quality
Result := fitness_and_quality_from_list (a_field, a_parsed_common).quality
end
quality (a_field: READABLE_STRING_8; commons: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a charset/encoding when compared against the
-- a list of charsets/encodings/
local
l_commons: LIST [READABLE_STRING_8]
res: ARRAYED_LIST [COMMON_RESULTS]
p_res: COMMON_RESULTS
do
l_commons := commons.split (',')
from
create res.make (10)
l_commons.start
until
l_commons.after
loop
p_res := parse_common (l_commons.item_for_iteration)
res.force (p_res)
l_commons.forth
end
Result := quality_parsed (a_field, res)
end
best_match (supported: LIST [READABLE_STRING_8]; header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the accept with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [COMMON_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [READABLE_STRING_8]
p_res: COMMON_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
do
l_res := header.split (',')
create {ARRAYED_LIST [COMMON_RESULTS]} l_header_results.make (l_res.count)
from
l_res.start
until
l_res.after
loop
p_res := parse_common (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
--| TODO extract method
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check
weighted_matches.item = fitness_and_quality
end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check
first_one > fitness_and_quality
end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
if attached l_header_results.item.field as l_field then
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (l_field) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
else
check
has_field: False
end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
@@ -1,14 +1,15 @@
note
description: "{MIME_TYPE_PARSER_UTILITIES}."
description: "Summary description for {HTTP_HEADER_PARSER}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
MIME_TYPE_PARSER_UTILITIES
deferred class
HTTP_HEADER_PARSER
feature {NONE} -- Implementation
feature {NONE} -- Helpers
mime_type (a_str: READABLE_STRING_8): READABLE_STRING_8
entity_value (a_str: READABLE_STRING_8): READABLE_STRING_8
-- `s' with any trailing parameters stripped
local
p: INTEGER
@@ -21,20 +22,17 @@ feature {NONE} -- Implementation
end
end
trim (a_string: READABLE_STRING_8): READABLE_STRING_8
trim (a_string: READABLE_STRING_8): STRING_8
-- trim whitespace from the beginning and end of a string
-- `a_string'
require
valid_argument : a_string /= Void
local
l_result: STRING
do
l_result := a_string.as_string_8
l_result.left_adjust
l_result.right_adjust
Result := l_result
create Result.make_from_string (a_string)
Result.left_adjust
Result.right_adjust
ensure
result_same_as_argument: Result.same_string_general (a_string)
result_trimmed: a_string.has_substring (Result)
end
note
@@ -1,312 +0,0 @@
note
description: "[
{LANGUAGE_PARSE} is encharge to parse language tags defined as follow:
Accept-Language = "Accept-Language" ":"
1#( language-range [ ";" "q" "=" qvalue ] )
language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
Example:
Accept-Language: da, en-gb;q=0.8, en;q=0.7
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Accept-Language", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4", "protocol=uri"
class
LANGUAGE_PARSE
inherit {NONE}
MIME_TYPE_PARSER_UTILITIES
REFACTORING_HELPER
feature -- Parser
parse_language (a_accept_language: READABLE_STRING_8): LANGUAGE_RESULTS
-- Parses `a_accept_language' request-header field into its component parts.
-- For example, the language range 'en-gb;q=0.8' would get parsed
-- into:
-- ('en-gb', {'q':'0.8',})
local
l_parts: LIST [READABLE_STRING_8]
p: READABLE_STRING_8
sub_parts: LIST [READABLE_STRING_8]
i: INTEGER
l_full_type: READABLE_STRING_8
l_types: LIST [READABLE_STRING_8]
do
fixme ("Improve code!!!")
create Result.make
l_parts := a_accept_language.split (';')
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts [2]), trim (sub_parts [1]))
end
i := i + 1
end
l_full_type := trim (l_parts [1])
if l_full_type.same_string ("*") then
l_full_type := "*"
end
l_types := l_full_type.split ('-')
if l_types.count = 1 then
Result.set_type (trim (l_types [1]))
else
Result.set_type (trim (l_types [1]))
Result.set_sub_type (trim (l_types [2]))
end
end
parse_language_range (a_language_range: READABLE_STRING_8): LANGUAGE_RESULTS
-- Languages-ranges are languages with wild-cards and a 'q' quality parameter.
-- For example, the language range ('en-* ;q=0.5') would get parsed into:
-- ('en', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
do
fixme ("Improve the code!!!")
Result := parse_language (a_language_range)
if attached Result.item ("q") as q then
if q.is_double and then attached {REAL_64} q.to_double as r and then (r >= 0.0 and r <= 1.0) then
--| Keep current value
if q.same_string ("1") then
--| Use 1.0 formatting
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
end
fitness_and_quality_parsed (a_language: READABLE_STRING_8; a_parsed_ranges: LIST [LANGUAGE_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given `a_language' against a list of language ranges `a_parsed_ranges'
-- that have already been parsed by parse_language_range.
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: LANGUAGE_RESULTS
range: LANGUAGE_RESULTS
keys: LIST [READABLE_STRING_8]
param_matches: INTEGER
element: detachable READABLE_STRING_8
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_language_range (a_language)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if attached target.type as l_target_type then
from
a_parsed_ranges.start
until
a_parsed_ranges.after
loop
range := a_parsed_ranges.item_for_iteration
if (attached range.type as l_range_type and then (l_target_type.same_string (l_range_type) or l_range_type.same_string ("*") or l_target_type.same_string ("*"))) then
from
param_matches := 0
keys := target.keys
keys.start
until
keys.after
loop
element := keys.item_for_iteration
if not element.same_string ("q") and then range.has_key (element) and then (attached target.item (element) as t_item and attached range.item (element) as r_item) and then t_item.same_string (r_item) then
param_matches := param_matches + 1
end
keys.forth
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if (attached range.sub_type as l_range_sub_type and then attached target.sub_type as l_target_sub_type and then (l_target_sub_type.same_string (l_range_sub_type) or l_range_sub_type.same_string ("*") or l_target_sub_type.same_string ("*"))) then
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
end
l_fitness := l_fitness + param_matches
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
a_parsed_ranges.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_language: READABLE_STRING_8; a_parsed_ranges: LIST [LANGUAGE_RESULTS]): REAL_64
-- Find the best match for a given `a_language' against a list of ranges `parsed_ranges' that
-- have already been parsed by parse_language_range. Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality except that 'a_parsed_ranges' must be a list
-- of parsed language ranges.
do
Result := fitness_and_quality_parsed (a_language, a_parsed_ranges).quality
end
quality (a_language: READABLE_STRING_8; a_ranges: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a `a_language' when compared against the
-- language range in `a_ranges'.
local
l_ranges: LIST [READABLE_STRING_8]
res: ARRAYED_LIST [LANGUAGE_RESULTS]
p_res: LANGUAGE_RESULTS
do
l_ranges := a_ranges.split (',')
from
create res.make (10);
l_ranges.start
until
l_ranges.after
loop
p_res := parse_language_range (l_ranges.item_for_iteration)
res.force (p_res)
l_ranges.forth
end
Result := quality_parsed (a_language, res)
end
best_match (a_supported: LIST [READABLE_STRING_8]; a_header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the `language' with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [LANGUAGE_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [READABLE_STRING_8]
p_res: LANGUAGE_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: READABLE_STRING_8
do
l_res := a_header.split (',')
create {ARRAYED_LIST [LANGUAGE_RESULTS]} l_header_results.make (l_res.count)
fixme ("Extract method!!!")
from
l_res.start
until
l_res.after
loop
p_res := parse_language_range (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (a_supported.count)
from
a_supported.start
until
a_supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (a_supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (a_supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
a_supported.forth
end
--| Keep only top quality+fitness types
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check
weighted_matches.item = fitness_and_quality
end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check
first_one > fitness_and_quality
end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.mime_type
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (s) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end