File Coverage

lib/Graph/Easy/Parser.pm
Criterion Covered Total %
statement 581 601 96.6
branch 233 310 75.1
condition 92 141 65.2
subroutine 52 53 98.1
pod 5 5 100.0
total 963 1110 86.7


line stmt bran cond sub pod time code
1             #############################################################################
2             # Parse text definition into a Graph::Easy object
3             #
4             #############################################################################
5              
6             package Graph::Easy::Parser;
7              
8 19     19   84560 use Graph::Easy;
  19         31  
  19         622  
9              
10             $VERSION = '0.76';
11 19     19   75 use Graph::Easy::Base;
  19         20  
  19         468  
12             @ISA = qw/Graph::Easy::Base/;
13 19     19   68 use Scalar::Util qw/weaken/;
  19         16  
  19         841  
14              
15 19     19   201 use strict;
  19         811  
  19         363  
16 19     19   60 use warnings;
  19         15  
  19         475  
17 19     19   54 use constant NO_MULTIPLES => 1;
  19         17  
  19         1002  
18              
19 19     19   74 use Graph::Easy::Util qw(ord_values);
  19         19  
  19         97946  
20              
21             sub _init
22             {
23 81     81   95 my ($self,$args) = @_;
24              
25 81         182 $self->{error} = '';
26 81         94 $self->{debug} = 0;
27 81         103 $self->{fatal_errors} = 1;
28              
29 81         272 foreach my $k (sort keys %$args)
30             {
31 117 50       356 if ($k !~ /^(debug|fatal_errors)\z/)
32             {
33 0         0 require Carp;
34 0         0 my $class = ref($self);
35 0         0 Carp::confess ("Invalid argument '$k' passed to $class" . '->new()');
36             }
37 117         139 $self->{$k} = $args->{$k};
38             }
39              
40             # what to replace the matched text with
41 81         158 $self->{replace} = '';
42 81         102 $self->{attr_sep} = ':';
43             # An optional regexp to remove parts of an autosplit label, used by Graphviz
44             # to remove " ":
45 81         100 $self->{_qr_part_clean} = undef;
46              
47             # setup default class names for generated objects
48             $self->{use_class} = {
49 81         281 edge => 'Graph::Easy::Edge',
50             group => 'Graph::Easy::Group',
51             graph => 'Graph::Easy',
52             node => 'Graph::Easy::Node',
53             };
54              
55 81         160 $self;
56             }
57              
58             sub reset
59             {
60             # reset the status of the parser, clear errors etc.
61 802     802 1 64245 my $self = shift;
62              
63 802         1129 $self->{error} = '';
64 802         859 $self->{anon_id} = 0;
65 802         882 $self->{cluster_id} = ''; # each cluster gets a unique ID
66 802         764 $self->{line_nr} = -1;
67 802         1027 $self->{match_stack} = []; # patterns and their handlers
68              
69 802         17947 $self->{clusters} = {}; # cluster names we already created
70              
71 802         1898 Graph::Easy::Base::_reset_id(); # start with the same set of IDs
72              
73             # After "[ 1 ] -> [ 2 ]" we push "2" on the stack and when we encounter
74             # " -> [ 3 ]" treat the stack as a node-list left of "3".
75             # In addition, for " [ 1 ], [ 2 ] => [ 3 ]", the stack will contain
76             # "1" and "2" when we encounter "3".
77 802         868 $self->{stack} = [];
78              
79 802         990 $self->{group_stack} = []; # all the (nested) groups we are currently in
80 802         918 $self->{left_stack} = []; # stack for the left side for "[]->[],[],..."
81 802         932 $self->{left_edge} = undef; # for -> [A], [B] continuations
82              
83 802         2653 Graph::Easy->_drop_special_attributes();
84              
85             $self->{_graph} = $self->{use_class}->{graph}->new( {
86             debug => $self->{debug},
87             strict => 0,
88             fatal_errors => $self->{fatal_errors},
89 802         3897 } );
90              
91 802         1878 $self;
92             }
93              
94             sub from_file
95             {
96             # read in entire file and call from_text() on the contents
97 2     2 1 14 my ($self,$file) = @_;
98              
99 2 50       27 $self = $self->new() unless ref $self;
100              
101 2         3 my $doc;
102 2         8 local $/ = undef; # slurp mode
103             # if given a reference, assume it is a glob, or something like that
104 2 50       7 if (ref($file))
105             {
106 0 0       0 binmode $file, ':utf8' or die ("binmode '$file', ':utf8' failed: $!");
107 0         0 $doc = <$file>;
108             }
109             else
110             {
111 2 50       84 open my $PARSER_FILE, $file or die (ref($self).": Cannot read $file: $!");
112 2 50       15 binmode $PARSER_FILE, ':utf8' or die ("binmode '$file', ':utf8' failed: $!");
113 2         35 $doc = <$PARSER_FILE>; # read entire file
114 2         16 close $PARSER_FILE;
115             }
116              
117 2         8 $self->from_text($doc);
118             }
119              
120             sub use_class
121             {
122             # use the provided class for generating objects of the type $object
123 4     4 1 13 my ($self, $object, $class) = @_;
124              
125 4 50       17 $self->_croak("Expected one of node, edge, group or graph, but got $object")
126             unless $object =~ /^(node|group|graph|edge)\z/;
127              
128 4         5 $self->{use_class}->{$object} = $class;
129              
130 4         4 $self;
131             }
132              
133             sub _register_handler
134             {
135             # register a pattern and a handler for it
136 4341     4341   3363 my $self = shift;
137              
138 4341         2697 push @{$self->{match_stack}}, [ @_ ];
  4341         6085  
139              
140 4341         5280 $self;
141             }
142              
143             sub _register_attribute_handler
144             {
145             # register a handler for attributes like "{ color: red; }"
146 541     541   626 my ($self, $qr_attr, $target) = @_;
147              
148             # $object is either undef (for Graph::Easy, meaning "node", or "parent" for Graphviz)
149              
150             # { attributes }
151             $self->_register_handler( qr/^$qr_attr/,
152             sub
153             {
154 41     41   39 my $self = shift;
155             # This happens in the case of "[ Test ]\n { ... }", the node is consumed
156             # first, and the attributes are left over:
157              
158 41 100       41 my $stack = $self->{stack}; $stack = $self->{group_stack} if @{$self->{stack}} == 0;
  41         29  
  41         97  
159              
160 41         38 my $object = $target;
161 41 100 66     131 if ($target && $target eq 'parent')
162             {
163             # for Graphviz, stray attributes always apply to the parent
164 33         31 $stack = $self->{group_stack};
165              
166 33 50       69 $object = $stack->[-1] if ref $stack;
167 33 100       51 if (!defined $object)
168             {
169             # try the scope stack next:
170 22         20 $stack = $self->{scope_stack};
171 22         26 $object = $self->{_graph};
172 22 50 33     80 if (!$stack || @$stack <= 1)
173             {
174 22         20 $object = $self->{_graph};
175 22         35 $stack = [ $self->{_graph} ];
176             }
177             }
178             }
179 41   50     159 my ($a, $max_idx) = $self->_parse_attributes($1||'', $object);
180 41 50       85 return undef if $self->{error}; # wrong attributes or empty stack?
181              
182 41 50       79 if (ref($stack->[-1]) eq 'HASH')
183             {
184             # stack is a scope stack
185             # XXX TODO: Find out wether the attribute goes to graph, node or edge
186 0         0 for my $k (sort keys %$a)
187             {
188 0         0 $stack->[-1]->{graph}->{$k} = $a->{$k};
189             }
190 0         0 return 1;
191             }
192              
193             print STDERR "max_idx = $max_idx, stack contains ", join (" , ", @$stack),"\n"
194 41 50 33     82 if $self->{debug} && $self->{debug} > 1;
195 41 100       67 if ($max_idx != 1)
196             {
197 1         1 my $i = 0;
198 1         2 for my $n (@$stack)
199             {
200 3         7 $n->set_attributes($a, $i++);
201             }
202             }
203             else
204             {
205             # set attributes on all nodes/groups on stack
206 40         46 for my $n (@$stack) { $n->set_attributes($a); }
  42         114  
207             }
208             # This happens in the case of "[ a | b ]\n { ... }", the node is consumed
209             # first, and the attributes are left over. And if we encounter a basename
210             # attribute here, the node-parts will already have been created with the
211             # wrong basename, so correct this:
212 41 100       83 if (defined $a->{basename})
213             {
214 2         3 for my $s (@$stack)
215             {
216             # for every node on the stack that is the primary one
217 6 100       15 $self->_set_new_basename($s, $a->{basename}) if exists $s->{autosplit_parts};
218             }
219             }
220 41         71 1;
221 541         19667 } );
222             }
223              
224             sub _register_node_attribute_handler
225             {
226             # register a handler for attributes like "[ A ] { ... }"
227 324     324   346 my ($self, $qr_node, $qr_oatr) = @_;
228              
229             $self->_register_handler( qr/^$qr_node$qr_oatr/,
230             sub
231             {
232 729     729   751 my $self = shift;
233 729         1150 my $n1 = $1;
234 729   100     2878 my $a1 = $self->_parse_attributes($2||'');
235 729 50       1244 return undef if $self->{error};
236              
237 729         1359 $self->{stack} = [ $self->_new_node ($self->{_graph}, $n1, $self->{group_stack}, $a1) ];
238              
239             # forget left stack
240 729         843 $self->{left_edge} = undef;
241 729         971 $self->{left_stack} = [];
242 729         1205 1;
243 324         1935 } );
244             }
245              
246             sub _new_group
247             {
248             # create a new (possible anonymous) group
249 57     57   73 my ($self, $name) = @_;
250              
251 57 50       101 $name = '' unless defined $name;
252              
253 57         97 my $gr = $self->{use_class}->{group};
254              
255 57         52 my $group;
256              
257 57 100       109 if ($name eq '')
258             {
259 15 50       26 print STDERR "# Creating new anon group.\n" if $self->{debug};
260 15         18 $gr .= '::Anon';
261 15         50 $group = $gr->new();
262             }
263             else
264             {
265 42         84 $name = $self->_unquote($name);
266 42 50       90 print STDERR "# Creating new group '$name'.\n" if $self->{debug};
267 42         207 $group = $gr->new( name => $name );
268             }
269              
270 57         176 $self->{_graph}->add_group($group);
271              
272 57         61 my $group_stack = $self->{group_stack};
273 57 100       104 if (@$group_stack > 0)
274             {
275 2         8 $group->set_attribute('group', $group_stack->[-1]->{name});
276             }
277              
278 57         81 $group;
279             }
280              
281             sub _add_group_match
282             {
283             # register two handlers for group start/end
284 324     324   302 my $self = shift;
285              
286 324         525 my $qr_group_start = $self->_match_group_start();
287 324         532 my $qr_group_end = $self->_match_group_end();
288 324         499 my $qr_oatr = $self->_match_optional_attributes();
289              
290             # "( group start [" or empty group like "( Group )"
291             $self->_register_handler( qr/^$qr_group_start/,
292             sub
293             {
294 44     44   41 my $self = shift;
295 44         48 my $graph = $self->{_graph};
296              
297 44 50       79 my $end = $2; $end = '' unless defined $end;
  44         74  
298              
299             # repair the start of the next node/group
300 44 100       105 $self->{replace} = '[' if $end eq '[';
301 44 100       69 $self->{replace} = '(' if $end eq '(';
302              
303             # create the new group
304 44         90 my $group = $self->_new_group($1);
305              
306 44 100       78 if ($end eq ')')
307             {
308             # we matched an empty group like "()", or "( group name )"
309 8         13 $self->{stack} = [ $group ];
310 8 50       13 print STDERR "# Seen end of group '$group->{name}'.\n" if $self->{debug};
311             }
312             else
313             {
314             # only put the group on the stack if it is still open
315 36         43 push @{$self->{group_stack}}, $group;
  36         60  
316             }
317              
318 44         52 1;
319 324         1754 } );
320              
321             # ") { }" # group end (with optional attributes)
322             $self->_register_handler( qr/^$qr_group_end$qr_oatr/,
323             sub
324             {
325 36     36   41 my $self = shift;
326              
327 36         36 my $group = pop @{$self->{group_stack}};
  36         54  
328 36 50       57 return $self->parse_error(0) if !defined $group;
329              
330 36 50       57 print STDERR "# Seen end of group '$group->{name}'.\n" if $self->{debug};
331              
332 36   100     154 my $a1 = $self->_parse_attributes($1||'', 'group', NO_MULTIPLES);
333 36 50       70 return undef if $self->{error};
334              
335 36         102 $group->set_attributes($a1);
336              
337             # the new left side is the group itself
338 36         51 $self->{stack} = [ $group ];
339 36         65 1;
340 324         1745 } );
341              
342             }
343              
344             sub _build_match_stack
345             {
346             # put all known patterns and their handlers on the match stack
347 324     324   356 my $self = shift;
348              
349             # regexps for the different parts
350 324         615 my $qr_node = $self->_match_node();
351 324         560 my $qr_attr = $self->_match_attributes();
352 324         525 my $qr_oatr = $self->_match_optional_attributes();
353 324         626 my $qr_edge = $self->_match_edge();
354 324         649 my $qr_comma = $self->_match_comma();
355 324         600 my $qr_class = $self->_match_class_selector();
356              
357 324         479 my $e = $self->{use_class}->{edge};
358              
359             # node { color: red; }
360             # node.graph { ... }
361             # .foo { ... }
362             # .foo, node, edge.red { ... }
363             $self->_register_handler( qr/^\s*$qr_class$qr_attr/,
364             sub
365             {
366 98     98   98 my $self = shift;
367 98   50     407 my $class = lc($1 || '');
368 98   50     377 my $att = $self->_parse_attributes($2 || '', $class, NO_MULTIPLES );
369              
370 98 50       196 return undef unless defined $att; # error in attributes?
371              
372 98         120 my $graph = $self->{_graph};
373 98         288 $graph->set_attributes ( $class, $att);
374              
375             # forget stacks
376 98         122 $self->{stack} = [];
377 98         109 $self->{left_edge} = undef;
378 98         110 $self->{left_stack} = [];
379 98         392 1;
380 324         2614 } );
381              
382 324         663 $self->_add_group_match();
383              
384 324         621 $self->_register_attribute_handler($qr_attr);
385 324         575 $self->_register_node_attribute_handler($qr_node,$qr_oatr);
386              
387             # , [ Berlin ] { color: red; }
388             $self->_register_handler( qr/^$qr_comma$qr_node$qr_oatr/,
389             sub
390             {
391 63     63   70 my $self = shift;
392 63         69 my $graph = $self->{_graph};
393 63         97 my $n1 = $1;
394 63   100     260 my $a1 = $self->_parse_attributes($2||'');
395 63 50       178 return undef if $self->{error};
396              
397 63         156 push @{$self->{stack}},
398 63         60 $self->_new_node ($graph, $n1, $self->{group_stack}, $a1, $self->{stack});
399              
400 63 100       135 if (defined $self->{left_edge})
401             {
402 26         24 my ($style, $edge_label, $edge_atr, $edge_bd, $edge_un) = @{$self->{left_edge}};
  26         44  
403              
404 26         19 foreach my $node (@{$self->{left_stack}})
  26         38  
405             {
406 28         94 my $edge = $e->new( { style => $style, name => $edge_label } );
407 28         70 $edge->set_attributes($edge_atr);
408             # "<--->": bidirectional
409 28 100       49 $edge->bidirectional(1) if $edge_bd;
410 28 50       43 $edge->undirected(1) if $edge_un;
411 28         118 $graph->add_edge ( $node, $self->{stack}->[-1], $edge );
412             }
413             }
414 63         107 1;
415 324         2063 } );
416              
417             # Things like "[ Node ]" will be consumed before, so we do not need a case
418             # for "[ A ] -> [ B ]":
419             # node chain continued like "-> { ... } [ Kassel ] { ... }"
420             $self->_register_handler( qr/^$qr_edge$qr_oatr$qr_node$qr_oatr/,
421             sub
422             {
423 780     780   745 my $self = shift;
424              
425 780 50       598 return if @{$self->{stack}} == 0; # only match this if stack non-empty
  780         1462  
426              
427 780         717 my $graph = $self->{_graph};
428 780         1111 my $eg = $1; # entire edge ("-- label -->" etc)
429              
430 780   66     1915 my $edge_bd = $2 || $4; # bidirectional edge ('<') ?
431 780         587 my $edge_un = 0; # undirected edge?
432 780 100 66     1256 $edge_un = 1 if !defined $2 && !defined $5;
433              
434             # optional edge label
435 780         744 my $edge_label = $7;
436 780   66     1599 my $ed = $3 || $5 || $1; # edge pattern/style ("--")
437              
438 780   100     2081 my $edge_atr = $11 || ''; # save edge attributes
439              
440 780         760 my $n = $12; # node name
441 780   100     2707 my $a1 = $self->_parse_attributes($13||''); # node attributes
442              
443 780         1099 $edge_atr = $self->_parse_attributes($edge_atr, 'edge');
444 780 50       1166 return undef if $self->{error};
445              
446             # allow undefined edge labels for setting them from the class
447             # strip trailing spaces and convert \[ => [
448 780 100       1097 $edge_label = $self->_unquote($edge_label) if defined $edge_label;
449             # strip trailing spaces
450 780 100       1070 $edge_label =~ s/\s+\z// if defined $edge_label;
451              
452             # the right side node(s) (multiple in case of autosplit)
453 780         1397 my $nodes_b = [ $self->_new_node ($self->{_graph}, $n, $self->{group_stack}, $a1) ];
454              
455 780         1661 my $style = $self->_link_lists( $self->{stack}, $nodes_b,
456             $ed, $edge_label, $edge_atr, $edge_bd, $edge_un);
457              
458             # remember the left side
459 780         1616 $self->{left_edge} = [ $style, $edge_label, $edge_atr, $edge_bd, $edge_un ];
460 780         952 $self->{left_stack} = $self->{stack};
461              
462             # forget stack and remember the right side instead
463 780         791 $self->{stack} = $nodes_b;
464 780         1286 1;
465 324         4333 } );
466              
467 324         511 my $qr_group_start = $self->_match_group_start();
468              
469             # Things like ")" will be consumed before, so we do not need a case
470             # for ") -> { ... } ( Group [ B ]":
471             # edge to a group like "-> { ... } ( Group ["
472             $self->_register_handler( qr/^$qr_edge$qr_oatr$qr_group_start/,
473             sub
474             {
475 6     6   8 my $self = shift;
476              
477 6 50       5 return if @{$self->{stack}} == 0; # only match this if stack non-empty
  6         14  
478              
479 6         11 my $eg = $1; # entire edge ("-- label -->" etc)
480              
481 6   33     17 my $edge_bd = $2 || $4; # bidirectional edge ('<') ?
482 6         4 my $edge_un = 0; # undirected edge?
483 6 0 33     11 $edge_un = 1 if !defined $2 && !defined $5;
484              
485             # optional edge label
486 6         7 my $edge_label = $7;
487 6   0     10 my $ed = $3 || $5 || $1; # edge pattern/style ("--")
488              
489 6   50     20 my $edge_atr = $11 || ''; # save edge attributes
490              
491 6         5 my $gn = $12;
492             # matched "-> ( Group [" or "-> ( Group ("
493 6 50 33     22 $self->{replace} = '[' if defined $13 && $13 eq '[';
494 6 50 33     20 $self->{replace} = '(' if defined $13 && $13 eq '(';
495              
496 6         13 $edge_atr = $self->_parse_attributes($edge_atr, 'edge');
497 6 50       12 return undef if $self->{error};
498              
499             # get the last group of the stack, lest the new one gets nested in it
500 6         5 pop @{$self->{group_stack}};
  6         8  
501              
502 6         8 $self->{group_stack} = [ $self->_new_group($gn) ];
503              
504             # allow undefined edge labels for setting them from the class
505 6 50       10 $edge_label = $self->_unquote($edge_label) if $edge_label;
506             # strip trailing spaces
507 6 50       9 $edge_label =~ s/\s+\z// if $edge_label;
508              
509             my $style = $self->_link_lists( $self->{stack}, $self->{group_stack},
510 6         12 $ed, $edge_label, $edge_atr, $edge_bd, $edge_un);
511              
512             # remember the left side
513 6         11 $self->{left_edge} = [ $style, $edge_label, $edge_atr, $edge_bd, $edge_un ];
514 6         7 $self->{left_stack} = $self->{stack};
515             # forget stack
516 6         6 $self->{stack} = [];
517             # matched "->()" so remember the group on the stack
518 6 50 33     30 $self->{stack} = [ $self->{group_stack}->[-1] ] if defined $13 && $13 eq ')';
519              
520 6         10 1;
521 324         2882 } );
522             }
523              
524             sub _line_insert
525             {
526             # what to insert between two lines, '' for Graph::Easy, ' ' for Graphviz;
527 962     962   2036 '';
528             }
529              
530             sub _clean_line
531             {
532             # do some cleanups on a line before handling it
533 962     962   1016 my ($self,$line) = @_;
534              
535 962         1157 chomp($line);
536              
537             # convert #808080 into \#808080, and "#fff" into "\#fff"
538 962         904 my $sep = $self->{attr_sep};
539 962         2693 $line =~ s/$sep\s*("?)(#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3}))("?)/$sep $1\\$2$3/g;
540              
541             # remove comment at end of line (but leave \# alone):
542 962         2382 $line =~ s/(:[^\\]|)$self->{qr_comment}.*/$1/;
543              
544             # remove white space at end (but not at the start, to keep " ||" intact
545 962         2591 $line =~ s/\s+\z//;
546              
547             # print STDERR "# at line '$line' stack: ", join(",",@{ $self->{stack}}),"\n";
548              
549 962         2319 $line;
550             }
551              
552             sub from_text
553             {
554 440     440 1 6585 my ($self,$txt) = @_;
555              
556             # matches a multi-line comment
557 440         1442 my $o_cmt = qr#((\s*/\*.*?\*/\s*)*\s*|\s+)#;
558              
559 440 100 66     12234 if ((ref($self)||$self) eq 'Graph::Easy::Parser' &&
      100        
      66        
560             # contains "digraph GRAPH {" or something similar
561             ( $txt =~ /^(\s*|\s*\/\*.*?\*\/\s*)(strict)?$o_cmt(di)?graph$o_cmt("[^"]*"|[\w_]+)$o_cmt\{/im ||
562             # contains "digraph {" or something similar
563             $txt =~ /^(\s*|\s*\/\*.*?\*\/\s*)(strict)?${o_cmt}digraph$o_cmt\{/im ||
564             # contains "strict graph {" or something similar
565             $txt =~ /^(\s*|\s*\/\*.*?\*\/\s*)strict${o_cmt}(di)?graph$o_cmt\{/im))
566             {
567 41         509 require Graph::Easy::Parser::Graphviz;
568             # recreate ourselfes, and pass our arguments along
569 41         46 my $debug = 0;
570 41         37 my $old_self = $self;
571 41 100       73 if (ref($self))
572             {
573 40         55 $debug = $self->{debug};
574 40         48 $self->{fatal_errors} = 0;
575             }
576 41         182 $self = Graph::Easy::Parser::Graphviz->new( debug => $debug, fatal_errors => 0 );
577 41         92 $self->reset();
578 41 50       106 $self->{_old_self} = $old_self if ref($self);
579             }
580              
581 440 100 66     2428 if ((ref($self)||$self) eq 'Graph::Easy::Parser' &&
      100        
582             # contains "graph: {"
583             $txt =~ /^([\s\n\t]*|\s*\/\*.*?\*\/\s*)graph\s*:\s*\{/m)
584             {
585 13         337 require Graph::Easy::Parser::VCG;
586             # recreate ourselfes, and pass our arguments along
587 13         14 my $debug = 0;
588 13         10 my $old_self = $self;
589 13 50       32 if (ref($self))
590             {
591 13         19 $debug = $self->{debug};
592 13         18 $self->{fatal_errors} = 0;
593             }
594 13         58 $self = Graph::Easy::Parser::VCG->new( debug => $debug, fatal_errors => 0 );
595 13         31 $self->reset();
596 13 50       36 $self->{_old_self} = $old_self if ref($self);
597             }
598              
599 440 100       850 $self = $self->new() unless ref $self;
600 440         1021 $self->reset();
601              
602 440         565 my $graph = $self->{_graph};
603 440 100 66     2527 return $graph if !defined $txt || $txt =~ /^\s*\z/; # empty text?
604              
605 439         562 my $uc = $self->{use_class};
606              
607             # instruct the graph to use the custom classes, too
608 439         1416 for my $o (sort keys %$uc)
609             {
610 1756 100       4172 $graph->use_class($o, $uc->{$o}) unless $o eq 'graph'; # group, node and edge
611             }
612              
613 439         3416 my @lines = split /(\r\n|\n|\r)/, $txt;
614              
615 439         529 my $backbuffer = ''; # left over fragments to be combined with next line
616              
617 439         923 my $qr_comment = $self->_match_commented_line();
618 439         893 $self->{qr_comment} = $self->_match_comment();
619             # cache the value of this since it can be expensive to construct:
620 439         1108 $self->{_match_single_attribute} = $self->_match_single_attribute();
621              
622 439         1026 $self->_build_match_stack();
623              
624             ###########################################################################
625             # main parsing loop
626              
627 439         541 my $handled = 0; # did we handle a fragment?
628 439         350 my $line;
629              
630             # my $counts = {};
631             LINE:
632 439   100     1228 while (@lines > 0 || $backbuffer ne '')
633             {
634             # only accumulate more text if we didn't handle a fragment
635 5902 100 100     16951 if (@lines > 0 && $handled == 0)
636             {
637 3573         2724 $self->{line_nr}++;
638 3573         3044 my $curline = shift @lines;
639              
640             # discard empty lines, or completely commented out lines
641 3573 100       14390 next if $curline =~ $qr_comment;
642              
643             # convert tabs to spaces (the regexps don't expect tabs)
644 1554         2405 $curline =~ tr/\t/ /;
645              
646             # combine backbuffer, what to insert between two lines and next line:
647 1554         2848 $line = $backbuffer . $self->_line_insert() . $self->_clean_line($curline);
648             }
649              
650 3883 50 33     7085 print STDERR "# Line is '$line'\n" if $self->{debug} && $self->{debug} > 2;
651 3883 50 33     6010 print STDERR "# Backbuffer is '$backbuffer'\n" if $self->{debug} && $self->{debug} > 2;
652              
653 3883         2809 $handled = 0;
654             #debug my $count = 0;
655             PATTERN:
656 3883         2977 for my $entry (@{$self->{match_stack}})
  3883         5530  
657             {
658             # nothing to match against?
659 19921 100       22948 last PATTERN if $line eq '';
660              
661 18667         14057 $self->{replace} = ''; # as default just remove the matched text
662 18667         16487 my ($pattern, $handler, $replace) = @$entry;
663              
664 18667 50 33     25848 print STDERR "# Matching against $pattern\n" if $self->{debug} && $self->{debug} > 3;
665              
666 18667 100       94810 if ($line =~ $pattern)
667             {
668             #debug $counts->{$count}++;
669 2693 50 33     4916 print STDERR "# Matched, calling handler\n" if $self->{debug} && $self->{debug} > 2;
670 2693         2061 my $rc = 1;
671 2693 100       5807 $rc = &$handler($self) if defined $handler;
672 2693 100       4280 if ($rc)
673             {
674 2514 100       4150 $replace = $self->{replace} unless defined $replace;
675 2514 100       3305 $replace = &$replace($self,$line) if ref($replace);
676 2514 50 33     4219 print STDERR "# Handled it successfully.\n" if $self->{debug} && $self->{debug} > 2;
677 2514         23835 $line =~ s/$pattern/$replace/;
678 2514 50 33     4958 print STDERR "# Line is now '$line' (replaced with '$replace')\n" if $self->{debug} && $self->{debug} > 2;
679 2514         1878 $handled++; last PATTERN;
  2514         3651  
680             }
681             }
682             #debug $count ++;
683              
684             }
685              
686             #debug if ($handled == 0) { $counts->{'-1'}++; }
687             # couldn't handle that fragment, so accumulate it and try again
688 3883         3557 $backbuffer = $line;
689              
690             # stop at the very last line
691 3883 100 100     8408 last LINE if $handled == 0 && @lines == 0;
692              
693             # stop at parsing errors
694 3871 50       10248 last LINE if $self->{error};
695             }
696              
697 439 100       835 $self->error("'$backbuffer' not recognized by " . ref($self)) if $backbuffer ne '';
698              
699             # if something was left on the stack, file ended unexpectedly
700 439 50 66     1423 $self->parse_error(7) if !$self->{error} && $self->{scope_stack} && @{$self->{scope_stack}} > 0;
  115   66     283  
701              
702 439 50 66     789 return undef if $self->{error} && $self->{fatal_errors};
703              
704             #debug use Data::Dumper; print Dumper($counts);
705              
706 439 50       711 print STDERR "# Parsing done.\n" if $graph->{debug};
707              
708             # Do final cleanup (for parsing Graphviz)
709 439 50       1858 $self->_parser_cleanup() if $self->can('_parser_cleanup');
710 439         1033 $graph->_drop_special_attributes();
711              
712             # turn on strict checking on returned graph
713 439         1358 $graph->strict(1);
714 439         890 $graph->fatal_errors(1);
715              
716 439         4642 $graph;
717             }
718              
719             #############################################################################
720             # internal routines
721              
722             sub _edge_style
723             {
724 786     786   650 my ($self, $ed) = @_;
725              
726 786         626 my $style = undef; # default is "inherit from class"
727 786 100       1237 $style = 'double-dash' if $ed =~ /^(= )+\z/;
728 786 100       1219 $style = 'double' if $ed =~ /^=+\z/;
729 786 100       1216 $style = 'dotted' if $ed =~ /^\.+\z/;
730 786 100       1016 $style = 'dashed' if $ed =~ /^(- )+\z/;
731 786 100       1035 $style = 'dot-dot-dash' if $ed =~ /^(..-)+\z/;
732 786 100       1203 $style = 'dot-dash' if $ed =~ /^(\.-)+\z/;
733 786 100       1090 $style = 'wave' if $ed =~ /^\~+\z/;
734 786 50       1148 $style = 'bold' if $ed =~ /^#+\z/;
735              
736 786         825 $style;
737             }
738              
739             sub _link_lists
740             {
741             # Given two node lists and an edge style, links each node from list
742             # one to list two.
743 904     904   1161 my ($self, $left, $right, $ed, $label, $edge_atr, $edge_bd, $edge_un) = @_;
744              
745 904         801 my $graph = $self->{_graph};
746              
747 904         1301 my $style = $self->_edge_style($ed);
748 904         1059 my $e = $self->{use_class}->{edge};
749              
750             # add edges for all nodes in the left list
751 904         1106 for my $node (@$left)
752             {
753 964         893 for my $node_b (@$right)
754             {
755 969         3524 my $edge = $e->new( { style => $style, name => $label } );
756              
757 969         2682 $graph->add_edge ( $node, $node_b, $edge );
758              
759             # 'string' => [ 'string' ]
760             # [ { hash }, 'string' ] => [ { hash }, 'string' ]
761 969 100       834 my $e = $edge_atr; $e = [ $edge_atr ] unless ref($e) eq 'ARRAY';
  969         2132  
762              
763 969         1288 for my $a (@$e)
764             {
765 1097 100       1401 if (ref $a)
766             {
767 969         1758 $edge->set_attributes($a);
768             }
769             else
770             {
771             # deferred parsing with the object as param:
772 128         224 my $out = $self->_parse_attributes($a, $edge);
773 128 50       255 return undef if $self->{error};
774 128         215 $edge->set_attributes($out);
775             }
776             }
777              
778             # "<--->": bidirectional
779 969 100       1448 $edge->bidirectional(1) if $edge_bd;
780 969 100       2101 $edge->undirected(1) if $edge_un;
781             }
782             }
783              
784 904         1359 $style;
785             }
786              
787             sub _unquote_attribute
788             {
789 611     611   689 my ($self,$name,$value) = @_;
790              
791 611         825 $self->_unquote($value);
792             }
793              
794             sub _unquote
795             {
796 2286     2286   2307 my ($self, $name, $no_collapse) = @_;
797              
798 2286 50       2856 $name = '' unless defined $name;
799              
800             # unquote special chars
801 2286         2434 $name =~ s/\\([\[\(\{\}\]\)#<>\-\.\=])/$1/g;
802              
803             # collapse multiple spaces
804 2286 100       3271 $name =~ s/\s+/ /g unless $no_collapse;
805              
806 2286         4079 $name;
807             }
808              
809             sub _add_node
810             {
811             # add a node to the graph, overidable by subclasses
812 1507     1507   1490 my ($self, $graph, $name) = @_;
813              
814 1507         3395 $graph->add_node($name); # add unless exists
815             }
816              
817             sub _get_cluster_name
818             {
819             # create a unique name for an autosplit node
820 65     65   79 my ($self, $base_name) = @_;
821              
822             # Try to find a unique cluster name in case some one get's creative and names the
823             # last part "-1":
824              
825             # does work without cluster-id?
826 65 100       142 if (exists $self->{clusters}->{$base_name})
827             {
828 2         3 my $g = 1;
829 2         7 while ($g == 1)
830             {
831 5 100       5 my $base_try = $base_name; $base_try .= '-' . $self->{cluster_id} if $self->{cluster_id};
  5         11  
832 5 100       10 last if !exists $self->{clusters}->{$base_try};
833 3         5 $self->{cluster_id}++;
834             }
835 2 50       6 $base_name .= '-' . $self->{cluster_id} if $self->{cluster_id}; $self->{cluster_id}++;
  2         2  
836             }
837              
838 65         127 $self->{clusters}->{$base_name} = undef; # reserve this name
839              
840 65         92 $base_name;
841             }
842              
843             sub _set_new_basename
844             {
845             # when encountering something like:
846             # [ a | b ]
847             # { basename: foo; }
848             # the Parser will create two nodes, ab.0 and ab.1, and then later see
849             # the "basename: foo". Sowe need to rename the already created nodes
850             # due to the changed basename:
851 2     2   3 my ($self, $node, $new_basename) = @_;
852              
853             # nothing changes?
854 2 50       7 return if $node->{autosplit_basename} eq $new_basename;
855              
856 2         2 my $g = $node->{graph};
857              
858 2         2 my @parts = @{$node->{autosplit_parts}};
  2         5  
859 2         3 my $nr = 0;
860 2         4 for my $part ($node, @parts)
861             {
862             print STDERR "# Setting new basename $new_basename for node $part->{name}\n"
863 6 50       14 if $self->{debug} > 1;
864              
865 6         4 $part->{autosplit_basename} = $new_basename;
866 6         13 $part->set_attribute('basename', $new_basename);
867              
868             # delete it from the list of nodes
869 6         12 delete $g->{nodes}->{$part->{name}};
870 6         12 $part->{name} = $new_basename . '.' . $nr; $nr++;
  6         5  
871             # and re-insert it with the right name
872 6         12 $g->{nodes}->{$part->{name}} = $part;
873              
874             # we do not need to care for edges here, as they are stored with refs
875             # to the nodes and not the node names itself
876             }
877             }
878              
879             sub _autosplit_node
880             {
881             # Takes a node name like "a|b||c" and splits it into "a", "b", and "c".
882             # Returns the individual parts.
883 65     65   93 my ($self, $graph, $name, $att, $allow_empty) = @_;
884              
885             # Default is to have empty parts. Graphviz sets this to true;
886 65 100       132 $allow_empty = 1 unless defined $allow_empty;
887              
888 65         67 my @rc;
889 65         73 my $uc = $self->{use_class};
890 65         70 my $qr_clean = $self->{_qr_part_clean};
891              
892             # build base name: "A|B |C||D" => "ABCD"
893 65         72 my $base_name = $name; $base_name =~ s/\s*\|\|?\s*//g;
  65         362  
894              
895             # use user-provided base name
896 65 100       156 $base_name = $att->{basename} if exists $att->{basename};
897              
898             # strip trailing/leading spaces on basename
899 65         138 $base_name =~ s/\s+\z//;
900 65         107 $base_name =~ s/^\s+//;
901              
902             # first one gets: "ABC", second one "ABC.1" and so on
903 65         128 $base_name = $self->_get_cluster_name($base_name);
904              
905 65 50       119 print STDERR "# Parser: Autosplitting node with basename '$base_name'\n" if $graph->{debug};
906              
907 65         55 my $first_in_row; # for relative placement of new row
908 65         51 my $x = 0; my $y = 0; my $idx = 0;
  65         51  
  65         62  
909 65         56 my $remaining = $name; my $sep; my $last_sep = '';
  65         56  
  65         59  
910 65         69 my $add = 0;
911 65         120 while ($remaining ne '')
912             {
913             # XXX TODO: parsing of "\|" and "|" in one node
914 188         693 $remaining =~ s/^((\\\||[^\|])*)(\|\|?|\z)//;
915 188   100     495 my $part = $1 || ' ';
916 188         173 $sep = $3;
917 188         145 my $port_name = '';
918              
919             # possible cleanup for this part
920 188 100       269 if ($qr_clean)
921             {
922 45         169 $part =~ s/^$qr_clean//; $port_name = $1;
  45         58  
923             }
924              
925             # fix [|G|] to have one empty part as last part
926 188 100 100     813 if ($add == 0 && $remaining eq '' && $sep =~ /\|\|?/)
      100        
927             {
928 2         1 $add++; # only do it once
929 2         3 $remaining .= '|'
930             }
931              
932 188 50       281 print STDERR "# Parser: Found autosplit part '$part'\n" if $graph->{debug};
933              
934 188         179 my $class = $uc->{node};
935 188 100 100     616 if ($allow_empty && $part eq ' ')
    100          
936             {
937             # create an empty node with no border
938 12         31 $class .= "::Empty";
939             }
940             elsif ($part =~ /^[ ]{2,}\z/)
941             {
942             # create an empty node with border
943 10         12 $part = ' ';
944             }
945             else
946             {
947 166         290 $part =~ s/^\s+//; # rem spaces at front
948 166         299 $part =~ s/\s+\z//; # rem spaces at end
949             }
950              
951 188         299 my $node_name = "$base_name.$idx";
952              
953 188 50       301 if ($graph->{debug})
954             {
955 0         0 my $empty = '';
956 0 0       0 $empty = ' empty' if $class ne $self->{use_class}->{node};
957 0 0       0 print STDERR "# Parser: Creating$empty autosplit part '$part'\n" if $graph->{debug};
958             }
959              
960             # if it doesn't exist, add it, otherwise retrieve node object to $node
961 188 100       351 if ($class =~ /::Empty/)
962             {
963 12         32 my $node = $graph->node($node_name);
964 12 100       29 if (!defined $node)
965             {
966             # create node object from the correct class
967 11         46 $node = $class->new($node_name);
968 11         22 $graph->add_node($node);
969             }
970             }
971              
972 188         402 my $node = $graph->add_node($node_name);
973 188         226 $node->{autosplit_label} = $part;
974             # remember these two for Graphviz
975 188         204 $node->{autosplit_portname} = $port_name;
976 188         302 $node->{autosplit_basename} = $base_name;
977              
978 188         193 push @rc, $node;
979 188 100       264 if (@rc == 1)
980             {
981             # for correct as_txt output
982 65         105 $node->{autosplit} = $name;
983 65         213 $node->{autosplit} =~ s/\s+\z//; # strip trailing spaces
984 65         139 $node->{autosplit} =~ s/^\s+//; # strip leading spaces
985 65         316 $node->{autosplit} =~ s/([^\|])\s+\|/$1 \|/g; # 'foo |' => 'foo |'
986 65         251 $node->{autosplit} =~ s/\|\s+([^\|])/\| $1/g; # '| foo' => '| foo'
987 65 100       160 $node->set_attribute('basename', $att->{basename}) if defined $att->{basename};
988             # list of all autosplit parts so as_txt() can find them easily again
989 65         99 $node->{autosplit_parts} = [ ];
990 65         82 $first_in_row = $node;
991             }
992             else
993             {
994             # second, third etc. get previous as origin
995 123         131 my ($sx,$sy) = (1,0);
996 123         235 my $origin = $rc[-2];
997 123 100       201 if ($last_sep eq '||')
998             {
999 16         19 ($sx,$sy) = (0,1); $origin = $first_in_row;
  16         15  
1000 16         14 $first_in_row = $node;
1001             }
1002 123         276 $node->relative_to($origin,$sx,$sy);
1003 123         86 push @{$rc[0]->{autosplit_parts}}, $node;
  123         218  
1004 123         106 weaken @{$rc[0]->{autosplit_parts}}[-1];
  123         274  
1005              
1006             # suppress as_txt output for other parts
1007 123         142 $node->{autosplit} = undef;
1008             }
1009             # nec. for border-collapse
1010 188         372 $node->{autosplit_xy} = "$x,$y";
1011              
1012 188         177 $idx++; # next node ID
1013 188         153 $last_sep = $sep;
1014 188         160 $x++;
1015             # || starts a new row:
1016 188 100       475 if ($sep eq '||')
1017             {
1018 16         23 $x = 0; $y++;
  16         30  
1019             }
1020             } # end for all parts
1021              
1022 65         192 @rc; # return all created nodes
1023             }
1024              
1025             sub _new_node
1026             {
1027             # Create a new node unless it doesn't already exist. If the group stack
1028             # contains entries, the new node appears first in this/these group(s), so
1029             # add it to these groups. If the newly created node contains "|", we auto
1030             # split it up into several nodes and cluster these together.
1031 1900     1900   2230 my ($self, $graph, $name, $group_stack, $att, $stack) = @_;
1032              
1033 1900 50       2970 print STDERR "# Parser: new node '$name'\n" if $graph->{debug};
1034              
1035 1900         2844 $name = $self->_unquote($name, 'no_collapse');
1036              
1037 1900         1523 my $autosplit;
1038 1900         1616 my $uc = $self->{use_class};
1039              
1040 1900         1945 my @rc = ();
1041              
1042 1900 100 100     8847 if ($name =~ /^\s*\z/)
    100          
1043             {
1044 22 50       47 print STDERR "# Parser: Creating anon node\n" if $graph->{debug};
1045             # create a new anon node and add it to the graph
1046 22         38 my $class = $uc->{node} . '::Anon';
1047 22         100 my $node = $class->new();
1048 22         53 @rc = ( $graph->add_node($node) );
1049             }
1050             # nodes to be autosplit will be done in a sep. pass for Graphviz
1051             elsif ((ref($self) eq 'Graph::Easy::Parser') && $name =~ /[^\\]\|/)
1052             {
1053 50         56 $autosplit = 1;
1054 50         105 @rc = $self->_autosplit_node($graph, $name, $att);
1055             }
1056             else
1057             {
1058             # strip trailing and leading spaces
1059 1828         4335 $name =~ s/\s+\z//;
1060 1828         2939 $name =~ s/^\s+//;
1061              
1062             # collapse multiple spaces
1063 1828         1957 $name =~ s/\s+/ /g;
1064              
1065             # unquote \|
1066 1828         1598 $name =~ s/\\\|/\|/g;
1067              
1068 1828 50       2887 if ($self->{debug})
1069             {
1070 0 0       0 if (!$graph->node($name))
1071             {
1072 0         0 print STDERR "# Parser: Creating normal node from name '$name'.\n";
1073             }
1074             else
1075             {
1076 0         0 print STDERR "# Parser: Found node '$name' already in graph.\n";
1077             }
1078             }
1079 1828         3219 @rc = ( $self->_add_node($graph, $name) ); # add to graph, unless exists
1080             }
1081              
1082 1900 50 66     3578 $self->parse_error(5) if exists $att->{basename} && !$autosplit;
1083              
1084 1900         1546 my $b = $att->{basename};
1085 1900         1533 delete $att->{basename};
1086              
1087             # on a node list "[A],[B] { ... }" set attributes on all nodes
1088             # encountered so far, too:
1089 1900 100       2542 if (defined $stack)
1090             {
1091 94         109 for my $node (@$stack)
1092             {
1093 145         251 $node->set_attributes ($att, 0);
1094             }
1095             }
1096 1900         1335 my $index = 0;
1097 1900         1869 my $group = $self->{group_stack}->[-1];
1098              
1099 1900         1976 for my $node (@rc)
1100             {
1101 1993 100       2741 $node->add_to_group($group) if $group;
1102 1993         3757 $node->set_attributes ($att, $index);
1103 1993         1888 $index++;
1104             }
1105              
1106 1900 100       2694 $att->{basename} = $b if defined $b;
1107              
1108             # return list of created nodes (usually one, but more for "A|B")
1109 1900         3768 @rc;
1110             }
1111              
1112             sub _match_comma
1113             {
1114             # return a regexp that matches something like " , " like in:
1115             # "[ Bonn ], [ Berlin ] => [ Hamburg ]"
1116 324     324   488 qr/\s*,\s*/;
1117             }
1118              
1119             sub _match_comment
1120             {
1121             # match the start of a comment
1122 324     324   636 qr/(^|[^\\])#/;
1123             }
1124              
1125             sub _match_commented_line
1126             {
1127             # match empty lines or a completely commented out line
1128 426     426   1067 qr/^\s*(#|\z)/;
1129             }
1130              
1131             sub _match_attributes
1132             {
1133             # return a regexp that matches something like " { color: red; }" and returns
1134             # the inner text without the {}
1135 324     324   516 qr/\s*\{\s*([^\}]+?)\s*\}/;
1136             }
1137              
1138             sub _match_optional_attributes
1139             {
1140             # return a regexp that matches something like " { color: red; }" and returns
1141             # the inner text with the {}
1142 649     649   1002 qr/(\s*\{[^\}]+?\})?/;
1143             }
1144              
1145             sub _match_node
1146             {
1147             # return a regexp that matches something like " [ bonn ]" and returns
1148             # the inner text without the [] (might leave some spaces)
1149              
1150 326     326   605 qr/\s*\[ # '[' start of the node
1151             (
1152             (?: # non-capturing group
1153             \\. # either '\]' or '\N' etc.
1154             | # or
1155             [^\]\\] # not ']' and not '\'
1156             )* # 0 times for '[]'
1157             )
1158             \]/x; # followed by ']'
1159             }
1160              
1161             sub _match_class_selector
1162             {
1163 324     324   478 my $class = qr/(?:\.\w+|graph|(?:edge|group|node)(?:\.\w+)?)/;
1164 324         2048 qr/($class(?:\s*,\s*$class)*)/;
1165             }
1166              
1167             sub _match_single_attribute
1168             {
1169 405     405   794 qr/\s*([^:]+?)\s*:\s*("(?:\\"|[^"])+"|(?:\\;|[^;])+?)(?:\s*;\s*|\s*\z)/; # "name: value"
1170             }
1171              
1172             sub _match_group_start
1173             {
1174             # Return a regexp that matches something like " ( group [" and returns
1175             # the text between "(" and "[". Also matches empty groups like "( group )"
1176             # or even "()":
1177 648     648   1187 qr/\s*\(\s*([^\[\)\(]*?)\s*([\[\)\(])/;
1178             }
1179              
1180             sub _match_group_end
1181             {
1182             # return a regexp that matches something like " )".
1183 324     324   663 qr/\s*\)\s*/;
1184             }
1185              
1186             sub _match_edge
1187             {
1188             # Matches all possible edge variants like:
1189             # -->, ---->, ==> etc
1190             # <-->, <---->, <==>, <..> etc
1191             # <-- label -->, <.- label .-> etc
1192             # -- label -->, .- label .-> etc
1193              
1194             # "- " must come before "-"!
1195             # likewise, "..-" must come before ".-" must come before "."
1196              
1197             # XXX TODO: convert the first group into a non-matching group
1198              
1199 325     325   591 qr/\s*
1200             ( # egde without label ("-->")
1201             (
1202             (=\s|=|-\s|-|\.\.-|\.-|\.|~)+> # pattern (style) of edge
1203             | # edge with label ("-- label -->")
1204             (
1205             ((=\s|=|-\s|-|\.\.-|\.-|\.|~)+) # pattern (style) of edge
1206             \s+ # followed by at least a space
1207             ((?:\\.|[^>\[\{])*?) # either \\, \[ etc, or not ">", "[", "{"
1208             (\s+\5)> # a space and pattern before ">"
1209              
1210             # inserting this needs mucking with all the code that access $5 etc
1211             # | # undirected edge (without arrows, but with label)
1212             # ((=\s|=|-\s|-|\.\.-|\.-|\.|~)+) # pattern (style) of edge
1213             # \s+ # followed by at least a space
1214             # ((?:\\.|[^>\[\{])*?) # either \\, \[ etc, or not ">", "[", "{"
1215             # (\s+\10) # a space and pattern
1216              
1217             | # undirected edge (without arrows and label)
1218             (\.\.-|\.-)+ # pattern (style) of edge (at least once)
1219             |
1220             (=\s|=|-\s|-|\.|~){2,} # these at least two times
1221             )
1222             /x;
1223             }
1224              
1225             sub _clean_attributes
1226             {
1227 2578     2578   2054 my ($self,$text) = @_;
1228              
1229 2578         2542 $text =~ s/^\s*\{\s*//; # remove left-over "{" and spaces
1230 2578         2774 $text =~ s/\s*\}\s*\z//; # remove left-over "}" and spaces
1231              
1232 2578         3289 $text;
1233             }
1234              
1235             sub _parse_attributes
1236             {
1237             # Takes a text like "attribute: value; attribute2 : value2;" and
1238             # returns a hash with the attributes. $class defaults to 'node'.
1239             # In list context, also returns a flag that is maxlevel-1 when one
1240             # of the attributes was a multiple one (aka 2 for "red|green", 1 for "red");
1241 2999     2999   3414 my ($self, $text, $object, $no_multiples) = @_;
1242              
1243 2999         2246 my $class = $object;
1244 2999 100       4033 $class = $object->{class} if ref($object);
1245 2999 100       4707 $class = 'node' unless defined $class;
1246 2999         2791 $class =~ s/\..*//; # remove subclass
1247              
1248 2999         1891 my $out;
1249 2999         2647 my $att = {};
1250 2999         2187 my $multiples = 0;
1251              
1252 2999         3897 $text = $self->_clean_attributes($text);
1253 2999         2652 my $qr_att = $self->{_match_single_attribute};
1254 2999 100       1871 my $qr_cmt; $qr_cmt = $self->_match_multi_line_comment()
  2999         8042  
1255             if $self->can('_match_multi_line_comment');
1256 2999 100       1866 my $qr_satt; $qr_satt = $self->_match_special_attribute()
  2999         6061  
1257             if $self->can('_match_special_attribute');
1258              
1259 2999 100       9192 return {} if $text =~ /^\s*\z/;
1260              
1261 666 50       1141 print STDERR "attr parsing: matching\n '$text'\n against $qr_att\n" if $self->{debug} > 3;
1262              
1263 666         1189 while ($text ne '')
1264             {
1265 877 50       1332 print STDERR "attr parsing: matching '$text'\n" if $self->{debug} > 3;
1266              
1267             # remove a possible comment
1268 877 100       1685 $text =~ s/^$qr_cmt//g if $qr_cmt;
1269              
1270             # if the last part was a comment, we end up with an empty text here:
1271 877 100       1580 last if $text =~ /^\s*\z/;
1272              
1273             # match and remove "name: value"
1274 876   100     7490 my $done = ($text =~ s/^$qr_att//) || 0;
1275              
1276             # match and remove "name" if "name: value;" didn't match
1277 876 100 100     2059 $done++ if $done == 0 && $qr_satt && ($text =~ s/^$qr_satt//);
      66        
1278              
1279 876 100       1101 return $self->error ("Error in attribute: '$text' doesn't look valid to me.")
1280             if $done == 0;
1281              
1282 872         1032 my $name = $1;
1283 872 100       863 my $v = $2; $v = '' unless defined $v; # for special attributes w/o value
  872         1134  
1284              
1285             # unquote and store
1286 872         1401 $out->{$name} = $self->_unquote_attribute($name,$v);
1287             }
1288              
1289 662 50 33     1196 if ($self->{debug} && $self->{debug} > 1)
1290             {
1291 0         0 require Data::Dumper;
1292 0         0 print STDERR "# ", join (" ", caller),"\n";
1293 0         0 print STDERR "# Parsed attributes into:\n", Data::Dumper::Dumper($out),"\n";
1294             }
1295             # possible remap attributes (for parsing Graphviz)
1296 662 100       1921 $out = $self->_remap_attributes($out, $object) if $self->can('_remap_attributes');
1297              
1298 662         792 my $g = $self->{_graph};
1299             # check for being valid and finally create hash with name => value pairs
1300 662         1641 for my $name (sort keys %$out)
1301             {
1302 837         2420 my ($rc, $newname, $v) = $g->validate_attribute($name,$out->{$name},$class,$no_multiples);
1303              
1304 837 100       1490 $self->error($g->{error}) if defined $rc;
1305              
1306 837 100       1288 $multiples = scalar @$v if ref($v) eq 'ARRAY';
1307              
1308 837 100       2152 $att->{$newname} = $v if defined $v; # undef => ignore attribute
1309             }
1310              
1311 662 100       2132 return $att unless wantarray;
1312              
1313 41   100     200 ($att, $multiples || 1);
1314             }
1315              
1316             sub parse_error
1317             {
1318             # take a msg number, plus params, and throws an exception
1319 3     3 1 4 my $self = shift;
1320 3         2 my $msg_nr = shift;
1321              
1322             # XXX TODO: should really use the msg nr mapping
1323 3         4 my $msg = "Found unexpected group end"; # 0
1324 3 100       8 $msg = "Error in attribute: '##param2##' is not a valid attribute for a ##param3##" # 1
1325             if $msg_nr == 1;
1326 3 100       5 $msg = "Error in attribute: '##param1##' is not a valid ##param2## for a ##param3##"
1327             if $msg_nr == 2; # 2
1328 3 100       6 $msg = "Error: Found attributes, but expected group or node start"
1329             if $msg_nr == 3; # 3
1330 3 50       5 $msg = "Error in attribute: multi-attribute '##param1##' not allowed here"
1331             if $msg_nr == 4; # 4
1332 3 50       4 $msg = "Error in attribute: basename not allowed for non-autosplit nodes"
1333             if $msg_nr == 5; # 5
1334             # for graphviz parsing
1335 3 50       6 $msg = "Error: Already seen graph start"
1336             if $msg_nr == 6; # 6
1337 3 50       5 $msg = "Error: Expected '}', but found file end"
1338             if $msg_nr == 7; # 7
1339              
1340 3         2 my $i = 1;
1341 3         5 foreach my $p (@_)
1342             {
1343 6         59 $msg =~ s/##param$i##/$p/g; $i++;
  6         8  
1344             }
1345              
1346 3         10 $self->error($msg . ' at line ' . $self->{line_nr});
1347             }
1348              
1349             sub _parser_cleanup
1350             {
1351             # After initial parsing, do a cleanup pass.
1352 324     324   362 my ($self) = @_;
1353              
1354 324         319 my $g = $self->{_graph};
1355              
1356 324         888 for my $n (ord_values ( $g->{nodes} ))
1357             {
1358 1126 100       1712 next if $n->{autosplit};
1359 1076 50 66     2000 $self->warn("Node '" . $self->_quote($n->{name}) . "' has an offset but no origin")
1360             if (($n->attribute('offset') ne '0,0') && $n->attribute('origin') eq '');
1361             }
1362              
1363 324         477 $self;
1364             }
1365              
1366             sub _quote
1367             {
1368             # make a node name safe for error message output
1369 0     0     my ($self,$n) = @_;
1370              
1371 0           $n =~ s/'/\\'/g;
1372              
1373 0           $n;
1374             }
1375              
1376             1;
1377             __END__