| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-FileCopyrightText: 2022-2023 Dennis Gläser <dennis.glaeser@iws.uni-stuttgart.de> | ||
| 2 | // SPDX-License-Identifier: MIT | ||
| 3 | /*! | ||
| 4 | * \file | ||
| 5 | * \ingroup VTK | ||
| 6 | * \brief Helper classes for writing VTK appendices of xml formats | ||
| 7 | */ | ||
| 8 | #ifndef GRIDFORMAT_VTK_APPENDIX_HPP_ | ||
| 9 | #define GRIDFORMAT_VTK_APPENDIX_HPP_ | ||
| 10 | |||
| 11 | #include <concepts> | ||
| 12 | #include <utility> | ||
| 13 | #include <ostream> | ||
| 14 | #include <string> | ||
| 15 | #include <vector> | ||
| 16 | #include <memory> | ||
| 17 | #include <limits> | ||
| 18 | #include <type_traits> | ||
| 19 | |||
| 20 | #include <gridformat/common/concepts.hpp> | ||
| 21 | #include <gridformat/common/indentation.hpp> | ||
| 22 | #include <gridformat/xml/element.hpp> | ||
| 23 | #include <gridformat/vtk/common.hpp> | ||
| 24 | #include <gridformat/vtk/data_array.hpp> | ||
| 25 | #include <gridformat/vtk/attributes.hpp> | ||
| 26 | |||
| 27 | namespace GridFormat::VTK { | ||
| 28 | |||
| 29 | /*! | ||
| 30 | * \ingroup VTK | ||
| 31 | * \brief Observer for appendices. | ||
| 32 | * Allows registering the offsets when streaming | ||
| 33 | * all fields that are part of the appendix. | ||
| 34 | */ | ||
| 35 | class AppendixStreamObserver { | ||
| 36 | public: | ||
| 37 | 96404 | void register_offset(std::size_t offset) { | |
| 38 | 96404 | _offsets.push_back(offset); | |
| 39 | 96404 | } | |
| 40 | |||
| 41 | 6152 | const std::vector<std::size_t>& offsets() const { | |
| 42 | 6152 | return _offsets; | |
| 43 | } | ||
| 44 | |||
| 45 | private: | ||
| 46 | std::vector<std::size_t> _offsets; | ||
| 47 | }; | ||
| 48 | |||
| 49 | /*! | ||
| 50 | * \ingroup VTK | ||
| 51 | * \brief Stores vtk data arrays to be exported as vtk-xml appendix. | ||
| 52 | */ | ||
| 53 | class Appendix { | ||
| 54 | struct DataArrayModel { | ||
| 55 | 385616 | virtual ~DataArrayModel() = default; | |
| 56 | |||
| 57 | 96404 | DataArrayModel() = default; | |
| 58 | 96404 | DataArrayModel(DataArrayModel&&) = default; | |
| 59 | DataArrayModel(const DataArrayModel&) = delete; | ||
| 60 | DataArrayModel& operator=(DataArrayModel&&) = default; | ||
| 61 | DataArrayModel& operator=(const DataArrayModel&) = delete; | ||
| 62 | |||
| 63 | 96404 | friend std::ostream& operator<<(std::ostream& s, const DataArrayModel& arr) { | |
| 64 | 96404 | arr.stream(s); | |
| 65 | 96404 | return s; | |
| 66 | } | ||
| 67 | |||
| 68 | private: | ||
| 69 | virtual void stream(std::ostream& s) const = 0; | ||
| 70 | }; | ||
| 71 | |||
| 72 | template<Concepts::StreamableWith<std::ostream> DataArray> | ||
| 73 | class DataArrayImpl : public DataArrayModel { | ||
| 74 | public: | ||
| 75 | 192808 | explicit DataArrayImpl(DataArray&& arr) | |
| 76 | 192808 | : _data_array(std::move(arr)) | |
| 77 | 192808 | {} | |
| 78 | |||
| 79 | private: | ||
| 80 | DataArray _data_array; | ||
| 81 | |||
| 82 | 192808 | void stream(std::ostream& s) const override { | |
| 83 | 192808 | s << _data_array; | |
| 84 | 192808 | } | |
| 85 | }; | ||
| 86 | |||
| 87 | public: | ||
| 88 | template<typename... Args> | ||
| 89 | 192808 | void add(DataArray<Args...>&& data_array) { | |
| 90 |
1/2✓ Branch 3 taken 96404 times.
✗ Branch 4 not taken.
|
192808 | _emplace_back(DataArrayImpl{std::move(data_array)}); |
| 91 | 192808 | } | |
| 92 | |||
| 93 | 6152 | friend std::ostream& operator<<(std::ostream& s, const Appendix& app) { | |
| 94 |
1/2✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
|
6152 | const auto start_pos = s.tellp(); |
| 95 |
2/2✓ Branch 5 taken 96404 times.
✓ Branch 6 taken 6152 times.
|
102556 | for (const auto& array_ptr : app._content) { |
| 96 |
1/2✓ Branch 1 taken 96404 times.
✗ Branch 2 not taken.
|
96404 | const auto pos_before = s.tellp(); |
| 97 |
1/2✓ Branch 2 taken 96404 times.
✗ Branch 3 not taken.
|
96404 | s << *array_ptr; |
| 98 |
1/2✓ Branch 0 taken 96404 times.
✗ Branch 1 not taken.
|
96404 | if (app._observer) |
| 99 |
1/2✓ Branch 2 taken 96404 times.
✗ Branch 3 not taken.
|
96404 | app._observer->register_offset(pos_before - start_pos); |
| 100 | } | ||
| 101 | 6152 | return s; | |
| 102 | } | ||
| 103 | |||
| 104 | 6152 | void set_observer(AppendixStreamObserver* observer) { | |
| 105 | 6152 | _observer = observer; | |
| 106 | 6152 | } | |
| 107 | |||
| 108 | private: | ||
| 109 | template<typename DA> | ||
| 110 | 192808 | void _emplace_back(DataArrayImpl<DA>&& impl) { | |
| 111 |
1/2✓ Branch 1 taken 96404 times.
✗ Branch 2 not taken.
|
192808 | _content.emplace_back( |
| 112 |
1/2✓ Branch 2 taken 96404 times.
✗ Branch 3 not taken.
|
385616 | std::make_unique<DataArrayImpl<DA>>(std::move(impl)) |
| 113 | ); | ||
| 114 | 192808 | } | |
| 115 | |||
| 116 | std::vector<std::unique_ptr<const DataArrayModel>> _content; | ||
| 117 | AppendixStreamObserver* _observer{nullptr}; | ||
| 118 | }; | ||
| 119 | |||
| 120 | |||
| 121 | #ifndef DOXYGEN | ||
| 122 | namespace XML::Detail { | ||
| 123 | |||
| 124 | struct XMLAppendixContent { | ||
| 125 | const Appendix& appendix; | ||
| 126 | |||
| 127 | 6152 | friend std::ostream& operator<<(std::ostream& s, const XMLAppendixContent& c) { | |
| 128 | 6152 | s << " _"; // prefix required by vtk | |
| 129 | 6152 | s << c.appendix; | |
| 130 | 6152 | s << "\n"; // newline such that closing xml element is easily visible | |
| 131 | 6152 | return s; | |
| 132 | } | ||
| 133 | }; | ||
| 134 | |||
| 135 | 144083 | void write_xml_element_with_offsets(const XMLElement& e, | |
| 136 | std::ostream& s, | ||
| 137 | Indentation& ind, | ||
| 138 | std::vector<std::size_t>& offset_positions) { | ||
| 139 | const std::string empty_string_for_max_header( | ||
| 140 |
1/2✓ Branch 2 taken 144083 times.
✗ Branch 3 not taken.
|
288166 | std::to_string(std::numeric_limits<std::size_t>::max()).size(), |
| 141 | ' ' | ||
| 142 |
1/2✓ Branch 2 taken 144083 times.
✗ Branch 3 not taken.
|
144083 | ); |
| 143 | |||
| 144 | 96404 | const auto cache_offset_xml_pos = [&] () { | |
| 145 | 96404 | s << " offset=\""; | |
| 146 |
2/4✓ Branch 1 taken 96404 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 96404 times.
✗ Branch 6 not taken.
|
96404 | offset_positions.push_back(s.tellp()); |
| 147 | 96404 | s << empty_string_for_max_header; | |
| 148 | 96404 | s << "\""; | |
| 149 | 96404 | }; | |
| 150 | |||
| 151 |
6/6✓ Branch 1 taken 137931 times.
✓ Branch 2 taken 6152 times.
✓ Branch 4 taken 96550 times.
✓ Branch 5 taken 41381 times.
✓ Branch 6 taken 96550 times.
✓ Branch 7 taken 47533 times.
|
144083 | if (!e.has_content() && e.number_of_children() == 0) { |
| 152 |
1/2✓ Branch 1 taken 96550 times.
✗ Branch 2 not taken.
|
96550 | s << ind; |
| 153 |
1/2✓ Branch 2 taken 96550 times.
✗ Branch 3 not taken.
|
96550 | GridFormat::XML::Detail::write_xml_tag_open(e, s, ""); |
| 154 |
3/4✓ Branch 2 taken 96550 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 96404 times.
✓ Branch 5 taken 146 times.
|
96550 | if (e.name() == "DataArray") |
| 155 |
1/2✓ Branch 1 taken 96404 times.
✗ Branch 2 not taken.
|
96404 | cache_offset_xml_pos(); |
| 156 |
1/2✓ Branch 1 taken 96550 times.
✗ Branch 2 not taken.
|
96550 | s << "/>"; |
| 157 | } else { | ||
| 158 |
1/2✓ Branch 1 taken 47533 times.
✗ Branch 2 not taken.
|
47533 | s << ind; |
| 159 |
1/2✓ Branch 1 taken 47533 times.
✗ Branch 2 not taken.
|
47533 | GridFormat::XML::Detail::write_xml_tag_open(e, s); |
| 160 |
2/4✓ Branch 2 taken 47533 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 47533 times.
|
47533 | if (e.name() == "DataArray") |
| 161 | ✗ | cache_offset_xml_pos(); | |
| 162 |
1/2✓ Branch 1 taken 47533 times.
✗ Branch 2 not taken.
|
47533 | s << "\n"; |
| 163 | |||
| 164 |
2/2✓ Branch 1 taken 6152 times.
✓ Branch 2 taken 41381 times.
|
47533 | if (e.has_content()) { |
| 165 |
1/2✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
|
6152 | e.stream_content(s); |
| 166 |
1/2✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
|
6152 | s << "\n"; |
| 167 | } | ||
| 168 | |||
| 169 |
1/2✓ Branch 1 taken 47533 times.
✗ Branch 2 not taken.
|
47533 | ++ind; |
| 170 |
2/2✓ Branch 6 taken 137931 times.
✓ Branch 7 taken 47533 times.
|
185464 | for (const auto& c : children(e)) { |
| 171 |
1/2✓ Branch 1 taken 137931 times.
✗ Branch 2 not taken.
|
137931 | write_xml_element_with_offsets(c, s, ind, offset_positions); |
| 172 |
1/2✓ Branch 1 taken 137931 times.
✗ Branch 2 not taken.
|
137931 | s << "\n"; |
| 173 | } | ||
| 174 |
1/2✓ Branch 1 taken 47533 times.
✗ Branch 2 not taken.
|
47533 | --ind; |
| 175 | |||
| 176 |
1/2✓ Branch 1 taken 47533 times.
✗ Branch 2 not taken.
|
47533 | s << ind; |
| 177 |
1/2✓ Branch 1 taken 47533 times.
✗ Branch 2 not taken.
|
47533 | GridFormat::XML::Detail::write_xml_tag_close(e, s); |
| 178 | } | ||
| 179 | 144083 | } | |
| 180 | |||
| 181 | 6152 | std::vector<std::size_t> write_xml_element_with_offsets(const XMLElement& e, | |
| 182 | std::ostream& s, | ||
| 183 | Indentation& ind) { | ||
| 184 | 6152 | std::vector<std::size_t> offset_positions; | |
| 185 |
1/2✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
|
6152 | write_xml_element_with_offsets(e, s, ind, offset_positions); |
| 186 | 6152 | return offset_positions; | |
| 187 | ✗ | } | |
| 188 | |||
| 189 | template<typename Context, typename Encoder> | ||
| 190 | requires(!std::is_const_v<std::remove_reference_t<Context>>) | ||
| 191 | 12304 | inline void write_with_appendix(Context&& context, | |
| 192 | std::ostream& s, | ||
| 193 | const Encoder& encoder, | ||
| 194 | Indentation indentation = {}) { | ||
| 195 |
2/2✓ Branch 1 taken 4377 times.
✓ Branch 2 taken 1775 times.
|
12304 | if (produces_valid_xml(encoder)) |
| 196 |
1/2✓ Branch 1 taken 4377 times.
✗ Branch 2 not taken.
|
8754 | s << "<?xml version=\"1.0\"?>\n"; |
| 197 | |||
| 198 | 12304 | AppendixStreamObserver observer; | |
| 199 | 12304 | context.appendix.set_observer(&observer); | |
| 200 | |||
| 201 |
2/4✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6152 times.
✗ Branch 5 not taken.
|
24608 | auto& app_element = context.xml_representation.add_child("AppendedData"); |
| 202 |
3/6✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6152 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6152 times.
✗ Branch 8 not taken.
|
36912 | app_element.set_attribute("encoding", attribute_name(encoder)); |
| 203 |
1/2✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
|
12304 | app_element.set_content(Detail::XMLAppendixContent{context.appendix}); |
| 204 | |||
| 205 | 12304 | const auto offset_positions = write_xml_element_with_offsets( | |
| 206 |
1/2✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
|
12304 | context.xml_representation, s, indentation |
| 207 | ); | ||
| 208 | 12304 | const auto& offsets = observer.offsets(); | |
| 209 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 6152 times.
|
12304 | if (offsets.size() != offset_positions.size()) |
| 210 | ✗ | throw SizeError("Number of written & registered offsets does not match"); | |
| 211 | |||
| 212 |
1/2✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
|
12304 | const auto cur_pos = s.tellp(); |
| 213 |
2/2✓ Branch 2 taken 96404 times.
✓ Branch 3 taken 6152 times.
|
205112 | for (std::size_t i = 0; i < offsets.size(); ++i) { |
| 214 |
1/2✓ Branch 3 taken 96404 times.
✗ Branch 4 not taken.
|
192808 | s.seekp(offset_positions[i]); |
| 215 |
1/2✓ Branch 2 taken 96404 times.
✗ Branch 3 not taken.
|
192808 | const std::string offset_str = std::to_string(offsets[i]); |
| 216 |
1/2✓ Branch 3 taken 96404 times.
✗ Branch 4 not taken.
|
192808 | s.write(offset_str.data(), offset_str.size()); |
| 217 | } | ||
| 218 |
1/2✓ Branch 1 taken 6152 times.
✗ Branch 2 not taken.
|
12304 | s.seekp(cur_pos); |
| 219 | 12304 | } | |
| 220 | |||
| 221 | } // namespace XML::Detail | ||
| 222 | #endif // DOXYGEN | ||
| 223 | |||
| 224 | } // namespace GridFormat::VTK | ||
| 225 | |||
| 226 | #endif // GRIDFORMAT_VTK_APPENDIX_HPP_ | ||
| 227 |