File Coverage

lib/Graph/Easy/Parser/VCG.pm
Criterion Covered Total %
statement 234 272 86.0
branch 45 88 51.1
condition 19 38 50.0
subroutine 44 52 84.6
pod 1 1 100.0
total 343 451 76.0


line stmt bran cond sub pod time code
1             #############################################################################
2             # Parse VCG text into a Graph::Easy object
3             #
4             #############################################################################
5              
6             package Graph::Easy::Parser::VCG;
7              
8             $VERSION = '0.75';
9 3     3   16504 use Graph::Easy::Parser::Graphviz;
  3         13  
  3         223  
10             @ISA = qw/Graph::Easy::Parser::Graphviz/;
11              
12 3     3   32 use strict;
  3         7  
  3         125  
13 3     3   19 use warnings;
  3         10  
  3         112  
14 3     3   20 use utf8;
  3         10  
  3         28  
15 3     3   92 use constant NO_MULTIPLES => 1;
  3         8  
  3         233  
16 3     3   13065 use Encode qw/decode/;
  3         42160  
  3         24943  
17              
18             sub _init
19             {
20 13     13   31 my $self = shift;
21              
22 13         84 $self->SUPER::_init(@_);
23 13         29 $self->{attr_sep} = '=';
24              
25 13         55 $self;
26             }
27              
28             my $vcg_color_by_name = {};
29              
30             my $vcg_colors = [
31             white => 'white',
32             blue => 'blue',
33             red => 'red',
34             green => 'green',
35             yellow => 'yellow',
36             magenta => 'magenta',
37             cyan => 'cyan',
38             darkgrey => 'rgb(85,85,85)',
39             darkblue => 'rgb(0,0,128)',
40             darkred => 'rgb(128,0,0)',
41             darkgreen => 'rgb(0,128,0)',
42             darkyellow => 'rgb(128,128,0)',
43             darkmagenta => 'rgb(128,0,128)',
44             darkcyan => 'rgb(0,128,128)',
45             gold => 'rgb(255,215,0)',
46             lightgrey => 'rgb(170,170,170)',
47             lightblue => 'rgb(128,128,255)',
48             lightred => 'rgb(255,128,128)',
49             lightgreen => 'rgb(128,255,128)',
50             lightyellow => 'rgb(255,255,128)',
51             lightmagenta => 'rgb(255,128,255)',
52             lightcyan => 'rgb(128,255,255)',
53             lilac => 'rgb(238,130,238)',
54             turquoise => 'rgb(64,224,208)',
55             aquamarine => 'rgb(127,255,212)',
56             khaki => 'rgb(240,230,140)',
57             purple => 'rgb(160,32,240)',
58             yellowgreen => 'rgb(154,205,50)',
59             pink => 'rgb(255,192,203)',
60             orange => 'rgb(255,165,0)',
61             orchid => 'rgb(218,112,214)',
62             black => 'black',
63             ];
64              
65             {
66             for (my $i = 0; $i < @$vcg_colors; $i+=2)
67             {
68             $vcg_color_by_name->{$vcg_colors->[$i]} = $vcg_colors->[$i+1];
69             }
70             }
71              
72             sub reset
73             {
74 26     26 1 48 my $self = shift;
75              
76 26         85 Graph::Easy::Parser::reset($self, @_);
77              
78 26         52 my $g = $self->{_graph};
79 26         61 $self->{scope_stack} = [];
80              
81 26         60 $g->{_vcg_color_map} = [];
82 26         97 for (my $i = 0; $i < @$vcg_colors; $i+=2)
83             {
84             # set the first 32 colors as the default
85 832         755 push @{$g->{_vcg_color_map}}, $vcg_colors->[$i+1];
  832         2665  
86             }
87              
88 26         67 $g->{_vcg_class_names} = {};
89              
90             # allow some temp. values during parsing
91 26         257 $g->_allow_special_attributes(
92             {
93             edge => {
94             source => [ "", undef, '', '', undef, ],
95             target => [ "", undef, '', '', undef, ],
96             },
97             } );
98              
99 26         62 $g->{_warn_on_unknown_attributes} = 1;
100              
101             # a hack to support multiline labels
102 26         48 $self->{_in_vcg_multi_line_label} = 0;
103              
104             # set some default attributes on the graph object, because GDL has
105             # some different defaults as Graph::Easy
106 26         124 $g->set_attribute('flow', 'south');
107 26         79 $g->set_attribute('edge', 'arrow-style', 'filled');
108 26         70 $g->set_attribute('node', 'align', 'left');
109              
110 26         81 $self;
111             }
112              
113             sub _vcg_color_map_entry
114             {
115 2     2   9 my ($self, $index, $color) = @_;
116              
117 2         8 $color =~ /([0-9]+)\s+([0-9]+)\s+([0-9]+)/;
118 2         18 $self->{_graph}->{_vcg_color_map}->[$index] = "rgb($1,$2,$3)";
119             }
120              
121             sub _unquote
122             {
123 81     81   109 my ($self, $name) = @_;
124              
125 81 50       150 $name = '' unless defined $name;
126              
127             # "foo bar" => foo bar
128             # we need to use "[ ]" here, because "\s" also matches 0x0c, and
129             # these color codes need to be kept intact:
130 81         383 $name =~ s/^"[ ]*//; # remove left-over quotes
131 81         262 $name =~ s/[ ]*"\z//;
132              
133             # unquote special chars
134 81         114 $name =~ s/\\([\[\(\{\}\]\)#"])/$1/g;
135              
136 81         223 $name;
137             }
138              
139             #############################################################################
140              
141             sub _match_commented_line
142             {
143             # matches only empty lines
144 13     13   84 qr/^\s*\z/;
145             }
146              
147             sub _match_multi_line_comment
148             {
149             # match a multi line comment
150              
151             # /* * comment * */
152 103     103   365 qr#^\s*/\*.*?\*/\s*#;
153             }
154              
155             sub _match_optional_multi_line_comment
156             {
157             # match a multi line comment
158              
159             # "/* * comment * */" or /* a */ /* b */ or ""
160 13     13   52 qr#(?:(?:\s*/\*.*?\*/\s*)*|\s+)#;
161             }
162              
163             sub _match_classname
164             {
165             # Return a regexp that matches something like classname 1: "foo"
166 13     13   22 my $self = shift;
167              
168 13         64 qr/^\s*classname\s([0-9]+)\s*:\s*"((\\"|[^"])*)"/;
169             }
170              
171             sub _match_node
172             {
173             # Return a regexp that matches a node at the start of the buffer
174 13     13   19 my $self = shift;
175              
176 13         52 my $attr = $self->_match_attributes();
177              
178             # Examples: "node: { title: "a" }"
179 13         266 qr/^\s*node:\s*$attr/;
180             }
181              
182             sub _match_edge
183             {
184             # Matches an edge at the start of the buffer
185 13     13   28 my $self = shift;
186              
187 13         30 my $attr = $self->_match_attributes();
188              
189             # Examples: "edge: { sourcename: "a" targetname: "b" }"
190             # "backedge: { sourcename: "a" targetname: "b" }"
191 13         270 qr/^\s*(|near|bentnear|back)edge:\s*$attr/;
192             }
193              
194             sub _match_single_attribute
195             {
196              
197 52     52   208 qr/\s*( energetic\s\w+ # "energetic attraction" etc.
198             |
199             \w+ # a word
200             |
201             border\s(?:x|y) # "border x" or "border y"
202             |
203             colorentry\s+[0-9]{1,2} # colorentry
204             )\s*:\s*
205             (
206             "(?:\\"|[^"])*" # "foo"
207             |
208             [0-9]{1,3}\s+[0-9]{1,3}\s+[0-9]{1,3} # "128 128 64" for color entries
209             |
210             \{[^\}]+\} # or {..}
211             |
212             [^<][^,\]\}\n\s;]* # or simple 'fooobar'
213             )
214             \s*/x; # possible trailing whitespace
215             }
216              
217             sub _match_class_attribute
218             {
219             # match something like "edge.color: 10"
220              
221 13     13   50 qr/\s*(edge|node)\.(\w+)\s*:\s* # the attribute name (label:")
222             (
223             "(?:\\"|[^"])*" # "foo"
224             |
225             [^<][^,\]\}\n\s]* # or simple 'fooobar'
226             )
227             \s*/x; # possible whitespace
228             }
229              
230             sub _match_attributes
231             {
232             # return a regexp that matches something like " { color=red; }" and returns
233             # the inner text without the {}
234              
235 39     39   78 my $qr_att = _match_single_attribute();
236 39         81 my $qr_cmt = _match_multi_line_comment();
237            
238 39         546 qr/\s*\{\s*((?:$qr_att|$qr_cmt)*)\s*\}/;
239             }
240              
241             sub _match_graph_attribute
242             {
243             # return a regexp that matches something like " color: red " for attributes
244             # that apply to a graph/subgraph
245 13     13   51 qr/^\s*(
246             (
247             colorentry\s+[0-9]{1,2}:\s+[0-9]+\s+[0-9]+\s+[0-9]+
248             |
249             (?!(node|edge|nearedge|bentnearedge|graph)) # not one of these
250             \w+\s*:\s*("(?:\\"|[^"])*"|[^\n\s]+)
251             )
252             )([\n\s]\s*|\z)/x;
253             }
254              
255             sub _clean_attributes
256             {
257 51     51   83 my ($self,$text) = @_;
258              
259 51         96 $text =~ s/^\s*\{\s*//; # remove left-over "{" and spaces
260 51         68 $text =~ s/\s*;?\s*\}\s*\z//; # remove left-over "}" and spaces
261              
262 51         127 $text;
263             }
264              
265             sub _match_group_end
266             {
267             # return a regexp that matches something like " }" at the beginning
268 13     13   52 qr/^\s*\}\s*/;
269             }
270              
271             sub _match_group_start
272             {
273             # return a regexp that matches something like "graph {" at the beginning
274 13     13   44 qr/^\s*graph:\s+\{\s*/;
275             }
276              
277             sub _clean_line
278             {
279             # do some cleanups on a line before handling it
280 96     96   146 my ($self,$line) = @_;
281              
282 96         172 chomp($line);
283              
284             # collapse white space at start
285 96         269 $line =~ s/^\s+//;
286              
287 96 100       353 if ($self->{_in_vcg_multi_line_label})
    100          
288             {
289 5 100       21 if ($line =~ /\"[^\"]*\z/)
290             {
291             # '"\n'
292 2         6 $self->{_in_vcg_multi_line_label} = 0;
293             # restore the match stack
294 2         6 $self->{match_stack} = $self->{_match_stack};
295 2         6 delete $self->{_match_stack};
296             }
297             else
298             {
299             # hack: convert "a" to \"a\" to fix faulty inputs
300 3         8 $line =~ s/([^\\])\"/$1\\\"/g;
301             }
302             }
303             # a line ending in 'label: "...\n' means a multi-line label
304             elsif ($line =~ /(^|\s)label:\s+\"[^\"]*\z/)
305             {
306 2         6 $self->{_in_vcg_multi_line_label} = 1;
307             # swap out the match stack since we just wait for the end of the label
308 2         5 $self->{_match_stack} = $self->{match_stack};
309 2         7 delete $self->{match_stack};
310             }
311              
312 96         342 $line;
313             }
314              
315             sub _line_insert
316             {
317             # What to insert between two lines.
318 96     96   161 my ($self) = @_;
319              
320 96 50 66     291 print STDERR "in multiline\n" if $self->{_in_vcg_multi_line_label} && $self->{debug};
321             # multiline labels => '\n'
322 96 100       215 return '\\n' if $self->{_in_vcg_multi_line_label};
323              
324             # the default is ' '
325 91         308 ' ';
326             }
327              
328             #############################################################################
329              
330             sub _new_scope
331             {
332             # create a new scope, with attributes from current scope
333 13     13   30 my ($self, $is_group) = @_;
334              
335 13         46 my $scope = {};
336              
337 13 50       24 if (@{$self->{scope_stack}} > 0)
  13         76  
338             {
339 0         0 my $old_scope = $self->{scope_stack}->[-1];
340              
341             # make a copy of the old scope's attribtues
342 0         0 for my $t (sort keys %$old_scope)
343             {
344 0 0       0 next if $t =~ /^_/;
345 0         0 my $s = $old_scope->{$t};
346 0 0       0 $scope->{$t} = {} unless ref $scope->{$t}; my $sc = $scope->{$t};
  0         0  
347 0         0 for my $k (sort keys %$s)
348             {
349             # skip things like "_is_group"
350 0 0       0 $sc->{$k} = $s->{$k} unless $k =~ /^_/;
351             }
352             }
353             }
354 13 50       59 $scope->{_is_group} = 1 if defined $is_group;
355              
356 13         20 push @{$self->{scope_stack}}, $scope;
  13         30  
357              
358 13         28 $scope;
359             }
360              
361             sub _edge_style
362             {
363             # To convert "--" or "->" we simple do nothing, since the edge style in
364             # VCG can only be set via the attributes (if at all)
365 0     0   0 my ($self, $ed) = @_;
366              
367 0         0 'solid';
368             }
369              
370             sub _build_match_stack
371             {
372 13     13   24 my $self = shift;
373              
374 13         47 my $qr_cn = $self->_match_classname();
375 13         57 my $qr_node = $self->_match_node();
376 13         54 my $qr_cmt = $self->_match_multi_line_comment();
377 13         56 my $qr_ocmt = $self->_match_optional_multi_line_comment();
378 13         40 my $qr_attr = $self->_match_attributes();
379 13         59 my $qr_gatr = $self->_match_graph_attribute();
380 13         96 my $qr_oatr = $self->_match_optional_attributes();
381 13         59 my $qr_edge = $self->_match_edge();
382 13         52 my $qr_class = $self->_match_class_attribute();
383              
384 13         50 my $qr_group_end = $self->_match_group_end();
385 13         51 my $qr_group_start = $self->_match_group_start();
386              
387             # "graph: {"
388             $self->_register_handler( $qr_group_start,
389             sub
390             {
391 13     13   33 my $self = shift;
392              
393             # the main graph
394 13 50       18 if (@{$self->{scope_stack}} == 0)
  13         50  
395             {
396 13 50       45 print STDERR "# Parser: found main graph\n" if $self->{debug};
397 13         34 $self->{_vcg_graph_name} = 'unnamed';
398 13         55 $self->_new_scope(1);
399             }
400             else
401             {
402 0 0       0 print STDERR "# Parser: found subgraph\n" if $self->{debug};
403             # a new subgraph
404 0         0 push @{$self->{group_stack}}, $self->_new_group();
  0         0  
405             }
406 13         32 1;
407 13         157 } );
408              
409             # graph or subgraph end "}"
410             $self->_register_handler( $qr_group_end,
411             sub
412             {
413 13     13   25 my $self = shift;
414              
415 13 50       45 print STDERR "# Parser: found end of (sub-)graph\n" if $self->{debug};
416            
417 13         16 my $scope = pop @{$self->{scope_stack}};
  13         32  
418 13 50       31 return $self->parse_error(0) if !defined $scope;
419              
420 13         57 1;
421 13         87 } );
422              
423             # classname 1: "foo"
424             $self->_register_handler( $qr_cn,
425             sub {
426 2     2   4 my $self = shift;
427 2         5 my $class = $1; my $name = $2;
  2         5  
428              
429 2 50       7 print STDERR "# Found classname '$name' for class '$class'\n" if $self->{debug} > 1;
430              
431 2         8 $self->{_graph}->{_vcg_class_names}->{$class} = $name;
432 2         5 1;
433 13         86 } );
434              
435             # node: { ... }
436             $self->_register_handler( $qr_node,
437             sub {
438 26     26   42 my $self = shift;
439 26   50     167 my $att = $self->_parse_attributes($1 || '', 'node', NO_MULTIPLES );
440 26 50       71 return undef unless defined $att; # error in attributes?
441              
442 26         135 my $name = $att->{title}; delete $att->{title};
  26         60  
443              
444 26 50       68 print STDERR "# Found node with name $name\n" if $self->{debug} > 1;
445              
446 26         163 my $node = $self->_new_node($self->{_graph}, $name, $self->{group_stack}, $att, []);
447              
448             # set attributes from scope
449 26   50     91 my $scope = $self->{scope_stack}->[-1] || {};
450 26 100       32 $node->set_attributes ($scope->{node}) if keys %{$scope->{node}} != 0;
  26         94  
451              
452             # override with local attributes
453 26 100       96 $node->set_attributes ($att) if keys %$att != 0;
454 26         89 1;
455 13         112 } );
456              
457             # "edge: { ... }"
458             $self->_register_handler( $qr_edge,
459             sub {
460 13     13   26 my $self = shift;
461 13   50     98 my $type = $1 || 'edge';
462 13   50     54 my $txt = $2 || '';
463 13 50       56 $type = "edge" if $type =~ /edge/; # bentnearedge => edge
464 13         88 my $att = $self->_parse_attributes($txt, 'edge', NO_MULTIPLES );
465 13 50       51 return undef unless defined $att; # error in attributes?
466              
467 13         31 my $from = $att->{source}; delete $att->{source};
  13         27  
468 13         25 my $to = $att->{target}; delete $att->{target};
  13         25  
469              
470 13 50       47 print STDERR "# Found edge ($type) from $from to $to\n" if $self->{debug} > 1;
471              
472 13         79 my $edge = $self->{_graph}->add_edge ($from, $to);
473              
474             # set attributes from scope
475 13   50     51 my $scope = $self->{scope_stack}->[-1] || {};
476 13 100       20 $edge->set_attributes ($scope->{edge}) if keys %{$scope->{edge}} != 0;
  13         71  
477              
478             # override with local attributes
479 13 100       55 $edge->set_attributes ($att) if keys %$att != 0;
480              
481 13         49 1;
482 13         106 } );
483              
484             # color: red (for graphs or subgraphs)
485 13         226 $self->_register_attribute_handler($qr_gatr, 'parent');
486              
487             # edge.color: 10
488             $self->_register_handler( $qr_class,
489             sub {
490 6     6   12 my $self = shift;
491 6         16 my $type = $1;
492 6         12 my $name = $2;
493 6         17 my $val = $3;
494              
495 6 50       18 print STDERR "# Found color definition $type $name $val\n" if $self->{debug} > 2;
496              
497 6         43 my $att = $self->_remap_attributes( { $name => $val }, $type, $self->_remap());
498              
499             # store the attributes in the current scope
500 6         19 my $scope = $self->{scope_stack}->[-1];
501 6 100       24 $scope->{$type} = {} unless ref $scope->{$type};
502 6         10 my $s = $scope->{$type};
503              
504 6         18 for my $k (sort keys %$att)
505             {
506 6         17 $s->{$k} = $att->{$k};
507             }
508              
509             #$self->{_graph}->set_attributes ($type, $att);
510 6         20 1;
511 13         149 });
512              
513             # remove multi line comments /* comment */
514 13         43 $self->_register_handler( $qr_cmt, undef );
515            
516             # remove single line comment // comment
517 13         66 $self->_register_handler( qr/^\s*\/\/.*/, undef );
518              
519 13         52 $self;
520             }
521              
522             sub _new_node
523             {
524             # add a node to the graph, overridable by subclasses
525 26     26   56 my ($self, $graph, $name, $group_stack, $att, $stack) = @_;
526              
527             # print STDERR "add_node $name\n";
528              
529 26         104 my $node = $graph->node($name);
530            
531 26 50       66 if (!defined $node)
532             {
533 26         94 $node = $graph->add_node($name); # add
534              
535             # apply attributes from the current scope (only for new nodes)
536 26         59 my $scope = $self->{scope_stack}->[-1];
537 26 50       58 return $self->error("Scope stack is empty!") unless defined $scope;
538            
539 26         51 my $is_group = $scope->{_is_group};
540 26         49 delete $scope->{_is_group};
541 26         124 $node->set_attributes($scope->{node});
542 26 50       78 $scope->{_is_group} = $is_group if $is_group;
543              
544 26         48 my $group = $self->{group_stack}->[-1];
545              
546 26 50       60 $node->add_to_group($group) if $group;
547             }
548              
549 26         56 $node;
550             }
551              
552             #############################################################################
553             # attribute remapping
554              
555             # undef => drop that attribute
556             # not listed attributes are simple copied unmodified
557              
558             my $vcg_remap = {
559             'node' => {
560             iconfile => 'x-vcg-iconfile',
561             info1 => 'x-vcg-info1',
562             info2 => 'x-vcg-info2',
563             info3 => 'x-vcg-info3',
564             invisible => \&_invisible_from_vcg,
565             importance => 'x-vcg-importance',
566             focus => 'x-vcg-focus',
567             margin => 'x-vcg-margin',
568             textmode => \&_textmode_from_vcg,
569             textcolor => \&_node_color_from_vcg,
570             color => \&_node_color_from_vcg,
571             bordercolor => \&_node_color_from_vcg,
572             level => 'rank',
573             horizontal_order => \&_horizontal_order_from_vcg,
574             shape => \&_vcg_node_shape,
575             vertical_order => \&_vertical_order_from_vcg,
576             },
577              
578             'edge' => {
579             anchor => 'x-vcg-anchor',
580             right_anchor => 'x-vcg-right_anchor',
581             left_anchor => 'x-vcg-left_anchor',
582             arrowcolor => 'x-vcg-arrowcolor',
583             arrowsize => 'x-vcg-arrowsize',
584             # XXX remap this
585             arrowstyle => 'x-vcg-arrowstyle',
586             backarrowcolor => 'x-vcg-backarrowcolor',
587             backarrowsize => 'x-vcg-backarrowsize',
588             backarrowstyle => 'x-vcg-backarrowstyle',
589             class => \&_edge_class_from_vcg,
590             color => \&_edge_color_from_vcg,
591             horizontal_order => 'x-vcg-horizontal_order',
592             linestyle => 'style',
593             priority => 'x-vcg-priority',
594             source => 'source',
595             sourcename => 'source',
596             target => 'target',
597             targetname => 'target',
598             textcolor => \&_edge_color_from_vcg,
599             thickness => 'x-vcg-thickness', # remap to broad etc.
600             },
601              
602             'graph' => {
603             color => \&_node_color_from_vcg,
604             bordercolor => \&_node_color_from_vcg,
605             textcolor => \&_node_color_from_vcg,
606              
607             x => 'x-vcg-x',
608             y => 'x-vcg-y',
609             xmax => 'x-vcg-xmax',
610             ymax => 'x-vcg-ymax',
611             xspace => 'x-vcg-xspace',
612             yspace => 'x-vcg-yspace',
613             xlspace => 'x-vcg-xlspace',
614             ylspace => 'x-vcg-ylspace',
615             xbase => 'x-vcg-xbase',
616             ybase => 'x-vcg-ybase',
617             xlraster => 'x-vcg-xlraster',
618             xraster => 'x-vcg-xraster',
619             yraster => 'x-vcg-yraster',
620              
621             amax => 'x-vcg-amax',
622             bmax => 'x-vcg-bmax',
623             cmax => 'x-vcg-cmax',
624             cmin => 'x-vcg-cmin',
625             smax => 'x-vcg-smax',
626             pmax => 'x-vcg-pmax',
627             pmin => 'x-vcg-pmin',
628             rmax => 'x-vcg-rmax',
629             rmin => 'x-vcg-rmin',
630              
631             splines => 'x-vcg-splines',
632             focus => 'x-vcg-focus',
633             hidden => 'x-vcg-hidden',
634             horizontal_order => 'x-vcg-horizontal_order',
635             iconfile => 'x-vcg-iconfile',
636             inport_sharing => \&_inport_sharing_from_vcg,
637             importance => 'x-vcg-importance',
638             ignore_singles => 'x-vcg-ignore_singles',
639             invisible => 'x-vcg-invisible',
640             info1 => 'x-vcg-info1',
641             info2 => 'x-vcg-info2',
642             info3 => 'x-vcg-info3',
643             infoname1 => 'x-vcg-infoname1',
644             infoname2 => 'x-vcg-infoname2',
645             infoname3 => 'x-vcg-infoname3',
646             level => 'x-vcg-level',
647             loc => 'x-vcg-loc',
648             layout_algorithm => 'x-vcg-layout_algorithm',
649             # also allow this variant:
650             layoutalgorithm => 'x-vcg-layout_algorithm',
651             layout_downfactor => 'x-vcg-layout_downfactor',
652             layout_upfactor => 'x-vcg-layout_upfactor',
653             layout_nearfactor => 'x-vcg-layout_nearfactor',
654             linear_segments => 'x-vcg-linear_segments',
655             margin => 'x-vcg-margin',
656             manhattan_edges => \&_manhattan_edges_from_vcg,
657             near_edges => 'x-vcg-near_edges',
658             nearedges => 'x-vcg-nearedges',
659             node_alignment => 'x-vcg-node_alignment',
660             port_sharing => \&_port_sharing_from_vcg,
661             priority_phase => 'x-vcg-priority_phase',
662             outport_sharing => \&_outport_sharing_from_vcg,
663             shape => 'x-vcg-shape',
664             smanhattan_edges => 'x-vcg-smanhattan_edges',
665             state => 'x-vcg-state',
666             splines => 'x-vcg-splines',
667             splinefactor => 'x-vcg-splinefactor',
668             spreadlevel => 'x-vcg-spreadlevel',
669              
670             title => 'label',
671             textmode => \&_textmode_from_vcg,
672             useractioncmd1 => 'x-vcg-useractioncmd1',
673             useractioncmd2 => 'x-vcg-useractioncmd2',
674             useractioncmd3 => 'x-vcg-useractioncmd3',
675             useractioncmd4 => 'x-vcg-useractioncmd4',
676             useractionname1 => 'x-vcg-useractionname1',
677             useractionname2 => 'x-vcg-useractionname2',
678             useractionname3 => 'x-vcg-useractionname3',
679             useractionname4 => 'x-vcg-useractionname4',
680             vertical_order => 'x-vcg-vertical_order',
681              
682             display_edge_labels => 'x-vcg-display_edge_labels',
683             edges => 'x-vcg-edges',
684             nodes => 'x-vcg-nodes',
685             icons => 'x-vcg-icons',
686             iconcolors => 'x-vcg-iconcolors',
687             view => 'x-vcg-view',
688             subgraph_labels => 'x-vcg-subgraph_labels',
689             arrow_mode => 'x-vcg-arrow_mode',
690             arrowmode => 'x-vcg-arrowmode',
691             crossing_optimization => 'x-vcg-crossing_optimization',
692             crossing_phase2 => 'x-vcg-crossing_phase2',
693             crossing_weight => 'x-vcg-crossing_weight',
694             equal_y_dist => 'x-vcg-equal_y_dist',
695             equalydist => 'x-vcg-equalydist',
696             finetuning => 'x-vcg-finetuning',
697             fstraight_phase => 'x-vcg-fstraight_phase',
698             straight_phase => 'x-vcg-straight_phase',
699             import_sharing => 'x-vcg-import_sharing',
700             late_edge_labels => 'x-vcg-late_edge_labels',
701             treefactor => 'x-vcg-treefactor',
702             orientation => \&_orientation_from_vcg,
703              
704             attraction => 'x-vcg-attraction',
705             'border x' => 'x-vcg-border-x',
706             'border y' => 'x-vcg-border-y',
707             'energetic' => 'x-vcg-energetic',
708             'energetic attraction' => 'x-vcg-energetic-attraction',
709             'energetic border' => 'x-vcg-energetic-border',
710             'energetic crossing' => 'x-vcg-energetic-crossing',
711             'energetic gravity' => 'x-vcg-energetic gravity',
712             'energetic overlapping' => 'x-vcg-energetic overlapping',
713             'energetic repulsion' => 'x-vcg-energetic repulsion',
714             fdmax => 'x-vcg-fdmax',
715             gravity => 'x-vcg-gravity',
716              
717             magnetic_field1 => 'x-vcg-magnetic_field1',
718             magnetic_field2 => 'x-vcg-magnetic_field2',
719             magnetic_force1 => 'x-vcg-magnetic_force1',
720             magnetic_force2 => 'x-vcg-magnetic_force2',
721             randomfactor => 'x-vcg-randomfactor',
722             randomimpulse => 'x-vcg-randomimpulse',
723             randomrounds => 'x-vcg-randomrounds',
724             repulsion => 'x-vcg-repulsion',
725             tempfactor => 'x-vcg-tempfactor',
726             tempmax => 'x-vcg-tempmax',
727             tempmin => 'x-vcg-tempmin'.
728             tempscheme => 'x-vcg-tempscheme'.
729             temptreshold => 'x-vcg-temptreshold',
730              
731             dirty_edge_labels => 'x-vcg-dirty_edge_labels',
732             fast_icons => 'x-vcg-fast_icons',
733              
734             },
735              
736             'group' => {
737             # graph attributes will be added here automatically
738             title => \&_group_name_from_vcg,
739             status => 'x-vcg-status',
740             },
741              
742             'all' => {
743             loc => 'x-vcg-loc',
744             folding => 'x-vcg-folding',
745             scaling => 'x-vcg-scaling',
746             shrink => 'x-vcg-shrink',
747             stretch => 'x-vcg-stretch',
748             width => 'x-vcg-width',
749             height => 'x-vcg-height',
750             fontname => 'font',
751             },
752             };
753              
754             {
755             # add all graph attributes to group, too
756             my $group = $vcg_remap->{group};
757             my $graph = $vcg_remap->{graph};
758             for my $k (sort keys %$graph)
759             {
760             $group->{$k} = $graph->{$k};
761             }
762             }
763              
764 57     57   128 sub _remap { $vcg_remap; }
765              
766             my $vcg_edge_color_remap = {
767             textcolor => 'labelcolor',
768             };
769              
770             my $vcg_node_color_remap = {
771             textcolor => 'color',
772             color => 'fill',
773             };
774              
775             sub _vertical_order_from_vcg
776             {
777             # remap "vertical_order: 5" to "rank: 5"
778 1     1   3 my ($graph, $name, $value) = @_;
779              
780 1         2 my $rank = $value;
781             # insert a really really high rank
782 1 50       4 $rank = '1000000' if $value eq 'maxdepth';
783              
784             # save the original value, too
785 1         5 ('x-vcg-vertical_order', $value, 'rank', $rank);
786             }
787              
788             sub _horizontal_order_from_vcg
789             {
790             # remap "horizontal_order: 5" to "rank: 5"
791 0     0   0 my ($graph, $name, $value) = @_;
792              
793 0         0 my $rank = $value;
794             # insert a really really high rank
795 0 0       0 $rank = '1000000' if $value eq 'maxdepth';
796              
797             # save the original value, too
798 0         0 ('x-vcg-horizontal_order', $value, 'rank', $rank);
799             }
800              
801             sub _invisible_from_vcg
802             {
803             # remap "invisible: yes" to "shape: invisible"
804 1     1   3 my ($graph, $name, $value) = @_;
805              
806 1 50       6 return (undef,undef) if $value ne 'yes';
807              
808 1         4 ('shape', 'invisible');
809             }
810              
811             sub _manhattan_edges_from_vcg
812             {
813             # remap "manhattan_edges: yes" for graphs
814 0     0   0 my ($graph, $name, $value) = @_;
815              
816 0 0       0 if ($value eq 'yes')
817             {
818 0         0 $graph->set_attribute('edge','start','front');
819 0         0 $graph->set_attribute('edge','end','back');
820             }
821             # store the value for proper VCG output
822 0         0 ('x-vcg-' . $name, $value);
823             }
824              
825             sub _textmode_from_vcg
826             {
827             # remap "textmode: left_justify" to "align: left;"
828 0     0   0 my ($graph, $name, $align) = @_;
829              
830 0         0 $align =~ s/_.*//; # left_justify => left
831              
832 0         0 ('align', lc($align));
833             }
834              
835             sub _edge_color_from_vcg
836             {
837             # remap "darkyellow" to "rgb(128 128 0)"
838 2     2   5 my ($graph, $name, $color) = @_;
839              
840             # print STDERR "edge $name $color\n";
841             # print STDERR ($vcg_edge_color_remap->{$name} || $name, " ", $vcg_color_by_name->{$color} || $color), "\n";
842              
843 2   33     9 my $c = $vcg_color_by_name->{$color} || $color;
844 2 50 33     9 $c = $graph->{_vcg_color_map}->[$c] if $c =~ /^[0-9]+\z/ && $c < 256;
845              
846 2   33     16 ($vcg_edge_color_remap->{$name} || $name, $c);
847             }
848              
849             sub _edge_class_from_vcg
850             {
851             # remap "1" to "edgeclass1" to create a valid class name
852 3     3   7 my ($graph, $name, $class) = @_;
853              
854 3 50 66     29 $class = $graph->{_vcg_class_names}->{$class} || ('edgeclass' . $class) if $class =~ /^[0-9]+\z/;
855             #$class = 'edgeclass' . $class if $class !~ /^[a-zA-Z]/;
856              
857 3         13 ('class', $class);
858             }
859              
860             my $vcg_orientation = {
861             top_to_bottom => 'south',
862             bottom_to_top => 'north',
863             left_to_right => 'east',
864             right_to_left => 'west',
865             };
866              
867             sub _orientation_from_vcg
868             {
869 4     4   10 my ($graph, $name, $value) = @_;
870              
871 4   50     35 ('flow', $vcg_orientation->{$value} || 'south');
872             }
873              
874             sub _port_sharing_from_vcg
875             {
876             # if we see this, add autojoin/autosplit
877 0     0   0 my ($graph, $name, $value) = @_;
878              
879 0 0       0 $value = ($value =~ /yes/i) ? 'yes' : 'no';
880            
881 0         0 ('autojoin', $value, 'autosplit', $value);
882             }
883              
884             sub _inport_sharing_from_vcg
885             {
886             # if we see this, add autojoin/autosplit
887 0     0   0 my ($graph, $name, $value) = @_;
888              
889 0 0       0 $value = ($value =~ /yes/i) ? 'yes' : 'no';
890            
891 0         0 ('autojoin', $value);
892             }
893              
894             sub _outport_sharing_from_vcg
895             {
896             # if we see this, add autojoin/autosplit
897 0     0   0 my ($graph, $name, $value) = @_;
898              
899 0 0       0 $value = ($value =~ /yes/i) ? 'yes' : 'no';
900            
901 0         0 ('autosplit', $value);
902             }
903              
904             sub _node_color_from_vcg
905             {
906             # remap "darkyellow" to "rgb(128 128 0)"
907 7     7   14 my ($graph, $name, $color) = @_;
908              
909 7   66     34 my $c = $vcg_color_by_name->{$color} || $color;
910 7 100 66     52 $c = $graph->{_vcg_color_map}->[$c] if $c =~ /^[0-9]+\z/ && $c < 256;
911              
912 7   33     40 ($vcg_node_color_remap->{$name} || $name, $c);
913             }
914              
915             my $shapes = {
916             box => 'rect',
917             rhomb => 'diamond',
918             triangle => 'triangle',
919             ellipse => 'ellipse',
920             circle => 'circle',
921             hexagon => 'hexagon',
922             trapeze => 'trapezium',
923             uptrapeze => 'invtrapezium',
924             lparallelogram => 'invparallelogram',
925             rparallelogram => 'parallelogram',
926             };
927              
928             sub _vcg_node_shape
929             {
930 2     2   5 my ($self, $name, $shape) = @_;
931              
932 2         5 my @rc;
933 2         5 my $s = lc($shape);
934              
935             # map the name to what Graph::Easy expects (ellipse stays as ellipse but
936             # everything unknown gets converted to rect)
937 2   50     11 $s = $shapes->{$s} || 'rect';
938              
939 2         9 (@rc, $name, $s);
940             }
941              
942             sub _group_name_from_vcg
943             {
944 0     0   0 my ($self, $attr, $name, $object) = @_;
945              
946 0 0       0 print STDERR "# Renaming anon group '$object->{name}' to '$name'\n"
947             if $self->{debug} > 0;
948              
949 0         0 $self->rename_group($object, $name);
950              
951             # name was set, so drop the "title: name" pair
952 0         0 (undef, undef);
953             }
954              
955             #############################################################################
956              
957             sub _remap_attributes
958             {
959 57     57   115 my ($self, $att, $object, $r) = @_;
960              
961             # print STDERR "# Remapping attributes\n";
962             # use Data::Dumper; print Dumper($att);
963              
964             # handle the "colorentry 00" entries:
965 57         232 for my $key (sort keys %$att)
966             {
967 87 100       197 if ($key =~ /^colorentry\s+([0-9]{1,2})/)
968             {
969             # put the color into the current color map
970 2         10 $self->_vcg_color_map_entry($1, $att->{$key});
971 2         3 delete $att->{$key};
972 2         5 next;
973             }
974              
975             # remap \fi065 to 'A'
976 85         155 $att->{$key} =~ s/(\x0c|\\f)i([0-9]{3})/ decode('iso-8859-1', chr($2)); /eg;
  2         19  
977              
978             # XXX TDOO: support inline colorations
979             # remap \f65 to ''
980 85         233 $att->{$key} =~ s/(\x0c|\\f)([0-9]{2})//g;
981              
982             # remap \c09 to color 09: TODO for now remove
983 85         136 $att->{$key} =~ s/(\x0c|\\f)([0-9]{2})//g;
984              
985             # XXX TODO: support real hor lines
986             # insert a fake
987 85         182 $att->{$key} =~ s/(\x0c|\\f)-/\\c ---- \\n /g;
988              
989             }
990 57         452 $self->SUPER::_remap_attributes($att,$object,$r);
991             }
992              
993             #############################################################################
994              
995             sub _parser_cleanup
996             {
997             # After initial parsing, do cleanup.
998 13     13   22 my ($self) = @_;
999              
1000 13         24 my $g = $self->{_graph};
1001 13         26 $g->{_warn_on_unknown_attributes} = 0; # reset to die again
1002              
1003 13         177 delete $g->{_vcg_color_map};
1004 13         37 delete $g->{_vcg_class_names};
1005              
1006 13         31 $self;
1007             }
1008              
1009             1;
1010             __END__