Skip to content

Commit 1f1cc50

Browse files
committed
enhance SvgElementFinder to identify graph elements from svg
Signed-off-by: Stefan Niederhauser <[email protected]>
1 parent a44377e commit 1f1cc50

File tree

5 files changed

+234
-8
lines changed

5 files changed

+234
-8
lines changed

graphviz-java/src/main/java/guru/nidi/graphviz/attribute/SimpleLabel.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ public static SimpleLabel of(Object value) {
3535
}
3636

3737
public String serialized() {
38-
return html ? ("<" + value + ">") : quoted();
38+
return html ? ("<" + value + ">") : ("\"" + quoted() + "\"");
39+
}
40+
41+
public String simpleSerialized() {
42+
return html ? value : quoted();
3943
}
4044

4145
private String quoted() {
@@ -46,7 +50,7 @@ private String quoted() {
4650
}
4751
final String end = endSlashes % 2 == 1 ? "\\" : "";
4852
//TODO check if works for cmdline engine too
49-
return "\"" + value.replace("\"", "\\\"").replaceAll("\\R", "\\\\n") + end + "\"";
53+
return value.replace("\"", "\\\"").replaceAll("\\R", "\\\\n") + end;
5054
}
5155

5256
public String value() {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.model;
17+
18+
import org.w3c.dom.Element;
19+
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import static java.util.stream.Collectors.toMap;
24+
25+
public class GraphElementFinder extends SvgElementFinder {
26+
private final Map<String, MutableNode> nodes;
27+
private final Map<String, Link> links;
28+
private final Map<String, MutableGraph> graphs;
29+
30+
GraphElementFinder(SvgElementFinder finder, MutableGraph graph) {
31+
super(finder);
32+
nodes = graph.nodes().stream().collect(toMap(n -> n.name().simpleSerialized(), n -> n));
33+
links = graph.edges().stream().collect(toMap(e -> e.from().name().simpleSerialized() + "--" + e.to().name().simpleSerialized(), e -> e));
34+
graphs = graph.graphs().stream().collect(toMap(g -> g.name().simpleSerialized(), g -> g));
35+
}
36+
37+
public MutableNode nodeOf(Element e) {
38+
return nodes.get(SvgElementFinder.nodeNameOf(e));
39+
}
40+
41+
public Link linkOf(Element e) {
42+
final List<String> fromTo = SvgElementFinder.linkedNodeNamesOf(e);
43+
return links.get(fromTo.get(0) + "--" + fromTo.get(1));
44+
}
45+
46+
public MutableGraph clusterOf(Element e) {
47+
return graphs.get(SvgElementFinder.clusterNameOf(e));
48+
}
49+
}

graphviz-java/src/main/java/guru/nidi/graphviz/model/SvgElementFinder.java

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
*/
1616
package guru.nidi.graphviz.model;
1717

18-
import org.w3c.dom.Document;
19-
import org.w3c.dom.Element;
18+
import org.w3c.dom.*;
2019
import org.xml.sax.InputSource;
2120
import org.xml.sax.SAXException;
2221

@@ -29,25 +28,38 @@
2928
import javax.xml.transform.stream.StreamResult;
3029
import javax.xml.xpath.*;
3130
import java.io.*;
31+
import java.util.ArrayList;
32+
import java.util.List;
3233
import java.util.function.Consumer;
3334

35+
import static java.util.Arrays.asList;
36+
3437
public class SvgElementFinder {
3538
private static final DocumentBuilderFactory FACTORY = builderFactory();
3639
private static final TransformerFactory TRANSFORMER_FACTORY = transformerFactory();
3740
private static final VariableResolver RESOLVER = new VariableResolver();
3841
private static final XPath X_PATH = xPath(RESOLVER);
39-
private static final XPathExpression EXPR_G = pathExpression(X_PATH, "//g");
40-
private static final XPathExpression EXPR_TITLE = pathExpression(X_PATH, "//title[text()=$var]");
41-
private static final XPathExpression EXPR_TITLE_OR = pathExpression(X_PATH, "//title[text()=$var or text()=$alt]");
42-
private final boolean hasHeader;
42+
private static final XPathExpression
43+
EXPR_G = pathExpression(X_PATH, "//g"),
44+
EXPR_TITLE = pathExpression(X_PATH, "//title[text()=$var]"),
45+
EXPR_TITLE_OR = pathExpression(X_PATH, "//title[text()=$var or text()=$alt]"),
46+
EXPR_NODE = pathExpression(X_PATH, "//g[contains(@class,'node')]"),
47+
EXPR_EDGE = pathExpression(X_PATH, "//g[contains(@class,'edge')]"),
48+
EXPR_CLUSTER = pathExpression(X_PATH, "//g[contains(@class,'cluster')]");
4349
private final Document doc;
50+
private final boolean hasHeader;
4451

4552
public static String use(String svg, Consumer<SvgElementFinder> actions) {
4653
final SvgElementFinder finder = new SvgElementFinder(svg);
4754
actions.accept(finder);
4855
return finder.getSvg();
4956
}
5057

58+
SvgElementFinder(SvgElementFinder finder) {
59+
this.doc = finder.doc;
60+
this.hasHeader = finder.hasHeader;
61+
}
62+
5163
public SvgElementFinder(String svg) {
5264
try {
5365
doc = builder().parse(new InputSource(new StringReader(svg)));
@@ -83,6 +95,14 @@ public Element findNode(String name) {
8395
return title == null ? null : (Element) title.getParentNode();
8496
}
8597

98+
public List<Element> findNodes() {
99+
return listOf(nodeExpr(EXPR_NODE));
100+
}
101+
102+
public static String nodeNameOf(Element e) {
103+
return e.getElementsByTagName("title").item(0).getTextContent();
104+
}
105+
86106
@Nullable
87107
public Element findLink(Link link) {
88108
return findLink(link.from().name().toString(), link.to().name().toString());
@@ -94,6 +114,15 @@ public Element findLink(String from, String to) {
94114
return title == null ? null : (Element) title.getParentNode();
95115
}
96116

117+
public List<Element> findLinks() {
118+
return listOf(nodeExpr(EXPR_EDGE));
119+
}
120+
121+
public static List<String> linkedNodeNamesOf(Element e) {
122+
final String name = e.getElementsByTagName("title").item(0).getTextContent();
123+
return asList(name.split("--"));
124+
}
125+
97126
@Nullable
98127
public Element findCluster(Graph cluster) {
99128
return findCluster(cluster.name().toString());
@@ -105,6 +134,22 @@ public Element findCluster(String name) {
105134
return title == null ? null : (Element) title.getParentNode();
106135
}
107136

137+
public List<Element> findClusters() {
138+
return listOf(nodeExpr(EXPR_CLUSTER));
139+
}
140+
141+
public static String clusterNameOf(Element e) {
142+
return e.getElementsByTagName("title").item(0).getTextContent().substring("cluster_".length());
143+
}
144+
145+
public GraphElementFinder fromGraph(Graph g) {
146+
return fromGraph((MutableGraph) g);
147+
}
148+
149+
public GraphElementFinder fromGraph(MutableGraph g) {
150+
return new GraphElementFinder(this, g);
151+
}
152+
108153
@Nullable
109154
private org.w3c.dom.Node nodeExpr(XPathExpression expr, String var) {
110155
RESOLVER.set(var);
@@ -115,6 +160,22 @@ private org.w3c.dom.Node nodeExpr(XPathExpression expr, String var) {
115160
}
116161
}
117162

163+
private org.w3c.dom.NodeList nodeExpr(XPathExpression expr) {
164+
try {
165+
return (org.w3c.dom.NodeList) expr.evaluate(doc, XPathConstants.NODESET);
166+
} catch (XPathExpressionException e) {
167+
throw new AssertionError("Could not execute XPath", e);
168+
}
169+
}
170+
171+
private List<Element> listOf(NodeList nodes) {
172+
final List<Element> res = new ArrayList<>();
173+
for (int i = 0; i < nodes.getLength(); i++) {
174+
res.add((Element) nodes.item(i));
175+
}
176+
return res;
177+
}
178+
118179
private static DocumentBuilderFactory builderFactory() {
119180
try {
120181
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.model;
17+
18+
import guru.nidi.graphviz.attribute.Label;
19+
import guru.nidi.graphviz.engine.Graphviz;
20+
import org.junit.jupiter.api.Test;
21+
22+
import static guru.nidi.graphviz.engine.Format.SVG;
23+
import static guru.nidi.graphviz.model.Factory.graph;
24+
import static guru.nidi.graphviz.model.Factory.node;
25+
import static org.junit.jupiter.api.Assertions.assertEquals;
26+
27+
class GraphElementFinderTest {
28+
@Test
29+
void nodeOf() {
30+
final Node c = node(Label.html("c"));
31+
final Node a = node("a\nb").link(c);
32+
final Graph g = graph().with(a);
33+
final String svg = Graphviz.fromGraph(g).render(SVG).toString();
34+
final GraphElementFinder finder = new SvgElementFinder(svg).fromGraph(g);
35+
assertEquals(c, finder.nodeOf(finder.findNodes().get(0)));
36+
assertEquals(a, finder.nodeOf(finder.findNodes().get(1)));
37+
}
38+
39+
@Test
40+
void linkOf() {
41+
final Node a = node("a").link("b");
42+
final Graph g = graph().with(a);
43+
final String svg = Graphviz.fromGraph(g).render(SVG).toString();
44+
final GraphElementFinder finder = new SvgElementFinder(svg).fromGraph(g);
45+
assertEquals(a.links().get(0), finder.linkOf(finder.findLinks().get(0)));
46+
}
47+
48+
49+
@Test
50+
void clusterOf() {
51+
final Graph sub = graph("sub").cluster().graphAttr().with("class", "c").with(node("a"));
52+
final Graph g = graph().with(sub);
53+
final String svg = Graphviz.fromGraph(g).render(SVG).toString();
54+
final GraphElementFinder finder = new SvgElementFinder(svg).fromGraph(g);
55+
assertEquals(sub, finder.clusterOf(finder.findClusters().get(0)));
56+
}
57+
}

graphviz-java/src/test/java/guru/nidi/graphviz/model/SvgElementFinderTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import guru.nidi.graphviz.engine.Graphviz;
2020
import org.junit.jupiter.api.Test;
2121

22+
import static guru.nidi.graphviz.attribute.Attributes.attr;
2223
import static guru.nidi.graphviz.engine.Format.SVG;
2324
import static guru.nidi.graphviz.model.Factory.*;
25+
import static java.util.Arrays.asList;
2426
import static org.hamcrest.MatcherAssert.assertThat;
2527
import static org.hamcrest.Matchers.containsString;
2628
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -43,6 +45,22 @@ void findNode() {
4345
assertEquals("node aclass", finder.findNode(a).getAttribute("class"));
4446
}
4547

48+
@Test
49+
void findNodes() {
50+
final Node a = node("a").with(attr("class", "aclass")).link(node("b").with("class", "bclass"));
51+
final String svg = Graphviz.fromGraph(graph().with(a)).render(SVG).toString();
52+
final SvgElementFinder finder = new SvgElementFinder(svg);
53+
assertEquals("node aclass", finder.findNodes().get(0).getAttribute("class"));
54+
assertEquals("node bclass", finder.findNodes().get(1).getAttribute("class"));
55+
}
56+
57+
@Test
58+
void nodeNameOf() {
59+
final String svg = Graphviz.fromGraph(graph().with(node("a"))).render(SVG).toString();
60+
final SvgElementFinder finder = new SvgElementFinder(svg);
61+
assertEquals("a", SvgElementFinder.nodeNameOf(finder.findNodes().get(0)));
62+
}
63+
4664
@Test
4765
void findEdge() {
4866
final Node a = node("a'").with(Label.of("hula")).link(to(node("b")).with("class", "link"));
@@ -54,6 +72,23 @@ void findEdge() {
5472
assertEquals("edge link", new SvgElementFinder(svg2).findLink("a'", "b").getAttribute("class"));
5573
}
5674

75+
@Test
76+
void findEdges() {
77+
final Node b = node("b");
78+
final Node a = node("a").link(to(b).with("class", "a-b"));
79+
final String svg = Graphviz.fromGraph(graph().with(a, b.link(to(a).with("class", "b-a")))).render(SVG).toString();
80+
final SvgElementFinder finder = new SvgElementFinder(svg);
81+
assertEquals("edge a-b", finder.findLinks().get(0).getAttribute("class"));
82+
assertEquals("edge b-a", finder.findLinks().get(1).getAttribute("class"));
83+
}
84+
85+
@Test
86+
void linkedNodeNamesOf() {
87+
final String svg = Graphviz.fromGraph(graph().with(node("a").link("b"))).render(SVG).toString();
88+
final SvgElementFinder finder = new SvgElementFinder(svg);
89+
assertEquals(asList("a", "b"), SvgElementFinder.linkedNodeNamesOf(finder.findLinks().get(0)));
90+
}
91+
5792
@Test
5893
void findCluster() {
5994
final Graph sub = graph("sub").cluster().graphAttr().with("class", "c").with(node("a"));
@@ -63,6 +98,26 @@ void findCluster() {
6398
assertEquals("cluster c", finder.findCluster(sub).getAttribute("class"));
6499
}
65100

101+
@Test
102+
void findClusters() {
103+
final String svg = Graphviz.fromGraph(graph().with(
104+
graph("sub1").cluster().graphAttr().with("class", "c").with(node("a")),
105+
graph("sub2").cluster().graphAttr().with("class", "d").with(node("b"))))
106+
.render(SVG).toString();
107+
final SvgElementFinder finder = new SvgElementFinder(svg);
108+
assertEquals("cluster c", finder.findClusters().get(0).getAttribute("class"));
109+
assertEquals("cluster d", finder.findClusters().get(1).getAttribute("class"));
110+
}
111+
112+
@Test
113+
void clusterNameOf() {
114+
final String svg = Graphviz.fromGraph(graph().with(
115+
graph("sub1").cluster().graphAttr().with("class", "c").with(node("a"))))
116+
.render(SVG).toString();
117+
final SvgElementFinder finder = new SvgElementFinder(svg);
118+
assertEquals("sub1", SvgElementFinder.clusterNameOf(finder.findClusters().get(0)));
119+
}
120+
66121
@Test
67122
void use() {
68123
final String svg = Graphviz.fromGraph(graph().with(node("node"))).render(SVG).toString();

0 commit comments

Comments
 (0)