jschairb-rets4r 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 (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