File Coverage

blib/lib/Net/UPCDatabase.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Net::UPCDatabase;
2              
3 1     1   22708 use 5.008;
  1         4  
  1         41  
4 1     1   458 use Frontier::Client;
  0            
  0            
5              
6             our $VERSION = '0.07';
7              
8             our $DEFAULTURL = 'http://www.upcdatabase.com/rpc';
9              
10             =head1 NAME
11              
12             Net::UPCDatabase - Simple OO interface to UPCDatabase.com
13              
14             =head1 SYNOPSIS
15              
16             use Net::UPCDatabase;
17             my $upcdb = Net::UPCDatabase->new;
18              
19             print "\n[lookup]\n";
20             my $upc = '035000764119';
21             my $item = $upcdb->lookup($upc);
22             print "UPC: $item->{upc}\n";
23             if ($item->{error}) {
24             print "Error: $item->{error}\n";
25             }
26             else {
27             print "Product: $item->{description}\n";
28             print "Size: $item->{size}\n";
29             }
30              
31             print "\n[convertUpcE]\n";
32             my $upcE = '01212901';
33             my $upcA = $upcdb->convertUpcE($upcE);
34             print "UPCE: $upcA->{upcE}\n";
35             if ($upcA->{error}) {
36             print "Error: $upcA->{error}\n";
37             }
38             else {
39             print "UPCA: $upcA->{upc}\n";
40             }
41              
42             print "\n[calculateCheckDigit]\n";
43             my $upcC = '01200000129C';
44             my $upcA = $upcdb->calculateCheckDigit($upcE);
45             print "UPCC: $upcA->{upcC}\n";
46             if ($upcA->{error}) {
47             print "Error: $upcA->{error}\n";
48             }
49             else {
50             print "UPCA: $upcA->{upc}\n";
51             }
52              
53             =head1 DESCRIPTION
54              
55             Connects to UPCDatabase.com to get information about a given UPC.
56              
57             =head1 FUNCTIONS
58              
59             =head2 new
60              
61             $upcObject = Net::UPCDatabase->new;
62              
63             # .. or ..
64              
65             $upcObject = Net::UPCDatabase->new( url => $aDifferentUrlThanDefault );
66              
67             Accepts an B argument, a URL to use instead of the default. Unless you're really sure what you're doing, don't give it a URL. It defaults to 'http://www.upcdatabase.com/rpc', which is probably the right thing.
68              
69             Returns the object.
70              
71             =cut
72              
73             sub new {
74             my $class = shift;
75             my $self = bless({}, $class);
76             my %arg = @_;
77             $self->{_debug} = $arg{debug} || 0;
78             $self->{_url} = $arg{url} || $DEFAULTURL;
79             $self->{_coder} = Frontier::RPC2->new;
80             $self->{_server} = Frontier::Client->new('url' => $self->{_url}, debug => $self->{_debug});
81             return $self;
82             }
83              
84             =head2 lookup
85              
86             $itemInfo = $upcObject->lookup($upc);
87              
88             # example usage
89             my $ean = '0012000000133'; # pepsi 12oz can
90             print "EAN: $ean\n";
91             my $item = $upcdb->lookup($ean);
92             die "LOOKUP-ERROR: $item->{error}\n" if $item->{error};
93             print Dumper($item);
94              
95             Accepts a B argument, the UPC to lookup.
96             The UPC can be UPC-E (8 digits), UPC-A (12 digits), or EAN (13 digits).
97              
98             Returns the data about the given UPC in a hash reference.
99              
100             On error, it returns the given error reason as C<< $itemInfo->{error} >>.
101              
102             =cut
103              
104             sub lookup
105             {
106             my $self = shift;
107             my $upc = uc(shift);
108             my $response = {};
109             $upc =~ s|X|C|g;
110             $upc =~ s|[^0-9C]||g;
111             if ($upc =~ m|^\d{8}$|)
112             {
113             my $upcA = $self->convertUpcE($upc);
114             if ($upcA->{error})
115             {
116             $response = $upcA;
117             }
118             else
119             {
120             $upc = $upcA->{upc};
121             }
122             }
123             if (!$response->{error} && $upc =~ m|C|)
124             {
125             my $upcC = $self->calculateCheckDigit($upc);
126             if ($upcC->{error})
127             {
128             $response = $upcC;
129             }
130             else
131             {
132             $upc = $upcC->{upc};
133             }
134             }
135             $upc = substr(('0' x 13).$upc, -13, 13); # if it ain't a 13-digit EAN, make it one.
136             if (!$response->{error})
137             {
138             my $data = $self->{_server}->call('lookupEAN', $self->{_coder}->string($upc));
139             if (ref($data) eq "HASH")
140             {
141             $response = $data;
142             }
143             else
144             {
145             $response->{upc} = $upc;
146             $response->{error} = $data;
147             }
148             }
149             return $response;
150             }
151              
152             =head2 convertUpcE
153              
154             $ean = $upcObject->convertUpcE($upcE);
155             die "ERROR: $ean->{error}\n" if $ean->{error};
156             print "EAN: $ean->{ean}\n";
157              
158             # example usage
159             my $upce = '01201701'; # pepsi 24 pack
160             print "UPCE: $upce\n";
161             $ean = $upcdb->convertUpcE($upce);
162             die "EAN-ERROR: $ean->{error}\n" if $ean->{error};
163             print "EAN: $ean->{ean}\n";
164             $item = $upcdb->lookup($ean->{ean});
165             die "LOOKUP-ERROR: $item->{error}\n" if $item->{error};
166             print Dumper($item);
167              
168             Accepts a B argument, the UPC-E to convert.
169              
170             Returns the EAN (exactly 13 digits).
171              
172             On error, it returns the given error reason as C<< $itemInfo->{error} >>.
173              
174             =cut
175              
176             sub convertUpcE
177             {
178             my $self = shift;
179             my $upc = shift;
180             my $data = $self->{_server}->call('convertUPCE', $self->{_coder}->string($upc));
181             my $response = {};
182             $response->{upcE} = $upc;
183             if ($data =~ m|^\d{13}$|)
184             {
185             $response->{ean} = $data;
186             }
187             else
188             {
189             $response->{error} = $data;
190             }
191             return $response;
192             }
193              
194             =head2 calculateCheckDigit
195              
196             $ean = '001200000C2X1'; # bad (more than one digit being calculated)
197             $ean = '001200000C29C'; # bad (more than one digit being calculated)
198             $ean = '001200000129C'; # good (only one digit)
199             $ean = '00120000012C1'; # good (only one digit)
200             $ean = $upcObject->calculateCheckDigit($ean);
201             die "ERROR: $ean->{error}\n" if $ean->{error};
202             print "EAN: $ean->{ean}\n";
203              
204             Accepts a B argument, the UPC-A or EAN with checkdigit placeholder (C or X) to calculate.
205             This function will calculate the missing digit for any position, not just the last position.
206             This only works if only one digit being calculated.
207             This doesn't work with UPC-E.
208             There is no difference between using "X" or "C" as the placeholder.
209              
210             Returns the EAN with the checkdigit properly calculated.
211              
212             On error, it returns the given error reason as C<< $itemInfo->{error} >>.
213              
214             NOTE: This uses an internal function, not the function on UPCDatabase.com because it appears that it is currently not
215             implemented on the UPCDatabase.com side of things.
216             If it is implemented to the same extent on UPCDatabase.com, it is a simple change to use it instead.
217              
218             =cut
219              
220             sub calculateCheckDigit
221             {
222             my $self = shift;
223             my $upc = uc(shift);
224             return $self->_calculateCheckDigit($upc); ## ???: If UPCDatabase.com supports this function (no longer "Unimplemented"), maybe remove this line?
225             #$upc =~ s|X|C|g;
226             #my $data = $self->{_rpcClient}->send_request('calculateCheckDigit', $upc)->value;
227             #my $response = {};
228             #$response->{upcC} = $upc;
229             #if ($data =~ m|^\d{12}$|)
230             #{
231             # $response->{upc} = $data;
232             #}
233             #else
234             #{
235             # $response->{error} = $data;
236             # if ($response->{error} eq "Unimplemented")
237             # {
238             # return $self->_calculateCheckDigit($upc);
239             # }
240             #}
241             #return $response;
242             }
243              
244             =head2 _calculateCheckDigit
245              
246             The internal function that calculates the check digit.
247             You won't want to use this yourself.
248              
249             =cut
250              
251             sub _calculateCheckDigit
252             {
253             my $self = shift;
254             my $upc = uc(shift);
255             $upc = substr(('0' x 13).$upc, -13, 13); # if it ain't a 13-digit EAN, make it one.
256             $upc =~ s|X|C|g;
257             my $response = {};
258             $response->{eanC} = $upc;
259             if ($upc =~ m|^([C\d]{12})([C\d])$| && $upc !~ m|C.*?C|)
260             {
261             my $code = $1;
262             my $check = $2;
263             my @odd = ();
264             my @even = ();
265             my $i = 0;
266             my $oddTotal = 0;
267             my $oddMissing = 0;
268             my $evenTotal = 0;
269             my $evenMissing = 0;
270             foreach my $digit (split(//, $code))
271             {
272             if ($i++ % 2)
273             {
274             if ($digit eq "C")
275             {
276             $oddMissing++;
277             }
278             else
279             {
280             $oddTotal += $digit * 3;
281             }
282             }
283             else
284             {
285             if ($digit eq "C")
286             {
287             $evenMissing++;
288             }
289             else
290             {
291             $evenTotal += $digit;
292             }
293             }
294             }
295             if ($check eq "C")
296             {
297             my $theTotal = $evenTotal + $oddTotal;
298             $theTotal -= int($theTotal / 10) * 10;
299             $theTotal ||= 10;
300             $check = 10 - $theTotal;
301             }
302             elsif ($oddMissing) # ???: Is there a better way to do this than a wasteful brute force method?
303             {
304             my $isDigit = 0;
305             foreach $digit (0 .. 9)
306             {
307             my $theTotal = $evenTotal + $oddTotal + ($digit * 3);
308             $theTotal -= int($theTotal / 10) * 10;
309             $theTotal ||= 10;
310             my $tCheck = 10 - $theTotal;
311             if ($check == $tCheck)
312             {
313             $isDigit = $digit;
314             }
315             }
316             $code =~ s|C|$isDigit|;
317             }
318             elsif ($evenMissing)
319             {
320             my $theTotal = $evenTotal + $oddTotal + $check;
321             $theTotal -= int($theTotal / 10) * 10;
322             $theTotal ||= 10;
323             my $diff = 10 - $theTotal;
324             $code =~ s|C|$diff|;
325             }
326             $response->{ean} = $code.$check;
327             }
328             else
329             {
330             $response->{error} = 'Unimplemented';
331             }
332             return $response;
333             }
334              
335             =head1 DEPENDENCIES
336              
337             L
338             L
339              
340             =head1 TODO
341              
342             =over
343              
344             =item UPC checksum checking/creation
345              
346             Clean up calculation of odd-position checkdigit calculation.
347             It currently uses an inefficient brute-force method of calculation for that position.
348             Even-position and checksum position calculation is pretty efficient.
349             OEOEOEOEOEOX (O=odd, E=even, X=checksum)
350             It's not *really* that wasteful, just not as efficient as it could be.
351              
352             =back
353              
354             =head1 BUGS
355              
356             Report bugs on the CPAN bug tracker.
357             Please, do complain if something is broken.
358              
359             =head1 SEE ALSO
360              
361             L
362              
363             =head1 COPYRIGHT AND LICENSE
364              
365             Copyright 2005-2009 by Dusty Wilson
366              
367             This library is free software; you can redistribute it and/or modify
368             it under the same terms as Perl itself.
369              
370             =cut
371              
372             1;