]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java
1b0c4221a038f763bc2b814cc04436fd5fdef7f9
[simantics/platform.git] / bundles / org.simantics.modeling / src / org / simantics / modeling / SCLScenegraph.java
1 package org.simantics.modeling;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Dimension;
6 import java.awt.RenderingHints;
7 import java.awt.RenderingHints.Key;
8 import java.awt.geom.AffineTransform;
9 import java.awt.geom.Rectangle2D;
10 import java.io.ByteArrayOutputStream;
11 import java.io.OutputStreamWriter;
12 import java.nio.charset.Charset;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Random;
21 import java.util.Set;
22 import java.util.UUID;
23 import java.util.function.Function;
24 import java.util.stream.Collectors;
25
26 import javax.xml.transform.OutputKeys;
27 import javax.xml.transform.Transformer;
28 import javax.xml.transform.TransformerFactory;
29 import javax.xml.transform.dom.DOMSource;
30 import javax.xml.transform.stream.StreamResult;
31
32 import org.apache.batik.dom.GenericDOMImplementation;
33 import org.apache.batik.svggen.SVGGeneratorContext;
34 import org.apache.batik.svggen.SVGGraphics2D;
35 import org.apache.batik.svggen.SVGIDGenerator;
36 import org.eclipse.swt.SWT;
37 import org.simantics.Simantics;
38 import org.simantics.datatypes.literal.GUID;
39 import org.simantics.db.ReadGraph;
40 import org.simantics.db.Resource;
41 import org.simantics.db.common.request.IndexRoot;
42 import org.simantics.db.common.request.ResourceRead;
43 import org.simantics.db.common.request.UnaryRead;
44 import org.simantics.db.exception.DatabaseException;
45 import org.simantics.db.exception.RuntimeDatabaseException;
46 import org.simantics.db.layer0.variable.Variables;
47 import org.simantics.diagram.elements.DecorationSVGNode;
48 import org.simantics.diagram.elements.DiagramNodeUtil;
49 import org.simantics.diagram.elements.TextGridNode;
50 import org.simantics.diagram.elements.TextNode;
51 import org.simantics.diagram.stubs.DiagramResource;
52 import org.simantics.diagram.ui.DiagramModelHints;
53 import org.simantics.g2d.canvas.Hints;
54 import org.simantics.g2d.canvas.ICanvasContext;
55 import org.simantics.g2d.diagram.DiagramHints;
56 import org.simantics.g2d.diagram.IDiagram;
57 import org.simantics.g2d.diagram.handler.DataElementMap;
58 import org.simantics.g2d.diagram.participant.ElementPainter.SelectionShapeNode;
59 import org.simantics.g2d.diagram.participant.Selection;
60 import org.simantics.g2d.element.IElement;
61 import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider;
62 import org.simantics.g2d.utils.CanvasUtils;
63 import org.simantics.layer0.Layer0;
64 import org.simantics.scenegraph.INode;
65 import org.simantics.scenegraph.ParentNode;
66 import org.simantics.scenegraph.g2d.G2DParentNode;
67 import org.simantics.scenegraph.g2d.G2DRenderingHints;
68 import org.simantics.scenegraph.g2d.G2DSceneGraph;
69 import org.simantics.scenegraph.g2d.IG2DNode;
70 import org.simantics.scenegraph.g2d.IG2DNodeVisitor;
71 import org.simantics.scenegraph.g2d.events.command.Commands;
72 import org.simantics.scenegraph.g2d.nodes.BackgroundNode;
73 import org.simantics.scenegraph.g2d.nodes.BoundsNode;
74 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
75 import org.simantics.scenegraph.g2d.nodes.DataNode;
76 import org.simantics.scenegraph.g2d.nodes.LinkNode;
77 import org.simantics.scenegraph.g2d.nodes.NavigationNode;
78 import org.simantics.scenegraph.g2d.nodes.SVGNode;
79 import org.simantics.scenegraph.g2d.nodes.SelectionNode;
80 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
81 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
82 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
83 import org.simantics.scenegraph.utils.NodeUtil;
84 import org.simantics.scl.runtime.function.Function1;
85 import org.simantics.scl.runtime.tuple.Tuple2;
86 import org.simantics.trend.impl.ItemNode;
87 import org.simantics.utils.threads.ThreadUtils;
88 import org.slf4j.Logger;
89 import org.slf4j.LoggerFactory;
90 import org.w3c.dom.DOMImplementation;
91 import org.w3c.dom.Document;
92 import org.w3c.dom.Element;
93 import org.w3c.dom.Node;
94 import org.w3c.dom.NodeList;
95
96 public class SCLScenegraph {
97
98         private static final Logger LOGGER = LoggerFactory.getLogger(SCLScenegraph.class);
99         
100         private static final String MAIN_SECTION = "main";
101         private static final String SELECTION_SECTION = "selection";
102         private static final String SELECTION_MASK_SECTION = "selectionMask";
103
104         private static final String[] ALL_SECTIONS = { MAIN_SECTION, SELECTION_SECTION, SELECTION_MASK_SECTION };
105
106         // Changed from 0.001 to 0.0001 to prevent creation of huge BufferedImage's when
107         // generating PDF from SVG. If SVG contains any transparency then Batik uses
108         // bitmap-rendering which remarkably slows things down
109         // See org.apache.batik.gvt.AbstractGraphicsNode.paint(Graphics2D) where decisions are made
110         // if AlphaComposite should be painted 
111         private static final String OPACITY = "0.0001";
112
113         @Deprecated
114         public static ICanvasSceneGraphProvider getICanvasSceneGraphProvider(Resource model, Resource diagram, String diagramRVI) throws DatabaseException, InterruptedException {
115                 ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(model, diagram, diagramRVI);
116                 return provider;
117         }
118         
119         @Deprecated
120         public static void disposeSceneGraphProvider(ICanvasSceneGraphProvider provider) {
121                 provider.dispose();
122         }
123
124     public static <T> T doWithICanvasSceneGraphProvider(Resource diagram, Function1<ICanvasSceneGraphProvider, T> func) throws DatabaseException {
125         return doWithICanvasSceneGraphProvider(diagram, (Function<ICanvasSceneGraphProvider, T>) provider -> func.apply(provider));
126     }
127
128     public static <T> T doWithICanvasSceneGraphProvider(Resource diagram, Function<ICanvasSceneGraphProvider, T> func) throws DatabaseException {
129         Tuple2 result = Simantics.getSession().syncRequest(new ResourceRead<Tuple2>(diagram) {
130
131             @Override
132             public Tuple2 perform(ReadGraph graph) throws DatabaseException {
133                 Resource indexRoot = graph.syncRequest(new IndexRoot(resource));
134                 String diagramRVI = Variables.getRVI(graph, resource);
135                 return new Tuple2(indexRoot, diagramRVI);
136             }
137         });
138         ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider((Resource) result.c0, diagram, (String) result.c1);
139         try {
140             return func.apply(provider);
141         } finally {
142             provider.dispose();
143         }
144     }
145
146     public static <T> T doWithCanvasContext(Resource diagram, Function1<ICanvasContext, T> func) throws DatabaseException {
147         return doWithCanvasContext(diagram, (Function<ICanvasContext, T>) canvasContext -> func.apply(canvasContext));
148     }
149
150     public static <T> T doWithCanvasContext(Resource diagram, Function<ICanvasContext, T> func) throws DatabaseException {
151         return doWithICanvasSceneGraphProvider(diagram, (Function<ICanvasSceneGraphProvider, T>) provider -> func.apply(provider.getCanvasContext()));
152     }
153
154         public static String getNodeTransform(ICanvasContext ctx, String name) {
155                 
156                 Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
157                 for (TextNode text : texts) {
158                         String nodeName = NodeUtil.getNodeName(text);
159                         if (nodeName.equals(name)) {
160                                 String transform = text.getTransform().toString();
161                                 return transform;
162                         }
163                 }
164                 return "No node found";
165         }
166         
167         public static String getNodeText(ICanvasContext ctx, String name) {
168                 
169                 Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
170                 for (TextNode text : texts) {
171                         String nodeName = NodeUtil.getNodeName(text);
172                         if (nodeName.equals(name)) {
173                                 String texti = text.getText();
174                                 return texti;
175                         }
176                 }
177                 return "No node found";
178         }
179         
180         public static String getNodeCount(ICanvasContext ctx) {
181                 G2DSceneGraph g2 = ctx.getSceneGraph();
182                 int amount = NodeUtil.countTreeNodes(g2);
183                 return "Node count: " + amount;
184         }
185         
186     public static String getAllNodes (ICanvasContext ctx) {
187         
188         Set<G2DSceneGraph> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DSceneGraph.class);
189         int amount = g2.size() +1;
190         return "All nodes: " + amount;
191     }
192     
193     public static String getBoundsNodes (ICanvasContext ctx) {
194         
195         Set<BoundsNode> bn = NodeUtil.collectNodes(ctx.getSceneGraph(), BoundsNode.class);
196         int amount = bn.size();
197         return "BoundsNodes: " + amount;
198     }
199     
200     public static String getBackgroundNodes (ICanvasContext ctx) {
201         
202         Set<BackgroundNode> bg = NodeUtil.collectNodes(ctx.getSceneGraph(), BackgroundNode.class);
203         int amount = bg.size();
204         return "BackgroundNodes: " + amount;
205     }
206     
207     public static String getDataNodes (ICanvasContext ctx) {
208         
209         Set<DataNode> dn = NodeUtil.collectNodes(ctx.getSceneGraph(), DataNode.class);
210         int amount = dn.size();
211         return "DataNodes: " + amount;
212     }
213     
214     public static String getNavigationNodes (ICanvasContext ctx) {
215         
216         Set<NavigationNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), NavigationNode.class);
217         int amount = g2.size();
218         return "NavigationNodes: " + amount;
219     }
220     
221     public static String getParentNodes (ICanvasContext ctx) {
222         
223         Set<G2DParentNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DParentNode.class);
224         int amount = g2.size();
225         return "ParentNodes: " + amount;
226     }
227     
228     public static String getDecorationNodes (ICanvasContext ctx) {
229         
230         Set<DecorationSVGNode> deco = NodeUtil.collectNodes(ctx.getSceneGraph(), DecorationSVGNode.class);
231         int amount = deco.size();
232         return "DecorationNodes: " + amount;
233     }
234     
235     public static String getSingleElementNodes (ICanvasContext ctx) {
236         
237         Set<SingleElementNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), SingleElementNode.class);
238         int amount = g2.size();
239         return "SingleElementNodes: " + amount;
240     }
241     
242     public static String getConnectionNodes (ICanvasContext ctx) {
243         
244         Set<ConnectionNode> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), ConnectionNode.class);
245         int amount = g2.size();
246         return "ConnectionNodes: " + amount;
247     }
248     
249     public static String getTextNodes (ICanvasContext ctx) {
250         
251         Set<TextNode> tn = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
252         Set<TextGridNode> tgn = NodeUtil.collectNodes(ctx.getSceneGraph(), TextGridNode.class);
253         int amount = tn.size() + tgn.size();
254         return "TextNodes: " + amount;
255     }
256     
257     public static String getItemNodes (ICanvasContext ctx) {
258         
259         Set<ItemNode> item = NodeUtil.collectNodes(ctx.getSceneGraph(), ItemNode.class);
260         int amount = item.size();
261         return "ItemNodes: " + amount;
262     }
263   
264     public static String editNodeText (ICanvasContext ctx, String module, String previous_value, String new_value) {
265                 
266         Set<TextNode> textGridNodes = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
267         for (TextNode modulenode : textGridNodes) {
268                 if (module.equals(modulenode.getText())) {
269                         //System.out.println("Module what we were looking for: " + module);
270                         //System.out.println("Modulenode: " + modulenode.getText());
271                         
272                         ParentNode<?> parentnode = modulenode.getParent();
273                         //System.out.println("Parentnode: " + parentnode);
274                         
275                         Collection<TextNode> textnodes = (Collection<TextNode>) parentnode.getNodes();
276                         for (TextNode valuenode : textnodes) {
277                                 if (previous_value.equals(valuenode.getText())) {
278                                         //System.out.println("Value what we were looking for: " + previous_value);
279                                         //System.out.println("Valuenode: " + valuenode.getText());
280                                         
281                                         //valuenode.setEditMode(true);
282                                         valuenode.activateEdit(0, null, ctx);
283                                         valuenode.setText(new_value);
284                                         valuenode.fireTextEditingEnded();
285                                         
286                                         //System.out.println("valuenode modified: " + valuenode);
287                                         return "Modified module " + module + " with value " + new_value;
288                                 }
289                         }
290                         return "Not found module : " + module;
291                 }
292         }
293         return "No nodes in scenegraph!";
294     }
295     
296     /**
297      * Returns background color of a canvasContext or null.
298      * @param ctx
299      * @return color in RGBA List<Integer> format, or null.
300      */
301     public static List<Integer> getBackgroundColor(ICanvasContext ctx) {
302         Color color = ctx.getDefaultHintContext().getHint(Hints.KEY_BACKGROUND_COLOR);
303         if (color == null)
304                 return null;
305         ArrayList<Integer> rgba = new ArrayList<>(4);
306         rgba.add(color.getRed());
307         rgba.add(color.getGreen());
308         rgba.add(color.getBlue());
309         rgba.add(color.getAlpha());
310         return rgba;
311     }
312
313     public static String sceneGraphTest (ICanvasContext ctx, String module, String value) {
314         
315         boolean module_founded = false;
316         boolean value_founded = false;
317         
318         Set<G2DSceneGraph> g2 = NodeUtil.collectNodes(ctx.getSceneGraph(), G2DSceneGraph.class);
319         System.out.println("Total amount of nodes: " + g2.size() + 1);
320         
321         Set<TextGridNode> grid = NodeUtil.collectNodes(ctx.getSceneGraph(), TextGridNode.class);
322         Integer textGridNodeAmount = grid.size();
323         System.out.println("Amount of TextGridNodes " + textGridNodeAmount);
324         
325         Set<TextNode> texts = NodeUtil.collectNodes(ctx.getSceneGraph(), TextNode.class);
326         Integer textNodeAmount = grid.size();
327         System.out.println("Amount of TextNodes " + textNodeAmount);
328
329         for (TextNode node : texts) {
330             if (module.equals(node.getText())) {
331                 module_founded = true;
332                 System.out.println("Correct module " + module + " founded.");
333             }
334             if (value.equals(node.getText())) {
335                 value_founded = true;
336                 System.out.println("Correct value " + value + " founded.");
337             }
338         }
339         
340         if (value_founded == true && module_founded == true) {
341                 return "Found both correct module " + module + " and value " + value;
342         }
343         if (value_founded == false && module_founded == true) {
344                 return "Found only correct module " + module + " but not value " + value;
345         }
346         if (value_founded == true && module_founded == false) {
347                 return "Found only correct value " + value + " but not module " + module;
348         }
349         else {
350                 return "Didn't found either module " + module + " or value " + value;
351         }
352     }
353     
354     public static boolean copyPaste (final ICanvasContext source_ctx, final ICanvasContext target_ctx, List<Resource> modules) throws DatabaseException {
355         
356         IDiagram idiagram = source_ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
357
358                 DataElementMap dem = idiagram.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class);
359                 if (dem != null) {
360                         final Collection<IElement> newSelection = new ArrayList<IElement>();
361                         for (Resource module : modules) {
362                                 IElement element = dem.getElement(idiagram, module);
363                                 if (element != null) {
364                                         newSelection.add(element);
365                                 } else {
366                                         throw new DatabaseException("Could not find IElement for " + element);
367                                 }
368                         }
369                         
370                         ThreadUtils.syncExec(source_ctx.getThreadAccess(), new Runnable() {
371                     @Override
372                     public void run() {
373                         if (source_ctx.isDisposed())
374                             return;
375                         Selection selection = source_ctx.getAtMostOneItemOfClass(Selection.class);
376                         if (selection != null) {
377                             // This prevents workbench selection from being left over.
378                             // Also prevents scene graph crap from being left on the screen.
379                             selection.setSelection(0, newSelection);
380                         }
381                                 CanvasUtils.sendCommand(source_ctx, Commands.COPY);
382                                 CanvasUtils.sendCommand(target_ctx, Commands.PASTE);
383                     }
384                 });
385                         
386                 //}
387                 
388                 while(source_ctx.getEventQueue().size() > 0) {
389                         try {
390                                 Thread.sleep(10);
391                         } catch (InterruptedException e) {
392                                 throw new DatabaseException(e);
393                         }
394                 }
395
396                 ThreadUtils.syncExec(source_ctx.getThreadAccess(), new Runnable() {
397             @Override
398             public void run() {
399             }
400         });             
401                                 
402                 }
403                 return true;
404     }
405
406     
407     static class UniqueIDGenerator extends SVGIDGenerator{
408         
409         String overallId;
410         public UniqueIDGenerator(String overallId) {
411                 super();
412                 this.overallId = overallId;
413         }
414         
415         @Override
416         public String generateID(String prefix) {
417                 return super.generateID(overallId+prefix);
418         }
419
420     }
421
422         static class Generator extends SVGGraphics2D {
423
424                 int elemLevel = 0;
425                 String newElementId = null;
426                 ArrayList<Element> elements = new ArrayList<Element>();
427
428                 public static final String svgNS = "http://www.w3.org/2000/svg";
429
430                 public Generator(SVGGeneratorContext ctx, boolean textAsShapes) {
431                         super(ctx, textAsShapes);
432                 }
433
434                 public Generator(Document document) {
435                         this(document,null);
436                 }
437                 public Generator(Document document, String id) {
438                         super(document);
439                         // prevent batik comment in each g-element
440                         getGeneratorContext().setComment(null);
441                         if (id != null)
442                                 getGeneratorContext().setIDGenerator(new UniqueIDGenerator(id));
443                 }
444
445                 @Override
446                 public Element getRoot() {
447                         Element root = super.getRoot();
448                         for(Element e : elements) {
449                                 root.appendChild(e);
450                         }
451                         return root;
452                 }
453
454                 @Override
455                 public void setRenderingHint(Key arg0, Object arg1) {
456                         if(G2DRenderingHints.KEY_BEGIN_ELEMENT == arg0) {
457                                 elemLevel++;
458                         }
459                         if(G2DRenderingHints.KEY_ELEMENT_ID == arg0) {
460                                 if(arg1 != null)
461                                         newElementId = arg1.toString();
462                                 else
463                                         newElementId = UUID.randomUUID().toString();
464                         }
465                         if(G2DRenderingHints.KEY_END_ELEMENT == arg0) {
466                                 elemLevel--;
467                                 if(elemLevel == 0) {
468                                         Element group = getDOMFactory().createElement(SVG_G_TAG);
469                                         //Element group = getDOMFactory().createElementNS(SVG_NAMESPACE_URI, SVG_G_TAG);
470                                         group.setAttributeNS(null, "id", newElementId);
471                                         group.setAttributeNS(null, "class", arg1.toString());
472                                         getRoot(group);
473                                         elements.add(group);
474                                 }
475                         }
476                         super.setRenderingHint(arg0, arg1);
477                 }
478
479         }
480
481         public static Element renderSVGNode(SVGGraphics2D svgGenerator, IG2DNode node) {
482
483                 try {
484
485                         svgGenerator.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
486                         svgGenerator.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
487                         svgGenerator.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
488
489                         node.render(svgGenerator);
490
491                 } catch (Throwable t) {
492                         LOGGER.error("Problems rendering scene graph to SVG", t);
493                 }
494
495                 return svgGenerator.getRoot();
496         }
497
498         public static String printSVGDocument(Element doce) {
499
500                 StringBuilder result = new StringBuilder();
501
502                 NodeList nl =  doce.getChildNodes();
503
504                 for(int i=0;i<nl.getLength();i++) {
505
506                         ByteArrayOutputStream os = new ByteArrayOutputStream();
507
508                         try {
509
510                                 TransformerFactory tf = TransformerFactory.newInstance();
511                                 Transformer transformer = tf.newTransformer();
512                                 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
513                                 transformer.setOutputProperty(OutputKeys.STANDALONE, "no");
514                                 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
515                                 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
516                                 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
517                                 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
518
519                                 transformer.transform(new DOMSource(nl.item(i)), 
520                                                 new StreamResult(new OutputStreamWriter(os, "UTF-8")));
521
522                                 os.flush();
523                                 os.close();
524
525                         } catch (Throwable t) {
526                                 LOGGER.error("Problems formatting SVGDocument to text.", t);
527                         }
528
529                         result.append(new String(os.toByteArray(), Charset.forName("UTF-8")));
530
531                 }
532
533                 return result.toString();
534
535         }
536
537         public static String renderSVG3(ICanvasContext ctx) {
538                 return renderSVG3(ctx, -1, -1);
539         }
540
541         public static String renderSVG3(ICanvasContext ctx, double width, double height) {
542         return renderSVG0(width, height, SWT.LEFT, SWT.TOP, ctx, p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2)));
543         }
544
545     /**
546      * @param graph
547      * @param component
548      * @throws DatabaseException
549      */
550     private static Object[] createURIBasedL0Identifier(ReadGraph graph, Resource component) throws DatabaseException {
551         String uri = graph.getPossibleURI(component);
552         int hashCode = uri.hashCode();
553         Random random = new Random(hashCode);
554         long l1 = random.nextLong();
555         long l2 = random.nextLong();
556         return new Object[] { l1, l2 };
557     }
558
559     /**
560      * Default no-op mapper
561      */
562     private static final Function1<Set<?>, Map<?, ?>> mapper = p0 -> p0.stream().collect(Collectors.toMap(p1 -> p1, p2 -> p2));
563
564     
565     /**
566      * Renders CanvasContext to SVG.
567      * @param ctx
568      * @param width Width of output image. Use -1 for autosize.
569      * @param height Height of output image. Use -1 for autosize.
570      * @param ax horizontal alignment. SWT.LEFT SWT.CENTER SWT.RIGHT are accepted values. Value is not used with autosize. 
571      * @param ay vertical alignment. SWT.TOP SWT.CENTER SWT.BOTTOM are accepted values. Value is not used with autosize.
572      * @return
573      */
574     public static String renderSVG(ICanvasContext ctx, double width, double height, int ax, int ay) {
575         return renderSVG0(width, height, ax, ay, ctx, mapper);
576     }
577     
578     public static String renderSVG(ICanvasContext ctx, double width, double height) {
579         return renderSVG(ctx,width,height, SWT.LEFT, SWT.TOP);
580     }
581
582     public static String renderSVG(ICanvasContext ctx) {
583         return renderSVG(ctx, -1, -1, SWT.LEFT, SWT.TOP);
584     }
585
586     public static String renderSVGMapIdentifiers(ICanvasContext ctx) {
587                 return renderSVGMapIdentifiers(ctx, -1, -1);
588     }
589     
590     /**
591      * Renders ICanvasContext into SVG by mapping the SVG id's into URI based
592      * GUID's
593      * 
594      * @param ctx
595      * @return
596      */
597     public static String renderSVGMapIdentifiers(ICanvasContext ctx, double width, double height) {
598         return renderSVG0(width, height, SWT.LEFT, SWT.TOP, ctx, new Function1<Set<?>, Map<?, ?>>() {
599
600             @Override
601             public Map<?, ?> apply(Set<?> p0) {
602                 try {
603                     return Simantics.getSession().syncRequest(new UnaryRead<Set<?>, Map<?, ?>>(p0) {
604
605                         @Override
606                         public Map<?, ?> perform(ReadGraph graph) throws DatabaseException {
607                             ModelingResources MOD = ModelingResources.getInstance(graph);
608                             DiagramResource DIA = DiagramResource.getInstance(graph);
609                             Layer0 L0 = Layer0.getInstance(graph);
610                             return parameter.stream().collect(Collectors.toMap(p -> p, p -> {
611                                 try {
612                                     if (p instanceof Resource) {
613                                         Resource element = (Resource) p;
614                                         if (graph.isInstanceOf(element, DIA.Element)) {
615                                             Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
616                                             if (component != null) {
617                                                 GUID identifier = graph.getPossibleRelatedValue(component, L0.identifier, GUID.BINDING);
618                                                 if (identifier != null) {
619                                                     return Arrays.toString(createURIBasedL0Identifier(graph, component));
620                                                 } else {
621                                                     LOGGER.error("Component {} does not have GUID identifier!", component);
622                                                 }
623                                             } else if (graph.isInstanceOf(element, DIA.Connection) || graph.isInstanceOf(element, DIA.Terminal)) {
624                                                 // Ok, lets create a hashcode for connections and terminals as they do not have identifiers 
625                                                 return graph.getURI(element).hashCode();
626                                             } else {
627                                                 // Ok, lets create a hashcode or either return empty string in cases where no ID is required
628                                                 if (graph.hasStatement(element, L0.HasName))
629                                                     return graph.getURI(element).hashCode();
630                                                 return "";
631                                             }
632                                         }
633                                     } else {
634                                         LOGGER.error("Parameter p {} is not resource but it is {}", p, p.getClass());
635                                     }
636                                     return p;
637                                 } catch (DatabaseException e) {
638                                     throw new RuntimeDatabaseException(e);
639                                 }
640                             }));
641                         }
642                     });
643                 } catch (DatabaseException e) {
644                     LOGGER.error("Could not apply mappings", e);
645                     throw new RuntimeDatabaseException(e);
646                 }
647             }
648         });
649         }
650         
651     static class RenderSVGContext {
652         
653         Map<String,StringBuilder> documents = new HashMap<>();
654         
655         public void append(String[] keys, String svgText) {
656                 for(String key : keys) append(key, svgText);
657         }
658
659         public void append(String key, String svgText) {
660                 StringBuilder builder = documents.get(key);
661                 if(builder == null) {
662                         builder = new StringBuilder();
663                         documents.put(key, builder);
664                 }
665                 builder.append(svgText);
666         }
667         
668         public void append(RenderSVGContext other) {
669                 for(String key : other.documents.keySet()) {
670                         append(key, other.get(key));
671                 }
672         }
673         
674         public String get(String key) {
675                 StringBuilder builder = documents.get(key);
676                 if(builder == null) return "";
677                 else return builder.toString();
678         }
679         
680     }
681     
682     
683     
684         private static String renderSVG0(double width, double height, int ax, int ay, ICanvasContext ctx, Function1<Set<?>, Map<?, ?>> mappingFunction) {
685
686                 // Get a DOMImplementation.
687                 DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
688
689                 // Create an instance of org.w3c.dom.Document.
690                 String svgNS = "http://www.w3.org/2000/svg";
691                 Document document = domImpl.createDocument(svgNS, "svg", null);
692
693                 // Create an instance of the SVG Generator.
694                 IDiagram d = ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
695                 Resource r = d.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);
696                 SVGGraphics2D svgGenerator;
697                 if (r != null)
698                         svgGenerator = new Generator(document,r.toString());
699                 else
700                         svgGenerator = new Generator(document);
701
702                 RenderSVGContext result = new RenderSVGContext();
703
704                 double[] matrix = new double[6];
705                 try {
706
707             Selection selection = ctx.getAtMostOneItemOfClass(Selection.class);
708             if (selection != null) {
709                 // This prevents workbench selection from being left over.
710                 // Also prevents scene graph crap from being left on the screen.
711                 selection.setSelection(0, d.getElements());
712             }
713
714                         G2DSceneGraph sg = ctx.getSceneGraph();
715                         sg.performCleanup();
716                         G2DParentNode root = (G2DParentNode) sg.getRootNode();
717
718                         // rtree is the actual content of the diagram
719                         RTreeNode rtree = NodeUtil.getNearestChildByClass(root, RTreeNode.class);
720                         Rectangle2D rtreeBounds = NodeUtil.getLocalBounds(rtree);
721
722                         // get the bounds of the content
723                         Rectangle2D content = rtreeBounds;
724                         
725 //                      int ax = SWT.LEFT;
726 //                      int ay = SWT.TOP;
727 //                      int ax = SWT.CENTER;
728 //                      int ay = SWT.CENTER;
729                         
730                         if (content != null) {
731                          // To account for dynamic padding of selection rectangles (5 units + stroke width)
732                     int offset = 6;
733                     
734                     double scale = width < 0 || height < 0 ? 1.0 : Math.min((width - 2*offset) / content.getWidth(), (height - 2*offset) / content.getHeight());
735                 
736                     AffineTransform tr = new AffineTransform();
737                     tr.translate(offset, offset);
738                     tr.scale(scale, scale);
739                     double dx = -content.getX();
740                     double dy = -content.getY();
741                     if (width > 0.0 && height > 0.0) {
742                             if (ax == SWT.LEFT) {
743                                 dx = -content.getX();
744                             } else if (ax == SWT.RIGHT) {
745                                 double t = ((width - 2*offset)/scale - content.getWidth());
746                                 dx = -content.getX() + t;
747                             } else {
748                                 double t = ((width - 2*offset)/scale - content.getWidth()) *0.5;
749                                 dx = -content.getX() + t;
750                             }
751                             if (ay == SWT.TOP) {
752                                 dy = -content.getY();
753                             } else if (ay == SWT.BOTTOM) {
754                                 double t = ((height - 2*offset)/scale - content.getHeight());
755                                 dy = -content.getY() + t;
756                             } else {
757                                 double t = ((height - 2*offset)/scale - content.getHeight()) * 0.5;
758                                 dy = -content.getY() + t;
759                             }
760                     }
761                             tr.translate(dx, dy);
762                             tr.getMatrix(matrix);
763                             svgGenerator.setSVGCanvasSize(new Dimension((int)Math.ceil(scale * content.getWidth()) + 2*offset, (int)Math.ceil(scale * content.getHeight()) + 2*offset));
764                         } else {
765                             svgGenerator.setSVGCanvasSize(new Dimension(100, 100));
766                         }
767                         //svgGenerator.translate(offset, offset);
768                         //svgGenerator.scale(scale, scale);
769                         // translate svgGenerator to the x and y coordinates of current content
770                         //svgGenerator.translate(-content.getX(), -content.getY());
771
772                         
773                         //svgGenerator.setClip(content);
774                         
775                         result.append(MAIN_SECTION, "<g class=\"symbols\">");
776                         result.append(SELECTION_SECTION, "<g class=\"selections\">");
777                         result.append(SELECTION_MASK_SECTION, "<g class=\"selectionMasks\">");
778
779                         KeyVisitor keyVisitor = new KeyVisitor();
780                         sg.accept(keyVisitor);
781                         
782                         Set<Object> keys = keyVisitor.getKeys();
783                         
784                         Map<?, ?> mappings = mappingFunction.apply(keys);
785
786                         IG2DNodeVisitor visitor = new PrintingVisitor(svgGenerator, result, mappings);
787                         
788                         sg.accept(visitor);
789
790                 } catch (Throwable t) {
791                         LOGGER.error("Problems rendering canvas context to SVG", t);
792                 }
793
794
795                 result.append(ALL_SECTIONS, "</g>");
796
797                 StringBuilder res = new StringBuilder();
798                 if (width > 0 && height > 0 ) {
799                         res.append("<svg width=\"" + width +"px\" height=\""+height+"px\" stroke=\"black\" xmlns=\"http://www.w3.org/2000/svg\">");
800                 } else {
801                         res.append("<svg width=\"100%\" height=\"100%\" stroke=\"black\" xmlns=\"http://www.w3.org/2000/svg\">");
802                 }
803                 res.append("<g transform=\"matrix(").append(matrix[0]).append(",").append(matrix[1]).append(",").append(matrix[2]).append(",").append(matrix[3]).append(",").append(matrix[4]).append(",").append(matrix[5]).append(")\">");
804                 res.append(result.get(MAIN_SECTION));
805                 res.append(result.get(SELECTION_SECTION));
806                 res.append(result.get(SELECTION_MASK_SECTION));
807                 res.append("</g>");
808                 res.append("</svg>");
809
810 //              System.err.println(" == FINAL RESULT == ");
811 //              System.err.println(res.toString());
812                 
813                 return res.toString();
814                 
815         }
816         
817         
818         
819         private static class KeyVisitor implements IG2DNodeVisitor {
820
821         private Set<Object> keys = new HashSet<>();
822
823         @Override
824         public void enter(IG2DNode node) {
825             if (node instanceof SingleElementNode) {
826                 Object key = ((SingleElementNode) node).getKey();
827                 if (key != null) {
828                     keys.add(key);
829                 }
830             }
831         }
832
833         @Override
834         public void leave(IG2DNode node) {
835             // Nothing to do
836         }
837
838         public Set<Object> getKeys() {
839             return keys;
840         }
841         }
842
843         private static class PrintingVisitor implements IG2DNodeVisitor {
844
845         int indent = 0;
846
847         HashMap<SingleElementNode,RenderSVGContext> senBuilders = new HashMap<>();
848
849         private RenderSVGContext result;
850         private SVGGraphics2D svgGenerator;
851
852         private Map<?, ?> mappings;
853
854         public PrintingVisitor(SVGGraphics2D svgGenerator, RenderSVGContext result, Map<?, ?> mappings) {
855             this.result = result;
856             this.mappings = mappings;
857             this.svgGenerator = svgGenerator;
858         }
859
860         private String getKey(SingleElementNode node) {
861             String key;
862                 if(node.getKey() != null) {
863                 if (mappings.containsKey(node.getKey())) {
864                     key = mappings.get(node.getKey()).toString();
865                     key = escape(key);
866                 } else {
867                     key = node.getKey().toString();
868                     key = escape(key);
869                 }
870                 } else {
871                         key = Long.toString(node.getId());
872                 }
873             return key;
874         }
875         
876         private String escape(String key) {
877                 // Keys may contain  '<' '>' characters, which causes errors in browser SVG handling.
878                 return org.apache.commons.lang.StringEscapeUtils.escapeHtml(key);
879         }
880         
881         private String removeElem(String xml, String elemStart) {
882                 // FIXME: This is rather nasty and error prone way of removing elements from XML string.
883                 // This only supports elements with /> end element tag. Elements ends with </elem name> are not supported!
884                 int start = xml.indexOf("<"+elemStart);
885                 if (start>=0) {
886                         int end = xml.indexOf(">",start);
887                         if (end >= 0) {
888                                 if (start > 0)
889                                         return xml.substring(0,start)+xml.substring(end+1);
890                                 else
891                                         return xml.substring(end+1);
892                         }
893                 }
894                 return null;
895         }
896
897         @Override
898         public void enter(IG2DNode node) {
899             
900                 RenderSVGContext parentBuilder = getParentBuilder(node);
901             
902             indent++;
903             if(node instanceof ConnectionNode) {
904                 
905                 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
906                     n.setIgnoreSelection(true);
907                 }
908
909                 String key = getKey((ConnectionNode) node);
910                 parentBuilder.append(MAIN_SECTION, "\n<g class=\"connection\" id=\"" + key + "\">");
911                 parentBuilder.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
912                 parentBuilder.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" opacity=\"" + OPACITY + "\" id=\"" + key + "\">");
913                 
914                 Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
915                 String svg = printSVGDocument(doc);
916                 parentBuilder.append(MAIN_SECTION, svg);
917
918                 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
919                     n.setIgnoreSelection(false);
920                 }
921
922                 doc = renderSVGNode(svgGenerator, (IG2DNode)node);
923                 svg = printSVGDocument(doc);
924                 parentBuilder.append(SELECTION_SECTION, svg);
925
926                 BasicStroke bs = new BasicStroke(10f);
927                 
928                 for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
929                     n.setDynamicStroke(bs);
930                 }
931
932                 doc = renderSVGNode(svgGenerator, (IG2DNode)node);
933                 svg = printSVGDocument(doc);
934                 parentBuilder.append(SELECTION_MASK_SECTION, svg);
935
936                 senBuilders.put((ConnectionNode)node, new RenderSVGContext());
937                 
938                 
939             } else if (isSelection0(node)) {
940                 
941                 SelectionNode n = (SelectionNode)node;
942                 SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
943                 if(parentSEN != null) {
944                     
945                         RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
946                     
947                     String key = getKey(parentSEN);
948                     n.setIgnore(false);
949                     Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
950                     n.setIgnore(true);
951                     String svg = printSVGDocument(doc);
952                     parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
953                     parentBuilder2.append(SELECTION_SECTION, svg);
954                     parentBuilder2.append(SELECTION_SECTION, "\n</g>");
955                     parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
956                     Rectangle2D rect = n.getRect();
957                     // NaN
958                     if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
959                             parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"" + OPACITY + "\"");
960                             parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
961                             parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
962                             parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
963                     }
964                     parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
965                    
966                 }
967             } else if (node instanceof SelectionNode) {
968
969                 Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
970                 String svg = printSVGDocument(doc);
971                 parentBuilder.append(MAIN_SECTION, "<g>");
972                 parentBuilder.append(MAIN_SECTION, svg);
973                 parentBuilder.append(MAIN_SECTION, "\n</g>");
974
975             } else if (node instanceof SVGNode) {
976                 SVGNode svg = (SVGNode)node;
977                 AffineTransform at = svg.getTransform();
978                 if (!at.isIdentity()) {
979                          if(at.getScaleX() == 1.0 && at.getScaleY() == 1.0 && at.getShearX() == 0.0 && at.getShearY() == 0.0) {
980                          String m = "translate(" + at.getTranslateX() + " " + at.getTranslateY() + ")";
981                          parentBuilder.append(MAIN_SECTION, "\n<g transform=\"" + m + "\">");
982                      } else {
983                          double[] ds = new double[6];
984                          at.getMatrix(ds);
985                          String m = "matrix(" + ds[0] + " " + ds[1] + " " + ds[2] + " " + ds[3] + " " + ds[4] + " " + ds[5] + ")";
986                          parentBuilder.append(MAIN_SECTION, "\n<g transform=\"" + m + "\">");
987                      }
988                 }
989                 String svgContent = svg.getSVGText();
990                 // SVGNode content may contain SVG/XML elements, which break browser compatibility
991                 int start = svgContent.indexOf("<svg");
992                 if (start >= 0)
993                         svgContent = svgContent.substring(start);
994                 else  {
995                         String s = removeElem(svgContent, "?xml");
996                         if (s != null) {
997                                 svgContent = "<g>"+s+"</g>";
998                                 s = removeElem(svgContent, "!DOCTYPE");
999                                 if (s != null)
1000                                         svgContent = s;
1001                         }
1002                 }
1003                 parentBuilder.append(MAIN_SECTION, svgContent);
1004                 if (!at.isIdentity()) {
1005                         parentBuilder.append(MAIN_SECTION, "\n</g>");
1006                 }
1007             } else if (node instanceof G2DParentNode) {
1008                 AffineTransform at = node.getTransform();
1009                 if(node instanceof SingleElementNode) {
1010                     SingleElementNode sen = (SingleElementNode)node;
1011                     String key = getKey(sen);
1012                     String typeClass = sen.getTypeClass();
1013                     String clazz = "definedElement";
1014                     if(typeClass != null && !typeClass.isEmpty())
1015                         clazz = clazz + " " + typeClass;
1016
1017                     parentBuilder.append(MAIN_SECTION, "\n<g class=\""+clazz+"\" id=\"" + key + "\">");
1018                     senBuilders.put(sen, new RenderSVGContext());
1019                 }
1020                 if(!at.isIdentity()) {
1021                     if(at.getScaleX() == 1.0 && at.getScaleY() == 1.0 && at.getShearX() == 0.0 && at.getShearY() == 0.0) {
1022                         String m = "translate(" + at.getTranslateX() + " " + at.getTranslateY() + ")";
1023                         parentBuilder.append(ALL_SECTIONS, "\n<g transform=\"" + m + "\">");
1024                     } else {
1025                         double[] ds = new double[6];
1026                         at.getMatrix(ds);
1027                         String m = "matrix(" + ds[0] + " " + ds[1] + " " + ds[2] + " " + ds[3] + " " + ds[4] + " " + ds[5] + ")";
1028                         parentBuilder.append(ALL_SECTIONS, "\n<g transform=\"" + m + "\">");
1029                     }
1030                 }
1031             } else if (node instanceof TextNode) {
1032                  TextNode text = (TextNode)node;
1033                                 
1034                  SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
1035                  if(parentSEN != null) {
1036                          
1037                          text.setShowSelection(false);
1038                          Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1039                      String svg = printSVGDocument(doc);
1040                      parentBuilder.append(MAIN_SECTION, svg);
1041                      
1042                          RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
1043                      
1044                      String key = getKey(parentSEN);
1045                      text.setShowSelection(true);
1046                      doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1047                      svg = printSVGDocument(doc);
1048                      
1049                      parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
1050                      parentBuilder2.append(SELECTION_SECTION, svg);
1051                      parentBuilder2.append(SELECTION_SECTION, "\n</g>");
1052                      parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
1053                      Rectangle2D rect = text.getBounds();
1054                      // NaN
1055                      if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
1056                             parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"" + OPACITY + "\"");
1057                             parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
1058                             parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
1059                             parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
1060                      }
1061                      parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
1062                     
1063                  }
1064             } else if (!(node instanceof RouteGraphNode) && !(node instanceof LinkNode)){
1065                 try {
1066                         Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1067                         NodeList gList = doc.getElementsByTagName("g");
1068                         if (gList.getLength() == 0)
1069                                 return;
1070                         boolean hasContent = false;
1071                         for (int i = 0; i < gList.getLength(); i++) {
1072                             Node gNode = gList.item(i);
1073                             if (gNode.hasChildNodes()) {
1074                                 hasContent = true;
1075                                 break;
1076                             }
1077                         }
1078                         if (!hasContent)
1079                             return;
1080                         String svg = printSVGDocument(doc);
1081                         if (node instanceof SelectionShapeNode) {
1082                                 SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
1083                         if(parentSEN != null) {
1084                                  String key = getKey(parentSEN);
1085                                  RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
1086                                  parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
1087                              parentBuilder2.append(SELECTION_SECTION, svg);
1088                              parentBuilder2.append(SELECTION_SECTION, "\n</g>");
1089                              
1090                              parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
1091                              Rectangle2D rect = node.getBounds();
1092                              // NaN
1093                              if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
1094                                     parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"" + OPACITY + "\"");
1095                                     parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
1096                                     parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
1097                                     parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
1098                              }
1099                              parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
1100                         }
1101                         } else {
1102                                 parentBuilder.append(MAIN_SECTION, "<g class=\"" +node.getSimpleClassName() +"\">");
1103                                 parentBuilder.append(MAIN_SECTION, svg);
1104                                 parentBuilder.append(MAIN_SECTION, "\n</g>");
1105                         }
1106                 } catch (Exception e) {
1107                         // TODO: There are nodes that do not behave well when rendered to SVG. For backwards compatibility, we don't handle the exceptions.
1108                 }
1109             }
1110
1111             //enters.put(node, b.length());
1112
1113         }
1114         
1115         private boolean isSelection0(IG2DNode node) {
1116             
1117             if(node instanceof SelectionNode) {
1118                 SelectionNode sn = (SelectionNode)node;
1119                 return sn.getSelectionId() == 0;
1120             } else {
1121                 return false;
1122             }
1123             
1124         }
1125         
1126         private RenderSVGContext getParentBuilder(IG2DNode node) {
1127             
1128             INode parentSEN = NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
1129             if(parentSEN instanceof G2DSceneGraph) return result;
1130
1131             RenderSVGContext parentBuilder = senBuilders.get(parentSEN);
1132             if(parentBuilder == null) return result;
1133             
1134             return parentBuilder;
1135             
1136         }
1137
1138         @Override
1139         public void leave(IG2DNode node) {
1140
1141             if( node instanceof SVGNode) {
1142                 // We are done
1143             } else if (node instanceof ConnectionNode) {
1144                 RenderSVGContext parentBuilder = getParentBuilder(node);
1145                 SingleElementNode sen = (SingleElementNode)node;
1146                 RenderSVGContext b = senBuilders.get(sen);
1147                 String content = b.get(MAIN_SECTION);
1148                 if(content.isEmpty()) {
1149 //                      Handling connection the same way as SingleElementNode would draw connection twice..
1150 //                      if(sen.getKey() != null) {
1151 //
1152 //                              for(SelectionNode n : NodeUtil.collectNodes(node, SelectionNode.class)) {
1153 //                                      n.setIgnore(true);
1154 //                              }
1155 //
1156 //                              Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1157 //                              String svg = printSVGDocument(doc);
1158 //                              parentBuilder.append(MAIN_SECTION, svg);
1159 //                      }
1160                 } else {
1161                         parentBuilder.append(b);
1162                 }
1163                 parentBuilder.append(SELECTION_MASK_SECTION, "\n</g>");
1164                 parentBuilder.append(SELECTION_SECTION, "\n</g>");
1165                 parentBuilder.append(MAIN_SECTION, "\n</g>");
1166                 
1167             } else if (node instanceof G2DParentNode) {
1168                 
1169                 RenderSVGContext parentBuilder = getParentBuilder(node);
1170                 
1171                 if(node instanceof SingleElementNode) {
1172                         SingleElementNode sen = (SingleElementNode)node;
1173                         RenderSVGContext b = senBuilders.get(sen);
1174                         String content = b.get(MAIN_SECTION);
1175                         if(content.isEmpty()) {
1176                                 if(sen.getKey() != null) {
1177
1178                                         for(SelectionNode n : NodeUtil.collectNodes(node, SelectionNode.class)) {
1179                                                 n.setIgnore(true);
1180                                         }
1181
1182                                         Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
1183                                         String svg = printSVGDocument(doc);
1184                                         parentBuilder.append(MAIN_SECTION, svg);
1185                                 }
1186                         } else {
1187                                 parentBuilder.append(b);
1188                         }
1189                 }
1190                 
1191                 AffineTransform at = node.getTransform();
1192                 if(!at.isIdentity()) {
1193                     parentBuilder.append(ALL_SECTIONS, "</g>");
1194                 }
1195                 if(node instanceof SingleElementNode) {
1196                     SingleElementNode sen = (SingleElementNode)node;
1197                     //if(sen.getKey() != null) {
1198                         parentBuilder.append(MAIN_SECTION, "</g>");
1199                     //}
1200                 }
1201             }
1202             indent --;
1203         }
1204         }
1205 }