File Coverage

blib/lib/Netscape/Bookmarks.pm
Criterion Covered Total %
statement 87 108 80.5
branch 36 54 66.6
condition 11 21 52.3
subroutine 17 20 85.0
pod 4 8 50.0
total 155 211 73.4


line stmt bran cond sub pod time code
1             package Netscape::Bookmarks;
2 5     5   335504 use v5.10;
  5         57  
3              
4             =encoding utf8
5              
6             =head1 NAME
7              
8             Netscape::Bookmarks - parse, manipulate, or create Netscape Bookmarks files
9              
10             =head1 SYNOPSIS
11              
12             use Netscape::Bookmarks;
13              
14             # parse an existing file
15             my $bookmarks = Netscape::Bookmarks->new( $bookmarks_file );
16              
17             # -- OR --
18             # start a new Bookmarks structure
19             my $bookmarks = Netscape::Bookmarks->new;
20              
21             # print a Netscape compatible file
22             print $bookmarks->as_string;
23              
24              
25             =head1 DESCRIPTION
26              
27             The Netscape bookmarks file has several basic components:
28              
29             title
30             folders (henceforth called categories)
31             links
32             aliases
33             separators
34              
35             On disk, Netscape browsers store this information in HTML.
36             In the browser, it is displayed under the "Bookmarks" menu.
37             The data can be manipulated through the browser interface.
38              
39             This module allows one to manipulate the bookmarks file
40             programmatically. One can parse an existing bookmarks file,
41             manipulate the information, and write it as a bookmarks file
42             again. Furthermore, one can skip the parsing step to create
43             a new bookmarks file and write it in the proper format to be
44             used by a Netscape browser.
45              
46             The Bookmarks module simply parses the bookmarks file passed
47             to it as the only argument to the constructor:
48              
49             my $bookmarks = Netscape::Bookmarks->new( $bookmarks_file );
50              
51             The returned object is a C object, since
52             the bookmark file is simply a collection of categories that
53             contain any of the components listed above. The top level
54             (i.e. root) category is treated specially and defines the
55             title of the bookmarks file.
56              
57             C is used behind the scenes to build the data structure (a
58             simple list of lists (of lists ...)). C,
59             C, C, or
60             C objects can be stored in a
61             C object. C
62             objects are treated as references to C
63             objects, so changes to one affect the other.
64              
65             Methods for manipulating this object are in the
66             C module. Methods for dealing with the
67             objects contained in a C object are in
68             their appropriate modules.
69              
70             =over 4
71              
72             =cut
73              
74 5     5   31 use strict;
  5         11  
  5         137  
75              
76 5     5   28 use base qw(HTML::Parser);
  5         20  
  5         3204  
77 5     5   35448 use subs qw();
  5         141  
  5         212  
78 5         558 use vars qw(@ISA
79             $DEBUG
80             $VERSION
81             @category_stack
82             $flag
83             %link_data
84             %category_data
85             $netscape
86             $state
87             $current_link
88             $ID
89             $text_flag
90 5     5   34 );
  5         10  
91              
92 5     5   35 use HTML::Entities;
  5         13  
  5         335  
93 5     5   34 use HTML::Parser;
  5         10  
  5         174  
94              
95 5     5   2473 use Netscape::Bookmarks::Alias;
  5         13  
  5         178  
96 5     5   2672 use Netscape::Bookmarks::Category;
  5         15  
  5         172  
97 5     5   2553 use Netscape::Bookmarks::Link;
  5         19  
  5         200  
98 5     5   2484 use Netscape::Bookmarks::Separator;
  5         20  
  5         175  
99 5     5   39 use Netscape::Bookmarks::Isa;
  5         14  
  5         6431  
100              
101             $VERSION = "2.303";
102              
103             $ID = 0;
104             $DEBUG = $ENV{NS_DEBUG} || 0;
105              
106 0     0 0 0 sub XML { 'XML' };
107              
108             =item new( [filename] )
109              
110             The constructor takes a filename as its single (optional) argument.
111             If you do not give C an argument, an empty
112             C object is returned so that
113             you can start to build up your new Bookmarks file. If the file
114             that you name does not exist, C is returned in scalar
115             context and an empty list is returned in list context. If the
116             file does exist it is parsed with C with the
117             internal parser subclass defined in the same package as C.
118             If the parsing finishes without error a C
119             object is returned.
120              
121             =cut
122              
123             sub new {
124 4     4 1 2232 my($class, $file) = @_;
125              
126 4 50       21 unless( $file ) {
127 0         0 my $cat = Netscape::Bookmarks::Category->new();
128 0         0 return $cat;
129             }
130              
131 4 50 33     88 return unless ( -e $file or ref $file );
132              
133 4         79 my $self = HTML::Parser->new();
134 4         326 $self->unbroken_text(1);
135              
136 4         11 bless $self, $class;
137              
138 4         35 $self->parse_file( $file );
139              
140 4         85 return $netscape;
141             }
142              
143             sub mozilla {
144 6     6 0 14 my $self = shift;
145 6         97 my $value = shift;
146              
147 6 100       34 $self->{'mozilla'} = $value if defined $value;
148              
149 6         71 $self->{'mozilla'};
150             }
151              
152             sub parse_string {
153 0     0 0 0 my $data_ref = shift;
154              
155 0         0 my $self = HTML::Parser->new();
156 0         0 bless $self, __PACKAGE__;
157              
158 0         0 my $length = length $$data_ref;
159 0         0 my $pos = 0;
160              
161 0         0 while( $pos < $length ) {
162             #512 bytes seems to be the magic number
163             #to make this work efficiently. don't know
164             #why really - its an HTML::Parser thing
165 0         0 $self->parse( substr( $$data_ref, $pos, 512 ) );
166 0         0 $pos += 512;
167             }
168              
169 0         0 $self->eof;
170              
171 0         0 return $netscape; # a global variable
172             }
173              
174             sub start {
175 1324     1324 1 4465 my($self, $tag, $attr) = @_;
176              
177 1324         1922 $text_flag = 0;
178              
179 1324 100 100     4712 if( $tag eq 'a' ) {
    100          
    100          
    100          
180 458         770 $state = 'anchor';
181 458         2160 %link_data = %$attr;
182             }
183             elsif( $tag eq 'h3' or $tag eq 'h1' ) {
184 78         127 $state = 'category';
185 78         326 %category_data = %$attr;
186             }
187             elsif( $tag eq 'hr' ) {
188 4         25 my $item = Netscape::Bookmarks::Separator->new();
189 4         10 $category_stack[-1]->add( $item );
190             }
191             elsif( $tag eq 'meta' ) {
192 2         10 $self->mozilla(1);
193             }
194              
195 1324         8915 $flag = $tag
196             }
197              
198             sub text {
199 1262     1262 1 4059 my($self, $text) = @_;
200              
201 1262 100       2276 if($text_flag) {
202 4 50 0     14 if( not defined $flag ) {
    0 0        
    0          
    0          
203             # sometimes $flag is not set (haven't figured out when that
204             # is), so without this no-op, you get a perl5.6.1 warning
205             # about "uninitialized value in string eq"
206 4         8 1;
207             }
208             elsif( $flag eq 'h1' or $flag eq 'h3' ) {
209 0         0 $category_stack[-1]->title( $text );
210             }
211             elsif( $flag eq 'a' and not exists $link_data{'aliasof'} ) {
212 0         0 $current_link->title( $text );
213             }
214             elsif( $flag eq 'dd' ) {
215 0 0       0 if( $state eq 'category' ) {
    0          
216 0         0 $category_stack[-1]->description( $text );
217             }
218             elsif( $state eq 'anchor' ) {
219 0         0 $current_link->description( $text );
220             }
221             }
222              
223             }
224             else {
225 1258 100 100     4426 if( not defined $flag ) {
    100 66        
    100          
    100          
    100          
    100          
226             # sometimes $flag is not set (haven't figured out when that
227             # is), so without this no-op, you get a perl5.6.1 warning
228             # about "uninitialized value in string eq"
229 544         808 1;
230             }
231             elsif( $flag eq 'h1' ) {
232             $netscape = Netscape::Bookmarks::Category->new(
233             {
234             title => $text,
235             folded => 0,
236             add_date => $category_data{'add_date'},
237 4         23 last_modified => $category_data{'last_modified'},
238             mozilla => $self->mozilla,
239             id => $ID++,
240             } );
241              
242 4         52 push @category_stack, $netscape;
243             }
244             elsif( $flag eq 'h3' ) {
245             #print STDERR "Personal Toolbar is [$category_data{'personal_toolbar_folder'}] for [$text]\n";
246             my $cat = Netscape::Bookmarks::Category->new(
247             {
248             title => $text,
249             folded => exists $category_data{'folded'},
250             add_date => $category_data{'add_date'},
251             last_modified => $category_data{'last_modified'},
252             personal_toolbar_folder => $category_data{'personal_toolbar_folder'},
253 74   66     574 id => $category_data{'id'} || $ID++,
254             });
255              
256 74         304 $category_stack[-1]->add( $cat );
257 74         149 push @category_stack, $cat;
258             }
259             elsif( $flag eq 'a' and not exists $link_data{'aliasof'} ) {
260             my $item = Netscape::Bookmarks::Link->new( {
261             HREF => $link_data{'href'},
262             ADD_DATE => $link_data{'add_date'},
263             LAST_MODIFIED => $link_data{'last_modified'},
264             LAST_VISIT => $link_data{'last_visit'},
265             ALIASID => $link_data{'aliasid'},
266             SHORTCUTURL => $link_data{'shortculurl'},
267             ICON => $link_data{'icon'},
268             LAST_CHARSET => $link_data{'last_charset'},
269             SCHEDULE => $link_data{'schedule'},
270             LAST_PING => $link_data{'last_ping'},
271             PING_CONTENT_LEN => $link_data{'ping_content_len'},
272 456         3695 PING_STATUS => $link_data{'ping_status'},
273             TITLE => $text,
274             });
275              
276 456 50       1624 unless( ref $item ) {
277 0 0       0 print "ERROR: $Netscape::Bookmarks::Link::ERROR\n" if $DEBUG;
278 0         0 return;
279             }
280              
281 456 100       1030 if( defined $link_data{'aliasid'} ) {
282             &Netscape::Bookmarks::Alias::add_target(
283 2         11 $item, $link_data{'aliasid'} )
284             }
285              
286 456         1650 $category_stack[-1]->add( $item );
287 456         866 $current_link = $item;
288             }
289             elsif( $flag eq 'a' and defined $link_data{'aliasof'} ) {
290 2         17 my $item = Netscape::Bookmarks::Alias->new( $link_data{'aliasof'} );
291 2 50       19 unless( ref $item ) {
292 0         0 return;
293             }
294              
295 2         13 $category_stack[-1]->add( $item );
296 2         4 $current_link = $item;
297             }
298             elsif( $flag eq 'dd' ) {
299 12 100       33 if( $state eq 'category' ) {
    50          
300 10         51 $category_stack[-1]->description( $text );
301             }
302             elsif( $state eq 'anchor' ) {
303 2         11 $current_link->description( $text );
304             }
305             }
306             }
307              
308 1262         6269 $text_flag = 1;
309             }
310              
311             sub end {
312 618     618 1 1587 my($self, $tag, $attr) = @_;
313              
314 618         984 $text_flag = 0;
315 618 100       1315 pop @category_stack if $tag eq 'dl';
316             # what does the next line do and why?
317             # if it is there then the
part of a link is discarded
318             # not having this line doesn't seem to break things.
319             # bug identified by Daniel Hottinger
320             #$current_link = undef if $tag eq 'a';
321 618         2899 $flag = undef;
322             }
323              
324       0 0   sub my_init {}
325              
326             "Seeing is believing";
327              
328             =back
329              
330             =head1 AUTHOR
331              
332             brian d foy C<< >>
333              
334             =head1 COPYRIGHT AND LICENSE
335              
336             Copyright © 2002-2018, brian d foy . All rights reserved.
337              
338             This program is free software; you can redistribute it and/or modify
339             it under the terms of the Artistic License 2.0.
340              
341             =head1 SEE ALSO
342              
343             L,
344             L,
345             L,
346             L,
347             L.
348              
349             =cut