File Coverage

blib/lib/Graph/Dijkstra.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Graph::Dijkstra;
2            
3 1     1   21775 use strict;
  1         3  
  1         27  
4 1     1   5 use warnings;
  1         2  
  1         32  
5            
6 1     1   5 use Carp qw(croak carp);
  1         5  
  1         74  
7            
8 1     1   12222 use English qw(-no_match_vars);
  1         18343  
  1         6  
9             $OUTPUT_AUTOFLUSH=1;
10            
11            
12 1     1   656 use vars qw($VERSION);
  1         2  
  1         67  
13             $VERSION = '0.55';
14            
15             my $VERBOSE = 0;
16            
17 1     1   690 use Readonly;
  0            
  0            
18            
19             Readonly my $EMPTY_STRING => q{};
20             Readonly my %IS_GRAPHML_WEIGHT_ATTR => map { ($_ => 1) } qw(weight value cost distance height);
21             Readonly my %IS_GRAPHML_LABEL_ATTR => map { ($_ => 1) } qw(label name description nlabel);
22             Readonly my $PINF => 1e9999; # positive infinity
23             Readonly my %GRAPH_ATTRIBUTES => (label=>$EMPTY_STRING, creator=>$EMPTY_STRING, edgedefault=>'undirected');
24             Readonly my %NODE_ATTRIBUTES => (label=>$EMPTY_STRING);
25             Readonly my %EDGE_ATTRIBUTES => (id=>$EMPTY_STRING, label=>$EMPTY_STRING, directed=>'undirected', weight=>0);
26            
27             ## no critic (PostfixControls)
28            
29             #############################################################################
30             #used Modules #
31             #############################################################################
32            
33            
34             use Benchmark qw(:hireswallclock);
35             use Array::Heap::ModifiablePriorityQueue;
36             use Scalar::Util qw(looks_like_number);
37             use HTML::Entities qw(encode_entities);
38             use utf8;
39            
40             #############################################################################
41             #Class Methods #
42             #############################################################################
43            
44             sub VERBOSE {
45             my $either = shift;
46             $VERBOSE = shift;
47             print "verbose output set\n" if ($VERBOSE);
48             return $either;
49             }
50            
51             sub stringifyAttribs {
52             my ($either, $attribHref) = @_;
53            
54             return if ref($attribHref) ne 'HASH';
55            
56             my $val = '';
57             foreach my $attrib (sort keys %$attribHref) {
58             $val .= ', ' if $val;
59             my $printval = (looks_like_number($attribHref->{$attrib})) ? "$attribHref->{$attrib}" : "'".encode_entities($attribHref->{$attrib})."'";
60             $val .= "$attrib=>$printval";
61             }
62             return "( $val )";
63             }
64            
65             sub hashifyAttribs {
66             my ($either, $attribStr) = @_;
67            
68             my %keyvals = ();
69            
70             if ($attribStr =~ /^\(\s*(.+)\s*\)$/) {
71             $attribStr = $1;
72             }
73             while ($attribStr =~ /([a-z]+) => ([+-]? [0-9]+ (?: \. [0-9]+ )?+|(?:(?:\')(?:[^\\\']*(?:\\.[^\\\']*)*)(?:\')))/igx) {
74             my $id = $1;
75             my $val = $2;
76             $val = substr($val,1,-1) if substr($val,0,1) eq "'";
77             $keyvals{$id} = $val;
78             }
79             return \%keyvals;
80             }
81            
82            
83             sub _initialize {
84             my ($self, $options) = @_;
85             $self->{graph} = ();
86            
87             foreach my $attrib (keys %GRAPH_ATTRIBUTES) {
88             $self->{$attrib} = $GRAPH_ATTRIBUTES{$attrib};
89             }
90            
91             if (ref($options) eq 'HASH') {
92             foreach my $attrib (keys %$options) {
93             if (exists($GRAPH_ATTRIBUTES{$attrib})) {
94             $self->{$attrib} = $options->{$attrib};
95             }
96             else {
97             carp "new: unrecognized graph attribute '$attrib'";
98             }
99             }
100             }
101             return $self;
102             }
103            
104             sub new {
105             my ($class, $options) = @_;
106            
107             my $self = {};
108             bless $self, $class;
109            
110             return $self->_initialize($options);
111            
112             #return $self;
113             }
114            
115            
116             #############################################################################
117             #Graph Method(s) #
118             #############################################################################
119            
120             sub graph {
121             my ($self, $options) = @_;
122             if (defined($options) and ref($options) eq 'HASH') { #SET method call
123             foreach my $attrib (keys %$options) {
124             if (exists($GRAPH_ATTRIBUTES{$attrib})) {
125             utf8::upgrade($options->{$attrib});
126             $self->{$attrib} = $options->{$attrib};
127             }
128             else {
129             carp "new: unrecognized graph attribute '$attrib'";
130             }
131             }
132             return $self;
133             }
134             #GET method call
135            
136             return( {map { ($_ => $self->{$_} ) } (keys %GRAPH_ATTRIBUTES)} );
137             }
138            
139             #############################################################################
140             #Node Methods #
141             #############################################################################
142            
143             sub node {
144             my ($self, $nodeParam) = @_;
145            
146             croak "node: missing nodeID / options parameter" if !defined($nodeParam);
147            
148             if (ref($nodeParam) eq $EMPTY_STRING) { #GET method call
149             #my $nodeID = $nodeParam;
150            
151             if (exists($self->{graph}{$nodeParam})) {
152             my %node = map { ($_ => $self->{graph}{$nodeParam}{$_} ) } (keys %NODE_ATTRIBUTES);
153             $node{id} = $nodeParam;
154             return( \%node );
155             }
156             return;
157             }
158            
159             if (ref($nodeParam) eq 'HASH') { #SET method call
160            
161             croak "node: missing \"id\" attribute in attributes hash" if !exists($nodeParam->{id});
162             my $nodeID = $nodeParam->{id};
163             croak "node: nodeID is not a SCALAR value" if ref($nodeID) ne $EMPTY_STRING;
164            
165             if (!exists($self->{graph}{$nodeID})) { #set default node attribute values for new node
166             foreach my $attrib (keys %NODE_ATTRIBUTES) {
167             $self->{graph}{$nodeID}{$attrib} = $NODE_ATTRIBUTES{$attrib};
168             }
169             }
170            
171             foreach my $attrib (keys %$nodeParam) { #update node attribute values from parameter values
172             if ( exists($NODE_ATTRIBUTES{$attrib}) ) {
173             utf8::upgrade($nodeParam->{$attrib});
174             $self->{graph}{$nodeID}{$attrib} = $nodeParam->{$attrib};
175             }
176             elsif ($attrib ne 'id') {
177             carp "node: unrecognized node attribute '$attrib'";
178             }
179             }
180            
181             return $self;
182             }
183             croak "node: invalid parameter: must be either a nodeID (simple scalar) or an attributes hash (reference)";
184             }
185            
186             sub nodeExists {
187             my ($self, $nodeID) = @_;
188            
189             croak "nodeExists: missing nodeID parameter" if !defined($nodeID);
190            
191             return (exists($self->{graph}{$nodeID})) ? 1 : 0;
192             }
193            
194            
195             sub nodeList {
196             my $self = shift;
197            
198             my @nodeList = ();
199             foreach my $node (keys %{$self->{graph}}) {
200             push(@nodeList, { id=>$node, map {($_ => $self->{graph}{$node}{$_})} (keys %NODE_ATTRIBUTES) } );
201             }
202             return @nodeList;
203             }
204            
205            
206             sub removeNode {
207             my ($self, $nodeID) = @_;
208            
209             croak "removeNode: missing nodeID parameter" if !defined($nodeID);
210            
211             if (exists($self->{graph}{$nodeID})) {
212             if (exists($self->{graph}{$nodeID}{edges})) {
213             foreach my $targetID (sort keys %{$self->{graph}{$nodeID}{edges}}) {
214             delete($self->{graph}{$targetID}{edges}{$nodeID});
215             }
216             }
217             delete($self->{graph}{$nodeID});
218             return $self;
219             }
220             return;
221             }
222            
223             #############################################################################
224             #Edge Methods #
225             #############################################################################
226            
227            
228             sub edge {
229             my ($self, $edgeHref) = @_;
230            
231             croak "edge: missing parameter hash reference" if !defined($edgeHref) or ref($edgeHref) ne 'HASH';
232             croak "edge: parameter hash missing sourceID" if !exists($edgeHref->{sourceID});
233             croak "edge: parameter hash missing targetID" if !exists($edgeHref->{targetID});
234            
235             my $sourceID = $edgeHref->{sourceID};
236             my $targetID = $edgeHref->{targetID};
237            
238             if (defined( $edgeHref->{weight} )) { #SET method call
239            
240             my $weight = $edgeHref->{weight};
241            
242             if ($weight <= 0) {
243             carp "edge: invalid edge cost $sourceID $targetID $weight";
244             return;
245             }
246            
247             if ($sourceID eq $targetID) {
248             carp "edge: source and target node IDs must be different";
249             return;
250             }
251            
252             if (!exists($self->{graph}{$sourceID})) {
253             carp "edge: sourceID $sourceID does not exist. Edge not created.";
254             return;
255             }
256             if (!exists($self->{graph}{$targetID})) {
257             carp "edge: targetID $targetID does not exist. Edge not created.";
258             return;
259             }
260             if (exists($edgeHref->{directed}) and ($edgeHref->{directed} ne 'directed' and $edgeHref->{directed} ne 'undirected')) {
261             carp "edge: unrecognized 'directed' attribute value '$edgeHref->{directed}'. Edge not created.";
262             return;
263             }
264            
265             $edgeHref->{directed} = $self->{edgedefault} if !exists($edgeHref->{directed});
266            
267             if (exists($self->{graph}{$sourceID}{edges}{$targetID}) and $self->{graph}{$sourceID}{edges}{$targetID}{directed} ne $edgeHref->{directed} ) {
268             carp "edge: edge (arc) $sourceID $targetID exists with different directed value. Edge not created.";
269             return;
270             }
271             if ($edgeHref->{directed} eq 'undirected' and exists($self->{graph}{$targetID}{edges}{$sourceID}) and $self->{graph}{$targetID}{edges}{$sourceID}{directed} eq 'directed') {
272             carp "edge: directed arc (edge) $targetID $sourceID exists. Undirected edges $sourceID $targetID not created.";
273             return;
274             }
275            
276             if (!exists($self->{graph}{$sourceID}{edges}{$targetID})) { #set default edge attribute values for new edge
277             foreach my $attrib (keys %EDGE_ATTRIBUTES) {
278             $self->{graph}{$sourceID}{edges}{$targetID}{$attrib} = $EDGE_ATTRIBUTES{$attrib};
279             $self->{graph}{$targetID}{edges}{$sourceID}{$attrib} = $EDGE_ATTRIBUTES{$attrib} if $edgeHref->{directed} eq 'undirected';
280             }
281             }
282            
283             foreach my $attrib (keys %$edgeHref) { #update node attribute values from parameter values
284             if ( exists($EDGE_ATTRIBUTES{$attrib}) ) {
285             utf8::upgrade($edgeHref->{$attrib});
286             $self->{graph}{$sourceID}{edges}{$targetID}{$attrib} = $edgeHref->{$attrib};
287             $self->{graph}{$targetID}{edges}{$sourceID}{$attrib} = $edgeHref->{$attrib} if $edgeHref->{directed} eq 'undirected';
288             }
289             elsif ($attrib ne 'sourceID' and $attrib ne 'targetID') {
290             carp "edge: unrecognized attribute '$attrib' not set";
291             }
292             }
293            
294             #$self->{graph}{$sourceID}{edges}{$targetID}{weight} = $weight;
295             #$self->{graph}{$targetID}{edges}{$sourceID}{weight} = $weight;
296            
297             return($self);
298             }
299             else { #GET method call
300            
301             if (exists($self->{graph}{$sourceID}{edges}{$targetID})) {
302             #return( {sourceID=>$sourceID, targetID=>$targetID, weight=>$self->{graph}{$sourceID}{edges}{$targetID}{weight} } );
303             return( {sourceID=>$sourceID, targetID=>$targetID, map { ($_ => $self->{graph}{$sourceID}{edges}{$targetID}{$_} ) } (keys %EDGE_ATTRIBUTES) } );
304             }
305            
306             if (exists($self->{graph}{$sourceID}) and exists($self->{graph}{$targetID})) {
307             return( {sourceID=>$sourceID, targetID=>$targetID, weight=>0} );
308             }
309            
310             if (!exists($self->{graph}{$sourceID})) {
311             carp "edge: sourceID $sourceID does not exist";
312             return;
313             }
314            
315             carp "edge: targetID $targetID does not exist";
316             return;
317             }
318             }
319            
320            
321             sub removeEdge {
322             my ($self, $edgeHref) = @_;
323            
324             croak "removeEdge: missing parameter hash reference" if !defined($edgeHref);
325             croak "removeEdge: parameter hash missing sourceID" if !exists($edgeHref->{sourceID});
326             croak "removeEdge: parameter hash missing targetID" if !exists($edgeHref->{targetID});
327            
328             my $sourceID = $edgeHref->{sourceID};
329             my $targetID = $edgeHref->{targetID};
330            
331             if (exists($self->{graph}{$sourceID}{edges}{$targetID})) {
332            
333             my $directed = $self->{graph}{$sourceID}{edges}{$targetID}{directed};
334            
335             delete($self->{graph}{$sourceID}{edges}{$targetID});
336            
337             my $hasNeighbors = 0;
338             foreach my $neighbor (keys %{$self->{graph}{$sourceID}{edges}}) {
339             $hasNeighbors = 1;
340             last;
341             }
342             if (!$hasNeighbors) {
343             delete($self->{graph}{$sourceID}{edges});
344             }
345            
346             if ($directed eq 'undirected') { #remove $targetID $sourceID for undirected edges
347            
348             delete($self->{graph}{$targetID}{edges}{$sourceID});
349            
350             my $hasNeighbors = 0;
351             foreach my $neighbor (keys %{$self->{graph}{$targetID}{edges}}) {
352             $hasNeighbors = 1;
353             last;
354             }
355             if (!$hasNeighbors) {
356             delete($self->{graph}{$targetID}{edges});
357             }
358             }
359             }
360             else {
361             carp "removeEdge: no edge found for sourceID $sourceID and targetID $targetID";
362             }
363            
364             return $self;
365             }
366            
367            
368            
369             sub edgeExists {
370             my ($self, $edgeHref) = @_;
371            
372             croak "edgeExists: missing parameter hash reference" if !defined($edgeHref);
373             croak "edgeExists: parameter hash missing sourceID" if !exists($edgeHref->{sourceID});
374             croak "edgeExists: parameter hash missing targetID" if !exists($edgeHref->{targetID});
375            
376             my $sourceID = $edgeHref->{sourceID};
377             my $targetID = $edgeHref->{targetID};
378            
379             return (exists($self->{graph}{$sourceID}{edges}{$targetID})) ? 1 : 0;
380             }
381            
382            
383             sub adjacent {
384             my ($self, $edgeHref) = @_;
385            
386             croak "adjacent: missing parameter hash reference" if !defined($edgeHref);
387             croak "adjacent: parameter hash missing sourceID" if !exists($edgeHref->{sourceID});
388             croak "adjacent: parameter hash missing targetID" if !exists($edgeHref->{targetID});
389            
390             my $sourceID = $edgeHref->{sourceID};
391             my $targetID = $edgeHref->{targetID};
392            
393             return ( exists($self->{graph}{$sourceID}{edges}{$targetID}) ) ? 1 : 0;
394             }
395            
396            
397             sub adjacentNodes {
398             my ($self, $sourceID) = @_;
399            
400             if (!defined($sourceID)) {
401             croak "adjacentNodes: missing node ID parameter";
402             }
403            
404             my @neighbors = ();
405             if (exists($self->{graph}{$sourceID}{edges})) {
406             foreach my $targetID (sort keys %{$self->{graph}{$sourceID}{edges}}) {
407             push(@neighbors, $targetID);
408             }
409             }
410             return @neighbors;
411             }
412            
413            
414            
415             #############################################################################
416             #Dijkstra Computation Methods #
417             #############################################################################
418            
419             #Computes Jordan center by creating all pairs shortest path matrix
420            
421             sub vertexCenter {
422             my ($self, $solutionMatrix) = @_;
423            
424             %$solutionMatrix = ();
425            
426             my @connectedNodeList = ();
427            
428             my $totalNodes = 0;
429             foreach my $nodeID ( keys %{$self->{graph}} ) {
430             $totalNodes++;
431             next if !exists($self->{graph}{$nodeID}{edges}); #exclude disconnected nodes.
432             push(@connectedNodeList, $nodeID);
433             }
434             my $nodeCount = scalar(@connectedNodeList);
435             print "vertexCenter: graph contains $nodeCount connected nodes (nodes >= 1 edges)\n" if $VERBOSE;
436             carp "vertexCenter: excluded one or more disconnected nodes (nodes with no edges)" if ($nodeCount < $totalNodes);
437            
438             foreach my $fromNodeID (@connectedNodeList) {
439            
440             $solutionMatrix->{rowMax}{$fromNodeID} = $PINF;
441            
442             foreach my $toNodeID (@connectedNodeList) {
443             $solutionMatrix->{row}{$fromNodeID}{$toNodeID} = $PINF;
444             }
445             $solutionMatrix->{row}{$fromNodeID}{$fromNodeID} = 0;
446             }
447             my $hasDirectedEdges = 0;
448             foreach my $nodeID (@connectedNodeList) {
449             foreach my $targetID (keys %{$self->{graph}{$nodeID}{edges}}) {
450             if ($self->{graph}{$nodeID}{edges}{$targetID}{directed} eq 'directed') {
451             $hasDirectedEdges = 1;
452             last;
453             }
454             }
455             last if $hasDirectedEdges;
456             }
457             my $matrixComputations = ($nodeCount * $nodeCount) - $nodeCount;
458             $matrixComputations = $matrixComputations / 2 if !$hasDirectedEdges;
459             print "vertexCenter: graph has directed edges. Computing shortest path for A -> C and C -> A separately.\n" if $hasDirectedEdges and $VERBOSE;
460             print "vertexCenter: graph has no directed edges. Shortest path for A -> C and C -> A are same.\n" if !$hasDirectedEdges and $VERBOSE;
461             print "vertexCenter: performing $matrixComputations shortest path computations.\n" if $VERBOSE;
462            
463             #should add code to limit computations at reasonable number
464            
465             my $cycle = 0;
466             my $t0 = Benchmark->new;
467            
468             foreach my $origin (@connectedNodeList) {
469             foreach my $destination (@connectedNodeList) {
470            
471             next if $solutionMatrix->{row}{$origin}{$destination} < $PINF or $origin eq $destination;
472             #print "shortest path $origin -> $destination...";
473            
474             my $pq = Array::Heap::ModifiablePriorityQueue->new();
475            
476             my %solution = ();
477             my %unvisited = ();
478             foreach my $node (@connectedNodeList) {
479             $solution{$node}{weight} = $PINF;
480             $pq->add($node, $PINF);
481             }
482            
483             $solution{$origin}{weight} = 0;
484             $pq->add($origin,0); #modify weight of origin node
485            
486            
487             #my $foundSolution = 0;
488             while ($pq->size()) {
489             $cycle++;
490            
491             my $visitNode = $pq->get();
492            
493             $solutionMatrix->{row}{$origin}{$visitNode} = $solution{$visitNode}{weight};
494             $solutionMatrix->{row}{$visitNode}{$origin} = $solution{$visitNode}{weight} if !$hasDirectedEdges;
495            
496             last if ($visitNode eq $destination);
497            
498             foreach my $adjacentNode (keys %{$self->{graph}{$visitNode}{edges}}) {
499             next if !defined($pq->weight($adjacentNode));
500            
501             my $thisWeight = $solution{$visitNode}{weight} + $self->{graph}{$visitNode}{edges}{$adjacentNode}{weight};
502             if ($thisWeight < $solution{$adjacentNode}{weight}) {
503             $solution{$adjacentNode}{weight} = $thisWeight;
504             # $solution{$adjacentNode}{prevnode} = $visitNode;
505             $pq->add($adjacentNode, $thisWeight);
506             }
507             }
508             }
509            
510             undef($pq);
511             }
512             }
513             if ($VERBOSE) {
514             my $t1 = Benchmark->new;
515             #if ($cycle >= 1000) {
516             # print "\n";
517             #}
518             my $td = timediff($t1, $t0);
519             print "computing shortest path matrix took: ",timestr($td),"\n";
520             }
521             my $graphMinMax = $PINF;
522             my $centerNode = '';
523             foreach my $origin (@connectedNodeList) {
524             my $rowMax = 0;
525             foreach my $destination (@connectedNodeList) {
526             next if $origin eq $destination;
527             if ($solutionMatrix->{row}{$origin}{$destination} > $rowMax) {
528             $rowMax = $solutionMatrix->{row}{$origin}{$destination};
529             }
530             }
531             $solutionMatrix->{rowMax}{$origin} = $rowMax;
532             if ($rowMax < $graphMinMax) {
533             $graphMinMax = $rowMax;
534             }
535             }
536             $solutionMatrix->{centerNodeSet} = [];
537             if ($graphMinMax < $PINF) {
538             foreach my $origin (@connectedNodeList) {
539             if ($solutionMatrix->{rowMax}{$origin} == $graphMinMax) {
540             push(@{$solutionMatrix->{centerNodeSet}}, $origin);
541             }
542             }
543             }
544             else {
545             carp "Graph contains disconnected sub-graph. Center node set undefined.";
546             $graphMinMax = 0;
547             }
548             #print "centernodeset ", join(',', @{$solutionMatrix->{centerNodeSet}}), "\n";
549             return($graphMinMax);
550             }
551            
552             sub farthestNode { ## no critic (ProhibitExcessComplexity)
553             my ($self, $solutionHref) = @_;
554            
555             if (!exists($solutionHref->{originID})) {
556             croak "farthestNode: originID attribute not set in solution hash reference parameter";
557             }
558             my $originID = $solutionHref->{originID};
559            
560             if (!exists($self->{graph}{$originID})) {
561             carp "farthestNode: originID not found: $originID";
562             return 0;
563             }
564             elsif (!exists($self->{graph}{$originID}{edges})) {
565             carp "farthestNode: origin node $originID has no edges";
566             return 0;
567             }
568             my $pq = Array::Heap::ModifiablePriorityQueue->new();
569            
570             my %solution = (); #initialize the solution hash
571             my %unvisited = ();
572             foreach my $node (keys %{$self->{graph}}) {
573             if (exists($self->{graph}{$node}{edges})) { #nodes without edges cannot be part of the solution
574             $solution{$node}{weight} = $PINF;
575             $solution{$node}{prevnode} = $EMPTY_STRING;
576             $pq->add($node, $PINF);
577             }
578             }
579            
580             $solution{$originID}{weight} = 0;
581             $pq->add($originID,0); #modify weight of origin node
582            
583             my $cycle = 0;
584             my $t0 = Benchmark->new;
585            
586             while ($pq->size()) {
587             $cycle++;
588             #print '.' if $VERBOSE and ($cycle % 1000 == 0);
589            
590             my $visitNode = $pq->get();
591            
592             foreach my $adjacentNode (keys %{$self->{graph}{$visitNode}{edges}}) {
593             next if !defined($pq->weight($adjacentNode));
594            
595             my $thisWeight = $solution{$visitNode}{weight} + $self->{graph}{$visitNode}{edges}{$adjacentNode}{weight};
596             if ($thisWeight < $solution{$adjacentNode}{weight}) {
597             $solution{$adjacentNode}{weight} = $thisWeight;
598             $solution{$adjacentNode}{prevnode} = $visitNode;
599             $pq->add($adjacentNode, $thisWeight);
600             }
601             }
602             }
603             if ($VERBOSE) {
604             my $t1 = Benchmark->new;
605             #if ($cycle >= 1000) {
606             # print "\n";
607             #}
608             my $td = timediff($t1, $t0);
609             print "dijkstra's algorithm took: ",timestr($td),"\n";
610             }
611            
612             my $farthestWeight = 0;
613             foreach my $node (sort keys %solution) {
614             if ($solution{$node}{weight} < $PINF and $solution{$node}{weight} > $farthestWeight) {
615             $farthestWeight = $solution{$node}{weight};
616             #$farthestnode = $node;
617             }
618             }
619            
620             my $solutioncnt = 0;
621             %{$solutionHref} = (
622             desc => 'farthest',
623             originID => $originID,
624             weight => $farthestWeight,
625             );
626            
627             foreach my $farthestnode (sort keys %solution) {
628             if ($solution{$farthestnode}{weight} == $farthestWeight) {
629            
630             $solutioncnt++;
631            
632             print "\nfarthestNode: (solution $solutioncnt) farthest node from origin $originID is $farthestnode at weight (cost) $farthestWeight\n" if $VERBOSE;
633            
634             my $fromNode = $solution{$farthestnode}{prevnode};
635             my @path = ( $farthestnode, $fromNode );
636            
637             my %loopCheck = ();
638             while ($solution{$fromNode}{prevnode} ne $EMPTY_STRING) {
639             $fromNode = $solution{$fromNode}{prevnode};
640             if (exists($loopCheck{$fromNode})) {
641             print "farthestNode: path loop at $fromNode\n";
642             print 'farthestNode: path = ', join(',',@path), "\n";
643             die 'farthestNode internal error: destination to origin path logic error';
644             }
645             $loopCheck{$fromNode} = 1;
646             push(@path,$fromNode);
647             }
648            
649             @path = reverse(@path);
650            
651             my $nexttolast = $#path - 1;
652            
653             $solutionHref->{path}{$solutioncnt}{destinationID} = $farthestnode;
654             $solutionHref->{path}{$solutioncnt}{edges} = [];
655            
656             foreach my $i (0 .. $nexttolast) {
657            
658             push(@{$solutionHref->{path}{$solutioncnt}{edges}}, {sourceID => $path[$i], targetID => $path[$i+1], weight => $self->edge( { sourceID=>$path[$i], targetID=>$path[$i+1] } )->{weight} } );
659            
660             }
661             }
662             }
663            
664             $solutionHref->{count} = $solutioncnt;
665            
666             return($farthestWeight);
667             }
668            
669             sub shortestPath { ## no critic (ProhibitExcessComplexity)
670             my ($self, $solutionHref) = @_;
671            
672             if (!exists($solutionHref->{originID})) {
673             croak "farthestNode: originID attribute not set in solution hash reference parameter";
674             }
675             my $originID = $solutionHref->{originID};
676            
677             if (!exists($solutionHref->{destinationID})) {
678             croak "farthestNode: destinationID attribute not set in solution hash reference parameter";
679             }
680             my $destinationID = $solutionHref->{destinationID};
681            
682             if (!exists($self->{graph}{$originID})) {
683             carp "shortestPath: originID not found: $originID";
684             return 0;
685             }
686            
687             if (!exists($self->{graph}{$originID}{edges})) {
688             carp "shortestPath: origin node $originID has no edges";
689             return 0;
690             }
691             if (!exists($self->{graph}{$destinationID})) {
692             carp "shortestPath: destinationID not found: $destinationID";
693             return 0;
694             }
695             if (!exists($self->{graph}{$destinationID}{edges})) {
696             carp "shortestPath: destination node $destinationID has no edges";
697             return 0;
698             }
699            
700             my $pq = Array::Heap::ModifiablePriorityQueue->new();
701            
702             my %solution = (); #initialize the solution hash
703             my %unvisited = ();
704             foreach my $node (keys %{$self->{graph}}) {
705             if (exists($self->{graph}{$node}{edges})) { #nodes without edges cannot be part of the solution
706             $solution{$node}{weight} = $PINF;
707             $solution{$node}{prevnode} = $EMPTY_STRING;
708             $pq->add($node, $PINF);
709             }
710             }
711            
712             $solution{$originID}{weight} = 0;
713             $pq->add($originID,0); #modify weight of origin node
714            
715             my $cycle = 0;
716             my $t0 = Benchmark->new;
717            
718             my $foundSolution = 0;
719             while ($pq->size()) {
720             $cycle++;
721             #print '.' if $VERBOSE and ($cycle % 1000 == 0);
722            
723             my $visitNode = $pq->get();
724            
725             if ($visitNode eq $destinationID) {
726             $foundSolution = 1 if $solution{$visitNode}{weight} < $PINF;
727             last;
728             }
729            
730             foreach my $adjacentNode (keys %{$self->{graph}{$visitNode}{edges}}) {
731             next if !defined($pq->weight($adjacentNode));
732            
733             my $thisWeight = $solution{$visitNode}{weight} + $self->{graph}{$visitNode}{edges}{$adjacentNode}{weight};
734             if ($thisWeight < $solution{$adjacentNode}{weight}) {
735             $solution{$adjacentNode}{weight} = $thisWeight;
736             $solution{$adjacentNode}{prevnode} = $visitNode;
737             $pq->add($adjacentNode, $thisWeight);
738             }
739             }
740             }
741             if ($VERBOSE) {
742             my $t1 = Benchmark->new;
743             #if ($cycle >= 1000) {
744             # print "\n";
745             #}
746             my $td = timediff($t1, $t0);
747             print "dijkstra's algorithm took: ",timestr($td),"\n";
748             }
749            
750             my $pathWeight = 0;
751             if ($foundSolution) {
752             $pathWeight = $solution{$destinationID}{weight};
753             print "shortestPath: pathWeight (cost) = $pathWeight\n" if $VERBOSE;
754            
755             my $solutioncnt = 0;
756             %{$solutionHref} = (
757             desc => 'path',
758             originID => $originID,
759             destinationID => $destinationID,
760             weight => $pathWeight,
761             );
762            
763             my $fromNode = $solution{$destinationID}{prevnode};
764             my @path = ( $destinationID, $fromNode );
765            
766             my %loopCheck = ();
767             while ($solution{$fromNode}{prevnode} ne $EMPTY_STRING) {
768             $fromNode = $solution{$fromNode}{prevnode};
769             if (exists($loopCheck{$fromNode})) {
770             print "shortestPath: path loop at $fromNode\n";
771             print "shortestPath: path = ", join(',',@path), "\n";
772             die "shortestPath internal error: destination to origin path logic error";
773             }
774             $loopCheck{$fromNode} = 1;
775             push(@path,$fromNode);
776             }
777            
778             @path = reverse(@path);
779            
780             my $nexttolast = $#path - 1;
781             foreach my $i (0 .. $nexttolast) {
782             push(@{$solutionHref->{edges}}, {sourceID => $path[$i], targetID => $path[$i+1], weight => $self->edge( { sourceID=>$path[$i], targetID=>$path[$i+1] } )->{weight} } );
783             }
784             }
785             return($pathWeight);
786             }
787            
788            
789            
790             #############################################################################
791             #input / output file methods #
792             #############################################################################
793            
794             { #CSV file format methods
795            
796             use Text::CSV_XS;
797            
798             sub getRowHref {
799             my $row = shift;
800             my $attribStr = $EMPTY_STRING;
801             foreach my $i (1 .. $#$row) {
802             $attribStr .= ', ' if $attribStr;
803             $attribStr .= $row->[$i];
804             }
805             return Graph::Dijkstra->hashifyAttribs( "($attribStr)" );
806             }
807            
808             sub inputGraphfromCSV {
809             my ($self, $filename) = @_;
810            
811             if (!ref($self)) {
812             $self = Graph::Dijkstra->new();
813             }
814            
815             my $nodecount = 0;
816             my $edgecount = 0;
817            
818             open(my $infile, '<:encoding(UTF-8)', $filename) or croak "could not open '$filename'";
819            
820             print "inputGraphfromCSV: opened '$filename' for input\n" if $VERBOSE;
821            
822             my $csv = Text::CSV_XS->new ({ binary => 1, auto_diag => 1 });
823             while (my $row = $csv->getline ($infile)) {
824             if (lc($row->[0]) eq 'graph') {
825             $self->graph( getRowHref( $row ) ) if $#$row;
826             }
827             elsif (lc($row->[0]) eq 'node') {
828             $self->node( getRowHref( $row ) );
829             $nodecount++;
830             }
831             elsif (lc($row->[0]) eq 'edge') {
832             $self->edge( getRowHref( $row ) );
833             $edgecount++;
834             }
835             }
836             close($infile);
837            
838             carp "inputGraphfromCSV: no nodes read from '$filename'" if !$nodecount;
839             carp "inputGraphfromCSV: no edges read from '$filename'" if !$edgecount;
840            
841             print "inputGraphfromCSV: found $nodecount nodes and $edgecount edges\n" if $VERBOSE;
842             return $self;
843             }
844            
845             sub makeRow {
846             my $href = shift;
847             my @rowdata = ();
848             foreach my $attrib (sort keys %$href) {
849             next if $href->{$attrib} eq $EMPTY_STRING;
850             my $printVal = (looks_like_number($href->{$attrib})) ? $href->{$attrib} : "'$href->{$attrib}'";
851             push(@rowdata, "$attrib=>$printVal");
852             }
853             return @rowdata;
854             }
855            
856             sub outputGraphtoCSV {
857             my ($self, $filename) = @_;
858            
859             open(my $outfile, '>:encoding(UTF-8)', $filename) or croak "could not open '$filename'";
860            
861             print "outputGraphtoCSV: opened '$filename' for output\n" if $VERBOSE;
862            
863             my $csv = Text::CSV_XS->new ({ binary => 1, auto_diag => 1 });
864            
865             my $nodecount = 0;
866             my $edgecount = 0;
867             my $graphHref = $self->graph();
868            
869             $csv->say( $outfile, ['graph', makeRow( $self->graph() ) ] );
870            
871             my $graphDirected = $self->{edgedefault};
872            
873             my %edges = ();
874             foreach my $nodeID (keys %{$self->{graph}}) {
875            
876             $csv->say($outfile, ['node', makeRow( $self->node($nodeID) ) ]);
877            
878             $nodecount++;
879             if (exists($self->{graph}{$nodeID}{edges})) {
880             foreach my $targetID (keys %{$self->{graph}{$nodeID}{edges}}) {
881             my $edgeDirected = $self->{graph}{$nodeID}{edges}{$targetID}{directed};
882             if ( ($edgeDirected eq 'undirected' and !exists($edges{$targetID}{$nodeID})) or $edgeDirected eq 'directed') {
883             $edges{$nodeID}{$targetID}{weight} = $self->{graph}{$nodeID}{edges}{$targetID}{weight};
884             }
885             }
886             }
887             }
888             foreach my $sourceID (keys %edges) {
889             foreach my $targetID (keys %{$edges{$sourceID}}) {
890            
891             $csv->say($outfile, ['edge', makeRow( $self->edge( {sourceID=>$sourceID, targetID=>$targetID} ) ) ]);
892            
893             $edgecount++;
894             }
895             }
896             close($outfile);
897             print "outputGraphtoCSV: wrote $nodecount nodes and $edgecount edges to '$filename'\n" if $VERBOSE;
898            
899             return $self;
900             }
901            
902             sub outputAPSPmatrixtoCSV {
903             my ($either, $solutionMatrix, $filename, $labelSort) = @_;
904            
905             $labelSort = '' if !defined($labelSort);
906            
907             open(my $outfile, '>:encoding(UTF-8)', $filename) or croak "could not open '$filename'";
908            
909             print "outputAPSPmatrixtoCSV: opened '$filename' for output\n" if $VERBOSE;
910            
911             my $csv = Text::CSV_XS->new ({ binary => 1, auto_diag => 1 });
912            
913             my @nodeList = (lc($labelSort) eq 'numeric') ? (sort {$a <=> $b} keys %{$solutionMatrix->{row}}) : (sort keys %{$solutionMatrix->{row}});
914            
915             $csv->say($outfile, ['From/To', @nodeList ]);
916             my $rowcount = 1;
917            
918             foreach my $nodeID (@nodeList) {
919             my @row = ();
920             foreach my $destinationID (@nodeList) {
921             push(@row, $solutionMatrix->{row}{$nodeID}{$destinationID});
922             }
923             $csv->say($outfile, [$nodeID, @row]);
924             $rowcount++;
925             }
926             close($outfile);
927             print "outputAPSPmatrixtoCSV: wrote $rowcount rows to '$filename'\n" if $VERBOSE;
928             return $either;
929            
930             }
931            
932             } #CSV file format I/O methods
933            
934             #############################################################################
935             #JSON Graph Specification file format methods #
936             #############################################################################
937             {
938            
939             use JSON;
940            
941             sub inputGraphfromJSON {
942             my ($self, $filename, $options) = @_;
943            
944             if (!ref($self)) {
945             $self = Graph::Dijkstra->new();
946             }
947            
948             my $json_text = $EMPTY_STRING;
949             open(my $infile, '<:encoding(UTF-8)', $filename) or croak "could not open '$filename'";
950            
951             print "inputGraphfromJSON: opened '$filename' for input\n" if $VERBOSE;
952            
953             while (my $line = <$infile>) {
954             $json_text .= $line;
955             }
956             close($infile);
957            
958             my $graphHref = from_json( $json_text, {utf8 => 1} ) or croak "inputGraphfromJSON: invalid json text";
959            
960             if (ref($graphHref) ne 'HASH') {
961             croak "inputGraphfromJSON: invalid JSON text";
962             }
963            
964             if (exists($graphHref->{graphs})) {
965             croak "inputGraphfromJSON: JSON \"multi graph\" type not supported";
966             }
967             if (!exists($graphHref->{graph}{edges})) {
968             croak "inputGraphfromJSON: not a JSON graph specification or graph has no edges";
969             }
970             my $edgeWeightKey = (defined($options) and ref($options) eq 'HASH' and exists($options->{edgeWeightKey})) ? $options->{edgeWeightKey} : 'value';
971            
972             my $graphDirected = 'undirected';
973             if (exists($graphHref->{graph}{directed}) and $graphHref->{graph}{directed} ) {
974             $graphDirected = 'directed';
975             }
976             print "inputGraphfromJSON: graph edge default is '$graphDirected'.\n" if $VERBOSE;
977            
978             $self->graph( {label=>$graphHref->{graph}{label} } ) if exists($graphHref->{graph}{label});
979             $self->graph( {creator=>$graphHref->{graph}{metadata}{creator} } ) if exists($graphHref->{graph}{metadata}{creator});
980            
981             my $nodecount = 0;
982             my $edgecount = 0;
983             my $dupedgecount = 0;
984            
985             foreach my $nodeHref (@{$graphHref->{graph}{nodes}}) {
986             $nodecount++;
987             $self->node( {id=>$nodeHref->{id}, label=>$nodeHref->{label} } );
988             }
989             foreach my $edgeHref (@{$graphHref->{graph}{edges}}) {
990            
991             my $edgeDirected = $graphDirected;
992             if (exists($edgeHref->{directed})) {
993             $edgeDirected = ($edgeHref->{directed}) ? 'directed' : 'undirected';
994             }
995             my $edgeLabel = $edgeHref->{label} || $EMPTY_STRING;
996             my $edgeID = $edgeHref->{metadata}{id} || $EMPTY_STRING;
997             my $weight = $edgeHref->{metadata}{$edgeWeightKey} || 1;
998            
999             $edgecount++;
1000             $dupedgecount++ if $self->edgeExists( { sourceID=>$edgeHref->{source}, targetID=>$edgeHref->{target} } );
1001             $self->edge( { sourceID=>$edgeHref->{source}, targetID=>$edgeHref->{target}, weight=>$weight, label=>$edgeLabel, directed=>$edgeDirected, id=>$edgeID } );
1002             }
1003            
1004             carp "inputGraphfromJSON: no nodes read from '$filename'" if !$nodecount;
1005             carp "inputGraphfromJSON: no edges read from '$filename'" if !$edgecount;
1006            
1007             print "inputGraphfromJSON: found $nodecount nodes and $edgecount edges\n" if $VERBOSE;
1008             print "inputGraphfromJSON: found $dupedgecount duplicate edges\n" if $dupedgecount and $VERBOSE;
1009            
1010             return $self;
1011             }
1012            
1013            
1014             sub outputGraphtoJSON {
1015             my ($self, $filename, $options) = @_;
1016            
1017             my $nodecount = 0;
1018             my $edgecount = 0;
1019            
1020             my %graph = ();
1021             my $graphDirected = $self->{edgedefault};
1022            
1023             $graph{graph}{directed} = ($graphDirected eq 'directed') ? JSON::true : JSON::false;
1024             @{$graph{graph}{nodes}} = ();
1025             @{$graph{graph}{edges}} = ();
1026            
1027             $graph{graph}{metadata}{comment} = 'generated by Graph::Dijkstra on ' . localtime;
1028             $graph{graph}{label} = $self->{label} if $self->{label};
1029             $graph{graph}{metadata}{creator} = $self->{creator} if $self->{creator};
1030            
1031             my $edgeWeightKey = (defined($options) and ref($options) eq 'HASH' and exists($options->{edgeWeightKey})) ? $options->{edgeWeightKey} : 'value';
1032            
1033             my %edges = ();
1034             foreach my $nodeID (keys %{$self->{graph}}) {
1035            
1036             push(@{$graph{graph}{nodes}}, { id => $nodeID, label => $self->{graph}{$nodeID}{label} } );
1037            
1038             $nodecount++;
1039             if (exists($self->{graph}{$nodeID}{edges})) {
1040             foreach my $targetID (keys %{$self->{graph}{$nodeID}{edges}}) {
1041            
1042             my $edgeDirected = $self->{graph}{$nodeID}{edges}{$targetID}{directed};
1043             if ( ($edgeDirected eq 'undirected' and !exists($edges{$targetID}{$nodeID})) or $edgeDirected eq 'directed') {
1044            
1045             $edges{$nodeID}{$targetID} = 1;
1046             my %edgeData = ( source => $nodeID, target => $targetID, metadata => {$edgeWeightKey => $self->{graph}{$nodeID}{edges}{$targetID}{weight} } );
1047            
1048             $edgeData{label} = $self->{graph}{$nodeID}{edges}{$targetID}{label} if $self->{graph}{$nodeID}{edges}{$targetID}{label};
1049            
1050             if ($edgeDirected ne $graphDirected) {
1051             $edgeData{directed} = ($edgeDirected eq 'directed') ? JSON::true : JSON::false;
1052             }
1053             if ($self->{graph}{$nodeID}{edges}{$targetID}{id} ne $EMPTY_STRING) {
1054             $edgeData{metadata}{id} = $self->{graph}{$nodeID}{edges}{$targetID}{id};
1055             }
1056            
1057             push( @{$graph{graph}{edges}}, \%edgeData );
1058             $edgecount++;
1059             }
1060             }
1061             }
1062             }
1063            
1064             my $json_text = to_json(\%graph, {utf8 => 1, pretty => 1});
1065            
1066             open(my $outfile, '>:encoding(UTF-8)', $filename) or croak "could not open '$filename'";
1067            
1068             print "outputGraphtoJSON: opened '$filename' for output\n" if $VERBOSE;
1069             print {$outfile} $json_text;
1070             close($outfile);
1071             print "outputGraphtoJSON: wrote $nodecount nodes and $edgecount edges to '$filename'\n" if $VERBOSE;
1072            
1073             return $self;
1074             }
1075            
1076             } #JSON Graph Specification file format methods
1077            
1078             #############################################################################
1079             #GML file format methods #
1080             #############################################################################
1081             {
1082            
1083             use Regexp::Common;
1084            
1085             sub inputGraphfromGML { ## no critic (ProhibitExcessComplexity)
1086             my ($self, $filename) = @_;
1087            
1088             if (!ref($self)) {
1089             $self = Graph::Dijkstra->new();
1090             }
1091             my $buffer = $EMPTY_STRING;
1092             my $linecount = 0;
1093             open(my $infile, '<:encoding(UTF-8)', $filename) or croak "could not open '$filename'";
1094            
1095             print "inputGraphfromGML: opened '$filename' for input\n" if $VERBOSE;
1096            
1097             while (my $line = <$infile>) {
1098             next if substr($line,0,1) eq '#';
1099             $buffer .= $line;
1100             $linecount++;
1101             }
1102             close($infile);
1103             print "inputGraphfromGML: read $linecount lines\n" if $VERBOSE;
1104            
1105             if ($buffer !~ /graph\s+\[.+?(?:node|edge)\s+\[/ixs) {
1106             croak "file does not appear to be GML format";
1107             }
1108            
1109             my $graphDirected = 'undirected';
1110            
1111             if ($buffer =~ /graph\s+\[\s+directed\s+(\d)/ixs) {
1112             $graphDirected = ($1) ? 'directed' : 'undirected';
1113             }
1114            
1115             print "inputGraphfromGML: graph edge default = '$graphDirected'\n" if $VERBOSE;
1116             $self->graph( { edgedefault=>$graphDirected } );
1117            
1118             if ($buffer =~ /^\s*creator\s+\"([^\"]+)\"/i) {
1119             my $creator = $1;
1120             $self->graph( {creator=>$creator} );
1121             print "inputGraphfromGML: graph attribute creator set: $creator\n" if $VERBOSE;
1122            
1123             }
1124            
1125             my $has_graphics_elements = ($buffer =~ /graphics\s+\[/) ? 1 : 0;
1126             print "GML file contain graphics elements\n" if ($VERBOSE and $has_graphics_elements);
1127            
1128             my $balancedRE = $RE{balanced}{-parens=>'[]'};
1129            
1130            
1131             my $nodecount = 0;
1132             my $edgecount = 0;
1133             my $dupedgecount = 0;
1134            
1135             while ($buffer =~ /(node|edge)\s+$balancedRE/gixso) {
1136             my $type = lc($1);
1137             my $attribs = $2;
1138             #my $bufferPos = $-[0];
1139            
1140             $attribs = substr($attribs, 1, -1);
1141            
1142             $attribs =~ s/graphics\s+$balancedRE//xio if $has_graphics_elements and $type eq 'node';
1143            
1144             my %keyvals = ();
1145             while ($attribs =~/(id|label|source|target|value)\s+(?|([0-9\.]+)|\"([^\"]+)\")/gixs) {
1146             my $attrib = lc($1);
1147             my $attribValue = $2;
1148             if ($type eq 'edge' and $attrib eq 'value' and !looks_like_number($attribValue)) {
1149             carp "non-numeric edge value '$attribValue'. Skipped.";
1150             next;
1151             }
1152             $keyvals{$attrib} = $attribValue;
1153             }
1154            
1155             if ($type eq 'node') {
1156             $nodecount++;
1157             if (exists($keyvals{id})) {
1158             $self->{graph}{$keyvals{id}}{label} = $keyvals{label} || $EMPTY_STRING;
1159             }
1160             else {
1161             croak "inputGraphfromGML: node: missing id problem -- matched attribs: '$attribs'";
1162             }
1163             }
1164             else {
1165             $edgecount++;
1166             my $edgeLabel = $keyvals{label} || $EMPTY_STRING;
1167             if (exists($keyvals{source}) and exists($keyvals{target}) and exists($keyvals{value}) and $keyvals{value} > 0) {
1168             $dupedgecount++ if $self->edgeExists( { sourceID=>$keyvals{source}, targetID=>$keyvals{target} } );
1169             $self->edge( { sourceID=>$keyvals{source}, targetID=>$keyvals{target}, weight=>$keyvals{value}, label=>$edgeLabel, directed=>$graphDirected } );
1170             }
1171             else {
1172             croak "inputGraphfromGML: edge: missing source, target, value, or value <= 0 problem -- matched attribs '$attribs'";
1173             }
1174             }
1175             }
1176            
1177             carp "inputGraphfromGML: no nodes read from '$filename'" if !$nodecount;
1178             carp "inputGraphfromGML: no edges read from '$filename'" if !$edgecount;
1179            
1180             print "inputGraphfromGML: found $nodecount nodes and $edgecount edges\n" if $VERBOSE;
1181             print "inputGraphfromGML: found $dupedgecount duplicate edges\n" if $dupedgecount and $VERBOSE;
1182            
1183             return $self;
1184             }
1185            
1186            
1187             sub outputGraphtoGML {
1188             my ($self, $filename) = @_;
1189            
1190             open(my $outfile, '>:encoding(UTF-8)', $filename) or croak "could not open '$filename' for output";
1191            
1192             print "outputGraphtoGML: opened '$filename' for output\n" if $VERBOSE;
1193            
1194             {
1195             my $now_string = localtime;
1196             print {$outfile} "# Generated by Graph::Dijkstra on $now_string\n";
1197             }
1198            
1199             print {$outfile} "Creator \"$self->{creator}\"\n" if $self->{creator};
1200             my $graphDirected = ($self->{edgedefault} eq 'directed') ? 1 : 0;
1201             print {$outfile} "Graph [\n\tDirected ", (($self->{edgedefault} eq 'directed') ? 1 : 0), "\n";
1202             $graphDirected = $self->{edgedefault};
1203            
1204             my $nodecount = 0;
1205             my $edgecount = 0;
1206            
1207             my %edges = ();
1208             foreach my $nodeID (keys %{$self->{graph}}) {
1209             my $nodeIDprint = (looks_like_number($nodeID)) ? $nodeID : '"' . encode_entities($nodeID) . '"';
1210             my $nodeLabel = encode_entities($self->{graph}{$nodeID}{label});
1211             print {$outfile} "\tnode [\n\t\tid $nodeIDprint\n\t\tlabel \"$nodeLabel\"\n\t]\n";
1212             $nodecount++;
1213             if (exists($self->{graph}{$nodeID}{edges})) {
1214             foreach my $targetID (keys %{$self->{graph}{$nodeID}{edges}}) {
1215             croak "outputGraphtoGML: internal graph includes both directed and undirected edges. Not supported by GML format." if $self->{graph}{$nodeID}{edges}{$targetID}{directed} ne $graphDirected;
1216             if ( ($graphDirected eq 'undirected' and !exists($edges{$targetID}{$nodeID})) or $graphDirected eq 'directed') {
1217             $edges{$nodeID}{$targetID}{weight} = $self->{graph}{$nodeID}{edges}{$targetID}{weight};
1218             $edges{$nodeID}{$targetID}{label} = $self->{graph}{$nodeID}{edges}{$targetID}{label};
1219             }
1220             }
1221             }
1222             }
1223             foreach my $sourceID (keys %edges) {
1224             foreach my $targetID (keys %{$edges{$sourceID}}) {
1225             my $sourceIDprint = (looks_like_number($sourceID)) ? $sourceID : '"' . encode_entities($sourceID) . '"';
1226             my $targetIDprint = (looks_like_number($targetID)) ? $targetID : '"' . encode_entities($targetID) . '"';
1227             my $edgeLabelprint = ($edges{$sourceID}{$targetID}{label}) ? "\t\tlabel \"" . encode_entities($edges{$sourceID}{$targetID}{label}) . "\"\n" : $EMPTY_STRING;
1228             print {$outfile} "\tedge [\n\t\tsource $sourceIDprint\n\t\ttarget $targetIDprint\n$edgeLabelprint\t\tvalue $edges{$sourceID}{$targetID}{weight}\n\t]\n";
1229             $edgecount++;
1230             }
1231             }
1232             print {$outfile} "]\n";
1233             close($outfile);
1234             print "outputGraphtoGML: wrote $nodecount nodes and $edgecount edges to '$filename'\n" if $VERBOSE;
1235            
1236             return $self;
1237             }
1238            
1239             } #GML file format methods
1240            
1241             #############################################################################
1242             #XML file format methods: GraphML and GEXF #
1243             #############################################################################
1244             {
1245            
1246             use XML::LibXML;
1247            
1248            
1249             sub inputGraphfromGraphML { ## no critic (ProhibitExcessComplexity)
1250             my ($self, $filename, $options) = @_;
1251            
1252             if (!ref($self)) {
1253             $self = Graph::Dijkstra->new();
1254             }
1255            
1256             my $dom = XML::LibXML->load_xml(location => $filename);
1257            
1258             print "inputGraphfromGraphML: input '$filename'\n" if $VERBOSE;
1259            
1260             my $topNode = $dom->nonBlankChildNodes()->[0];
1261            
1262             croak "inputGraphfromGraphML: not a GraphML format XML file" if lc($topNode->nodeName()) ne 'graphml';
1263            
1264             my $nsURI = $topNode->getAttribute('xmlns') || '';
1265            
1266             croak "inputGraphfromGraphML: not a GraphML format XML file" if (lc($nsURI) ne 'http://graphml.graphdrawing.org/xmlns');
1267            
1268             my $xpc = XML::LibXML::XPathContext->new($dom);
1269             $xpc->registerNs('gml', $nsURI);
1270            
1271             my $labelKey = $options->{nodeKeyLabelID} || $EMPTY_STRING;
1272             my $weightKey = $options->{edgeKeyValueID} || $EMPTY_STRING;
1273             my $edgeLabelKey = 'label';
1274            
1275             my $defaultWeight = 1;
1276            
1277             my $nodecount = 0;
1278             my $edgecount = 0;
1279             my $dupedgecount = 0;
1280             my $graphDirected = $EMPTY_STRING;
1281            
1282             if (my $graphNode = $xpc->findnodes('/gml:graphml/gml:graph')->[0] ) {
1283             $graphDirected = lc($graphNode->getAttribute('edgedefault'));
1284             print "inputGraphfromGraphML: graph edge default is '$graphDirected'.\n" if $VERBOSE;
1285             }
1286             else {
1287             croak "inputGraphfromGraphML: GraphML file has no element";
1288             }
1289            
1290             if (my $graphNode = $xpc->findnodes('/gml:graphml/gml:graph[2]')->[0] ) {
1291             croak "inputGraphfromGraphML: file contains more than one graph. Not supported.";
1292             }
1293            
1294             if (my $graphNode = $xpc->findnodes('/gml:graphml/gml:graph/gml:node/gml:graph')->[0] ) {
1295             croak "inputGraphfromGraphML: file contains one or more embedded graphs. Not supported.";
1296             }
1297            
1298             if ($weightKey) {
1299             if (my $keyWeightNode = $xpc->findnodes("/gml:graphml/gml:key[\@for=\"edge\" and \@id=\"$weightKey\"]")->[0]) {
1300             print "inputGraphfromGraphML: found edgeKeyWeightID '$weightKey' in GraphML key elements list\n" if $VERBOSE;
1301             if (my $defaultNode = $xpc->findnodes('.//gml:default[1]',$keyWeightNode)->[0]) {
1302             $defaultWeight = $defaultNode->textContent();
1303             }
1304             }
1305             else {
1306             carp "inputGraphfromGraphML: edgeKeyValueID '$weightKey' not found in GraphML key elements list";
1307             $weightKey = $EMPTY_STRING;
1308             }
1309             }
1310            
1311             if (!$weightKey) {
1312             foreach my $keyEdge ($xpc->findnodes('/gml:graphml/gml:key[@for="edge"]') ) {
1313             my $attrName = $keyEdge->getAttribute('attr.name');
1314             if ($IS_GRAPHML_WEIGHT_ATTR{ lc($attrName) } ) {
1315             $weightKey = $keyEdge->getAttribute('id');
1316             print "inputGraphfromGraphML: found key attribute for edge attr.name='$attrName' id='$weightKey'\n" if $VERBOSE;
1317             if (my $defaultNode = $xpc->findnodes('.//gml:default[1]',$keyEdge)->[0]) {
1318             $defaultWeight = $defaultNode->textContent();
1319             }
1320             last;
1321             }
1322             }
1323            
1324             if (!$weightKey) {
1325             croak "inputGraphfromGraphML: graph does not contain key attribute for edge weight/value/cost/distance ''. Not supported.";
1326             }
1327             }
1328            
1329             if ($edgeLabelKey) {
1330             if (my $keyEdgeLabelNode = $xpc->findnodes("/gml:graphml/gml:key[\@for=\"edge\" and \@id=\"$edgeLabelKey\"]")->[0]) {
1331             print "inputGraphfromGraphML: found edgeKeyLabelID '$edgeLabelKey' in GraphML key elements list\n" if $VERBOSE;
1332             }
1333             else {
1334             # carp "inputGraphfromGraphML: edgeKeyLabelID '$edgeLabelKey' not found in GraphML key elements list";
1335             $edgeLabelKey = $EMPTY_STRING;
1336             }
1337             }
1338             my $edgeLabelXPATH = ($edgeLabelKey) ? ".//gml:data[\@key=\"$edgeLabelKey\"]" : $EMPTY_STRING;
1339            
1340             my $labelXPATH = $EMPTY_STRING;
1341            
1342             if ($labelKey) {
1343             if (my $keyNodeLabelNode = $xpc->findnodes("/gml:graphml/gml:key[\@for=\"node\" and \@id=\"$labelKey\"]")->[0]) {
1344             print "inputGraphfromGraphML: found nodeLabelValueID '$labelKey' in GraphML key elements list\n" if $VERBOSE;
1345             }
1346             else {
1347             carp "inputGraphfromGraphML: nodeLabelValueID '$labelKey' not found in GraphML key elements list";
1348             $labelKey = $EMPTY_STRING;
1349             }
1350             }
1351            
1352             if (!$labelKey) {
1353             foreach my $keyNode ($xpc->findnodes('/gml:graphml/gml:key[@for="node" and @attr.type="string"]')) {
1354             my $attrName = $keyNode->getAttribute('attr.name') || $EMPTY_STRING;
1355             if ($IS_GRAPHML_LABEL_ATTR{lc($attrName)}) {
1356             $labelKey = $keyNode->getAttribute('id');
1357             print "inputGraphfromGraphML: found key attribute for node 'label' attr.name='$attrName' id='$labelKey'\n" if $VERBOSE;
1358             last;
1359             }
1360             }
1361             }
1362            
1363             if (!$labelKey) {
1364             carp "inputGraphfromGraphML: key node name / label / description attribute not found in graphml";
1365             }
1366             else {
1367             $labelXPATH = ".//gml:data[\@key=\"$labelKey\"]";
1368             }
1369            
1370             if (my $keyGraphCreator = $xpc->findnodes("/gml:graphml/gml:key[\@for=\"graph\" and \@id=\"creator\"]")->[0]) {
1371             if (my $dataGraphCreator = $xpc->findnodes("/gml:graphml/gml:graph/gml:data[\@key=\"creator\"]")->[0]) {
1372             if (my $creator = $dataGraphCreator->textContent()) {
1373             $self->graph( {creator=>$creator} );
1374             }
1375             }
1376             }
1377             if (my $keyGraphLabel = $xpc->findnodes("/gml:graphml/gml:key[\@for=\"graph\" and \@id=\"graphlabel\"]")->[0]) {
1378             if (my $dataGraphLabel = $xpc->findnodes("/gml:graphml/gml:graph/gml:data[\@key=\"graphlabel\"]")->[0]) {
1379             if (my $label = $dataGraphLabel->textContent()) {
1380             $self->graph( {label=>$label} );
1381             }
1382             }
1383             }
1384            
1385             foreach my $nodeElement ($xpc->findnodes('/gml:graphml/gml:graph/gml:node')) {
1386            
1387             my $node = $nodeElement->nodeName();
1388             my $id = $nodeElement->getAttribute('id');
1389             my $label = $EMPTY_STRING;
1390             if ($labelXPATH and my $dataNameNode = $xpc->findnodes($labelXPATH,$nodeElement)->[0]) {
1391             $label = $dataNameNode->textContent();
1392             }
1393             $self->node( {id=>$id,label=>$label } );
1394             $nodecount++;
1395             }
1396            
1397             my $weightXPATH = ".//gml:data[\@key=\"$weightKey\"]";
1398            
1399             foreach my $edgeElement ($xpc->findnodes('/gml:graphml/gml:graph/gml:edge')) {
1400            
1401             my $edge = $edgeElement->nodeName();
1402             my $source = $edgeElement->getAttribute('source');
1403             my $target = $edgeElement->getAttribute('target');
1404             my $edgeID = ($edgeElement->hasAttribute('id')) ? $edgeElement->getAttribute('id') : $EMPTY_STRING;
1405             my $edgeDirected = ($edgeElement->hasAttribute('directed')) ? $edgeElement->getAttribute('directed') : $graphDirected;
1406             my $edgeLabel = '';
1407             if ($edgeLabelXPATH and my $dataEdgeLabelNode = $xpc->findnodes($edgeLabelXPATH,$edgeElement)->[0]) {
1408             $edgeLabel = $dataEdgeLabelNode->textContent();
1409             }
1410             my $weight = $defaultWeight;
1411             if (my $dataWeightNode = $xpc->findnodes($weightXPATH,$edgeElement)->[0]) {
1412             $weight = $dataWeightNode->textContent();
1413             }
1414             if ($weight) {
1415             $dupedgecount++ if $self->edgeExists( { sourceID=>$source, targetID=>$target } );
1416             $self->edge( { sourceID=>$source, targetID=>$target, weight=>$weight, id=>$edgeID, directed=>$edgeDirected, label=>$edgeLabel } );
1417             $edgecount++;
1418             }
1419             else {
1420             carp "inputGraphfromGraphML: edge $source $target has no weight. Not created."
1421             }
1422            
1423             }
1424            
1425             carp "inputGraphfromGraphML: no nodes read from '$filename'" if !$nodecount;
1426             carp "inputGraphfromGraphML: no edges read from '$filename'" if !$edgecount;
1427            
1428             print "inputGraphfromGraphML: found $nodecount nodes and $edgecount edges\n" if $VERBOSE;
1429             print "inputGraphfromGraphML: found $dupedgecount duplicate edges\n" if $dupedgecount and $VERBOSE;
1430            
1431             return $self;
1432             }
1433            
1434            
1435             sub outputGraphtoGraphML {
1436             my ($self, $filename, $options) = @_;
1437            
1438             my $nsURI = "http://graphml.graphdrawing.org/xmlns";
1439            
1440             my $doc = XML::LibXML::Document->new('1.0','UTF-8');
1441             my $graphML = $doc->createElementNS( $EMPTY_STRING, 'graphml' );
1442             $doc->setDocumentElement( $graphML );
1443            
1444             $graphML->setNamespace( $nsURI , $EMPTY_STRING, 1 );
1445            
1446             {
1447             my $now_string = localtime;
1448             $graphML->appendChild($doc->createComment("Generated by Graph::Dijkstra on $now_string"));
1449             }
1450            
1451             $graphML->setAttribute('xmlns:xsi','http://www.w3.org/2001/XMLSchema-instance');
1452             $graphML->setAttribute('xsi:schemaLocation','http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd');
1453            
1454            
1455            
1456             my $keyEdgeWeightID = $options->{keyEdgeWeightID} || 'weight';
1457             my $keyEdgeWeightAttrName = $options->{keyEdgeWeightAttrName} || 'weight';
1458             my $keyNodeLabelID = $options->{keyNodeLabelID} || 'name';
1459             my $keyNodeLabelAttrName = $options->{keyNodeLabelAttrName} || 'name';
1460             my $keyEdgeLabelID = $options->{keyEdgeLabelID} || 'label';
1461             my $keyEdgeLabelAttrName = $options->{keyEdgeLabelAttrName} || 'label';
1462            
1463             my $keyNode = $graphML->addNewChild( $nsURI, 'key' );
1464            
1465             $keyNode->setAttribute('for','node');
1466             $keyNode->setAttribute('id', $keyNodeLabelID );
1467             $keyNode->setAttribute('attr.name', $keyNodeLabelAttrName );
1468             $keyNode->setAttribute('attr.type', 'string' );
1469            
1470             my $keyEdge = $graphML->addNewChild( $nsURI, 'key' );
1471             $keyEdge->setAttribute('for','edge');
1472             $keyEdge->setAttribute('id', $keyEdgeWeightID );
1473             $keyEdge->setAttribute('attr.name', $keyEdgeWeightAttrName );
1474             $keyEdge->setAttribute('attr.type', 'double' );
1475            
1476             $keyEdge = $graphML->addNewChild( $nsURI, 'key' );
1477             $keyEdge->setAttribute('for','edge');
1478             $keyEdge->setAttribute('id', $keyEdgeLabelID );
1479             $keyEdge->setAttribute('attr.name', $keyEdgeLabelAttrName );
1480             $keyEdge->setAttribute('attr.type', 'string' );
1481            
1482             if ($self->{creator}) {
1483             my $keyGraph = $graphML->addNewChild( $nsURI, 'key' );
1484             $keyGraph->setAttribute('for','graph');
1485             $keyGraph->setAttribute('id','creator');
1486             $keyGraph->setAttribute('attr.name','creator');
1487             $keyGraph->setAttribute('attr.type','string');
1488             }
1489             if ($self->{label}) {
1490             my $keyGraph = $graphML->addNewChild( $nsURI, 'key' );
1491             $keyGraph->setAttribute('for','graph');
1492             $keyGraph->setAttribute('id','graphlabel');
1493             $keyGraph->setAttribute('attr.name','label');
1494             $keyGraph->setAttribute('attr.type','string');
1495             }
1496            
1497             my $graph = $graphML->addNewChild( $nsURI, 'graph' );
1498             $graph->setAttribute('id','G');
1499             $graph->setAttribute('edgedefault', $self->{edgedefault} );
1500             if ($self->{creator}) {
1501             my $dataNode = $graph->addNewChild( $nsURI, 'data');
1502             $dataNode->setAttribute('key', 'creator');
1503             $dataNode->appendTextNode( $self->{creator} );
1504             }
1505             if ($self->{label}) {
1506             my $dataNode = $graph->addNewChild( $nsURI, 'data');
1507             $dataNode->setAttribute('key', 'label');
1508             $dataNode->appendTextNode( $self->{label} );
1509             }
1510            
1511             my $nodecount = 0;
1512             my $edgecount = 0;
1513            
1514             my %edges = ();
1515             foreach my $nodeID (keys %{$self->{graph}}) {
1516            
1517             my $nodeNode = $graph->addNewChild( $nsURI, 'node' );
1518             $nodeNode->setAttribute('id', $nodeID);
1519             my $dataNode = $nodeNode->addNewChild( $nsURI, 'data');
1520             $dataNode->setAttribute('key', $keyNodeLabelID);
1521             $dataNode->appendTextNode( $self->{graph}{$nodeID}{label} );
1522            
1523             $nodecount++;
1524             if (exists($self->{graph}{$nodeID}{edges})) {
1525             foreach my $targetID (keys %{$self->{graph}{$nodeID}{edges}}) {
1526             my $directed = $self->{graph}{$nodeID}{edges}{$targetID}{directed};
1527             if ( ($directed eq 'undirected' and !exists($edges{$targetID}{$nodeID})) or $directed eq 'directed') {
1528             $edges{$nodeID}{$targetID}{weight} = $self->{graph}{$nodeID}{edges}{$targetID}{weight};
1529             $edges{$nodeID}{$targetID}{id} = $self->{graph}{$nodeID}{edges}{$targetID}{id};
1530             $edges{$nodeID}{$targetID}{directed} = $directed;
1531             $edges{$nodeID}{$targetID}{label} = $self->{graph}{$nodeID}{edges}{$targetID}{label};
1532             }
1533             }
1534             }
1535             }
1536             foreach my $sourceID (keys %edges) {
1537             foreach my $targetID (keys %{$edges{$sourceID}}) {
1538            
1539             $edgecount++;
1540             my $edgeNode = $graph->addNewChild( $nsURI, 'edge');
1541             $edgeNode->setAttribute('id', ($edges{$sourceID}{$targetID}{id} ne $EMPTY_STRING) ? $edges{$sourceID}{$targetID}{id} : $edgecount);
1542             $edgeNode->setAttribute('source', $sourceID );
1543             $edgeNode->setAttribute('target', $targetID );
1544             $edgeNode->setAttribute('directed', $edges{$sourceID}{$targetID}{directed} ) if $edges{$sourceID}{$targetID}{directed} ne $self->{edgedefault};
1545             my $dataNode = $edgeNode->addNewChild( $nsURI, 'data');
1546             $dataNode->setAttribute('key', $keyEdgeWeightID );
1547             $dataNode->appendTextNode( $edges{$sourceID}{$targetID}{weight} );
1548            
1549             if ( $edges{$sourceID}{$targetID}{label} ) {
1550             $dataNode = $edgeNode->addNewChild( $nsURI, 'data');
1551             $dataNode->setAttribute('key', $keyEdgeLabelID );
1552             $dataNode->appendTextNode( $edges{$sourceID}{$targetID}{label} );
1553             }
1554             }
1555             }
1556            
1557             my $state = $doc->toFile($filename,2);
1558             croak "could not output internal grap to '$filename'" if !$state;
1559            
1560             print "outputGraphtoGraphML: wrote $nodecount nodes and $edgecount edges to '$filename'\n" if $VERBOSE;
1561             return $self;
1562             }
1563            
1564            
1565             sub inputGraphfromGEXF { ## no critic (ProhibitExcessComplexity)
1566             my ($self, $filename) = @_;
1567            
1568             if (!ref($self)) {
1569             $self = Graph::Dijkstra->new();
1570             }
1571            
1572             my $dom = XML::LibXML->load_xml(location => $filename);
1573            
1574             print "inputGraphfromGEXF: input '$filename'\n" if $VERBOSE;
1575            
1576             my $topNode = $dom->nonBlankChildNodes()->[0];
1577            
1578             croak "inputGraphfromGEXF: not a GEXF format XML file" if lc($topNode->nodeName()) ne 'gexf';
1579            
1580             my $nsURI = $topNode->getAttribute('xmlns') || '';
1581            
1582             croak "inputGraphfromGEXF: not a GEXF draft specification 1.1 or 1.2 format XML file" if ( $nsURI !~ /^http:\/\/www.gexf.net\/1\.[1-2]draft$/i );
1583            
1584             my $gexfVersion = $topNode->getAttribute('version') || ''; #don't do anything with the GEXF version string
1585            
1586             my $xpc = XML::LibXML::XPathContext->new($dom);
1587             $xpc->registerNs('gexf', $nsURI);
1588            
1589             my $nodecount = 0;
1590             my $edgecount = 0;
1591             my $dupedgecount = 0;
1592             my $defaultWeight = 1;
1593             my $graphDirected = 'undirected';
1594            
1595             if (my $graphNode = $xpc->findnodes('/gexf:gexf/gexf:graph')->[0] ) {
1596             $graphDirected = ($graphNode->hasAttribute('defaultedgetype')) ? lc($graphNode->getAttribute('defaultedgetype')) : 'undirected';
1597             croak "inputGraphfromGEXF: graph defaultedgetype is 'mutual'. Not supported." if $graphDirected eq 'mutual';
1598             $self->graph( {edgedefault=>$graphDirected} );
1599             print "inputGraphfromGEXF: graph edgedefault is '$graphDirected'.\n" if $VERBOSE;
1600             my $mode = $graphNode->getAttribute('mode') || $EMPTY_STRING;
1601             carp "inputGraphfromGEXF: graph mode is 'dynamic'. Not supported." if lc($mode) eq 'dynamic';
1602             }
1603             else {
1604             croak "inputGraphfromGEXF: GEXF file has no element";
1605             }
1606            
1607             if (my $graphNode = $xpc->findnodes('/gexf:gexf/gexf:graph[2]')->[0] ) {
1608             croak "inputGraphfromGEXF: file contains more than one graph. Not supported.";
1609             }
1610            
1611             if (my $heirarchyNode = $xpc->findnodes('/gexf:gexf/gexf:graph/gexf:nodes/gexf:node/gexf:nodes')->[0] ) {
1612             croak "inputGraphfromGEXF: file contains heirarchical nodes. Not supported.";
1613             }
1614             if (my $parentsNode = $xpc->findnodes('/gexf:gexf/gexf:graph/gexf:nodes/gexf:node/gexf:parents')->[0] ) {
1615             croak "inputGraphfromGEXF: file contains parent nodes. Not supported.";
1616             }
1617            
1618             if (my $metaNode = $xpc->findnodes('/gexf:gexf/gexf:meta/gexf:creator')->[0] ) {
1619             if (my $creator = $metaNode->textContent()) {
1620             $self->graph( { creator=>$creator } );
1621             print "inputGraphfromGEXF: set graph attribute creator: $creator\n" if $VERBOSE;
1622             }
1623             }
1624            
1625             if (my $metaNode = $xpc->findnodes('/gexf:gexf/gexf:meta/gexf:description')->[0] ) {
1626             if (my $label = $metaNode->textContent()) {
1627             $self->graph( { label=>$label } );
1628             print "inputGraphfromGEXF: set graph attribute label (from meta attribute description): $label\n" if $VERBOSE;
1629             }
1630             }
1631            
1632            
1633             foreach my $nodeElement ($xpc->findnodes('/gexf:gexf/gexf:graph/gexf:nodes/gexf:node')) {
1634            
1635             #my $node = $nodeElement->nodeName();
1636             my $id = $nodeElement->getAttribute('id');
1637             my $label = $nodeElement->getAttribute('label') || $EMPTY_STRING;
1638             $self->node( {id=>$id, label=>$label} );
1639             $nodecount++;
1640             }
1641            
1642             foreach my $edgeElement ($xpc->findnodes('/gexf:gexf/gexf:graph/gexf:edges/gexf:edge')) {
1643            
1644             #my $edge = $edgeElement->nodeName();
1645             my $source = $edgeElement->getAttribute('source'); #source, target, and id are required attributes
1646             my $target = $edgeElement->getAttribute('target');
1647             my $edgeID = $edgeElement->getAttribute('id');
1648             my $weight = ($edgeElement->hasAttribute('weight')) ? $edgeElement->getAttribute('weight') : $defaultWeight;
1649             my $label = ($edgeElement->hasAttribute('label')) ? $edgeElement->getAttribute('label') : $EMPTY_STRING;
1650             my $edgeDirected = ($edgeElement->hasAttribute('type')) ? $edgeElement->getAttribute('type') : $graphDirected;
1651             if ($weight) {
1652             $dupedgecount++ if $self->edgeExists( { sourceID=>$source, targetID=>$target } );
1653             $self->edge( { sourceID=>$source, targetID=>$target, weight=>$weight, directed=>$edgeDirected, label=>$label, id=>$edgeID } );
1654             $edgecount++;
1655             }
1656             else {
1657             carp "inputGraphfromGEXF: edge $source $target has no weight. Not created."
1658             }
1659            
1660             }
1661            
1662             carp "inputGraphfromGEXF: no nodes read from '$filename'" if !$nodecount;
1663             carp "inputGraphfromGEXF: no edges read from '$filename'" if !$edgecount;
1664            
1665             print "inputGraphfromGEXF: found $nodecount nodes and $edgecount edges\n" if $VERBOSE;
1666             print "inputGraphfromGEXF: found $dupedgecount duplicate edges\n" if $dupedgecount and $VERBOSE;
1667            
1668             return $self;
1669             }
1670            
1671            
1672             sub outputGraphtoGEXF {
1673             my ($self, $filename) = @_;
1674            
1675             my $nsURI = 'http://www.gexf.net/1.2draft';
1676            
1677             my $doc = XML::LibXML::Document->new('1.0','UTF-8');
1678             my $GEXF = $doc->createElementNS( $EMPTY_STRING, 'gexf' );
1679             $doc->setDocumentElement( $GEXF );
1680            
1681             $GEXF->setNamespace( $nsURI , $EMPTY_STRING, 1 );
1682            
1683             $GEXF->setAttribute('xmlns:xsi','http://www.w3.org/2001/XMLSchema-instance');
1684             $GEXF->setAttribute('xsi:schemaLocation','http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd');
1685             $GEXF->setAttribute('version','1.2');
1686            
1687             {
1688             my $now_string = localtime;
1689             $GEXF->appendChild($doc->createComment("Generated by Graph::Dijkstra on $now_string"));
1690             }
1691             {
1692             my (undef, undef, undef, $mday, $month, $year, undef, undef, undef) = localtime;
1693             my $ISODATE = sprintf "%4d-%02d-%02d", $year+1900, $month+1, $mday;
1694             my $meta = $GEXF->addNewChild( $nsURI, 'meta');
1695             $meta->setAttribute('lastmodifieddate', $ISODATE);
1696             if ($self->{creator}) {
1697             my $creatorNode = $meta->addNewChild( $nsURI, 'creator');
1698             $creatorNode->appendTextNode( $self->{creator} );
1699             }
1700             if ($self->{label}) {
1701             my $descriptionNode = $meta->addNewChild( $nsURI, 'description');
1702             $descriptionNode->appendTextNode( $self->{label} );
1703             }
1704             }
1705            
1706             my $graph = $GEXF->addNewChild( $nsURI, 'graph' );
1707             $graph->setAttribute('mode','static');
1708             $graph->setAttribute('defaultedgetype', $self->{edgedefault} );
1709             my $nodesElement = $graph->addNewChild( $nsURI, 'nodes' );
1710            
1711             my $nodecount = 0;
1712             my $edgecount = 0;
1713            
1714             my %edges = ();
1715             foreach my $nodeID (keys %{$self->{graph}}) {
1716            
1717             my $nodeNode = $nodesElement->addNewChild( $nsURI, 'node' );
1718             $nodeNode->setAttribute('id', $nodeID);
1719             $nodeNode->setAttribute('label', $self->{graph}{$nodeID}{label} );
1720            
1721             $nodecount++;
1722             if (exists($self->{graph}{$nodeID}{edges})) {
1723             foreach my $targetID (keys %{$self->{graph}{$nodeID}{edges}}) {
1724             my $directed = $self->{graph}{$nodeID}{edges}{$targetID}{directed};
1725             if ( ($directed eq 'undirected' and !exists($edges{$targetID}{$nodeID})) or $directed eq 'directed') {
1726             $edges{$nodeID}{$targetID}{weight} = $self->{graph}{$nodeID}{edges}{$targetID}{weight};
1727             $edges{$nodeID}{$targetID}{id} = $self->{graph}{$nodeID}{edges}{$targetID}{id};
1728             $edges{$nodeID}{$targetID}{directed} = $directed;
1729             $edges{$nodeID}{$targetID}{label} = $self->{graph}{$nodeID}{edges}{$targetID}{label};
1730             }
1731             }
1732             }
1733             }
1734            
1735             my $edgesElement = $graph->addNewChild( $nsURI, 'edges' );
1736            
1737             foreach my $sourceID (keys %edges) {
1738             foreach my $targetID (keys %{$edges{$sourceID}}) {
1739            
1740             $edgecount++;
1741             my $edgeNode = $edgesElement->addNewChild( $nsURI, 'edge');
1742             $edgeNode->setAttribute('id', ($edges{$sourceID}{$targetID}{id} ne '') ? $edges{$sourceID}{$targetID}{id} : $edgecount);
1743             $edgeNode->setAttribute('source', $sourceID );
1744             $edgeNode->setAttribute('target', $targetID );
1745             $edgeNode->setAttribute('weight', $edges{$sourceID}{$targetID}{weight} );
1746             $edgeNode->setAttribute('directed', $edges{$sourceID}{$targetID}{directed} ) if $edges{$sourceID}{$targetID}{directed} ne $self->{edgedefault};
1747             $edgeNode->setAttribute('label', $edges{$sourceID}{$targetID}{label} ) if $edges{$sourceID}{$targetID}{label};
1748            
1749             }
1750             }
1751             my $state = $doc->toFile($filename,2);
1752             croak "could not output internal grap to '$filename'" if !$state;
1753            
1754             print "outputGraphtoGEXF: wrote $nodecount nodes and $edgecount edges to '$filename'\n" if $VERBOSE;
1755             return $self;
1756             }
1757            
1758             sub validateGraphMLxml {
1759             my ($either, $filename) = @_;
1760            
1761             my $GraphML_URL = 'http://graphml.graphdrawing.org/xmlns/1.1/graphml.xsd';
1762            
1763             my $GraphMLschema = XML::LibXML::Schema->new( location => $GraphML_URL );
1764             print "validateGraphMLxml: loaded GraphML schema\n" if $VERBOSE;
1765            
1766             my $dom = XML::LibXML->load_xml(location => $filename);
1767             print "validateGraphMLxml: loaded '$filename'\n" if $VERBOSE;
1768            
1769             eval { $GraphMLschema->validate( $dom ); };
1770            
1771             if ($@) {
1772             print "validateGraphMLxml: validate failed\n$@\n" if $VERBOSE;
1773             return(0,$@);
1774             }
1775             else {
1776             print "validateGraphMLxml: validated\n" if $VERBOSE;
1777             return(1,$EMPTY_STRING);
1778             }
1779            
1780             }
1781            
1782             sub validateGEXFxml {
1783             my ($either, $filename) = @_;
1784            
1785             my $GEXF_URL = 'http://www.gexf.net/1.2draft/gexf.xsd';
1786            
1787             my $GEXFschema = XML::LibXML::Schema->new( location => $GEXF_URL );
1788             print "validateGEXFxml: loaded GEXF schema\n" if $VERBOSE;
1789            
1790             my $dom = XML::LibXML->load_xml(location => $filename);
1791             print "validateGEXFxml: loaded '$filename'\n" if $VERBOSE;
1792            
1793             eval { $GEXFschema->validate( $dom ); };
1794            
1795             if ($@) {
1796             print "validateGEXFxml: validate failed\n$@\n" if $VERBOSE;
1797             return(0,$@);
1798             }
1799             else {
1800             print "validateGEXFxml: validated\n" if $VERBOSE;
1801             return(1,$EMPTY_STRING);
1802             }
1803            
1804             }
1805            
1806            
1807             } #XML file format methods
1808            
1809             #############################################################################
1810             #NET (Pajek) file format methods #
1811             #############################################################################
1812             {
1813             sub inputGraphfromNET {
1814             my ($self, $filename) = @_;
1815            
1816             use Regexp::Common qw /delimited/;
1817            
1818             if (!ref($self)) {
1819             $self = Graph::Dijkstra->new();
1820             }
1821            
1822             open(my $infile, '<:encoding(UTF-8)', $filename) or croak "inputGraphfromNET: could not open '$filename' for input";
1823            
1824             print "inputGraphfromNET: opened '$filename' for input\n" if $VERBOSE;
1825            
1826             my $nodes = 0;
1827             while (my $line = <$infile>) {
1828             if ($line =~ /^\*vertices\s+(\d+)/ix) {
1829             $nodes = $1;
1830             last;
1831             }
1832             }
1833             croak "inputGraphfromNET: vertices element not found" if !$nodes;
1834             print "inputGraphfromNET: vertices = $nodes\n" if $VERBOSE;
1835            
1836             my $nodecount = 0;
1837             my $edgecount = 0;
1838             my $dupedgecount = 0;
1839            
1840             my $quotedRE = $RE{delimited}{-delim=>'"'};
1841             #print "quotedRE = '$quotedRE'\n";
1842            
1843             my $nextSection = '';
1844             foreach my $i (1 .. $nodes) {
1845             my $line = '';
1846             while(1) {
1847             $line = <$infile>;
1848             chomp $line;
1849             croak "inputGraphfromNET: unexpected EOF in vertices section" if !defined($line);
1850             last if substr($line,0,1) ne '%';
1851             }
1852            
1853             if (substr($line,0,1) eq '*') {
1854             chomp $line;
1855             $nextSection = lc($line);
1856             last;
1857             }
1858            
1859             if ($line =~ /^\s*(\d+)\s+($quotedRE)/ix) {
1860             my $id = $1;
1861             my $label = $2;
1862             $label = substr($label,1,-1); #strip quotes
1863             $self->node( {id=>$id, label=>$label} );
1864             $nodecount++;
1865             }
1866             }
1867             if ($nextSection and $nodecount == 0) {
1868             print "inputGraphfromNET: empty vertices section (no node labels). Generating node ID values 1 .. $nodes\n" if $VERBOSE;
1869             foreach my $i (1 .. $nodes) {
1870             $self->node( {id=>$i, label=>$EMPTY_STRING} );
1871             $nodecount++;
1872             }
1873             }
1874             elsif ($nodes != $nodecount) {
1875             die "inputGraphfromNET: internal logic error: # vertices ($nodes) != # read nodes ($nodecount)";
1876             }
1877            
1878             if ($nextSection =~ /^(\*\w+)/) {
1879             $nextSection = $1;
1880             }
1881             elsif ($nextSection) {
1882             die "inputGraphfromNET: internal logic error. Did not recognize next section '$nextSection' in NET (pajek) file.";
1883             }
1884            
1885             croak "inputGraphfromNET: input file contains *arclist section. Not supported." if $nextSection eq '*arclist';
1886             croak "inputGraphfromNET: input file contains *edgelist section. Not supported." if $nextSection eq '*edgelist';
1887            
1888             print "inputGraphfromNET: next section is '$nextSection'\n" if $nextSection and $VERBOSE;
1889            
1890             while (1) {
1891            
1892             if ($nextSection ne '*arcs' and $nextSection ne '*edges') {
1893             $nextSection = '';
1894             while (my $line = <$infile>) {
1895             if ($line =~ /^(\*(?:edges|arcs))/i) {
1896             $nextSection = lc($1);
1897             last;
1898             }
1899             }
1900             last if !$nextSection;
1901             }
1902            
1903             my $edgeDirected = ($nextSection eq '*edges') ? 'undirected' : 'directed';
1904             $nextSection = '';
1905            
1906             while (my $line = <$infile>) {
1907             chomp $line;
1908             next if !$line;
1909             next if substr($line,0,1) eq '%';
1910             if ($line =~ /^(\*\w+)/) {
1911             $nextSection = lc($1);
1912             last;
1913             }
1914             if ($line =~ /^\s+(\d+)\s+(\d+)\s+([0-9\.]+)/s) {
1915             my $sourceID = $1;
1916             my $targetID = $2;
1917             my $weight = $3;
1918             $dupedgecount++ if $self->edgeExists( { sourceID=>$sourceID, targetID=>$targetID } );
1919             $self->edge( { sourceID=>$sourceID, targetID=>$targetID, weight=>$weight, directed=>$edgeDirected } );
1920             $edgecount++;
1921             }
1922             else {
1923             chomp $line;
1924             carp "inputGraphfromNET: unrecognized input line (maybe edge with no weight?) =>$line<=";
1925             last;
1926             }
1927             }
1928             last if !$nextSection;
1929             }
1930             close($infile);
1931            
1932             carp "inputGraphfromNET: no nodes read from '$filename'" if !$nodecount;
1933             carp "inputGraphfromNET: no edges read from '$filename'" if !$edgecount;
1934            
1935             print "inputGraphfromNET: found $nodecount nodes and $edgecount edges\n" if $VERBOSE;
1936             print "inputGraphfromNET: found $dupedgecount duplicate edges\n" if $dupedgecount and $VERBOSE;
1937            
1938             return $self;
1939             }
1940            
1941             sub outputGraphtoNET {
1942             my ($self, $filename) = @_;
1943            
1944             open(my $outfile, '>:encoding(UTF-8)', $filename) or croak "outputGraphtoNET: could not open '$filename' for output";
1945            
1946             print "outputGraphtoNET: opened '$filename' for output\n" if $VERBOSE;
1947            
1948             my %edges = ();
1949             my $nodecount = 0;
1950             my $edgecount = 0;
1951             my $useConsecutiveNumericIDs = 1;
1952             my $hasNonBlankLabels = 0;
1953             my $highestNumericID = 0;
1954             my $lowestNumericID = $PINF;
1955            
1956             my @nodeList = $self->nodeList();
1957            
1958             foreach my $nodeHref (@nodeList) {
1959             $nodecount++;
1960             my $nodeID = $nodeHref->{id};
1961             my $label = $nodeHref->{label};
1962             if ($useConsecutiveNumericIDs) {
1963             if ($nodeID =~ /^\d+$/) {
1964             $highestNumericID = $nodeID if $nodeID > $highestNumericID;
1965             $lowestNumericID = $nodeID if $nodeID < $lowestNumericID;
1966             }
1967             else {
1968             $useConsecutiveNumericIDs = 0;
1969             }
1970             }
1971            
1972             $hasNonBlankLabels = 1 if (!$hasNonBlankLabels and $label ne $EMPTY_STRING);
1973             }
1974             print "outputGraphtoNET: internal graph has non-blank labels.\n" if $VERBOSE and $hasNonBlankLabels;
1975            
1976             if ($useConsecutiveNumericIDs) {
1977             if ($highestNumericID != $nodecount or $lowestNumericID != 1) {
1978             $useConsecutiveNumericIDs = 0;
1979             }
1980             }
1981            
1982            
1983             {
1984             my $now_string = localtime;
1985             print {$outfile} "% Generated by Graph::Dijkstra on $now_string\n";
1986             }
1987            
1988             print {$outfile} "*Vertices $nodecount\n";
1989            
1990             my $hasArcs = 0;
1991             my $hasEdges = 0;
1992            
1993             if ($useConsecutiveNumericIDs) {
1994            
1995             print "outputGraphtoNET: internal graph has consecutive numeric IDs.\n" if $VERBOSE;
1996             $nodecount = 0;
1997             foreach my $nodeHref (sort { $a->{id} <=> $b->{id} } @nodeList) {
1998            
1999             $nodecount++;
2000            
2001             my $nodeID = $nodeHref->{id};
2002             my $label = $nodeHref->{label};
2003             croak "outputGraphtoNET: node IDs are not consecutive numeric integers starting at 1" if ($nodeID != $nodecount);
2004            
2005             if ($hasNonBlankLabels) {
2006             printf {$outfile} "%7d \"%s\"\n", $nodeID, $label;
2007             }
2008            
2009             if (exists($self->{graph}{$nodeID}{edges})) {
2010             foreach my $targetID (keys %{$self->{graph}{$nodeID}{edges}}) {
2011             my $edgeDirected = $self->{graph}{$nodeID}{edges}{$targetID}{directed};
2012             if ( ($edgeDirected eq 'undirected' and !exists($edges{$targetID}{$nodeID}) ) or $edgeDirected eq 'directed') {
2013             $edges{$nodeID}{$targetID}{weight} = $self->{graph}{$nodeID}{edges}{$targetID}{weight};
2014             $edges{$nodeID}{$targetID}{directed} = $edgeDirected;
2015             if ($edgeDirected eq 'directed') {
2016             $hasArcs++;
2017             }
2018             else {
2019             $hasEdges++;
2020             }
2021             }
2022             }
2023             }
2024             }
2025             }
2026             else {
2027             if ($VERBOSE) {
2028             print "outputGraphtoNET: internal graph node ID values are not consecutive integer values starting at 1.\n";
2029             print "outputGraphtoNET: internal graph node ID values not perserved in output\n";
2030             print "outputGraphtoNET: generating consecutive integer ID values in output\n";
2031             }
2032            
2033             my %nodeIDtoNumericID = ();
2034             foreach my $i (0 .. $#nodeList) {
2035             $nodeIDtoNumericID{ $nodeList[$i]->{id} } = $i+1;
2036             }
2037            
2038             foreach my $nodeID (sort {$nodeIDtoNumericID{$a} <=> $nodeIDtoNumericID{$b}} keys %nodeIDtoNumericID) {
2039             if ($hasNonBlankLabels) {
2040             printf {$outfile} "%7d \"%s\"\n", $nodeIDtoNumericID{$nodeID}, $self->{graph}{$nodeID}{label};
2041             }
2042            
2043             if (exists($self->{graph}{$nodeID}{edges})) {
2044             my $numericNodeID = $nodeIDtoNumericID{$nodeID};
2045             foreach my $targetID (keys %{$self->{graph}{$nodeID}{edges}}) {
2046             my $edgeDirected = $self->{graph}{$nodeID}{edges}{$targetID}{directed};
2047             my $numericTargetID = $nodeIDtoNumericID{$targetID};
2048             if ( ($edgeDirected eq 'undirected' and !exists($edges{$numericTargetID}{$numericNodeID})) or $edgeDirected eq 'directed') {
2049             $edges{$numericNodeID}{$numericTargetID}{weight} = $self->{graph}{$nodeID}{edges}{$targetID}{weight};
2050             $edges{$numericNodeID}{$numericTargetID}{directed} = $edgeDirected;
2051             if ($edgeDirected eq 'directed') {
2052             $hasArcs++;
2053             }
2054             else {
2055             $hasEdges++;
2056             }
2057             }
2058             }
2059             }
2060             }
2061             }
2062            
2063             if ($hasEdges) {
2064             print {$outfile} "*Edges\n";
2065             foreach my $sourceID (sort {$a <=> $b} keys %edges) {
2066             foreach my $targetID (sort {$a <=> $b} keys %{$edges{$sourceID}} ) {
2067             next if $edges{$sourceID}{$targetID}{directed} eq 'directed';
2068             printf {$outfile} "%7d %7d %10s\n", $sourceID, $targetID, "$edges{$sourceID}{$targetID}{weight}";
2069             $edgecount++;
2070             }
2071             }
2072             }
2073             if ($hasArcs) {
2074             print {$outfile} "*Arcs\n";
2075             foreach my $sourceID (sort {$a <=> $b} keys %edges) {
2076             foreach my $targetID (sort {$a <=> $b} keys %{$edges{$sourceID}} ) {
2077             next if $edges{$sourceID}{$targetID}{directed} eq 'undirected';
2078             printf {$outfile} "%7d %7d %10s\n", $sourceID, $targetID, "$edges{$sourceID}{$targetID}{weight}";
2079             $edgecount++;
2080             }
2081             }
2082             }
2083             close($outfile);
2084            
2085             print "outputGraphtoNET: wrote $nodecount nodes and $edgecount edges to '$filename'\n" if $VERBOSE;
2086             return $self;
2087             }
2088            
2089            
2090             } #NET (Pagek) file format methods
2091            
2092             1;
2093            
2094             __END__