File Coverage

lib/Text/ProcessMap.pm
Criterion Covered Total %
statement 371 568 65.3
branch 106 202 52.4
condition 39 89 43.8
subroutine 35 46 76.0
pod 5 5 100.0
total 556 910 61.1


line stmt bran cond sub pod time code
1             #======================================================================
2             #
3             # Text::ProcessMap
4             #
5             # Perl module which displays Activity Diagrams in plain text format.
6             #
7             # Copyright 2005, Brad J. Adkins. All rights reserved.
8             #
9             # This library is free software; you can redistribute it and/or modify
10             # it under the same terms as Perl itself.
11             #
12             # Address bug reports and comments to: .
13             #
14             #======================================================================
15            
16             package Text::ProcessMap;
17            
18 2     2   54250 use strict;
  2         4  
  2         61  
19 2     2   11 use Carp;
  2         4  
  2         157  
20 2     2   9 use File::Spec;
  2         6  
  2         11369  
21            
22             our $VERSION = '0.01';
23            
24             {
25             my %_attrs = (
26             _title => 'header',
27             _description => 'header',
28             _topnote => 'header',
29             _diagramnote => 'header',
30             _name => 'header',
31             _number => 'header',
32             _loader_file => 'header',
33             _output_file => 'header',
34             _minwidth => 'header',
35             _test => 'header',
36             _layout => 'body',
37             _coltitles => 'body',
38             _colwidths => 'body',
39             _boxchars => 'body',
40             _colsp => 'body'
41             );
42            
43             sub _accessible {
44 21     21   98 my ($self, $property, $method) = @_;
45            
46 21         37 $property = '_' . $property;
47 21 50 33     106 if ( exists $_attrs{$property} && $_attrs{$property} eq $method ) {
48 21         59 return 1;
49             } else {
50 0         0 croak("invalid property");
51             }
52             }
53            
54             sub _set {
55 21     21   32 my ($self, $property, $value) = @_;
56            
57 21         31 $property = '_' . $property;
58 21         80 $self->{$property} = $value;
59             }
60            
61             sub _get {
62 0     0   0 my ($self, $property) = @_;
63            
64 0         0 $property = '_' . $property;
65 0         0 $self->{$property};
66             }
67             }
68            
69             sub new {
70 2     2 1 33 my ($class, %params) = @_;
71            
72 2   50     310 my $self = bless {
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
      50        
73             _title => $params{title} || '',
74             _description => $params{description} || '',
75             _topnote => $params{topnote} || '',
76             _diagramnote => $params{diagramnote} || '',
77             _name => $params{name} || '',
78             _number => $params{number} || '',
79             _minwidth => $params{minwidth} || 0,
80             _layout => $params{layout} || 'stack',
81             _loader_file => $params{loader_file} || '',
82             _output_file => $params{output_file} || '',
83             _coltitles => $params{coltitles} || [],
84             _colwidths => $params{colwidths} || [],
85             _boxchars => $params{boxchars} || ["+", ".", "'", "`", "-", "|"],
86             _colsp => $params{colsp} || ' ',
87             _sp => $params{sp} || ' ',
88             _nl => $params{nl} || "\n",
89             _mlayout => [],
90             _mheight => [],
91             _fnotes => [],
92             _test => 0
93             }, $class;
94            
95 2         12 Text::ProcessMap::Object::_init();
96            
97 2         7 return $self;
98             }
99            
100             sub header {
101 4     4 1 32 my ($self, %params) = @_;
102            
103 4         13 foreach my $key ( keys %params ) {
104 17 50       37 if ( $self->_accessible($key, 'header') ) { $self->_set($key, $params{$key}); }
  17         38  
105             }
106             }
107            
108             sub body {
109 2     2 1 15 my ($self, %params) = @_;
110            
111 2         6 foreach my $key ( keys %params ) {
112 4 50       15 if ( $self->_accessible($key, 'body') ) { $self->_set($key, $params{$key}); }
  4         11  
113             }
114             }
115            
116             sub node {
117 8     8 1 29 my ($self, @args) = @_;
118 8         36 my %params = @args;
119            
120             # validate column argument
121 8         15 my $col = $params{col}; # column number range is 1..n
122 8 50 33     52 if ( !defined $col || $col < 1 ) { croak("invalid column number"); }
  0         0  
123            
124 8         29 my $obj = Text::ProcessMap::Object->new(@args, 'parent', $self);
125 8         21 my $row = $obj->_get_row;
126 8         11 @{ $self->{_mlayout}[$col] }[$row] = $obj;
  8         42  
127             }
128            
129             sub draw {
130 2     2 1 13 my ($self, $fd) = @_;
131            
132 2 50       10 $fd = '' unless $fd;
133 2         9 $self->_read_loader;
134 2 50       12 $fd = $self->{_output_file} if $self->{_output_file};
135            
136 2         6 local *OUTPUT;
137            
138             # choose between existing filehandle, filename, or stdout
139             SWITCH: {
140 2 50       4 if ( $fd =~ /::/ ) { *OUTPUT = $fd; last SWITCH; }
  2         8  
  0         0  
  0         0  
141 2 50       8 if ( $fd ) { open(OUTPUT, ">$fd") or croak("file open error"); last SWITCH; }
  2 50       270  
  2         14  
142 0         0 *OUTPUT = *STDOUT;
143             }
144            
145             # output the diagram
146 2         4 print OUTPUT @{$self->_build_header};
  2         9  
147 2         6 print OUTPUT @{$self->_build_body};
  2         8  
148 0         0 print OUTPUT @{$self->_build_footer};
  0         0  
149             }
150            
151             sub _build_header {
152 2     2   2 my $self = shift;
153 2         4 my @header;
154            
155 2         4 my $nl = $self->{_nl};
156 2         7 my $sp = $self->{_sp};
157 2         3 my $ml_imax = $#{$self->{_mlayout}} - 1;
  2         7  
158            
159             # must have at least one column
160 2 50       7 if ( $ml_imax < 0 ) {
161 0         0 croak "no columns defined";
162             }
163             # number of column titles must be same as number of columns
164 2 50       4 if ( $#{$self->{_coltitles}} != $ml_imax ) {
  2         7  
165 0         0 croak "columns/column-titles mismatch";
166             }
167             # number of column widths must be same as number of columns
168 2 50       4 if ( $#{$self->{_colwidths}} != $ml_imax ) {
  2         6  
169 0         0 croak "columns/column-widths mismatch";
170             }
171            
172 2 50       15 return \@header unless $self->_is_header_fancy; # empty
173            
174 2         7 push @header, $self->_separator_line('-');
175            
176 2         13 my $dwidth = $self->_display_width;
177             # title diaplay line
178 2 50       13 if ( $self->{_title} ) {
179 2         10 push @header, map { $_ .= $nl }
  2         6  
180             $self->_center_wrap($self->{_title}, $dwidth, $sp);
181             }
182             # description display line
183 2 50       7 if ( $self->{_description} ) {
184 2         7 push @header, map { $_ .= $nl }
  2         7  
185             $self->_center_wrap($self->{_description}, $dwidth, $sp);
186             }
187             # diagram number display line
188 2 50       8 if ( $self->{_number} ) {
189 2         9 push @header, map { $_ .= $nl }
  2         12  
190             $self->_center_wrap('Diagram Number ' . $self->{_number}, $dwidth, $sp);
191             }
192             # topnote display line
193 2 50       9 if ( $self->{_topnote} ) {
194 2         9 push @header, $self->_separator_line('-');
195 2         16 push @header, map { $_ .= $nl }
  2         14  
196             $self->_center_wrap($self->{_topnote}, $dwidth, $sp);
197             }
198 2         4 my $headline = '';
199 2         3 my $jstr;
200 2         5 for my $i ( 0 .. $ml_imax )
201             {
202 6 100       26 if ( $i < $ml_imax )
203             {
204 4         7 $jstr = '||'
205             }
206             else
207             {
208 2         2 $jstr = $self->{_nl};
209             }
210 6         7 $headline .= $self->_center(@{$self->{_coltitles}}[$i], @{$self->{_colwidths}}[$i]);
  6         14  
  6         17  
211 6         12 $headline .= $jstr;
212             }
213 2         15 push @header, $self->_separator_line('-');
214 2         5 push @header, $headline;
215 2         6 push @header, $self->_separator_line('-') . $self->{_nl};
216            
217 2         38 return \@header;
218             }
219            
220             # ---------------------------------------------------------------------
221             # _build_body
222             #
223             # Build the body section of the diagram. This is done using either a
224             # stack layout or a matrix layout. When stacking, the column objects
225             # are aligned one atop the other with no vertical spacing. When using
226             # a matrix layout, the column objects are vertically aligned at their
227             # top and spaced one object per row, a row can be empty for any given
228             # column. The default layout is stack.
229             # ---------------------------------------------------------------------
230             sub _build_body {
231 2     2   3 my $self = shift;
232            
233 2         5 my $sp = $self->{_sp};
234 2         4 my $nl = $self->{_nl};
235 2         2 my $colsp = $self->{_colsp};
236 2         4 my @clines; # aoa of lines representing node objects
237            
238             # check layout requested
239 2 50       9 unless ( $self->{_layout} =~ /^stack$|^matrix$/ )
240             {
241 0         0 croak("invalid layout specificied");
242             }
243            
244 2         4 my $numcols = $#{$self->{_mlayout}};
  2         4  
245            
246             # using stack layout
247 2 50       9 if ( $self->{_layout} eq 'stack' )
248             {
249 2         4 for my $col ( 0 .. $numcols )
250             {
251 2         4 my $numrows = $#{ @{ $self->{_mlayout}[$col] } };
  2         3  
  2         10852  
252 0         0 for my $row ( 1 .. $numrows )
253             {
254 0 0       0 if ( defined @{ $self->{_mlayout}[$col] }[$row] )
  0         0  
255             {
256 0         0 my $obj = @{ $self->{_mlayout}[$col] }[$row];
  0         0  
257 0         0 push @{ $clines[$col - 1] }, @{ $obj->{_boxlines} };
  0         0  
  0         0  
258             }
259             }
260             }
261             }
262            
263             # using matrix layout
264 0 0       0 if ( $self->{_layout} eq 'matrix' )
265             {
266             # determine row heights and save to mheight array
267 0         0 for my $col ( 0 .. $numcols )
268             {
269 0         0 my $numrows = $#{ @{ $self->{_mlayout}[$col] } };
  0         0  
  0         0  
270 0         0 for my $row ( 1 .. $numrows )
271             {
272 0 0       0 if ( !defined @{ $self->{_mheight} }[$row] )
  0         0  
273             {
274 0         0 @{ $self->{_mheight} }[$row] = 0;
  0         0  
275             }
276 0 0       0 if ( defined @{ $self->{_mlayout}[$col] }[$row] )
  0         0  
277             {
278 0         0 my $obj = @{ $self->{_mlayout}[$col] }[$row];
  0         0  
279 0         0 my $rheight = $obj->_get_height;
280 0 0       0 if ( !defined @{ $self->{_mheight} }[$row] )
  0         0  
281             {
282 0         0 @{ $self->{_mheight} }[$row] = 0;
  0         0  
283             }
284 0 0       0 if ( $rheight > @{ $self->{_mheight} }[$row] )
  0         0  
285             {
286 0         0 @{ $self->{_mheight} }[$row] = $rheight;
  0         0  
287             }
288             }
289             }
290             }
291             # create blank column objects
292 0         0 for my $col ( 0 .. $numcols )
293             {
294 0         0 my $numrows = $#{ @{ $self->{_mlayout}[$col] } };
  0         0  
  0         0  
295 0         0 for my $row ( 1 .. $numrows )
296             {
297 0 0       0 if ( !defined @{ $self->{_mlayout}[$col] }[$row] )
  0         0  
298             {
299             # create a blank object using prev object attributes
300 0         0 my $connect = ' ';
301 0         0 my $boxheight = 0;
302 0 0       0 if ( $row > 1 )
303             {
304             # prev object connect attribute
305 0         0 my $pobj = @{ $self->{_mlayout}[$col] }[$row-1];
  0         0  
306 0         0 $connect = $pobj->_get_connect;
307             # current row height
308 0         0 $boxheight = @{ $self->{_mheight} }[$row];
  0         0  
309             }
310             # create blank object
311 0         0 my $obj = Text::ProcessMap::Object->new(
312             parent => $self,
313             col => $col,
314             row => $row,
315             type => 'blank',
316             boxheight => $boxheight,
317             connect => $connect,
318             border => 'off' );
319             # store blank object in layout
320 0         0 @{ $self->{_mlayout}[$col] }[$row] = $obj;
  0         0  
321             }
322             }
323             }
324             # output column objects
325 0         0 for my $col ( 0 .. $numcols )
326             {
327 0         0 my $numrows = $#{ @{ $self->{_mlayout}[$col] } };
  0         0  
  0         0  
328 0         0 for my $row ( 1 .. $numrows )
329             {
330 0 0       0 if ( defined @{ $self->{_mlayout}[$col] }[$row] )
  0         0  
331             {
332 0         0 my $obj = @{ $self->{_mlayout}[$col] }[$row];
  0         0  
333            
334 0         0 my $height = @{ $self->{_mheight} }[$row];
  0         0  
335 0         0 $obj->_pad($col-1, $height);
336            
337 0         0 push @{ $clines[$col - 1] }, @{ $obj->{_boxlines} };
  0         0  
  0         0  
338             }
339             }
340             }
341             }
342            
343             # get max column lines
344 0         0 my @aomax;
345 0         0 for my $i ( 0 .. $numcols ) {
346 0         0 push @aomax, $#{ $clines[$i] } - 1;
  0         0  
347             }
348 0         0 @aomax = sort _numerically(@aomax);
349 0         0 my $linmax = $aomax[0]; # max column lines
350            
351             # pad all columns to same length
352 0         0 for my $i ( 0 .. $numcols - 1) {
353 0         0 my $numrows = $#{ $clines[$i] }; # number of rows in this column
  0         0  
354 0         0 my $colwid = @{$self->{_colwidths}}[$i]; # width of this column
  0         0  
355 0         0 push @{ $clines[$i] }, map { $sp x $colwid } $numrows .. $linmax;
  0         0  
  0         0  
356             }
357            
358             # nest three columns into one array
359 0         0 my @body;
360 0         0 for my $i ( 0 .. $linmax ) {
361 0         0 my $line = '';
362 0         0 for my $j ( 0 .. $numcols - 1) {
363 0 0       0 my $glue = $j < $numcols - 1 ? $colsp : $nl;
364 0         0 $line .= $clines[$j][$i] . $glue;
365             }
366 0         0 push @body, $line;
367             }
368            
369 0         0 push @body, $nl;
370             # add diagramnote after the diagram
371 0 0       0 if ( $self->{_diagramnote} ) {
372 0         0 my $dwidth = $self->_display_width;
373 0         0 push @body, map { $_ .= $nl }
  0         0  
374             $self->_center_wrap($self->{_diagramnote}, $dwidth, $sp);
375 0         0 push @body, $nl;
376             }
377            
378 0         0 return \@body; # ref to array of body lines
379             }
380            
381             sub _build_footer {
382 0     0   0 my $self = shift;
383 0         0 my @footnotes;
384 0         0 my $fcnt = 0;
385 0         0 my $fln = '';
386            
387 0 0       0 return \@footnotes unless $self->_is_footer_fancy; # empty
388            
389 0         0 push @footnotes, $self->_separator_line('-');
390 0         0 my $sp = $self->{_sp};
391            
392             # check for footnotes and output as required
393 0 0       0 if ( $#{$self->{_fnotes}} > 0 ) {
  0         0  
394 0         0 push @footnotes, 'Footnotes:' . $self->{_nl};
395 0         0 foreach my $note ( @{ $self->{_fnotes} } ) {
  0         0  
396 0         0 my $pad = length($note->{_id}) + 1;
397 0         0 push @footnotes, $note->{_id} . ':' . $note->{_short_name} . $self->{_nl} . $self->{_sp} x $pad . $note->{_long_name} . $self->{_nl};
398 0         0 $fcnt++;
399             }
400             }
401 0 0       0 if ( $fcnt ) {
402 0         0 push @footnotes, $self->_separator_line('-');
403             }
404            
405             # add page footer
406 0 0       0 if ( $self->{_name} ) {
407 0         0 $fln = $self->{_name};
408 0         0 $fln = $self->_append_right($fln, $self->_printed, $self->_display_width);
409 0         0 push @footnotes, $fln;
410 0         0 push @footnotes, $self->_separator_line('-');
411             }
412            
413 0         0 return \@footnotes;
414             }
415            
416             sub _read_loader {
417 2     2   6 my $self = shift;
418 2         4 my %kvps;
419 2         2 my ($key, $val);
420 0         0 my (@elem, @boxc, @colt, @colw);
421 0         0 my $section;
422 0         0 my $column;
423            
424 2 50       9 unless ( $self->{_loader_file} ) { return; }
  0         0  
425            
426 2 50       112 open(LOAD, $self->{_loader_file}) || die "unable to open definition file";
427 2         48 while ( ) {
428 99         123 chomp;
429 99         197 s/^\s+//;
430 99         318 s/\s+$//;
431 99 100       236 next unless $_;
432 79 50       171 next if /^#/; # comments
433            
434 79 100       149 if ( /^\[/ ) { # start new section
435 10 50       72 if ( /(header|body|column\s+(\d{1,}))/i ) { # start of section
436 10         27 $section = $1;
437 10         18 $column = $2;
438 10         28 $section =~ s/\s+\d{1,}//;
439 10         18 %kvps = ();
440 10         17 @elem = ();
441 10         32 @boxc = ();
442 10         25 @colt = ();
443 10         14 @colw = ();
444 10         33 next;
445             }
446             }
447 69 100       176 if ( $section =~ /header/i ) { # header contains only kvps
448 13 100       30 unless ( /^put/ ) {
449 11         29 $key = $self->_get_key($_);
450 11         46 $val = $self->_get_val($_);
451 11         26 $kvps{$key} = $val;
452             }
453             }
454 69 100       155 if ( $section =~ /body/i ) { # body section can contain kvps and array defs
455 6 100       20 if ( /^boxchars|^coltitles|^colwidths/i ) {
456 4 50       14 unless ( /^put/i ) {
457 4 50       20 if ( /^boxchars/i ) {
458 0         0 @boxc = $self->_get_arr($_);
459             }
460 4 100       14 if ( /^coltitles/i ) {
461 2         10 @colt = $self->_get_arr($_);
462             }
463 4 100       15 if ( /^colwidths/i ) {
464 2         80 @colw = $self->_get_arr($_);
465             }
466             }
467             } else {
468 2 50       9 unless ( /^put/i ) {
469 0         0 $key = $self->_get_key($_);
470 0         0 $val = $self->_get_val($_);
471 0         0 $kvps{$key} = $val;
472             }
473             }
474             }
475 69 100       163 if ( $section =~ /column/i ) {
476 50 100       109 if ( /^element/i ) {
477 22         43 push @elem, $self->_get_val($_);
478             } else {
479 28 100       72 unless ( /^put/i ) {
480 20         42 $key = $self->_get_key($_);
481 20         45 $val = $self->_get_val($_);
482 20         46 $kvps{$key} = $val;
483             }
484             }
485             }
486 69 100       278 if ( /^put/i ) { # /
487 12 100       31 if ( $section =~ /body/i ) {
488 2 50       14 if ( @boxc ) { $kvps{boxchars} = [ @boxc ]; }
  0         0  
489 2 50       7 if ( @colt ) { $kvps{coltitles} = [ @colt ]; }
  2         7  
490 2 50       8 if ( @colw ) { $kvps{colwidths} = [ @colw ]; }
  2         7  
491 2         9 $self->body(%kvps);
492             }
493 12 100       31 if ( $section =~ /header/i ) {
494 2         10 $self->header(%kvps);
495             }
496 12 100       36 if ( $section =~ /column/i ) {
497 8         24 $kvps{elements} = [ @elem ];
498 8         18 $kvps{col} = $column;
499 8         61 $self->node(%kvps);
500             }
501 12         30 %kvps = ();
502 12         19 @elem = ();
503 12         75 next;
504             }
505             }
506 2         27 close(LOAD);
507             }
508            
509             sub _numerically {
510 0     0   0 $b <=> $a; # reverse numeric sort
511             }
512            
513             sub _append_right {
514 0     0   0 my ($self, $basestr, $addstr, $width) = @_;
515            
516 0         0 my $pad = $width - length($basestr) - length($addstr);
517 0         0 return $basestr . $self->{_sp} x $pad . $addstr . $self->{_nl};
518             }
519            
520             sub _center {
521 6     6   8 my ($self, $str, $width) = @_;
522            
523 6 50       15 if ( length($str) >= $width) {
524 0         0 return substr($str, 0, $width);
525             }
526            
527 6         11 my $lead = int(($width - length($str)) / 2);
528 6         9 my $trail = int($width - (length($str) + $lead));
529            
530 6         21 return $self->{_sp} x $lead . $str . $self->{_sp} x $trail;
531             }
532            
533             sub _center_wrap {
534 8     8   44 my ($self, $str, $width, $sp) = @_;
535 8         9 my $tmp;
536             my @w;
537            
538 8         9 $width = $width;
539 8         35 $str =~ s/\s+/ /g;
540 8         30 my @str = split ' ', $str;
541            
542 8         14 @str = map { $self->_cwfix($_, $width) } @str;
  28         46  
543            
544 8         15 my $ll = 0;
545 8         24 while (@str) {
546 28         38 my $w = shift(@str);
547 28 50       42 if ($ll + length($w) > $width) {
548 0         0 push @w, $tmp;
549 0         0 $ll = length($w) + 1;
550 0         0 $tmp = $w . $sp;
551             } else {
552 28         36 $tmp .= $w . $sp;
553 28         55 $ll += length($w) + 1;
554             }
555             }
556 8 50       21 push @w, $tmp if $tmp;
557            
558 8         10 @w = map { $self->_cwctr($_, $width, $sp) } @w;
  8         17  
559 8         18 return @w;
560             }
561            
562             sub _cwfix {
563 28     28   38 my ($self, $str, $width) = @_;
564 28 50       49 if ( length($str) > $width ) {
565 0         0 $str = substr($str, 0, $width - 1) . '~';
566             }
567 28         67 return $str;
568             }
569            
570             sub _cwctr {
571 8     8   13 my ($self, $str, $width, $sp) = @_;
572 8         51 $str =~ s/^\s+|\s+$//g;
573 8         18 my $lead = int(($width - length($str)) / 2);
574 8         12 my $tail = int($width - (length($str) + $lead));
575 8         36 return $sp x $lead . $str . $sp x $tail;
576             }
577            
578             sub _is_header_fancy {
579 2     2   4 my $self = shift;
580 2 0 33     14 if ( $self->{_title} || $self->{_description} || $self->{_number} ) {
      33        
581 2         8 return 1;
582             }
583 0         0 return 0;
584             }
585            
586             sub _is_footer_fancy {
587 0     0   0 my $self = shift;
588 0 0 0     0 if ( $#{$self->{_fnotes}} > 0 || $self->{_name} ) {
  0         0  
589 0         0 return 1;
590             }
591 0         0 return 0;
592             }
593            
594             sub _body_width {
595 10     10   11 my $self = shift;
596            
597 10         11 my $numcols = $#{$self->{_mlayout}} - 1;
  10         22  
598 10         12 my $bwidth = 0;
599            
600 10         20 for my $i ( 0 .. $numcols ) {
601 30         27 $bwidth += @{$self->{_colwidths}}[$i];
  30         53  
602             }
603 10         17 $bwidth += ($numcols) * 2; # add space between cols
604 10         17 return $bwidth;
605             }
606            
607             sub _display_width {
608 10     10   11 my $self = shift;
609            
610 10         20 my $bwidth = $self->_body_width;
611 10         12 my $mwidth = $self->{_minwidth};
612 10 50       25 return $mwidth > $bwidth ? $mwidth : $bwidth;
613             }
614            
615             sub _printed {
616 0     0   0 my ($self) = @_;
617            
618 0 0       0 if ( $self->{_test} ) { return ' 00/00/0000'; }
  0         0  
619            
620 0         0 my ($sec, $min, $hr, $dy, $mo, $yr, $wd, $doy, $dst) = localtime(time);
621 0         0 return sprintf(" %02d/%02d/%04d", $mo + 1, $dy, $yr += 1900);
622             }
623            
624             sub _separator_line {
625 8     8   77 my ($self, $char) = @_;
626            
627 8         16 my $dwidth = $self->_display_width;
628 8         28 return $char x $dwidth . $self->{_nl};
629             }
630            
631             sub _get_key {
632 31     31   49 my ($self, $str) = @_;
633 31         81 my ($k,$v) = split '=', $str;
634 31         61 $k =~ s/^\s+//;
635 31         154 $k =~ s/\s+$//;
636 31         70 return $k;
637             }
638            
639             sub _get_val {
640 53     53   70 my ($self, $str) = @_;
641 53         113 my ($k,$v) = split '=', $str;
642 53         180 $v =~ s/^\s+//;
643 53         174 $v =~ s/\s+$//;
644 53         114 return $v;
645             }
646            
647             sub _get_arr {
648 4     4   7 my ($self, $str) = @_;
649 4         13 my ($k,$v) = split '=', $str;
650 4         15 $v =~ s/^\s+//;
651 4         11 $v =~ s/\s+$//;
652 4         22 my @items = split ',', $v; # extract list items
653 4         9 @items = map {_trim($_)} @items; # trim list items
  12         21  
654 4         16 return @items;
655             }
656            
657             sub _trim {
658 12     12   17 my $s = shift;
659 12         27 $s =~ s/^\s+//g;
660 12         18 $s =~ s/\s+$//g;
661 12         36 return $s;
662             }
663            
664             1;
665            
666             # ---------------------------------------------------------------------
667             # package Text::ProcessMap::Object;
668             #
669             # When a new box is instantiated, the box object immediately invokes
670             # a function to build an array of lines representing the box and store
671             # those lines inside of the box object. The array of lines is justified
672             # and bordered using preferences supplied by the parent object. This
673             # allows the height of the box to be calculated and stored at the same
674             # time the box is instantiated.
675             # ---------------------------------------------------------------------
676             package Text::ProcessMap::Object;
677            
678 2     2   23 use Carp;
  2         4  
  2         4977  
679            
680             our $ccol = 1; # current column
681             our $crow = 0; # current row
682            
683             sub new {
684 8     8   46 my ($class, %params) = @_;
685            
686 8   50     392 my $self = bless {
      100        
      50        
      50        
      50        
      50        
      50        
      50        
      100        
      100        
      100        
      50        
      50        
687             _parent => $params{parent},
688             _col => $params{col},
689             _row => $params{row} || 0, # new 2/21
690             _id => $params{id} || '',
691             _title => $params{title} || '',
692             _elements => $params{elements} || [],
693             _in => $params{in} || '-',
694             _out => $params{out} || '-',
695             _connect => $params{connect} || '',
696             _vertex => $params{vertex} || '', # new 2/27
697             _header => $params{header} || '', # new 2/21
698             _footer => $params{footer} || '', # new 2/21
699             _type => $params{type} || 'box', # new 2/21
700             _border => $params{border} || 'on', # new 2/21
701             _boxheight => $params{boxheight} || 0, # new 2/27
702             _subtype => 0, # new 2/21
703             _footnotes => [],
704             _boxlines => [],
705             }, $class;
706            
707 8 50       35 unless ( $self->{_connect} ) {
708 8         20 $self->{_connect} = $self->{_parent}->{_sp};
709             }
710            
711 8 100       23 if ( $self->{_type} =~ /^arrow/ ) # get arrow extended attributes
712             {
713 1         5 $self->{_type} =~ /^arrow:(\d)/;
714 1   50     6 $self->{_subtype} = $1 || 0;
715 1         3 $self->{_type} = 'arrow';
716 1         2 $self->{_border} = 'off';
717 1         4 $self->{_sp} = $self->{_parent}->{_sp};
718            
719 1 50       10 if ( $self->{_subtype} > 3 ) {
720 0         0 croak("invalid arrow type");
721             }
722             }
723            
724 8 50       29 unless ( $self->{_type} =~ /^box$|^arrow$|^blank$/ ) {
725 0         0 croak("invalid type");
726             }
727            
728 8 50       27 unless ( $self->{_border} =~ /^on$|^off$/ ) {
729 0         0 croak("invalid border type");
730             }
731            
732             # store row info, row is automatically generated if not given
733 8 50       26 if ( $self->{_type} =~ /^arrow$|^box$/ )
734             {
735 8         11 $crow++;
736 8 100       20 if ( $self->{_col} > $ccol ) { $ccol = $self->{_col}; $crow = 1;}
  4         8  
  4         8  
737 8 50 33     26 if ( $self->{_row} > 0 && $self->{_row} < $crow ) { croak("invalid row sequence"); }
  0         0  
738 8 50       24 if ( $self->{_row} > $crow ) { $crow = $self->{_row}; }
  0         0  
739 8         12 $self->{_row} = $crow;
740             }
741            
742 8         21 $self->_build_box;
743            
744 8         26 return $self;
745             }
746            
747             sub _init {
748 2     2   4 $ccol = 1; # reset current column
749 2         5 $crow = 0; # reset current row
750             }
751            
752             sub _get_row {
753 8     8   10 my $self = shift;
754 8         18 return $self->{_row};
755             }
756            
757             sub _get_height {
758 0     0   0 my $self = shift;
759 0         0 return $self->{_boxheight};
760             }
761            
762             sub _get_connect {
763 0     0   0 my $self = shift;
764 0         0 return $self->{_connect};
765             }
766            
767             # ---------------------------------------------------------------------
768             # _build_box
769             #
770             # Build the array containing box lines for this box. The lines produced
771             # comprise a complete image of this particular box instance. The lines
772             # are stored inside the box object for later reference.
773             # ---------------------------------------------------------------------
774             sub _build_box {
775 8     8   10 my $self = shift;
776            
777 8         13 my $parent = $self->{_parent};
778 8         11 my ($tlch, $trch, $brch, $blch, $hch, $vch) = @{ $parent->{_boxchars} };
  8         20  
779 8         16 my $width = @{$parent->{_colwidths}}[$self->{_col}-1]; # this column width
  8         16  
780 8         30 my $sp = $parent->{_sp}; # space char
781 8         11 my $center = 1; # default centered, TODO all user defined, stored in parent
782 8         9 my $border = 0;
783 8 100       31 if ( $self->{_border} eq 'on' ) {
784 7         10 $border = 1;
785             } else {
786 1         4 ($tlch, $trch, $brch, $blch, $hch, $vch) = ($sp, $sp, $sp, $sp, $sp, $sp);
787             }
788            
789 8 100       22 if ( $self->{_type} eq 'box' )
790             {
791 7 50       23 if ( $border )
792             {
793 7         9 push @{ $self->{_boxlines} },
  7         25  
794             $self->_box_line($hch, $tlch, $trch, $self->{_in}, $width);
795             }
796 7 100       20 if ( $self->{_header} )
797             {
798 2         3 push @{ $self->{_boxlines} },
  2         8  
799             $self->_wrap($self->{_header}, $width, $vch, $sp, 1);
800 2         5 push @{ $self->{_boxlines} },
  2         7  
801             $self->_box_line($hch, $vch, $vch, '', $width);
802             }
803 7 50       43 if ( $self->{_id} )
804             {
805 7         13 push @{ $self->{_boxlines} },
  7         32  
806             $self->_wrap('['.$self->{_id}.']', $width, $vch, $sp, 1);
807             }
808 7 50       25 if ( $self->{_title} )
809             {
810 7         9 push @{ $self->{_boxlines} },
  7         20  
811             $self->_wrap($self->{_title}, $width, $vch, $sp, 1);
812             }
813 7 50       39 if ( $#{ $self->{_elements} } > -1 )
  7         28  
814             {
815 7         6 foreach my $line ( @{ $self->{_elements} } )
  7         16  
816             {
817 22         22 push @{ $self->{_boxlines} },
  22         59  
818             $self->_wrap($line, $width, $vch, $sp, 1);
819             }
820             }
821 7 100       24 if ( $self->{_footer} )
822             {
823 2         79 push @{ $self->{_boxlines} },
  2         9  
824             $self->_box_line($hch, $vch, $vch, '', $width);
825 2         3 push @{ $self->{_boxlines} },
  2         8  
826             $self->_wrap($self->{_footer}, $width, $vch, $sp, 1);
827             }
828 7 50       17 if ( $border )
829             {
830 7         7 push @{ $self->{_boxlines} },
  7         24  
831             $self->_box_line($hch, $blch, $brch, $self->{_out}, $width);
832             }
833             }
834            
835 8 100       25 if ( $self->{_type} eq 'arrow' )
836             {
837 1         15 push @{ $self->{_boxlines} },
  1         5  
838             $self->_box_line($sp, $sp, $sp, $self->{_connect}, $width);
839 1 50       5 if ( $self->{_title} )
840             {
841 1         2 push @{ $self->{_boxlines} },
  1         5  
842             $self->_wrap($self->{_title}, $width, $sp, $sp, 1);
843             }
844 1         2 push @{ $self->{_boxlines} },
  1         5  
845             $self->_arr_line($width);
846             }
847            
848 8 50       20 if ( $self->{_type} eq 'blank' )
849             {
850 0 0       0 if ( $self->{_boxheight} > 0 )
851             {
852 0         0 for ( 1 .. $self->{_boxheight} - 1 )
853             {
854 0         0 push @{ $self->{_boxlines} },
  0         0  
855             $self->_box_line($sp, $sp, $sp, $self->{_connect}, $width);
856             }
857             }
858             }
859            
860             # all objects get connect space
861 8 50       39 if ( $self->{_connect} )
862             {
863 8         9 push @{ $self->{_boxlines} },
  8         31  
864             $self->_box_line($sp, $sp, $sp, $self->{_connect}, $width);
865             }
866            
867             # store the height
868 8         12 $self->{_boxheight} = $#{ $self->{_boxlines} } + 1; # overall height
  8         29  
869             }
870            
871             # ---------------------------------------------------------------------
872             # _pad
873             #
874             # Pad object height to specified number of rows. If a connect char has
875             # been specified for this object, use that char when padding.
876             # ---------------------------------------------------------------------
877             sub _pad {
878 0     0   0 my ($self, $col, $height) = @_;
879            
880 0 0       0 if ( $height > $self->{_boxheight} )
881             {
882 0         0 my $parent = $self->{_parent};
883 0         0 my $width = @{ $parent->{_colwidths} }[$col]; # this column width
  0         0  
884 0         0 my $sp = $parent->{_sp};
885 0         0 for ( $self->{_boxheight} .. $height - 1 )
886             {
887 0         0 push @{ $self->{_boxlines} },
  0         0  
888             $self->_box_line($sp, $sp, $sp, $self->{_connect}, $width);
889             }
890             }
891             }
892            
893             # ---------------------------------------------------------------------
894             # _wrap
895             #
896             # Accept a string and wrap it to multiple lines of the specified width.
897             # Either left justify or center justify the lines depending on
898             # argument. Any single word which is longer than the specified width
899             # is automatically footnoted and the footnote object created is stored
900             # in the box's parent object. The string is returned as an array of
901             # lines, each line bordered by the specified border char.
902             #
903             # used by:
904             # Text::ProcessMap::Object::new
905             #
906             # uses:
907             # _wftn, _wctr, _wlft
908             # ---------------------------------------------------------------------
909             sub _wrap {
910 41     41   71 my ($self, $str, $width, $echar, $sp, $center) = @_;
911 41         47 my $tmp;
912             my @w;
913            
914 41         53 $width = $width - 2;
915 41         139 $str =~ s/\s+/ /g;
916 41         109 my @str = split ' ', $str;
917            
918 41         65 @str = map { $self->_wftn($_, $width) } @str;
  89         161  
919            
920 41         59 my $ll = 0;
921 41         80 while (@str) {
922 89         149 my $w = shift(@str);
923 89 100       222 if ($ll + length($w) > $width) {
924 4         6 push @w, $tmp;
925 4         5 $ll = length($w) + 1;
926 4         12 $tmp = $w . $sp;
927             } else {
928 85         110 $tmp .= $w . $sp;
929 85         186 $ll += length($w) + 1;
930             }
931             }
932 41 50       92 push @w, $tmp if $tmp;
933            
934 41 50       69 if ( $center ) {
935 41         50 @w = map { $echar . $self->_wctr($_, $width, $sp) . $echar } @w;
  45         96  
936             } else {
937 0         0 @w = map { $echar . $self->_wlft($_, $width, $sp) . $echar } @w;
  0         0  
938             }
939            
940 41         125 return @w;
941             }
942            
943             # ---------------------------------------------------------------------
944             # _wftn
945             #
946             # Create footnote for word longer than the specified width.
947             #
948             # used by: _wrap
949             # ---------------------------------------------------------------------
950             sub _wftn {
951 89     89   180 my ($self, $str, $width) = @_;
952             # handle single words longer than width
953 89 50       156 if ( length($str) > $width ) {
954 0         0 my $longstr = $str;
955 0         0 $str = substr($str, 0, $width - 1) . '~';
956            
957             # create a new footnote object to hold long text
958 0         0 my $note = Text::ProcessMap::Footnote->new( # create footnote object
959             parent => $self,
960             id => $self->{_id},
961             long_name => $longstr,
962             short_name => $str,
963             );
964             # store footnote object in parent object
965 0   0     0 push @{$self->{_parent}->{_fnotes}}, $note || croak("box stack error");
  0         0  
966            
967             }
968 89         257 return $str;
969             }
970            
971             # ---------------------------------------------------------------------
972             # _wlft
973             #
974             # Left justify string using specified width.
975             #
976             # used by: _wrap
977             # ---------------------------------------------------------------------
978             sub _wlft {
979 0     0   0 my ($self, $str, $width, $sp) = @_;
980 0         0 $str =~ s/^\s+|\s+$//g;
981 0         0 my $tail = int($width - (length($str)));
982 0         0 return $str . $sp x $tail;
983             }
984            
985             # ---------------------------------------------------------------------
986             # _wctr
987             #
988             # Center string using specified width.
989             #
990             # used by: _wrap
991             # ---------------------------------------------------------------------
992             sub _wctr {
993 45     45   75 my ($self, $str, $width, $sp) = @_;
994 45         232 $str =~ s/^\s+|\s+$//g;
995 45         93 my $lead = int(($width - length($str)) / 2);
996 45         96 my $tail = int($width - (length($str) + $lead));
997 45         209 return $sp x $lead . $str . $sp x $tail;
998             }
999            
1000             # ---------------------------------------------------------------------
1001             # _arr_line
1002             #
1003             # Build an arrow object.
1004             # ---------------------------------------------------------------------
1005             sub _arr_line {
1006 1     1   4 my ($self, $width) = @_;
1007            
1008 1         2 my $subtype = $self->{_subtype};
1009 1         3 my $sp = $self->{_sp};
1010            
1011 1         3 my $al = '-' x ($width - 2); # arrow line
1012 1 50       4 if ( $subtype == 0 ) { $al = '-'.$al.'-'; }
  0         0  
1013 1 50       4 if ( $subtype == 1 ) { $al = '<'.$al.'.'; }
  0         0  
1014 1 50       3 if ( $subtype == 2 ) { $al = '<'.$al.'>'; }
  0         0  
1015 1 50       3 if ( $subtype == 3 ) { $al = '-'.$al.'>'; }
  1         3  
1016 1         3 return $al;
1017             }
1018            
1019             sub _box_line {
1020 27     27   69 my ($self, @args) = @_;
1021 27         51 my ($hc, $lc, $rc, $cc, $width) = @args;
1022            
1023 27 100       53 $cc = '' unless $cc;
1024            
1025 27         28 my $tempc = '~'; # use a temp char to build the string initially
1026 27         40 my $ww = $width - 2;
1027 27         42 my $str = $tempc x $ww;
1028 27         107 my $clen = length($cc);
1029 27         55 my $cloc = int($ww / 2) - int($clen / 2) - 1;
1030            
1031 27         66 $str = substr($str,0,$cloc) . $cc . substr($str,$cloc+$clen,$ww);
1032 27         410 $str =~ s/$tempc/$hc/g; # replace the temp chars with real chars
1033 27         121 return $lc . $str . $rc;
1034             }
1035            
1036             1;
1037            
1038             package Text::ProcessMap::Footnote;
1039            
1040             sub new {
1041 0     0     my ($class, %params) = @_;
1042            
1043 0   0       my $self = bless {
      0        
      0        
      0        
1044             _parent => $params{parent},
1045             _col => $params{col} || '',
1046             _id => $params{id} || '',
1047             _long_name => $params{long_name} || '',
1048             _short_name => $params{short_name} || ''
1049             }, $class;
1050            
1051 0           return $self;
1052             }
1053            
1054             1;
1055            
1056             __END__