File Coverage

blib/lib/yEd/Document.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package yEd::Document;
2              
3 1     1   806 use 5.006;
  1         3  
  1         31  
4 1     1   4 use strict;
  1         1  
  1         36  
5 1     1   15 use warnings FATAL => 'all';
  1         1  
  1         40  
6 1     1   188 use XML::LibXML;
  0            
  0            
7             use Carp;
8             use yEd::Label::EdgeLabel;
9             use yEd::Label::NodeLabel;
10             use yEd::Edge::ArcEdge;
11             use yEd::Edge::BezierEdge;
12             use yEd::Edge::GenericEdge;
13             use yEd::Edge::PolyLineEdge;
14             use yEd::Edge::QuadCurveEdge;
15             use yEd::Edge::SplineEdge;
16             use yEd::Node::ShapeNode;
17             use yEd::Node::GenericNode;
18              
19             =head1 NAME
20              
21             yEd::Document - pure perl API to easily create yEd-loadable Documents from scratch (using XML::LibXML)
22              
23             =head1 VERSION
24              
25             Version 1.00
26              
27             =cut
28              
29             our $VERSION = '1.00';
30              
31             =head1 DEPENDENCIES
32              
33             =over 4
34            
35             =item *
36            
37             L
38              
39             =back
40              
41             =head1 INTENTION
42              
43             This package is intended to offer a way to create yEd Documents from scratch using perl.
44              
45             It is ment to help automating the task of creating graphical overviews of platforms, workflows, dependencies, ...
46              
47             Since it doesn't support all the features of a yEd created Document you can only create Documents, loading Documents into the package is not supported, yet.
48              
49             =head1 SUPPORTED FEATURES
50              
51             This package and all available features have been developed and tested for yEd version 3.13.
52              
53             If you find it doesn't work on another version or a specific platform, please let me know.
54              
55             Object features are supported as described at
56              
57             =over 4
58            
59             =item *
60            
61             L
62            
63             =item *
64            
65             L
66            
67             =item *
68            
69             L
70            
71             =back
72              
73             and its subclasses.
74              
75             The Document itself supports basic templating, layers and id management.
76              
77             You can also use relative coords for positioning your entities and use this feature for basic grouping.
78              
79             All entities are property based and can also be selected by properties.
80              
81             =head1 SYNOPSIS
82              
83             This package provides a pure object oriented implementation,
84             so only use functions and properties of yEd::Document and the other types in the context of a blessed instance.
85              
86             Minimal example (creating an empty, yEd loadable Document):
87              
88             use yEd::Document;
89             # create the document
90             $d = yEd::Document->new();
91             # build the document
92             $xmlstring = $d->buildDocument();
93             # or
94             $d->buildDocument('/mypath/mydocument');
95              
96             Example of using relative entities and layers (ready to run):
97              
98             use strict;
99             use yEd::Document;
100            
101             my $d = yEd::Document->new();
102             # play around with these values and note that the edge will always keep its right angles :)
103             my $sx = -15; # -0.5 * default width
104             my $sy = 15; # 0.5 * default height -> lower left corner of source
105             my $tx = 15; # 0.5 * default width
106             my $ty = 15; # 0.5 * default height -> lower right corner of target
107             my $distance = 250;
108            
109             # place the absolute "root" node
110             my $grouproot = $d->addNewNode('ShapeNode', 'x' => 1000, 'y' => 1000);
111             # place another node $distance units to its right (or left if negative)
112             my $n = $d->addNewNode('ShapeNode', 'x' => $distance, 'y' => 0, 'relative' => $grouproot);
113             # place an edge on both which will go 50 units down, then $distance units (modified by anchor offsets) right/left (and then up again to connect to its target)
114             my $e = $d->addNewEdge('PolyLineEdge', $grouproot, $n, 'relativeWaypoints' => 1, 'sx' => $sx, 'sy' => $sy, 'tx' => $tx, 'ty' => $ty);
115             $e->waypoints([0,50],[$distance - $sx + $tx,0]);
116             # place a circle on top of $grouproot to make the "group movement" visible in yEd
117             my $c = $d->addNewNode('ShapeNode','shape' => 'ellipse', 'layer' => 1, 'x' => 1000, 'y' => 1000);
118             $d->addNewEdge('PolyLineEdge', $n, $c, 'tArrow' => 'standard', 'sArrow' => 'standard');
119             # you can now move the whole "group" by modifying $grouproot's x and y values (uncomment it and watch the difference in yEd)
120             #$grouproot->setProperties('x' => 778.88, 'y' => 900);
121            
122             $d->buildDocument('test');
123              
124             Example of using templating (ready to run):
125              
126             use strict;
127             use yEd::Document;
128            
129             my $d = yEd::Document->new();
130            
131             # preparing the templates
132             $d->addNewLabelTemplate('headline', 'NodeLabel', 'The house of Santa Claus', 'positionModell' => 'sandwich-n', 'fontSize' => 20);
133             $d->addNewNodeTemplate('housenode', 'ShapeNode', 'shape' => 'ellipse', 'fillColor' => '#0000ff');
134             my $wall = $d->addNewEdgeTemplate('wall', 'GenericEdge', 'lineWidth' => 5, 'fillColor' => '#0000ff', 'tArrow' => 'standard');
135             $wall->addNewLabel('This', 'positionModell' => 'three_center-scentr', 'textColor' => '#005500', 'backgroundColor' => '#cccccc');
136             $d->addEdgeTemplate('roof', $wall, 'fillColor' => '#ff0000');
137            
138             # adding the nodes
139             my $n = $d->addTemplateNode('housenode', 'y' => 300);
140             my $n1 = $d->addTemplateNode('housenode');
141             my $n2 = $d->addTemplateNode('housenode', 'x' => 300);
142             my $n3 = $d->addTemplateNode('housenode', 'x' => 300, 'y' => 300);
143             # adding a node that wasn't defined as a template
144             my $n4 = $d->addNewNode('ShapeNode', 'x' => 150, 'y' => -150, 'shape' => 'triangle', 'fillColor' => '#ff0000');
145             $n4->addLabel($d->getTemplateLabel('headline'));
146            
147             # adding the edges
148             $d->addTemplateEdge('wall',$n,$n1);
149             ($d->addTemplateEdge('wall',$n1,$n2)->labels())[0]->text('is');
150             ($d->addTemplateEdge('wall',$n2,$n)->labels())[0]->text('the');
151             ($d->addTemplateEdge('wall',$n,$n3)->labels())[0]->text('house');
152             ($d->addTemplateEdge('wall',$n3,$n2)->labels())[0]->text('of');
153             ($d->addTemplateEdge('roof',$n2,$n4)->labels())[0]->text('San-');
154             ($d->addTemplateEdge('roof',$n4,$n1)->labels())[0]->text('ta');
155             ($d->addTemplateEdge('wall',$n1,$n3)->labels())[0]->text('Claus');
156            
157             $d->buildDocument('santa_claus');
158              
159             =head1 OBJECT IDENTIFIERS
160              
161             All Nodes and Edges of a Document must have an unique ID.
162              
163             If you only use the yEd::Document's addNew... functions and templating features you won't have to care for IDs.
164              
165             But if you need to externally create Nodes or Edges from its classes you will have to provide the ID yourself.
166              
167             In this case it is up to you to ensure unique values unless you obtain the IDs via yEd::Document's getFreeId function.
168              
169             This automatically created IDs will always be positive integer values, but if you provide your own IDs you can use almost anything.
170              
171             If you load and save a Document in yEd all IDs will be converted to yEd's syntax ('n0','n1',...,'e0','e1',...).
172              
173             =head1 TEMPLATING
174              
175             If you have to add many copies of the same modification of a Node, Edge or Label have a look at yEd::Document's template functions.
176              
177             These offer the ability to save an object's configuration and return and/or add copies of it.
178              
179             For Nodes and Edges added Labels will be copied along with the template.
180              
181             For Nodes that are relative to other Nodes you may want to call 'relative(0)' or 'relative($newTarget)' on the copy (or provide it with the properties parameter).
182              
183             For Edges all waypoints are copied along with the template, this may not be what you want unless the Edge's 'relativeWaypoints' property is set, consider calling 'clearWaypoints()' on the copy.
184              
185             =head1 RELATIVE OBJECTS
186              
187             A Node or Edge-waypoint by default will be absolute, this is its x and y values are directly applied to the drawing pane.
188              
189             However you can change a Node to be relative to a given other Node by using the Node's 'relative' property.
190             In this case x and y are values that are added to the other Node's absolute position to determine the Node's position.
191             The other Node however may be relative to yet another Node, too.
192             Just make sure you don't create dependency loops. ;)
193              
194             If you manually cut the relation of two Nodes by setting 'relative(0)' on the dependend Node, the provided x and y value will not be changed but only interpreted as being absolute further on.
195             If you want to cut the relation without changing the Node's current absolute position call '$n->x($n->absX)' and '$n->y($n->absY)' BEFORE the call to 'relative(0)'.
196              
197             Note that the x and y coords of a Node refer to its upper left corner or rather the upper left corner of the Node's surrounding rectangle (e.g. for ellipse ShapeNodes).
198             Consider this if you do any Node positioning math in your scripts.
199              
200             Edges do have such a property, too, it is called 'relativeWaypoints' but is a boolean value in contrast to Node's property.
201             If set to a true value any waypoints added to this Edge will be positioned relative to the previous waypoint.
202             The first waypoint's position will be calculated relative to the Edge's anchor point on its source Node (sx,sy).
203              
204             If you change from 'relativeWaypoints(1)' to 'relativeWaypoints(0)' there will be no conversion of the waypoint coords by default (same behavior as for Nodes).
205              
206             Note that the source Node anchor point (sx,sy) is relative to the Node's center (sx = sy = 0).
207             This is somehow inconsistent as the Node's coords don't describe its center.
208             Consider this if you do any positioning math in your scripts.
209             However for relative waypoints you will likely have the desired bahavior by default, since first waypoint is relative to the anchor point which's absolute position is automatically computed for you by this package.
210              
211             Hint: If you create "groups" with the reative feature always build your group on top of a root node that spans the whole area of the group.
212             If you need the Nodes to not be surrounded by a background root Node simply make it invisible (no fill color, no border).
213             This way you can always ask your root Node for the width and height of the whole "group" if you need these values for calculation. :)
214              
215             Note that this way of "grouping" has no effect in yEd, it does only exist within the code.
216              
217             =cut
218              
219             #TODO: VirtualGroupNode Type ? invisible rectangle ShapeNode that allows adding relative Nodes and autosizes itself on Node addition/removal ...
220              
221             =head1 LAYERS
222              
223             yEd Documents / Nodes do only support "virtual" layering which is saved to the graphml file by giving the Nodes a special order (first Node defined is drawn first).
224              
225             This concept has been adopted by this package.
226             The layer is described as a property of each Node, named 'layer' (Edges do not support layering, they will always be drawn on top the Nodes).
227             Its default value is 0 which is the bottom layer.
228             You can define the layer of a Node as any positive integer value, where a higher value is "more in front".
229              
230             Within a single layer the drawing order of Nodes is undefined, if they overlap or need a special order because of other reasons use different layers.
231              
232             Like with any other property you may obtain a list of all Nodes on a specified layer by using 'getNodesByProperties' (e.g. getNodesByProperties(layer => 3)).
233              
234             =head1 SUBROUTINES/METHODS
235              
236             =head2 new
237              
238             Creates a new instance of yEd::Document and initializes it.
239              
240             =cut
241              
242             sub new {
243             my $self = {};
244             bless $self, shift;
245             $self->resetDocument();
246             $self->{'dummynode'} = yEd::Node::ShapeNode->new('noid');
247             return $self;
248             }
249              
250             my %graphmlAttr = (
251             'xmlns'=> 'http://graphml.graphdrawing.org/xmlns',
252             'xmlns:xsi'=> 'http://www.w3.org/2001/XMLSchema-instance',
253             'xmlns:y' => 'http://www.yworks.com/xml/graphml',
254             'xmlns:yed' => 'http://www.yworks.com/xml/yed/3',
255             'xsi:schemaLocation' => 'http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd',
256             );
257             my %docKeyAttr = (
258             'd0' => { 'for' => 'graph', 'attr.type' => 'string', 'attr.name' => 'Description' },
259             'd1' => { 'for' => 'port', 'yfiles.type' => 'portgraphics' },
260             'd2' => { 'for' => 'port', 'yfiles.type' => 'portgeometry' },
261              
262             'd4' => { 'for' => 'node', 'attr.type' => 'string', 'attr.name' => 'url' },
263             'd5' => { 'for' => 'node', 'attr.type' => 'string', 'attr.name' => 'description' },
264             'd6' => { 'for' => 'node', 'yfiles.type' => 'nodegraphics' },
265             'd7' => { 'for' => 'graphml', 'yfiles.type' => 'resources' },
266             'd8' => { 'for' => 'edge', 'attr.type' => 'string', 'attr.name' => 'url' },
267             'd9' => { 'for' => 'edge', 'attr.type' => 'string', 'attr.name' => 'description' },
268             'd10' => { 'for' => 'edge', 'yfiles.type' => 'edgegraphics' },
269             );
270              
271             =head2 resetDocument
272              
273             Resets (empties) and reinitializes this Document.
274              
275             Previously registered templates will be kept.
276              
277             =cut
278              
279             sub resetDocument {
280             my $self = shift;
281             $self->{'DOC'} = XML::LibXML::Document->new('1.0', 'UTF-8');
282             $self->{'DOC'}->setStandalone(0);
283             $self->{'ROOT'} = $self->{'DOC'}->createElement('graphml');
284             foreach my $key (keys %graphmlAttr) {
285             $self->{'ROOT'}->setAttribute($key => $graphmlAttr{$key});
286             }
287             foreach my $id (keys %docKeyAttr) {
288             my $keyelement = $self->{'ROOT'}->addNewChild('', 'key');
289             $keyelement->setAttribute('id' => $id);
290             foreach my $key (keys %{$docKeyAttr{$id}}) {
291             $keyelement->setAttribute($key => $docKeyAttr{$id}{$key});
292             }
293             }
294             $self->{'nodes'} = {};
295             $self->{'edges'} = {};
296             $self->{'lastid'} = 0;
297             return;
298             }
299              
300             =head2 buildDocument
301              
302             Builds the Document and returns it as a string.
303              
304             If a filename is provided (without the ending) it will additionally be written into filename.graphml .
305              
306             =cut
307              
308             sub buildDocument {
309             my $self = shift;
310             my $filename = shift;
311             my $graph = $self->{'ROOT'}->addNewChild('', 'graph');
312             $graph->setAttribute('edgedefault' => 'directed');
313             $graph->setAttribute('id' => 'G');
314             $graph->addNewChild('', 'data')->setAttribute('key' => 'd0');
315             for (my $l = 0; $l <= $self->getFrontLayer(); $l++) {
316             foreach my $n ($self->getNodesByProperties('layer' => $l)) {
317             if (my $n2 = $n->relative()) {
318             confess "node $n2 (id: " . $n2->id() . ") as referenced by relative node $n (id: " . $n->id() . ") is not part of this document" unless ($self->hasNodeId($n2->id()));
319             }
320             $graph->addChild($n->_build());
321             }
322             }
323             foreach my $e ($self->getEdges()) {
324             foreach my $n ($e->getNodes()) {
325             confess "node $n (id: " . $n->id() . ") as referenced by edge $e (id: " . $e->id() . ") is not part of this document" unless ($self->hasNodeId($n->id()));
326             }
327             $graph->addChild($e->_build());
328             }
329             my $datablock = $self->{'ROOT'}->addNewChild('', 'data');
330             $datablock->setAttribute('key' => 'd7');
331             $datablock->addNewChild('', 'y:Resources');
332             $self->{'DOC'}->setDocumentElement($self->{'ROOT'});
333             my $out = $self->{'DOC'}->toString();
334             if ($filename) {
335             $filename .= '.graphml';
336             open( OUTFILE, '>', $filename ) or confess "couldn't open $filename for write access";
337             print OUTFILE $out;
338             close OUTFILE;
339             }
340             return $out;
341             }
342              
343             =head2 getFreeId
344              
345             Returns the next still free ID number, that can be used for creating new Nodes or Edges.
346              
347             This is only required if you externally create new Nodes/Edges and you could also use own values as IDs (you will then have to ensure that all IDs are unique).
348              
349             =cut
350              
351             sub getFreeId {
352             my $self = shift;
353             my $id = $self->{'lastid'};
354             do {
355             $id++;
356             } while ($self->hasNodeId($id) or $self->hasEdgeId($id));
357             $self->{'lastid'} = $id;
358             return $id;
359             }
360              
361             =head2 getFrontLayer
362              
363             Returns the highest layer number that is set for any Node of this Document.
364              
365             =cut
366              
367             sub getFrontLayer {
368             my $self = shift;
369             my $layer = 0;
370             foreach my $node ($self->getNodes()) {
371             $layer = $node->layer() if ($node->layer() > $layer);
372             }
373             return $layer;
374             }
375              
376             # Templates
377              
378             =head2 addNewLabelTemplate
379              
380             Creates a template for Labels.
381              
382             Must have parameters are: templatename (a string for accessing the template) , labeltype (Node- or EdgeLabel) , labeltext (the text for the Label)
383              
384             Further parameters to set properties are optional (property => value, ...).
385              
386             The template Label is returned as a reference, if you modify this reference all further copies of the template will be affected !
387             Copies created earlier will not be affected since they are as said copies.
388              
389             =cut
390              
391             sub addNewLabelTemplate {
392             my ($self, $name, $type, @param) = @_;
393             confess 'provide templateName, labelType, labelText, [labelProperties]' unless (defined $name and defined $type and @param);
394             $type = 'yEd::Label::' . $type;
395             my $o = $type->new(@param);
396             return $self->addLabelTemplate($name, $o);
397             }
398              
399             =head2 addLabelTemplate
400              
401             Creates a template for Labels.
402              
403             Must have parameters are: templatename (a string for accessing the template) , label (a reference to a Label object)
404              
405             Further parameters to set properties are optional (property => value, ...).
406              
407             The source Label will not be changed, a copy of the Label will be used as template.
408             If you want to modify the template after adding it use the returned reference to the copy.
409              
410             =cut
411              
412             sub addLabelTemplate {
413             my ($self, $name, $label,@param) = @_;
414             confess 'must be of type yEd::Label' unless ($label->isa('yEd::Label'));
415             $self->{'labelTemplates'}{$name} = $label->copy(@param);
416             return $self->{'labelTemplates'}{$name};
417             }
418              
419             =head2 getTemplateLabel
420              
421             Creates a copy of a previously created Label template and returns it.
422              
423             Must have parameters are: templatename (a string for accessing the template)
424              
425             Further parameters to set properties are optional (property => value, ...).
426              
427             In contrast to Nodes and Edges Label templates can not be added directly because the need to be added to a Node or Edge.
428             So use this function to obtain a copy and do with it what ever you want.
429              
430             =cut
431              
432             sub getTemplateLabel {
433             my ($self, $name, @params) = @_;
434             confess 'provide templateName, [labelProperties]' unless (defined $name);
435             confess "no such template: $name" unless (exists $self->{'labelTemplates'}{$name});
436             return $self->{'labelTemplates'}{$name}->copy(@params);
437             }
438              
439             =head2 addNewNodeTemplate
440              
441             Creates a template for Nodes.
442              
443             Must have parameters are: templatename (a string for accessing the template) , nodetype (Shape- or GenericNode)
444              
445             Further parameters to set properties are optional (property => value, ...).
446              
447             The template Node is returned as a reference, if you modify this reference all further copies of the template will be affected !
448             Copies created earlier will not be affected since they are as said copies.
449              
450             =cut
451              
452             sub addNewNodeTemplate {
453             my ($self, $name, $type, @param) = @_;
454             confess 'provide templateName, nodeType, [nodeProperties]' unless (defined $name and defined $type);
455             $type = 'yEd::Node::' . $type;
456             my $o = $type->new('noid', @param);
457             return $self->addNodeTemplate($name, $o);;
458             }
459              
460             =head2 addNodeTemplate
461              
462             Creates a template for Nodes.
463              
464             Must have parameters are: templatename (a string for accessing the template) , node (a reference to a Node object)
465              
466             Further parameters to set properties are optional (property => value, ...).
467              
468             The source Node will not be changed, a copy of the Node will be used as template.
469             If you want to modify the template after adding it use the returned reference to the copy.
470              
471             =cut
472              
473             sub addNodeTemplate {
474             my ($self, $name, $node, @param) = @_;
475             confess 'must be of type yEd::Node' unless ($node->isa('yEd::Node'));
476             $self->{'nodeTemplates'}{$name} = $node->copy('noid', @param);
477             return $self->{'nodeTemplates'}{$name};
478             }
479              
480             =head2 getTemplateNode
481              
482             Creates a copy of a previously created Node template and returns it.
483            
484             Must have parameters are: templatename (a string for accessing the template)
485            
486             Further parameters to set properties are optional (property => value, ...).
487              
488             =cut
489              
490             sub getTemplateNode {
491             my ($self, $name, @params) = @_;
492             confess 'provide templateName, [nodeProperties]' unless (defined $name);
493             confess "no such template: $name" unless (exists $self->{'nodeTemplates'}{$name});
494             return $self->{'nodeTemplates'}{$name}->copy($self->getFreeId(), @params);
495             }
496              
497             =head2 addTemplateNode
498              
499             Creates a copy of a previously created Node template, adds it to the Document and returns it.
500            
501             Must have parameters are: templatename (a string for accessing the template)
502            
503             Further parameters to set properties are optional (property => value, ...).
504              
505             =cut
506              
507             sub addTemplateNode {
508             my ($self, $name, @params) = @_;
509             my $o = $self->getTemplateNode($name, @params);
510             $self->addNode($o);
511             return $o;
512             }
513              
514             =head2 addNewEdgeTemplate
515              
516             Creates a template for Edges.
517              
518             Must have parameters are: templatename (a string for accessing the template) , edgetype (one of the supported Edge class names)
519              
520             Further parameters to set properties are optional (property => value, ...).
521              
522             The template Edge is returned as a reference, if you modify this reference all further copies of the template will be affected !
523             Copies created earlier will not be affected since they are as said copies.
524              
525             =cut
526              
527             sub addNewEdgeTemplate {
528             my ($self, $name, $type, @param) = @_;
529             confess 'provide templateName, edgeType, [edgeProperties]' unless (defined $name and defined $type);
530             $type = 'yEd::Edge::' . $type;
531             my $o = $type->new('noid', $self->{'dummynode'}, $self->{'dummynode'}, @param);
532             return $self->addEdgeTemplate($name, $o);;
533             }
534              
535             =head2 addEdgeTemplate
536              
537             Creates a template for Edges.
538            
539             Must have parameters are: templatename (a string for accessing the template) , edge (a reference to an Edge object)
540            
541             Further parameters to set properties are optional (property => value, ...).
542              
543             The source Edge will not be changed, a copy of the Edge will be used as template.
544             If you want to modify the template after adding it use the returned reference to the copy.
545              
546             =cut
547              
548             sub addEdgeTemplate {
549             my ($self, $name, $edge, @param) = @_;
550             confess 'must be of type yEd::Edge' unless ($edge->isa('yEd::Edge'));
551             $self->{'edgeTemplates'}{$name} = $edge->copy('noid', $self->{'dummynode'}, $self->{'dummynode'}, @param);
552             return $self->{'edgeTemplates'}{$name};
553             }
554              
555             =head2 getTemplateEdge
556              
557             Creates a copy of a previously created Edge template and returns it.
558            
559             Must have parameters are: templatename (a string for accessing the template), source, target (the new source and target yEd::Node refs for the new Edge)
560            
561             Further parameters to set properties are optional (property => value, ...).
562              
563             =cut
564              
565             sub getTemplateEdge {
566             my ($self, $name, $s, $t, @params) = @_;
567             confess 'provide templateName, edgeSourceNode, edgeTargetNode, [edgeProperties]' unless (defined $name and defined $s and defined $t);
568             confess "no such template: $name" unless (exists $self->{'edgeTemplates'}{$name});
569             return $self->{'edgeTemplates'}{$name}->copy($self->getFreeId(), $s, $t, @params);
570             }
571              
572             =head2 addTemplateEdge
573              
574             Creates a copy of a previously created Edge template, adds it to the Document and returns it.
575            
576             Must have parameters are: templatename (a string for accessing the template), source, target (the new source and target yEd::Node refs for the new Edge)
577            
578             Further parameters to set properties are optional (property => value, ...).
579              
580             =cut
581              
582             sub addTemplateEdge {
583             my ($self, $name, $s, $t, @params) = @_;
584             my $o = $self->getTemplateEdge($name, $s, $t, @params);
585             $self->addEdge($o);
586             return $o;
587             }
588              
589             # Nodes
590              
591             =head2 addNewNode
592              
593             Creates a new Node, adds it and returns a reference to it.
594              
595             Must have parameters are: nodetype (Shape- or GenericNode)
596              
597             Further parameters to set properties are optional (property => value, ...).
598              
599             =cut
600              
601             sub addNewNode {
602             my ($self, $type, @param) = @_;
603             $type = 'yEd::Node::' . $type;
604             my $node = $type->new($self->getFreeId(), @param);
605             $self->addNode($node);
606             return $node;
607             }
608              
609             =head2 addNode
610              
611             Takes a yEd::Node as parameter and adds it to the Document.
612              
613             =cut
614              
615             sub addNode {
616             my ($self, $node) = @_;
617             confess 'must be of type yEd::Node' unless ($node->isa('yEd::Node'));
618             confess "node ids must be unique: " . $node->id() if $self->hasNodeId($node->id());
619             $self->{'nodes'}{$node->id()} = $node;
620             return;
621             }
622              
623             =head2 getNodes
624              
625             Returns an array of all Nodes of this Document.
626              
627             =cut
628              
629             sub getNodes {
630             return values $_[0]->{'nodes'};
631             }
632              
633             =head2 getNodesByProperties
634              
635             Takes arguments of the form 'property1 => value, property2 => value2, ...'.
636              
637             Returns a list of all Nodes that matches the given filter.
638              
639             =cut
640              
641             sub getNodesByProperties {
642             my ($self, @properties) = @_;
643             my @nodes;
644             foreach my $node ($self->getNodes()) {
645             push @nodes, $node if $node->hasProperties(@properties);
646             }
647             return @nodes;
648             }
649              
650             =head2 hasNodeId
651              
652             Takes a Node's id as parameter and returns true if it is present in this Document.
653              
654             =cut
655              
656             sub hasNodeId {
657             my ($self, $id) = @_;
658             confess 'no id provided' unless (defined $id);
659             return exists $self->{'nodes'}{$id};
660             }
661              
662             =head2 getNodeById
663              
664             Takes an id as parameter and returns the Node with this id, if present in this Document.
665              
666             =cut
667              
668             sub getNodeById {
669             my ($self, $id) = @_;
670             confess 'no id provided' unless (defined $id);
671             return $self->{'nodes'}{$id};
672             }
673              
674             =head2 removeNode
675              
676             Takes a yEd::Node as parameter and tries to remove it from this document.
677              
678             All connected Edges will be removed, too.
679              
680             If there are Nodes relative to the given Node, they will also be removed (and their Edges and Nodes relative to these Nodes and so on, until all dependencies are resolved) unless the second (keepRelative) parameter is true.
681             In this case the absolute x and y values are calculated and the relationship is cut, making the orphaned Nodes absolute.
682              
683             Example: if you call removeNode($grouproot) in the "example of using relative entities and layers" (see SYNOPSIS), only the circle will remain.
684             If you call removeNode($grouproot, 1) instead, only $grouproot and $e will be removed, making $n absolute at its last position.
685              
686             =cut
687              
688             sub removeNode {
689             my ($self, $node, $keepRelative) = @_;
690             confess 'no yEd::Node provided' unless (defined $node and $node->isa('yEd::Node'));
691             if ($self->hasNodeId($node->id())) {
692             delete $self->{'nodes'}{$node->id()};
693             foreach my $e ($node->getEdges()) {
694             $self->removeEdge($e);
695             }
696             foreach my $n ($self->getNodesByProperties('relative' => $node)) {
697             if ($keepRelative) {
698             $n->x($n->absX());
699             $n->y($n->absY());
700             $n->relative(0);
701             } else {
702             $self->removeNode($n);
703             }
704             }
705             }
706             return;
707             }
708              
709             # Edges
710              
711             =head2 addNewEdge
712              
713             Creates a new Edge, adds it and returns a reference to it.
714              
715             Must have parameters are: edgetype (one of the supported Edge class names), source, target (the source and target yEd::Node refs for the new Edge)
716              
717             Further parameters to set properties are optional (property => value, ...).
718              
719             =cut
720              
721             sub addNewEdge {
722             my ($self, $type, @param) = @_;
723             $type = 'yEd::Edge::' . $type;
724             my $edge = $type->new($self->getFreeId(), @param);
725             $self->addEdge($edge);
726             return $edge;
727             }
728              
729             =head2 addEdge
730              
731             Takes a yEd::Edge as parameter and adds it to the Document.
732              
733             =cut
734              
735             sub addEdge {
736             my ($self, $edge) = @_;
737             confess 'must be of type yEd::Edge' unless ($edge->isa('yEd::Edge'));
738             confess 'edge ids must be unique: ' . $edge->id() if $self->hasEdgeId($edge->id());
739             $self->{'edges'}{$edge->id()} = $edge;
740             return;
741             }
742              
743             =head2 getEdges
744              
745             Returns an array of all Edges of this Document.
746              
747             =cut
748              
749             sub getEdges {
750             return values $_[0]->{'edges'};
751             }
752              
753             =head2 getEdgesByProperties
754              
755             Takes arguments of the form 'property1 => value, property2 => value2, ...'.
756              
757             Returns a list of all Edges that matches the given filter.
758              
759             =cut
760              
761             sub getEdgesByProperties {
762             my ($self, @properties) = @_;
763             my @edges;
764             foreach my $edge ($self->getEdges()) {
765             push @edges, $edge if $edge->hasProperties(@properties);
766             }
767             return @edges;
768             }
769              
770             =head2 hasEdgeId
771              
772             Takes an Edge's id as parameter and returns true if it is present in this Document.
773              
774             =cut
775              
776             sub hasEdgeId {
777             my ($self, $id) = @_;
778             confess 'no id provided' unless (defined $id);
779             return exists $self->{'edges'}{$id};
780             }
781              
782             =head2 getEdgeById
783              
784             Takes an id as parameter and returns the Edge with this id, if present in this Document.
785              
786             =cut
787              
788             sub getEdgeById {
789             my ($self, $id) = @_;
790             confess 'no id provided' unless (defined $id);
791             return $self->{'edges'}{$id};
792             }
793              
794             =head2 removeEdge
795              
796             Takes a yEd::Edge as parameter and tries to remove it from this document.
797              
798             =cut
799              
800             sub removeEdge {
801             my ($self, $edge) = @_;
802             confess 'no yEd::Edge provided' unless (defined $edge and $edge->isa('yEd::Edge'));
803             if ($self->hasEdgeId($edge->id())) {
804             $edge->_deregisterAll();
805             delete $self->{'edges'}{$edge->id()};
806             }
807             return;
808             }
809              
810             =head1 AUTHOR
811              
812             Heiko Finzel, C<< >>
813              
814             =head1 BUGS
815              
816             Please report any bugs or feature requests to C, or through
817             the web interface at L. I will be notified, and then you'll
818             automatically be notified of progress on your bug as I make changes.
819              
820              
821             =head1 SUPPORT
822              
823             You can find documentation for this module with the perldoc command.
824              
825             perldoc yEd::Document
826              
827             Also see perldoc of the other classes of this package.
828              
829             You can also look for information at:
830              
831             =over 4
832              
833             =item * RT: CPAN's request tracker (report bugs here)
834              
835             L
836              
837             =item * AnnoCPAN: Annotated CPAN documentation
838              
839             L
840              
841             =item * CPAN Ratings
842              
843             L
844              
845             =item * Search CPAN
846              
847             L
848              
849             =back
850              
851             =head1 LICENSE AND COPYRIGHT
852              
853             Copyright 2014 Heiko Finzel.
854              
855             This program is free software; you can redistribute it and/or modify it
856             under the terms of the the Artistic License (2.0). You may obtain a
857             copy of the full license at:
858              
859             L
860              
861             Any use, modification, and distribution of the Standard or Modified
862             Versions is governed by this Artistic License. By using, modifying or
863             distributing the Package, you accept this license. Do not use, modify,
864             or distribute the Package, if you do not accept this license.
865              
866             If your Modified Version has been derived from a Modified Version made
867             by someone other than you, you are nevertheless required to ensure that
868             your Modified Version complies with the requirements of this license.
869              
870             This license does not grant you the right to use any trademark, service
871             mark, tradename, or logo of the Copyright Holder.
872              
873             This license includes the non-exclusive, worldwide, free-of-charge
874             patent license to make, have made, use, offer to sell, sell, import and
875             otherwise transfer the Package with respect to any patent claims
876             licensable by the Copyright Holder that are necessarily infringed by the
877             Package. If you institute patent litigation (including a cross-claim or
878             counterclaim) against any party alleging that the Package constitutes
879             direct or contributory patent infringement, then this Artistic License
880             to you shall terminate on the date that such litigation is filed.
881              
882             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
883             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
884             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
885             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
886             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
887             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
888             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
889             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
890              
891             =cut
892              
893             1;