File Coverage

blib/lib/OGDL/Graph.pm
Criterion Covered Total %
statement 255 441 57.8
branch 93 176 52.8
condition 29 54 53.7
subroutine 18 35 51.4
pod 0 29 0.0
total 395 735 53.7


line stmt bran cond sub pod time code
1             # Graph.pm
2             # This class implements a nested named list.
3             # author 'Rolf Veen'
4             # license zlib
5             # date 20030609
6             # Modified by Hui Zhou
7              
8             package OGDL::Graph;
9              
10 2     2   11 use strict;
  2         3  
  2         62  
11 2     2   23 use warnings;
  2         5  
  2         58  
12              
13              
14 2     2   11470 use OGDL::Path;
  2         6  
  2         6381  
15              
16             sub new {
17 154     154 0 248 my ($class,$name)=@_;
18 154         680 my $rec = {
19             parent=>undef,
20             name => $name,
21             list => [ () ]
22             };
23             # print "New Graph node: [$name]\n";
24 154         524 return bless $rec,$class;
25             }
26              
27             #$g->addGraph($name); append node with name=$name
28             sub addGraph {
29 0     0 0 0 my ($self,$name) = @_;
30 0         0 my $n=OGDL::Graph->new($name);
31 0         0 $self->addNode($n);
32 0         0 return $n;
33             }
34             #$g->addNode($sub); append node to the end
35             sub addNode {
36 160     160 0 197 my ($self,$node) = @_;
37 160         202 my $list = $self->{list};
38 160         185 my $len = @$list;
39 160         202 $node->{parent}=$self;
40 160         423 $self->{list}[$len] = $node;
41             # print "Adding node: [",$node->{name},"]\n";
42             }
43              
44             sub merge{
45 0     0 0 0 my ($self,$node)=@_;
46 0         0 my $list1=$self->{"list"};
47 0         0 my $list2=$node->{"list"};
48 0 0       0 if(!$list2){return;}
  0         0  
49 0         0 push @$list1, @$list2;
50             }
51              
52             sub unlink{
53 0     0 0 0 my $node=shift;
54 0         0 my $parent=$node->{"parent"};
55 0         0 $node->{"parent"}=undef;
56 0         0 my $j=0;
57 0         0 my $l=$$parent{"list"};
58 0         0 while($j<=$#$l){
59 0 0       0 if($$l[$j]==$node){
60 0         0 splice @$l,$j,1;
61 0         0 return;
62             }
63 0         0 $j++;
64             }
65             }
66              
67             sub getname{
68 1     1 0 12 my $node=get(@_);
69 1 50       3 if(!$node){return undef;}
  0         0  
70 1         4 return $$node{"name"};
71             }
72              
73             sub childrencount{
74 0     0 0 0 my $g=shift;
75 0         0 my $list=$$g{"list"};
76 0         0 return scalar @$list;
77             }
78              
79             sub getChildren{
80 0     0 0 0 my $g=shift;
81 0         0 my $list=$$g{"list"};
82 0         0 return @$list;
83             }
84              
85             sub isempty{
86 0     0 0 0 my $g=shift;
87 0         0 my $l=$$g{"list"};
88 0         0 return (! scalar(@$l));
89             }
90            
91             sub clear{
92 0     0 0 0 my $g=shift;
93 0         0 my $l=$$g{"list"};
94 0         0 foreach(@$l){
95 0         0 $_->{"parent"}=undef;
96             }
97 0         0 $$g{"list"}=[ () ];
98             }
99              
100             #Not sure how to deal with the order
101             #sub diff{
102             # my ($g1,$g2)=@_;
103             # my ($pg1,$pg2);
104             #}
105              
106             sub listmatch{
107 0     0 0 0 my ($g,$list,$rnum,@path)=@_;
108 0         0 my $inum=-1; #match all index
109 0         0 my $namepat;
110 0         0 my $pat=shift @path;
111 0 0       0 if($pat=~/(.*)(\[(\d*)\])/){
112 0 0       0 if($3 eq ""){$inum=-1;}
  0         0  
  0         0  
113             else{$inum=$3;}
114 0 0       0 if($1 eq ""){$namepat="^*\$";}
  0         0  
  0         0  
115             else{$namepat="^$1\$";}
116             }
117             else{
118 0         0 $inum=-1;
119 0         0 $namepat="^$pat\$";
120             }
121 0         0 $namepat=~s/\*/\.\*/g;
122 0         0 $namepat=~s/\?/\./g;
123 0         0 $inum++;
124            
125             # print "listmatch ",$$g{"name"},"=~$namepat\n";
126 0 0       0 if($$g{"name"}=~/$namepat/){
127 0         0 $$rnum=$$rnum+1;
128             # print "Match: ",$$rnum,", $inum, (",$#path,")\n";
129 0 0 0     0 if($inum>0 && $$rnum!=$inum){ #index doesn't match
130 0         0 return undef;
131             }
132 0 0       0 if($#path==-1){
133             # print "Matched\n";
134 0         0 {push @$list,$g;return 1;} #matches finally
  0         0  
  0         0  
135             }
136 0         0 my $n=0;
137 0         0 my $l=$g->{list};
138 0         0 foreach(@$l){
139 0         0 $_->listmatch($list,\$n,@path);
140             }
141             }
142             }
143              
144             sub glist{
145 0     0 0 0 my @list;
146 0         0 my ($g,$pathstr)=@_;
147 0         0 my @path=splitPath($pathstr);
148 0 0       0 if($#path<0){push @list,$g;return @list;}
  0         0  
  0         0  
149 0         0 unshift @path,"*";
150 0         0 my $n=0;
151             # my $j=0; foreach(@path){print "$j:[$_],";$j++;}print "\n";
152 0         0 listmatch($g,\@list,\$n,@path);
153 0         0 return @list;
154             }
155              
156             sub removematch{
157 0     0 0 0 my ($g,$list,@path)=@_;
158 0         0 my $inum=-1; #match all index
159 0         0 my $namepat;
160 0         0 my $pat=shift @path;
161 0 0       0 if($pat=~/(.*)(\[(\d*)\])/){
162 0 0       0 if($3 eq ""){$inum=-1;}
  0         0  
  0         0  
163             else{$inum=$3;}
164 0 0       0 if($1 eq ""){$namepat="^*\$";}
  0         0  
  0         0  
165             else{$namepat="^$1\$";}
166             }
167             else{
168 0         0 $inum=-1;
169 0         0 $namepat="^$pat\$";
170             }
171 0         0 $namepat=~s/\*/\.\*/g;
172 0         0 $namepat=~s/\?/\./g;
173 0 0       0 if($inum>=0){$inum++;}
  0         0  
174            
175 0         0 my $j=0;
176 0         0 my $num=0;
177 0         0 my $l=$g->{"list"};
178 0         0 my $n=$$l[$j];
179 0         0 while($n){
180             # print "removematch ",$$n{"name"},"=~/$namepat/\n";
181 0 0       0 if($$n{"name"}=~/$namepat/){
182 0         0 $num++;
183             # print "Match :$num, $inum\n";
184 0 0 0     0 if($inum>=0 && $num!=$inum){ #index doesn't match
185 0         0 $j++;
186             }
187             else{
188 0         0 {my $j=0; foreach(@path){print "$j:[$_],";$j++;}print "\n";}
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
189 0 0       0 if($#path==-1){
190 0         0 push @$list,$n;
191 0         0 splice(@$l,$j,1);
192 0 0       0 if($inum>=0){ last; }
  0         0  
193             else{
194 0         0 $num++;
195             }
196             }
197             else{
198 0         0 removematch($n,$list,@path);
199 0 0       0 if($inum>=0){last;}
  0         0  
200 0         0 $j++;
201             }
202             }
203             }
204             else{
205 0         0 $j++;
206             }
207 0         0 $n=$$l[$j];
208             }
209             }
210              
211             sub gremove{
212 0     0 0 0 my ($g,$pathstr)=@_;
213 0         0 my @path=splitPath($pathstr);
214 0 0       0 if($#path<0){unshift @path,"*";}
  0         0  
215 0         0 my @list;
216 0 0       0 if($#path<0){return $g;}
  0         0  
217 0         0 removematch($g,\@list,@path);
218 0         0 return @list;
219             }
220              
221             sub gmove{
222 0     0 0 0 my ($g,$from,$to)=@_;
223 0         0 my @frompath=splitPath($from);
224 0         0 my @topath=splitPath($to);
225 0         0 my @remove=gremove($from);
226 0         0 foreach (@remove){
227 0         0 addmatch($g,$_,@topath);
228             }
229             }
230              
231             sub addmatch{
232 26     26 0 47 my ($g,$node,@path)=@_;
233 26 100       57 if($#path<0){
234 9 50       18 if($node){
235 0         0 $g->clear;
236 0         0 $g->addNode($node)
237             };
238 9         24 return;
239             }
240 17         20 my $inum=-1; #match all index
241 17         20 my $uniq=1;
242 17         14 my $namepat;
243 17         48 my $pat=shift @path;
244 17 100       38 if($pat=~/(.*)(\[(\d*)\])/){
245 1 50       7 if($3 eq ""){ $uniq=0;$inum=-1;}
  0         0  
  0         0  
  1         2  
246             else{$inum=$3;}
247 1 50       5 if($1 eq ""){$namepat="*";}
  0         0  
  1         2  
248             else{$namepat="$1";}
249             }
250             else{
251 16         18 $namepat="$pat";
252             }
253 17 100       42 if($namepat=~/[*?]/){
254 3         8 $namepat=~s/\*/\.\*/g;
255 3         5 $namepat=~s/\?/\./g;
256 3         4 $uniq=0;
257             }
258 17 100       96 if($inum>=0){$inum++;}
  1         1  
259            
260 17         82 my $j=0;
261 17         18 my $num=0;
262 17         97 my $n=$g->{list}[$j];
263 17         18 my $exist=0;
264 17         35 while($n){
265             # print "addmatch ",$$n{"name"},"=~/^$namepat\$/\n";
266 12 100       244 if($$n{"name"}=~/^$namepat$/){
267 11         13 $num++;
268             # print "Match: $num, $inum\n";
269 11 50 33     100 if($inum<0 || $num==$inum){ # match
270 11         12 $exist=1;
271 11         16 addmatch($n,$node,@path);
272             }
273             }
274 12         16 $j++;
275 12         32 $n=$g->{list}[$j];
276             }
277 17 100 66     64 if(!$exist && $uniq){
278             # print "Add $namepat?\n";
279 12         12 $j=$num;
280 12 100       31 if($inum<0){$inum=1;}
  11         184  
281 12         24 while($j<$inum){
282 17         35 $n=OGDL::Graph->new($namepat);
283 17         33 $g->addNode($n);
284 17         33 $j++;
285             # print "Added node [$namepat]\n";
286             }
287 12         29 addmatch($n,$node,@path);
288             }
289             }
290              
291             sub gadd{
292 3     3 0 18 my ($g,$pathstr,$str)=@_;
293 3         10 my @path=splitPath($pathstr);
294             # my $j=0;foreach(@path){print "$j:[$_],";$j++;}print "\n";
295 3         5 my $node=undef;
296 3 50       7 if($str){
297 0         0 $node=OGDL::Graph->new($str);
298             }
299 3         8 addmatch($g,$node,@path);
300             }
301              
302             # g->add(path, string)
303             # doesn't work with numeric indices
304             sub add
305             {
306 0     0 0 0 my ($g,$path,$string)=@_;
307 0         0 my $n=$g->get($path);
308 0         0 return $n->addGraph($string);
309             }
310              
311             #$g->getNode($index); return subnode by index
312             sub getNode {
313 59     59 0 61 my $self = shift;
314 59         161 return $self->{list}[$_[0]];
315             }
316              
317             sub getNodeByName {
318 35     35 0 37 my $self = shift;
319 35         43 my $name = shift;
320            
321 35         49 my $list = $self->{list};
322 35         34 my $i=0;
323            
324 35         60 for (@$list) {
325 90 100       189 if ($_->{name} eq $name)
326 35         79 { return $i; }
327 55         57 $i++;
328             }
329 0         0 return -1;
330             }
331              
332             # look for the nth ocurrence of a name
333              
334             sub getNodeByNameN {
335 5     5 0 9 my $self = shift;
336 5         8 my $name = shift;
337 5         11 my $n = shift;
338              
339 5         18 my $list = $self->{list};
340 5         6 my $i=0;
341            
342 5         11 for (@$list) {
343 30 100       68 if ($_->{name} eq $name) {
344 14 100       31 if ($n-- == 0)
345 5         20 { return $i; }
346             }
347 25         27 $i++;
348             }
349 0         0 return -1;
350             }
351              
352             # make a new Graph with all nodes with given name.
353              
354             sub newGraphByName {
355 2     2 0 4 my $self = shift;
356 2         4 my $name = shift;
357 2         5 my $list = $self->{list};
358 2         3 my $i=0;
359 2         8 my $g = OGDL::Graph->new($name);
360 2         4 my $list2;
361            
362 2         4 for (@$list) {
363 14 100       34 if ($_->{name} eq $name) {
364 6         7 $list2=$_->{list};
365 6         18 for (@$list2) {
366 12         27 $g->addNode($_);
367             }
368             }
369 14         22 $i++;
370             }
371 2         5 return $g;
372             }
373              
374             sub get
375             {
376 19     19 0 4854 my $self = shift;
377 19         59 my @path = OGDL::Path::path2list(shift);
378 19         30 my $node = $self;
379 19         25 my $i=0;
380 19         16 my $prev; # to distinguish between x[n] and x.[n] and hold the
381             # previous node
382            
383 19         36 for (@path) {
384 96 50 33     200 if ( !$_ && $_ ne '0') { last; } # Whose bug is this ?
  0         0  
385            
386 96 100       171 if ($_ eq ".") { $prev=""; next; }
  35         41  
  35         52  
387              
388             # [n]?
389 61 100       185 if(/\[(\d*)\]/){
390             # if ( substr($_,0,1) eq '[') {
391             # $i = 0 + substr($_,1,100); # get the numeric index
392 26 100       70 if($1 eq ""){$i=0;}
  2         4  
  24         43  
393             else{$i=$1;}
394            
395             # if prev & i>0 then we must look for ith ocurrence
396             # of prev
397            
398             # if prev & i==0 then we group all nodes with the same name
399             # as $node->{name} in a new Graph and continue from there.
400            
401 26 100       55 if ( $prev ) {
402 7 100       23 if ( $i > 0 ) {
    50          
403 5         18 $i = $prev->getNodeByNameN($node->{name},$i);
404 5         12 $node = $prev->getNode($i);
405 5         7 $prev = 0;
406 5         9 next;
407             }
408             elsif ( $i == 0 ) {
409 2         8 $node = $prev->newGraphByName($node->{name});
410 2         5 $prev = 0;
411 2         5 next;
412             }
413 0         0 else { $i = -1; }
414             }
415             }
416             else {
417 35         80 $i = $node->getNodeByName($_);
418             }
419            
420 54 50       223 if ($i == -1) { return undef; }
  0         0  
421 54         61 $prev = $node;
422 54         99 $node = $node->getNode($i);
423             }
424            
425 19         64 return $node;
426             }
427              
428             sub getGraph
429             {
430 0     0 0 0 return get(@_);
431             }
432              
433             sub getScalar
434             {
435 0     0 0 0 my $node = get(@_);
436 0 0       0 if ($node) {
437 0         0 $node = $node->{list}[0];
438 0 0       0 if ($node)
439 0         0 { return $node->{name}; }
440             }
441 0         0 return undef;
442             }
443              
444              
445             #_print_str($name,$indent,$pending,$blockquote,$noquote,*FILE)
446             sub _print_str
447             {
448 132     132   303 my ($s,$n,$pending,$blockquote,$noquote,$sameline,$output)=@_;
449             #$pending = $_[2]; #Whether continuing at previous line or starting at begining of new line
450             #$blockquote=$_[3]; #Whether use \ quote or " quote
451             # see what type of string it is: word, quoted or block
452 132 100       327 if ($s =~ /[ \n\r]/) {#block
453 4 100 66     18 if($blockquote && $pending){
454 2         221 print $output " \\\n";
455 2         7 my $c;
456 2         4 my $pend=1;
457 2         81 print $output ' ' x $n; $pend = 0;
  2         7  
458 2         10 for (my $i=0;$i
459 1560         2394 $c = substr($s,$i,1) ;
460             # if(!defined $c) {last;}
461 1560 100       2700 if ( $pend == 1 ) { print $output ' ' x $n; $pend = 0;}
  24         934  
  24         53  
462 1560 100       4372 if ($c eq "\n") {
463 24         27 $pend = 1;
464             }
465 1560         47635 print $output $c;
466             }
467 2 50       12 if($pend){$pending = 0;}
  0         0  
  2         5  
468             else{$pending=1;}
469             }
470             else{ #use double quote block
471 2 50       7 if($pending){
472 2 50       5 if($sameline){print $output ' ';}
  0         0  
473             else{
474 2         5 print $output "\n";
475 2         5 print $output ' ' x $n;
476             }
477             }
478 2         5 my $c;
479 2         4 my $i=0;
480 2         3 my $pend=0;
481 2 50       7 if(!$noquote){ print $output '"';$n++;} #Opening quote
  2         4  
  2         3  
482 2         8 for (my $i=0;$i
483 1560         1872 $c = substr($s,$i,1);
484 1560 100       2456 if ( $pend == 1 ) { print $output ' ' x $n; $pend = 0;}
  24         42  
  24         30  
485 1560 100 66     4891 if ($c eq "\n") {
    100          
486 24         25 $pend = 1;
487             }
488 4         10 elsif($c eq '"' && !$noquote) { print $output "\\"; } #Quote the quote
489 1560         4370 print $output $c;
490             }
491 2 50       7 if(!$noquote){print $output '"'; }#Closing quote
  2         5  
492 2         4 $pending = 1;
493             }
494             }
495             else {
496 128 100       234 if ($pending == 1) {
497 119 50       199 if($sameline){
498 0         0 print $output ' ';
499             }
500             else{
501 119         5439 print $output "\n" ;
502 119         2392 print $output ' ' x $n;
503             }
504             }
505             else{
506 9         28 print $output ' ' x $n;
507             }
508 128         1566 print $output $s;
509 128         232 $pending = 1;
510             }
511 132         272 return $pending;
512             }
513              
514             #assuming it always start at $indentlevel==0
515             sub _print {
516 2     2   1948 use integer;
  2         24  
  2         11  
517 132     132   328 my ($self,$output,$indentlevel,$indentwidth,$pending,$single,$singlequote,$noblockquote,$depth, $group)=@_;
518 132         247 my $list = $self->{list};
519 132         233 my @l = @$list;
520              
521 132         171 my $indent=$indentwidth*$indentlevel;
522 132         132 my $blockquote=0;
523 132         141 my $noquote=0;
524 132         139 my $sameline=0;
525 132 50       266 if($group==0){$sameline=1;}
  0         0  
526 132 100 100     425 if (!$noblockquote && $single && $#l<0){$blockquote=1;}
  3   100     4  
527 132 50 100     428 if (!$singlequote && $indentlevel==0 && $single && ($#l<0 ||$depth ==0)){$noquote=1;}#single node output
  1   66     2  
      33        
      66        
528 132         343 $pending = _print_str($self->{name}, $indent, $pending,$blockquote,$noquote,$sameline,$output);
529 132 100       284 if($#l==0){
530 18         30 $single=1;
531             }
532             else{
533 114         150 $single=0;
534             }
535 132         149 $indentlevel++;
536            
537             #negative $depth is equivalent to infinity depth
538 132         133 $depth--;
539 132 100 66     605 if($depth==0 || $#l<0){return $pending;}
  83         222  
540 49 50       108 if($group>0){$group--;}
  0         0  
541 49 50       128 if($group==0){$sameline=1;}
  0         0  
542 49 50       98 if($sameline){
543 0 0       0 if($#l>0){print $output " (";}
  0         0  
544             }
545 49         69 my $j=$#l;
546             {
547 49         50 foreach my $g(@l){
  49         81  
548 123         438 $pending=$g->_print($output,$indentlevel,$indentwidth,$pending,$single,$singlequote,$noblockquote,$depth,$group);
549 123 50 33     433 if($sameline && $j>0){
550 0         0 print $output ",";
551 0         0 $j--;
552             }
553             }
554 49 50       130 if($sameline){
555 0 0       0 if($#l>0){print $output " )";}
  0         0  
556             }
557             }
558 49         145 return $pending;
559             }
560              
561             # arguments: A hash with keys: depth, indentwidth, filehandle, singlequote, printroot, noblockquote
562             sub print{
563 3     3 0 102291 my ($g,%params) = @_;
564 3         14 my $list = $g->{list};
565 3         51 my @l = @$list;
566 3         8 my $singleblock=0;
567 3         7 my $indentwidth=4;
568 3         6 my $quote=0;
569 3         8 my $depth=0;#infinity
570 3         6 my $pending=0;
571 3         7 my $noblockquote=0;
572 3         13 my $output=*STDOUT;
573 3         7 my $group=-1; #put all nodes after $group depth in one line
574 3 50       20 if($params{"indentwidth"}){$indentwidth=$params{"indentwidth"};}
  0         0  
575 3 100       31 if($params{"singlequote"}){$quote=$params{"singlequote"};}
  2         7  
576 3 100       25 if($params{"filehandle"}){$output=$params{"filehandle"};}
  2         22  
577 3 50       14 if($params{"depth"}){$depth=$params{"depth"};}
  0         0  
578 3 50       14 if(exists $params{"group"}){$group=$params{"group"};}
  0         0  
579 3 100       12 if($params{"noblockquote"}){$noblockquote=$params{"noblockquote"};}
  2         6  
580 3 100 66     31 if(defined $params{printroot} and $params{"printroot"} eq "0"){
581 2         7 $g=$list->[0];
582 2         8 foreach my $g2(@$list){
583 8         12 my $indent=0;
584 8         26 $pending = $g2->_print($output,$indent,$indentwidth,0,1,$quote,$noblockquote,$depth,$group);
585 8 50       23 if ($pending) { print $output "\n"; }
  8         41  
586             }
587             }
588             else{
589 1         2 my $indent=0;
590 1         4 $pending = $g->_print($output,$indent,$indentwidth,0,1,$quote,$noblockquote,$depth,$group);
591 1 50       7 if ($pending) { print $output "\n"; }
  1         117  
592             }
593             }
594              
595             sub printnodes{
596 0     0 0 0 my ($g,%params)=@_;
597 0         0 my $list=$g->{list};
598 0         0 foreach(@$list){
599 0         0 $_->print(%params);
600             }
601             }
602              
603             sub dump{
604 0     0 0 0 my ($g,$file,%params)=@_;
605 0         0 $params{"quote"}=1;
606 0 0       0 open my $fh, ">$file" or return 0;
607 0         0 my $l=$g->{"list"};
608 0         0 foreach(@$l){
609 0         0 $_->print(%params,"filehandle"=>$fh);
610             }
611             }
612              
613              
614             ###############Path##############
615             sub splitPath{
616 3     3 0 6 my $path=shift;
617 3         4 my @paths;
618 3 50 33     31 if(!defined $path || $path eq "" || $path eq "."){return @paths;}
  0   33     0  
619 3         5 my $n=length($path);
620 3         5 my $j=0;
621 3         4 my $c;
622 3         5 my $s="";
623 3         8 while($j<$n){
624 27         35 my $c=substr($path,$j,1);$j++;
  27         23  
625 27 100       60 if($c eq '.' ){
    50          
626 7         11 push @paths,$s;
627 7         15 $s="";
628             }
629             elsif($c eq '\\'){
630 0 0       0 if($j==$n){$s=$s.$c;}
  0         0  
631             else{
632 0         0 $c=substr($path,$j,1);$j++;
  0         0  
633 0 0       0 if($c eq '.'){$s=$s.$c;}
  0         0  
  0         0  
634             else{$s=$s."\\$c";}
635             }
636             }
637             else{
638 20         41 $s=$s.$c;
639             }
640             }
641 3         5 push @paths,$s;
642 3         14 return @paths;
643             }
644             1;
645             __END__