File Coverage

blib/lib/Tree/Nary/Extended.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             # Author: Murat Uenalan (muenalan@cpan.org)
2             #
3             # Copyright (c) 2001 Murat Uenalan. All rights reserved.
4             #
5             # Note: This program is free software; you can redistribute
6             #
7             # it and/or modify it under the same terms as Perl itself.
8            
9 1     1   5070 require 5.005_62; use strict; use warnings;
  1     1   2  
  1         41  
  1         5  
  1         3  
  1         33  
10            
11 1     1   8770 use Tree::Nary;
  1         10812  
  1         72  
12            
13 1     1   2353 use DBI;
  1         19621  
  1         72  
14            
15 1     1   1924 use SQL::Generator;
  0            
  0            
16            
17             package Tree::Nary::Extended;
18            
19             our @ISA = qw(Tree::Nary);
20            
21             our $DEBUG = 0;
22            
23             use Data::Dumper;
24            
25             our $VERSION = '0.01';
26            
27             =pod
28            
29             =head1 NAME
30            
31             Tree::Nary::Extended - Tree::Nary with substantial load/save from/to sql/hash
32            
33             =head1 SYNOPSIS
34            
35             use Tree::Nary::Extended;
36            
37             Tree::Nary->to_hash( $ntree )
38            
39             Tree::Nary->from_dbi_to_hash( $dbh, $table_name )
40            
41             Tree::Nary->from_dbi_to_tree( $dbh, $table_name )
42            
43             Tree::Nary->to_dbi( $tree, $dbh, $table_name )
44            
45             Tree::Nary->bread_crumb_trail( $node )
46            
47             Tree::Nary->depth( $node )
48            
49             Tree::Nary->type( $node )
50            
51             my $href_nodes = Tree::Nary->from_dbi( $dbh, $table_name );
52            
53             my $nary = Tree::Nary->from_hash( $href_nodes );
54            
55             Tree::Nary->depth( $nary->{children} );
56            
57             my $found = Tree::Nary->find( $nary, $Tree::Nary::IN_ORDER, $Tree::Nary::TRAVERSE_ALL, 'foobar' );
58            
59             my $aref_trail = Tree::Nary->bread_crumb_trail( $found );
60            
61             Tree::Nary->append( $found, new Tree::Nary( 'Dummy' ) );
62            
63             Tree::Nary->traverse( $nary, $Tree::Nary::PRE_ORDER, $Tree::Nary::TRAVERSE_ALL, -1, \&Tree::Nary::Extended::_callback_textout );
64            
65             my $href_nodes = Tree::Nary->to_hash( $nary );
66            
67             Tree::Nary->to_dbi( $href_nodes, $dbh, $table_name );
68            
69             =head1 DESCRIPTION
70            
71             This module is not inheriting from Tree::Nary, but adds service methods to the namespace. So it an be seen as an "extended", but a bit "fishy" replacement for Tree::Nary. It loads C in the background and relies on some private innerts, which risks future compability. But noteworthy it works very well and does a good job for its author so far.
72            
73             =head1 Tree::Nary
74            
75             You should understand C (from CPAN) concepts first, before you proceed with this module.
76            
77             =head1 METHODS
78            
79             =over 1
80            
81             =item from_hash( $href_all_nodes [, $root_id (default: -1) ] )
82            
83             Creates a Nary Tree from a hash. The keys must be id, parent_id and data.
84            
85             =cut
86            
87             sub from_hash
88             {
89             my $this = shift;
90            
91             my $all = shift;
92            
93             my $root_id = shift || -1;
94            
95             my $href_parents = {};
96            
97             # 1. Step) first we connect child >> parent
98             #
99             # nearby we create a hashtable parent => [children] for the 2. Step
100            
101             foreach my $id ( keys %$all )
102             {
103             my $obj = bless $all->{$id}, 'Tree::Nary';
104            
105             if( $root_id != $id )
106             {
107             $obj->{parent} = undef;
108            
109             if( $obj->{parent_id} != $root_id )
110             {
111             $obj->{parent} = $all->{ $obj->{parent_id} };
112             }
113            
114             $href_parents->{ $obj->{parent_id} } = [] unless $href_parents->{ $obj->{parent_id} };
115            
116             push @{ $href_parents->{ $obj->{parent_id} } }, $obj;
117             }
118            
119             delete @$obj{ qw(level parent_id) };
120             }
121             # 2. Step) connect parent >> child and child >> child
122            
123             foreach my $parent_id ( keys %$href_parents )
124             {
125             my $children = $href_parents->{$parent_id};
126            
127             my $prev;
128            
129             foreach my $child ( @$children )
130             {
131             $prev->{next} = $child if $prev;
132            
133             $child->{prev} = $prev;
134            
135             $prev = $child;
136             }
137            
138             $all->{$parent_id}->{children} = $children->[0];
139             }
140            
141             return $all->{0};
142             }
143            
144             =pod
145            
146             =item to_hash( $nary )
147            
148             Produces a hash from Nary Tree. Returns a reference to it.
149            
150             =cut
151            
152             sub to_hash
153             {
154             my $this = shift;
155            
156             my $nary = shift;
157            
158             my $href_all = {};
159            
160             my ( $highest_id, $unique_id );
161            
162             $this->traverse( $nary, $Tree::Nary::PRE_ORDER, $Tree::Nary::TRAVERSE_ALL, -1, \&_callback_find_highest, \$highest_id );
163            
164             print "HIGHEST ID ", $highest_id, "\n" if $DEBUG;
165            
166             $unique_id = $highest_id + 1 if defined($highest_id);
167            
168             $this->traverse( $nary, $Tree::Nary::PRE_ORDER, $Tree::Nary::TRAVERSE_ALL, -1, \&_callback_give_id, \$unique_id );
169            
170             $this->traverse( $nary, $Tree::Nary::PRE_ORDER, $Tree::Nary::TRAVERSE_ALL, -1, \&_callback_convert_with_idkeys, $href_all );
171            
172             return $href_all;
173             }
174            
175             =item from_dbi_to_hash( $dbh, $table_name )
176            
177             Reads a table from a DBI $dbh and produces a hash and returns the hashref to it.
178            
179             =cut
180            
181             sub from_dbi_to_hash : method
182             {
183             my $this = shift;
184            
185             my $dbh = shift;
186            
187             my $table_name = shift;
188            
189             my $tree_hash = {};
190            
191             if( $dbh )
192             {
193             my $statement = sprintf 'SELECT id, parent_id, data FROM %s ORDER BY id', $table_name;
194            
195             print "Load with DBI from table '$table_name':\n" if $DEBUG;
196            
197             foreach my $aref ( @{ $dbh->selectall_arrayref($statement) } )
198             {
199             my $obj;
200            
201             @$obj{qw(id parent_id data)} = @$aref;
202            
203             printf "\t%d %d %s\n", @$obj{qw(id parent_id data)} if $DEBUG;
204            
205             $tree_hash->{ $obj->{id} } = $obj;
206             }
207             }
208            
209             print Dumper $tree_hash if $DEBUG;
210            
211             return $tree_hash;
212             }
213            
214             =item from_dbi_to_tree( $dbh, $table_name )
215            
216             Produces a Tree::Nary::Extended tree out of a DBI table.
217            
218             Note: Read the C from C for the format of the sql table. The columns names must be "id", "parent_id" and "data"
219            
220             =cut
221            
222             sub from_dbi_to_tree : method
223             {
224             my $this = shift;
225            
226             my $dbh = shift;
227            
228             my $table_name = shift;
229            
230             return Tree::Nary::Extended->from_hash( Tree::Nary::Extended->from_dbi_to_hash( $dbh, $table_name ) );
231             }
232            
233             =item to_dbi( $tree, $dbh, $table_name )
234            
235             Write a C tree to a DBI table. See Note of C for the format. Only the
236             three node attributes id, parent_id, data are saved.
237            
238             Note: The written sql table can be read with C method.
239            
240             =cut
241            
242             sub to_dbi : method
243             {
244             my $this = shift;
245            
246             my $tree = shift;
247            
248             my $dbh = shift;
249            
250             my $table_name = shift;
251            
252             my $sql = new SQL::Generator( historysize => 10*1000 );
253            
254             my %types = ( id => 'INTEGER NOT NULL', parent_id => 'INTEGER', data => 'VARCHAR(80)' );
255            
256             eval
257             {
258             $sql->DROP( TABLE => $table_name ) if Tree::Nary::Extended::tables( $dbh )->{ $table_name };
259            
260             $sql->CREATE( TABLE => $table_name, COLS => \%types, PRIMARYKEY => 'id' );
261            
262             foreach ( keys %$tree )
263             {
264             $tree->{$_}->{id} = $_;
265            
266             $sql->INSERT( SET => bless( $tree->{$_}, 'HASH' ) , INTO => $table_name );
267             }
268             };
269             if( $@ )
270             {
271             die;
272             }
273             else
274             {
275             foreach ( @{ $sql->history() } )
276             {
277             print "deploy: $_\n" if $DEBUG;
278            
279             $dbh->do( $_ ) or die;
280             }
281             }
282            
283             return $sql->history();
284             }
285            
286             =item bread_crumb_trail( $node )
287            
288             Traverses the anchestrol tree (partent->to->parent) upwards and collects all parents to an array.
289             A reference to it is returned. This can be used for building a "bread crumb trail" in website navigation.
290            
291             =cut
292            
293             sub bread_crumb_trail : method
294             {
295             my $this = shift;
296            
297             my $node = shift || return undef;
298            
299             my @nodes;
300            
301             do
302             {
303             push @nodes, $node;
304             }
305             while( $node = $node->{parent} );
306            
307             @nodes = reverse @nodes;
308            
309             return \@nodes;
310             }
311            
312             =item depth( $node )
313            
314             Returns the depth of a node, which is the distance to the root parent as an integer value.
315            
316             =cut
317            
318             sub depth
319             {
320             my ($self, $node) = (shift, shift);
321            
322             my $depth = 0;
323            
324             while( UNIVERSAL::isa( $node, "Tree::Nary" ) )
325             {
326             $depth++;
327            
328             if( ( $node || 0 ) == ( $node->{parent} || 0 ) )
329             {
330             die sprintf 'malicious circular reference detected (%s)', $node->{data};
331             }
332            
333             $node = $node->{parent};
334             }
335            
336             return($depth);
337             }
338            
339             =item type( $node )
340            
341             Returns the node "type". 'root' if it is the root node. 'leaf' if it is a leaf node.
342            
343             =cut
344            
345             sub type : method
346             {
347             my $this = shift;
348            
349             my $node = shift;
350            
351             my $root;
352             my $leaf;
353            
354             $root = 'root' if $this->is_root( $node );
355            
356             $leaf = 'leaf' if $this->is_leaf( $node );
357            
358             return $root || $leaf || 'dir';
359             }
360            
361             =back
362            
363             =head1 CALLBACKS
364            
365             C heavily uses callbacks for doing something. C ships with some
366             preconfectioned callbacks usefull for various things.
367            
368             =over 1
369            
370             =item _callback_find_highest( $sref_highest_id )
371            
372             Fills the scalarref with the highest node id number of the tree.
373            
374             =cut
375            
376             sub _callback_find_highest
377             {
378             my $node = shift;
379            
380             my $sref_highest_id = shift;
381            
382             if( exists $node->{id} )
383             {
384             if( $node->{id} > ( $$sref_highest_id || 0 ) )
385             {
386             $$sref_highest_id = $node->{id};
387             }
388             }
389            
390             return $Tree::Nary::FALSE;
391             }
392            
393             =item _callback_give_id( $sref_unique )
394            
395             Overwrite the node ids with primary key ids (linear (+1) unique id).
396            
397             =cut
398            
399             sub _callback_give_id
400             {
401             my $node = shift;
402            
403             my $sref_unique = shift;
404            
405             unless( exists $node->{id} )
406             {
407             $node->{id} = $$sref_unique++;
408             }
409            
410             return $Tree::Nary::FALSE;
411             }
412            
413             =item _callback_convert_with_idkeys( $ref_arg )
414            
415             Internal use. Cannot remember what is was.
416            
417             =cut
418            
419             sub _callback_convert_with_idkeys
420             {
421             my $node = shift;
422            
423             my $ref_arg = shift;
424            
425             my $parent_id = exists $node->{parent}->{id} ? $node->{parent}->{id} : -1 ;
426            
427             $ref_arg->{ $node->{id} } =
428             {
429             parent_id => $parent_id,
430            
431             data => $node->{data},
432             };
433            
434             return $Tree::Nary::FALSE;
435             }
436            
437             =item _callback_find_id( $aref_args = [ $id ] )
438            
439             Returns the first node with a given id. $aref_args[1] will contain the resulting node.
440            
441             =cut
442            
443             sub _callback_find_id
444             {
445             my $node = shift;
446            
447             my $aref_args = shift;
448            
449             die "callback parameters mismatch" unless ref $aref_args eq 'ARRAY';
450            
451             printf "Searching %d at %s (will put into %s)\n", $aref_args->[0], $node->{data}, $aref_args->[1] if $DEBUG;
452            
453             if( exists $node->{id} )
454             {
455             if( $aref_args->[0] == $node->{id} )
456             {
457             ${ $aref_args->[1] } = $node;
458            
459             return $Tree::Nary::TRUE;
460             }
461             }
462            
463             return $Tree::Nary::FALSE;
464             }
465            
466             =item _callback_textout( $ref_args )
467            
468             Dumps a textual printout of the node structure. Helps debugging.
469            
470             =cut
471            
472             sub _callback_textout
473             {
474             my $node = shift;
475            
476             my $ref_arg = shift;
477            
478             my $depth = Tree::Nary::Extended->depth( $node );
479            
480             print " " x 3 x $depth;
481            
482             #println "BUGGGG" if( $node->{data} eq 'Perl' && defined($node->{parent}) );
483            
484             my $parent_data;
485            
486             if( exists $node->{parent} )
487             {
488             $parent_data = $node->{parent}->{data} if exists $node->{parent}->{data};
489             }
490            
491             printf( " %s %s (depth %d, children: %d, parent: %s) %s\n",
492            
493             Tree::Nary::Extended->type( $node ),
494            
495             $node->{data},
496            
497             $depth,
498            
499             Tree::Nary::Extended->n_children( $node ),
500            
501             $parent_data || 'none',
502            
503             exists $node->{id} ? $node->{id} : ''
504             );
505            
506             print $$ref_arg || '' if defined $ref_arg;
507            
508             return $Tree::Nary::FALSE;
509             }
510            
511             1;
512             __END__