root/trunk/plagger/lib/Plagger/Plugin/Subscription/OPML.pm

Revision 630 (checked in by miyagawa, 15 years ago)

Subscription::OPML: support broken OPML feed. use XML::Liberal if installed

  • Property svn:keywords set to Id Revision
Line 
1 package Plagger::Plugin::Subscription::OPML;
2 use strict;
3 use base qw( Plagger::Plugin );
4
5 use Plagger::UserAgent;
6 use URI;
7 use XML::OPML;
8
9 our $HAS_LIBERAL;
10 BEGIN {
11     eval { require XML::Liberal; $HAS_LIBERAL = 1 };
12 }
13
14 sub register {
15     my($self, $context) = @_;
16
17     $context->register_hook(
18         $self,
19         'subscription.load' => \&load,
20     );
21 }
22
23 sub load {
24     my($self, $context) = @_;
25     my $uri = URI->new($self->conf->{url})
26         or $context->error("config 'url' is missing");
27
28     $self->load_opml($context, $uri);
29 }
30
31 sub load_opml {
32     my($self, $context, $uri) = @_;
33
34     my $xml;
35     if (ref($uri) eq 'SCALAR') {
36         $xml = $$uri;
37     }
38     elsif ($uri->scheme =~ /^https?$/) {
39         $context->log(debug => "Fetch remote OPML from $uri");
40
41         my $response = Plagger::UserAgent->new->fetch($uri, $self);
42         if ($response->is_error) {
43             $context->log(error => "GET $uri failed: " .
44                           $response->http_status . " " .
45                           $response->http_response->message);
46         }
47         $xml = $response->content;
48     }
49     elsif ($uri->scheme eq 'file') {
50         $context->log(debug => "Open local OPML file " . $uri->path);
51         open my $fh, '<', $uri->path
52             or $context->error( $uri->path . ": $!" );
53         $xml = join '', <$fh>;
54     }
55     else {
56         $context->error("Unsupported URI scheme: " . $uri->scheme);
57     }
58
59     if ($HAS_LIBERAL) {
60         my $parser = XML::Liberal->new('LibXML');
61         my $doc = $parser->parse_string($xml);
62         $xml = $doc->toString;
63     }
64
65     my $opml = XML::OPML->new;
66     $opml->parse($xml);
67     for my $outline (@{ $opml->outline }) {
68         $self->walk_down($outline, $context, 0, []);
69     }
70 }
71
72 sub walk_down {
73     my($self, $outline, $context, $depth, $containers) = @_;
74
75     if (delete $outline->{opmlvalue}) {
76         my $title = delete $outline->{title};
77         push @$containers, $title if $title ne 'Subscriptions';
78         for my $channel (values %$outline) {
79             $self->walk_down($channel, $context, $depth + 1, $containers);
80         }
81         pop @$containers if $title ne 'Subscriptions';
82     } else {
83         my $feed = Plagger::Feed->new;
84         $feed->url($outline->{xmlUrl});
85         $feed->link($outline->{htmlUrl});
86         $feed->title($outline->{title});
87         $feed->tags($containers);
88         $context->subscription->add($feed);
89     }
90 }
91
92 1;
93
94 __END__
95
96 =head1 NAME
97
98 Plagger::Plugin::Subscription::OPML - OPML subscription
99
100 =head1 SYNOPSIS
101
102   - module: Subscription::OPML
103     config:
104       url: http://example.com/mySubscriptions.opml
105
106 =head1 DESCRIPTION
107
108 This plugin creates Subscription by fetching remote OPML file by HTTP
109 or locally (with C<file://> URI). It supports nested folder structure
110 of OPML subscription.
111
112 =head1 AUTHOR
113
114 Tatsuhiko Miyagawa
115
116 =head1 SEE ALSO
117
118 L<Plagger>, L<XML::OPML>
119
120 =cut
Note: See TracBrowser for help on using the browser.