File Coverage

blib/lib/Netscape/Bookmarks.pm
Criterion Covered Total %
statement 85 106 80.1
branch 36 54 66.6
condition 11 21 52.3
subroutine 16 19 84.2
pod 4 8 50.0
total 152 208 73.0


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