From 1b14fd3ffe0677c6a3c66d8906cc285de46ecd90 Mon Sep 17 00:00:00 2001 From: Magnus Jacobsson Date: Thu, 21 Jul 2022 13:16:41 +0200 Subject: [PATCH] tests: SvgAnalyzer: add internal re-creation of the SVG element hierarchy This will be used in upcoming commits in this series to store attributes on and to re-create the original SVG from. Also, an upcoming commit series will extend the SVG analyzer to be aware of Graphviz graphs, nodes and edges and will use this hierarchy and the associated attributes to be able to answer questions about their properties. --- tests/CMakeLists.txt | 2 + tests/svg_analyzer.cpp | 76 +++++++++++++++++++++++++++++----- tests/svg_analyzer.h | 15 +++++++ tests/svg_analyzer_interface.h | 1 + tests/svg_element.cpp | 3 ++ tests/svg_element.h | 35 ++++++++++++++++ tests/svgpp_context.cpp | 2 +- 7 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 tests/svg_element.cpp create mode 100644 tests/svg_element.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0a1b956c7..0ccd8cc98 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(test_common SHARED svg_analyzer.cpp svg_analyzer.h svg_analyzer_interface.h + svg_element.cpp + svg_element.h ../cmd/dot/dot_builtins.cpp ) set_target_properties(test_common PROPERTIES CXX_STANDARD 20) diff --git a/tests/svg_analyzer.cpp b/tests/svg_analyzer.cpp index 2ac848a14..a69591a40 100644 --- a/tests/svg_analyzer.cpp +++ b/tests/svg_analyzer.cpp @@ -1,28 +1,84 @@ +#include + #include "svg_analyzer.h" #include "svgpp_context.h" #include "svgpp_document_traverser.h" -SVGAnalyzer::SVGAnalyzer(char *text) { +SVGAnalyzer::SVGAnalyzer(char *text) + : m_svg(SVG::SVGElement(SVG::SVGElementType::Svg)) { + m_elements_in_process.push_back(&m_svg); SvgppContext context{this}; traverseDocumentWithSvgpp(context, text); + if (m_elements_in_process.size() != 1) { + throw std::runtime_error{ + "Wrong number of elements in process after traversing SVG document"}; + } } void SVGAnalyzer::on_enter_element_svg() { m_num_svgs++; } -void SVGAnalyzer::on_enter_element_g() { m_num_groups++; } +void SVGAnalyzer::on_enter_element_g() { + enter_element(SVG::SVGElementType::Group); + m_num_groups++; +} -void SVGAnalyzer::on_enter_element_circle() { m_num_circles++; } +void SVGAnalyzer::on_enter_element_circle() { + enter_element(SVG::SVGElementType::Circle); + m_num_circles++; +} -void SVGAnalyzer::on_enter_element_ellipse() { m_num_ellipses++; } +void SVGAnalyzer::on_enter_element_ellipse() { + enter_element(SVG::SVGElementType::Ellipse); + m_num_ellipses++; +} -void SVGAnalyzer::on_enter_element_line() { m_num_lines++; } +void SVGAnalyzer::on_enter_element_line() { + enter_element(SVG::SVGElementType::Line); + m_num_lines++; +} -void SVGAnalyzer::on_enter_element_path() { m_num_paths++; } +void SVGAnalyzer::on_enter_element_path() { + enter_element(SVG::SVGElementType::Path); + m_num_paths++; +} -void SVGAnalyzer::on_enter_element_polygon() { m_num_polygons++; } +void SVGAnalyzer::on_enter_element_polygon() { + enter_element(SVG::SVGElementType::Polygon); + m_num_polygons++; +} -void SVGAnalyzer::on_enter_element_polyline() { m_num_polylines++; } +void SVGAnalyzer::on_enter_element_polyline() { + enter_element(SVG::SVGElementType::Polyline); + m_num_polylines++; +} -void SVGAnalyzer::on_enter_element_rect() { m_num_rects++; } +void SVGAnalyzer::on_enter_element_rect() { + enter_element(SVG::SVGElementType::Rect); + m_num_rects++; +} + +void SVGAnalyzer::on_enter_element_title() { + enter_element(SVG::SVGElementType::Title); + m_num_titles++; +} + +void SVGAnalyzer::on_exit_element() { m_elements_in_process.pop_back(); } -void SVGAnalyzer::on_enter_element_title() { m_num_titles++; } +SVG::SVGElement &SVGAnalyzer::current_element() { + if (m_elements_in_process.empty()) { + throw std::runtime_error{"No current element"}; + } + return *m_elements_in_process.back(); +} + +void SVGAnalyzer::enter_element(SVG::SVGElementType type) { + if (m_elements_in_process.empty()) { + throw std::runtime_error{ + "No element is currently being processed by the SVG++ document " + "traverser when entering a new element. Expecting at least the top " + "level 'svg' to be in process"}; + } + auto &element = current_element(); + element.children.emplace_back(type); + m_elements_in_process.push_back(&element.children.back()); +} diff --git a/tests/svg_analyzer.h b/tests/svg_analyzer.h index 77aff3aa3..124c1d7df 100644 --- a/tests/svg_analyzer.h +++ b/tests/svg_analyzer.h @@ -1,8 +1,10 @@ #pragma once #include +#include #include "svg_analyzer_interface.h" +#include "svg_element.h" /** * @brief The SVGAnalyzer class analyzes the contents of an SVG document. @@ -21,6 +23,7 @@ public: void on_enter_element_polyline() override; void on_enter_element_rect() override; void on_enter_element_title() override; + void on_exit_element() override; std::size_t num_svgs() const { return m_num_svgs; }; std::size_t num_groups() const { return m_num_groups; }; std::size_t num_circles() const { return m_num_circles; }; @@ -33,6 +36,16 @@ public: std::size_t num_titles() const { return m_num_titles; }; private: + /// Get the current element being processed by the SVG document traverser + SVG::SVGElement ¤t_element(); + /// Enter a new SVG element retrieved by the SVG document traverser into the + /// list of elements currently being processed + void enter_element(SVG::SVGElementType type); + + /// A list of pointers to elements currently being processed by the SVG++ + /// document traverser, in hierarchical order. The front element is the top + /// level SVG and the back element is the current element. + std::vector m_elements_in_process; std::size_t m_num_svgs = 1; // the top level svg is implicit. See // https://github.com/svgpp/svgpp/issues/98 std::size_t m_num_groups = 0; @@ -44,4 +57,6 @@ private: std::size_t m_num_polylines = 0; std::size_t m_num_rects = 0; std::size_t m_num_titles = 0; + /// The top level SVG `svg` element corresponding to the Graphviz graph + SVG::SVGElement m_svg; }; diff --git a/tests/svg_analyzer_interface.h b/tests/svg_analyzer_interface.h index a0a0bc431..53f40f52c 100644 --- a/tests/svg_analyzer_interface.h +++ b/tests/svg_analyzer_interface.h @@ -22,4 +22,5 @@ public: virtual void on_enter_element_polyline() = 0; virtual void on_enter_element_rect() = 0; virtual void on_enter_element_title() = 0; + virtual void on_exit_element() = 0; }; diff --git a/tests/svg_element.cpp b/tests/svg_element.cpp new file mode 100644 index 000000000..719430c73 --- /dev/null +++ b/tests/svg_element.cpp @@ -0,0 +1,3 @@ +#include "svg_element.h" + +SVG::SVGElement::SVGElement(SVGElementType type) : type(type) {} diff --git a/tests/svg_element.h b/tests/svg_element.h new file mode 100644 index 000000000..0e8acc848 --- /dev/null +++ b/tests/svg_element.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace SVG { + +enum class SVGElementType { + Circle, + Ellipse, + Group, + Line, + Path, + Polygon, + Polyline, + Rect, + Svg, + Text, + Title, +}; + +/** + * @brief The SVGElement class represents an SVG element + */ + +class SVGElement { +public: + SVGElement() = delete; + explicit SVGElement(SVG::SVGElementType type); + + std::vector children; + /// The type of SVG element + const SVGElementType type; +}; + +} // namespace SVG diff --git a/tests/svgpp_context.cpp b/tests/svgpp_context.cpp index b69f5cb98..9d13c607f 100644 --- a/tests/svgpp_context.cpp +++ b/tests/svgpp_context.cpp @@ -46,7 +46,7 @@ void SvgppContext::on_enter_element(svgpp::tag::element::title) { m_svgAnalyzer->on_enter_element_title(); } -void SvgppContext::on_exit_element() {} +void SvgppContext::on_exit_element() { m_svgAnalyzer->on_exit_element(); } void SvgppContext::path_move_to(double x, double y, svgpp::tag::coordinate::absolute c) { -- 2.40.0