XML::Grammar::ProductsSyndication - an XML Grammar for ProductsSyndication.


XML-Grammar-ProductsSyndication documentation Contained in the XML-Grammar-ProductsSyndication distribution.

Index


Code Index:

NAME

Top

XML::Grammar::ProductsSyndication - an XML Grammar for ProductsSyndication.

VERSION

Top

Version 0.0400

SYNOPSIS

Top

    use XML::Grammar::ProductsSyndication;

    my $synd = 
        XML::Grammar::ProductsSyndication->new(
            {
                'source' =>
                {
                    'file' => "products.xml",
                },
            }
        );

    # A LibXML compatible XHTML DOM
    my $xhtml = $synd->transform_into_html({ 'output' => "xml" });

    # Not implemented yet!
    $synd->download_preview_images(
        {
            'dir' => "mydir/",
        }
        );

FUNCTIONS

Top

XML::Grammar::ProductsSyndication->new({ arg1 => "value"...})

The constructor - accepts a single hash reference with the following keys:

'source'

A reference to a hash that contains the information for the source XML for the file. Currently supported is a 'file' key that contains a path to the file.

'data_dir'

Points to the data directory where the DTD files, the XSLT stylesheet, etc. are stored. Should not be generally over-ridden.

$processor->is_valid()

Checks if the filename validates according to the DTD.

$processor->transform_into_html({ 'output' => $output, })

Transforms the output into HTML, and returns the results. If 'output' is 'xml' returns the XML::LibXML XML DOM. If 'output' is 'string' returns the XML as a monolithic string. Other 'output' formats are undefined.

$self->update_cover_images({...});

Updates the cover images from Amazon. Receives one hash ref being the arguments. Valid keys are:

* size

The request size of the image - 's', 'm', 'l',

* resize_to

An optional hash ref containing width and height maximal dimensions of the image to clip to.

* name_cb

A callback to determine the fully qualified path of the file. Receives the following information:

* xml_node
* id
* isbn

* amazon_token

An Amazon.com web services token. See XML::Amazon.

* amazon_associate

An optional Amazon.com associate ID. See XML::Amazon.

* amazon_sak

An optional Amazon.com Secret Access Key (sak). See XML::Amazon.

* overwrite

If true, instructs to overwrite the files in case they exist.

AUTHOR

Top

Shlomi Fish, <shlomif at cpan.org>

BUGS

Top

Please report any bugs or feature requests to bug-xml-grammar-productssyndication at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=XML::Grammar::ProductsSyndication. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

TODO

Top

* Automatically Download Preview Images from Amazon.com

SUPPORT

Top

You can find documentation for this module with the perldoc command.

    perldoc XML::Grammar::ProductsSyndication

You can also look for information at:

* AnnoCPAN: Annotated CPAN documentation

http://annocpan.org/dist/XML::Grammar::ProductsSyndication

* CPAN Ratings

http://cpanratings.perl.org/d/XML::Grammar::ProductsSyndication

* RT: CPAN's request tracker

http://rt.cpan.org/NoAuth/Bugs.html?Dist=XML::Grammar::ProductsSyndication

* Search CPAN

http://search.cpan.org/dist/XML::Grammar::ProductsSyndication

ACKNOWLEDGEMENTS

Top

* http://www.zvon.org/ for their excellent XSLT Tutorial.

* http://search.cpan.org/~pajas/ for squashing some XML::LibXML bugs I reported to him.

TODO

Top

* Trace the progress of the Amazon.com progress.
* More XSLT customisation.
* Generate a table-of-contents.

COPYRIGHT & LICENSE

Top


XML-Grammar-ProductsSyndication documentation Contained in the XML-Grammar-ProductsSyndication distribution.
package XML::Grammar::ProductsSyndication;

use warnings;
use strict;

use XML::Grammar::ProductsSyndication::ConfigData;

use XML::LibXML;
use XML::LibXSLT;
use XML::Amazon;
use LWP::UserAgent;
use Imager;

use base 'Class::Accessor';

__PACKAGE__->mk_accessors(qw(
    _data_dir
    _filename
    _img_fn
    _source_dom
    _stylesheet
    _xml_parser
));

our $VERSION = '0.0400';

sub new
{
    my $class = shift;
    my $self = {};
    bless $self, $class;
    $self->_init(@_);
    return $self;
}

sub _init
{
    my ($self, $args) = @_;

    my $source = $args->{'source'} or
        die "did not specify the source";
    
    my $file = $source->{file};

    $self->_filename($file);

    my $data_dir = $args->{'data_dir'} ||
        XML::Grammar::ProductsSyndication::ConfigData->config('extradata_install_path')->[0];

    $self->_data_dir($data_dir);
    return 0;
}

sub _get_xml_parser
{
    my $self = shift;

    if (!defined($self->_xml_parser()))
    {
        $self->_xml_parser(XML::LibXML->new());
        $self->_xml_parser()->validation(0);
    }
    return $self->_xml_parser();
}

sub _get_source_dom
{
    my $self = shift;

    if (!defined($self->_source_dom()))
    {
        $self->_source_dom($self->_get_xml_parser()->parse_file($self->_filename()));
    }
    return $self->_source_dom();
}

sub is_valid
{
    my $self = shift;

    my $dtd = 
        XML::LibXML::Dtd->new(
            "Products Syndication Markup Language 0.1.1",
            File::Spec->catfile(
                $self->_data_dir(), 
                "product-syndication.dtd"
            ),
        );

    return $self->_get_source_dom()->validate($dtd);
}

sub _get_stylesheet
{
    my $self = shift;

    if (!defined($self->_stylesheet()))
    {
        my $xslt = XML::LibXSLT->new();

        my $style_doc = $self->_get_xml_parser()->parse_file(
                File::Spec->catfile(
                    $self->_data_dir(), 
                    "product-syndication.xslt"
                ),
            );

        $self->_stylesheet($xslt->parse_stylesheet($style_doc));
    }
    return $self->_stylesheet();
}

sub transform_into_html
{
    my ($self, $args) = @_;

    my $source_dom = $self->_get_source_dom();
    my $stylesheet = $self->_get_stylesheet();

    my $results = $stylesheet->transform($source_dom);

    my $medium = $args->{output};

    if ($medium eq "string")
    {
        return $stylesheet->output_string($results);
    }
    elsif ($medium eq "xml")
    {
        return $results;
    }
    else
    {
        die "Unknown medium";
    }
}

sub _transform_image
{
    my ($self, $args) = @_;

    my $content = $args->{content};
    my $resize_to = $args->{resize_to};

    if (!defined($resize_to))
    {
        return $content;
    }
    else
    {
        my ($req_w, $req_h) = @{$resize_to}{qw(width height)};

        my $image = Imager->new();
        $image->read(data => $content, type => "jpeg");

        $image = $image->scale(xpixels => $req_w, ypixels => $req_h, type => 'min');

        my $buffer = "";
        $image->write (data => \$buffer, type => "jpeg");

        return $buffer;
    }
}

sub _get_not_available_cover_image_data
{
    my $self = shift;
    open my $in, "<", File::Spec->catfile($self->_data_dir(), "na-cover.jpg");
    my $content = "";
    local $/;
    $content = <$in>;
    close($in);
    return $content;
}

sub _write_image
{
    my ($self, $contents) = @_;

    my $filename = $self->_img_fn();

    open my $out, ">", $filename
        or die "Could not open file '$filename'";
    print {$out} $contents;
    close ($out);
}

sub update_cover_images
{
    my ($self, $args) = @_;

    my $size = $args->{size};
    my $name_cb = $args->{name_cb};
    my $overwrite = $args->{overwrite};

    my $amazon_token = $args->{amazon_token};
    my @amazon_associate = 
        (
            (exists($args->{amazon_associate}) ? 
                (associate => $args->{amazon_associate},) :
                ()
            ),
            (exists($args->{amazon_sak}) ?
                (sak => $args->{amazon_sak},) :
                (),
            ),
        );

    my $dom = $self->_get_source_dom();

    my @products = $dom->findnodes('//prod');

    my $amazon =
        XML::Amazon->new(
            token => $amazon_token,
            @amazon_associate,
        );

    my $ua = LWP::UserAgent->new();

    PROD_LOOP:
    foreach my $prod (@products)
    {
        my ($asin_node) = $prod->findnodes('isbn');

        my $disable = $asin_node->getAttribute("disable");
        if (defined($disable) && ($disable eq "1"))
        {
            next PROD_LOOP;
        }

        my $asin = $asin_node->textContent();

        $self->_img_fn(
            $name_cb->(
                {
                    'xml_node' => $prod,
                    'id' => $prod->getAttribute("id"),
                    'isbn' => $asin,
                }
            )
        );
        
        if ($overwrite || (! -e $self->_img_fn()))
        {
            my $item = $amazon->asin($asin);

            my $image_url = $item->image($size);
            if (!defined($image_url))
            {
                $self->_write_image(
                    $self->_transform_image(
                        {
                            %$args,
                            'content' => 
                                $self->_get_not_available_cover_image_data(),
                        }
                    )
                );
            }
            else
            {
                my $response = $ua->get($image_url);
                if ($response->is_success)
                {
                    $self->_write_image(
                        $self->_transform_image(
                            {
                                %$args,
                                'content' => $response->content(),
                            },
                        ),
                    );
                }
                else
                {
                    die $response->status_line();
                }
            }
        }
    }
}

1; # End of XML::Grammar::ProductsSyndication