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,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