File Coverage

blib/lib/XML/OPML.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             # $Id: OPML.pm,v 0.26 2004/03/06 09:19:00 szul Exp $
2             package XML::OPML;
3              
4 1     1   22660 use strict;
  1         2  
  1         37  
5 1     1   5 use Carp;
  1         2  
  1         86  
6 1     1   469 use XML::Parser;
  0            
  0            
7             use XML::SimpleObject;
8             use Fcntl qw(:DEFAULT :flock);
9             use vars qw($VERSION $AUTOLOAD @ISA $modules $AUTO_ADD);
10              
11             $VERSION = '0.26';
12              
13             $AUTO_ADD = 0;
14              
15             my %opml_fields = (
16             head => {
17             title => '',
18             dateCreated => '',
19             dateModified => '',
20             ownerName => '',
21             ownerEmail => '',
22             expansionState => '',
23             vertScrollState => '',
24             windowTop => '',
25             windowLeft => '',
26             windowBottom => '',
27             windowRight => ''
28             },
29             body => {
30             outline => [],
31             },
32             );
33              
34             sub new {
35             my $class = shift;
36             my $self = {};
37             bless $self, $class;
38             $self->_initialize(@_);
39             return $self;
40             }
41              
42             sub _initialize {
43             my $self = shift;
44             my %hash = @_;
45              
46             # internal hash
47             $self->{_internal} = {};
48              
49             # initialize number of outlines to 0
50             $self->{num_items} = 0;
51              
52             # initialize outlines
53             $self->{outline} = [];
54              
55             # encode output from as_string?
56             (exists($hash{encode_output}))
57             ? ($self->{encode_output} = $hash{encode_output})
58             : ($self->{encode_output} = 1);
59              
60             # get version information
61             (exists($hash{version}))
62             ? ($self->{version} = $hash{version})
63             : ($self->{version} = '1.0');
64              
65             # set default output
66             (exists($hash{output}))
67             ? ($self->{output} = $hash{output})
68             : ($self->{output} = "");
69              
70             # encoding
71             (exists($hash{encoding}))
72             ? ($self->{encoding} = $hash{encoding})
73             : ($self->{encoding} = 'UTF-8');
74              
75             # opml version 1.1 -- version 1.0 not supported
76             if ($self->{version} eq '1.1') {
77             foreach my $i (qw(head body)) {
78             my %template = %{$opml_fields{$i}};
79             $self->{$i} = \%template;
80             }
81             }
82             }
83              
84             sub add_outline {
85             my $self = shift;
86             my $hash = {@_};
87             push (@{$self->{outline}}, $hash);
88             return $self->{outline};
89             }
90              
91             sub insert_outline {
92             my $self = shift;
93             my $hash = {@_};
94             $self->{group} = $hash->{group};
95             delete($hash->{group});
96             $self->{add_on} = $hash;
97             }
98              
99             sub as_opml_1_1 {
100             my $self = shift;
101             my $output;
102              
103             # XML declaration
104             $output .= '{encoding}.'"?>'."\n";
105              
106             # DOCTYPE: No official DocType for version 1.1
107              
108             # OPML root element
109             $output .= ''."\n";
110              
111             ################
112             # Head Element #
113             ################
114             $output .= ''."\n";
115             $output .= ''. $self->encode($self->{head}->{title}) .''."\n";
116             $output .= ''. $self->encode($self->{head}->{dateCreated}) .''."\n";
117             $output .= ''. $self->encode($self->{head}->{dateModified}) .''."\n";
118             $output .= ''. $self->encode($self->{head}->{ownerName}) .''."\n";
119             $output .= ''. $self->encode($self->{head}->{ownerEmail}) .''."\n";
120             $output .= ''. $self->encode($self->{head}->{expansionState}) .''."\n";
121             $output .= ''. $self->encode($self->{head}->{vertScrollState}) .''."\n";
122             $output .= ''. $self->encode($self->{head}->{windowTop}) .''."\n";
123             $output .= ''. $self->encode($self->{head}->{windowLeft}) .''."\n";
124             $output .= ''. $self->encode($self->{head}->{windowBottom}) .''."\n";
125             $output .= ''. $self->encode($self->{head}->{windowRight}) .''."\n";
126             $output .= '' . "\n";
127             $output .= '' . "\n";
128              
129             ###################
130             # outline element #
131             ###################
132              
133             foreach my $outline (@{$self->{outline}}) {
134             if(($outline->{opmlvalue}) && ($outline->{opmlvalue} eq "embed")) {
135             my $embed_text = "";
136             $embed_text .= "dateAdded=\"$outline->{dateAdded}\" " if($outline->{dateAdded});
137             $embed_text .= "date_added=\"$outline->{date_added}\" " if($outline->{date_added});
138             $embed_text .= "dateDownloaded=\"$outline->{dateDownloaded}\" " if($outline->{dateDownloaded});
139             $embed_text .= "date_downloaded=\"$outline->{date_downloaded}\" " if($outline->{date_downloaded});
140             $embed_text .= "description=\"$outline->{description}\" " if($outline->{description});
141             $embed_text .= "email=\"$outline->{email}\" " if($outline->{email});
142             $embed_text .= "filename=\"$outline->{filename}\" " if($outline->{filename});
143             $embed_text .= "htmlUrl=\"$outline->{htmlUrl}\" " if($outline->{htmlUrl});
144             $embed_text .= "htmlurl=\"$outline->{htmlurl}\" " if($outline->{htmlurl});
145             $embed_text .= "keywords=\"$outline->{keywords}\" " if($outline->{keywords});
146             $embed_text .= "text=\"$outline->{text}\" " if($outline->{text});
147             $embed_text .= "title=\"$outline->{title}\" " if($outline->{title});
148             $embed_text .= "type=\"$outline->{type}\" " if($outline->{type});
149             $embed_text .= "version=\"$outline->{version}\" " if($outline->{version});
150             $embed_text .= "xmlUrl=\"$outline->{xmlUrl}\" " if($outline->{xmlUrl});
151             $embed_text .= "xmlurl=\"$outline->{xmlurl}\" " if($outline->{xmlurl});
152             if($embed_text eq "") {
153             $output .= "\n";
154             }
155             else {
156             $output .= "\n";
157             if(($self->{group}) && ($outline->{text} eq "$self->{group}")) {
158             $outline->{time()} = $self->{add_on};
159             }
160             }
161             $output .= return_embedded($self, $outline);
162             $output .= "\n";
163             next;
164             }
165             $output .= "
166             foreach my $atts (sort {$a cmp $b} keys %{$outline}) {
167             $output .= "$atts=\"" . $self->encode($outline->{$atts}) . "\" ";
168             }
169             $output .= " />";
170             $output .= "\n";
171             }
172             $output .= '' . "\n";
173             $output .= '' . "\n";
174              
175             return $output;
176             }
177              
178             # Global array for capturing embedded outlines
179             my @return_values = ();
180              
181             # Recurse down the outline elements to build proper tree structure
182             sub return_embedded {
183             my ($self, $outline) = @_;
184             foreach my $inner_out (keys %{$outline}) {
185             next if($inner_out eq "opmlvalue");
186             next if($inner_out eq "dateAdded");
187             next if($inner_out eq "date_added");
188             next if($inner_out eq "dateDownloaded");
189             next if($inner_out eq "date_downloaded");
190             next if($inner_out eq "description");
191             next if($inner_out eq "email");
192             next if($inner_out eq "filename");
193             next if($inner_out eq "htmlUrl");
194             next if($inner_out eq "htmlurl");
195             next if($inner_out eq "keywords");
196             next if($inner_out eq "text");
197             next if($inner_out eq "title");
198             next if($inner_out eq "type");
199             next if($inner_out eq "version");
200             next if($inner_out eq "xmlUrl");
201             next if($inner_out eq "xmlurl");
202             if(($outline->{$inner_out}->{'opmlvalue'}) && ($outline->{$inner_out}->{'opmlvalue'} eq "embed")) {
203             my @elems = keys(%{$outline->{$inner_out}});
204             my $pop_num = scalar(@elems);
205             foreach my $elems (@elems) {
206             $pop_num-- if(($elems eq "opmlvalue") || ($elems eq "dateAdded") || ($elems eq "date_added") || ($elems eq "dateDownloaded") || ($elems eq "date_downloaded") || ($elems eq "description") || ($elems eq "email") || ($elems eq "filename") || ($elems eq "htmlUrl") || ($elems eq "htmlurl") || ($elems eq "keywords") || ($elems eq "text") || ($elems eq "title") || ($elems eq "type") || ($elems eq "version") || ($elems eq "xmlUrl") || ($elems eq "xmlurl"));
207             }
208             $pop_num = 1 if($pop_num == 0);
209             my $return_output = "";
210             my $embed_text = "";
211             $embed_text .= "dateAdded=\"$outline->{$inner_out}->{dateAdded}\" " if($outline->{$inner_out}->{dateAdded});
212             $embed_text .= "date_added=\"$outline->{$inner_out}->{date_added}\" " if($outline->{$inner_out}->{date_added});
213             $embed_text .= "dateDownloaded=\"$outline->{$inner_out}->{dateDownloaded}\" " if($outline->{$inner_out}->{dateDownloaded});
214             $embed_text .= "date_downloaded=\"$outline->{$inner_out}->{date_downloaded}\" " if($outline->{$inner_out}->{date_downloaded});
215             $embed_text .= "description=\"$outline->{$inner_out}->{description}\" " if($outline->{$inner_out}->{description});
216             $embed_text .= "email=\"$outline->{$inner_out}->{email}\" " if($outline->{$inner_out}->{email});
217             $embed_text .= "filename=\"$outline->{$inner_out}->{filename}\" " if($outline->{$inner_out}->{filename});
218             $embed_text .= "htmlUrl=\"$outline->{$inner_out}->{htmlUrl}\" " if($outline->{$inner_out}->{hmtlUrl});
219             $embed_text .= "htmlurl=\"$outline->{$inner_out}->{htmlurl}\" " if($outline->{$inner_out}->{htmlurl});
220             $embed_text .= "keywords=\"$outline->{$inner_out}->{keywords}\" " if($outline->{$inner_out}->{keywords});
221             $embed_text .= "text=\"$outline->{$inner_out}->{text}\" " if($outline->{$inner_out}->{text});
222             $embed_text .= "title=\"$outline->{$inner_out}->{title}\" " if($outline->{$inner_out}->{title});
223             $embed_text .= "type=\"$outline->{$inner_out}->{type}\" " if($outline->{$inner_out}->{type});
224             $embed_text .= "version=\"$outline->{$inner_out}->{version}\" " if($outline->{$inner_out}->{version});
225             $embed_text .= "xmlUrl=\"$outline->{$inner_out}->{xmlUrl}\" " if($outline->{$inner_out}->{xmlUrl});
226             $embed_text .= "xmlurl=\"$outline->{$inner_out}->{xmlurl}\" " if($outline->{$inner_out}->{xmlurl});
227             if($embed_text eq "") {
228             $return_output .= "\n";
229             }
230             else {
231             $return_output .= "\n";
232             if(($self->{group}) && ($outline->{$inner_out}->{text} eq "$self->{group}")) {
233             $outline->{$inner_out}->{time()} = $self->{add_on};
234             }
235             }
236             return_embedded($self, $outline->{$inner_out});
237             while($pop_num > 0) {
238             $return_output .= pop(@return_values);
239             $pop_num--;
240             }
241             $return_output .= "\n";
242             push(@return_values, $return_output);
243             next;
244             }
245             else {
246             my $return_output = "";
247             $return_output .= "
248             foreach my $atts (sort {$a cmp $b} keys %{$outline->{$inner_out}}) {
249             $return_output .= "$atts=\"" . $self->encode($outline->{$inner_out}->{$atts}) . "\" ";
250             }
251             $return_output .= " />\n";
252             push(@return_values, $return_output);
253             }
254             }
255             my $return_value = join('', @return_values);
256             return $return_value;
257             }
258              
259             sub as_string {
260             my $self = shift;
261             my $version = ($self->{output} =~ /\d/) ? $self->{output} : $self->{version};
262             my $output;
263             $output = &as_opml_1_1($self);
264             return $output;
265             }
266              
267             sub save {
268             my ($self,$file) = @_;
269             open(OUT,">$file") || croak "Cannot open file $file for write: $!";
270             flock(OUT, LOCK_EX);
271             print OUT $self->as_string();
272             flock(OUT, LOCK_UN);
273             close OUT;
274             }
275              
276             # Parser the OPML with XML::Parser and XML::SimpleObject to add additional outlines.
277              
278             sub parse {
279             my $self = shift;
280             my $content = shift;
281             $self->_initialize((%$self));
282             @return_values = ();
283             my $xmlobj;
284             my $bool;
285             eval {
286             $bool = "true" if(-e $content);
287             };
288             if($bool) {
289             my $parser = XML::Parser->new(ErrorContext => 2, Style => "Tree");
290             $xmlobj = XML::SimpleObject->new($parser->parsefile($content));
291             }
292             else {
293             my $parser = XML::Parser->new(ErrorContext => 2, Style => "Tree");
294             $xmlobj = XML::SimpleObject->new($parser->parse($content));
295             }
296             my $head = $xmlobj->child('opml')->child('head');
297             my @head_children = $head->children();
298             my $head_hash = {};
299             foreach my $head_child (@head_children) {
300             my $elem_value = $head_child->value() || "";
301             $head_hash->{$head_child->name()} = $elem_value;
302             }
303             my $body = $xmlobj->child('opml')->child('body');
304             my @outlines = $body->children('outline');
305             my $outline_list = [];
306             foreach my $outlines (@outlines) {
307             my $outline_holder = {};
308             my %atts = $outlines->attributes();
309             foreach my $atts (keys(%atts)) {
310             $outline_holder->{$atts} = $outlines->attribute($atts);
311             }
312             unless($outlines->children()) {
313             push(@{$outline_list}, $outline_holder);
314             }
315             else {
316             my $new_hash = return_outlines($outline_holder, $outlines);
317             push(@{$outline_list}, $new_hash);
318             }
319             }
320             $self->{head} = $head_hash;
321             $self->{outline} = $outline_list;
322             }
323              
324             sub return_outlines {
325             my($outline_holder, $outlines) = @_;
326             $outline_holder->{'opmlvalue'} = 'embed';
327             my @outlines = $outlines->children('outline');
328             my $out_count = 0;
329             foreach my $outs (@outlines) {
330             $out_count++;
331             my $new_outline_holder = {};
332             my %atts = $outs->attributes();
333             foreach my $atts (keys(%atts)) {
334             $new_outline_holder->{$atts} = $outs->attribute($atts);
335             }
336             my $var_name = time() . $out_count;
337             unless($outs->children()) {
338             $outline_holder->{$var_name} = $new_outline_holder;
339             }
340             else {
341             my $new_hash = return_outlines($new_outline_holder, $outs);
342             $outline_holder->{$var_name} = $new_hash;
343             }
344             }
345             return $outline_holder;
346             }
347              
348             sub strict {
349             my ($self,$value) = @_;
350             $self->{'strict'} = $value;
351             }
352              
353             sub AUTOLOAD {
354             my $self = shift;
355             my $type = ref($self) || croak "$self is not an object\n";
356             my $name = $AUTOLOAD;
357             $name =~ s/.*://;
358             return if $name eq 'DESTROY';
359              
360             croak "Unregistered entity: Can't access $name field in object of class $type"
361             unless (exists $self->{$name});
362              
363             # return reference to OPML structure
364             if (@_ == 1) {
365             return $self->{$name}->{$_[0]} if defined $self->{$name}->{$_[0]};
366              
367             # we're going to set values here
368             } elsif (@_ > 1) {
369             my %hash = @_;
370             # return value
371             foreach my $key (keys(%hash)) {
372             $self->{$name}->{$key} = $hash{$key};
373             }
374             return $self->{$name};
375              
376             # otherwise, just return a reference to the whole thing
377             } else {
378             return $self->{$name};
379             }
380             return 0;
381             }
382              
383             # Entities for encoding
384             my %entity = (
385             nbsp => " ",
386             iexcl => "¡",
387             cent => "¢",
388             pound => "£",
389             curren => "¤",
390             yen => "¥",
391             brvbar => "¦",
392             sect => "§",
393             uml => "¨",
394             copy => "©",
395             ordf => "ª",
396             laquo => "«",
397             not => "¬",
398             shy => "­",
399             reg => "®",
400             macr => "¯",
401             deg => "°",
402             plusmn => "±",
403             sup2 => "²",
404             sup3 => "³",
405             acute => "´",
406             micro => "µ",
407             para => "¶",
408             middot => "·",
409             cedil => "¸",
410             sup1 => "¹",
411             ordm => "º",
412             raquo => "»",
413             frac14 => "¼",
414             frac12 => "½",
415             frac34 => "¾",
416             iquest => "¿",
417             Agrave => "À",
418             Aacute => "Á",
419             Acirc => "Â",
420             Atilde => "Ã",
421             Auml => "Ä",
422             Aring => "Å",
423             AElig => "Æ",
424             Ccedil => "Ç",
425             Egrave => "È",
426             Eacute => "É",
427             Ecirc => "Ê",
428             Euml => "Ë",
429             Igrave => "Ì",
430             Iacute => "Í",
431             Icirc => "Î",
432             Iuml => "Ï",
433             ETH => "Ð",
434             Ntilde => "Ñ",
435             Ograve => "Ò",
436             Oacute => "Ó",
437             Ocirc => "Ô",
438             Otilde => "Õ",
439             Ouml => "Ö",
440             times => "×",
441             Oslash => "Ø",
442             Ugrave => "Ù",
443             Uacute => "Ú",
444             Ucirc => "Û",
445             Uuml => "Ü",
446             Yacute => "Ý",
447             THORN => "Þ",
448             szlig => "ß",
449             agrave => "à",
450             aacute => "á",
451             acirc => "â",
452             atilde => "ã",
453             auml => "ä",
454             aring => "å",
455             aelig => "æ",
456             ccedil => "ç",
457             egrave => "è",
458             eacute => "é",
459             ecirc => "ê",
460             euml => "ë",
461             igrave => "ì",
462             iacute => "í",
463             icirc => "î",
464             iuml => "ï",
465             eth => "ð",
466             ntilde => "ñ",
467             ograve => "ò",
468             oacute => "ó",
469             ocirc => "ô",
470             otilde => "õ",
471             ouml => "ö",
472             divide => "÷",
473             oslash => "ø",
474             ugrave => "ù",
475             uacute => "ú",
476             ucirc => "û",
477             uuml => "ü",
478             yacute => "ý",
479             thorn => "þ",
480             yuml => "ÿ",
481             );
482              
483             my $entities = join('|', keys %entity);
484              
485             sub encode {
486             my ($self, $text) = @_;
487             return $text unless $self->{'encode_output'};
488             my $encoded_text = '';
489             while ( $text =~ s/(.*?)(\<\!\[CDATA\[.*?\]\]\>)//s ) {
490             $encoded_text .= encode_text($1) . $2;
491             }
492             $encoded_text .= encode_text($text);
493             return $encoded_text;
494             }
495              
496             sub encode_text {
497             my $text = shift;
498             $text =~ s/&(?!(#[0-9]+|#x[0-9a-fA-F]+|\w+);)/&/g;
499             $text =~ s/&($entities);/$entity{$1}/g;
500             $text =~ s/
501             return $text;
502             }
503             1;
504             __END__