File Coverage

blib/lib/Netscape/Bookmarks/Category.pm
Criterion Covered Total %
statement 138 185 74.5
branch 44 62 70.9
condition 23 33 69.7
subroutine 26 35 74.2
pod 21 22 95.4
total 252 337 74.7


line stmt bran cond sub pod time code
1             package Netscape::Bookmarks::Category;
2              
3             =encoding utf8
4              
5             =head1 NAME
6              
7             Netscape::Bookmarks::Category - manipulate, or create Netscape Bookmarks files
8              
9             =head1 SYNOPSIS
10              
11             use Netscape::Bookmarks;
12              
13             #parse an existing file
14             my $bookmarks = new Netscape::Bookmarks $bookmarks_file;
15              
16             #print a Netscape compatible file
17             print $bookmarks->as_string;
18              
19             =head1 DESCRIPTION
20              
21             THIS IS AN ABANDONED MODULE. THERE IS NO SUPPORT. YOU CAN ADOPT IT
22             IF YOU LIKE: https://pause.perl.org/pause/query?ACTION=pause_04about#takeover
23              
24             The Netscape bookmarks file has several basic components:
25              
26             title
27             folders (henceforth called categories)
28             links
29             aliases
30             separators
31              
32             On disk, Netscape browsers store this information in HTML. In the browser,
33             it is displayed under the "Bookmarks" menu. The data can be manipulated
34             through the browser interface.
35              
36             This module allows one to manipulate the bookmarks file programmatically. One
37             can parse an existing bookmarks file, manipulate the information, and write it
38             as a bookmarks file again. Furthermore, one can skip the parsing step to create
39             a new bookmarks file and write it in the proper format to be used by a Netscape
40             browser.
41              
42             The Bookmarks.pm module simply parses the bookmarks file passed to it as the
43             only argument to the constructor:
44              
45             my $bookmarks = new Netscape::Bookmarks $bookmarks_file;
46              
47             The returned object is a Netscape::Bookmarks::Category object, since the bookmark file is
48             simply a collection of categories that contain any of the components listed
49             above. The top level (i.e. root) category is treated specially and defines the
50             title of the bookmarks file.
51              
52             =head1 METHODS
53              
54             =over 4
55              
56             =cut
57              
58 6     6   59607 use strict;
  6         19  
  6         348  
59              
60 6     6   71 use base qw( Netscape::Bookmarks::AcceptVisitor Netscape::Bookmarks::Isa );
  6         11  
  6         1104  
61 6     6   35 use subs qw();
  6         40  
  6         135  
62 6     6   28 use vars qw( $VERSION $ERROR $LAST_ID %IDS );
  6         10  
  6         345  
63              
64 6     6   32 use constant START_LIST => '

';

  6         10  
  6         468  
65 6     6   34 use constant END_LIST => '

';

  6         19  
  6         256  
66 6     6   32 use constant START_LIST_ITEM => '
';
  6         10  
  6         263  
67 6     6   32 use constant TAB => ' ';
  6         12  
  6         260  
68 6     6   31 use constant FOLDED_TRUE => 1;
  6         10  
  6         237  
69 6     6   30 use constant FOLDED_FALSE => 0;
  6         16  
  6         253  
70 6     6   32 use constant TRUE => 'true';
  6         7  
  6         9697  
71              
72             $VERSION = "2.304";
73              
74             %IDS = ();
75             $LAST_ID = -1;
76              
77             =item Netscape::Bookmarks::Category-Enew( \%hash )
78              
79             The new method creates a Category. It takes a hash reference
80             that specifies the properties of the category. The valid keys
81             in that hash are
82              
83             folded collapsed state of the category ( 1 or 0 )
84             title
85             add_date
86             description
87              
88             =cut
89              
90             sub new
91             {
92 78     78 1 146 my $class = shift;
93 78         118 my $param = shift;
94              
95 78         105 my $self = {};
96 78         138 bless $self, $class;
97              
98 78 100       178 $self->{'folded'} = FOLDED_TRUE unless $param->{'folded'} == FOLDED_FALSE;
99 78 100       154 $self->{'personal_toolbar_folder'} = TRUE if $param->{'personal_toolbar_folder'};
100              
101 78 100 66     410 unless( exists $IDS{$param->{'id'}} or $param->{'id'} =~ /\D/)
102             {
103 12         21 $param->{'id'} = ++$LAST_ID;
104 12         29 $IDS{$LAST_ID}++;
105             }
106              
107 78 50 66     262 if( defined $param->{'add_date'} and $param->{'add_date'} =~ /\D/ )
108             {
109 0         0 $param->{'add_date'} = 0;
110             }
111              
112 78         176 $self->{'mozilla'} = $param->{'mozilla'};
113 78         133 $self->{'title'} = $param->{'title'};
114 78         119 $self->{'add_date'} = $param->{'add_date'};
115 78         145 $self->{'last_modified'} = $param->{'last_modified'};
116 78         115 $self->{'id'} = $param->{'id'};
117 78         129 $self->{'description'} = $param->{'description'};
118 78         156 $self->{'thingys'} = [];
119              
120 78         183 $self;
121             }
122              
123             sub mozilla
124             {
125 41     41 0 58 my $self = shift;
126 41         51 my $value = shift;
127              
128 41 50       75 $self->{'mozilla'} = $value if defined $value;
129              
130 41         86 $self->{'mozilla'};
131             }
132              
133             =item $category-Eadd( $object )
134              
135             The add() function adds an element to a category. The element must be a Alias,
136             Link, Category, or Separator object. Returns TRUE or FALSE.
137              
138             =cut
139              
140             sub add
141             {
142 536     536 1 725 my $self = shift;
143 536         628 my $thingy = shift;
144              
145             return unless
146 536 50 100     1228 ref $thingy eq 'Netscape::Bookmarks::Link' or
      100        
      66        
147             ref $thingy eq 'Netscape::Bookmarks::Category' or
148             ref $thingy eq 'Netscape::Bookmarks::Separator' or
149             ref $thingy eq 'Netscape::Bookmarks::Alias';
150              
151 536         717 push @{ $self->{'thingys'} }, $thingy;
  536         1194  
152             }
153              
154             =item $category-Eremove_element( $object )
155              
156             Removes the given object from the Category by calling the object's
157             remove() method.
158              
159             Returns the number of objects removed from the Category.
160              
161             =cut
162              
163             sub remove_element
164             {
165 0     0 1 0 my $self = shift;
166 0         0 my $thingy = shift;
167              
168 0         0 my $old_count = $self->count;
169              
170             $self->{'thingys'} =
171 0 0       0 [ grep { $_ ne $thingy and $_->remove } $self->elements ];
  0         0  
172              
173 0         0 return $old_count - $self->count;
174             }
175              
176             =item $category-Eremove()
177              
178             Performs any clean up necessary to remove this object from the
179             Bookmarks tree. Although this method does not recursively remove
180             objects which it contains, it probably should.
181              
182             =cut
183              
184 0     0 1 0 sub remove { 1; }
185              
186             =item $category-Etitle( [ TITLE ] )
187              
188             Returns title to the category. With a
189             defined argument TITLE, it replaces the current
190             title.
191              
192             =cut
193              
194             sub title
195             {
196 39     39 1 52 my $self = shift;
197              
198 39 50       76 if( defined $_[0] )
199             {
200 0         0 $self->{'title'} = shift;
201             }
202              
203 39         76 $self->{'title'};
204             }
205              
206             =item $category-Eid()
207              
208             Returns the ID of the category. This is an arbitrary, unique number.
209              
210             =cut
211              
212             sub id
213             {
214 37     37 1 51 my $self = shift;
215              
216 37         66 $self->{'id'};
217             }
218              
219             =item $category-Edescription( [ DESCRIPTION ] )
220              
221             Returns the description of the category. With a
222             defined argument DESCRIPTION, it replaces the current
223             description.
224              
225             =cut
226              
227             sub description
228             {
229 49     49 1 69 my $self = shift;
230              
231 49 100       88 if( defined $_[0] )
232             {
233 10         19 $self->{'description'} = shift;
234             }
235              
236 49         84 $self->{'description'};
237             }
238              
239             =item $category-Efolded( $object )
240              
241             Returns the folded state of the category (TRUE or FALSE). If the category is
242             "folded", Netscape shows a collapsed folder for this category.
243              
244             =cut
245              
246             sub folded
247             {
248 37     37 1 49 my $self = shift;
249              
250 37 100       89 return $self->{'folded'} ? 1 : 0;
251             }
252              
253             =item $category-Eadd_date()
254              
255             Returns the ADD_DATE attribute of the category.
256              
257             =cut
258              
259             sub add_date
260             {
261 37     37 1 45 my $self = shift;
262              
263 37         83 return $self->{'add_date'};
264             }
265              
266             =item $category-Elast_modified()
267              
268             Returns the LAST_MODIFIED attribute of the category.
269              
270             =cut
271              
272             sub last_modified
273             {
274 37     37 1 47 my $self = shift;
275              
276 37         53 return $self->{'last_modified'};
277             }
278              
279             =item $category-Epersonal_toolbar_folder()
280              
281             Returns the PERSONAL_TOOLBAR_FOLDER attribute of the category.
282              
283             =cut
284              
285             sub personal_toolbar_folder
286             {
287 37     37 1 49 my $self = shift;
288              
289 37         58 return $self->{'personal_toolbar_folder'};
290             }
291              
292             =item $category-Eelements()
293              
294             In scalar context returns an array reference to the elements in
295             the category. In list context returns a list of the elements in
296             the category.
297              
298             =cut
299              
300             sub elements
301             {
302 44     44 1 63 my $self = shift;
303              
304 44 50       71 if( wantarray ) { @{ $self->{'thingys'} } }
  44         56  
  44         133  
305 0         0 else { $self->{'thingys'} }
306             }
307              
308             =item $category-Ecount()
309              
310             Returns a count of the number of objects in the Category.
311              
312             =cut
313              
314 0     0 1 0 sub count { scalar @{ $_[0]->{'thingys'} } }
  0         0  
315              
316             =item $category-Ecategories()
317              
318             Returns a list of the Category objects in the category.
319              
320             =cut
321              
322             sub categories
323             {
324 0     0 1 0 my $self = shift;
325              
326 0         0 my @list = grep ref $_ eq 'Netscape::Bookmarks::Category',
327             $self->elements;
328              
329 0         0 return @list;
330             }
331              
332             =item $category-Elinks()
333              
334             Returns a list of the Link objects in the category.
335              
336             =cut
337              
338             sub links
339             {
340 0     0 1 0 my $self = shift;
341              
342 0         0 my @list = grep ref $_ eq 'Netscape::Bookmarks::Link',
343             $self->elements;
344              
345 0         0 return @list;
346             }
347              
348             =item $category-Eas_headline()
349              
350             Returns an HTML string representation of the category, but not
351             the elements of the category.
352              
353             =cut
354              
355             sub as_headline
356             {
357 37     37 1 54 my $self = shift;
358              
359 37 100       58 my $folded = $self->folded ? "FOLDED" : "";
360 37         65 my $title = $self->title;
361 37         66 my $desc = $self->description;
362 37         64 my $add_date = $self->add_date;
363 37         62 my $last_modified = $self->last_modified;
364 37         59 my $id = $self->id;
365 37         341 my $personal_toolbar_folder = $self->personal_toolbar_folder;
366              
367 37 100 66     104 $desc = defined $desc && $desc ne '' ? "\n
$desc" : "\n";
368              
369 37 100       56 $folded = $folded ? qq|FOLDED| : '';
370 37 100       63 $add_date = $add_date ? qq|ADD_DATE="$add_date"| : '';
371 37 100       62 $last_modified = $last_modified ? qq|LAST_MODIFIED="$last_modified"| : '';
372 37 100       57 $personal_toolbar_folder = $personal_toolbar_folder
373             ? qq|PERSONAL_TOOLBAR_FOLDER="true"| : '';
374 37 100       123 $id = $id =~ m/\D/ ? qq|ID="$id"| : '';
375              
376 37         104 my $attr = join " ", grep $_, ($folded, $add_date, $last_modified,
377             $personal_toolbar_folder, $id );
378              
379 37 50       101 $attr = " " . $attr if $attr;
380 37         106 $attr =~ s/\s+$//; # XXX: ugh
381              
382 37         116 return qq|$title$desc|
383             }
384              
385             =item $category-Erecurse( CODE, [ LEVEL ] )
386              
387             This method performs a depth-first traversal of the Bookmarks
388             tree and executes the CODE reference at each node.
389              
390             The CODE reference receives two arguments - the object on which
391             it should operate and its level in the tree.
392              
393             =cut
394              
395             sub recurse
396             {
397 0     0 1 0 my $self = shift;
398 0         0 my $sub = shift;
399 0   0     0 my $level = shift || 0;
400              
401 0 0       0 unless( ref $sub eq 'CODE' )
402             {
403 0         0 warn "Argument to recurse is not a code reference";
404 0         0 return;
405             }
406              
407 0         0 $sub->( $self, $level );
408              
409 0         0 ++$level;
410 0         0 foreach my $element ( $self->elements )
411             {
412 0 0       0 if( $element->isa( __PACKAGE__ ) )
413             {
414 0         0 $element->recurse( $sub, $level );
415             }
416             else
417             {
418 0         0 $sub->( $element, $level );
419             }
420             }
421 0         0 --$level;
422              
423             }
424              
425             =item $category-Eintroduce( VISITOR, [ LEVEL ] )
426              
427             This method performs a depth-first traversal of the Bookmarks
428             tree and introduces the visitor object to each object.
429              
430             This is different from recurse() which only calls its
431             CODEREF on nodes. The VISITOR operates on nodes and
432             vertices. The VISITOR must have a visit() method
433             recognizable by can(). This method does not trap
434             errors in the VISITOR.
435              
436             See L for details on
437             Visitors.
438              
439             =cut
440              
441             sub introduce
442             {
443 5     5 1 369 my $self = shift;
444 5         9 my $visitor = shift;
445 5   100     18 my $level = shift || 0;
446              
447 5 50       29 unless( $visitor->can('visit') )
448             {
449 0         0 warn "Argument to introduce cannot visit()!";
450 0         0 return;
451             }
452              
453 5         25 $self->visitor( $visitor );
454              
455 5         11 ++$level;
456 5         17 foreach my $element ( $self->elements )
457             {
458              
459 16 100       209 if( $element->isa( __PACKAGE__ ) )
460             {
461 4         17 $element->introduce( $visitor, $level );
462             }
463             else
464             {
465 12         67 $element->visitor( $visitor );
466             }
467              
468             }
469 5         141 --$level;
470              
471             }
472              
473             =item $category-Esort_elements( [ CODE ] )
474              
475             Sorts the elements in the category using the provided CODE
476             reference. If you do not specify a CODE reference, the
477             elements are sorted by title (with the side effect of
478             removing Separators from the Category).
479              
480             This function does not recurse, although you can use
481             the recurse() method to do that.
482              
483             Since the built-in sort() uses the package variables
484             C<$a> and C<$b>, your sort subroutine has to make sure
485             that it is accessing the right C<$a> and C<$b>, which
486             are the ones in the package C.
487             You can start your CODE reference with a package
488             declaration to ensure the right thing happens:
489              
490             my $sub = sub {
491             package Netscape::Bookmarks::Category;
492              
493             $b->title cmp $a->title;
494             };
495              
496             $category->sort_elements( $sub );
497              
498             If you know a better way to do this, please let me know. :)
499              
500             =cut
501              
502             sub sort_elements
503             {
504 0     0 1 0 my $self = shift;
505 0         0 my $sub = shift;
506              
507 0 0 0     0 if( defined $sub and not ref $sub eq 'CODE' )
    0          
508             {
509 0         0 warn "Second argument to sort_elements is not a CODE reference.";
510 0         0 return;
511             }
512             elsif( not defined $sub )
513             {
514 0     0   0 $sub = sub { $a->title cmp $b->title };
  0         0  
515             }
516              
517 0         0 local *my_sorter = $sub;
518              
519             $self->{'thingys'} = [ sort my_sorter
520 0         0 grep { not $_->isa( 'Netscape::Bookmarks::Separator' ) }
521 0         0 @{ $self->{'thingys'} } ];
  0         0  
522             }
523              
524             =item $category-Eas_string()
525              
526             Returns an HTML string representation of the category as the
527             top level category, along with all of the elements of the
528             category and the Categories that it contains, recursively.
529              
530             =cut
531              
532             sub as_string
533             {
534 2     2 1 1277 my $self = shift;
535              
536 2         10 my $title = $self->title;
537 2   100     7 my $desc = $self->description || "\n";
538              
539 2 100       7 my $meta = $self->mozilla ?
540             qq|\n| :
541             '';
542              
543 2         10 my $str = <<"HTML";
544            
545             $meta
548             $title
549            

$title

550              
551             HTML
552              
553 2 100 66     6 $str .= "
" . $desc unless( $self->mozilla and $desc eq "\n" );
554              
555 2         6 $str .= START_LIST . "\n";
556              
557 2         10 foreach my $element ( $self->elements )
558             {
559 34         73 $str .= $self->_as_string( $element, 1 );
560             }
561              
562 2         6 $str .= END_LIST . "\n";
563              
564 2         167 return $str;
565             }
566              
567             # _as_string does most of the work that as_string would normally
568             # do.
569             sub _as_string
570             {
571 268     268   345 my $self = shift;
572 268         309 my $obj = shift;
573 268         356 my $level = shift;
574              
575 268         314 my $str;
576 268 100 100     648 if( ref $obj eq 'Netscape::Bookmarks::Category' )
    100          
    50          
577             {
578 37         95 $str .= TAB x ($level) . START_LIST_ITEM . $obj->as_headline;
579              
580 37 100       71 unless( $self->mozilla )
581             {
582 4         8 $str .= TAB x ($level-1) . START_LIST . "\n";
583             }
584             else
585             {
586 33         62 $str .= TAB x ($level) . START_LIST . "\n";
587             }
588              
589 37         46 ++$level;
590 37         67 foreach my $ref ( $obj->elements )
591             {
592 234         410 $str .= $self->_as_string( $ref, $level );
593             }
594 37         59 --$level;
595              
596 37         73 $str .= TAB x ($level) . END_LIST . "\n";
597             }
598             elsif( ref $obj eq 'Netscape::Bookmarks::Link' or
599             ref $obj eq 'Netscape::Bookmarks::Alias' )
600             {
601 229         537 $str .= TAB x ($level) . START_LIST_ITEM
602             . $obj->as_string . "\n"
603             }
604             elsif( ref $obj eq 'Netscape::Bookmarks::Separator' )
605             {
606 2         6 $str .= TAB x ($level) . $obj->as_string . "\n"
607             }
608              
609 268         782 return $str;
610              
611             }
612              
613             =item $obj->write_file( FILENAME )
614              
615             UNIMPLEMENTED!
616              
617             =cut
618              
619             sub write_file
620             {
621 0     0 1   my $self = shift;
622 0           my $filename = shift;
623              
624 0           return;
625             }
626              
627             "if you want to beleive everything you read, so be it.";
628              
629             __END__