File Coverage

blib/lib/HTML/ListToTree.pm
Criterion Covered Total %
statement 183 202 90.5
branch 57 94 60.6
condition 39 62 62.9
subroutine 29 30 96.6
pod 19 20 95.0
total 327 408 80.1


line stmt bran cond sub pod time code
1             =pod
2              
3             =begin classdoc
4              
5             Converts an HTML nested list document to a Javascripted
6             tree widget.

7              
8             Copyright© 2007, Dean Arnold, Presicient Corp., USA. All rights reserved.

9              
10             Excluding the dtree widget software and components included in the
11             L package, permission is granted to use this software
12             under the same terms as Perl itself. Refer to the L for details.
13              
14             @author Dean Arnold
15             @since 2007-Jun-10
16             @self $self
17              
18             =end classdoc
19              
20             =cut
21              
22             package HTML::ListToTree;
23              
24 1     1   30986 use HTML::TreeBuilder;
  1         45186  
  1         13  
25 1     1   807 use HTML::ListToTree::DTree;
  1         3  
  1         26  
26              
27 1     1   5 use strict;
  1         2  
  1         26  
28 1     1   5 use warnings;
  1         1  
  1         3834  
29              
30             our $VERSION = '0.10';
31              
32             our %tags_accepted = qw(a 1 li 1 ul 1 ol 1 /a 1 /li 1 /ul 1 /ol 1);
33             #
34             # have to use class variable for unlink action, due to
35             # recursive structure
36             #
37             our %unlinks = ( 'include' => 1, 'warn' => 1, 'ignore' => 1 );
38             our $onUnlink;
39              
40             =pod
41              
42             =begin classdoc
43              
44             Create an HTML::ListToTree object with specified text label and link url,
45             optionally setting an initial set of child nodes and/or extracting
46             children from a source document.
47              
48             @constructor
49             @param Text a text label for the node
50             @param Link a link URL for the node.
51             @optional Children an arrayref of HTML::ListToTree objects
52             @optional Source a document from which to collect child nodes
53             @optional Widget either a Perl object, or the name of a Perl package, providing browser widget construction methods;
54             default 'HTML::ListToTree::DTree'
55             @optional UnlinkedLeaves string specifying disposition of unlinked leaf nodes; valid values are
56            
57            
  • include (the default) - include in three
  • 58            
  • warn - emit warning, but include in tree
  • 59            
  • ignore - don't include, and don't warn
  • 60            
    61              
    62             @return an HTML::ListToTree object
    63              
    64             =end classdoc
    65              
    66             =cut
    67              
    68             sub new {
    69 52     52 1 1318 my $class = shift;
    70 52         135 my %args = @_;
    71              
    72 52   100     207 $args{Children} ||= [];
    73 52         55 my $widget;
    74 52 100       101 if ($args{Widget}) {
    75 1 50       3 if (ref $args{Widget}) {
    76 0         0 $widget = $args{Widget};
    77             }
    78             else {
    79 1         105 eval "
    80             require $args{Widget};
    81             \$widget = $args{Widget}->new();
    82             ";
    83 1 50       6 return undef if $@;
    84             }
    85             }
    86             else {
    87 51         144 $widget = HTML::ListToTree::DTree->new();
    88             }
    89             #
    90             # if an unlink action is specified, update it
    91             #
    92 52 0       122 $onUnlink = $unlinks{lc $args{UnlinkedLeaves}} ? lc $args{UnlinkedLeaves} : 'include'
        50          
    93             if exists $args{UnlinkedLeaves};
    94              
    95 52   100     158 $onUnlink ||= 'include';
    96 52         270 my $self = bless {
    97             _text => $args{Text},
    98             _link => $args{Link},
    99             _children => $args{Children},
    100             _widget => $widget,
    101             }, $class;
    102              
    103 52 100       108 push @{$self->{_children}}, $self->extractTree($args{Source})
      3         13  
    104             if exists $args{Source};
    105              
    106 52         183 return $self;
    107             }
    108              
    109             =pod
    110              
    111             =begin classdoc
    112              
    113             Add a set of sibling nodes to the tree as a child of this node.
    114             The nodes are appended to any existing list of immediate children
    115             of this node.
    116              
    117             @param @nodes a list of nodes. Nodes are specified as either 2-tuples of
    118             Text => Link, or as an HTML::ListToTree object
    119              
    120             @returnlist HTML::ListToTree objects added as children of this object
    121              
    122             =end classdoc
    123              
    124             =cut
    125              
    126             sub addChildren {
    127 15     15 1 17 my $self = shift;
    128 15         15 my ($text, $link);
    129 15         19 my @nodes = ();
    130 15 100       36 my @args = (ref $_[0] eq 'ARRAY') ? @{$_[0]} : @_;
      14         29  
    131 15 100       42 push(@{$self->{_children}},
      38         180  
    132             ref $args[0] ? shift @args : HTML::ListToTree->new(Text => shift @args, Link => shift @args)),
    133             push(@nodes, $self->{_children}[-1])
    134             while (@args);
    135 15         53 return @nodes;
    136             }
    137              
    138             =pod
    139              
    140             =begin classdoc
    141              
    142             Extract a tree from a nested lists of the input document, and
    143             add it as a child of this node.
    144              
    145             @param $html the source HTML document
    146              
    147             @returnlist HTML::ListToTree objects extracted from the document
    148              
    149             =end classdoc
    150              
    151             =cut
    152              
    153             sub addFromDocument {
    154 1     1 1 2 my ($self, $html) = @_;
    155 1         4 my @nodes = $self->extractTree($html);
    156 1         3 push @{$self->{_children}}, @nodes;
      1         3  
    157 1         6 return @nodes;
    158             }
    159              
    160             =pod
    161              
    162             =begin classdoc
    163              
    164             Return the child nodes of this node as a list.
    165             The list is in the order in which the nodes were added
    166             to this node.
    167              
    168             @returnlist the child nodes aHTML::ListToTree objects
    169              
    170             =end classdoc
    171              
    172             =cut
    173              
    174             sub getChildren {
    175 1     1 1 3 my $self = shift;
    176              
    177 1         2 return @{$self->{_children}};
      1         5  
    178             }
    179              
    180             =pod
    181              
    182             =begin classdoc
    183              
    184             Scans this node's children to locate a node with the specified text label.
    185             The scan is breadth first (i.e., siblings are scanned before children).
    186              
    187             @return if a match is found, an HTML::ListToTree object; otherwise, undef.
    188              
    189             =end classdoc
    190              
    191             =cut
    192              
    193             sub getNodeByText {
    194 2     2 1 4 my ($self, $text) = @_;
    195              
    196 2         2 foreach (@{$self->{_children}}) {
      2         5  
    197 6 100       17 return $_
    198             if ($_->{_text} eq $text);
    199             }
    200 1         2 foreach (@{$self->{_children}}) {
      1         3  
    201 1         5 my $node = $_->getNodeByText($text);
    202 1 50       7 return $node if $node;
    203             }
    204 0         0 return undef;
    205             }
    206              
    207             =pod
    208              
    209             =begin classdoc
    210              
    211             Scans this node's children to locate a node with the specified URL link.
    212             The scan is breadth first (i.e., siblings are scanned before children).
    213              
    214             @return if a match is found, an HTML::ListToTree object; otherwise, undef.
    215              
    216             =end classdoc
    217              
    218             =cut
    219              
    220             sub getNodeByLink {
    221 17     17 1 19 my ($self, $link) = @_;
    222              
    223 17         20 my $offset = -1 * length($link);
    224 17         15 foreach (@{$self->{_children}}) {
      17         29  
    225 23 100       57 return $_
    226             if (substr($_->{_link}, $offset) eq $link);
    227             }
    228 16         17 foreach (@{$self->{_children}}) {
      16         27  
    229 16         28 my $node = $_->getNodeByLink($link);
    230 16 100       36 return $node if $node;
    231             }
    232 14         18 return undef;
    233             }
    234              
    235             =pod
    236              
    237             =begin classdoc
    238              
    239             Return the text label of this node.
    240              
    241             @return the text label of this node
    242              
    243             =end classdoc
    244              
    245             =cut
    246              
    247             sub getText {
    248 15     15 1 88 return $_[0]->{_text};
    249             }
    250              
    251             =pod
    252              
    253             =begin classdoc
    254              
    255             Set the text label of this node.
    256              
    257             @param $text the text label to set
    258              
    259             @return this node
    260              
    261             =end classdoc
    262              
    263             =cut
    264              
    265             sub setText {
    266 1     1 1 3 $_[0]->{_text} = $_[1];
    267 1         3 return $_[0];
    268             }
    269              
    270             =pod
    271              
    272             =begin classdoc
    273              
    274             Return the link URL of this node.
    275              
    276             @return the link URL of this node
    277              
    278             =end classdoc
    279              
    280             =cut
    281              
    282             sub getLink {
    283 15     15 1 106 return $_[0]->{_link};
    284             }
    285              
    286             =pod
    287              
    288             =begin classdoc
    289              
    290             Set the link URL of this node.
    291              
    292             @param $link the link URL to set
    293              
    294             @return this node
    295              
    296             =end classdoc
    297              
    298             =cut
    299              
    300             sub setLink {
    301 1     1 1 4 $_[0]->{_link} = $_[1];
    302 1         3 return $_[0];
    303             }
    304              
    305             =pod
    306              
    307             =begin classdoc
    308              
    309             Render this HTML::ListToTree object into an HTML document containing Javascript
    310             required for dtree, and suitable for use as a frame
    311             within a frameset. Subclasses may override this method to provide
    312             alternate renderings of the tree.
    313              
    314             @constructor
    315             @optional Additions HTML text to be appended to the generated tree
    316             @optional BasePath the base directory path for all local hyperlinks
    317             @optional CloseIcon name of icon used for closed tree nodes; default 'closedbook.gif'
    318             @optional CSSPath path to the stylesheet file dtree.css used by dtree; default './css'
    319             @optional IconPath path to the location of icons used by dtree; default './img'
    320             @optional JSPath path to the Javascript file dtree.js; default '.js'
    321             @optional UseIcons when set to a true value, tree nodes are decorated with icons; default true
    322             @optional OpenIcon name of icon used for open tree nodes; default 'openbook.gif'
    323             @optional RootIcon name of icon used for the root tree node; default is same as OpenIcon
    324             @optional Target the name of an HTML frame to contain the document being navigated; default 'mainframe'
    325              
    326             @return an HTML document
    327              
    328             =end classdoc
    329              
    330             =cut
    331              
    332             sub render {
    333 3     3 1 992 my $self = shift;
    334 3         16 my %args = @_;
    335              
    336 3   100     12 $args{CloseIcon} ||= 'closedbook.gif';
    337 3   100     14 $args{OpenIcon} ||= 'openbook.gif';
    338 3   100     10 $args{IconPath} ||= './img';
    339 3   100     8 $args{CSSPath} ||= './css/dtree.css';
    340 3   100     11 $args{JSPath} ||= './js/dtree.js';
    341 3   66     8 $args{RootIcon} ||= $args{OpenIcon};
    342 3   100     9 $args{Target} ||= 'mainframe';
    343 3   100     9 $args{Additions} ||= '';
    344              
    345 3 100       10 $args{UseIcons} = 1 unless exists $args{UseIcons};
    346            
    347 3 100       16 my ($openimg, $closeimg, $rootimg) = $args{UseIcons} ?
    348             ("$args{IconPath}/$args{OpenIcon}",
    349             "$args{IconPath}/$args{CloseIcon}",
    350             "$args{IconPath}/$args{RootIcon}") :
    351             ('', '', '');
    352             #
    353             # adjust paths for css/javascript/images
    354             #
    355 3 50       9 if ($args{BasePath}) {
    356             $args{$_} = _pathAdjust($args{BasePath}, $args{$_})
    357 0         0 foreach (qw(JSPath CSSPath IconPath));
    358 0 0       0 $self->{_link} = _pathAdjust($args{BasePath}, $self->{_link})
    359             if $self->{_link};
    360             }
    361             #
    362             # save path info if needed later
    363             #
    364 3         7 $self->{_jspath} = $args{JSPath};
    365 3         4 $self->{_iconpath} = $args{IconPath};
    366 3         5 $self->{_csspath} = $args{CSSPath};
    367 3   100     32 $self->{_widget}->start(
    368             IconPath => $args{IconPath},
    369             CSSPath => $args{CSSPath},
    370             JSPath => $args{JSPath},
    371             UseIcons => $args{UseIcons} || 0,
    372             RootIcon => $rootimg,
    373             RootText => $self->{_text},
    374             RootLink => $self->{_link},
    375             Target => $args{Target},
    376             OpenIcon => $openimg,
    377             CloseIcon => $closeimg,
    378             );
    379             #
    380             # sort current tree into levels
    381             #
    382 3         8 my @levels = ( [ $self ] );
    383 3         8 _sort_tree([ $self ], \@levels);
    384             #
    385             # draw root level first
    386             #
    387 3         8 my ($close, $open);
    388 3         4 shift @levels;
    389 3         6 foreach (@{$self->{_children}}) {
      3         8  
    390 15         65 $_->{_text}=~s/'/\\'/g;
    391 15 50       29 $_->{_link} = _pathAdjust($args{BasePath}, $_->{_link})
    392             if $args{BasePath};
    393 15 100 100     16 (($#{$_->{_children}} >= 0) && $args{UseIcons}) ?
    394             $self->{_widget}->add($_->{_node}, 0, $_->{_text}, $_->{_link}) :
    395             $self->{_widget}->addLeaf($_->{_node}, 0, $_->{_text}, $_->{_link});
    396             }
    397             #
    398             # then draw succeding levels
    399             #
    400 3         4 my $offset = scalar @{$levels[0]};
      3         7  
    401 3         6 foreach my $i (1..$#levels) {
    402 9         9 foreach (@{$levels[$i]}) {
      9         21  
    403 102         125 $_->{_node} += $offset;
    404 102         140 $_->{_text}=~s/'/\\'/g;
    405 102 50       176 $_->{_link} = _pathAdjust($args{BasePath}, $_->{_link})
    406             if $args{BasePath};
    407 102 100 100     87 (($#{$_->{_children}} >= 0) && $args{UseIcons}) ?
    408             $self->{_widget}->add($_->{_node}, $levels[$i-1][$_->{_parent}]->{_node}, $_->{_text}, $_->{_link}) :
    409             $self->{_widget}->addLeaf($_->{_node}, $levels[$i-1][$_->{_parent}]->{_node}, $_->{_text}, $_->{_link});
    410             }
    411 9         13 $offset += scalar @{$levels[$i]};
      9         23  
    412             }
    413              
    414 3         11 return $self->{_widget}->getWidget($args{Additions});
    415             }
    416              
    417             sub _pathAdjust {
    418 0     0   0 my ($path, $jspath) = @_;
    419 0 0 0     0 return $jspath
    420             unless (substr($jspath, 0, 2) eq './') && (substr($path, 0, 2) eq './');
    421             #
    422             # relative path, adjust as needed from current base
    423             #
    424 0         0 my @parts = split /\//, $path;
    425 0         0 my @jsparts = split /\//, $jspath;
    426 0         0 shift @parts;
    427 0         0 shift @jsparts; # and the relative lead
    428 0         0 my $prefix = '';
    429 0   0     0 shift @parts,
          0        
    430             shift @jsparts
    431             while @parts && @jsparts && ($parts[0] eq $jsparts[0]);
    432 0         0 return ('../' x scalar @parts) . join('/', @jsparts)
    433             }
    434              
    435             =pod
    436              
    437             =begin classdoc
    438              
    439             Extract the nested list from the supplied HTML document and convert it
    440             to an HTML::ListToTree object. Subclasses may override this method
    441             to provide alternate list extraction logic.
    442              
    443             @param $html the source document
    444              
    445             @return an HTML::ListToTree object
    446              
    447             =end classdoc
    448              
    449             =cut
    450              
    451             sub extractTree {
    452 4     4 1 6 my ($self, $src) = @_;
    453             #
    454             # enforce some canonical form: only start with a list,
    455             # remove all comments, and insert list items between
    456             # consecutive list elements
    457             #
    458 4         14 $src=~s/