rets4r 0.8.5 → 1.1.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/.document +5 -0
  2. data/{test/client/data/1.5/metadata.xml → .gemtest} +0 -0
  3. data/CHANGELOG +611 -66
  4. data/CONTRIBUTORS +6 -2
  5. data/Gemfile +1 -0
  6. data/LICENSE +22 -0
  7. data/MANIFEST +63 -0
  8. data/NEWS +203 -0
  9. data/{README → README.rdoc} +11 -4
  10. data/RUBYS +7 -7
  11. data/Rakefile +48 -0
  12. data/TODO +5 -1
  13. data/examples/client_get_object.rb +31 -42
  14. data/examples/client_login.rb +20 -18
  15. data/examples/client_mapper.rb +17 -0
  16. data/examples/client_metadata.rb +28 -28
  17. data/examples/client_parser.rb +9 -0
  18. data/examples/client_search.rb +25 -27
  19. data/examples/settings.yml +114 -0
  20. data/lib/rets4r.rb +14 -1
  21. data/lib/rets4r/auth.rb +70 -66
  22. data/lib/rets4r/client.rb +470 -650
  23. data/lib/rets4r/client/data.rb +13 -13
  24. data/lib/rets4r/client/dataobject.rb +27 -19
  25. data/lib/rets4r/client/exceptions.rb +116 -0
  26. data/lib/rets4r/client/links.rb +32 -0
  27. data/lib/rets4r/client/metadata.rb +12 -12
  28. data/lib/rets4r/client/parsers/compact.rb +42 -0
  29. data/lib/rets4r/client/parsers/compact_nokogiri.rb +91 -0
  30. data/lib/rets4r/client/parsers/metadata.rb +92 -0
  31. data/lib/rets4r/client/parsers/response_parser.rb +100 -0
  32. data/lib/rets4r/client/requester.rb +143 -0
  33. data/lib/rets4r/client/transaction.rb +30 -33
  34. data/lib/rets4r/core_ext/array/extract_options.rb +15 -0
  35. data/lib/rets4r/core_ext/class/attribute_accessors.rb +58 -0
  36. data/lib/rets4r/core_ext/hash/keys.rb +46 -0
  37. data/lib/rets4r/core_ext/hash/slice.rb +39 -0
  38. data/lib/rets4r/listing_mapper.rb +17 -0
  39. data/lib/rets4r/listing_service.rb +35 -0
  40. data/lib/rets4r/loader.rb +8 -0
  41. data/lib/tasks/annotations.rake +121 -0
  42. data/lib/tasks/coverage.rake +13 -0
  43. data/rets4r.gemspec +24 -0
  44. data/spec/rets4r_compact_data_parser_spec.rb +7 -0
  45. data/test/data/1.5/bad_compact.xml +7 -0
  46. data/test/data/1.5/count_only_compact.xml +3 -0
  47. data/test/{client/data → data}/1.5/error.xml +0 -0
  48. data/test/{client/data → data}/1.5/invalid_compact.xml +0 -0
  49. data/test/{client/data → data}/1.5/login.xml +0 -0
  50. data/test/data/1.5/metadata.xml +0 -0
  51. data/test/{client/data → data}/1.5/search_compact.xml +0 -0
  52. data/test/data/1.5/search_compact_big.xml +136 -0
  53. data/test/{client/data → data}/1.5/search_unescaped_compact.xml +0 -0
  54. data/test/data/listing_service.yml +36 -0
  55. data/test/test_auth.rb +68 -0
  56. data/test/test_client.rb +342 -0
  57. data/test/test_client_links.rb +39 -0
  58. data/test/test_compact_nokogiri.rb +64 -0
  59. data/test/test_helper.rb +12 -0
  60. data/test/test_listing_mapper.rb +112 -0
  61. data/test/test_loader.rb +24 -0
  62. data/test/test_parser.rb +96 -0
  63. data/test/test_quality.rb +57 -0
  64. metadata +168 -53
  65. data/GPL +0 -340
  66. data/examples/metadata.xml +0 -42
  67. data/lib/rets4r/client/metadataindex.rb +0 -82
  68. data/lib/rets4r/client/parser.rb +0 -141
  69. data/lib/rets4r/client/parser/rexml.rb +0 -75
  70. data/lib/rets4r/client/parser/xmlparser.rb +0 -95
  71. data/test/client/parser/tc_rexml.rb +0 -17
  72. data/test/client/parser/tc_xmlparser.rb +0 -21
  73. data/test/client/tc_auth.rb +0 -68
  74. data/test/client/tc_client.rb +0 -320
  75. data/test/client/tc_metadataindex.rb +0 -36
  76. data/test/client/test_parser.rb +0 -128
  77. data/test/client/ts_all.rb +0 -8
  78. data/test/ts_all.rb +0 -1
  79. data/test/ts_client.rb +0 -1
@@ -1,14 +1,14 @@
1
1
  module RETS4R
2
- class Client
3
- # Represents a row of data. Nothing more than a glorfied Hash with a custom constructor and a
4
- # type attribute.
5
- class Data < ::Hash
6
- attr_accessor :type
7
-
8
- def initialize(type = false)
9
- super
10
- self.type = type
11
- end
12
- end
13
- end
14
- end
2
+ class Client
3
+ # Represents a row of data. Nothing more than a glorfied Hash with a custom constructor and a
4
+ # type attribute.
5
+ class Data < ::Hash
6
+ attr_accessor :type
7
+
8
+ def initialize(type = false)
9
+ super
10
+ self.type = type
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,20 +1,28 @@
1
1
  module RETS4R
2
- class Client
3
- # Represents a RETS object (as returned by the get_object) transaction.
4
- class DataObject
5
- attr_accessor :type, :data
6
-
7
- alias :info :type
8
-
9
- def initialize(type, data)
10
- self.type = type
11
- self.data = data
12
- end
13
-
14
- def success?
15
- return true if self.data
16
- return false
17
- end
18
- end
19
- end
20
- end
2
+ class Client
3
+ class ObjectHeader < Hash
4
+ include Net::HTTPHeader
5
+ def initialize(raw_header)
6
+ initialize_http_header( raw_header )
7
+ end
8
+ end
9
+ # Represents a RETS object (as returned by the get_object) transaction.
10
+ class DataObject
11
+
12
+ attr_accessor :header, :data
13
+
14
+ alias :type :header
15
+ alias :info :type
16
+
17
+ def initialize(headers, data)
18
+ @header = ObjectHeader.new(headers)
19
+ @data = data
20
+ end
21
+
22
+ def success?
23
+ return true if self.data
24
+ return false
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,116 @@
1
+ module RETS4R
2
+ class Client
3
+ # This exception should be thrown when a generic client error is encountered.
4
+ class ClientException < Exception; end
5
+
6
+ # This exception should be thrown when there is an error with the parser, which is
7
+ # considered a subcomponent of the RETS client. It also includes the XML data that
8
+ # that was being processed at the time of the exception.
9
+ class ParserException < ClientException
10
+ attr_accessor :file
11
+ end
12
+
13
+ # The client does not currently support a specified action.
14
+ class Unsupported < ClientException; end
15
+
16
+ # The HTTP response returned by the server indicates that there was an error processing
17
+ # the request and the client cannot continue on its own without intervention.
18
+ class HTTPError < ClientException
19
+ attr_accessor :http_response
20
+
21
+ # Takes a HTTPResponse object
22
+ def initialize(http_response)
23
+ self.http_response = http_response
24
+ end
25
+
26
+ # Shorthand for calling HTTPResponse#code
27
+ def code
28
+ http_response.code
29
+ end
30
+
31
+ # Shorthand for calling HTTPResponse#message
32
+ def message
33
+ http_response.message
34
+ end
35
+
36
+ # Returns the RETS specification message for the HTTP response code
37
+ def rets_message
38
+ Client::RETS_HTTP_MESSAGES[code]
39
+ end
40
+
41
+ def to_s
42
+ "#{code} #{message}: #{rets_message}"
43
+ end
44
+ end
45
+
46
+ # A general RETS level exception was encountered. This would include HTTP and RETS
47
+ # specification level errors as well as informative mishaps such as authentication being
48
+ # required for access.
49
+ class RETSException < RuntimeError; end
50
+
51
+ # There was a problem with logging into the RETS server.
52
+ class LoginError < RETSException; end
53
+
54
+ # For internal client use only, it is thrown when the a RETS request is made but a password
55
+ # is prompted for.
56
+ class AuthRequired < RETSException; end
57
+
58
+ # A RETS transaction failed
59
+ class RETSTransactionException < RETSException; end
60
+
61
+ # Search Transaction Exceptions
62
+ class UnknownQueryFieldException < RETSTransactionException; end
63
+ class NoRecordsFoundException < RETSTransactionException; end
64
+ class InvalidSelectException < RETSTransactionException; end
65
+ class MiscellaneousSearchErrorException < RETSTransactionException; end
66
+ class InvalidQuerySyntaxException < RETSTransactionException; end
67
+ class UnauthorizedQueryException < RETSTransactionException; end
68
+ class MaximumRecordsExceededException < RETSTransactionException; end
69
+ class TimeoutException < RETSTransactionException; end
70
+ class TooManyOutstandingQueriesException < RETSTransactionException; end
71
+ class DTDVersionUnavailableException < RETSTransactionException; end
72
+
73
+ # GetObject Exceptions
74
+ class InvalidResourceException < RETSTransactionException; end
75
+ class InvalidTypeException < RETSTransactionException; end
76
+ class InvalidIdentifierException < RETSTransactionException; end
77
+ class NoObjectFoundException < RETSTransactionException; end
78
+ class UnsupportedMIMETypeException < RETSTransactionException; end
79
+ class UnauthorizedRetrievalException < RETSTransactionException; end
80
+ class ResourceUnavailableException < RETSTransactionException; end
81
+ class ObjectUnavailableException < RETSTransactionException; end
82
+ class RequestTooLargeException < RETSTransactionException; end
83
+ class TimeoutException < RETSTransactionException; end
84
+ class TooManyOutstandingRequestsException < RETSTransactionException; end
85
+ class MiscellaneousErrorException < RETSTransactionException; end
86
+
87
+ EXCEPTION_TYPES = {
88
+ # Search Transaction Reply Codes
89
+ 20200 => UnknownQueryFieldException,
90
+ 20201 => NoRecordsFoundException,
91
+ 20202 => InvalidSelectException,
92
+ 20203 => MiscellaneousSearchErrorException,
93
+ 20206 => InvalidQuerySyntaxException,
94
+ 20207 => UnauthorizedQueryException,
95
+ 20208 => MaximumRecordsExceededException,
96
+ 20209 => TimeoutException,
97
+ 20210 => TooManyOutstandingQueriesException,
98
+ 20514 => DTDVersionUnavailableException,
99
+
100
+ # GetObject Reply Codes
101
+ 20400 => InvalidResourceException,
102
+ 20401 => InvalidTypeException,
103
+ 20402 => InvalidIdentifierException,
104
+ 20403 => NoObjectFoundException,
105
+ 20406 => UnsupportedMIMETypeException,
106
+ 20407 => UnauthorizedRetrievalException,
107
+ 20408 => ResourceUnavailableException,
108
+ 20409 => ObjectUnavailableException,
109
+ 20410 => RequestTooLargeException,
110
+ 20411 => TimeoutException,
111
+ 20412 => TooManyOutstandingRequestsException,
112
+ 20413 => MiscellaneousErrorException
113
+
114
+ }
115
+ end
116
+ end
@@ -0,0 +1,32 @@
1
+ require 'uri'
2
+
3
+ module RETS4R #:nodoc:
4
+ class Client #:nodoc:
5
+ class Links < Hash
6
+ attr_accessor :logger
7
+ def self.from_login_url(login_url)
8
+ links = self.new
9
+ links['Login'] = URI.parse(login_url)
10
+ links
11
+ end
12
+ def login
13
+ self['Login']
14
+ end
15
+ def logout
16
+ self['Logout']
17
+ end
18
+ def metadata
19
+ self['GetMetadata']
20
+ end
21
+ def objects
22
+ self['GetObject']
23
+ end
24
+ def search
25
+ self['Search']
26
+ end
27
+ def action
28
+ self['Action']
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,15 +1,15 @@
1
1
  require 'rets4r/client/data'
2
2
 
3
3
  module RETS4R
4
- class Client
5
- # Represents a set of metadata. It is simply an extended Array with type and attributes accessors.
6
- class Metadata < Array
7
- attr_accessor :type, :attributes
8
-
9
- def initialize(type = false)
10
- self.type = type if type
11
- self.attributes = {}
12
- end
13
- end
14
- end
15
- end
4
+ class Client
5
+ # Represents a set of metadata. It is simply an extended Array with type and attributes accessors.
6
+ class Metadata < Array
7
+ attr_accessor :type, :attributes
8
+
9
+ def initialize(type = false)
10
+ self.type = type if type
11
+ self.attributes = {}
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ # Parses XML response containing 'COMPACT' data format.
2
+
3
+ require 'cgi'
4
+
5
+ module RETS4R
6
+ class Client
7
+ class CompactDataParser
8
+ def parse_results(doc)
9
+
10
+ delimiter = doc.get_elements('/RETS/DELIMITER')[0] &&
11
+ doc.get_elements('/RETS/DELIMITER')[0].attributes['value'].to_i.chr
12
+ columns = doc.get_elements('/RETS/COLUMNS')[0]
13
+ rows = doc.get_elements('/RETS/DATA')
14
+
15
+ parse_data(columns, rows, delimiter)
16
+ end
17
+
18
+ def parse_data(column_element, row_elements, delimiter = "\t")
19
+
20
+ column_names = column_element.to_s.split(delimiter)
21
+
22
+ result = []
23
+
24
+ data = row_elements.each do |data_row|
25
+ data_row = data_row.text.split(delimiter)
26
+
27
+ row_result = {}
28
+
29
+ column_names.each_with_index do |col, x|
30
+ row_result[col] = data_row[x]
31
+ end
32
+
33
+ row_result.reject! { |k,v| k.nil? || k.empty? }
34
+
35
+ result << row_result
36
+ end
37
+
38
+ return result
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,91 @@
1
+ require 'nokogiri'
2
+ module RETS4R
3
+ class Client
4
+ class CompactNokogiriParser
5
+ include Enumerable
6
+ def initialize(io)
7
+ @doc = CompactDocument.new
8
+ @parser = Nokogiri::XML::SAX::Parser.new(@doc)
9
+ @io = io
10
+ end
11
+
12
+ def to_a
13
+ @parser.parse(@io) if @doc.results.empty?
14
+ @doc.results
15
+ end
16
+
17
+ def each(&block)
18
+ @doc.proc = block.to_proc
19
+ @parser.parse(@io)
20
+ nil
21
+ end
22
+
23
+ class CompactDocument < Nokogiri::XML::SAX::Document
24
+ attr_reader :results
25
+ attr_writer :proc
26
+
27
+ def initialize
28
+ @results = []
29
+ end
30
+ def start_element name, attrs = []
31
+ case name
32
+ when 'DELIMITER'
33
+ @delimiter = attrs.last.to_i.chr
34
+ when 'COLUMNS'
35
+ @columns_element = true
36
+ @string = ''
37
+ when 'DATA'
38
+ @data_element = true
39
+ @string = ''
40
+ when 'RETS'
41
+ handle_body_start attrs
42
+ end
43
+ end
44
+
45
+ def end_element name
46
+ case name
47
+ when 'COLUMNS'
48
+ @columns_element = false
49
+ @columns = @string.split(@delimiter)
50
+ when 'DATA'
51
+ @data_element = false
52
+ handle_row
53
+ end
54
+ end
55
+
56
+ def characters string
57
+ if @columns_element
58
+ @string << string
59
+ elsif @data_element
60
+ @string << string
61
+ elsif @reply_code
62
+ throw string
63
+ @reply_code = false
64
+ end
65
+ end
66
+
67
+ private
68
+ def handle_row
69
+ data = make_hash
70
+ if @proc
71
+ @proc.call(data)
72
+ else
73
+ @results << data
74
+ end
75
+ end
76
+ def handle_body_start attrs
77
+ attrs = Hash[*attrs]
78
+ if exception_class = Client::EXCEPTION_TYPES[attrs['ReplyCode'].to_i]
79
+ raise exception_class.new(attrs['ReplyText'])
80
+ end
81
+ end
82
+ def make_hash
83
+ @columns.zip(@string.split(@delimiter)).inject({}) do | h,(k,v) |
84
+ h[k] = v unless k.empty?
85
+ next h
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,92 @@
1
+ require 'rexml/document'
2
+ require 'yaml'
3
+ require 'rets4r/client/parsers/compact'
4
+
5
+ module RETS4R
6
+ class Client
7
+ class MetadataParser
8
+
9
+ TAGS = [ 'METADATA-RESOURCE',
10
+ 'METADATA-CLASS',
11
+ 'METADATA-TABLE',
12
+ 'METADATA-OBJECT',
13
+ 'METADATA-LOOKUP',
14
+ 'METADATA-LOOKUP_TYPE' ]
15
+
16
+ def initialize()
17
+ @parser = RETS4R::Client::CompactDataParser.new
18
+ end
19
+
20
+ def parse_file(file = 'metadata.xml')
21
+ xml = File.read(file)
22
+ doc = REXML::Document.new(xml)
23
+ parse(doc)
24
+ end
25
+
26
+ def parse(doc)
27
+
28
+ rets_resources = {}
29
+
30
+ doc.get_elements('/RETS/*').each do |elem|
31
+
32
+ next unless TAGS.include?(elem.name)
33
+
34
+ columns = elem.get_elements('COLUMNS')[0]
35
+ rows = elem.get_elements('DATA')
36
+
37
+ data = @parser.parse_data(columns, rows)
38
+
39
+ resource_id = elem.attributes['Resource']
40
+
41
+ case elem.name
42
+ when 'METADATA-RESOURCE'
43
+ data.each do |resource_info|
44
+ id = resource_info.delete('ResourceID')
45
+ rets_resources[id] = resource_info
46
+ end
47
+
48
+ when 'METADATA-CLASS'
49
+ data.each do |class_info|
50
+ class_name = class_info.delete('ClassName')
51
+ rets_resources[resource_id][:classes] ||= {}
52
+ rets_resources[resource_id][:classes][class_name] = class_info
53
+ end
54
+
55
+ when 'METADATA-TABLE'
56
+ class_name = elem.attributes['Class']
57
+ data.each do |table_info|
58
+ system_name = table_info.delete('SystemName')
59
+ rets_resources[resource_id][:classes][class_name][:tables] ||= {}
60
+ rets_resources[resource_id][:classes][class_name][:tables][system_name] = table_info
61
+ end
62
+
63
+ when 'METADATA-OBJECT'
64
+ data.each do |object_info|
65
+ object_type = object_info.delete('ObjectType')
66
+ rets_resources[resource_id][:objects] ||= {}
67
+ rets_resources[resource_id][:objects][object_type] = object_info
68
+ end
69
+
70
+ when 'METADATA-LOOKUP'
71
+ data.each do |lookup_info|
72
+ lookup_name = lookup_info.delete('LookupName')
73
+ rets_resources[resource_id][:lookups] ||= {}
74
+ rets_resources[resource_id][:lookups][lookup_name] = lookup_info
75
+ end
76
+
77
+ when 'METADATA-LOOKUP_TYPE'
78
+ lookup = elem.attributes['Lookup']
79
+ rets_resources[resource_id][:lookup_types] ||= {}
80
+ rets_resources[resource_id][:lookup_types][lookup] = {}
81
+ data.each do |lookup_type_info|
82
+ value = lookup_type_info.delete('Value')
83
+ rets_resources[resource_id][:lookup_types][lookup][value] = lookup_type_info
84
+ end
85
+ end
86
+ end
87
+
88
+ rets_resources
89
+ end
90
+ end
91
+ end
92
+ end