Class: Currency::Exchange::Rate::Source::Xe

Inherits:
Provider show all
Defined in:
lib/currency/exchange/rate/source/xe.rb

Overview

Connects to xe.com and parses “XE.com Quick Cross Rates” from home page HTML.

Constant Summary collapse

PIVOT_CURRENCY =

Defines the pivot currency for xe.com/.

:USD

Instance Attribute Summary

Attributes inherited from Provider

#date, #uri, #uri_path

Attributes inherited from Base

#pivot_currency, #time_quantitizer, #verbose

Instance Method Summary collapse

Methods inherited from Provider

#available?, #date_DD, #date_MM, #date_YYYY, #get_page_content, #get_rate, #get_uri, #rates

Methods inherited from Base

#__subclass_responsibility, #clear_rate, #convert, #currencies, #get_rate, #get_rate_base, #get_rates, #new_rate, #normalize_time, #rate, #rates, #to_s

Constructor Details

#initialize(*opt) ⇒ Xe

Returns a new instance of Xe.



19
20
21
22
23
24
# File 'lib/currency/exchange/rate/source/xe.rb', line 19

def initialize(*opt)
  self.uri = 'http://xe.com/'
  self.pivot_currency = PIVOT_CURRENCY
  @raw_rates = nil
  super(*opt)
end

Instance Method Details

#clear_ratesObject



33
34
35
36
# File 'lib/currency/exchange/rate/source/xe.rb', line 33

def clear_rates
  @raw_rates = nil
  super
end

#eat_lines_until(rx) ⇒ Object

Raises:



124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/currency/exchange/rate/source/xe.rb', line 124

def eat_lines_until(rx)
  until @lines.empty?
    @line = @lines.shift
    if md = rx.match(@line)
      $stderr.puts "\nMATCHED #{@line.inspect} WITH #{rx.inspect} AT LINES:\n#{@lines[0..4].inspect}" if @verbose
      return md
    end
    yield @line if block_given?
  end

  raise ParserError, [ 'eat_lines_until failed', :rx, rx ]

  false
end

#load_rates(time = nil) ⇒ Object

Return a list of known base rates.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/currency/exchange/rate/source/xe.rb', line 141

def load_rates(time = nil)
  if time
    $stderr.puts "#{self}: WARNING CANNOT SUPPLY HISTORICAL RATES" unless @time_warning
    @time_warning = true
  end

  rates = raw_rates # Load rates
  rates_pivot = rates[PIVOT_CURRENCY]
  raise ::Currency::Exception::UnknownRate,
  [ 
   "Cannot get base rate #{PIVOT_CURRENCY.inspect}",
   :pivot_currency, PIVOT_CURRENCY,
  ] unless rates_pivot
  
  result = rates_pivot.keys.collect do | c2 | 
    new_rate(PIVOT_CURRENCY, c2, rates_pivot[c2], @rate_timestamp)
  end
  
  result
end

#nameObject

Returns ‘xe.com’.



28
29
30
# File 'lib/currency/exchange/rate/source/xe.rb', line 28

def name
  'xe.com'
end

#parse_page_rates(data = nil) ⇒ Object

Parses xe.com homepage HTML for quick rates of 10 currencies.

Raises:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/currency/exchange/rate/source/xe.rb', line 52

def parse_page_rates(data = nil)
  data = get_page_content unless data

  @lines = data = data.split(/\n/);

  # xe.com no longer gives date/time.
  # Remove usecs.
  time = Time.at(Time.new.to_i).getutc
  @rate_timestamp = time

  eat_lines_until /More currencies\.\.\.<\/a>/i
  eat_lines_until /^\s*<tr>/i
  eat_lines_until /^\s*<tr>/i
  
  # Read first table row to get position for each currency
  currency = [ ]
  eat_lines_until /^\s*<\/tr>/i do 
    if md = /<td[^>]+?>.*?\/> ([A-Z][A-Z][A-Z])<\/td>/.match(@line)
      cur = md[1].intern
      cur_i = currency.size
      currency.push(cur)
      $stderr.puts "Found currency header: #{cur.inspect} at #{cur_i}" if @verbose
    end
  end
  raise ParserError, "Currencies header not found" if currency.empty?
  

  # Skip until "1 USD ="
  eat_lines_until /^\s*<td[^>]+?> 1&nbsp;+USD&nbsp;=/
   
  # Read first row of 1 USD = ...
  rate = { }
  cur_i = -1 
  eat_lines_until /^\s*<\/tr>/i do 
    # Grok:
    #
    #   <td align="center" class="cur2 currencyA">114.676</td>\n
    #
    # AND
    #
    #   <td align="center" class="cur2 currencyA"><div id="positionImage">0.9502\n
    #
    if md = /<td[^>]+?>\s*(<div[^>]+?>\s*)?(\d+\.\d+)\s*(<\/td>)?/i.match(@line)
      usd_to_cur = md[2].to_f
      cur_i = cur_i + 1
      cur = currency[cur_i]
      raise ParserError, "Currency not found at column #{cur_i}" unless cur
      next if cur.to_s == PIVOT_CURRENCY.to_s
      (rate[PIVOT_CURRENCY] ||= {})[cur] = usd_to_cur
      (rate[cur] ||= { })[PIVOT_CURRENCY] ||= 1.0 / usd_to_cur
      $stderr.puts "#{cur.inspect} => #{usd_to_cur}" if @verbose
    end
  end

  raise ::Currency::Exception::UnavailableRates, "No rates found in #{get_uri.inspect}" if rate.keys.empty?

  raise ParserError, 
  [
   "Not all currencies found", 
   :expected_currences, currency,
   :found_currencies, rate.keys,
   :missing_currencies, currency - rate.keys,
  ] if rate.keys.size != currency.size

  @lines = @line = nil

  raise ParserError, "Rate date not found" unless @rate_timestamp

  rate
end

#raw_ratesObject

Returns a cached Hash of rates:

xe.raw_rates[:USD][:CAD] => 1.0134


43
44
45
46
# File 'lib/currency/exchange/rate/source/xe.rb', line 43

def raw_rates
  # Force load of rates
  @raw_rates ||= parse_page_rates
end