File Coverage

blib/lib/XML/Grammar/ProductsSyndication.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package XML::Grammar::ProductsSyndication;
2              
3 4     4   181473 use warnings;
  4         11  
  4         154  
4 4     4   23 use strict;
  4         8  
  4         192  
5              
6 4     4   122 use 5.008;
  4         20  
  4         170  
7              
8 4     4   24 use File::Spec;
  4         17  
  4         118  
9              
10 4     4   8628 use XML::Grammar::ProductsSyndication::ConfigData;
  4         12  
  4         189  
11              
12 4     4   5819 use XML::LibXML;
  0            
  0            
13             use XML::LibXSLT;
14             use XML::Amazon;
15             use LWP::UserAgent;
16             use Imager;
17              
18             use base 'Class::Accessor';
19              
20             __PACKAGE__->mk_accessors(qw(
21             _data_dir
22             _filename
23             _img_fn
24             _source_dom
25             _stylesheet
26             _xml_parser
27             ));
28              
29             =head1 NAME
30              
31             XML::Grammar::ProductsSyndication - an XML Grammar for ProductsSyndication.
32              
33             =head1 VERSION
34              
35             Version 0.0403
36              
37             =cut
38              
39             our $VERSION = '0.0403';
40              
41             =head1 SYNOPSIS
42              
43             use XML::Grammar::ProductsSyndication;
44              
45             my $synd =
46             XML::Grammar::ProductsSyndication->new(
47             {
48             'source' =>
49             {
50             'file' => "products.xml",
51             },
52             }
53             );
54              
55             # A LibXML compatible XHTML DOM
56             my $xhtml = $synd->transform_into_html({ 'output' => "xml" });
57              
58             # Not implemented yet!
59             $synd->download_preview_images(
60             {
61             'dir' => "mydir/",
62             }
63             );
64              
65             =head1 FUNCTIONS
66              
67             =head2 XML::Grammar::ProductsSyndication->new({ arg1 => "value"...})
68              
69             The constructor - accepts a single hash reference with the following keys:
70              
71             =over 4
72              
73             =item 'source'
74              
75             A reference to a hash that contains the information for the source XML for the
76             file. Currently supported is a C<'file'> key that contains a path to the file.
77              
78             =item 'data_dir'
79              
80             Points to the data directory where the DTD files, the XSLT stylesheet, etc.
81             are stored. Should not be generally over-ridden.
82              
83             =back
84              
85             =cut
86              
87             sub new
88             {
89             my $class = shift;
90             my $self = {};
91             bless $self, $class;
92             $self->_init(@_);
93             return $self;
94             }
95              
96             sub _init
97             {
98             my ($self, $args) = @_;
99              
100             my $source = $args->{'source'} or
101             die "did not specify the source";
102              
103             my $file = $source->{file};
104              
105             $self->_filename($file);
106              
107             my $data_dir = $args->{'data_dir'} ||
108             XML::Grammar::ProductsSyndication::ConfigData->config('extradata_install_path')->[0];
109              
110             $self->_data_dir($data_dir);
111             return 0;
112             }
113              
114             sub _get_xml_parser
115             {
116             my $self = shift;
117              
118             if (!defined($self->_xml_parser()))
119             {
120             $self->_xml_parser(XML::LibXML->new());
121             $self->_xml_parser()->validation(0);
122             }
123             return $self->_xml_parser();
124             }
125              
126             sub _get_source_dom
127             {
128             my $self = shift;
129              
130             if (!defined($self->_source_dom()))
131             {
132             $self->_source_dom($self->_get_xml_parser()->parse_file($self->_filename()));
133             }
134             return $self->_source_dom();
135             }
136              
137             =head2 $processor->is_valid()
138              
139             Checks if the filename validates according to the DTD.
140              
141             =cut
142              
143             sub is_valid
144             {
145             my $self = shift;
146              
147             my $dtd =
148             XML::LibXML::Dtd->new(
149             "Products Syndication Markup Language 0.1.1",
150             File::Spec->catfile(
151             $self->_data_dir(),
152             "product-syndication.dtd"
153             ),
154             );
155              
156             return $self->_get_source_dom()->validate($dtd);
157             }
158              
159             sub _get_stylesheet
160             {
161             my $self = shift;
162              
163             if (!defined($self->_stylesheet()))
164             {
165             my $xslt = XML::LibXSLT->new();
166              
167             my $style_doc = $self->_get_xml_parser()->parse_file(
168             File::Spec->catfile(
169             $self->_data_dir(),
170             "product-syndication.xslt"
171             ),
172             );
173              
174             $self->_stylesheet($xslt->parse_stylesheet($style_doc));
175             }
176             return $self->_stylesheet();
177             }
178              
179             =head2 $processor->transform_into_html({ 'output' => $output, })
180              
181             Transforms the output into HTML, and returns the results. If C<'output'> is
182             C<'xml'> returns the L XML DOM. If C<'output'> is C<'string'>
183             returns the XML as a monolithic string. Other C<'output'> formats are
184             undefined.
185              
186             =cut
187              
188             sub transform_into_html
189             {
190             my ($self, $args) = @_;
191              
192             my $source_dom = $self->_get_source_dom();
193             my $stylesheet = $self->_get_stylesheet();
194              
195             my $results = $stylesheet->transform($source_dom);
196              
197             my $medium = $args->{output};
198              
199             if ($medium eq "string")
200             {
201             return $stylesheet->output_string($results);
202             }
203             elsif ($medium eq "xml")
204             {
205             return $results;
206             }
207             else
208             {
209             die "Unknown medium";
210             }
211             }
212              
213             =head2 $self->update_cover_images({...});
214              
215             Updates the cover images from Amazon. Receives one hash ref being the
216             arguments. Valid keys are:
217              
218             =over 4
219              
220             =item * size
221              
222             The request size of the image - C<'s'>, C<'m'>, C<'l'>,
223              
224             =item * resize_to
225              
226             An optional hash ref containing width and height maximal dimensions of the
227             image to clip to.
228              
229             =item * name_cb
230              
231             A callback to determine the fully qualified path of the file. Receives the
232             following information:
233              
234             =over 4
235              
236             =item * xml_node
237              
238             =item * id
239              
240             =item * isbn
241              
242             =back
243              
244             =item * amazon_token
245              
246             An Amazon.com web services token. See L.
247              
248             =item * amazon_associate
249              
250             An optional Amazon.com associate ID. See L.
251              
252             =item * amazon_sak
253              
254             An optional Amazon.com Secret Access Key (sak). See L.
255              
256             =item * overwrite
257              
258             If true, instructs to overwrite the files in case they exist.
259              
260             =back
261              
262             =cut
263              
264             sub _transform_image
265             {
266             my ($self, $args) = @_;
267              
268             my $content = $args->{content};
269             my $resize_to = $args->{resize_to};
270              
271             if (!defined($resize_to))
272             {
273             return $content;
274             }
275             else
276             {
277             my ($req_w, $req_h) = @{$resize_to}{qw(width height)};
278              
279             my $image = Imager->new();
280             $image->read(data => $content, type => "jpeg");
281              
282             $image = $image->scale(xpixels => $req_w, ypixels => $req_h, type => 'min');
283              
284             my $buffer = "";
285             $image->write (data => \$buffer, type => "jpeg");
286              
287             return $buffer;
288             }
289             }
290              
291             sub _get_not_available_cover_image_data
292             {
293             my $self = shift;
294             open my $in, "<", File::Spec->catfile($self->_data_dir(), "na-cover.jpg");
295             my $content = "";
296             local $/;
297             $content = <$in>;
298             close($in);
299             return $content;
300             }
301              
302             sub _write_image
303             {
304             my ($self, $contents) = @_;
305              
306             my $filename = $self->_img_fn();
307              
308             open my $out, ">", $filename
309             or die "Could not open file '$filename'";
310             print {$out} $contents;
311             close ($out);
312             }
313              
314             sub update_cover_images
315             {
316             my ($self, $args) = @_;
317              
318             my $size = $args->{size};
319             my $name_cb = $args->{name_cb};
320             my $overwrite = $args->{overwrite};
321              
322             my $amazon_token = $args->{amazon_token};
323             my @amazon_associate =
324             (
325             (exists($args->{amazon_associate}) ?
326             (associate => $args->{amazon_associate},) :
327             ()
328             ),
329             (exists($args->{amazon_sak}) ?
330             (sak => $args->{amazon_sak},) :
331             (),
332             ),
333             );
334              
335             my $dom = $self->_get_source_dom();
336              
337             my @products = $dom->findnodes('//prod');
338              
339             my $amazon =
340             XML::Amazon->new(
341             token => $amazon_token,
342             @amazon_associate,
343             );
344              
345             my $ua = LWP::UserAgent->new();
346              
347             PROD_LOOP:
348             foreach my $prod (@products)
349             {
350             my ($asin_node) = $prod->findnodes('isbn');
351              
352             my $disable = $asin_node->getAttribute("disable");
353             if (defined($disable) && ($disable eq "1"))
354             {
355             next PROD_LOOP;
356             }
357              
358             my $asin = $asin_node->textContent();
359              
360             $self->_img_fn(
361             $name_cb->(
362             {
363             'xml_node' => $prod,
364             'id' => $prod->getAttribute("id"),
365             'isbn' => $asin,
366             }
367             )
368             );
369              
370             if ($overwrite || (! -e $self->_img_fn()))
371             {
372             my $item = $amazon->asin($asin);
373              
374             my $image_url = $item->image($size);
375             if (!defined($image_url))
376             {
377             $self->_write_image(
378             $self->_transform_image(
379             {
380             %$args,
381             'content' =>
382             $self->_get_not_available_cover_image_data(),
383             }
384             )
385             );
386             }
387             else
388             {
389             my $response = $ua->get($image_url);
390             if ($response->is_success)
391             {
392             $self->_write_image(
393             $self->_transform_image(
394             {
395             %$args,
396             'content' => $response->content(),
397             },
398             ),
399             );
400             }
401             else
402             {
403             die $response->status_line();
404             }
405             }
406             }
407             }
408             }
409              
410             =head1 AUTHOR
411              
412             Shlomi Fish, C<< >>
413              
414             =head1 BUGS
415              
416             Please report any bugs or feature requests to
417             C, or through the web interface at
418             L.
419             I will be notified, and then you'll automatically be notified of progress on
420             your bug as I make changes.
421              
422             =head1 TODO
423              
424             =over 4
425              
426             =item * Automatically Download Preview Images from Amazon.com
427              
428             =back
429              
430             =head1 SUPPORT
431              
432             You can find documentation for this module with the perldoc command.
433              
434             perldoc XML::Grammar::ProductsSyndication
435              
436             You can also look for information at:
437              
438             =over 4
439              
440             =item * AnnoCPAN: Annotated CPAN documentation
441              
442             L
443              
444             =item * CPAN Ratings
445              
446             L
447              
448             =item * RT: CPAN's request tracker
449              
450             L
451              
452             =item * Search CPAN
453              
454             L
455              
456             =back
457              
458             =head1 ACKNOWLEDGEMENTS
459              
460             * L for their excellent XSLT Tutorial.
461              
462             * L for squashing some L bugs
463             I reported to him.
464              
465             =head1 TODO
466              
467             =over 4
468              
469             =item * Trace the progress of the Amazon.com progress.
470              
471             =item * More XSLT customisation.
472              
473             =item * Generate a table-of-contents.
474              
475             =back
476              
477             =head1 COPYRIGHT & LICENSE
478              
479             Copyright 2006 Shlomi Fish, all rights reserved.
480              
481             This program is released under the following license: MIT X11.
482              
483             =cut
484              
485             1; # End of XML::Grammar::ProductsSyndication