jschairb-rets4r 1.1.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.document +5 -0
  2. data/CHANGELOG +566 -0
  3. data/CONTRIBUTORS +7 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE +29 -0
  6. data/MANIFEST +62 -0
  7. data/NEWS +186 -0
  8. data/README.rdoc +43 -0
  9. data/RUBYS +56 -0
  10. data/Rakefile +50 -0
  11. data/TODO +35 -0
  12. data/examples/client_get_object.rb +49 -0
  13. data/examples/client_login.rb +39 -0
  14. data/examples/client_mapper.rb +17 -0
  15. data/examples/client_metadata.rb +42 -0
  16. data/examples/client_parser.rb +9 -0
  17. data/examples/client_search.rb +49 -0
  18. data/examples/settings.yml +114 -0
  19. data/lib/rets4r.rb +14 -0
  20. data/lib/rets4r/auth.rb +73 -0
  21. data/lib/rets4r/client.rb +487 -0
  22. data/lib/rets4r/client/data.rb +14 -0
  23. data/lib/rets4r/client/dataobject.rb +28 -0
  24. data/lib/rets4r/client/exceptions.rb +116 -0
  25. data/lib/rets4r/client/links.rb +32 -0
  26. data/lib/rets4r/client/metadata.rb +15 -0
  27. data/lib/rets4r/client/parsers/compact.rb +42 -0
  28. data/lib/rets4r/client/parsers/compact_nokogiri.rb +91 -0
  29. data/lib/rets4r/client/parsers/metadata.rb +92 -0
  30. data/lib/rets4r/client/parsers/response_parser.rb +100 -0
  31. data/lib/rets4r/client/requester.rb +143 -0
  32. data/lib/rets4r/client/transaction.rb +31 -0
  33. data/lib/rets4r/core_ext/array/extract_options.rb +15 -0
  34. data/lib/rets4r/core_ext/class/attribute_accessors.rb +58 -0
  35. data/lib/rets4r/core_ext/hash/keys.rb +46 -0
  36. data/lib/rets4r/core_ext/hash/slice.rb +39 -0
  37. data/lib/rets4r/listing_mapper.rb +17 -0
  38. data/lib/rets4r/listing_service.rb +35 -0
  39. data/lib/rets4r/loader.rb +8 -0
  40. data/lib/tasks/annotations.rake +121 -0
  41. data/lib/tasks/coverage.rake +13 -0
  42. data/rets4r.gemspec +24 -0
  43. data/spec/rets4r_compact_data_parser_spec.rb +7 -0
  44. data/test/data/1.5/bad_compact.xml +7 -0
  45. data/test/data/1.5/count_only_compact.xml +3 -0
  46. data/test/data/1.5/error.xml +1 -0
  47. data/test/data/1.5/invalid_compact.xml +4 -0
  48. data/test/data/1.5/login.xml +16 -0
  49. data/test/data/1.5/metadata.xml +0 -0
  50. data/test/data/1.5/search_compact.xml +8 -0
  51. data/test/data/1.5/search_compact_big.xml +136 -0
  52. data/test/data/1.5/search_unescaped_compact.xml +8 -0
  53. data/test/data/listing_service.yml +36 -0
  54. data/test/test_auth.rb +68 -0
  55. data/test/test_client.rb +342 -0
  56. data/test/test_client_links.rb +39 -0
  57. data/test/test_compact_nokogiri.rb +64 -0
  58. data/test/test_helper.rb +12 -0
  59. data/test/test_listing_mapper.rb +112 -0
  60. data/test/test_loader.rb +24 -0
  61. data/test/test_parser.rb +96 -0
  62. data/test/test_quality.rb +57 -0
  63. metadata +211 -0
data/TODO ADDED
@@ -0,0 +1,35 @@
1
+ Fix
2
+ * Unit Tests (We need METADATA!)
3
+
4
+ Add support for
5
+ * Standard (non-compact) XML
6
+
7
+ Add
8
+ * More Examples
9
+ * More Documentation
10
+ * Reply Codes (Readable Meaning)
11
+ * A search convenience method that makes subsequent requests if maxrows is true.
12
+ * Extend examples so they are useful replacements for SimpleRETS
13
+ * Shared configuration between examples
14
+
15
+ Verify
16
+ * 1.7 Compliance and support
17
+
18
+ Check
19
+ * GetMetadata
20
+
21
+ Possible To-do Items
22
+ * RETS 1.0
23
+ * RETS 2.0 (May not be necessary since it is now SOAP based, but it would be nice to have a consistent API)
24
+ * Running a RETS Server
25
+ * Update Actions
26
+ * Password Change Transaction
27
+ * Get Transaction
28
+ * HTTPS Support
29
+ * Simple mapping to ActiveRecord
30
+
31
+
32
+ * remove devver link from readme
33
+ * add metric_fu
34
+ * remove runcoderun link
35
+ * set up integrety on heroku
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This is an example of how to use the RETS client to retrieve an objet.
4
+ #
5
+ # You will need to set the necessary variables below.
6
+ #
7
+ #############################################################################################
8
+ # Settings
9
+
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
15
+
16
+ #############################################################################################
17
+ $:.unshift 'lib'
18
+
19
+ require 'rets4r'
20
+ require 'logger'
21
+
22
+ def handle_object(object)
23
+ case object.info['Content-Type']
24
+ when 'image/jpeg' then extension = 'jpg'
25
+ when 'image/gif' then extension = 'gif'
26
+ when 'image/png' then extension = 'png'
27
+ else extension = 'unknown'
28
+ end
29
+
30
+ File.open("#{object.info['Content-ID']}_#{object.info['Object-ID']}.#{extension}", 'w') do |f|
31
+ f.write(object.data)
32
+ end
33
+ end
34
+
35
+ client = RETS4R::Client.new(settings[:url])
36
+
37
+ client.login(settings[:username], settings[:password]) do |login_result|
38
+
39
+ if login_result.success?
40
+ ## Method 1
41
+ # Get objects using a block
42
+ client.get_object(settings[:resource], settings[:object_type], settings[:resource_id] + ':0:*') do |object|
43
+ handle_object(object)
44
+ end
45
+ else
46
+ puts "We were unable to log into the RETS server."
47
+ puts "Please check that you have set the login variables correctly."
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This is an example of how to use the RETS client to log in and out of a server.
4
+ #
5
+ # You will need to set the necessary variables below.
6
+ #
7
+ #############################################################################################
8
+ # Settings
9
+
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
15
+
16
+ #############################################################################################
17
+ $:.unshift 'lib'
18
+
19
+ require 'rets4r'
20
+ require 'logger'
21
+
22
+ client = RETS4R::Client.new(settings[:url])
23
+ client.logger = Logger.new(STDOUT)
24
+
25
+ login_result = client.login(settings[:username], settings[:password])
26
+
27
+ if login_result.success?
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."
36
+ else
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
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This is an example of how to use the RETS client to login to a server and retrieve metadata. It
4
+ # also makes use of passing blocks to client methods and demonstrates how to set the output format.
5
+ #
6
+ # You will need to set the necessary variables below.
7
+ #
8
+ #############################################################################################
9
+ # Settings
10
+
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
+
17
+ #############################################################################################
18
+ $:.unshift 'lib'
19
+
20
+ require 'rets4r'
21
+
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 }
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This is an example of how to use the RETS client to perform a basic search.
4
+ #
5
+ # You will need to set the necessary variables below.
6
+ #
7
+ #############################################################################################
8
+ # Settings
9
+
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
15
+
16
+ #############################################################################################
17
+ $:.unshift 'lib'
18
+
19
+ require 'rets4r'
20
+
21
+ client = RETS4R::Client.new(settings[:url])
22
+
23
+ logger = Logger.new($stdout)
24
+ logger.level = Logger::WARN
25
+ client.logger = logger
26
+
27
+ login_result = client.login(settings[:username], settings[:password])
28
+
29
+ if login_result.success?
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."
44
+ else
45
+ puts "We were unable to log into the RETS server."
46
+ puts "Please check that you have set the login variables correctly."
47
+ end
48
+
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
data/lib/rets4r.rb ADDED
@@ -0,0 +1,14 @@
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'
@@ -0,0 +1,73 @@
1
+ require 'digest/md5'
2
+
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
+ 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