rets4r 0.8.5 → 1.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,15 +1,17 @@
1
- #!/usr/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
  #
3
- # This is an example of how to use the RETS client to log in and out of a server.
3
+ # This is an example of how to use the RETS client to log in and out of a server.
4
4
  #
5
5
  # You will need to set the necessary variables below.
6
6
  #
7
7
  #############################################################################################
8
8
  # Settings
9
9
 
10
- rets_url = 'http://server.com/my/rets/url'
11
- username = 'username'
12
- password = 'password'
10
+ require 'yaml'
11
+ require 'active_support/core_ext/hash'
12
+ settings_file = File.expand_path(File.join(File.dirname(__FILE__), "settings.yml"))
13
+ env = ENV['LISTING_ENV'] || 'development'
14
+ settings = YAML.load_file(settings_file)[env].symbolize_keys
13
15
 
14
16
  #############################################################################################
15
17
  $:.unshift 'lib'
@@ -17,21 +19,21 @@ $:.unshift 'lib'
17
19
  require 'rets4r'
18
20
  require 'logger'
19
21
 
20
- client = RETS4R::Client.new(rets_url)
22
+ client = RETS4R::Client.new(settings[:url])
21
23
  client.logger = Logger.new(STDOUT)
22
24
 
23
- login_result = client.login(username, password)
25
+ login_result = client.login(settings[:username], settings[:password])
24
26
 
25
27
  if login_result.success?
26
- puts "We successfully logged into the RETS server!"
27
-
28
- # Print the action URL results (if any)
29
- puts login_result.secondary_response
30
-
31
- client.logout
32
-
33
- puts "We just logged out of the server."
28
+ puts "We successfully logged into the RETS server!"
29
+
30
+ # Print the action URL results (if any)
31
+ puts login_result.secondary_response
32
+
33
+ client.logout
34
+
35
+ puts "We just logged out of the server."
34
36
  else
35
- puts "We were unable to log into the RETS server."
36
- puts "Please check that you have set the login variables correctly."
37
- end
37
+ puts "We were unable to log into the RETS server."
38
+ puts "Please check that you have set the login variables correctly."
39
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift 'lib'
4
+ require 'rets4r'
5
+ require 'yaml'
6
+
7
+ listing_service_config_file = File.expand_path(File.join(File.dirname(__FILE__), "settings.yml"))
8
+ RETS4R::ListingService.configurations = YAML.load_file(listing_service_config_file)
9
+ RETS4R::ListingService.env = ENV['RETS4RENV'] || 'development'
10
+
11
+ xml = ARGF
12
+
13
+ mapper = RETS4R::ListingMapper.new
14
+ RETS4R::Loader.load(xml) do |record|
15
+ attributes = mapper.map(record)
16
+ puts attributes.inspect
17
+ end
@@ -1,4 +1,4 @@
1
- #!/usr/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
  #
3
3
  # This is an example of how to use the RETS client to login to a server and retrieve metadata. It
4
4
  # also makes use of passing blocks to client methods and demonstrates how to set the output format.
@@ -8,35 +8,35 @@
8
8
  #############################################################################################
9
9
  # Settings
10
10
 
11
- rets_url = 'http://server.com/my/rets/url'
12
- username = 'username'
13
- password = 'password'
14
-
11
+ require 'yaml'
12
+ require 'active_support/core_ext/hash'
13
+ settings_file = File.expand_path(File.join(File.dirname(__FILE__), "settings.yml"))
14
+ env = ENV['LISTING_ENV'] || 'development'
15
+ settings = YAML.load_file(settings_file)[env].symbolize_keys
16
+
15
17
  #############################################################################################
16
18
  $:.unshift 'lib'
17
19
 
18
20
  require 'rets4r'
19
21
 
20
- RETS4R::Client.new(rets_url) do |client|
21
- client.login(username, password) do |login_result|
22
- if login_result.success?
23
- puts "Logged in successfully!"
24
-
25
- # We want the raw metadata, so we need to set the output to raw XML.
26
- client.set_output RETS4R::Client::OUTPUT_RAW
27
- metadata = ''
28
-
29
- begin
30
- metadata = client.get_metadata
31
- rescue
32
- puts "Unable to get metadata: '#{$!}'"
33
- end
34
-
35
- File.open('metadata.xml', 'w') do |file|
36
- file.write metadata
37
- end
38
- else
39
- puts "Unable to login: '#{login_result.reply_text}'."
40
- end
41
- end
42
- end
22
+ RETS4R::Client.new(settings[:url]) do |client|
23
+ client.login(settings[:username], settings[:password]) do |login_result|
24
+ if login_result.success?
25
+ puts "Logged in successfully!"
26
+
27
+ metadata = ''
28
+
29
+ begin
30
+ metadata = client.get_metadata(*ARGV)
31
+ rescue
32
+ puts "Unable to get metadata: '#{$!}'"
33
+ end
34
+
35
+ File.open('metadata.xml', 'w') do |file|
36
+ file.write metadata
37
+ end
38
+ else
39
+ puts "Unable to login: '#{login_result.reply_text}'."
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift 'lib'
3
+ require 'rets4r'
4
+
5
+ xml = ARGF
6
+
7
+ parser = RETS4R::Client::ResponseParser.new
8
+ transaction = parser.parse_results(xml, 'COMPACT')
9
+ transaction.response.each {|row| puts row.inspect }
@@ -1,4 +1,4 @@
1
- #!/usr/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
  #
3
3
  # This is an example of how to use the RETS client to perform a basic search.
4
4
  #
@@ -7,45 +7,43 @@
7
7
  #############################################################################################
8
8
  # Settings
9
9
 
10
- rets_url = 'http://server.com/my/rets/url'
11
- username = 'username'
12
- password = 'password'
13
-
14
- rets_resource = 'Property'
15
- rets_class = 'Residential'
16
- rets_query = '(RetsField=Value)'
10
+ require 'yaml'
11
+ require 'active_support/core_ext/hash'
12
+ settings_file = File.expand_path(File.join(File.dirname(__FILE__), "settings.yml"))
13
+ ENV['LISTING_ENV'] ||= 'development'
14
+ settings = YAML.load_file(settings_file)[ENV['LISTING_ENV']].symbolize_keys
17
15
 
18
16
  #############################################################################################
19
17
  $:.unshift 'lib'
20
18
 
21
19
  require 'rets4r'
22
20
 
23
- client = RETS4R::Client.new(rets_url)
21
+ client = RETS4R::Client.new(settings[:url])
24
22
 
25
23
  logger = Logger.new($stdout)
26
24
  logger.level = Logger::WARN
27
25
  client.logger = logger
28
26
 
29
- login_result = client.login(username, password)
27
+ login_result = client.login(settings[:username], settings[:password])
30
28
 
31
29
  if login_result.success?
32
- puts "We successfully logged into the RETS server!"
33
-
34
- options = {'Limit' => 5}
35
-
36
- client.search(rets_resource, rets_class, rets_query, options) do |result|
37
- result.data.each do |row|
38
- puts row.inspect
39
- puts
40
- end
41
- end
42
-
43
- client.logout
44
-
45
- puts "We just logged out of the server."
30
+ puts "We successfully logged into the RETS server!"
31
+
32
+ options = {'Limit' => settings[:limit]}
33
+
34
+ client.search(settings[:resource], settings[:class], settings[:query], options) do |result|
35
+ result.response.each do |row|
36
+ puts row.inspect
37
+ puts
38
+ end
39
+ end
40
+
41
+ client.logout
42
+
43
+ puts "We just logged out of the server."
46
44
  else
47
- puts "We were unable to log into the RETS server."
48
- puts "Please check that you have set the login variables correctly."
45
+ puts "We were unable to log into the RETS server."
46
+ puts "Please check that you have set the login variables correctly."
49
47
  end
50
48
 
51
- logger.close
49
+ logger.close
@@ -0,0 +1,114 @@
1
+ # This file specifies the access information for the listing service
2
+ # To access the standard RETS demo server, use:
3
+ #
4
+ #test:
5
+ # url: http://demo.crt.realtors.org:6103/rets/login
6
+ # username: Joe
7
+ # password: Schmoe
8
+ # version: 1.7.2
9
+ # resource: Property
10
+ # class: RES
11
+ # select:
12
+ # ListingID: :rets_id
13
+ # AgentID: :agent_id
14
+ # ModificationTimestamp: :modification_timestamp
15
+ # Status: :status
16
+ # ListDate: :list_date
17
+ # ListPrice: :list_price
18
+ # ClosePrice: :close_price
19
+ # ContractDate: :contract_date
20
+ # StreetNumber: :street_number
21
+ # ZipCode: :zip_code
22
+ # City: :city
23
+ # County: :county
24
+ # Unit: :unit_number
25
+ # StreetName: :street_name
26
+ # StreetDirection: :street_direction
27
+ # Rooms: :rooms
28
+ # State: :state
29
+ # Board: :board
30
+ # LivingArea: :living_area
31
+ # Baths: :baths
32
+ # Bedrooms: :beds
33
+ # Garage: :garage
34
+ # SqFt: :square_footage
35
+ # YearBuilt: :year_built
36
+ # AssociationFee: :assoc_fee
37
+ # BathsFull: :baths_full
38
+ # BathHalf: :baths_half
39
+ # Directions: :directions
40
+ # OriginalListPrice: :original_list_price
41
+ # LotSizeDim: :lot_size_dim
42
+ # ListOfficeBrokerID: :office_list
43
+ # OfficeName: :office_name
44
+ # Pool: :pool
45
+ # Remarks: :remarks
46
+ # Stories: :stories
47
+ # TaxID: :taxid
48
+ # ConstructionMaterial: :construction
49
+ # ExteriorFeatures: :exterior
50
+ # Fireplaces: :fireplaces
51
+ # Heating: :heating
52
+ # InteriorFeatures: :interior
53
+ # LotSizeArea: :lot_size
54
+ # MapCoordinate: :mapbook
55
+ # PresentUse: :present_use
56
+ # Style: :style
57
+
58
+ common: &COMMON
59
+ url: http://demo.crt.realtors.org:6103/rets/login
60
+ username: Joe
61
+ password: Schmoe
62
+ version: 1.5
63
+ resource: Property
64
+ class: RES
65
+ limit: 10
66
+ resource_id: "10202512"
67
+ object_type: Photo
68
+ query: "(*)"
69
+ select:
70
+ MLSNUM: :mls
71
+ AGENTLIST: :agent_id
72
+ AGENTLIST_FULLNAME: :agent_full_name
73
+ OFFICELIST_OFFICENAM1: :office_name
74
+ MODIFIED: :rets_updated_at
75
+ LISTSTATUS: :status
76
+ PHOTOCOUNT: :photo_count
77
+ LISTPRICE: :list_price
78
+ STREETNUMDISPLAY: :street_number
79
+ STREETDIR: :street_direction
80
+ STREETDIRSUFFIX: :street_dir_suffix
81
+ STREETNAME: :street_name
82
+ STREETTYPE: :street_type
83
+ UNITNUM: :unit_number
84
+ ZIPCODE: :zip_code
85
+ CITY: :city
86
+ COUNTY: :county
87
+ AREA: :area
88
+ LOTSIZE: :lot_size
89
+ SQFTTOTAL: :square_feet
90
+ NUMLIVINGAREAS: :living_area
91
+ BATHSTOTAL: :baths
92
+ BEDS: :beds
93
+ GARAGECAP: :garage
94
+ YEARBUILT: :year_built
95
+ UID: :uid
96
+ INTERNETDISPLAYYN: :allow_internet_display
97
+ INTERNETLIST_ALL: :internet_list_all
98
+ INTERNETADDRYN: :allow_address_display
99
+ SCHOOLDISTRICT: :school_district
100
+
101
+ development:
102
+ <<: *COMMON
103
+
104
+ test: &TEST
105
+ <<: *COMMON
106
+
107
+ production:
108
+ <<: *COMMON
109
+
110
+ cucumber:
111
+ <<: *TEST
112
+
113
+ staging:
114
+ <<: *TEST
@@ -1 +1,14 @@
1
- require 'rets4r/client'
1
+ # Add lib/rets4r as a default load path.
2
+ dir = File.join File.dirname(__FILE__), 'rets4r'
3
+ $:.unshift(dir) unless $:.include?(dir) || $:.include?(File.expand_path(dir))
4
+
5
+ module RETS4R # :nodoc:
6
+ VERSION = '1.1.18'
7
+ end
8
+
9
+ require 'client'
10
+ require 'loader'
11
+ require 'rubygems'
12
+ require 'client/parsers/compact_nokogiri'
13
+ require 'rets4r/listing_service'
14
+ require 'rets4r/listing_mapper'
@@ -1,69 +1,73 @@
1
1
  require 'digest/md5'
2
2
 
3
3
  module RETS4R
4
- class Auth
5
- # This is the primary method that would normally be used, and while it
6
- def Auth.authenticate(response, username, password, uri, method, requestId, useragent, nc = 0)
7
- authHeader = Auth.parse_header(response['www-authenticate'])
8
-
9
- cnonce = cnonce(useragent, password, requestId, authHeader['nonce'])
10
-
11
- authHash = calculate_digest(username, password, authHeader['realm'], authHeader['nonce'], method, uri, authHeader['qop'], cnonce, nc)
12
-
13
- header = ''
14
- header << "Digest username=\"#{username}\", "
15
- header << "realm=\"#{authHeader['realm']}\", "
16
- header << "qop=\"#{authHeader['qop']}\", "
17
- header << "uri=\"#{uri}\", "
18
- header << "nonce=\"#{authHeader['nonce']}\", "
19
- header << "nc=#{('%08x' % nc)}, "
20
- header << "cnonce=\"#{cnonce}\", "
21
- header << "response=\"#{authHash}\", "
22
- header << "opaque=\"#{authHeader['opaque']}\""
23
-
24
- return header
25
- end
26
-
27
- def Auth.calculate_digest(username, password, realm, nonce, method, uri, qop = false, cnonce = false, nc = 0)
28
- a1 = "#{username}:#{realm}:#{password}"
29
- a2 = "#{method}:#{uri}"
30
-
31
- response = '';
32
-
33
- requestId = Auth.request_id unless requestId
34
-
35
- if (qop)
36
- throw ArgumentException, 'qop requires a cnonce to be provided.' unless cnonce
37
-
38
- response = Digest::MD5.hexdigest("#{Digest::MD5.hexdigest(a1)}:#{nonce}:#{('%08x' % nc)}:#{cnonce}:#{qop}:#{Digest::MD5.hexdigest(a2)}")
39
- else
40
- response = Digest::MD5.hexdigest("#{Digest::MD5.hexdigest(a1)}:#{nonce}:#{Digest::MD5.hexdigest(a2)}")
41
- end
42
-
43
- return response
44
- end
45
-
46
- def Auth.parse_header(header)
47
- type = header[0, header.index(' ')]
48
- args = header[header.index(' '), header.length].strip.split(',').map {|x| x.strip}
49
-
50
- parts = {'type' => type}
51
-
52
- args.each do |arg|
53
- name, value = arg.split('=')
54
-
55
- parts[name.downcase] = value.tr('"', '')
56
- end
57
-
58
- return parts
59
- end
60
-
61
- def Auth.request_id
62
- Digest::MD5.hexdigest(Time.new.to_f.to_s)
63
- end
64
-
65
- def Auth.cnonce(useragent, password, requestId, nonce)
66
- Digest::MD5.hexdigest("#{useragent}:#{password}:#{requestId}:#{nonce}")
67
- end
68
- end
69
- end
4
+ class Auth
5
+ # This is the primary method that would normally be used, and while it
6
+ def Auth.authenticate(response, username, password, uri, method, requestId, useragent, nc = 0)
7
+ if response['www-authenticate'].nil? || response['www-authenticate'].empty?
8
+ raise "Missing required header 'www-authenticate'. Got: #{response}"
9
+ end
10
+
11
+ authHeader = Auth.parse_header(response['www-authenticate'])
12
+
13
+ cnonce = cnonce(useragent, password, requestId, authHeader['nonce'])
14
+
15
+ authHash = calculate_digest(username, password, authHeader['realm'], authHeader['nonce'], method, uri, authHeader['qop'], cnonce, nc)
16
+
17
+ header = ''
18
+ header << "Digest username=\"#{username}\", "
19
+ header << "realm=\"#{authHeader['realm']}\", "
20
+ header << "qop=\"#{authHeader['qop']}\", "
21
+ header << "uri=\"#{uri}\", "
22
+ header << "nonce=\"#{authHeader['nonce']}\", "
23
+ header << "nc=#{('%08x' % nc)}, "
24
+ header << "cnonce=\"#{cnonce}\", "
25
+ header << "response=\"#{authHash}\", "
26
+ header << "opaque=\"#{authHeader['opaque']}\""
27
+
28
+ return header
29
+ end
30
+
31
+ def Auth.calculate_digest(username, password, realm, nonce, method, uri, qop = false, cnonce = false, nc = 0)
32
+ a1 = "#{username}:#{realm}:#{password}"
33
+ a2 = "#{method}:#{uri}"
34
+
35
+ response = '';
36
+
37
+ requestId = Auth.request_id unless requestId
38
+
39
+ if (qop)
40
+ throw ArgumentException, 'qop requires a cnonce to be provided.' unless cnonce
41
+
42
+ response = Digest::MD5.hexdigest("#{Digest::MD5.hexdigest(a1)}:#{nonce}:#{('%08x' % nc)}:#{cnonce}:#{qop}:#{Digest::MD5.hexdigest(a2)}")
43
+ else
44
+ response = Digest::MD5.hexdigest("#{Digest::MD5.hexdigest(a1)}:#{nonce}:#{Digest::MD5.hexdigest(a2)}")
45
+ end
46
+
47
+ return response
48
+ end
49
+
50
+ def Auth.parse_header(header)
51
+ type = header[0, header.index(' ')]
52
+ args = header[header.index(' '), header.length].strip.split(',')
53
+
54
+ parts = {'type' => type}
55
+
56
+ args.each do |arg|
57
+ name, value = arg.split('=')
58
+
59
+ parts[name.downcase.strip] = value.tr('"', '').strip
60
+ end
61
+
62
+ return parts
63
+ end
64
+
65
+ def Auth.request_id
66
+ Digest::MD5.hexdigest(Time.new.to_f.to_s)
67
+ end
68
+
69
+ def Auth.cnonce(useragent, password, requestId, nonce)
70
+ Digest::MD5.hexdigest("#{useragent}:#{password}:#{requestId}:#{nonce}")
71
+ end
72
+ end
73
+ end