File Coverage

blib/lib/Finance/Currency/Convert/Yahoo.pm
Criterion Covered Total %
statement 18 95 18.9
branch 0 82 0.0
condition 0 58 0.0
subroutine 6 11 54.5
pod 2 2 100.0
total 26 248 10.4


line stmt bran cond sub pod time code
1             package Finance::Currency::Convert::Yahoo;
2            
3 1     1   11938 use vars qw/$VERSION $DATE $CHAT %currencies/;
  1         2  
  1         98  
4            
5             $VERSION = 0.2;
6             $DATE = "14 December 2005";
7            
8             =head1 NAME
9            
10             Finance::Currency::Convert::Yahoo - convert currencies using Yahoo
11            
12             =head1 SYNOPSIS
13            
14             use Finance::Currency::Convert::Yahoo;
15             $Finance::Currency::Convert::Yahoo::CHAT = 1;
16             $_ = Finance::Currency::Convert::Yahoo::convert(1,'USD','GBP');
17             print defined($_)? "Is $_\n" : "Error.";
18             exit;
19            
20             # See the currencies in a dirty way:
21             use Finance::Currency::Convert::Yahoo;
22             use Data::Dumper;
23             warn %Finance::Currency::Convert::Yahoo::currencies;
24             exit;
25            
26             =head1 DESCRIPTION
27            
28             Using Finance.Yahoo.com, converts a sum between two currencies.
29            
30             =cut
31            
32 1     1   5 use strict;
  1         2  
  1         40  
33 1     1   6 use Carp;
  1         6  
  1         69  
34 1     1   5 use warnings;
  1         1  
  1         32  
35 1     1   1181 use LWP::UserAgent;
  1         68809  
  1         34  
36 1     1   11 use HTTP::Request;
  1         2  
  1         2923  
37            
38             #
39             # Glabal variables
40             #
41            
42             $CHAT = 0; # Set for real-time notes to STDERR
43            
44             # Should have been %CURRENCIES but too late now.
45             our %CURRENCIES = %currencies = (
46             'AFA'=>'Afghanistan Afghani', 'ALL'=>'Albanian Lek', 'DZD'=>'Algerian Dinar',
47             'ADF'=>'Andorran Franc', 'ADP'=>'Andorran Peseta', 'ARS'=>'Argentine Peso',
48             'AWG'=>'Aruba Florin', 'AUD'=>'Australian Dollar', 'ATS'=>'Austrian Schilling',
49             'BSD'=>'Bahamian Dollar', 'BHD'=>'Bahraini Dinar', 'BDT'=>'Bangladesh Taka',
50             'BBD'=>'Barbados Dollar', 'BEF'=>'Belgian Franc', 'BZD'=>'Belize Dollar',
51             'BMD'=>'Bermuda Dollar', 'BTN'=>'Bhutan Ngultrum', 'BOB'=>'Bolivian Boliviano',
52             'BWP'=>'Botswana Pula', 'BRL'=>'Brazilian Real', 'GBP'=>'British Pound',
53             'BND'=>'Brunei Dollar', 'BIF'=>'Burundi Franc', 'XOF'=>'CFA Franc (BCEAO)',
54             'XAF'=>'CFA Franc (BEAC)', 'KHR'=>'Cambodia Riel', 'CAD'=>'Canadian Dollar',
55             'CVE'=>'Cape Verde Escudo', 'KYD'=>'Cayman Islands Dollar', 'CLP'=>'Chilean Peso',
56             'CNY'=>'Chinese Yuan', 'COP'=>'Colombian Peso', 'KMF'=>'Comoros Franc',
57             'CRC'=>'Costa Rica Colon', 'HRK'=>'Croatian Kuna', 'CUP'=>'Cuban Peso',
58             'CYP'=>'Cyprus Pound', 'CZK'=>'Czech Koruna', 'DKK'=>'Danish Krone',
59             'DJF'=>'Dijibouti Franc', 'DOP'=>'Dominican Peso', 'NLG'=>'Dutch Guilder',
60             'XCD'=>'East Caribbean Dollar', 'ECS'=>'Ecuadorian Sucre', 'EGP'=>'Egyptian Pound',
61             'SVC'=>'El Salvador Colon', 'EEK'=>'Estonian Kroon', 'ETB'=>'Ethiopian Birr',
62             'EUR'=>'Euro', 'FKP'=>'Falkland Islands Pound', 'FJD'=>'Fiji Dollar',
63             'FIM'=>'Finnish Mark', 'FRF'=>'French Franc', 'GMD'=>'Gambian Dalasi',
64             'DEM'=>'German Mark', 'GHC'=>'Ghanian Cedi', 'GIP'=>'Gibraltar Pound',
65             'XAU'=>'Gold Ounces', 'GRD'=>'Greek Drachma', 'GTQ'=>'Guatemala Quetzal',
66             'GNF'=>'Guinea Franc', 'GYD'=>'Guyana Dollar', 'HTG'=>'Haiti Gourde',
67             'HNL'=>'Honduras Lempira', 'HKD'=>'Hong Kong Dollar', 'HUF'=>'Hungarian Forint',
68             'ISK'=>'Iceland Krona', 'INR'=>'Indian Rupee', 'IDR'=>'Indonesian Rupiah',
69             'IQD'=>'Iraqi Dinar', 'IEP'=>'Irish Punt', 'ILS'=>'Israeli Shekel',
70             'ITL'=>'Italian Lira', 'JMD'=>'Jamaican Dollar', 'JPY'=>'Japanese Yen',
71             'JOD'=>'Jordanian Dinar', 'KZT'=>'Kazakhstan Tenge', 'KES'=>'Kenyan Shilling',
72             'KRW'=>'Korean Won', 'KWD'=>'Kuwaiti Dinar', 'LAK'=>'Lao Kip', 'LVL'=>'Latvian Lat',
73             'LBP'=>'Lebanese Pound', 'LSL'=>'Lesotho Loti', 'LRD'=>'Liberian Dollar',
74             'LYD'=>'Libyan Dinar', 'LTL'=>'Lithuanian Lita', 'LUF'=>'Luxembourg Franc',
75             'MOP'=>'Macau Pataca', 'MKD'=>'Macedonian Denar', 'MGF'=>'Malagasy Franc',
76             'MWK'=>'Malawi Kwacha', 'MYR'=>'Malaysian Ringgit', 'MVR'=>'Maldives Rufiyaa',
77             'MTL'=>'Maltese Lira', 'MRO'=>'Mauritania Ougulya', 'MUR'=>'Mauritius Rupee',
78             'MXN'=>'Mexican Peso', 'MDL'=>'Moldovan Leu', 'MNT'=>'Mongolian Tugrik',
79             'MAD'=>'Moroccan Dirham', 'MZM'=>'Mozambique Metical', 'MMK'=>'Myanmar Kyat',
80             'NAD'=>'Namibian Dollar', 'NPR'=>'Nepalese Rupee', 'ANG'=>'Neth Antilles Guilder',
81             'NZD'=>'New Zealand Dollar', 'NIO'=>'Nicaragua Cordoba', 'NGN'=>'Nigerian Naira',
82             'KPW'=>'North Korean Won', 'NOK'=>'Norwegian Krone', 'OMR'=>'Omani Rial',
83             'XPF'=>'Pacific Franc', 'PKR'=>'Pakistani Rupee', 'XPD'=>'Palladium Ounces',
84             'PAB'=>'Panama Balboa', 'PGK'=>'Papua New Guinea Kina', 'PYG'=>'Paraguayan Guarani',
85             'PEN'=>'Peruvian Nuevo Sol', 'PHP'=>'Philippine Peso', 'XPT'=>'Platinum Ounces',
86             'PLN'=>'Polish Zloty', 'PTE'=>'Portuguese Escudo', 'QAR'=>'Qatar Rial',
87             'ROL'=>'Romanian Leu', 'RUB'=>'Russian Rouble', 'WST'=>'Samoa Tala',
88             'STD'=>'Sao Tome Dobra', 'SAR'=>'Saudi Arabian Riyal', 'SCR'=>'Seychelles Rupee',
89             'SLL'=>'Sierra Leone Leone', 'XAG'=>'Silver Ounces', 'SGD'=>'Singapore Dollar',
90             'SKK'=>'Slovak Koruna', 'SIT'=>'Slovenian Tolar', 'SBD'=>'Solomon Islands Dollar',
91             'SOS'=>'Somali Shilling', 'ZAR'=>'South African Rand', 'ESP'=>'Spanish Peseta',
92             'LKR'=>'Sri Lanka Rupee', 'SHP'=>'St Helena Pound', 'SDD'=>'Sudanese Dinar',
93             'SRG'=>'Surinam Guilder', 'SZL'=>'Swaziland Lilageni', 'SEK'=>'Swedish Krona',
94             'CHF'=>'Swiss Franc', 'SYP'=>'Syrian Pound', 'TWD'=>'Taiwan Dollar',
95             'TZS'=>'Tanzanian Shilling', 'THB'=>'Thai Baht', 'TOP'=>"Tonga Pa'anga",
96             'TTD'=>'Trinida and Tobago Dollar', 'TND'=>'Tunisian Dinar', 'TRL'=>'Turkish Lira',
97             'USD'=>'US Dollar', 'AED'=>'UAE Dirham', 'UGX'=>'Ugandan Shilling',
98             'UAH'=>'Ukraine Hryvnia', 'UYU'=>'Uruguayan New Peso', 'VUV'=>'Vanuatu Vatu',
99             'VEB'=>'Venezuelan Bolivar', 'VND'=>'Vietnam Dong', 'YER'=>'Yemen Riyal',
100             'YUM'=>'Yugoslav Dinar', 'ZMK'=>'Zambian Kwacha', 'ZWD'=>'Zimbabwe Dollar'
101             );
102            
103            
104             =head1 USE
105            
106             Call the module's C<&convert> routine, supplying three arguments:
107             the amount to convert, and the currencies to convert from and to.
108            
109             Codes are used to identify currencies: you may view them in the
110             values of the C<%currencies> hash, where keys are descriptions of
111             the currencies.
112            
113             In the event that attempts to convert fail, you will recieve C
114             in response, with errors going to STDERR, and notes displayed if
115             the modules global C<$CHAT> is defined.
116            
117             =head2 SUBROUTINE convert
118            
119             $value = &convert( $amount_to_convert, $from, $to);
120            
121             Requires the sum to convert, and two symbols to represent the source
122             and target currencies.
123            
124             In more detail, access L,
125             where the value of C (in the example, C) is the value of the source
126             and target currencies, and the rest is stuff I've not looked into....
127            
128             =cut
129            
130 0     0 1   sub convert { my ($amount, $from, $to) = (shift,shift,shift);
131 0 0 0       die "Please call as ...::convert(\$amount,\$from,\$to) " unless (defined $amount and defined $from and defined $to);
      0        
132 0 0 0       carp "No such currency code as <$from>." and return undef if not exists $currencies{$from};
133 0 0 0       carp "No such currency code as <$to>." and return undef if not exists $currencies{$to};
134 0 0 0       carp "Please supply a positive sum to convert ." and return undef if $amount<0;
135 0 0         warn "Converting <$amount> from <$from> to <$to> " if $CHAT;
136 0           my ($value);
137 0           for my $attempt (0..3){
138 0 0         warn "Attempt $attempt ...\n" if $CHAT;
139 0           $value = _get_document_csv($amount,$from,$to);
140             # Can't really say "last if defined $doc" as $doc may be a Yahoo 404-like error?
141 0 0         last if defined $value;
142             }
143 0           return $value;
144             }
145            
146             =head2 DEPRECATED SUBROUTINE deprecated_convert
147            
148             The old C routine: accesses C,
149             where C is the currency being converted, C is the
150             target currency, and C is the amount being converted.
151             The latter is a number; the former two codes defined in our
152             C<%currencies> hash. (For the date this was last checked, C).
153            
154             =cut
155            
156 0     0 1   sub deprecated_convert { my ($amount, $from, $to) = (shift,shift,shift);
157 0           require HTML::TokeParser;
158 0           import HTML::TokeParser;
159 0 0         die 'You not have HTML::TokeParser...?' unless HTML::TokeParser->VERSION;
160 0 0 0       die "Please call as ...::convert(\$amount,\$from,\$to) " unless (defined $amount and defined $from and defined $to);
      0        
161 0 0 0       carp "No such currency code as <$from>." and return undef if not exists $currencies{$from};
162 0 0 0       carp "No such currency code as <$to>." and return undef if not exists $currencies{$to};
163 0 0 0       carp "Please supply a positive sum to convert ." and return undef if $amount<0;
164 0 0         warn "Converting <$amount> from <$from> to <$to> " if $CHAT;
165 0           my ($doc,$result);
166 0           for my $attempt (0..3){
167 0 0         warn "Attempt $attempt ...\n" if $CHAT;
168 0           $doc = _get_document_html($amount,$from,$to);
169             # Can't really say "last if defined $doc"
170             # as $doc may be a Yahoo 404-like error?
171 0 0         last if defined $doc;
172             }
173 0 0         if (defined $doc){
174 0 0         if ($result = _extract_data($doc)){
175 0 0         warn "Got doc, result is $result" if defined $CHAT;
176             }
177             }
178 0 0 0       if (defined $doc and defined $result){
    0 0        
179 0 0 0       warn "Result:$result\n" if defined $result and defined $CHAT;
180 0           return $result;
181             } elsif (defined $doc and not defined $result){
182 0 0         carp "Connected to Yahoo but could not read the page: sorry" if defined $CHAT;
183 0           return undef;
184             } else {
185 0 0         carp "Could not connect to Yahoo" if defined $CHAT;
186 0           return undef;
187             }
188             }
189            
190            
191             #
192             # PRIVATE SUB get_document_csv
193             # Accepts: amount, starting currency, target currency
194             # Returns: HTML content
195             # URI: http://finance.yahoo.com/d/quotes.csv?s=GBPEUR=X&f=l1
196             #
197 0     0     sub _get_document_csv { my ($amount,$from,$to) = (shift,shift,shift);
198 0 0 0       die "get_document requires a \$amount,\$from_currency,\$target_currency arrity" unless (defined $amount and defined $to and defined $from);
      0        
199            
200 0           my $ua = LWP::UserAgent->new; # Create a new UserAgent
201 0           $ua->agent('Mozilla/25.'.(localtime)." (PERL ".__PACKAGE__." $VERSION"); # Give it a type name
202            
203 0           my $url =
204             'http://finance.yahoo.com/d/quotes.csv?'
205             . 's='.$from.$to
206             . '=X&f=l1'
207             ;
208 0 0         warn "Attempting to access <$url> ...\n" if $CHAT;
209            
210             # Format URL request
211 0 0 0       my $req = new HTTP::Request ('GET',$url) or die "...could not GET.\n" and return undef;
212 0           my $res = $ua->request($req); # $res is the object UA returned
213 0 0         if (not $res->is_success()) { # If successful
214 0           warn"...failed to retrieve currency document from Yahoo...\nTried: $url\n";
215 0           return undef;
216             }
217 0 0         warn "...ok.\n" if $CHAT;
218            
219 0           my $r = $res->content;
220 0           $r =~ s/^\s*([\d.]+)\s*$/$1/sg;
221 0 0         if ($r eq ''){
222 0           warn "...document contained no data/unexpected data\n";
223 0           return undef;
224             }
225            
226 0           return $amount * $r;
227             }
228            
229            
230            
231             #
232             # PRIVATE SUB get_document_html
233             # Accepts: amount, starting currency, target currency
234             # Returns: HTML content
235             # URI: http://finance.yahoo.com/currency/convert?amt=1&from=GBP&to=HUF&submit=Convert
236             #
237 0     0     sub _get_document_html { my ($amount,$from,$to) = (shift,shift,shift);
238 0 0 0       die "get_document requires a \$amount,\$from_currency,\$target_currency arrity" unless (defined $amount and defined $to and defined $from);
      0        
239            
240 0           my $ua = LWP::UserAgent->new; # Create a new UserAgent
241 0           $ua->agent('Mozilla/25.'.(localtime)." (PERL ".__PACKAGE__." $VERSION"); # Give it a type name
242            
243 0           my $url =
244             'http://finance.yahoo.com/currency/convert?'
245             . 'amt='.$amount
246             . '&from='.$from
247             . '&to='.$to
248             . '&submit=Convert'
249             ;
250 0 0         warn "Attempting to access <$url> ...\n" if $CHAT;
251            
252             # Format URL request
253 0 0 0       my $req = new HTTP::Request ('GET',$url) or die "...could not GET.\n" and return undef;
254 0           my $res = $ua->request($req); # $res is the object UA returned
255 0 0         if (not $res->is_success()) { # If successful
256 0 0         warn"...failed to retrieve currency document.\n" if $CHAT;
257             return undef
258 0           }
259 0 0         warn "...ok.\n" if $CHAT;
260            
261 0           return $res->content;
262             }
263            
264            
265             #
266             # PRIVATE SUB _extract_data
267             # Accept: HTML doc as arg
268             # Return amount on success, undef on failure
269             # NOV 2004: Fifth yfnc_tabledata1 class TD, and bold
270             # MAY 2003: Sloopy errors fixed. Sorry.
271             # APR 2003: Data is now in SIXTH table, second row, second (non-header) cell, in bold
272             # JAN 2003: Data is now in SEVENTH table, second row, second (non-header) cell, in bold
273             # JULY 2001: Data is in fourth table's fourth TD
274             # DEC 2001: Data is in FIFTH table
275             #
276 0     0     sub _extract_data { my $doc = shift;
277 0           my $token;
278 0 0         my $p = HTML::TokeParser->new(\$doc) or die "Couldn't create TokePraser: $!";
279             # Fifth TD and class is 'yfnc_tabledata1'
280 0           for (1..5){
281 0   0       while ($token = $p->get_token and not (
      0        
282             @$token[0] eq 'S' and @$token[1] eq 'td'
283             and @$token[2]->{class}
284             and @$token[2]->{class} eq 'yfnc_tabledata1'
285             ) ){}
286             }
287 0 0         $token = $p->get_token or return undef;
288 0 0 0       return undef if @$token[0] ne 'S' and @$token[1] ne 'b';
289            
290 0 0         $token = $p->get_token or return undef;
291 0 0         return undef if @$token[0] ne 'T';
292            
293 0 0         return @$token[1] =~ /^[\d.,]+$/ ? @$token[1] : undef;
294             }
295            
296            
297            
298            
299             =head1 EXPORTS
300            
301             None.
302            
303             =head1 REVISIONS
304            
305             Please see the enclosed file CHANGES.
306            
307             =head1 PROBLEMS?
308            
309             If this doesn't work, Yahoo have probably changed their URI or HTML format.
310             Let me know and I'll fix the code. Or by all means send a patch.
311             Please don't just post a bad review on CPAN, I don't get CC'd them.
312            
313             =head1 SEE ALSO
314            
315             L: L;
316             L;
317             L.
318            
319             =head1 AUTHOR
320            
321             Lee Goddard, lgoddard -at- cpan -dot- org.
322            
323             =head1 COPYRIGHT
324            
325             Copyright (C) Lee Goddard, 2001, 2005, ff. - All Rights Reserved.
326            
327             This library is free software and may be used only under the same terms as Perl itself.
328            
329             =cut
330            
331            
332             # $Finance::Currency::Convert::Yahoo::CHAT=1;
333             # print Finance::Currency::Convert::Yahoo::convert(1,'EUR','GBP');
334            
335            
336             1;
337            
338             __END__