XML-Atom-SimpleFeed-0.86000755001750000144 011220211333 13724 5ustar00apusers000000000000Build.PL000444001750000144 70411220211333 15257 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86use strict; use warnings; use Module::Build; my $builder = Module::Build->new( module_name => 'XML::Atom::SimpleFeed', license => 'perl', dist_author => 'Aristotle Pagaltzis ', requires => { 'perl' => '5.8.1', }, build_requires => { 'Test::More' => 0, }, add_to_cleanup => [ 'XML-Atom-SimpleFeed-*' ], create_makefile_pl => 'traditional', ); $builder->create_build_script(); Makefile.PL000444001750000144 66511220211333 15743 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86# Note: this file was auto-generated by Module::Build::Compat version 0.33 require 5.8.1; use ExtUtils::MakeMaker; WriteMakefile ( 'NAME' => 'XML::Atom::SimpleFeed', 'VERSION_FROM' => 'lib/XML/Atom/SimpleFeed.pm', 'PREREQ_PM' => { 'Test::More' => 0 }, 'INSTALLDIRS' => 'site', 'EXE_FILES' => [], 'PL_FILES' => {} ) ; MANIFEST000444001750000144 24711220211333 15116 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86Build.PL Changes MANIFEST META.yml # Will be created by "make dist" README lib/XML/Atom/SimpleFeed.pm t/00.load.t t/01.xml_writer.t t/10.bugs.t Makefile.PL t/cpants.t README000444001750000144 103411220211333 14660 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86XML::Atom::SimpleFeed This module provides a minimal API for generating Atom syndication feeds quickly and easily. It supports all aspects of the Atom format, but it has no provisions for generating feeds with extension elements. INSTALLATION To install this module, run the following commands: perl Build.PL ./Build ./Build test ./Build install COPYRIGHT AND LICENCE Copyright (C) 2005, Aristotle Pagaltzis This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Changes000444001750000144 1244611220211333 15324 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86=head1 Revision history for XML-Atom-SimpleFeed =head2 0.86 (2009-06-23) =over 4 =item * Person constructs are properly escaped and encoded =back =head2 0.85 (2009-06-23) =over 4 =item * Used a less finicky implementation strategy for the CDATA flattener so hopefully it will not be buggy any more =back =head2 0.84 (2009-05-26) =over 4 =item * Added some basic XML writer tests, which uncovered a bug in the CDATA flattener, which is now fixed =back =head2 0.83 (2009-05-25) =over 4 =item * Thanks to JMASTROS for spotting another bug in the XML escaping function and contributing a test case =back =head2 0.82 (2008-06-21) =over 4 =item * I can't believe no one noticed in such a long time that the XML escaping function was broken. I need unit testsE =item * Also, the date in the changelog entry for 0.81 was wrong. =back =head2 0.81 (2008-06-21) =over 4 =item * Put private functions in XML::Atom::SimpleFeed::YeWhoEnters and placed methods in XML::Atom::SimpleFeed explicitly. This gets rid of approximately 734 prefix underscores. =item * It turns out L wasn't even necessary, L works that way by default. *blush* =item * More big POD cleanups (converted lots of list items to subheadings so they're linkable and listed in the TOC). =item * Throw out the pointless POD and POD coverage tests. =item * Automatically escape the content of the C, C, C, C, and C elements. Oops. (CPAN RT #36961) =back =head2 0.8 (2006-06-03) =over 4 =item * Multiple consecutive internal refactors; code structure is now actually satisfactory =item * Handles multiple authors and contributors =item * Support for icon and logo elements =item * Big POD cleanup =item * Use L to get rid of silly C<$Carp::CarpLevel> juggling =item * B: Elements such as C which may appear multiple times are no longer specified in an anonymous array, but simply given repeatedly. =item * B: Atom 0.3 element and attribute names are no longer supported. (No point keeping a lot of deprecation code around in the face of a change like the above.) =item * B: Suppressing the default C element requires calling the C method instead of passing a C key to C with an undefined value. =item * B: Well, since I'm at it, the C method is no longer supported. C now takes a handle, though. =item * Cleaned up errors and warning messages and got rid of DIAGNOSTICS section in POD =back =head2 0.8_004 (2006-05-10) =over 4 =item * Brownbag upload: forgot to update F in 0.8_003 =back =head2 0.8_003 (2006-05-10) =over 4 =item * Minor incremental progress; various bugfixes, some refactor. =back =head2 0.8_002 (2006-04-09) =over 4 =item * Use builtin XML writer instead of SAX for output. This eliminates huge amounts of redundancy. =item * Big improvements in the distribution of responsibilities for deprecation and validation checks. =item * Array-based implementation rather than inside-out objects. =item * Internal structure is now more logical and consistent. =back =head2 0.8_001 (2005-09-28) =over 4 =item * Emit Atom 1.0. Documentation updated to reflect Atom 1.0. Usage according to Atom 0.3 will transparently generate 1.0 elements but emit deprectation warnings. =item * Remove C<_generate_entry_id> and use HTTP URLs as IDs by default. Using tag: URIs is useful for generating the ID once, up front, so that it won't change even if the permalink does -- if the ID is generated from the permalink, we might as well use the permalink directly. =item * Use L instead of L for output. =back Change of maintainers: H. Wade Minter passes the module on to Aristotle Pagaltzis. =head2 0.7 (2005-05-06) =over 4 =item * Fix the module prereq in F to actually do the proper dependency tests. Oops. =back =head2 0.6 (2005-04-27) =over 4 =item * Use L to generate the XML, instead of doing strings by hand. =item * Make the module more robust: it now can handle multiple titles, links, etc. by passing in arrayrefs and hashrefs, while still allowing for simple use with strings. =back Many thanks to Aristotle Pagaltzis for the help. =head2 0.5 (2005-05-09) =over 4 =item * Add an C method to return the feed as a scalar. =item * Escape any C<< > or C<< ]]> >> strings that appear in the content CDATA section. =item * Allow the save_file method to take either an open filehandle or a scalar containing a filename. =back These issues reported by Aristotle Pagaltzis. =head2 0.4 (2005-02-22) =over 4 =item * Rework the author code again to fixe more bugs that I probably should have caught before releasing 0.3. Sigh. =back =head2 0.3 (2005-02-22) =over 4 =item * Fixed a bug in the entry author section where the author name was always showing up as "name". (CPAN RT #11620) =item * Fixed a similar bug in the feed-level author section. =item * Happy Birthday, Holly! =back =head2 0.2 (2005-02-19) =over 4 =item * Adjust the XML encoding to produce valid feeds. =back =head2 0.1 (2005-02-18) =over 4 =item * Initial release. =back =for vim vi:tw=72:ft=pod META.yml000444001750000144 75611220211333 15243 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86--- name: XML-Atom-SimpleFeed version: 0.86 author: - 'Aristotle Pagaltzis ' abstract: No-fuss generation of Atom syndication feeds license: perl resources: license: http://dev.perl.org/licenses/ requires: perl: 5.8.1 build_requires: Test::More: 0 provides: XML::Atom::SimpleFeed: file: lib/XML/Atom/SimpleFeed.pm version: 0.86 generated_by: Module::Build version 0.33 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 t000755001750000144 011220211333 14110 5ustar00apusers000000000000XML-Atom-SimpleFeed-0.8600.load.t000444001750000144 20611220211333 15545 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86/tuse Test::More tests => 1; require_ok( 'XML::Atom::SimpleFeed' ) or BAIL_OUT( 'testing pointless if the module won\'t even load' ); 01.xml_writer.t000444001750000144 214111220211333 17043 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86/t#!/usr/bin/perl use warnings; use strict; use XML::Atom::SimpleFeed; package XML::Atom::SimpleFeed; use Test::More tests => 7; is xml_escape( $_ = qq(<\x{FF34}\x{FF25}\x{FF33}\x{FF34} "&' d\xE3t\xE3>) ), qq(<TEST "&' dãtã>), 'XML tag content is properly encoded'; is xml_attr_escape( $_ = qq(<\x{FF34}\x{FF25}\x{FF33}\x{FF34}\n"&'\rd\xE3t\xE3>) ), qq(<TEST "&' dãtã>), 'XML attribute content is properly encoded'; is xml_string( $_ = qq(Test ]]> sections) ), qq(Test <CDATA> sections), 'CDATA sections are properly flattened'; is xml_tag( 'br' ), '
', 'simple tags are self-closed'; is xml_tag( 'b', 'foo', '
' ), 'foo
', 'tags with content are properly formed'; is xml_tag( [ 'br', clear => 'left' ] ), '
', 'simple tags can have attributes'; is xml_tag( [ 'b', style => 'color: red' ], 'foo', '
' ), 'foo
', 'simple tags can have attributes'; 10.bugs.t000444001750000144 61211220211333 15570 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86/t#!/usr/bin/perl use warnings; use strict; use XML::Atom::SimpleFeed; package XML::Atom::SimpleFeed; use Test::More tests => 2; is person_construct( author => 'John Doe &' ), 'John Doe &', 'author names are escaped'; is person_construct( author => "John Doe \x{263A}" ), 'John Doe ☺', 'non-ASCII author names are encoded'; cpants.t000444001750000144 15711220211333 15705 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86/tuse Test::More skip_all => 'This is just to fob CPANTS'; eval 'use Test::Pod::Coverage'; eval 'use Test::Pod'; lib000755001750000144 011220211333 14413 5ustar00apusers000000000000XML-Atom-SimpleFeed-0.86XML000755001750000144 011220211333 15053 5ustar00apusers000000000000XML-Atom-SimpleFeed-0.86/libAtom000755001750000144 011220211333 15753 5ustar00apusers000000000000XML-Atom-SimpleFeed-0.86/lib/XMLSimpleFeed.pm000444001750000144 5505311220211333 20513 0ustar00apusers000000000000XML-Atom-SimpleFeed-0.86/lib/XML/Atom#!/usr/bin/perl require 5.008001; # no good Unicode support? you lose package XML::Atom::SimpleFeed; $VERSION = "0.86"; use warnings FATAL => 'all'; use strict; use Carp; use Encode (); use POSIX (); sub ATOM_NS () { 'http://www.w3.org/2005/Atom' } sub XHTML_NS () { 'http://www.w3.org/1999/xhtml' } sub PREAMBLE () { qq(\n) } sub W3C_DATETIME () { '%Y-%m-%dT%H:%M:%SZ' } sub DEFAULT_GENERATOR () { { uri => 'http://search.cpan.org/dist/' . join( '-', split /::/, __PACKAGE__ ) . '/', version => __PACKAGE__->VERSION, name => __PACKAGE__, } } #################################################################### # superminimal XML writer # my %XML_ESC = ( "\xA" => ' ', "\xD" => ' ', '"' => '"', '&' => '&', "'" => ''', '<' => '<', '>' => '>', ); sub xml_cref { Encode::encode 'us-ascii', $_[ 0 ], Encode::HTMLCREF } sub xml_escape { $_[ 0 ] =~ s{ ( [<>&'"] ) }{ $XML_ESC{ $1 } }gex; goto &xml_cref; } sub xml_attr_escape { $_[ 0 ] =~ s{ ( [\x0A\x0D<>&'"] ) }{ $XML_ESC{ $1 } }gex; goto &xml_cref; } sub xml_cdata_flatten { for ( $_[0] ) { my $cdata_content; s{}{ xml_escape $cdata_content = $1 }ge; croak 'Incomplete CDATA section' if -1 < index $_, '[ $i ] . '="' . xml_attr_escape( $name->[ $i + 1 ] ) . '"'; $i += 2; } $name = $name->[ 0 ]; } @_ ? join( '', "<$name$attr>", @_, "" ) : "<$name$attr/>"; } #################################################################### # misc utility functions # sub natural_enum { my @and; unshift @and, pop @_ if @_; unshift @and, join ', ', @_ if @_; join ' and ', @and; } sub permalink { my ( $link_arg ) = ( @_ ); if( ref $link_arg ne 'HASH' ) { return $link_arg; } elsif( not exists $link_arg->{ rel } or $link_arg->{ rel } eq 'alternate' ) { return $link_arg->{ href }; } return; } #################################################################### # actual implementation of RFC 4287 # sub simple_construct { my ( $name, $content ) = @_; xml_tag $name, xml_escape $content; } sub person_construct { my ( $name, $arg ) = @_; my $prop = 'HASH' ne ref $arg ? { name => $arg } : $arg; croak "name required for $name element" if not exists $prop->{ name }; return xml_tag $name => ( map { xml_tag $_ => xml_escape $prop->{ $_ } } grep { exists $prop->{ $_ } } qw( name email uri ) ); } sub text_construct { my ( $name, $arg ) = @_; my ( $type, $content ); if( ref $arg eq 'HASH' ) { # FIXME doesn't support @src attribute for $name eq 'content' yet $type = exists $arg->{ type } ? $arg->{ type } : 'html'; croak "content required for $name element" unless exists $arg->{ content }; # a lof of the effort that follows is to omit the type attribute whenever possible # if( $type eq 'xhtml' ) { $content = xml_string $arg->{ content }; if( $content !~ / xmlns => XHTML_NS ], $content; } } elsif( $type eq 'html' or $type eq 'text' ) { $content = xml_escape $arg->{ content }; } else { croak "type '$type' not allowed in $name element" if $name ne 'content'; # FIXME non-XML/text media types must be base64 encoded! $content = xml_string $arg->{ content }; } } else { $type = 'html'; $content = xml_escape $arg; } if( $type eq 'html' and $content !~ /&/ ) { $type = 'text'; $content =~ s/[\n\t]+/ /g; } return xml_tag [ $name => $type ne 'text' ? ( type => $type ) : () ], $content; } sub empty_tag_maker { my ( $required_attr, @optional_attr ) = @_; sub { my ( $name, $arg ) = @_; if ( $name eq 'link' ) { # HACK: no other simple tag needs similar features so it's easiest # to hack them in here instead of providing an indirection # croak "link '$arg->{ href }' is not a valid URI" # if $arg->{ href } XXX TODO # omit atom:link/@rel value when possible delete $arg->{ rel } if ref $arg eq 'HASH' and exists $arg->{ rel } and $arg->{ rel } eq 'alternate'; } if( ref $arg eq 'HASH' ) { croak "$required_attr required for $name element" if not exists $arg->{ $required_attr }; my @attr = map { $_ => $arg->{ $_ } } grep exists $arg->{ $_ }, $required_attr, @optional_attr; xml_tag [ $name => @attr ]; } else { xml_tag [ $name => $required_attr => $arg ]; } } } # tag makers are called with the name of the tag they're supposed to handle as the first parameter my %make_tag = ( icon => \&simple_construct, id => \&simple_construct, logo => \&simple_construct, published => \&simple_construct, updated => \&simple_construct, author => \&person_construct, contributor => \&person_construct, title => \&text_construct, subtitle => \&text_construct, rights => \&text_construct, summary => \&text_construct, content => \&text_construct, link => empty_tag_maker( qw( href rel type title hreflang length ) ), category => empty_tag_maker( qw( term scheme label ) ), generator => sub { my ( $name, $arg ) = @_; if( ref $arg eq 'HASH' ) { croak 'name required for generator element' if not exists $arg->{ name }; my $content = delete $arg->{ name }; xml_tag [ generator => map +( $_ => $arg->{ $_ } ), grep exists $arg->{ $_ }, qw( uri version ) ], xml_escape( $content ); } else { xml_tag generator => xml_escape( $arg ); } }, ); sub container_content { my ( $name, %arg ) = @_; my ( $elements, $required, $optional, $singular, $deprecation, $callback ) = @arg{ qw( elements required optional singular deprecate callback ) }; my ( $content, %permission, %count, $permalink ); undef @permission{ @$required, @$optional }; # populate while( my ( $elem, $arg ) = splice @$elements, 0, 2 ) { if( exists $permission{ $elem } ) { $content .= $make_tag{ $elem }->( $elem, $arg ); ++$count{ $elem }; } else { croak "Unknown element $elem"; } if( $elem eq 'link' and defined ( my $alt = permalink $arg ) ) { $permalink = $alt unless $count{ 'alternate link' }++; } if( exists $callback->{ $elem } ) { $callback->{ $elem }->( $arg ) } if( not @$elements ) { # end of input? # we would normally fall off the bottom of the loop now; # before that happens, it's time to defaultify stuff and # put it in the input so we will keep going for a little longer if( not $count{ id } and defined $permalink ) { carp 'Falling back to alternate link as id'; push @$elements, id => $permalink; } if( not $count{ updated } ) { push @$elements, updated => $arg{ default_upd }; } } } my @error; my @missing = grep { not exists $count{ $_ } } @$required; my @toomany = grep { ( $count{ $_ } || 0 ) > 1 } 'alternate link', @$singular; push @error, 'requires at least one ' . natural_enum( @missing ) . ' element' if @missing; push @error, 'must have no more than one ' . natural_enum( @toomany ) . ' element' if @toomany; croak $name, ' ', join ' and ', @error if @error; return $content; } #################################################################### # implementation of published interface and rest of RFC 4287 # sub XML::Atom::SimpleFeed::new { my $self = bless {}, shift; my $arg = ( @_ and 'HASH' eq ref $_[0] ) ? shift : {}; $self->{ do_add_generator } = 1; $self->feed( @_ ) if @_; # support old-style invocation return $self; } sub XML::Atom::SimpleFeed::feed { my $self = shift; $self->{ meta } = container_content feed => ( elements => \@_, required => [ qw( id title updated ) ], optional => [ qw( author category contributor generator icon logo link rights subtitle ) ], singular => [ qw( generator icon logo id rights subtitle title updated ) ], callback => { author => sub { $self->{ have_default_author } = 1 }, updated => sub { $self->{ global_updated } = $_[ 0 ] }, generator => sub { $self->{ do_add_generator } = 0 }, }, default_upd => POSIX::strftime( W3C_DATETIME, gmtime ), ); return $self; } sub XML::Atom::SimpleFeed::add_entry { my $self = shift; my @required = qw( id title updated ); my @optional = qw( category content contributor link published rights summary ); push @{ $self->{ have_default_author } ? \@optional : \@required }, 'author'; # FIXME # # o atom:entry elements that contain no child atom:content element # MUST contain at least one atom:link element with a rel attribute # value of "alternate". # # o atom:entry elements MUST contain an atom:summary element in either # of the following cases: # * the atom:entry contains an atom:content that has a "src" # attribute (and is thus empty). # * the atom:entry contains content that is encoded in Base64; # i.e., the "type" attribute of atom:content is a MIME media type # [MIMEREG], but is not an XML media type [RFC3023], does not # begin with "text/", and does not end with "/xml" or "+xml". push @{ $self->{ entries } }, xml_tag entry => container_content entry => ( elements => \@_, required => \@required, optional => \@optional, singular => [ qw( content id published rights summary ) ], default_upd => $self->{ global_updated }, ); return $self; } sub XML::Atom::SimpleFeed::no_generator { my $self = shift; $self->{ do_add_generator } = 0; return $self; } sub XML::Atom::SimpleFeed::as_string { my $self = shift; if( $self->{ do_add_generator } ) { $self->{ meta } .= $make_tag{ generator }->( generator => DEFAULT_GENERATOR ); $self->{ do_add_generator } = 0; } PREAMBLE . xml_tag [ feed => xmlns => ATOM_NS ], $self->{ meta }, @{ $self->{ entries } }; } sub XML::Atom::SimpleFeed::print { my $self = shift; my ( $handle ) = @_; local $, = local $\ = ''; defined $handle ? print $handle $self->as_string : print $self->as_string; } sub XML::Atom::SimpleFeed::save_file { croak q{no longer supported, use 'print' instead and pass in a filehandle} } ! ! 'Funky and proud of it.'; __END__ =head1 NAME XML::Atom::SimpleFeed - No-fuss generation of Atom syndication feeds =head1 VERSION This document describes XML::Atom::SimpleFeed version 0.84 =head1 SYNOPSIS use XML::Atom::SimpleFeed; my $feed = XML::Atom::SimpleFeed->new( title => 'Example Feed', link => 'http://example.org/', link => { rel => 'self', href => 'http://example.org/atom', }, updated => '2003-12-13T18:30:02Z', author => 'John Doe', id => 'urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6', ); $feed->add_entry( title => 'Atom-Powered Robots Run Amok', link => 'http://example.org/2003/12/13/atom03', id => 'urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', summary => 'Some text.', updated => '2003-12-13T18:30:02Z', category => 'Atom', category => 'Miscellaneous', ); $feed->print; =head1 DESCRIPTION This module provides a minimal API for generating Atom syndication feeds quickly and easily. It supports all aspects of the Atom format, but has no provisions for generating feeds with extension elements. You can supply strings for most things, and the module will provide useful defaults. When you want more control, you can provide data structures, as documented, to specify more particulars. =head1 INTERFACE =head2 C XML::Atom::SimpleFeed instances are created by the C constructor, which takes a list of key-value pairs as parameters. The keys are used to create the corresponding L<"Atom elements"|/ATOM ELEMENTS>. The following elements are available: =over =item * L> (I) =item * L> (I, multiple) =item * L> (B) =item * L> (optional, multiple) =item * L> (optional, multiple) =item * L> (optional, multiple) =item * L> (optional) =item * L> (optional) =item * L> (optional) =item * L> (optional) =item * L> (optional) =item * L> (optional) =back To specify multiple instances of an element that may be given multiple times, simply list multiple key-value pairs with the same key. =head2 C This method adds an entry into the Atom feed. It takes a list of key-value pairs as parameters. The keys are used to create the corresponding L<"Atom Elements"|/ATOM ELEMENTS>. The following elements are available: =over =item * L> (B unless there is a feed-level author, multiple) =item * L> (I) =item * L> (B, multiple) =item * L> (B) =item * L> (optional, multiple) =item * L> (optional) =item * L> (optional, multiple) =item * L> (optional) =item * L> (optional) =item * L> (optional) =item * L> (optional) =back To specify multiple instances of an element that may be given multiple times, simply list multiple key-value pairs with the same key. =head2 C Suppresses the output of a default C element. It is not necessary to call this method if you supply a custom C element. =head2 C Returns the XML representation of the feed as a string. =head2 C Outputs the XML representation of the feed to a handle which should be passed as a parameter. Defaults to C if you do not pass a handle. =head1 ATOM ELEMENTS =head2 C A L denoting the author of the feed or entry. If you supply at least one author for the feed, you can omit this information from entries; the feed's author(s) will be assumed as the author(s) for those entries. If you do not supply any author for the feed, you B supply one for each entry. =head2 C One or more categories that apply to the feed or entry. You can supply a string which will be used as the category term. The full range of details that can be provided by passing a hash instead of a string is as follows: =over =item C (B) The category term. =item C (optional) A URI that identifies a categorization scheme. It is common to provide the base of some kind of by-category URL here. F.ex., if the weblog C can be browsed by category using URLs such as C, you would supply C as the scheme and, in that case, C as the term. =item C