File Coverage

blib/lib/XML/Grammar/Fortune/Synd.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package XML::Grammar::Fortune::Synd;
2              
3 1     1   28105 use warnings;
  1         3  
  1         70  
4 1     1   6 use strict;
  1         2  
  1         39  
5              
6 1     1   33 use 5.008;
  1         9  
  1         889  
7              
8             =head1 NAME
9              
10             XML::Grammar::Fortune::Synd - Provides syndication for a set of
11             XML-Grammar-Fortune files.
12              
13             =head1 VERSION
14              
15             Version 0.0211
16              
17             =cut
18              
19             our $VERSION = '0.0211';
20              
21 1     1   8 use base 'Class::Accessor';
  1         2  
  1         1120  
22              
23 1     1   4069 use YAML::XS (qw( DumpFile LoadFile ));
  1         3137  
  1         60  
24 1     1   866 use Heap::Elem::Ref (qw(RefElem));
  1         1232  
  1         52  
25 1     1   809 use Heap::Binary;
  1         1835  
  1         31  
26 1     1   467 use XML::Feed;
  0            
  0            
27             use XML::Grammar::Fortune;
28             use DateTime::Format::W3CDTF;
29             use XML::Grammar::Fortune::Synd::Heap::Elem;
30              
31             use File::Spec;
32              
33             __PACKAGE__->mk_accessors(qw(
34             _xml_parser
35             _file_doms
36             _date_formatter
37             xml_files
38             url_callback
39             _file_processors
40             ));
41              
42             =head1 SYNOPSIS
43              
44             use XML::Grammar::Fortune::Synd;
45              
46             my $syndicator = XML::Grammar::Fortune::Synd->new();
47             ...
48              
49             =head1 FUNCTIONS
50              
51             =head2 my $syndicator = $class->new(\%args)
52              
53             Returns the new Syndicator.
54              
55             =cut
56              
57             sub new
58             {
59             my ($class, $args) = @_;
60              
61             my $self = $class->SUPER::new($args);
62              
63             $self->_xml_parser(XML::LibXML->new());
64             $self->_date_formatter(DateTime::Format::W3CDTF->new());
65             $self->_file_doms(+{});
66             $self->_file_processors(+{});
67              
68             return $self;
69             }
70              
71             =head2 $syndicator->calc_feeds(\%args)
72              
73             C<\%args> should be:
74              
75             {
76             yaml_persistence_file => "/path/to/yaml-persistence.yaml",
77             yaml_persistence_file_out => "/path/to/yaml-persistence.yaml",
78             xml_dirs => "/path/to/the/directory-containing-xmls",
79             feed_params =>
80             {
81             title => "My feed title",
82             link => "http://mysite.tld/",
83             tagline => "Feed tagline",
84             author => "john.doe@hello.tld (John Doe)"
85             atom_self_link => "http://mysite.tld/my-feed.atom",
86             rss_self_link => "http://mysite.tld/my-feed.rss",
87             }
88             }
89              
90             Returns:
91              
92             {
93             recent-ids => \@list_of_recent_ids,
94             feeds =>
95             {
96             Atom => $atom_XML_Feed_obj,
97             rss20 => $rss_XML_Feed_obj,
98             },
99             }
100              
101             =cut
102              
103             sub calc_feeds
104             {
105             my ($self, $args) = @_;
106              
107             my $scripts_hash_filename = $args->{'yaml_persistence_file'};
108             my $scripts_hash_fn_out = $args->{'yaml_persistence_file_out'};
109             my $xmls_dir = $args->{xmls_dir};
110              
111              
112             my $persistent_data;
113             if (-e $scripts_hash_filename)
114             {
115             $persistent_data = LoadFile ($scripts_hash_filename);
116             }
117             else
118             {
119             $persistent_data = +{};
120             }
121              
122             if (!exists($persistent_data->{'files'}))
123             {
124             $persistent_data->{'files'} = +{};
125             }
126              
127             my $scripts_hash = $persistent_data->{'files'};
128              
129             my $ids_heap = Heap::Binary->new();
130              
131             my $ids_heap_count = 0;
132              
133             my $ids_limit = 20;
134              
135             foreach my $file (@{$self->xml_files()})
136             {
137             my $xml = $self->_xml_parser->parse_file(
138             File::Spec->catfile($xmls_dir, $file)
139             );
140              
141             $self->_file_doms->{$file} = $xml;
142              
143             my @fortune_elems = $xml->findnodes("//fortune");
144              
145             my @ids = (map { $_->getAttribute("id") } @fortune_elems);
146              
147             my $id_count = 1;
148              
149             # Get rid of IDs in the hash refs that don't exist in the file,
150             # so we won't have globally duplicate IDs.
151             {
152             my $hash_ref = $scripts_hash->{$file};
153             my %ids_map = (map { $_ => 1 } @ids);
154              
155             foreach my $id (keys(%$hash_ref))
156             {
157             if (! exists($ids_map{$id}))
158             {
159             delete ($hash_ref->{$id});
160             }
161             }
162             }
163              
164             IDS_LOOP:
165             foreach my $id (@ids)
166             {
167             if (! exists($scripts_hash->{$file}->{$id}))
168             {
169             $scripts_hash->{$file}->{$id} =
170             {
171             'date' => $self->_date_formatter->format_datetime(
172             DateTime->now(),
173             ),
174             };
175             }
176              
177             my $date = $self->_date_formatter->parse_datetime(
178             $scripts_hash->{$file}->{$id}->{'date'},
179             );
180              
181             $ids_heap->add(
182             RefElem(
183             XML::Grammar::Fortune::Synd::Heap::Elem->new(
184             {
185             date => $date,
186             idx => $id_count,
187             id => $id,
188             file => $file,
189             }
190             )
191             )
192             );
193              
194             if (++$ids_heap_count > $ids_limit)
195             {
196             $ids_heap->extract_top();
197             $ids_heap_count--;
198             }
199             }
200             continue
201             {
202             $id_count++;
203             }
204             }
205              
206             my @recent_ids = ();
207              
208             # TODO : Should we reverse this?
209             while (defined(my $id_obj = $ids_heap->extract_top()))
210             {
211             push @recent_ids, $id_obj;
212             }
213             DumpFile($scripts_hash_fn_out, $persistent_data);
214              
215             my @feed_formats = (qw(Atom RSS));
216              
217             my %feeds = (map { $_ => XML::Feed->new($_), } @feed_formats);
218              
219             # First set some global parameters
220             foreach my $feed (values(%feeds))
221             {
222             $feed->title($args->{feed_params}->{'title'});
223             $feed->link($args->{feed_params}->{'link'});
224             $feed->tagline($args->{feed_params}->{'tagline'});
225             $feed->author($args->{feed_params}->{'author'});
226              
227             my $self_link = $args->{feed_params}->{'atom_self_link'};
228             $feed->self_link($self_link);
229             $feed->id($self_link);
230             }
231              
232             # Now fill the XML-Feed object:
233             {
234              
235             foreach my $id_obj (map { $_->val() } @recent_ids)
236             {
237             my $file_dom =
238             $self->_file_doms()->{$id_obj->file()};
239              
240             my ($fortune_dom) =
241             $file_dom->findnodes("descendant::fortune[\@id='". $id_obj->id() . "']");
242              
243             my %entries = (map { $_ => XML::Feed::Entry->new($_) } @feed_formats);
244              
245             my $title = $fortune_dom->findnodes("meta/title")->get_node(0)->textContent();
246              
247             my $on_entries = sub {
248              
249             my ($callback) = @_;
250              
251             foreach my $entry (values(%entries))
252             {
253             $callback->($entry);
254             }
255             };
256              
257             $on_entries->(sub {
258             my $entry = shift;
259              
260             $entry->title($title);
261             $entry->summary($title);
262             });
263              
264             my $url =
265             $self->url_callback()->(
266             $self,
267             {
268             id_obj => $id_obj,
269             }
270             );
271              
272             $on_entries->(sub {
273             my $entry = shift;
274              
275             $entry->link( $url );
276              
277             $entry->id($url);
278              
279             $entry->issued($id_obj->date());
280             });
281              
282             {
283             $self->_file_processors()->{$id_obj->file()} ||=
284             XML::Grammar::Fortune->new(
285             {
286             mode => "convert_to_html",
287             output_mode => "string",
288             }
289             );
290              
291             my $file_processor =
292             $self->_file_processors()->{$id_obj->file()};
293              
294             my $content = "";
295              
296             $file_processor->run(
297             {
298             xslt_params =>
299             {
300             'fortune.id' => "'" . $id_obj->id() . "'",
301             },
302             output => \$content,
303             input => File::Spec->catfile($xmls_dir, $id_obj->file()),
304             }
305             );
306              
307             $content =~ s{\A.*?}{}ms;
308             $content =~ s{.*\z}{}ms;
309              
310             $on_entries->(sub {
311             my $entry = shift;
312              
313             $entry->content(
314             XML::Feed::Content->new(
315             {
316             type => "text/html",
317             body => $content,
318             },
319             )
320             );
321              
322             });
323             }
324              
325             foreach my $format (@feed_formats)
326             {
327             $feeds{$format}->add_entry($entries{$format});
328             }
329             }
330             }
331              
332             $feeds{"RSS"}->self_link($args->{feed_params}->{'rss_self_link'});
333              
334             {
335             my $num_entries = scalar (() = $feeds{'RSS'}->entries());
336             if ($num_entries > $ids_limit)
337             {
338             die "Assert failed. $num_entries rather than the $ids_limit limit.";
339             }
340             }
341              
342             return
343             {
344             'recent_ids' => [reverse(@recent_ids)],
345             'feeds' =>
346             {
347             'Atom' => $feeds{"Atom"},
348             'rss20' => $feeds{"RSS"},
349             },
350             };
351             }
352              
353              
354             =head1 AUTHOR
355              
356             Shlomi Fish, C<< >>
357              
358             =head1 BUGS
359              
360             Please report any bugs or feature requests to C, or through
361             the web interface at L. I will be notified, and then you'll
362             automatically be notified of progress on your bug as I make changes.
363              
364              
365              
366              
367             =head1 SUPPORT
368              
369             You can find documentation for this module with the perldoc command.
370              
371             perldoc XML::Grammar::Fortune::Synd
372              
373              
374             You can also look for information at:
375              
376             =over 4
377              
378             =item * RT: CPAN's request tracker
379              
380             L
381              
382             =item * AnnoCPAN: Annotated CPAN documentation
383              
384             L
385              
386             =item * CPAN Ratings
387              
388             L
389              
390             =item * Search CPAN
391              
392             L
393              
394             =back
395              
396              
397             =head1 ACKNOWLEDGEMENTS
398              
399              
400             =head1 COPYRIGHT & LICENSE
401              
402             Copyright 2008 Shlomi Fish, all rights reserved.
403              
404             This program is released under the following license: MIT/X11 License
405              
406             http://www.opensource.org/licenses/mit-license.php
407              
408             =cut
409              
410             1; # End of XML::Grammar::Fortune::Synd