josephholsten-rets4r 1.1.16 → 1.1.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,8 @@ module RETS4R
7
7
  class CompactDataParser
8
8
  def parse_results(doc)
9
9
 
10
- delimiter = doc.get_elements('/RETS/DELIMITER')[0].attributes['value'].to_i.chr
10
+ delimiter = doc.get_elements('/RETS/DELIMITER')[0] &&
11
+ doc.get_elements('/RETS/DELIMITER')[0].attributes['value'].to_i.chr
11
12
  columns = doc.get_elements('/RETS/COLUMNS')[0]
12
13
  rows = doc.get_elements('/RETS/DATA')
13
14
 
@@ -16,7 +17,7 @@ module RETS4R
16
17
 
17
18
  def parse_data(column_element, row_elements, delimiter = "\t")
18
19
 
19
- column_names = column_element.text.split(delimiter)
20
+ column_names = column_element.to_s.split(delimiter)
20
21
 
21
22
  result = []
22
23
 
@@ -2,15 +2,28 @@ require 'rubygems'
2
2
  require 'nokogiri'
3
3
  module RETS4R
4
4
  class Client
5
- class CompactNokogiriParser < Nokogiri::XML::SAX::Document
6
- def parse_results(io)
7
- doc = CompactDocument.new
8
- parser = Nokogiri::XML::SAX::Parser.new(doc)
9
- parser.parse(io)
10
- doc.results
5
+ class CompactNokogiriParser
6
+
7
+ def initialize(io)
8
+ @doc = CompactDocument.new
9
+ @parser = Nokogiri::XML::SAX::Parser.new(@doc)
10
+ @io = io
11
+ end
12
+
13
+ def to_a
14
+ @parser.parse(@io) if @doc.results.empty?
15
+ @doc.results
11
16
  end
17
+
18
+ def each(&block)
19
+ @doc.proc = block.to_proc
20
+ @parser.parse(@io)
21
+ nil
22
+ end
23
+
12
24
  class CompactDocument < Nokogiri::XML::SAX::Document
13
25
  attr_reader :results
26
+ attr_writer :proc
14
27
 
15
28
  def initialize
16
29
  @results = []
@@ -35,10 +48,7 @@ module RETS4R
35
48
  @columns = @string.split(@delimiter)
36
49
  when 'DATA'
37
50
  @data_element = false
38
- @results << @columns.zip(@string.split(@delimiter)).inject({}) do | h,(k,v) |
39
- h[k] = v unless k.empty?
40
- next h
41
- end
51
+ handle_row
42
52
  end
43
53
  end
44
54
 
@@ -49,7 +59,23 @@ module RETS4R
49
59
  @string << string
50
60
  end
51
61
  end
62
+
63
+ private
64
+ def handle_row
65
+ data = make_hash
66
+ if @proc
67
+ @proc.call(data)
68
+ else
69
+ @results << data
70
+ end
71
+ end
72
+ def make_hash
73
+ @columns.zip(@string.split(@delimiter)).inject({}) do | h,(k,v) |
74
+ h[k] = v unless k.empty?
75
+ next h
76
+ end
77
+ end
52
78
  end
53
79
  end
54
80
  end
55
- end
81
+ end
@@ -61,10 +61,6 @@ module RETS4R
61
61
  raise RETSException, 'No transaction body was returned!'
62
62
  end
63
63
 
64
- File.open('/tmp/meh', 'w') do |file|
65
- file.write(xml)
66
- end
67
-
68
64
  doc = REXML::Document.new(xml)
69
65
 
70
66
  root = doc.root
@@ -91,7 +87,7 @@ module RETS4R
91
87
 
92
88
  def get_parser_by_name(name)
93
89
  case name
94
- when 'COMPACT'
90
+ when 'COMPACT', 'COMPACT-DECODED'
95
91
  type = RETS4R::Client::CompactDataParser
96
92
  else
97
93
  raise "Invalid format #{name}"
@@ -23,6 +23,9 @@ module RETS4R
23
23
  def ascii_delimiter
24
24
  self.delimiter.chr
25
25
  end
26
+
27
+ # For compatibility with the original library.
28
+ alias :data :response
26
29
  end
27
30
  end
28
31
  end
@@ -0,0 +1,15 @@
1
+ # from ActiveSupport
2
+ class Array
3
+ # Extracts options from a set of arguments. Removes and returns the last
4
+ # element in the array if it's a hash, otherwise returns a blank hash.
5
+ #
6
+ # def options(*args)
7
+ # args.extract_options!
8
+ # end
9
+ #
10
+ # options(1, 2) # => {}
11
+ # options(1, 2, :a => :b) # => {:a=>:b}
12
+ def extract_options!
13
+ last.is_a?(::Hash) ? pop : {}
14
+ end
15
+ end
@@ -0,0 +1,58 @@
1
+ require 'rets4r/core_ext/array/extract_options'
2
+ # from ActiveSupport
3
+
4
+ # Extends the class object with class and instance accessors for class attributes,
5
+ # just like the native attr* accessors for instance attributes.
6
+ #
7
+ # class Person
8
+ # cattr_accessor :hair_colors
9
+ # end
10
+ #
11
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
12
+ class Class
13
+ def cattr_reader(*syms)
14
+ syms.flatten.each do |sym|
15
+ next if sym.is_a?(Hash)
16
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
17
+ unless defined? @@#{sym} # unless defined? @@hair_colors
18
+ @@#{sym} = nil # @@hair_colors = nil
19
+ end # end
20
+ #
21
+ def self.#{sym} # def self.hair_colors
22
+ @@#{sym} # @@hair_colors
23
+ end # end
24
+ #
25
+ def #{sym} # def hair_colors
26
+ @@#{sym} # @@hair_colors
27
+ end # end
28
+ EOS
29
+ end
30
+ end
31
+
32
+ def cattr_writer(*syms)
33
+ options = syms.extract_options!
34
+ syms.flatten.each do |sym|
35
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
36
+ unless defined? @@#{sym} # unless defined? @@hair_colors
37
+ @@#{sym} = nil # @@hair_colors = nil
38
+ end # end
39
+ #
40
+ def self.#{sym}=(obj) # def self.hair_colors=(obj)
41
+ @@#{sym} = obj # @@hair_colors = obj
42
+ end # end
43
+ #
44
+ #{" #
45
+ def #{sym}=(obj) # def hair_colors=(obj)
46
+ @@#{sym} = obj # @@hair_colors = obj
47
+ end # end
48
+ " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
49
+ EOS
50
+ self.send("#{sym}=", yield) if block_given?
51
+ end
52
+ end
53
+
54
+ def cattr_accessor(*syms, &blk)
55
+ cattr_reader(*syms)
56
+ cattr_writer(*syms, &blk)
57
+ end
58
+ end
@@ -0,0 +1,46 @@
1
+ # from ActiveSupport
2
+ class Hash
3
+ # Return a new hash with all keys converted to strings.
4
+ def stringify_keys
5
+ dup.stringify_keys!
6
+ end
7
+
8
+ # Destructively convert all keys to strings.
9
+ def stringify_keys!
10
+ keys.each do |key|
11
+ self[key.to_s] = delete(key)
12
+ end
13
+ self
14
+ end
15
+
16
+ # Return a new hash with all keys converted to symbols, as long as
17
+ # they respond to +to_sym+.
18
+ def symbolize_keys
19
+ dup.symbolize_keys!
20
+ end
21
+
22
+ # Destructively convert all keys to symbols, as long as they respond
23
+ # to +to_sym+.
24
+ def symbolize_keys!
25
+ keys.each do |key|
26
+ self[(key.to_sym rescue key) || key] = delete(key)
27
+ end
28
+ self
29
+ end
30
+
31
+ alias_method :to_options, :symbolize_keys
32
+ alias_method :to_options!, :symbolize_keys!
33
+
34
+ # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
35
+ # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
36
+ # as keys, this will fail.
37
+ #
38
+ # ==== Examples
39
+ # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
40
+ # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age"
41
+ # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
42
+ def assert_valid_keys(*valid_keys)
43
+ unknown_keys = keys - [valid_keys].flatten
44
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
45
+ end
46
+ end
@@ -0,0 +1,39 @@
1
+ # from ActiveSupport
2
+ class Hash
3
+ # Slice a hash to include only the given keys. This is useful for
4
+ # limiting an options hash to valid keys before passing to a method:
5
+ #
6
+ # def search(criteria = {})
7
+ # assert_valid_keys(:mass, :velocity, :time)
8
+ # end
9
+ #
10
+ # search(options.slice(:mass, :velocity, :time))
11
+ #
12
+ # If you have an array of keys you want to limit to, you should splat them:
13
+ #
14
+ # valid_keys = [:mass, :velocity, :time]
15
+ # search(options.slice(*valid_keys))
16
+ def slice(*keys)
17
+ keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
18
+ hash = self.class.new
19
+ keys.each { |k| hash[k] = self[k] if has_key?(k) }
20
+ hash
21
+ end
22
+
23
+ # Replaces the hash with only the given keys.
24
+ # Returns a hash contained the removed key/value pairs
25
+ # {:a => 1, :b => 2, :c => 3, :d => 4}.slice!(:a, :b) # => {:c => 3, :d =>4}
26
+ def slice!(*keys)
27
+ keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
28
+ omit = slice(*self.keys - keys)
29
+ hash = slice(*keys)
30
+ replace(hash)
31
+ omit
32
+ end
33
+
34
+ def extract!(*keys)
35
+ result = {}
36
+ keys.each {|key| result[key] = delete(key) }
37
+ result
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ module RETS4R
2
+ class ListingMapper
3
+ def initialize(params = {})
4
+ @select = params[:select] || ListingService.connection[:select]
5
+ end
6
+ def select
7
+ @select
8
+ end
9
+ def map(original)
10
+ listing = {}
11
+ @select.each_pair {|rets_key, record_key|
12
+ listing[record_key] = original[rets_key]
13
+ }
14
+ listing
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ require 'rets4r/core_ext/class/attribute_accessors'
2
+ require 'rets4r/core_ext/hash/keys'
3
+ require 'rets4r/core_ext/hash/slice'
4
+
5
+ module RETS4R
6
+ class ListingService
7
+ # RECORD_COUNT_ONLY=Librets::SearchRequest::RECORD_COUNT_ONLY
8
+ RECORD_COUNT_ONLY='fixme'
9
+ # Contains the listing service configurations - typically stored in
10
+ # config/listing_service.yml - as a Hash.
11
+ cattr_accessor :configurations, :instance_writer => false
12
+ cattr_accessor :env, :instance_writer => false
13
+
14
+ class << self
15
+
16
+ # Connection configuration for the specified environment, or the current
17
+ # environment if none is given.
18
+ def connection(spec = nil)
19
+ case spec
20
+ when nil
21
+ connection(RETS4R::ListingService.env)
22
+ when Symbol, String
23
+ if configuration = configurations[spec.to_s]
24
+ configuration.symbolize_keys
25
+ else
26
+ raise ArgumentError, "#{spec} listing service is not configured"
27
+ end
28
+ else
29
+ raise ArgumentError, "#{spec} listing service is not configured"
30
+ end
31
+ end
32
+
33
+ end # class << self
34
+ end
35
+ end
data/lib/rets4r/loader.rb CHANGED
@@ -1,12 +1,8 @@
1
1
  module RETS4R
2
2
  class Loader
3
- def self.load(file)
4
- parser = RETS4R::Client::CompactNokogiriParser.new
5
- listings = parser.parse_results(file)
6
-
7
- listings.each {|original|
8
- yield original
9
- }
3
+ def self.load(file, &block)
4
+ parser = RETS4R::Client::CompactNokogiriParser.new(file)
5
+ parser.each(&block)
10
6
  end
11
7
  end
12
- end
8
+ end
@@ -6,14 +6,30 @@ require 'rets4r'
6
6
  class CompactNokogiriTest < Test::Unit::TestCase
7
7
  def test_should_do_stuff
8
8
  file = File.expand_path(File.join('test', 'data', '1.5', 'search_compact.xml'))
9
- listings = RETS4R::Client::CompactNokogiriParser.new.parse_results(open(file))
9
+ listings = RETS4R::Client::CompactNokogiriParser.new(open(file)).to_a
10
10
  assert_equal({"Third"=>"Datum3", "Second"=>"Datum2", "First"=>"Datum1"}, listings[0])
11
11
  assert_equal({"Third"=>"Datum6", "Second"=>"Datum5", "First"=>"Datum4"}, listings[1])
12
12
  end
13
13
  def test_should_handle_big_data
14
14
  file = File.expand_path(File.join('test', 'data', '1.5', 'bad_compact.xml'))
15
- listings = RETS4R::Client::CompactNokogiriParser.new.parse_results(open(file))
15
+ listings = RETS4R::Client::CompactNokogiriParser.new(open(file)).to_a
16
16
  assert_equal 1, listings.length
17
17
  assert_equal 79, listings.first.keys.length
18
18
  end
19
+ def test_each_should_yield_between_results
20
+ file = File.expand_path(
21
+ File.join('test', 'data', '1.5', 'search_compact_big.xml'))
22
+ stat = File::Stat.new(file)
23
+ unless stat.size > stat.blksize
24
+ flunk "This test probably won't work on this machine.
25
+ It needs a test input file larger than the native block size."
26
+ end
27
+ stream = open(file)
28
+ positions = []
29
+ listings = RETS4R::Client::CompactNokogiriParser.new(stream).each do |row|
30
+ positions << stream.pos
31
+ end
32
+ assert positions.first < positions.last,
33
+ "data was yielded durring the reading of the stream"
34
+ end
19
35
  end
@@ -0,0 +1,3 @@
1
+ <RETS ReplyCode="0" ReplyText="SUCCESS">
2
+ <COUNT Records="1024"/>
3
+ </RETS>
@@ -0,0 +1,136 @@
1
+ <RETS ReplyCode="0" ReplyText="SUCCESS">
2
+ <COUNT Records="4"/>
3
+ <DELIMITER value="09" />
4
+ <COLUMNS> First Second Third </COLUMNS>
5
+ <DATA> Datum1 Datum2 Datum3</DATA>
6
+ <DATA> Datum4 Datum5 Datum6</DATA>
7
+ <DATA> Datum4 Datum5 Datum6</DATA>
8
+ <DATA> Datum1 Datum2 Datum3</DATA>
9
+ <DATA> Datum1 Datum2 Datum3</DATA>
10
+ <DATA> Datum4 Datum5 Datum6</DATA>
11
+ <DATA> Datum4 Datum5 Datum6</DATA>
12
+ <DATA> Datum1 Datum2 Datum3</DATA>
13
+ <DATA> Datum4 Datum5 Datum6</DATA>
14
+ <DATA> Datum4 Datum5 Datum6</DATA>
15
+ <DATA> Datum1 Datum2 Datum3</DATA>
16
+ <DATA> Datum1 Datum2 Datum3</DATA>
17
+ <DATA> Datum4 Datum5 Datum6</DATA>
18
+ <DATA> Datum4 Datum5 Datum6</DATA>
19
+ <DATA> Datum1 Datum2 Datum3</DATA>
20
+ <DATA> Datum4 Datum5 Datum6</DATA>
21
+ <DATA> Datum4 Datum5 Datum6</DATA>
22
+ <DATA> Datum1 Datum2 Datum3</DATA>
23
+ <DATA> Datum1 Datum2 Datum3</DATA>
24
+ <DATA> Datum4 Datum5 Datum6</DATA>
25
+ <DATA> Datum4 Datum5 Datum6</DATA>
26
+ <DATA> Datum1 Datum2 Datum3</DATA>
27
+ <DATA> Datum4 Datum5 Datum6</DATA>
28
+ <DATA> Datum4 Datum5 Datum6</DATA>
29
+ <DATA> Datum1 Datum2 Datum3</DATA>
30
+ <DATA> Datum1 Datum2 Datum3</DATA>
31
+ <DATA> Datum4 Datum5 Datum6</DATA>
32
+ <DATA> Datum4 Datum5 Datum6</DATA>
33
+ <DATA> Datum1 Datum2 Datum3</DATA>
34
+ <DATA> Datum4 Datum5 Datum6</DATA>
35
+ <DATA> Datum4 Datum5 Datum6</DATA>
36
+ <DATA> Datum1 Datum2 Datum3</DATA>
37
+ <DATA> Datum1 Datum2 Datum3</DATA>
38
+ <DATA> Datum4 Datum5 Datum6</DATA>
39
+ <DATA> Datum4 Datum5 Datum6</DATA>
40
+ <DATA> Datum1 Datum2 Datum3</DATA>
41
+ <DATA> Datum4 Datum5 Datum6</DATA>
42
+ <DATA> Datum4 Datum5 Datum6</DATA>
43
+ <DATA> Datum1 Datum2 Datum3</DATA>
44
+ <DATA> Datum1 Datum2 Datum3</DATA>
45
+ <DATA> Datum4 Datum5 Datum6</DATA>
46
+ <DATA> Datum4 Datum5 Datum6</DATA>
47
+ <DATA> Datum1 Datum2 Datum3</DATA>
48
+ <DATA> Datum4 Datum5 Datum6</DATA>
49
+ <DATA> Datum4 Datum5 Datum6</DATA>
50
+ <DATA> Datum1 Datum2 Datum3</DATA>
51
+ <DATA> Datum1 Datum2 Datum3</DATA>
52
+ <DATA> Datum4 Datum5 Datum6</DATA>
53
+ <DATA> Datum4 Datum5 Datum6</DATA>
54
+ <DATA> Datum1 Datum2 Datum3</DATA>
55
+ <DATA> Datum4 Datum5 Datum6</DATA>
56
+ <DATA> Datum4 Datum5 Datum6</DATA>
57
+ <DATA> Datum1 Datum2 Datum3</DATA>
58
+ <DATA> Datum1 Datum2 Datum3</DATA>
59
+ <DATA> Datum4 Datum5 Datum6</DATA>
60
+ <DATA> Datum4 Datum5 Datum6</DATA>
61
+ <DATA> Datum1 Datum2 Datum3</DATA>
62
+ <DATA> Datum4 Datum5 Datum6</DATA>
63
+ <DATA> Datum4 Datum5 Datum6</DATA>
64
+ <DATA> Datum1 Datum2 Datum3</DATA>
65
+ <DATA> Datum1 Datum2 Datum3</DATA>
66
+ <DATA> Datum4 Datum5 Datum6</DATA>
67
+ <DATA> Datum4 Datum5 Datum6</DATA>
68
+ <DATA> Datum1 Datum2 Datum3</DATA>
69
+ <DATA> Datum4 Datum5 Datum6</DATA>
70
+ <DATA> Datum4 Datum5 Datum6</DATA>
71
+ <DATA> Datum1 Datum2 Datum3</DATA>
72
+ <DATA> Datum1 Datum2 Datum3</DATA>
73
+ <DATA> Datum4 Datum5 Datum6</DATA>
74
+ <DATA> Datum4 Datum5 Datum6</DATA>
75
+ <DATA> Datum1 Datum2 Datum3</DATA>
76
+ <DATA> Datum4 Datum5 Datum6</DATA>
77
+ <DATA> Datum4 Datum5 Datum6</DATA>
78
+ <DATA> Datum1 Datum2 Datum3</DATA>
79
+ <DATA> Datum1 Datum2 Datum3</DATA>
80
+ <DATA> Datum4 Datum5 Datum6</DATA>
81
+ <DATA> Datum4 Datum5 Datum6</DATA>
82
+ <DATA> Datum1 Datum2 Datum3</DATA>
83
+ <DATA> Datum4 Datum5 Datum6</DATA>
84
+ <DATA> Datum4 Datum5 Datum6</DATA>
85
+ <DATA> Datum1 Datum2 Datum3</DATA>
86
+ <DATA> Datum1 Datum2 Datum3</DATA>
87
+ <DATA> Datum4 Datum5 Datum6</DATA>
88
+ <DATA> Datum4 Datum5 Datum6</DATA>
89
+ <DATA> Datum1 Datum2 Datum3</DATA>
90
+ <DATA> Datum4 Datum5 Datum6</DATA>
91
+ <DATA> Datum4 Datum5 Datum6</DATA>
92
+ <DATA> Datum1 Datum2 Datum3</DATA>
93
+ <DATA> Datum1 Datum2 Datum3</DATA>
94
+ <DATA> Datum4 Datum5 Datum6</DATA>
95
+ <DATA> Datum4 Datum5 Datum6</DATA>
96
+ <DATA> Datum1 Datum2 Datum3</DATA>
97
+ <DATA> Datum4 Datum5 Datum6</DATA>
98
+ <DATA> Datum4 Datum5 Datum6</DATA>
99
+ <DATA> Datum1 Datum2 Datum3</DATA>
100
+ <DATA> Datum1 Datum2 Datum3</DATA>
101
+ <DATA> Datum4 Datum5 Datum6</DATA>
102
+ <DATA> Datum4 Datum5 Datum6</DATA>
103
+ <DATA> Datum1 Datum2 Datum3</DATA>
104
+ <DATA> Datum4 Datum5 Datum6</DATA>
105
+ <DATA> Datum4 Datum5 Datum6</DATA>
106
+ <DATA> Datum1 Datum2 Datum3</DATA>
107
+ <DATA> Datum1 Datum2 Datum3</DATA>
108
+ <DATA> Datum4 Datum5 Datum6</DATA>
109
+ <DATA> Datum4 Datum5 Datum6</DATA>
110
+ <DATA> Datum1 Datum2 Datum3</DATA>
111
+ <DATA> Datum4 Datum5 Datum6</DATA>
112
+ <DATA> Datum4 Datum5 Datum6</DATA>
113
+ <DATA> Datum1 Datum2 Datum3</DATA>
114
+ <DATA> Datum1 Datum2 Datum3</DATA>
115
+ <DATA> Datum4 Datum5 Datum6</DATA>
116
+ <DATA> Datum4 Datum5 Datum6</DATA>
117
+ <DATA> Datum1 Datum2 Datum3</DATA>
118
+ <DATA> Datum4 Datum5 Datum6</DATA>
119
+ <DATA> Datum4 Datum5 Datum6</DATA>
120
+ <DATA> Datum1 Datum2 Datum3</DATA>
121
+ <DATA> Datum1 Datum2 Datum3</DATA>
122
+ <DATA> Datum4 Datum5 Datum6</DATA>
123
+ <DATA> Datum4 Datum5 Datum6</DATA>
124
+ <DATA> Datum1 Datum2 Datum3</DATA>
125
+ <DATA> Datum4 Datum5 Datum6</DATA>
126
+ <DATA> Datum4 Datum5 Datum6</DATA>
127
+ <DATA> Datum1 Datum2 Datum3</DATA>
128
+ <DATA> Datum1 Datum2 Datum3</DATA>
129
+ <DATA> Datum1 Datum2 Datum3</DATA>
130
+ <DATA> Datum4 Datum5 Datum6</DATA>
131
+ <DATA> Datum4 Datum5 Datum6</DATA>
132
+ <DATA> Datum1 Datum2 Datum3</DATA>
133
+ <DATA> Datum4 Datum5 Datum6</DATA>
134
+ <DATA> Datum4 Datum5 Datum6</DATA>
135
+ <MAXROWS/>
136
+ </RETS>