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.
- data/.document +5 -0
- data/CHANGELOG +566 -0
- data/CONTRIBUTORS +7 -0
- data/Gemfile +10 -0
- data/LICENSE +29 -0
- data/MANIFEST +62 -0
- data/NEWS +186 -0
- data/README.rdoc +43 -0
- data/RUBYS +56 -0
- data/Rakefile +50 -0
- data/TODO +35 -0
- data/examples/client_get_object.rb +49 -0
- data/examples/client_login.rb +39 -0
- data/examples/client_mapper.rb +17 -0
- data/examples/client_metadata.rb +42 -0
- data/examples/client_parser.rb +9 -0
- data/examples/client_search.rb +49 -0
- data/examples/settings.yml +114 -0
- data/lib/rets4r.rb +14 -0
- data/lib/rets4r/auth.rb +73 -0
- data/lib/rets4r/client.rb +487 -0
- data/lib/rets4r/client/data.rb +14 -0
- data/lib/rets4r/client/dataobject.rb +28 -0
- data/lib/rets4r/client/exceptions.rb +116 -0
- data/lib/rets4r/client/links.rb +32 -0
- data/lib/rets4r/client/metadata.rb +15 -0
- 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 +31 -0
- 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/data/1.5/error.xml +1 -0
- data/test/data/1.5/invalid_compact.xml +4 -0
- data/test/data/1.5/login.xml +16 -0
- data/test/data/1.5/metadata.xml +0 -0
- data/test/data/1.5/search_compact.xml +8 -0
- data/test/data/1.5/search_compact_big.xml +136 -0
- data/test/data/1.5/search_unescaped_compact.xml +8 -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 +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,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'
|
data/lib/rets4r/auth.rb
ADDED
@@ -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
|