File Coverage

lib/Graph/Easy/As_graphml.pm
Criterion Covered Total %
statement 143 151 94.7
branch 28 36 77.7
condition 9 11 81.8
subroutine 15 15 100.0
pod 1 3 33.3
total 196 216 90.7


line stmt bran cond sub pod time code
1             #############################################################################
2             # Output an Graph::Easy object as GraphML text
3             #
4             #############################################################################
5              
6             package Graph::Easy::As_graphml;
7              
8             $VERSION = '0.75';
9              
10             #############################################################################
11             #############################################################################
12              
13             package Graph::Easy;
14              
15 2     2   12 use strict;
  2         5  
  2         101  
16 2     2   10 use warnings;
  2         5  
  2         70  
17              
18 2     2   10 use Graph::Easy::Attributes;
  2         5  
  2         1283  
19              
20             # map the Graph::Easy attribute types to a GraphML name:
21             my $attr_type_to_name =
22             {
23             ATTR_STRING() => 'string',
24             ATTR_COLOR() => 'string',
25             ATTR_ANGLE() => 'double',
26             ATTR_PORT() => 'string',
27             ATTR_UINT() => 'integer',
28             ATTR_URL() => 'string',
29              
30             ATTR_LIST() => 'string',
31             ATTR_LCTEXT() => 'string',
32             ATTR_TEXT() => 'string',
33             };
34              
35             sub _graphml_attr_keys
36             {
37 48     48   89 my ($self, $tpl, $tpl_no_default, $class, $att, $ids, $id) = @_;
38              
39 48         47 my $base_class = $class; $base_class =~ s/\..*//;
  48         84  
40 48 50       102 $base_class = 'graph' if $base_class =~ /group/;
41 48 100       133 $ids->{$base_class} = {} unless ref $ids->{$base_class};
42              
43 48         66 my $txt = '';
44 48         119 for my $name (sort keys %$att)
45             {
46 20         67 my $entry = $self->_attribute_entry($class,$name);
47             # get a fresh template
48 20         31 my $t = $tpl;
49 20 100       48 $t = $tpl_no_default unless defined $entry->[ ATTR_DEFAULT_SLOT ];
50              
51             # only keep it once
52 20 100       47 next if exists $ids->{$base_class}->{$name};
53              
54 18         75 $t =~ s/##id##/$$id/;
55              
56             # node.foo => node, group.bar => graph
57 18         310 $t =~ s/##class##/$base_class/;
58 18         55 $t =~ s/##name##/$name/;
59 18   50     52 $t =~ s/##type##/$attr_type_to_name->{ $entry->[ ATTR_TYPE_SLOT ] || ATTR_COLOR }/eg;
  18         88  
60              
61             # will only be there and thus replaced if we have a default
62 18 100       59 if ($t =~ /##default##/)
63             {
64 12         36 my $def = $entry->[ ATTR_DEFAULT_SLOT ];
65             # not a simple value?
66 12 50       31 $def = $self->default_attribute($name) if ref $def;
67 12         35 $t =~ s/##default##/$def/;
68             }
69              
70             # remember name => ID
71 18         45 $ids->{$base_class}->{$name} = $$id; $$id++;
  18         25  
72             # append the definition
73 18         53 $txt .= $t;
74             }
75 48         182 $txt;
76             }
77              
78             # yED example:
79              
80             #
81             #
82             #
83             #
84             #
85             # 1
86             #
87             #
88             #
89              
90 2     2   22 use Graph::Easy::Util qw(ord_values);
  2         5  
  2         2628  
91              
92             sub _as_graphml
93             {
94 18     18   35 my $self = shift;
95              
96 18         29 my $args = $_[0];
97 18 50 66     112 $args = { name => $_[0] } if ref($args) ne 'HASH' && @_ == 1;
98 18 100 100     108 $args = { @_ } if ref($args) ne 'HASH' && @_ > 1;
99            
100 18 100       68 $args->{format} = 'graph-easy' unless defined $args->{format};
101              
102 18 50       115 if ($args->{format} !~ /^(graph-easy|Graph::Easy|yED)\z/i)
103             {
104 0         0 return $self->error("Format '$args->{format}' not understood by as_graphml.");
105             }
106 18         33 my $format = $args->{format};
107              
108             # Convert the graph to a textual representation - does not need layout().
109              
110 18         31 my $schema = "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd";
111 18 100       52 $schema = "http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd" if $format eq 'yED';
112 18         29 my $y_schema = '';
113 18 100       48 $y_schema = "\n xmlns:y=\"http://www.yworks.com/xml/graphml\"" if $format eq 'yED';
114              
115 18         26 my $txt = <
116            
117            
118             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"##Y##
119             xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
120             ##SCHEMA##">
121              
122            
123              
124             EOF
125             ;
126            
127 18         120 $txt =~ s/##DATE##/scalar localtime()/e;
  18         2816  
128 18         194 $txt =~ s/##VERSION##/$Graph::Easy::VERSION/;
129 18         80 $txt =~ s/##SCHEMA##/$schema/;
130 18         79 $txt =~ s/##Y##/$y_schema/;
131              
132             #
133             # yellow
134             #
135             #
136              
137             # First gather all possible attributes, then add defines for them. This
138             # avoids lengthy re-definitions of attributes that aren't used:
139              
140 18         30 my %keys;
141              
142 18         33 my $tpl = ' '
143             ."\n ##default##\n"
144             ." \n";
145 18         33 my $tpl_no_default = ' '."\n";
146              
147             # for yED:
148             #
149             #
150             #
151             #
152             #
153              
154             # we need to remember the mapping between attribute name and ID:
155 18         33 my $ids = {};
156 18         33 my $id = 'd0';
157              
158             ###########################################################################
159             # first the class attributes
160 18         30 for my $class (sort keys %{$self->{att}})
  18         178  
161             {
162 14         28 my $att = $self->{att}->{$class};
163              
164 14         47 $txt .=
165             $self->_graphml_attr_keys( $tpl, $tpl_no_default, $class, $att, $ids, \$id);
166              
167             }
168              
169 18         101 my @nodes = $self->sorted_nodes('name','id');
170              
171             ###########################################################################
172             # now the attributes on the objects:
173 18         80 for my $o (@nodes, ord_values ( $self->{edges} ))
174             {
175 34         117 $txt .=
176             $self->_graphml_attr_keys( $tpl, $tpl_no_default, $o->class(),
177             $o->raw_attributes(), $ids, \$id);
178             }
179 18 100       62 $txt .= "\n" unless $id eq 'd0';
180              
181 18         26 my $indent = ' ';
182 18         159 $txt .= $indent . '\n";
183              
184             # output graph attributes:
185 18         92 $txt .= $self->_attributes_as_graphml($self,' ',$ids->{graph});
186              
187             # output groups recursively
188 18         178 my @groups = $self->groups_within(0);
189 18         126 foreach my $g (@groups)
190             {
191 2         13 $txt .= $g->as_graphml($indent.' ',$ids); # marks nodes as processed if nec.
192             }
193            
194 18         27 $indent = ' ';
195 18         36 foreach my $n (@nodes)
196             {
197 22 50       76 next if $n->{group}; # already done in a group
198 22         76 $txt .= $n->as_graphml($indent,$ids); #
199             }
200              
201 18         34 $txt .= "\n";
202              
203 18         37 foreach my $n (@nodes)
204             {
205 22 50       76 next if $n->{group}; # already done in a group
206              
207 22         107 my @out = $n->sorted_successors();
208             # for all outgoing connections
209 22         60 foreach my $other (@out)
210             {
211             # in case there exists more than one edge from $n --> $other
212 12         60 my @edges = $n->edges_to($other);
213 12         31 for my $edge (sort { $a->{id} <=> $b->{id} } @edges)
  0         0  
214             {
215 12         125 $txt .= $edge->as_graphml($indent,$ids); #
216             }
217             }
218             }
219              
220 18         40 $txt .= " \n\n";
221 18         185 $txt;
222             }
223              
224             sub _safe_xml
225             {
226             # make a text XML safe
227 70     70   255 my ($self,$txt) = @_;
228              
229 70         145 $txt =~ s/&/&/g; # quote &
230 70         113 $txt =~ s/>/>/g; # quote >
231 70         113 $txt =~ s/
232 70         112 $txt =~ s/"/"/g; # quote "
233 70         98 $txt =~ s/'/'/g; # quote '
234 70         101 $txt =~ s/\\\\/\\/g; # "\\" to "\"
235              
236 70         231 $txt;
237             }
238              
239             sub _attributes_as_graphml
240             {
241             # output the attributes of an object
242 56     56   447 my ($graph, $self, $indent, $ids) = @_;
243              
244 56         178 my $tpl = "$indent ##value##\n";
245 56         279 my $att = $self->get_attributes();
246 56         89 my $txt = '';
247 56         952 for my $n (sort keys %$att)
248             {
249 1802 100       3183 next unless exists $ids->{$n};
250 26         121 my $def = $self->default_attribute($n);
251 26 100 100     319 next if defined $def && $def eq $att->{$n};
252 20         31 my $t = $tpl;
253 20         125 $t =~ s/##id##/$ids->{$n}/;
254 20         58 $t =~ s/##value##/$graph->_safe_xml($att->{$n})/e;
  20         77  
255 20         135 $txt .= $t;
256             }
257 56         1018 $txt;
258             }
259              
260             #############################################################################
261              
262             package Graph::Easy::Group;
263              
264 2     2   13 use strict;
  2         4  
  2         64  
265              
266 2     2   11 use Graph::Easy::Util qw(ord_values);
  2         4  
  2         880  
267              
268             sub as_graphml
269             {
270 2     2 1 5 my ($self, $indent, $ids) = @_;
271              
272 2         31 my $txt = $indent . ' 273             $self->{graph}->type() . "\">\n";
274 2         10 $txt .= $self->{graph}->_attributes_as_graphml($self, $indent, $ids->{graph});
275              
276 2         19 foreach my $n (ord_values ( $self->{nodes} ))
277             {
278 2         15 my @out = $n->sorted_successors();
279              
280 2         13 $txt .= $n->as_graphml($indent.' ', $ids); #
281              
282             # for all outgoing connections
283 2         9 foreach my $other (@out)
284             {
285             # in case there exists more than one edge from $n --> $other
286 0         0 my @edges = $n->edges_to($other);
287 0         0 for my $edge (sort { $a->{id} <=> $b->{id} } @edges)
  0         0  
288             {
289 0         0 $txt .= $edge->as_graphml($indent.' ',$ids);
290             }
291 0 0       0 $txt .= "\n" if @edges > 0;
292             }
293             }
294              
295             # output groups recursively
296 2         41 my @groups = $self->groups_within(0);
297 2         6 foreach my $g (@groups)
298             {
299 0         0 $txt .= $g->_as_graphml($indent.' ',$ids); # marks nodes as processed if nec.
300             }
301              
302             # XXX TODO: edges from/to this group
303              
304             # close this group
305 2         6 $txt .= $indent . "";
306              
307 2         25 $txt;
308             }
309              
310             #############################################################################
311              
312             package Graph::Easy::Node;
313              
314 2     2   12 use strict;
  2         5  
  2         294  
315              
316             sub as_graphml
317             {
318 24     24 0 52 my ($self, $indent, $ids) = @_;
319              
320 24         43 my $g = $self->{graph};
321 24         90 my $txt = $indent . '\n";
322              
323 24         74 $txt .= $g->_attributes_as_graphml($self, $indent, $ids->{node});
324              
325 24         62 $txt .= "$indent\n";
326              
327 24         100 return $txt;
328             }
329              
330             #############################################################################
331              
332             package Graph::Easy::Edge;
333              
334 2     2   10 use strict;
  2         4  
  2         305  
335              
336             sub as_graphml
337             {
338 12     12 0 25 my ($self, $indent, $ids) = @_;
339              
340 12         19 my $g = $self->{graph};
341 12         57 my $txt = $indent . ' 342             '" target="' . $g->_safe_xml($self->{to}->{name}) . "\">\n";
343              
344 12         42 $txt .= $g->_attributes_as_graphml($self, $indent, $ids->{edge});
345              
346 12         34 $txt .= "$indent\n";
347              
348 12         94 $txt;
349             }
350            
351             1;
352             __END__