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;
+}
#pragma once
#include <cstddef>
+#include <string>
#include <vector>
#include "svg_analyzer_interface.h"
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
+#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();
+}
#pragma once
+#include <string>
+#include <string_view>
#include <vector>
namespace SVG {
Title,
};
+std::string_view tag(SVG::SVGElementType type);
+
/**
* @brief The SVGElement class represents an SVG element
*/
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
+#include <boost/algorithm/string.hpp>
#include <catch2/catch.hpp>
#include <fmt/format.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") {
+ "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};
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);
}
}