Skip to content

Commit 21bbbc4

Browse files
committed
add SvgElementFinder
Signed-off-by: Stefan Niederhauser <[email protected]>
1 parent 7636a7f commit 21bbbc4

File tree

2 files changed

+212
-0
lines changed

2 files changed

+212
-0
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright © 2015 Stefan Niederhauser ([email protected])
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package guru.nidi.graphviz.engine;
17+
18+
import guru.nidi.graphviz.model.Graph;
19+
import guru.nidi.graphviz.model.Link;
20+
import org.w3c.dom.Document;
21+
import org.w3c.dom.Node;
22+
import org.xml.sax.InputSource;
23+
import org.xml.sax.SAXException;
24+
25+
import javax.annotation.Nullable;
26+
import javax.xml.namespace.QName;
27+
import javax.xml.parsers.*;
28+
import javax.xml.transform.TransformerException;
29+
import javax.xml.transform.TransformerFactory;
30+
import javax.xml.transform.dom.DOMSource;
31+
import javax.xml.transform.stream.StreamResult;
32+
import javax.xml.xpath.*;
33+
import java.io.*;
34+
35+
public class SvgElementFinder {
36+
private static final DocumentBuilderFactory FACTORY = DocumentBuilderFactory.newInstance();
37+
private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
38+
private static final VariableResolver RESOLVER = new VariableResolver();
39+
private static final XPath X_PATH = xPath(RESOLVER);
40+
private static final XPathExpression EXPR_G = pathExpression(X_PATH, "//g");
41+
private static final XPathExpression EXPR_TITLE = pathExpression(X_PATH, "//title[text()=$var]");
42+
private static final XPathExpression EXPR_TITLE_OR = pathExpression(X_PATH, "//title[text()=$var or text()=$alt]");
43+
private final Document doc;
44+
45+
public SvgElementFinder(String svg) {
46+
try {
47+
doc = builder().parse(new InputSource(new StringReader(svg)));
48+
} catch (SAXException | IOException e) {
49+
throw new GraphvizException("Could not read SVG", e);
50+
}
51+
}
52+
53+
public String getSvg() {
54+
final StringWriter sw = new StringWriter();
55+
try {
56+
TRANSFORMER_FACTORY.newTransformer().transform(new DOMSource(doc), new StreamResult(sw));
57+
return sw.toString();
58+
} catch (TransformerException e) {
59+
throw new AssertionError("Could not generate string from DOM", e);
60+
}
61+
}
62+
63+
public Node findGraph() {
64+
return nodeExpr(EXPR_G, "");
65+
}
66+
67+
@Nullable
68+
public Node findNode(guru.nidi.graphviz.model.Node node) {
69+
return findNode(node.name().toString());
70+
}
71+
72+
@Nullable
73+
public Node findNode(String name) {
74+
final Node title = nodeExpr(EXPR_TITLE, name);
75+
return title == null ? null : title.getParentNode();
76+
}
77+
78+
@Nullable
79+
public Node findLink(Link link) {
80+
return findLink(link.from().name().toString(), link.to().name().toString());
81+
}
82+
83+
@Nullable
84+
public Node findLink(String from, String to) {
85+
final Node title = nodeExpr(EXPR_TITLE_OR, from + "--" + to);
86+
return title == null ? null : title.getParentNode();
87+
}
88+
89+
@Nullable
90+
public Node findCluster(Graph cluster) {
91+
return findCluster(cluster.name().toString());
92+
}
93+
94+
@Nullable
95+
public Node findCluster(String name) {
96+
final Node title = nodeExpr(EXPR_TITLE, "cluster_" + name);
97+
return title == null ? null : title.getParentNode();
98+
}
99+
100+
@Nullable
101+
private Node nodeExpr(XPathExpression expr, String var) {
102+
RESOLVER.set(var);
103+
try {
104+
return (Node) expr.evaluate(doc, XPathConstants.NODE);
105+
} catch (XPathExpressionException e) {
106+
throw new AssertionError("Could not execute XPath", e);
107+
}
108+
}
109+
110+
private DocumentBuilder builder() {
111+
try {
112+
return FACTORY.newDocumentBuilder();
113+
} catch (ParserConfigurationException e) {
114+
throw new RuntimeException("Could not initialize DOM", e);
115+
}
116+
}
117+
118+
private static XPath xPath(XPathVariableResolver resolver) {
119+
final XPath xPath = XPathFactory.newInstance().newXPath();
120+
xPath.setXPathVariableResolver(resolver);
121+
return xPath;
122+
}
123+
124+
private static XPathExpression pathExpression(XPath xPath, String exp) {
125+
try {
126+
return xPath.compile(exp);
127+
} catch (XPathExpressionException e) {
128+
throw new AssertionError("Invalid XPath expression", e);
129+
}
130+
}
131+
132+
private static class VariableResolver implements XPathVariableResolver {
133+
private final static ThreadLocal<String> var = new ThreadLocal<>();
134+
135+
public void set(String value) {
136+
var.set(value);
137+
}
138+
139+
@Override
140+
public Object resolveVariable(QName varName) {
141+
return varName.getLocalPart().equals("var") ? var.get() : var.get().replace("--", "->");
142+
}
143+
}
144+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright © 2015 Stefan Niederhauser ([email protected])
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package guru.nidi.graphviz.engine;
17+
18+
import guru.nidi.graphviz.attribute.Label;
19+
import guru.nidi.graphviz.model.Graph;
20+
import guru.nidi.graphviz.model.Node;
21+
import org.junit.jupiter.api.Test;
22+
23+
import static guru.nidi.graphviz.engine.Format.SVG;
24+
import static guru.nidi.graphviz.model.Factory.*;
25+
import static org.junit.jupiter.api.Assertions.assertEquals;
26+
27+
class SvgElementFinderTest {
28+
@Test
29+
void findGraph() {
30+
final Graph g = graph().graphAttr().with("class", "g").with(node("a"));
31+
final String svg = Graphviz.fromGraph(g).render(SVG).toString();
32+
final SvgElementFinder finder = new SvgElementFinder(svg);
33+
assertEquals("graph g", classAttr(finder.findGraph()));
34+
}
35+
36+
@Test
37+
void findNode() {
38+
final Node a = node("a'").with("class", "aclass").with(Label.of("hula")).link("b");
39+
final String svg = Graphviz.fromGraph(graph().with(a)).render(SVG).toString();
40+
final SvgElementFinder finder = new SvgElementFinder(svg);
41+
assertEquals("node aclass", classAttr(finder.findNode("a'")));
42+
assertEquals("node aclass", classAttr(finder.findNode(a)));
43+
}
44+
45+
@Test
46+
void findEdge() {
47+
final Node a = node("a'").with(Label.of("hula")).link(to(node("b")).with("class", "link"));
48+
final String svg = Graphviz.fromGraph(graph().with(a)).render(SVG).toString();
49+
final SvgElementFinder finder = new SvgElementFinder(svg);
50+
assertEquals("edge link", classAttr(finder.findLink("a'", "b")));
51+
assertEquals("edge link", classAttr(finder.findLink(a.links().get(0))));
52+
final String svg2 = Graphviz.fromGraph(graph().directed().with(a)).render(SVG).toString();
53+
assertEquals("edge link", classAttr(new SvgElementFinder(svg2).findLink("a'", "b")));
54+
}
55+
56+
@Test
57+
void findCluster() {
58+
final Graph sub = graph("sub").cluster().graphAttr().with("class", "c").with(node("a"));
59+
final String svg = Graphviz.fromGraph(graph().with(sub)).render(SVG).toString();
60+
final SvgElementFinder finder = new SvgElementFinder(svg);
61+
assertEquals("cluster c", classAttr(finder.findCluster("sub")));
62+
assertEquals("cluster c", classAttr(finder.findCluster(sub)));
63+
}
64+
65+
private String classAttr(org.w3c.dom.Node node) {
66+
return node.getAttributes().getNamedItem("class").getNodeValue();
67+
}
68+
}

0 commit comments

Comments
 (0)