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.
- data/.document +5 -0
- data/{test/client/data/1.5/metadata.xml → .gemtest} +0 -0
- data/CHANGELOG +611 -66
- data/CONTRIBUTORS +6 -2
- data/Gemfile +1 -0
- data/LICENSE +22 -0
- data/MANIFEST +63 -0
- data/NEWS +203 -0
- data/{README → README.rdoc} +11 -4
- data/RUBYS +7 -7
- data/Rakefile +48 -0
- data/TODO +5 -1
- data/examples/client_get_object.rb +31 -42
- data/examples/client_login.rb +20 -18
- data/examples/client_mapper.rb +17 -0
- data/examples/client_metadata.rb +28 -28
- data/examples/client_parser.rb +9 -0
- data/examples/client_search.rb +25 -27
- data/examples/settings.yml +114 -0
- data/lib/rets4r.rb +14 -1
- data/lib/rets4r/auth.rb +70 -66
- data/lib/rets4r/client.rb +470 -650
- data/lib/rets4r/client/data.rb +13 -13
- data/lib/rets4r/client/dataobject.rb +27 -19
- data/lib/rets4r/client/exceptions.rb +116 -0
- data/lib/rets4r/client/links.rb +32 -0
- data/lib/rets4r/client/metadata.rb +12 -12
- data/lib/rets4r/client/parsers/compact.rb +42 -0
- data/lib/rets4r/client/parsers/compact_nokogiri.rb +91 -0
- data/lib/rets4r/client/parsers/metadata.rb +92 -0
- data/lib/rets4r/client/parsers/response_parser.rb +100 -0
- data/lib/rets4r/client/requester.rb +143 -0
- data/lib/rets4r/client/transaction.rb +30 -33
- data/lib/rets4r/core_ext/array/extract_options.rb +15 -0
- data/lib/rets4r/core_ext/class/attribute_accessors.rb +58 -0
- data/lib/rets4r/core_ext/hash/keys.rb +46 -0
- data/lib/rets4r/core_ext/hash/slice.rb +39 -0
- data/lib/rets4r/listing_mapper.rb +17 -0
- data/lib/rets4r/listing_service.rb +35 -0
- data/lib/rets4r/loader.rb +8 -0
- data/lib/tasks/annotations.rake +121 -0
- data/lib/tasks/coverage.rake +13 -0
- data/rets4r.gemspec +24 -0
- data/spec/rets4r_compact_data_parser_spec.rb +7 -0
- data/test/data/1.5/bad_compact.xml +7 -0
- data/test/data/1.5/count_only_compact.xml +3 -0
- data/test/{client/data → data}/1.5/error.xml +0 -0
- data/test/{client/data → data}/1.5/invalid_compact.xml +0 -0
- data/test/{client/data → data}/1.5/login.xml +0 -0
- data/test/data/1.5/metadata.xml +0 -0
- data/test/{client/data → data}/1.5/search_compact.xml +0 -0
- data/test/data/1.5/search_compact_big.xml +136 -0
- data/test/{client/data → data}/1.5/search_unescaped_compact.xml +0 -0
- data/test/data/listing_service.yml +36 -0
- data/test/test_auth.rb +68 -0
- data/test/test_client.rb +342 -0
- data/test/test_client_links.rb +39 -0
- data/test/test_compact_nokogiri.rb +64 -0
- data/test/test_helper.rb +12 -0
- data/test/test_listing_mapper.rb +112 -0
- data/test/test_loader.rb +24 -0
- data/test/test_parser.rb +96 -0
- data/test/test_quality.rb +57 -0
- metadata +168 -53
- data/GPL +0 -340
- data/examples/metadata.xml +0 -42
- data/lib/rets4r/client/metadataindex.rb +0 -82
- data/lib/rets4r/client/parser.rb +0 -141
- data/lib/rets4r/client/parser/rexml.rb +0 -75
- data/lib/rets4r/client/parser/xmlparser.rb +0 -95
- data/test/client/parser/tc_rexml.rb +0 -17
- data/test/client/parser/tc_xmlparser.rb +0 -21
- data/test/client/tc_auth.rb +0 -68
- data/test/client/tc_client.rb +0 -320
- data/test/client/tc_metadataindex.rb +0 -36
- data/test/client/test_parser.rb +0 -128
- data/test/client/ts_all.rb +0 -8
- data/test/ts_all.rb +0 -1
- data/test/ts_client.rb +0 -1
data/examples/client_login.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
|
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(
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
36
|
-
|
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
|
data/examples/client_metadata.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
13
|
-
|
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(
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
data/examples/client_search.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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(
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
48
|
-
|
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
|
data/lib/rets4r.rb
CHANGED
@@ -1 +1,14 @@
|
|
1
|
-
|
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'
|
data/lib/rets4r/auth.rb
CHANGED
@@ -1,69 +1,73 @@
|
|
1
1
|
require 'digest/md5'
|
2
2
|
|
3
3
|
module RETS4R
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|