File Coverage

blib/lib/XML/APML.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package XML::APML;
2              
3 4     4   22 use strict;
  4         8  
  4         147  
4 4     4   21 use warnings;
  4         7  
  4         141  
5              
6 4     4   57 use 5.8.1;
  4         13  
  4         184  
7              
8 4     4   21 use base 'Class::Accessor::Fast';
  4         8  
  4         5693  
9              
10             __PACKAGE__->mk_accessors(qw/
11             version
12             title
13             generator
14             user_email
15             date_created
16             defaultprofile
17             /);
18              
19 4     4   17475 use XML::LibXML;
  0            
  0            
20              
21             use XML::APML::Profile;
22             use XML::APML::Application;
23              
24             use Carp ();
25              
26             our $VERSION = '0.04';
27              
28             use constant DEFAULT_NS => 'http://www.apml.org/apml-0.6';
29             use constant DEFAULT_VERSION => '0.6';
30              
31             my @HEAD_ELEMENTS = qw/title generator user_email date_created/;
32              
33             =head1 NAME
34              
35             XML::APML - APML parser/builder
36              
37             =head1 SYNOPSIS
38              
39             # parse APML
40              
41             use XML::APML;
42             use Perl6::Say;
43             use DateTime;
44             use DateTime::Format::W3CDTF;
45              
46             my $path = "/path/to/apml.xml";
47             my $apml = XML::APML->parse_file($path);
48              
49             my $fh = IO::File->open($path);
50             my $apml = XML::APML->parse_fh($fh);
51              
52             my $str = "...";
53             my $apml = XML::APML->parse_string($str);
54              
55             foreach my $profile ($apml->profiles) {
56              
57             my $implicit = $profile->implicit_data;
58              
59             foreach my $concept ($implicit->concepts) {
60             say $concept->key;
61             say $concept->value;
62             say $concept->from;
63             my $dt = DateTime::Format::W3CDTF->new->parse_datetime($concept->updated);
64             say $dt->year;
65             say $dt->month;
66             }
67              
68             foreach my $source ($implicit->sources) {
69              
70             say $source->key;
71             say $source->value;
72             say $source->name;
73             say $source->type;
74              
75             foreach my $author ($source->authors) {
76             say $author->key;
77             say $author->value;
78             say $author->from;
79             my $dt = DateTime::Format::W3CDTF->new->parse_datetime($author->updated);
80             say $dt->year;
81             say $dt->month;
82             }
83             }
84              
85             my $explicit = $profile->explicit_data;
86             # my $explicit = $profile->explicit;
87              
88             foreach my $concept ($explicit->concepts) {
89             my $key = $concept->key;
90             my $value = $concept->value;
91             }
92              
93             foreach my $source ($explicit->sources) {
94              
95             $source->key;
96             $source->value;
97             $source->name;
98             $source->type;
99              
100             foreach my $author ($source->authors) {
101             $author->key;
102             $author->value;
103             }
104              
105             }
106             }
107              
108             foreach my $application ($apml->applications) {
109             $application->name;
110             $application->elem;
111             }
112              
113             # build apml
114              
115             my $apml = XML::APML->new;
116             $apml->title('My Attention Profile');
117             $apml->generator('My Application');
118             $apml->user_email('example@example.com');
119             $apml->date_created( DateTime::Format::W3CDTF->new->format_datetime( DateTime->now ) );
120             $apml->defaultprofile("Home");
121              
122             # or you can set them at once
123             my $apml = XML::APML->new(
124             title => 'My Attention Profile',
125             generator => 'My Application',
126             user_email => 'example@example.org',
127             date_created => DateTime::Format::W3CDTF->new->format_datetime( DateTime->now ),
128             defaultprofile => 'Home',
129             );
130              
131             my $profile = XML::APML::Profile->new;
132             $profile->name("Home");
133              
134             $profile->explicit->add_concept( XML::APML::Concept->new(
135             key => 'music',
136             value => 0.5,
137             ) );
138             $profile->explicit->add_concept( XML::APML::Concept->new(
139             key => 'sports',
140             value => 0.9,
141             ) );
142              
143             $profile->explicit->add_source( XML::APML::Source->new(
144             key => 'http://feeds.feedburner.com/TechCrunch',
145             value => 0.4,
146             name => 'Techchunch',
147             type => 'application/rss+xml',
148             ) );
149              
150             $profile->implicit->add_concept( XML::APML::Concept->new(
151             key => 'business',
152             value => 0.93,
153             from => 'GatheringTool.com',
154             updated => '2007-03-11T01:55:00Z',
155             ) );
156              
157             $profile->implicit->add_source( XML::APML::Source->new(
158             key => 'http://feeds.feedburner.com/apmlspec',
159             value => 1.00,
160             from => 'GatheringTool.com',
161             updated => '2007-03-11T01:55:00Z',
162             name => 'APML.org',
163             type => 'application/rss+xml',
164             ) );
165              
166             my $source = XML::APML::Source->new(
167             key => 'http://feeds.feeedburner.com/TechCrunch',
168             value => 0.4,
169             name => 'Techchunch',
170             type => 'application/rss+xml',
171             );
172              
173             $source->add_author( XML::APML::Author->new(
174             key => 'Sample',
175             value => 0.5,
176             from => 'GatheringTool.com',
177             updated => '2007-03-11T01:55:00Z',
178             ) );
179              
180             $profile->implicit->add_source($source);
181              
182             $apml->add_profile($profile);
183              
184             my $application = XML::APML::Application->new;
185             $application->name("MyApplication");
186             $apml->add_application($application);
187              
188             print $apml->as_xml;
189              
190             =head1 DESCRIPTION
191              
192             APML (Attention Profiling Mark-up Language) Parser / Builder
193              
194             This module allows you to parse or build XML strings according to APML specification.
195             Now this supports version 0.6 of APML.
196              
197             See http://www.apml.org/
198              
199             =head1 METHODS
200              
201             =head2 new
202              
203             =cut
204              
205             sub new {
206             my $class = shift;
207             my $self = bless {
208             version => DEFAULT_VERSION,
209             title => '',
210             generator => join("/", __PACKAGE__, $VERSION),
211             user_email => '',
212             date_created => '',
213             defaultprofile => undef,
214             profiles => [],
215             applications => [],
216             }, $class;
217             $self->_init(@_);
218             $self;
219             }
220              
221             sub _init {
222             my ($self, %args) = @_;
223             for my $elem (@HEAD_ELEMENTS, qw/version defaultprofile/) {
224             $self->{$elem} = delete $args{$elem} if exists $args{$elem};
225             }
226             }
227              
228             =head2 parse_string
229              
230             =head2 parse_file
231              
232             =head2 parse_fh
233              
234             =cut
235              
236             sub parse_string {
237             my $class = shift;
238             my $doc = XML::LibXML->new->parse_string(@_);
239             $class->parse_dom($doc);
240             }
241              
242             sub parse_file {
243             my $class = shift;
244             my $doc = XML::LibXML->new->parse_file(@_);
245             $class->parse_dom($doc);
246             }
247              
248             sub parse_fh {
249             my $class = shift;
250             my $doc = XML::LibXML->new->parse_fh(@_);
251             $class->parse_dom($doc);
252             }
253              
254             sub parse_dom {
255             my ($class, $doc) = @_;
256             my $root = $doc->documentElement;
257             my $apml = $class->new;
258             my $version = $root->getAttribute('version');
259             $apml->version($version);
260             my $head = $root->getElementsByTagName('Head')->[0];
261             $class->_parse_head_elem($apml, $head, $_) for @HEAD_ELEMENTS;
262             my $defaultprofile = $root->findvalue('*[local-name()=\'Body\']/@defaultprofile');
263             $apml->defaultprofile($defaultprofile);
264             my @profiles = $root->findnodes('*[local-name()=\'Body\']/*[local-name()=\'Profile\']');
265             $apml->add_profile(XML::APML::Profile->parse_node($_)) for @profiles;
266             my @apps = $root->findnodes('*[local-name()=\'Body\']/*[local-name()=\'Applications\']/*[local-name()=\'Application\']');
267             $apml->add_application(XML::APML::Application->parse_node($_)) for @apps;
268             $apml;
269             }
270              
271             =head2 add_profile
272              
273             =cut
274              
275             sub add_profile {
276             my ($self, $profile) = @_;
277             push(@{ $self->{profiles} }, $profile);
278             }
279              
280             =head2 profiles
281              
282             =cut
283              
284             sub profiles {
285             my $self = shift;
286             $self->add_profile($_) for @_;
287             return wantarray ? @{ $self->{profiles} } : $self->{profiles};
288             }
289              
290             =head2 add_application
291              
292             =cut
293              
294             sub add_application {
295             my ($self, $application) = @_;
296             push(@{ $self->{applications} }, $application);
297             }
298              
299             =head2 applications
300              
301             =cut
302              
303             sub applications {
304             my $self = shift;
305             $self->add_application($_) for @_;
306             return wantarray ? @{ $self->{applications} } : $self->{applications};
307             }
308              
309             sub _parse_head_elem {
310             my ($class, $apml, $head, $elem) = @_;
311             my $elem_name = join("", map(ucfirst, split("_", $elem)));
312             my $e = $head->getElementsByTagName($elem_name)->[0];
313             if ($e && $e->string_value) {
314             $apml->$elem( $e->string_value );
315             } else {
316             warn "$elem is not found.";
317             }
318             }
319              
320             =head2 as_xml
321              
322             Build XML from object and returns it as string.
323              
324             my $apml = XML::APML->new;
325             $apml->title(...);
326             $apml->user_email(...);
327             ...
328             $apml->as_xml;
329              
330             =cut
331              
332             sub as_xml {
333             my $self = shift;
334             my $indent = shift || 1;
335             my $doc = XML::LibXML->createDocument('1.0', 'utf-8');
336             my $root = $doc->createElementNS(DEFAULT_NS, 'APML');
337             $root->setAttribute('version', $self->{version});
338             $doc->setDocumentElement($root);
339             my $head = $doc->createElement('Head');
340             $self->_set_header_element($doc, $head, $_) for @HEAD_ELEMENTS;
341             $root->appendChild($head);
342             my $body = $doc->createElement('Body');
343             my $defaultprofile = $self->_find_defaultprofile();
344             $body->setAttribute('defaultprofile', $defaultprofile);
345             Carp::croak "APML needs at least one profile." unless @{ $self->{profiles} } > 0;
346             for my $profile ( @{ $self->{profiles} } ) {
347             $body->appendChild($profile->build_dom($doc));
348             }
349             my $applications = $doc->createElement('Applications');
350             $body->appendChild($applications);
351             for my $application ( @{ $self->{applications} } ) {
352             $applications->appendChild($application->build_dom($doc));
353             }
354             $root->appendChild($body);
355             return $doc->toString($indent);
356             }
357              
358             sub _find_defaultprofile {
359             my $self = shift;
360             my $defaultprofile = $self->{defaultprofile};
361             if (!defined $defaultprofile || $defaultprofile eq '') {
362             my $first_profile = $self->{body}{profiles}[0]
363             or Carp::croak "APML needs at least one profile.";
364             $defaultprofile = $first_profile->name
365             or Carp::croak "Profile needs its name";
366             }
367             $defaultprofile;
368             }
369              
370             sub _set_header_element {
371             my ($self, $doc, $head, $elem) = @_;
372             my $elem_name = join("", map(ucfirst, split("_", $elem)));
373             my $e = $doc->createElement($elem_name);
374             my $value = $self->{$elem};
375             Carp::croak "Header element:$elem not found." unless (defined $value && $value ne '');
376             $e->appendText($self->{$elem});
377             $head->appendChild($e);
378             }
379              
380             1;
381             __END__