File Coverage

blib/lib/Finance/Currency/Convert/XE.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Finance::Currency::Convert::XE;
2              
3 7     7   168256 use strict;
  7         20  
  7         288  
4 7     7   40 use warnings;
  7         12  
  7         246  
5              
6 7     7   37 use vars qw($VERSION);
  7         22  
  7         607  
7             $VERSION = '0.25';
8              
9             #--------------------------------------------------------------------------
10              
11             =head1 NAME
12              
13             Finance::Currency::Convert::XE - Currency conversion module.
14              
15             =head1 SYNOPSIS
16              
17             use Finance::Currency::Convert::XE;
18             my $obj = Finance::Currency::Convert::XE->new()
19             || die "Failed to create object\n" ;
20              
21             my $value = $obj->convert(
22             'source' => 'GBP',
23             'target' => 'EUR',
24             'value' => '123.45',
25             'format' => 'text'
26             ) || die "Could not convert: " . $obj->error . "\n";
27              
28             my @currencies = $obj->currencies;
29              
30             or
31              
32             use Finance::Currency::Convert::XE;
33             my $obj = Finance::Currency::Convert::XE->new(
34             'source' => 'GBP',
35             'target' => 'EUR',
36             'format' => 'text'
37             ) || die "Failed to create object\n" ;
38              
39             my $value = $obj->convert(
40             'value' => '123.45',
41             'format' => 'abbv'
42             ) || die "Could not convert: " . $obj->error . "\n";
43              
44             $value = $obj->convert('123.45')
45             || die "Could not convert: " . $obj->error . "\n";
46              
47             my @currencies = $obj->currencies;
48              
49             =head1 DESCRIPTION
50              
51             Currency conversion module using XE.com's Universal Currency Converter (tm)
52             site.
53              
54             WARNING: Do not use this module for any commercial purposes, unless you have
55             obtain an explicit license to use the service provided by XE.com. For further
56             details please read the Terms and Conditions available at L.
57              
58             =over
59              
60             =item * http://www.xe.com/errors/noautoextract.htm
61              
62             =back
63              
64             =cut
65              
66             #--------------------------------------------------------------------------
67              
68             ###########################################################################
69             #Library Modules #
70             ###########################################################################
71              
72 7     7   11079 use WWW::Mechanize;
  0            
  0            
73             use HTML::TokeParser;
74              
75             ###########################################################################
76             #Constants #
77             ###########################################################################
78              
79             use constant UCC => 'http://www.xe.com/currencyconverter';
80              
81             ###########################################################################
82             #Variables #
83             ###########################################################################
84              
85             my %currencies; # only need to load once!
86             my @defaults = ('source', 'target', 'format');
87              
88             my $web = WWW::Mechanize->new();
89             $web->agent_alias( 'Mac Safari' );
90              
91             #--------------------------------------------------------------------------
92              
93             ###########################################################################
94             #Interface Functions #
95             ###########################################################################
96              
97             =head1 METHODS
98              
99             =over 4
100              
101             =item new
102              
103             Creates a new Finance::Currency::Convert::XE object. Can be supplied with
104             default values for source and target currency, and the format required of the
105             output. These can be overridden in the convert() method.
106              
107             =cut
108              
109             sub new {
110             my ($this, @args) = @_;
111             my $class = ref($this) || $this;
112             my $self = {};
113             bless $self, $class;
114             $self->_initialize(@args);
115             return $self;
116             }
117              
118             =item currencies
119              
120             Returns a plain array of the currencies available for conversion.
121              
122             =cut
123              
124             sub currencies {
125             my $self = shift;
126             return sort keys %currencies;
127             }
128              
129             =item add_currencies
130              
131             Allows the user to add currencies to the internal hash. Currencies can be added
132             as per the code below:
133              
134             $self->add_currencies(
135             ZZZ => {text => 'An Example', symbol => '$'},
136             ZZY => {text => 'Testing'}
137             );
138              
139             Note that unless otherwise stated, the symbol will be set to '¤'. The code
140             used must be 3 characters in length, and a text part must be included.
141              
142             =cut
143              
144             sub add_currencies {
145             my ($self,%hash) = @_;
146             for my $code (keys %hash) {
147             if($code =~ /[A-Z]{3}/i) {
148             $code = uc $code;
149             if($hash{$code}->{text}) {
150             $currencies{$code}->{name} = $hash{$code}->{text} || die;
151             $currencies{$code}->{symbol} = $hash{$code}->{symbol} || '¤';
152             } else {
153             $self->{error} = "User currency '$code' has no text part";
154             }
155             } else {
156             $self->{error} = "User currency '$code' is invalid";
157             }
158             }
159             }
160              
161             =item convert
162              
163             Converts some currency value into another using XE.com's UCC.
164              
165             An anonymous hash is used to pass parameters. Legal hash keys and values
166             are as follows:
167              
168             convert(
169             source => $currency_from,
170             target => $currency_to,
171             value => $currency_from_value,
172             format => $print_format
173             );
174              
175             The format key is optional, and takes one of the following strings:
176              
177             'number' (returns '12.34')
178             'symbol' (returns '£12.34')
179             'text' (returns '12.34 Great Britain, Pound')
180             'abbv' (returns '12.34 GBP')
181              
182             If format key is omitted, 'number' is assumed and the converted value
183             is returned.
184              
185             If only a value is passed, it is assumed that this is the value to be
186             converted and the remaining parameters will be defined by the defaults set
187             in the constructor. Note that no internal defaults are assumed.
188              
189             Note that not all countries have symbols in the standard character set.
190             Where known the appropriate currency symbol is used, otherwise the
191             generic currency symbol is used.
192              
193             It should also be noted that there is a recommendation to use only the
194             standardised three letter abbreviation ('abbv' above). However, for
195             further reading please see:
196              
197             http://www.jhall.demon.co.uk/currency/
198             http://www.jhall.demon.co.uk/currency/by_symbol.html
199              
200             =cut
201              
202             sub convert {
203             my $self = shift;
204             my %params = @_ > 1 ? @_ : (value => $_[0]);
205             $params{$_} ||= $self->{$_} for(@defaults);
206              
207             undef $self->{error};
208             unless( $params{source} ){
209             $self->{error} = 'Source currency is blank. This parameter is required';
210             return;
211             }
212              
213             unless( exists($currencies{$params{source}}) ){
214             $self->{error} = 'Source currency "' . $params{source} . '" is not available';
215             return;
216             }
217              
218             unless( $params{target} ){
219             $self->{error} = 'Target currency is blank. This parameter is required';
220             return;
221             }
222              
223             unless( exists($currencies{$params{target}}) ){
224             $self->{error} = 'Target currency "' . $params{target} . '" is not available';
225             return;
226             }
227              
228             # store later use
229             $self->{code} = $params{target};
230             $self->{name} = $currencies{$params{target}}->{name};
231             $self->{symbol} = $currencies{$params{target}}->{symbol};
232             $self->{string} = $self->_format($params{format});
233              
234             # This "feature" is actually useful as a pass-thru filter.
235             if( $params{source} eq $params{target} ) {
236             return sprintf $self->{string}, $params{value}
237             }
238              
239             # get the base site
240             $web->get( UCC );
241              
242             unless($web->success()) {
243             $self->{error} = 'Unable to retrieve webpage';
244             return;
245             }
246              
247             my @forms = $web->forms();
248             my $form_number = 1;
249             my $found = 0;
250              
251             foreach my $form (@forms) {
252             if ($form->action eq 'http://www.xe.com/currencyconverter/convert/') {
253             $found = 1;
254             last;
255             }
256              
257             $form_number++;
258             }
259              
260             if ($found) {
261             # complete and submit the form
262             $web->submit_form(
263             form_number => $form_number,
264             fields => { 'From' => $params{source},
265             'To' => $params{target},
266             'Amount' => $params{value}
267             }
268             );
269             }
270              
271             unless($found && $web->success()) {
272             $self->{error} = 'Unable to retrieve webform';
273             return;
274             }
275              
276             # return the converted value
277             return $self->_extract_text($web->content());
278             }
279              
280             =item error
281              
282             Returns a (hopefully) meaningful error string.
283              
284             =cut
285              
286             sub error {
287             my $self = shift;
288             return $self->{error};
289             }
290              
291             ###########################################################################
292             #Internal Functions #
293             ###########################################################################
294              
295             sub _initialize {
296             my($self, %params) = @_;
297             # set defaults
298             $self->{$_} = $params{$_} for(@defaults);
299              
300             return if(keys %currencies);
301             local($_);
302              
303             # Extract the mapping of currencies and their atrributes
304             while(){
305             s/\s*$//;
306             my ($code,$text,$symbol) = split /\|/;
307             $currencies{$code}->{name} = $text;
308             $currencies{$code}->{symbol} = $symbol;
309             }
310              
311             return;
312             }
313              
314             # Formats the return string to the requirements of the caller
315             sub _format {
316             my($self, $form) = @_;
317              
318             my %formats = (
319             'symbol' => $self->{symbol} . '%.02f',
320             'abbv' => '%.02f ' . $self->{code},
321             'text' => '%.02f ' . $self->{name},
322             'number' => '%.02f',
323             );
324              
325             return $formats{$form} if(defined $form && $formats{$form});
326             return '%.02f';
327             }
328              
329             # Extract the text from the html we get back from UCC and return
330             # it (keying on the fact that what we want is in the table after
331             # the faq link).
332             sub _extract_text {
333             my($self, $html) = @_;
334             my $tag;
335             my $p = HTML::TokeParser->new(\$html);
336              
337             # first look for the 'td' element
338             while (1) {
339             return unless ($tag = $p->get_tag('td'));
340             next unless (defined($tag->[1]{'align'}) && ($tag->[1]{'align'} eq 'left'));
341             # this will probably be the value
342             my $value = $p->get_trimmed_text;
343              
344             # then make sure this has the 'span' with the target
345             # currency code
346             my $tag2 = $p->get_tag('span');
347             my $cd = $p->get_trimmed_text;
348             if (defined($tag2) && defined($tag2->[1]{'class'} && $tag2->[1]{class} eq 'uccResCde'
349             )) {
350             if ($cd eq $self->{code}) {
351             # found it, return
352             $value =~ s/,//g;
353             return sprintf $self->{string}, $value;
354             }
355             }
356             }
357              
358             # didn't find anything
359             return;
360             }
361              
362             1;
363              
364             #--------------------------------------------------------------------------
365              
366             =back
367              
368             =head1 TERMS OF USE
369              
370             XE.com have a Terms of Use policy that states:
371              
372             This website is for informational purposes only and is not intended to
373             provide specific commercial, financial, investment, accounting, tax, or
374             legal advice. It is provided to you solely for your own personal,
375             non-commercial use and not for purposes of resale, distribution, public
376             display or performance, or any other uses by you in any form or manner
377             whatsoever. Unless otherwise indicated on this website, you may display,
378             download, archive, and print a single copy of any information on this
379             website, or otherwise distributed from XE.com, for such personal,
380             non-commercial use, provided it is done pursuant to the User Conduct and
381             Obligations set forth herein.
382              
383             As such this software is for personal use ONLY. No liability is accepted by
384             the author for abuse or miuse of the software herein. Use of this software
385             is only permitted under the terms stipulated by XE.com.
386              
387             The full legal document is available at L
388              
389             =head1 TODO
390              
391             Currency symbols are currently specified with a generic symbol, if the
392             currency symbol is unknown. Are there any other symbols available in
393             Unicode? Let me know if there are.
394              
395             =head1 SEE ALSO
396              
397             L,
398             L
399              
400             =head1 SUPPORT
401              
402             There are no known bugs at the time of this release. However, if you spot a
403             bug or are experiencing difficulties that are not explained within the POD
404             documentation, please submit a bug to the RT system (see link below). However,
405             it would help greatly if you are able to pinpoint problems or even supply a
406             patch.
407              
408             Fixes are dependant upon their severity and my availablity. Should a fix not
409             be forthcoming, please feel free to (politely) remind me by sending an email
410             to barbie@cpan.org .
411              
412             RT: L
413              
414             =head1 AUTHOR
415              
416             Barbie,
417             for Miss Barbell Productions .
418              
419             =head1 COPYRIGHT
420              
421             Copyright © 2002-2011 Barbie for Miss Barbell Productions.
422              
423             This module is free software; you can redistribute it and/or
424             modify it under the Artistic Licence v2.
425              
426             =cut
427              
428             #--------------------------------------------------------------------------
429              
430             __DATA__