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.76';
9              
10             #############################################################################
11             #############################################################################
12              
13             package Graph::Easy;
14              
15 2     2   6 use strict;
  2         2  
  2         62  
16 2     2   6 use warnings;
  2         2  
  2         43  
17              
18 2     2   6 use Graph::Easy::Attributes;
  2         2  
  2         595  
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   57 my ($self, $tpl, $tpl_no_default, $class, $att, $ids, $id) = @_;
38              
39 48         29 my $base_class = $class; $base_class =~ s/\..*//;
  48         52  
40 48 50       60 $base_class = 'graph' if $base_class =~ /group/;
41 48 100       83 $ids->{$base_class} = {} unless ref $ids->{$base_class};
42              
43 48         37 my $txt = '';
44 48         64 for my $name (sort keys %$att)
45             {
46 20         42 my $entry = $self->_attribute_entry($class,$name);
47             # get a fresh template
48 20         22 my $t = $tpl;
49 20 100       32 $t = $tpl_no_default unless defined $entry->[ ATTR_DEFAULT_SLOT ];
50              
51             # only keep it once
52 20 100       31 next if exists $ids->{$base_class}->{$name};
53              
54 18         46 $t =~ s/##id##/$$id/;
55              
56             # node.foo => node, group.bar => graph
57 18         35 $t =~ s/##class##/$base_class/;
58 18         32 $t =~ s/##name##/$name/;
59 18   50     24 $t =~ s/##type##/$attr_type_to_name->{ $entry->[ ATTR_TYPE_SLOT ] || ATTR_COLOR }/eg;
  18         55  
60              
61             # will only be there and thus replaced if we have a default
62 18 100       31 if ($t =~ /##default##/)
63             {
64 12         12 my $def = $entry->[ ATTR_DEFAULT_SLOT ];
65             # not a simple value?
66 12 50       20 $def = $self->default_attribute($name) if ref $def;
67 12         20 $t =~ s/##default##/$def/;
68             }
69              
70             # remember name => ID
71 18         26 $ids->{$base_class}->{$name} = $$id; $$id++;
  18         15  
72             # append the definition
73 18         30 $txt .= $t;
74             }
75 48         103 $txt;
76             }
77              
78             # yED example:
79              
80             #
81             #
82             #
83             #
84             #
85             # 1
86             #
87             #
88             #
89              
90 2     2   9 use Graph::Easy::Util qw(ord_values);
  2         2  
  2         1424  
91              
92             sub _as_graphml
93             {
94 18     18   19 my $self = shift;
95              
96 18         12 my $args = $_[0];
97 18 50 66     64 $args = { name => $_[0] } if ref($args) ne 'HASH' && @_ == 1;
98 18 100 100     50 $args = { @_ } if ref($args) ne 'HASH' && @_ > 1;
99              
100 18 100       37 $args->{format} = 'graph-easy' unless defined $args->{format};
101              
102 18 50       66 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         14 my $format = $args->{format};
107              
108             # Convert the graph to a textual representation - does not need layout().
109              
110 18         16 my $schema = "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd";
111 18 100       23 $schema = "http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd" if $format eq 'yED';
112 18         15 my $y_schema = '';
113 18 100       22 $y_schema = "\n xmlns:y=\"http://www.yworks.com/xml/graphml\"" if $format eq 'yED';
114              
115 18         14 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         54 $txt =~ s/##DATE##/scalar localtime()/e;
  18         497  
128 18         60 $txt =~ s/##VERSION##/$Graph::Easy::VERSION/;
129 18         317 $txt =~ s/##SCHEMA##/$schema/;
130 18         43 $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         16 my %keys;
141              
142 18         15 my $tpl = ' '
143             ."\n ##default##\n"
144             ." \n";
145 18         14 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         18 my $ids = {};
156 18         18 my $id = 'd0';
157              
158             ###########################################################################
159             # first the class attributes
160 18         13 for my $class (sort keys %{$self->{att}})
  18         65  
161             {
162 14         20 my $att = $self->{att}->{$class};
163              
164 14         22 $txt .=
165             $self->_graphml_attr_keys( $tpl, $tpl_no_default, $class, $att, $ids, \$id);
166              
167             }
168              
169 18         45 my @nodes = $self->sorted_nodes('name','id');
170              
171             ###########################################################################
172             # now the attributes on the objects:
173 18         48 for my $o (@nodes, ord_values ( $self->{edges} ))
174             {
175 34         72 $txt .=
176             $self->_graphml_attr_keys( $tpl, $tpl_no_default, $o->class(),
177             $o->raw_attributes(), $ids, \$id);
178             }
179 18 100       36 $txt .= "\n" unless $id eq 'd0';
180              
181 18         15 my $indent = ' ';
182 18         42 $txt .= $indent . '\n";
183              
184             # output graph attributes:
185 18         42 $txt .= $self->_attributes_as_graphml($self,' ',$ids->{graph});
186              
187             # output groups recursively
188 18         50 my @groups = $self->groups_within(0);
189 18         20 foreach my $g (@groups)
190             {
191 2         7 $txt .= $g->as_graphml($indent.' ',$ids); # marks nodes as processed if nec.
192             }
193              
194 18         17 $indent = ' ';
195 18         15 foreach my $n (@nodes)
196             {
197 22 50       36 next if $n->{group}; # already done in a group
198 22         32 $txt .= $n->as_graphml($indent,$ids); #
199             }
200              
201 18         19 $txt .= "\n";
202              
203 18         19 foreach my $n (@nodes)
204             {
205 22 50       39 next if $n->{group}; # already done in a group
206              
207 22         42 my @out = $n->sorted_successors();
208             # for all outgoing connections
209 22         27 foreach my $other (@out)
210             {
211             # in case there exists more than one edge from $n --> $other
212 12         25 my @edges = $n->edges_to($other);
213 12         16 for my $edge (sort { $a->{id} <=> $b->{id} } @edges)
  0         0  
214             {
215 12         22 $txt .= $edge->as_graphml($indent,$ids); #
216             }
217             }
218             }
219              
220 18         19 $txt .= " \n\n";
221 18         74 $txt;
222             }
223              
224             sub _safe_xml
225             {
226             # make a text XML safe
227 70     70   62 my ($self,$txt) = @_;
228              
229 70         79 $txt =~ s/&/&/g; # quote &
230 70         57 $txt =~ s/>/>/g; # quote >
231 70         54 $txt =~ s/
232 70         61 $txt =~ s/"/"/g; # quote "
233 70         46 $txt =~ s/'/'/g; # quote '
234 70         47 $txt =~ s/\\\\/\\/g; # "\\" to "\"
235              
236 70         123 $txt;
237             }
238              
239             sub _attributes_as_graphml
240             {
241             # output the attributes of an object
242 56     56   71 my ($graph, $self, $indent, $ids) = @_;
243              
244 56         60 my $tpl = "$indent ##value##\n";
245 56         106 my $att = $self->get_attributes();
246 56         49 my $txt = '';
247 56         573 for my $n (sort keys %$att)
248             {
249 1802 100       1958 next unless exists $ids->{$n};
250 26         46 my $def = $self->default_attribute($n);
251 26 100 100     97 next if defined $def && $def eq $att->{$n};
252 20         18 my $t = $tpl;
253 20         71 $t =~ s/##id##/$ids->{$n}/;
254 20         31 $t =~ s/##value##/$graph->_safe_xml($att->{$n})/e;
  20         30  
255 20         41 $txt .= $t;
256             }
257 56         283 $txt;
258             }
259              
260             #############################################################################
261              
262             package Graph::Easy::Group;
263              
264 2     2   8 use strict;
  2         4  
  2         39  
265              
266 2     2   5 use Graph::Easy::Util qw(ord_values);
  2         2  
  2         435  
267              
268             sub as_graphml
269             {
270 2     2 1 4 my ($self, $indent, $ids) = @_;
271              
272             my $txt = $indent . ' 273 2         14 $self->{graph}->type() . "\">\n";
274 2         7 $txt .= $self->{graph}->_attributes_as_graphml($self, $indent, $ids->{graph});
275              
276 2         9 foreach my $n (ord_values ( $self->{nodes} ))
277             {
278 2         7 my @out = $n->sorted_successors();
279              
280 2         7 $txt .= $n->as_graphml($indent.' ', $ids); #
281              
282             # for all outgoing connections
283 2         6 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         14 my @groups = $self->groups_within(0);
297 2         4 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         5 $txt .= $indent . "";
306              
307 2         6 $txt;
308             }
309              
310             #############################################################################
311              
312             package Graph::Easy::Node;
313              
314 2     2   8 use strict;
  2         3  
  2         219  
315              
316             sub as_graphml
317             {
318 24     24 0 26 my ($self, $indent, $ids) = @_;
319              
320 24         19 my $g = $self->{graph};
321 24         41 my $txt = $indent . '\n";
322              
323 24         40 $txt .= $g->_attributes_as_graphml($self, $indent, $ids->{node});
324              
325 24         37 $txt .= "$indent\n";
326              
327 24         39 return $txt;
328             }
329              
330             #############################################################################
331              
332             package Graph::Easy::Edge;
333              
334 2     2   8 use strict;
  2         2  
  2         178  
335              
336             sub as_graphml
337             {
338 12     12 0 11 my ($self, $indent, $ids) = @_;
339              
340 12         8 my $g = $self->{graph};
341             my $txt = $indent . ' 342 12         23 '" target="' . $g->_safe_xml($self->{to}->{name}) . "\">\n";
343              
344 12         22 $txt .= $g->_attributes_as_graphml($self, $indent, $ids->{edge});
345              
346 12         18 $txt .= "$indent\n";
347              
348 12         39 $txt;
349             }
350              
351             1;
352             __END__