+ @Override
+ public Map<?, ?> apply(Set<?> p0) {
+ try {
+ return Simantics.getSession().syncRequest(new UnaryRead<Set<?>, Map<?, ?>>(p0) {
+
+ @Override
+ public Map<?, ?> perform(ReadGraph graph) throws DatabaseException {
+ ModelingResources MOD = ModelingResources.getInstance(graph);
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ Layer0 L0 = Layer0.getInstance(graph);
+ return parameter.stream().collect(Collectors.toMap(p -> p, p -> {
+ try {
+ if (p instanceof Resource) {
+ Resource element = (Resource) p;
+ if (graph.isInstanceOf(element, DIA.Element)) {
+ Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);
+ if (component != null) {
+ GUID identifier = graph.getPossibleRelatedValue(component, L0.identifier, GUID.BINDING);
+ if (identifier != null) {
+ return Arrays.toString(createURIBasedL0Identifier(graph, component));
+ } else {
+ LOGGER.error("Component {} does not have GUID identifier!", component);
+ }
+ } else if (graph.isInstanceOf(element, DIA.Connection) || graph.isInstanceOf(element, DIA.Terminal)) {
+ // Ok, lets create a hashcode for connections and terminals as they do not have identifiers
+ return graph.getURI(element).hashCode();
+ } else {
+ // Ok, lets create a hashcode or either return empty string in cases where no ID is required
+ if (graph.hasStatement(element, L0.HasName))
+ return graph.getURI(element).hashCode();
+ return "";
+ }
+ }
+ } else {
+ LOGGER.error("Parameter p {} is not resource but it is {}", p, p.getClass());
+ }
+ return p;
+ } catch (DatabaseException e) {
+ throw new RuntimeDatabaseException(e);
+ }
+ }));
+ }
+ });
+ } catch (DatabaseException e) {
+ LOGGER.error("Could not apply mappings", e);
+ throw new RuntimeDatabaseException(e);
+ }
+ }
+ });
+ }
+
+ static class RenderSVGContext {
+
+ Map<String,StringBuilder> documents = new HashMap<>();
+
+ public void append(String[] keys, String svgText) {
+ for(String key : keys) append(key, svgText);
+ }
+
+ public void append(String key, String svgText) {
+ StringBuilder builder = documents.get(key);
+ if(builder == null) {
+ builder = new StringBuilder();
+ documents.put(key, builder);
+ }
+ builder.append(svgText);
+ }
+
+ public void append(RenderSVGContext other) {
+ for(String key : other.documents.keySet()) {
+ append(key, other.get(key));
+ }
+ }
+
+ public String get(String key) {
+ StringBuilder builder = documents.get(key);
+ if(builder == null) return "";
+ else return builder.toString();
+ }
+
+ }
+
+ private static String renderSVG0(double width, double height, ICanvasContext ctx, Function1<Set<?>, Map<?, ?>> mappingFunction) {
+
+ // Get a DOMImplementation.
+ DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
+
+ // Create an instance of org.w3c.dom.Document.
+ String svgNS = "http://www.w3.org/2000/svg";
+ Document document = domImpl.createDocument(svgNS, "svg", null);
+
+ // Create an instance of the SVG Generator.
+ SVGGraphics2D svgGenerator = new Generator(document);
+
+ RenderSVGContext result = new RenderSVGContext();
+
+ double[] matrix = new double[6];
+ try {
+
+ Selection selection = ctx.getAtMostOneItemOfClass(Selection.class);
+ if (selection != null) {
+ // This prevents workbench selection from being left over.
+ // Also prevents scene graph crap from being left on the screen.
+ IDiagram d = ctx.getDefaultHintContext().getHint(DiagramHints.KEY_DIAGRAM);
+ selection.setSelection(0, d.getElements());
+ }
+
+ G2DSceneGraph sg = ctx.getSceneGraph();
+ sg.performCleanup();
+ G2DParentNode root = (G2DParentNode) sg.getRootNode();
+
+ // rtree is the actual content of the diagram
+ RTreeNode rtree = NodeUtil.getNearestChildByClass(root, RTreeNode.class);
+ Rectangle2D rtreeBounds = NodeUtil.getLocalBounds(rtree);
+
+ // get the bounds of the content
+ Rectangle2D content = rtreeBounds;
+
+
+ if (content != null) {
+ // To account for dynamic padding of selection rectangles (5 units + stroke width)
+ int offset = 6;
+
+ double scale = width < 0 || height < 0 ? 1.0 : Math.min((width - 2*offset) / content.getWidth(), (height - 2*offset) / content.getHeight());
+
+ AffineTransform tr = new AffineTransform();
+ tr.translate(offset, offset);
+ tr.scale(scale, scale);
+ tr.translate(-content.getX(), -content.getY());
+ tr.getMatrix(matrix);
+ svgGenerator.setSVGCanvasSize(new Dimension((int)Math.ceil(scale * content.getWidth()) + 2*offset, (int)Math.ceil(scale * content.getHeight()) + 2*offset));
+ } else {
+ svgGenerator.setSVGCanvasSize(new Dimension(100, 100));
+ }
+ //svgGenerator.translate(offset, offset);
+ //svgGenerator.scale(scale, scale);
+ // translate svgGenerator to the x and y coordinates of current content
+ //svgGenerator.translate(-content.getX(), -content.getY());
+
+
+ //svgGenerator.setClip(content);
+
+ result.append(MAIN_SECTION, "<g class=\"symbols\">");
+ result.append(SELECTION_SECTION, "<g class=\"selections\">");
+ result.append(SELECTION_MASK_SECTION, "<g class=\"selectionMasks\">");
+
+ KeyVisitor keyVisitor = new KeyVisitor();
+ sg.accept(keyVisitor);
+
+ Set<Object> keys = keyVisitor.getKeys();
+
+ Map<?, ?> mappings = mappingFunction.apply(keys);
+
+ IG2DNodeVisitor visitor = new PrintingVisitor(svgGenerator, result, mappings);
+
+ sg.accept(visitor);
+
+ } catch (Throwable t) {
+ LOGGER.error("Problems rendering canvas context to SVG", t);
+ }
+
+
+ result.append(ALL_SECTIONS, "</g>");
+
+ StringBuilder res = new StringBuilder();
+ if (width > 0 && height > 0 ) {
+ res.append("<svg width=\"" + width +"px\" height=\""+height+"px\" stroke=\"black\" xmlns=\"http://www.w3.org/2000/svg\">");
+ } else {
+ res.append("<svg width=\"100%\" height=\"100%\" stroke=\"black\" xmlns=\"http://www.w3.org/2000/svg\">");
+ }
+ 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(")\">");
+ res.append(result.get(MAIN_SECTION));
+ res.append(result.get(SELECTION_SECTION));
+ res.append(result.get(SELECTION_MASK_SECTION));
+ res.append("</g>");
+ res.append("</svg>");
+
+// System.err.println(" == FINAL RESULT == ");
+// System.err.println(res.toString());
+
+ return res.toString();
+
+ }
+
+
+
+ private static class KeyVisitor implements IG2DNodeVisitor {
+
+ private Set<Object> keys = new HashSet<>();
+
+ @Override
+ public void enter(IG2DNode node) {
+ if (node instanceof SingleElementNode) {
+ Object key = ((SingleElementNode) node).getKey();
+ if (key != null) {
+ keys.add(key);
+ }
+ }
+ }
+
+ @Override
+ public void leave(IG2DNode node) {
+ // Nothing to do
+ }
+
+ public Set<Object> getKeys() {
+ return keys;
+ }
+ }
+
+ private static class PrintingVisitor implements IG2DNodeVisitor {
+
+ int indent = 0;
+
+ HashMap<SingleElementNode,RenderSVGContext> senBuilders = new HashMap<>();
+
+ private RenderSVGContext result;
+ private SVGGraphics2D svgGenerator;
+
+ private Map<?, ?> mappings;
+
+ public PrintingVisitor(SVGGraphics2D svgGenerator, RenderSVGContext result, Map<?, ?> mappings) {
+ this.result = result;
+ this.mappings = mappings;
+ this.svgGenerator = svgGenerator;
+ }
+
+ private String getKey(SingleElementNode node) {
+ String key;
+ if(node.getKey() != null) {
+ if (mappings.containsKey(node.getKey())) {
+ key = mappings.get(node.getKey()).toString();
+ key = escape(key);
+ } else {
+ key = node.getKey().toString();
+ key = escape(key);
+ }
+ } else {
+ key = Long.toString(node.getId());
+ }
+ return key;
+ }
+
+ private String escape(String key) {
+ // Keys may contain '<' '>' characters, which causes errors in browser SVG handling.
+ return org.apache.commons.lang.StringEscapeUtils.escapeHtml(key);
+ }
+
+ private String removeElem(String xml, String elemStart) {
+ // FIXME: This is rather nasty and error prone way of removing elements from XML string.
+ // This only supports elements with /> end element tag. Elements ends with </elem name> are not supported!
+ int start = xml.indexOf("<"+elemStart);
+ if (start>=0) {
+ int end = xml.indexOf(">",start);
+ if (end >= 0) {
+ if (start > 0)
+ return xml.substring(0,start)+xml.substring(end+1);
+ else
+ return xml.substring(end+1);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void enter(IG2DNode node) {
+
+ RenderSVGContext parentBuilder = getParentBuilder(node);
+
+ indent++;
+ if(node instanceof ConnectionNode) {
+
+ for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
+ n.setIgnoreSelection(true);
+ }
+
+ String key = getKey((ConnectionNode) node);
+ parentBuilder.append(MAIN_SECTION, "\n<g class=\"connection\" id=\"" + key + "\">");
+ parentBuilder.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
+ parentBuilder.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" opacity=\"" + OPACITY + "\" id=\"" + key + "\">");
+
+ Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
+ String svg = printSVGDocument(doc);
+ parentBuilder.append(MAIN_SECTION, svg);
+
+ for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
+ n.setIgnoreSelection(false);
+ }
+
+ doc = renderSVGNode(svgGenerator, (IG2DNode)node);
+ svg = printSVGDocument(doc);
+ parentBuilder.append(SELECTION_SECTION, svg);
+
+ BasicStroke bs = new BasicStroke(10f);
+
+ for(RouteGraphNode n : NodeUtil.collectNodes(node, RouteGraphNode.class)) {
+ n.setDynamicStroke(bs);
+ }
+
+ doc = renderSVGNode(svgGenerator, (IG2DNode)node);
+ svg = printSVGDocument(doc);
+ parentBuilder.append(SELECTION_MASK_SECTION, svg);
+
+ parentBuilder.append(SELECTION_MASK_SECTION, "\n</g>");
+ parentBuilder.append(SELECTION_SECTION, "\n</g>");
+ parentBuilder.append(MAIN_SECTION, "\n</g>");
+
+ } else if (isSelection0(node)) {
+
+ SelectionNode n = (SelectionNode)node;
+ SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
+ if(parentSEN != null) {
+
+ RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
+
+ String key = getKey(parentSEN);
+ n.setIgnore(false);
+ Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
+ n.setIgnore(true);
+ String svg = printSVGDocument(doc);
+ parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
+ parentBuilder2.append(SELECTION_SECTION, svg);
+ parentBuilder2.append(SELECTION_SECTION, "\n</g>");
+ parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
+ Rectangle2D rect = n.getRect();
+ // NaN
+ if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
+ parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"" + OPACITY + "\"");
+ parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
+ parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
+ parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
+ }
+ parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
+
+ }
+ } else if (node instanceof SelectionNode) {
+
+ Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
+ String svg = printSVGDocument(doc);
+ parentBuilder.append(MAIN_SECTION, "<g>");
+ parentBuilder.append(MAIN_SECTION, svg);
+ parentBuilder.append(MAIN_SECTION, "\n</g>");
+
+ } else if (node instanceof SVGNode) {
+ SVGNode svg = (SVGNode)node;
+ AffineTransform at = svg.getTransform();
+ if (!at.isIdentity()) {
+ if(at.getScaleX() == 1.0 && at.getScaleY() == 1.0 && at.getShearX() == 0.0 && at.getShearY() == 0.0) {
+ String m = "translate(" + at.getTranslateX() + " " + at.getTranslateY() + ")";
+ parentBuilder.append(MAIN_SECTION, "\n<g transform=\"" + m + "\">");
+ } else {
+ double[] ds = new double[6];
+ at.getMatrix(ds);
+ String m = "matrix(" + ds[0] + " " + ds[1] + " " + ds[2] + " " + ds[3] + " " + ds[4] + " " + ds[5] + ")";
+ parentBuilder.append(MAIN_SECTION, "\n<g transform=\"" + m + "\">");
+ }
+ }
+ String svgContent = svg.getSVGText();
+ // SVGNode content may contain SVG/XML elements, which break browser compatibility
+ int start = svgContent.indexOf("<svg");
+ if (start >= 0)
+ svgContent = svgContent.substring(start);
+ else {
+ String s = removeElem(svgContent, "?xml");
+ if (s != null) {
+ svgContent = "<g>"+s+"</g>";
+ s = removeElem(svgContent, "!DOCTYPE");
+ if (s != null)
+ svgContent = s;
+ }
+ }
+ parentBuilder.append(MAIN_SECTION, svgContent);
+ if (!at.isIdentity()) {
+ parentBuilder.append(MAIN_SECTION, "\n</g>");
+ }
+ } else if (node instanceof G2DParentNode) {
+ AffineTransform at = node.getTransform();
+ if(node instanceof SingleElementNode) {
+ SingleElementNode sen = (SingleElementNode)node;
+ String key = getKey(sen);
+ String typeClass = sen.getTypeClass();
+ String clazz = "definedElement";
+ if(typeClass != null && !typeClass.isEmpty())
+ clazz = clazz + " " + typeClass;
+
+ parentBuilder.append(MAIN_SECTION, "\n<g class=\""+clazz+"\" id=\"" + key + "\">");
+ senBuilders.put(sen, new RenderSVGContext());
+ }
+ if(!at.isIdentity()) {
+ if(at.getScaleX() == 1.0 && at.getScaleY() == 1.0 && at.getShearX() == 0.0 && at.getShearY() == 0.0) {
+ String m = "translate(" + at.getTranslateX() + " " + at.getTranslateY() + ")";
+ parentBuilder.append(ALL_SECTIONS, "\n<g transform=\"" + m + "\">");
+ } else {
+ double[] ds = new double[6];
+ at.getMatrix(ds);
+ String m = "matrix(" + ds[0] + " " + ds[1] + " " + ds[2] + " " + ds[3] + " " + ds[4] + " " + ds[5] + ")";
+ parentBuilder.append(ALL_SECTIONS, "\n<g transform=\"" + m + "\">");
+ }
+ }
+ } else if (node instanceof TextNode) {
+ TextNode text = (TextNode)node;
+
+ SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
+ if(parentSEN != null) {
+
+ text.setShowSelection(false);
+ Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
+ String svg = printSVGDocument(doc);
+ parentBuilder.append(MAIN_SECTION, svg);
+
+ RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
+
+ String key = getKey(parentSEN);
+ text.setShowSelection(true);
+ doc = renderSVGNode(svgGenerator, (IG2DNode)node);
+ svg = printSVGDocument(doc);
+
+ parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
+ parentBuilder2.append(SELECTION_SECTION, svg);
+ parentBuilder2.append(SELECTION_SECTION, "\n</g>");
+ parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
+ Rectangle2D rect = text.getBounds();
+ // NaN
+ if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
+ parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"" + OPACITY + "\"");
+ parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
+ parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
+ parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
+ }
+ parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
+
+ }
+ } else if (!(node instanceof RouteGraphNode) && !(node instanceof LinkNode)){
+ try {
+ Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
+ NodeList gList = doc.getElementsByTagName("g");
+ if (gList.getLength() == 0)
+ return;
+ boolean hasContent = false;
+ for (int i = 0; i < gList.getLength(); i++) {
+ Node gNode = gList.item(i);
+ if (gNode.hasChildNodes()) {
+ hasContent = true;
+ break;
+ }
+ }
+ if (!hasContent)
+ return;
+ String svg = printSVGDocument(doc);
+ if (node instanceof SelectionShapeNode) {
+ SingleElementNode parentSEN = (SingleElementNode)NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
+ if(parentSEN != null) {
+ String key = getKey(parentSEN);
+ RenderSVGContext parentBuilder2 = getParentBuilder(parentSEN);
+ parentBuilder2.append(SELECTION_SECTION, "\n<g style=\"visibility:hidden\" class=\"selection\" id=\"" + key + "\">");
+ parentBuilder2.append(SELECTION_SECTION, svg);
+ parentBuilder2.append(SELECTION_SECTION, "\n</g>");
+
+ parentBuilder2.append(SELECTION_MASK_SECTION, "\n<g class=\"selectionMask\" id=\"" + key /*+ "\" transform=\"" + matrixString + "\"*/+ "\">");
+ Rectangle2D rect = node.getBounds();
+ // NaN
+ if(rect.getHeight() == rect.getHeight() && rect.getWidth() == rect.getWidth()) {
+ parentBuilder2.append(SELECTION_MASK_SECTION,"<rect style=\"fill:#fff\" opacity=\"" + OPACITY + "\"");
+ parentBuilder2.append(SELECTION_MASK_SECTION," x=\"" + rect.getX() + "\" y=\"" + rect.getY() + "\"");
+ parentBuilder2.append(SELECTION_MASK_SECTION," width=\"" + rect.getWidth() + "\" height=\"" + rect.getHeight() + "\"");
+ parentBuilder2.append(SELECTION_MASK_SECTION,"></rect>");
+ }
+ parentBuilder2.append(SELECTION_MASK_SECTION,"\n</g>");
+ }
+ } else {
+ parentBuilder.append(MAIN_SECTION, "<g class=\"" +node.getSimpleClassName() +"\">");
+ parentBuilder.append(MAIN_SECTION, svg);
+ parentBuilder.append(MAIN_SECTION, "\n</g>");
+ }
+ } catch (Exception e) {
+ // TODO: There are nodes that do not behave well when rendered to SVG. For backwards compatibility, we don't handle the exceptions.
+ }
+ }
+
+ //enters.put(node, b.length());
+
+ }
+
+ private boolean isSelection0(IG2DNode node) {
+
+ if(node instanceof SelectionNode) {
+ SelectionNode sn = (SelectionNode)node;
+ return sn.getSelectionId() == 0;
+ } else {
+ return false;
+ }
+
+ }
+
+ private RenderSVGContext getParentBuilder(IG2DNode node) {
+
+ INode parentSEN = NodeUtil.getNearestParentOfType(node, SingleElementNode.class);
+ if(parentSEN instanceof G2DSceneGraph) return result;
+
+ RenderSVGContext parentBuilder = senBuilders.get(parentSEN);
+ if(parentBuilder == null) return result;
+
+ return parentBuilder;
+
+ }
+
+ @Override
+ public void leave(IG2DNode node) {
+
+ if(node instanceof ConnectionNode || node instanceof SVGNode) {
+ // We are done
+ } else if (node instanceof G2DParentNode) {
+
+ RenderSVGContext parentBuilder = getParentBuilder(node);
+
+ if(node instanceof SingleElementNode) {
+ SingleElementNode sen = (SingleElementNode)node;
+ RenderSVGContext b = senBuilders.get(sen);
+ String content = b.get(MAIN_SECTION);
+ if(content.isEmpty()) {
+ if(sen.getKey() != null) {
+
+ for(SelectionNode n : NodeUtil.collectNodes(node, SelectionNode.class)) {
+ n.setIgnore(true);
+ }
+
+ Element doc = renderSVGNode(svgGenerator, (IG2DNode)node);
+ String svg = printSVGDocument(doc);
+ parentBuilder.append(MAIN_SECTION, svg);
+ }
+ } else {
+ parentBuilder.append(b);
+ }
+ }
+
+ AffineTransform at = node.getTransform();
+ if(!at.isIdentity()) {
+ parentBuilder.append(ALL_SECTIONS, "</g>");
+ }
+ if(node instanceof SingleElementNode) {
+ SingleElementNode sen = (SingleElementNode)node;
+ //if(sen.getKey() != null) {
+ parentBuilder.append(MAIN_SECTION, "</g>");
+ //}
+ }
+ }
+ indent --;
+ }
+ }