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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4f011e9a58934f4b5152a5f01428b58275602342
4
- data.tar.gz: 188cdccdacaeb7b8c64df570208f073bfb00d9fb
3
+ metadata.gz: 2d46af696793fde645c0c712cdb2e8a708bb93a1
4
+ data.tar.gz: dd858c7811d76a390e178afd853f73a6f1d9807b
5
5
  SHA512:
6
- metadata.gz: 9ce589fca7e82987db2bdf0eb3432e9482bf1344b9d79a69d56b00f29219dd1495680c903bab456e8759e319ec6952245bb3bb88d69cbd1dcf4124a3abf8957e
7
- data.tar.gz: 80dbdc930daa3eedb1f9566b7decbb4b69964396f16300522d481c226b442ff71d5bbbcf4d0c8049ee6a3e0df88fd0f490e6b69d11041265ca9ec94d069de964
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.7.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
- Client::ErrorChecker.check(res)
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(res.body.encode("UTF-8", "binary", :invalid => :replace, :undef => :replace))
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(/=/, 2) }.
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
@@ -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
- Client::ErrorChecker.check(res)
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
- Client::ErrorChecker.check(res)
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
- instance_variable_get("@#{field}") ||
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
- def print_tree
13
- puts " #{long_value} -> #{value}"
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
- def print_tree
73
- puts "Resource: #{id} (Key Field: #{key_field})"
74
-
75
- rets_classes.each(&:print_tree)
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
- def print_tree
37
- puts " Class: #{name}"
38
- puts " Visible Name: #{visible_name}"
39
- puts " Description : #{description}"
40
- tables.each(&:print_tree)
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)
@@ -106,9 +106,12 @@ module Rets
106
106
  @tree ||= build_tree
107
107
  end
108
108
 
109
- def print_tree
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
 
@@ -28,15 +28,18 @@ module Rets
28
28
  self.long_name = table_fragment["LongName"]
29
29
  end
30
30
 
31
- def print_tree
32
- puts " Table: #{name}"
33
- puts " Resource: #{resource.id}"
34
- puts " ShortName: #{ table_fragment["ShortName"] }"
35
- puts " LongName: #{ table_fragment["LongName"] }"
36
- puts " StandardName: #{ table_fragment["StandardName"] }"
37
- puts " Units: #{ table_fragment["Units"] }"
38
- puts " Searchable: #{ table_fragment["Searchable"] }"
39
- puts " Required: #{table_fragment['Required']}"
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
- def print_tree
73
- puts " LookupTable: #{name}"
74
- puts " Resource: #{resource.id}"
75
- puts " Required: #{table_fragment['Required']}"
76
- puts " Searchable: #{ table_fragment["Searchable"] }"
77
- puts " Units: #{ table_fragment["Units"] }"
78
- puts " ShortName: #{ table_fragment["ShortName"] }"
79
- puts " LongName: #{ table_fragment["LongName"] }"
80
- puts " StandardName: #{ table_fragment["StandardName"] }"
81
- puts " Types:"
82
-
83
- lookup_types.each(&:print_tree)
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)
@@ -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 = doc.at("//COLUMNS")
21
- if column_node.nil?
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.parse(columns, data.text, delimiter)
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.parse(columns, data, delimiter = TAB)
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
- column_names = columns.split(delimiter)
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
- Client::ErrorChecker.check(parts.first)
35
+ ErrorChecker.check(parts.first)
37
36
  end
38
37
  end
39
38
  end
data/test/fixtures.rb CHANGED
@@ -1,4 +1,16 @@
1
- RETS_ERROR = <<-XML
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&#x2019;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&amp;#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 &lt;tag&gt; 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])
@@ -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(:parse).with("A\tB", "1\t2", /\t/)
14
- Rets::Parser::Compact.expects(:parse).with("A\tB", "4\t5", /\t/)
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.parse("A\tB", "1\t2")
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
- columns = "A B C D"
41
- data = "1 2"
40
+ column_names = %w(A B C D)
41
+ data = "1 2"
42
42
 
43
- assert_equal({"A" => "1", "B" => "2", "C" => "", "D" => ""}, Rets::Parser::Compact.parse(columns, data, / /))
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
- columns = "A\tB\tC\tD"
48
- data = "\t\t3\t4" # first two columns are empty 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"}, Rets::Parser::Compact.parse(columns, data, /\t/))
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.parse("a", "b", " ")
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=\"20403\" ReplyText=\"No Object Found\" />\r\n\r\n--89467f8e0c6b48158c8f1883910212ec--\r\n"
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.7.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-02-16 00:00:00.000000000 Z
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.6'
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.6'
110
+ version: '3.13'
111
111
  description: |-
112
112
  [![Build Status](https://secure.travis-ci.org/estately/rets.png?branch=master)](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: rets
176
- rubygems_version: 2.2.0
176
+ rubyforge_project:
177
+ rubygems_version: 2.2.3
177
178
  signing_key:
178
179
  specification_version: 4
179
180
  summary: "[![Build Status](https://secure.travis-ci.org/estately/rets.png?branch=master)](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: []