]> granicus.if.org Git - graphviz/commitdiff
tests: SvgAnalyzer: add support for retrieving Graphviz components
authorMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Wed, 17 Aug 2022 10:59:08 +0000 (12:59 +0200)
committerMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Tue, 23 Aug 2022 06:19:35 +0000 (08:19 +0200)
tests/svg_analyzer.cpp
tests/svg_analyzer.h
tests/test_svg_analyzer.cpp

index 23ffd9d374d046881653edc66a402ff225a8716b..bb352b059d0aa06464feadb2b6f8d4a38f754b72 100644 (file)
@@ -1,5 +1,7 @@
 #include <stdexcept>
 
+#include <fmt/format.h>
+
 #include "svg_analyzer.h"
 #include "svgpp_context.h"
 #include "svgpp_document_traverser.h"
@@ -13,6 +15,12 @@ SVGAnalyzer::SVGAnalyzer(char *text)
     throw std::runtime_error{
         "Wrong number of elements in process after traversing SVG document"};
   }
+  m_elements_in_process.pop_back();
+  retrieve_graphviz_components();
+}
+
+const std::vector<GraphvizGraph> &SVGAnalyzer::graphs() const {
+  return m_graphs;
 }
 
 void SVGAnalyzer::on_enter_element_svg() { m_num_svgs++; }
@@ -130,6 +138,42 @@ void SVGAnalyzer::set_class(std::string_view class_) {
   current_element().attributes.class_ = class_;
 }
 
+void SVGAnalyzer::retrieve_graphviz_components() {
+  retrieve_graphviz_components_impl(m_svg);
+}
+
+void SVGAnalyzer::retrieve_graphviz_components_impl(
+    SVG::SVGElement &svg_element) {
+  if (svg_element.type == SVG::SVGElementType::Group) {
+    // The SVG 'class' attribute determines which type of Graphviz element a 'g'
+    // element corresponds to
+    const auto class_ = svg_element.attributes.class_;
+    if (class_ == "graph") {
+      m_graphs.emplace_back(svg_element);
+    } else if (class_ == "node") {
+      if (m_graphs.empty()) {
+        throw std::runtime_error{
+            fmt::format("No graph to add node {} to", svg_element.graphviz_id)};
+      }
+      m_graphs.back().add_node(svg_element);
+    } else if (class_ == "edge") {
+      if (m_graphs.empty()) {
+        throw std::runtime_error{
+            fmt::format("No graph to add edge {} to", svg_element.graphviz_id)};
+      }
+      m_graphs.back().add_edge(svg_element);
+    } else if (class_ == "cluster") {
+      // ignore of now
+    } else {
+      throw std::runtime_error("Unknown class" + std::string{class_});
+    }
+  }
+
+  for (auto &child : svg_element.children) {
+    retrieve_graphviz_components_impl(child);
+  }
+}
+
 void SVGAnalyzer::set_cx(double cx) { current_element().attributes.cx = cx; }
 
 void SVGAnalyzer::set_cy(double cy) { current_element().attributes.cy = cy; }
index f4a418dea7786fb5c14237ce87f8fc005763b8b1..033b4ee68a2558f99ab40c15527347d5854c3937 100644 (file)
@@ -5,16 +5,20 @@
 #include <string_view>
 #include <vector>
 
+#include "graphviz_graph.h"
 #include "svg_analyzer_interface.h"
 #include "svg_element.h"
 
 /**
- * @brief The SVGAnalyzer class analyzes the contents of an SVG document.
+ * @brief The SVGAnalyzer class analyzes the contents of an SVG document and
+ * identifies Graphviz graphs, nodes and edges.
  */
 
 class SVGAnalyzer : public ISVGAnalyzer {
 public:
   SVGAnalyzer(char *text);
+  /// Return a non-mutable reference to the list of Graphviz graphs
+  const std::vector<GraphvizGraph> &graphs() const;
   void on_enter_element_svg() override;
   void on_enter_element_g() override;
   void on_enter_element_circle() override;
@@ -78,6 +82,10 @@ private:
   /// Get the parent element of the current element being processed by the SVG
   /// document traverser
   SVG::SVGElement &parent_element();
+  /// Traverses the processed SVG element hierarchy, identifies nodes and edges,
+  /// creates objects representing them and stores them in lists
+  void retrieve_graphviz_components();
+  void retrieve_graphviz_components_impl(SVG::SVGElement &parent_svg_element);
 
   /// A list of pointers to elements currently being processed by the SVG++
   /// document traverser, in hierarchical order. The front element is the top
@@ -95,6 +103,8 @@ private:
   std::size_t m_num_rects = 0;
   std::size_t m_num_texts = 0;
   std::size_t m_num_titles = 0;
+  /// A list of Graphviz recreated graphs
+  std::vector<GraphvizGraph> m_graphs;
   /// The top level SVG `svg` element corresponding to the Graphviz graph
   SVG::SVGElement m_svg;
 };
index 6b1b6b888040fc86d9b1d69bde730411f14b41b7..6d3e282b9a4356ade3db7eafecfcbce95c935576 100644 (file)
@@ -1,7 +1,9 @@
 #include <boost/algorithm/string.hpp>
+#include <boost/range/adaptor/indexed.hpp>
 #include <catch2/catch.hpp>
 #include <fmt/format.h>
 
+#include "graphviz_graph.h"
 #include "svg_analyzer.h"
 #include <cgraph++/AGraph.h>
 #include <gvc++/GVContext.h>
@@ -41,7 +43,29 @@ TEST_CASE(
   const std::size_t expected_num_nodes = 2;
   const std::size_t expected_num_edges = 1;
 
-  {
+  CHECK(svgAnalyzer.graphs().size() == expected_num_graphs);
+  for (const auto &graph : svgAnalyzer.graphs()) {
+    CHECK(graph.svg_g_element().type == SVG::SVGElementType::Group);
+    CHECK(graph.svg_g_element().attributes.class_ == "graph");
+    CHECK(graph.svg_g_element().graphviz_id == "g1");
+
+    CHECK(graph.nodes().size() == expected_num_nodes);
+    for (const auto &node_it : graph.nodes() | boost::adaptors::indexed(0)) {
+      const auto node = node_it.value();
+      const auto i = node_it.index();
+      CHECK(node.svg_g_element().type == SVG::SVGElementType::Group);
+      CHECK(node.svg_g_element().attributes.class_ == "node");
+      const auto node_id = i == 0 ? "a" : "b";
+      CHECK(node.svg_g_element().graphviz_id == node_id);
+    }
+
+    CHECK(graph.edges().size() == expected_num_edges);
+    for (const auto &edge : graph.edges()) {
+      CHECK(edge.svg_g_element().type == SVG::SVGElementType::Group);
+      CHECK(edge.svg_g_element().attributes.class_ == "edge");
+      CHECK(edge.svg_g_element().graphviz_id == "a->b");
+    }
+
     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;