--- /dev/null
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+
+#include "svg_analyzer.h"
+#include <cgraph++/AGraph.h>
+#include <gvc++/GVContext.h>
+#include <gvc++/GVLayout.h>
+#include <gvc++/GVRenderData.h>
+
+#include "test_utilities.h"
+
+TEST_CASE(
+ "SvgAnalyzer",
+ "The SvgAnalyzer parses an SVG produced by Graphviz to an internal data "
+ "structure and supports retrieval of information about that graph") {
+
+ const auto shape_char_ptr = GENERATE(from_range(all_node_shapes));
+ const std::string shape{shape_char_ptr};
+ INFO(fmt::format("Shape: {}", shape));
+
+ auto dot = fmt::format("digraph g1 {{node [shape={}]; a -> b}}", shape);
+
+ auto g = CGraph::AGraph{dot};
+
+ const auto demand_loading = false;
+ auto gvc = GVC::GVContext(lt_preloaded_symbols, demand_loading);
+
+ const auto layout = GVC::GVLayout(std::move(gvc), std::move(g), "dot");
+
+ const auto result = layout.render("svg");
+ const std::string original_svg{result.string_view()};
+ SVGAnalyzer svgAnalyzer{result.c_str()};
+
+ const std::size_t expected_num_graphs = 1;
+ const std::size_t expected_num_nodes = 2;
+ const std::size_t expected_num_edges = 1;
+
+ {
+ const std::size_t expected_num_svgs = expected_num_graphs;
+ const std::size_t expected_num_groups =
+ expected_num_graphs + expected_num_nodes + expected_num_edges;
+ const std::size_t expected_num_circles = 0;
+ const std::size_t expected_num_ellipses = [&]() {
+ if (shape == "doublecircle") {
+ return expected_num_nodes * 2;
+ } else if (contains_ellipse_shape(shape)) {
+ return expected_num_nodes;
+ } else {
+ return 0UL;
+ }
+ }();
+ const std::size_t expected_num_lines = 0;
+ const std::size_t expected_num_paths =
+ expected_num_edges + (shape == "cylinder" ? expected_num_nodes * 2 : 0);
+ const std::size_t expected_num_polygons =
+ expected_num_graphs + expected_num_edges + [&]() {
+ if (shape == "noverhang") {
+ return expected_num_nodes * 4;
+ } else if (shape == "tripleoctagon") {
+ return expected_num_nodes * 3;
+ } else if (shape == "doubleoctagon" || shape == "fivepoverhang" ||
+ shape == "threepoverhang" || shape == "assembly") {
+ return expected_num_nodes * 2;
+ } else if (contains_polygon_shape(shape)) {
+ return expected_num_nodes;
+ } else {
+ return 0UL;
+ }
+ }();
+ const std::size_t expected_num_polylines = [&]() {
+ if (shape == "Mdiamond" || shape == "Msquare") {
+ return expected_num_nodes * 4;
+ } else if (shape == "box3d" || shape == "signature" ||
+ shape == "insulator" || shape == "ribosite" ||
+ shape == "rnastab") {
+ return expected_num_nodes * 3;
+ } else if (shape == "Mcircle" || shape == "note" ||
+ shape == "component" || shape == "restrictionsite" ||
+ shape == "noverhang" || shape == "assembly" ||
+ shape == "proteasesite" || shape == "proteinstab") {
+ return expected_num_nodes * 2;
+ } else if (shape == "underline" || shape == "tab" ||
+ shape == "promoter" || shape == "terminator" ||
+ shape == "utr" || shape == "primersite" ||
+ shape == "fivepoverhang" || shape == "threepoverhang") {
+ return expected_num_nodes;
+ } else {
+ return 0UL;
+ }
+ }();
+ const std::size_t expected_num_rects = 0;
+ const std::size_t expected_num_titles =
+ expected_num_graphs + expected_num_nodes + expected_num_edges;
+
+ CHECK(svgAnalyzer.num_svgs() == expected_num_svgs);
+ CHECK(svgAnalyzer.num_groups() == expected_num_groups);
+ CHECK(svgAnalyzer.num_circles() == expected_num_circles);
+ CHECK(svgAnalyzer.num_ellipses() == expected_num_ellipses);
+ CHECK(svgAnalyzer.num_lines() == expected_num_lines);
+ CHECK(svgAnalyzer.num_paths() == expected_num_paths);
+ CHECK(svgAnalyzer.num_polygons() == expected_num_polygons);
+ CHECK(svgAnalyzer.num_polylines() == expected_num_polylines);
+ CHECK(svgAnalyzer.num_rects() == expected_num_rects);
+ CHECK(svgAnalyzer.num_titles() == expected_num_titles);
+ }
+}
--- /dev/null
+#include <string_view>
+#include <unordered_set>
+
+#include "test_utilities.h"
+
+const std::unordered_set<std::string_view> node_shapes_consisting_of_ellipse = {
+ "ellipse", //
+ "oval", //
+ "circle", //
+ "doublecircle", //
+ "point", //
+};
+
+const std::unordered_set<std::string_view>
+ node_shapes_consisting_of_ellipse_and_polyline = {
+ "Mcircle", //
+};
+
+const std::unordered_set<std::string_view> node_shapes_consisting_of_path = {
+ "cylinder", //
+};
+
+const std::unordered_set<std::string_view> node_shapes_consisting_of_polygon = {
+ "box", //
+ "polygon", //
+ "egg", //
+ "triangle", //
+ "diamond", //
+ "trapezium", //
+ "parallelogram", //
+ "house", //
+ "pentagon", //
+ "hexagon", //
+ "septagon", //
+ "octagon", //
+ "doubleoctagon", //
+ "tripleoctagon", //
+ "invtriangle", //
+ "invtrapezium", //
+ "invhouse", //
+ "rect", //
+ "rectangle", //
+ "square", //
+ "star", //
+ "cds", //
+ "rpromoter", //
+ "rarrow", //
+ "larrow", //
+ "lpromoter", //
+ "folder", //
+};
+
+const std::unordered_set<std::string_view>
+ node_shapes_consisting_of_polygon_and_polyline = {
+ "Mdiamond", //
+ "Msquare", //
+ "underline", //
+ "note", //
+ "tab", //
+ "box3d", //
+ "component", //
+ "promoter", //
+ "terminator", //
+ "utr", //
+ "primersite", //
+ "restrictionsite", //
+ "fivepoverhang", //
+ "threepoverhang", //
+ "noverhang", //
+ "assembly", //
+ "signature", //
+ "insulator", //
+ "ribosite", //
+ "rnastab", //
+ "proteasesite", //
+ "proteinstab", //
+};
+
+const std::unordered_set<std::string_view> node_shapes_without_svg_shape = {
+ "plaintext", //
+ "plain", //
+ "none", //
+};
+
+const std::unordered_set<std::string_view> all_node_shapes = {
+ "box", //
+ "polygon", //
+ "ellipse", //
+ "oval", //
+ "circle", //
+ "point", //
+ "egg", //
+ "triangle", //
+ "plaintext", //
+ "plain", //
+ "diamond", //
+ "trapezium", //
+ "parallelogram", //
+ "house", //
+ "pentagon", //
+ "hexagon", //
+ "septagon", //
+ "octagon", //
+ "doublecircle", //
+ "doubleoctagon", //
+ "tripleoctagon", //
+ "invtriangle", //
+ "invtrapezium", //
+ "invhouse", //
+ "Mdiamond", //
+ "Msquare", //
+ "Mcircle", //
+ "rect", //
+ "rectangle", //
+ "square", //
+ "star", //
+ "none", //
+ "underline", //
+ "cylinder", //
+ "note", //
+ "tab", //
+ "folder", //
+ "box3d", //
+ "component", //
+ "promoter", //
+ "cds", //
+ "terminator", //
+ "utr", //
+ "primersite", //
+ "restrictionsite", //
+ "fivepoverhang", //
+ "threepoverhang", //
+ "noverhang", //
+ "assembly", //
+ "signature", //
+ "insulator", //
+ "ribosite", //
+ "rnastab", //
+ "proteasesite", //
+ "proteinstab", //
+ "rpromoter", //
+ "rarrow", //
+ "larrow", //
+ "lpromoter" //
+};
+
+bool contains_polygon_shape(std::string_view shape) {
+ return node_shapes_consisting_of_polygon.contains(shape) ||
+ node_shapes_consisting_of_polygon_and_polyline.contains(shape);
+}
+
+bool contains_ellipse_shape(std::string_view shape) {
+ return node_shapes_consisting_of_ellipse.contains(shape) ||
+ node_shapes_consisting_of_ellipse_and_polyline.contains(shape);
+}
--- /dev/null
+#pragma once
+
+#include <string_view>
+#include <unordered_set>
+
+/// node shapes
+
+extern const std::unordered_set<std::string_view> all_node_shapes;
+extern const std::unordered_set<std::string_view>
+ node_shapes_consisting_of_ellipse;
+extern const std::unordered_set<std::string_view>
+ node_shapes_consisting_of_ellipse_and_polyline;
+extern const std::unordered_set<std::string_view>
+ node_shapes_consisting_of_path;
+extern const std::unordered_set<std::string_view>
+ node_shapes_consisting_of_polygon;
+extern const std::unordered_set<std::string_view>
+ node_shapes_consisting_of_polygon_and_polyline;
+extern const std::unordered_set<std::string_view> node_shapes_without_svg_shape;
+
+bool contains_ellipse_shape(std::string_view shape);
+bool contains_polygon_shape(std::string_view shape);