rets 0.7.0 → 0.8.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/rets.rb +13 -1
- data/lib/rets/client.rb +5 -49
- data/lib/rets/http_client.rb +2 -2
- data/lib/rets/metadata/containers.rb +7 -2
- data/lib/rets/metadata/lookup_type.rb +5 -2
- data/lib/rets/metadata/resource.rb +8 -4
- data/lib/rets/metadata/rets_class.rb +10 -5
- data/lib/rets/metadata/root.rb +5 -2
- data/lib/rets/metadata/table.rb +29 -21
- data/lib/rets/parser/compact.rb +7 -14
- data/lib/rets/parser/multipart.rb +1 -2
- data/test/fixtures.rb +47 -2
- data/test/test_client.rb +35 -0
- data/test/test_parser_compact.rb +23 -10
- data/test/test_parser_multipart.rb +1 -1
- metadata +9 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d46af696793fde645c0c712cdb2e8a708bb93a1
|
4
|
+
data.tar.gz: dd858c7811d76a390e178afd853f73a6f1d9807b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6358cf33cb29fb6801dc0e6046f281a41deb49b1163f8a8344f9441ee7737edbd70b2cb5b04190da9abfd5c29c7721062ae7caf7885815efef11fe28b93aea27
|
7
|
+
data.tar.gz: 4ac48ac0f4230210b4e52c9fcd49db1896510f4d819c2ebf7fb075618c1e7bfcfc4b76cccdbc135c5948c6a8903987c42d958387c5b77c3ebf8b5166421a3b36
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
### 0.8.0 / 2015-06-09
|
2
|
+
|
3
|
+
* reduce memory usage on parsing metadata
|
4
|
+
* correctly raise authorization error when given XHTML instead of XML
|
5
|
+
* unescape HTML encoded responses
|
6
|
+
* make httpclient version requirement more specific
|
7
|
+
* add ability to print metadata to a file
|
8
|
+
* remove Gemfile.lock from repository
|
9
|
+
|
1
10
|
### 0.7.0 / 2015-01-16
|
2
11
|
|
3
12
|
* feature: optionally treat No Records Found as not an error
|
data/lib/rets.rb
CHANGED
@@ -3,7 +3,7 @@ require 'digest/md5'
|
|
3
3
|
require 'nokogiri'
|
4
4
|
|
5
5
|
module Rets
|
6
|
-
VERSION = '0.
|
6
|
+
VERSION = '0.8.0'
|
7
7
|
|
8
8
|
MalformedResponse = Class.new(ArgumentError)
|
9
9
|
UnknownResponse = Class.new(ArgumentError)
|
@@ -28,6 +28,16 @@ module Rets
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
class NoObjectFound < ArgumentError
|
32
|
+
ERROR_CODE = 20403
|
33
|
+
attr_reader :reply_text
|
34
|
+
|
35
|
+
def initialize(reply_text)
|
36
|
+
@reply_text = reply_text
|
37
|
+
super("Got error code #{ERROR_CODE} (#{reply_text})")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
31
41
|
class InvalidRequest < ArgumentError
|
32
42
|
attr_reader :error_code, :reply_text
|
33
43
|
def initialize(error_code, reply_text)
|
@@ -47,8 +57,10 @@ module Rets
|
|
47
57
|
end
|
48
58
|
end
|
49
59
|
|
60
|
+
require 'rets/http_client'
|
50
61
|
require 'rets/client'
|
51
62
|
require 'rets/metadata'
|
63
|
+
require 'rets/parser/error_checker'
|
52
64
|
require 'rets/parser/compact'
|
53
65
|
require 'rets/parser/multipart'
|
54
66
|
require 'rets/measuring_http_client'
|
data/lib/rets/client.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'httpclient'
|
2
2
|
require 'logger'
|
3
|
-
require_relative 'http_client'
|
4
3
|
|
5
4
|
module Rets
|
6
5
|
class HttpError < StandardError ; end
|
@@ -61,7 +60,7 @@ module Rets
|
|
61
60
|
# page 34 of http://www.realtor.org/retsorg.nsf/retsproto1.7d6.pdf#page=34
|
62
61
|
def login
|
63
62
|
res = http_get(login_url)
|
64
|
-
|
63
|
+
Parser::ErrorChecker.check(res)
|
65
64
|
|
66
65
|
self.capabilities = extract_capabilities(Nokogiri.parse(res.body))
|
67
66
|
raise UnknownResponse, "Cannot read rets server capabilities." unless @capabilities
|
@@ -148,7 +147,9 @@ module Rets
|
|
148
147
|
if opts[:count] == COUNT.only
|
149
148
|
Parser::Compact.get_count(res.body)
|
150
149
|
else
|
151
|
-
results = Parser::Compact.parse_document(
|
150
|
+
results = Parser::Compact.parse_document(
|
151
|
+
res.body.encode("UTF-8", res.body.encoding, :invalid => :replace, :undef => :replace)
|
152
|
+
)
|
152
153
|
if resolve
|
153
154
|
rets_class = find_rets_class(opts[:search_type], opts[:class])
|
154
155
|
decorate_results(results, rets_class)
|
@@ -328,7 +329,7 @@ module Rets
|
|
328
329
|
# ... :(
|
329
330
|
# Feel free to make this better. It has a test.
|
330
331
|
raw_key_values.split(/\n/).
|
331
|
-
map { |r| r.split(
|
332
|
+
map { |r| r.split(/\=/, 2) }.
|
332
333
|
each { |k,v| hash[k.strip.downcase] = v.strip }
|
333
334
|
|
334
335
|
hash
|
@@ -357,50 +358,5 @@ module Rets
|
|
357
358
|
super(IO::NULL)
|
358
359
|
end
|
359
360
|
end
|
360
|
-
|
361
|
-
class ErrorChecker
|
362
|
-
def self.check(response)
|
363
|
-
# some RETS servers returns HTTP code 412 when session cookie expired, yet the response body
|
364
|
-
# passes XML check. We need to special case for this situation.
|
365
|
-
# This method is also called from multipart.rb where there are headers and body but no status_code
|
366
|
-
if response.respond_to?(:status_code) && response.status_code == 412
|
367
|
-
raise HttpError, "HTTP status: #{response.status_code}, body: #{response.body}"
|
368
|
-
end
|
369
|
-
|
370
|
-
# some RETS servers return success code in XML body but failure code 4xx in http status
|
371
|
-
# If xml body is present we ignore http status
|
372
|
-
|
373
|
-
if !response.body.empty?
|
374
|
-
begin
|
375
|
-
xml = Nokogiri::XML.parse(response.body, nil, nil, Nokogiri::XML::ParseOptions::STRICT)
|
376
|
-
|
377
|
-
rets_element = xml.xpath("/RETS")
|
378
|
-
if rets_element.empty?
|
379
|
-
return
|
380
|
-
end
|
381
|
-
reply_text = (rets_element.attr("ReplyText") || rets_element.attr("replyText")).value
|
382
|
-
reply_code = (rets_element.attr("ReplyCode") || rets_element.attr("replyCode")).value.to_i
|
383
|
-
|
384
|
-
if reply_code == NoRecordsFound::ERROR_CODE
|
385
|
-
raise NoRecordsFound.new(reply_text)
|
386
|
-
elsif reply_code.nonzero?
|
387
|
-
raise InvalidRequest.new(reply_code, reply_text)
|
388
|
-
else
|
389
|
-
return
|
390
|
-
end
|
391
|
-
rescue Nokogiri::XML::SyntaxError
|
392
|
-
#Not xml
|
393
|
-
end
|
394
|
-
end
|
395
|
-
|
396
|
-
if response.respond_to?(:ok?) && ! response.ok?
|
397
|
-
if response.status_code == 401
|
398
|
-
raise AuthorizationFailure.new(response.status_code, response.body)
|
399
|
-
else
|
400
|
-
raise HttpError, "HTTP status: #{response.status_code}, body: #{response.body}"
|
401
|
-
end
|
402
|
-
end
|
403
|
-
end
|
404
|
-
end
|
405
361
|
end
|
406
362
|
end
|
data/lib/rets/http_client.rb
CHANGED
@@ -17,7 +17,7 @@ module Rets
|
|
17
17
|
log_http_traffic("GET", url, params, headers) do
|
18
18
|
res = http.get(url, params, headers)
|
19
19
|
end
|
20
|
-
|
20
|
+
Parser::ErrorChecker.check(res)
|
21
21
|
res
|
22
22
|
end
|
23
23
|
|
@@ -28,7 +28,7 @@ module Rets
|
|
28
28
|
log_http_traffic("POST", url, params, headers) do
|
29
29
|
res = http.post(url, params, headers)
|
30
30
|
end
|
31
|
-
|
31
|
+
Parser::ErrorChecker.check(res)
|
32
32
|
res
|
33
33
|
end
|
34
34
|
|
@@ -14,8 +14,7 @@ module Rets
|
|
14
14
|
def self.uses(*fields)
|
15
15
|
fields.each do |field|
|
16
16
|
define_method(field) do
|
17
|
-
|
18
|
-
instance_variable_set("@#{field}", extract(fragment, field.to_s.capitalize))
|
17
|
+
fields_hash[field] || fields_hash[field] = extract(fragment, field.to_s.capitalize)
|
19
18
|
end
|
20
19
|
end
|
21
20
|
end
|
@@ -30,6 +29,12 @@ module Rets
|
|
30
29
|
fragment.attr(attr)
|
31
30
|
end
|
32
31
|
|
32
|
+
private
|
33
|
+
|
34
|
+
def fields_hash
|
35
|
+
@fields ||= {}
|
36
|
+
end
|
37
|
+
|
33
38
|
end
|
34
39
|
|
35
40
|
class RowContainer < Container
|
@@ -9,8 +9,11 @@ module Rets
|
|
9
9
|
self.long_value = lookup_type_fragment["LongValue"]
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
# Print the tree to a file
|
13
|
+
#
|
14
|
+
# [out] The file to print to. Defaults to $stdout.
|
15
|
+
def print_tree(out = $stdout)
|
16
|
+
out.puts " #{long_value} -> #{value}"
|
14
17
|
end
|
15
18
|
end
|
16
19
|
end
|
@@ -69,10 +69,14 @@ module Rets
|
|
69
69
|
nil
|
70
70
|
end
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
# Print the tree to a file
|
73
|
+
#
|
74
|
+
# [out] The file to print to. Defaults to $stdout.
|
75
|
+
def print_tree(out = $stdout)
|
76
|
+
out.puts "Resource: #{id} (Key Field: #{key_field})"
|
77
|
+
rets_classes.each do |rets_class|
|
78
|
+
rets_class.print_tree(out)
|
79
|
+
end
|
76
80
|
end
|
77
81
|
|
78
82
|
def find_rets_class(rets_class_name)
|
@@ -33,11 +33,16 @@ module Rets
|
|
33
33
|
rets_class
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
# Print the tree to a file
|
37
|
+
#
|
38
|
+
# [out] The file to print to. Defaults to $stdout.
|
39
|
+
def print_tree(out = $stdout)
|
40
|
+
out.puts " Class: #{name}"
|
41
|
+
out.puts " Visible Name: #{visible_name}"
|
42
|
+
out.puts " Description : #{description}"
|
43
|
+
tables.each do |table|
|
44
|
+
table.print_tree(out)
|
45
|
+
end
|
41
46
|
end
|
42
47
|
|
43
48
|
def find_table(name)
|
data/lib/rets/metadata/root.rb
CHANGED
@@ -106,9 +106,12 @@ module Rets
|
|
106
106
|
@tree ||= build_tree
|
107
107
|
end
|
108
108
|
|
109
|
-
|
109
|
+
# Print the tree to a file
|
110
|
+
#
|
111
|
+
# [out] The file to print to. Defaults to $stdout.
|
112
|
+
def print_tree(out = $stdout)
|
110
113
|
tree.each do |name, value|
|
111
|
-
value.print_tree
|
114
|
+
value.print_tree(out)
|
112
115
|
end
|
113
116
|
end
|
114
117
|
|
data/lib/rets/metadata/table.rb
CHANGED
@@ -28,15 +28,18 @@ module Rets
|
|
28
28
|
self.long_name = table_fragment["LongName"]
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
puts "
|
36
|
-
puts "
|
37
|
-
puts "
|
38
|
-
puts "
|
39
|
-
puts "
|
31
|
+
# Print the tree to a file
|
32
|
+
#
|
33
|
+
# [out] The file to print to. Defaults to $stdout.
|
34
|
+
def print_tree(out = $stdout)
|
35
|
+
out.puts " Table: #{name}"
|
36
|
+
out.puts " Resource: #{resource.id}"
|
37
|
+
out.puts " ShortName: #{ table_fragment["ShortName"] }"
|
38
|
+
out.puts " LongName: #{ table_fragment["LongName"] }"
|
39
|
+
out.puts " StandardName: #{ table_fragment["StandardName"] }"
|
40
|
+
out.puts " Units: #{ table_fragment["Units"] }"
|
41
|
+
out.puts " Searchable: #{ table_fragment["Searchable"] }"
|
42
|
+
out.puts " Required: #{table_fragment['Required']}"
|
40
43
|
end
|
41
44
|
|
42
45
|
def resolve(value)
|
@@ -69,18 +72,23 @@ module Rets
|
|
69
72
|
resource.lookup_types[lookup_name]
|
70
73
|
end
|
71
74
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
puts "
|
77
|
-
puts "
|
78
|
-
puts "
|
79
|
-
puts "
|
80
|
-
puts "
|
81
|
-
puts "
|
82
|
-
|
83
|
-
|
75
|
+
# Print the tree to a file
|
76
|
+
#
|
77
|
+
# [out] The file to print to. Defaults to $stdout.
|
78
|
+
def print_tree(out = $stdout)
|
79
|
+
out.puts " LookupTable: #{name}"
|
80
|
+
out.puts " Resource: #{resource.id}"
|
81
|
+
out.puts " Required: #{table_fragment['Required']}"
|
82
|
+
out.puts " Searchable: #{ table_fragment["Searchable"] }"
|
83
|
+
out.puts " Units: #{ table_fragment["Units"] }"
|
84
|
+
out.puts " ShortName: #{ table_fragment["ShortName"] }"
|
85
|
+
out.puts " LongName: #{ table_fragment["LongName"] }"
|
86
|
+
out.puts " StandardName: #{ table_fragment["StandardName"] }"
|
87
|
+
out.puts " Types:"
|
88
|
+
|
89
|
+
lookup_types.each do |lookup_type|
|
90
|
+
lookup_type.print_tree(out)
|
91
|
+
end
|
84
92
|
end
|
85
93
|
|
86
94
|
def lookup_type(value)
|
data/lib/rets/parser/compact.rb
CHANGED
@@ -3,8 +3,6 @@ module Rets
|
|
3
3
|
class Compact
|
4
4
|
TAB = /\t/
|
5
5
|
|
6
|
-
INCLUDE_NULL_FIELDS = -1
|
7
|
-
|
8
6
|
InvalidDelimiter = Class.new(ArgumentError)
|
9
7
|
|
10
8
|
def self.parse_document(xml)
|
@@ -17,16 +15,12 @@ module Rets
|
|
17
15
|
raise InvalidDelimiter, "Empty or invalid delimiter found, unable to parse."
|
18
16
|
end
|
19
17
|
|
20
|
-
column_node
|
21
|
-
|
22
|
-
columns = ''
|
23
|
-
else
|
24
|
-
columns = column_node.text
|
25
|
-
end
|
26
|
-
rows = doc.xpath("//DATA")
|
18
|
+
column_node = doc.at("//COLUMNS")
|
19
|
+
column_names = column_node.nil? ? [] : column_node.text.split(delimiter)
|
27
20
|
|
21
|
+
rows = doc.xpath("//DATA")
|
28
22
|
rows.map do |data|
|
29
|
-
self.
|
23
|
+
self.parse_row(column_names, data.text, delimiter)
|
30
24
|
end
|
31
25
|
end
|
32
26
|
|
@@ -35,13 +29,12 @@ module Rets
|
|
35
29
|
# Delimiter must be a regexp because String#split behaves differently when
|
36
30
|
# given a string pattern. (It removes leading spaces).
|
37
31
|
#
|
38
|
-
def self.
|
32
|
+
def self.parse_row(column_names, data, delimiter = TAB)
|
39
33
|
raise ArgumentError, "Delimiter must be a regular expression" unless Regexp === delimiter
|
40
34
|
|
41
|
-
|
42
|
-
data_values = data.split(delimiter, INCLUDE_NULL_FIELDS)
|
35
|
+
data_values = data.split(delimiter).map { |x| CGI.unescapeHTML(x) }
|
43
36
|
|
44
|
-
zipped_key_values = column_names.zip(data_values).map { |k, v| [k, v.to_s] }
|
37
|
+
zipped_key_values = column_names.zip(data_values).map { |k, v| [k.freeze, v.to_s] }
|
45
38
|
|
46
39
|
hash = Hash[*zipped_key_values.flatten]
|
47
40
|
hash.reject { |key, value| key.empty? && value.to_s.empty? }
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module Rets
|
2
2
|
module Parser
|
3
|
-
|
4
3
|
# Inspired by Mail.
|
5
4
|
class Multipart
|
6
5
|
CRLF = "\r\n"
|
@@ -33,7 +32,7 @@ module Rets
|
|
33
32
|
|
34
33
|
def self.check_for_invalids_parts!(parts)
|
35
34
|
return unless parts.length == 1 && parts.first.headers['content-type'] == 'text/xml'
|
36
|
-
|
35
|
+
ErrorChecker.check(parts.first)
|
37
36
|
end
|
38
37
|
end
|
39
38
|
end
|
data/test/fixtures.rb
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
|
1
|
+
RETS_NO_RECORDS_ERROR = <<-XML
|
2
|
+
<?xml version="1.0"?>
|
3
|
+
<RETS ReplyCode="20201" ReplyText="Error message">
|
4
|
+
</RETS>
|
5
|
+
XML
|
6
|
+
|
7
|
+
RETS_NO_OBJECT_ERROR = <<-XML
|
8
|
+
<?xml version="1.0"?>
|
9
|
+
<RETS ReplyCode="20403" ReplyText="Error message">
|
10
|
+
</RETS>
|
11
|
+
XML
|
12
|
+
|
13
|
+
RETS_INVALID_REQUEST_ERROR = <<-XML
|
2
14
|
<?xml version="1.0"?>
|
3
15
|
<RETS ReplyCode="123" ReplyText="Error message">
|
4
16
|
</RETS>
|
@@ -205,8 +217,41 @@ SAMPLE_COMPACT_2 = <<XML
|
|
205
217
|
<DATA> 15n1172 ZipCode_f1172 Zip ZipCo_1172 Zip 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
|
206
218
|
<DATA> 15n1177 ADDRESS1_f1177 Address Line 1 ADDRE_1177 Address1 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
|
207
219
|
<DATA> 15n1182 MLSYN_f1182 MLS Y/N MLSYN_1182 MLSYN 1 Character 0 1 0 0 0 0 0 0 0 </DATA>
|
208
|
-
<DATA> 15n1184 OFFICENAME_f1184 Name Office Name OFFIC_1184 Office Name 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
|
220
|
+
<DATA> 15n1184 OFFICENAME_f1184 Office Name Office’s Name OFFIC_1184 Office Name 50 Character 0 1 0 0 0 0 0 0 0 </DATA>
|
209
221
|
<DATA> 15n1193 OfficeCode_f1193 OfficeID Office Code Offic_1193 Office Code 12 Character 0 1 0 0 0 0 0 0 1 </DATA>
|
210
222
|
</METADATA-TABLE>
|
211
223
|
</RETS>
|
212
224
|
XML
|
225
|
+
|
226
|
+
HTML_AUTH_FAILURE = <<EOF
|
227
|
+
<html><head><title>Apache Tomcat/6.0.26 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 401 - </h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u></u></p><p><b>description</b> <u>This request requires HTTP authentication ().</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/6.0.26</h3></body></html>
|
228
|
+
EOF
|
229
|
+
|
230
|
+
XHTML_AUTH_FAILURE = <<EOF
|
231
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
232
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
233
|
+
<head>
|
234
|
+
<title>401 - Unauthorized: Access is denied due to invalid credentials.</title>
|
235
|
+
</head>
|
236
|
+
<body>
|
237
|
+
<h1>401 - Unauthorized: Access is denied due to invalid credentials.</h1>
|
238
|
+
<p>You do not have permission to view this directory or page using the credentials that you supplied.</p>
|
239
|
+
</body>
|
240
|
+
</html>
|
241
|
+
EOF
|
242
|
+
|
243
|
+
SAMPLE_COMPACT_WITH_SPECIAL_CHARS = <<EOF
|
244
|
+
<RETS ReplyCode=\"0\" ReplyText=\"Operation Success.\">
|
245
|
+
<DELIMITER value=\"09\" />
|
246
|
+
<COLUMNS> PublicRemarksNew WindowCoverings YearBuilt Zoning ZoningCompatibleYN </COLUMNS>
|
247
|
+
<DATA> porte-coch&#xE8;re welcomes 1999 00 </DATA>
|
248
|
+
</RETS>
|
249
|
+
EOF
|
250
|
+
|
251
|
+
SAMPLE_COMPACT_WITH_SPECIAL_CHARS_2 = <<EOF
|
252
|
+
<RETS ReplyCode=\"0\" ReplyText=\"Operation Success.\">
|
253
|
+
<DELIMITER value=\"09\" />
|
254
|
+
<COLUMNS> PublicRemarksNew WindowCoverings YearBuilt Zoning ZoningCompatibleYN </COLUMNS>
|
255
|
+
<DATA> text with <tag> 1999 00 </DATA>
|
256
|
+
</RETS>
|
257
|
+
EOF
|
data/test/test_client.rb
CHANGED
@@ -99,6 +99,41 @@ class TestClient < MiniTest::Test
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
def test_response_text_encoding_from_ascii
|
103
|
+
@client.stubs(:capability_url).with("Search").returns("search_url")
|
104
|
+
|
105
|
+
response = mock
|
106
|
+
response.stubs(:body).returns(("An ascii string").encode("binary", "UTF-8"))
|
107
|
+
@client.stubs(:http_post).with("search_url", anything).returns(response)
|
108
|
+
|
109
|
+
Rets::Parser::Compact.expects(:parse_document).with("An ascii string")
|
110
|
+
|
111
|
+
@client.find_every({}, false)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_response_text_encoding_from_utf_8
|
115
|
+
@client.stubs(:capability_url).with("Search").returns("search_url")
|
116
|
+
|
117
|
+
response = mock
|
118
|
+
response.stubs(:body).returns("Some string with non-ascii characters \u0119")
|
119
|
+
@client.stubs(:http_post).with("search_url", anything).returns(response)
|
120
|
+
|
121
|
+
Rets::Parser::Compact.expects(:parse_document).with("Some string with non-ascii characters \u0119")
|
122
|
+
|
123
|
+
@client.find_every({}, false)
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_response_text_encoding_from_utf_16
|
127
|
+
@client.stubs(:capability_url).with("Search").returns("search_url")
|
128
|
+
|
129
|
+
response = mock
|
130
|
+
response.stubs(:body).returns("Some string with non-utf-8 characters \xC2")
|
131
|
+
@client.stubs(:http_post).with("search_url", anything).returns(response)
|
132
|
+
|
133
|
+
Rets::Parser::Compact.expects(:parse_document).with("Some string with non-utf-8 characters \uFFFD")
|
134
|
+
|
135
|
+
@client.find_every({}, false)
|
136
|
+
end
|
102
137
|
|
103
138
|
def test_find_retries_when_receiving_no_records_found
|
104
139
|
@client.stubs(:find_every).raises(Rets::NoRecordsFound.new('')).then.returns([1])
|
data/test/test_parser_compact.rb
CHANGED
@@ -10,8 +10,8 @@ class TestParserCompact < MiniTest::Test
|
|
10
10
|
def test_parse_document_uses_default_delimiter_when_none_provided
|
11
11
|
# we assert that the delimeter character getting to parse is a tab
|
12
12
|
# even though COMPACT defines no delimiter tag
|
13
|
-
Rets::Parser::Compact.expects(:
|
14
|
-
Rets::Parser::Compact.expects(:
|
13
|
+
Rets::Parser::Compact.expects(:parse_row).with(%w(A B), "1\t2", /\t/)
|
14
|
+
Rets::Parser::Compact.expects(:parse_row).with(%w(A B), "4\t5", /\t/)
|
15
15
|
Rets::Parser::Compact.parse_document(COMPACT)
|
16
16
|
end
|
17
17
|
|
@@ -30,29 +30,31 @@ class TestParserCompact < MiniTest::Test
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_parse_returns_key_value_pairs
|
33
|
-
result = Rets::Parser::Compact.
|
33
|
+
result = Rets::Parser::Compact.parse_row(%w(A B), "1\t2")
|
34
34
|
|
35
35
|
assert_equal({"A" => "1", "B" => "2"}, result)
|
36
36
|
end
|
37
37
|
|
38
38
|
# RMLS does this. :|
|
39
39
|
def test_remaining_columns_produce_empty_string_values
|
40
|
-
|
41
|
-
data
|
40
|
+
column_names = %w(A B C D)
|
41
|
+
data = "1 2"
|
42
42
|
|
43
|
-
assert_equal({"A" => "1", "B" => "2", "C" => "", "D" => ""},
|
43
|
+
assert_equal({"A" => "1", "B" => "2", "C" => "", "D" => ""},
|
44
|
+
Rets::Parser::Compact.parse_row(column_names, data, / /))
|
44
45
|
end
|
45
46
|
|
46
47
|
def test_leading_empty_columns_are_preserved_with_delimiter
|
47
|
-
|
48
|
-
data
|
48
|
+
column_names = %w(A B C D)
|
49
|
+
data = "\t\t3\t4" # first two columns are empty data.
|
49
50
|
|
50
|
-
assert_equal({"A" => "", "B" => "", "C" => "3", "D" => "4"},
|
51
|
+
assert_equal({"A" => "", "B" => "", "C" => "3", "D" => "4"},
|
52
|
+
Rets::Parser::Compact.parse_row(column_names, data, /\t/))
|
51
53
|
end
|
52
54
|
|
53
55
|
def test_parse_only_accepts_regexp
|
54
56
|
assert_raises ArgumentError do
|
55
|
-
Rets::Parser::Compact.
|
57
|
+
Rets::Parser::Compact.parse_row(["a"], "b", " ")
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
@@ -83,4 +85,15 @@ class TestParserCompact < MiniTest::Test
|
|
83
85
|
assert_equal "", rows.first["ModTimeStamp"]
|
84
86
|
end
|
85
87
|
|
88
|
+
def test_parse_html_encoded_chars
|
89
|
+
rows = Rets::Parser::Compact.parse_document(SAMPLE_COMPACT_WITH_SPECIAL_CHARS)
|
90
|
+
|
91
|
+
assert_equal "porte-coch\u{E8}re welcomes ", rows.last["PublicRemarksNew"]
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_parse_html_encoded_chars_2
|
95
|
+
rows = Rets::Parser::Compact.parse_document(SAMPLE_COMPACT_WITH_SPECIAL_CHARS_2)
|
96
|
+
|
97
|
+
assert_equal "text with <tag>", rows.last["PublicRemarksNew"]
|
98
|
+
end
|
86
99
|
end
|
@@ -30,7 +30,7 @@ class TestParserMultipart < MiniTest::Test
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_parse_with_error
|
33
|
-
raw = "\r\n--89467f8e0c6b48158c8f1883910212ec\r\nContent-Type: text/xml\r\nContent-ID: foo\r\nObject-ID: *\r\n\r\n<RETS ReplyCode=\"
|
33
|
+
raw = "\r\n--89467f8e0c6b48158c8f1883910212ec\r\nContent-Type: text/xml\r\nContent-ID: foo\r\nObject-ID: *\r\n\r\n<RETS ReplyCode=\"999\" ReplyText=\"An Unexplaned Error\" />\r\n\r\n--89467f8e0c6b48158c8f1883910212ec--\r\n"
|
34
34
|
boundary = "89467f8e0c6b48158c8f1883910212ec"
|
35
35
|
assert_raises Rets::InvalidRequest do
|
36
36
|
Rets::Parser::Multipart.parse(raw, boundary)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rets
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Estately, Inc. Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httpclient
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '3.
|
103
|
+
version: '3.13'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '3.
|
110
|
+
version: '3.13'
|
111
111
|
description: |-
|
112
112
|
[](http://travis-ci.org/estately/rets)
|
113
113
|
A pure-ruby library for fetching data from [RETS] servers.
|
@@ -153,7 +153,8 @@ files:
|
|
153
153
|
- test/test_parser_multipart.rb
|
154
154
|
- test/vcr_cassettes/unauthorized_response.yml
|
155
155
|
homepage: http://github.com/estately/rets
|
156
|
-
licenses:
|
156
|
+
licenses:
|
157
|
+
- MIT
|
157
158
|
metadata: {}
|
158
159
|
post_install_message:
|
159
160
|
rdoc_options:
|
@@ -172,15 +173,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
173
|
- !ruby/object:Gem::Version
|
173
174
|
version: '0'
|
174
175
|
requirements: []
|
175
|
-
rubyforge_project:
|
176
|
-
rubygems_version: 2.2.
|
176
|
+
rubyforge_project:
|
177
|
+
rubygems_version: 2.2.3
|
177
178
|
signing_key:
|
178
179
|
specification_version: 4
|
179
180
|
summary: "[](http://travis-ci.org/estately/rets)
|
180
181
|
A pure-ruby library for fetching data from [RETS] servers"
|
181
|
-
test_files:
|
182
|
-
- test/test_client.rb
|
183
|
-
- test/test_locking_http_client.rb
|
184
|
-
- test/test_metadata.rb
|
185
|
-
- test/test_parser_compact.rb
|
186
|
-
- test/test_parser_multipart.rb
|
182
|
+
test_files: []
|