]> granicus.if.org Git - graphviz/commitdiff
tests: SvgAnalyzer: add an svg_string method
authorMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Thu, 28 Jul 2022 11:56:26 +0000 (13:56 +0200)
committerMagnus Jacobsson <Magnus.Jacobsson@berotec.se>
Tue, 16 Aug 2022 10:21:45 +0000 (12:21 +0200)
tests/svg_analyzer.cpp
tests/svg_analyzer.h
tests/svg_element.cpp
tests/svg_element.h
tests/test_svg_analyzer.cpp

index a69591a40217a2e374c40577ff01c2f7de7bc0bf..552075c57b0c0c91b5f4e14ed098e059b430addb 100644 (file)
@@ -82,3 +82,9 @@ void SVGAnalyzer::enter_element(SVG::SVGElementType type) {
   element.children.emplace_back(type);
   m_elements_in_process.push_back(&element.children.back());
 }
+
+std::string SVGAnalyzer::svg_string(std::size_t indent_size) const {
+  std::string output{};
+  output += m_svg.to_string(indent_size);
+  return output;
+}
index 124c1d7df77f4da7351f3b16adddd02975754223..4c779a6f6db46650e3e3a0279750e395206c6554 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <cstddef>
+#include <string>
 #include <vector>
 
 #include "svg_analyzer_interface.h"
@@ -34,6 +35,7 @@ public:
   std::size_t num_polylines() const { return m_num_polylines; };
   std::size_t num_rects() const { return m_num_rects; };
   std::size_t num_titles() const { return m_num_titles; };
+  std::string svg_string(std::size_t indent_size = 2) const;
 
 private:
   /// Get the current element being processed by the SVG document traverser
index 719430c73caff157e7f645da9a140ccc43e80a49..20bac09e0b505bc4d6b8f3665ab5406267f42708 100644 (file)
@@ -1,3 +1,69 @@
+#include <fmt/format.h>
+
 #include "svg_element.h"
+#include <cgraph/unreachable.h>
 
 SVG::SVGElement::SVGElement(SVGElementType type) : type(type) {}
+
+std::string SVG::SVGElement::to_string(std::size_t indent_size = 2) const {
+  std::string output;
+  output += R"(<?xml version="1.0" encoding="UTF-8" standalone="no"?>)"
+            "\n";
+  output += R"(<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN")"
+            "\n";
+  output += R"( "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">)"
+            "\n";
+  to_string_impl(output, indent_size, 0);
+  return output;
+}
+
+void SVG::SVGElement::to_string_impl(std::string &output,
+                                     std::size_t indent_size,
+                                     std::size_t current_indent) const {
+  const auto indent_str = std::string(current_indent, ' ');
+  output += indent_str;
+
+  output += "<";
+  output += tag(type);
+
+  if (children.empty()) {
+    output += "/>\n";
+  } else {
+    output += ">\n";
+    for (const auto &child : children) {
+      child.to_string_impl(output, indent_size, current_indent + indent_size);
+    }
+    output += indent_str;
+    output += "</";
+    output += tag(type);
+    output += ">\n";
+  }
+}
+
+std::string_view SVG::tag(SVGElementType type) {
+  switch (type) {
+  case SVG::SVGElementType::Circle:
+    return "circle";
+  case SVG::SVGElementType::Ellipse:
+    return "ellipse";
+  case SVG::SVGElementType::Group:
+    return "g";
+  case SVG::SVGElementType::Line:
+    return "line";
+  case SVG::SVGElementType::Path:
+    return "path";
+  case SVG::SVGElementType::Polygon:
+    return "polygon";
+  case SVG::SVGElementType::Polyline:
+    return "polyline";
+  case SVG::SVGElementType::Rect:
+    return "rect";
+  case SVG::SVGElementType::Svg:
+    return "svg";
+  case SVG::SVGElementType::Text:
+    return "text";
+  case SVG::SVGElementType::Title:
+    return "title";
+  }
+  UNREACHABLE();
+}
index 0e8acc848e65d995b3aa3238f35d12dd399b20c2..cd9cc4ecc7b8825a8302e0db508b196df0669914 100644 (file)
@@ -1,5 +1,7 @@
 #pragma once
 
+#include <string>
+#include <string_view>
 #include <vector>
 
 namespace SVG {
@@ -18,6 +20,8 @@ enum class SVGElementType {
   Title,
 };
 
+std::string_view tag(SVG::SVGElementType type);
+
 /**
  * @brief The SVGElement class represents an SVG element
  */
@@ -27,9 +31,15 @@ public:
   SVGElement() = delete;
   explicit SVGElement(SVG::SVGElementType type);
 
+  std::string to_string(std::size_t indent_size) const;
+
   std::vector<SVGElement> children;
   /// The type of SVG element
   const SVGElementType type;
+
+private:
+  void to_string_impl(std::string &output, std::size_t indent_size,
+                      std::size_t current_indent) const;
 };
 
 } // namespace SVG
index 33e5f72115131340770350b9d125aa16d906907a..20ce416c5ef601c0e1dc611afb3aca01894e67d1 100644 (file)
@@ -1,3 +1,4 @@
+#include <boost/algorithm/string.hpp>
 #include <catch2/catch.hpp>
 #include <fmt/format.h>
 
@@ -12,7 +13,8 @@
 TEST_CASE(
     "SvgAnalyzer",
     "The SvgAnalyzer parses an SVG produced by Graphviz to an internal data "
-    "structure and supports retrieval of information about that graph") {
+    "structure, supports retrieval of information about that graph and "
+    "recreation of the original SVG from that data structure") {
 
   const auto shape_char_ptr = GENERATE(from_range(all_node_shapes));
   const std::string shape{shape_char_ptr};
@@ -102,5 +104,35 @@ TEST_CASE(
     CHECK(svgAnalyzer.num_polylines() == expected_num_polylines);
     CHECK(svgAnalyzer.num_rects() == expected_num_rects);
     CHECK(svgAnalyzer.num_titles() == expected_num_titles);
+
+    const auto indent_size = 0;
+    auto recreated_svg = svgAnalyzer.svg_string(indent_size);
+
+    // compare the initial lines of the recreated SVG that we can fully recreate
+    // with the original SVG
+    std::vector<std::string> original_svg_lines;
+    boost::split(original_svg_lines, original_svg, boost::is_any_of("\n"));
+    std::vector<std::string> recreated_svg_lines;
+    boost::split(recreated_svg_lines, recreated_svg, boost::is_any_of("\n"));
+    for (std::size_t i = 0; i < original_svg_lines.size(); i++) {
+      REQUIRE(i < recreated_svg_lines.size());
+      if (recreated_svg_lines[i] == "<svg>") {
+        // stop comparison here since we do not yet handle the Graphviz version
+        // and build date comment and the graph title comment that comes before
+        // the 'svg' element
+        break;
+      }
+      REQUIRE(recreated_svg_lines[i] == original_svg_lines[i]);
+    }
+
+    // do some sanity checks of the parts of the recreated SVG that we cannot
+    // yet compare with the original SVG
+    CHECK(recreated_svg.find("<svg>") != std::string::npos);
+    CHECK(recreated_svg.find("</svg>") != std::string::npos);
+    CHECK(recreated_svg.find("<g>") != std::string::npos);
+    CHECK(recreated_svg.find("</g>") != std::string::npos);
+    CHECK(recreated_svg.find("<title/>") != std::string::npos);
+    CHECK(recreated_svg.find("<polygon/>") != std::string::npos);
+    CHECK(recreated_svg.find("<path/>") != std::string::npos);
   }
 }