GridFormat 0.2.1
I/O-Library for grid-like data structures
Loading...
Searching...
No Matches
xml.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2022-2023 Dennis Gläser <dennis.glaeser@iws.uni-stuttgart.de>
2// SPDX-License-Identifier: MIT
8#ifndef GRIDFORMAT_VTK_XML_HPP_
9#define GRIDFORMAT_VTK_XML_HPP_
10
11#include <bit>
12#include <string>
13#include <ranges>
14#include <utility>
15#include <type_traits>
16#include <functional>
17#include <optional>
18#include <iterator>
19#include <string_view>
20#include <concepts>
21
22#include <gridformat/common/detail/crtp.hpp>
23#include <gridformat/common/callable_overload_set.hpp>
24#include <gridformat/common/optional_reference.hpp>
25#include <gridformat/common/exceptions.hpp>
26#include <gridformat/common/type_traits.hpp>
27#include <gridformat/common/variant.hpp>
28#include <gridformat/common/precision.hpp>
29#include <gridformat/common/logging.hpp>
31#include <gridformat/common/lazy_field.hpp>
32#include <gridformat/common/path.hpp>
33
37
41
44#include <gridformat/grid/grid.hpp>
45
46#include <gridformat/xml/element.hpp>
47#include <gridformat/xml/parser.hpp>
48
53
54namespace GridFormat::VTK {
55
56#ifndef DOXYGEN
57namespace XMLDetail {
58 using LZMACompressor = std::conditional_t<Compression::Detail::_have_lzma, Compression::LZMA, None>;
59 using ZLIBCompressor = std::conditional_t<Compression::Detail::_have_zlib, Compression::ZLIB, None>;
60 using LZ4Compressor = std::conditional_t<Compression::Detail::_have_lz4, Compression::LZ4, None>;
61 using Compressor = UniqueVariant<LZ4Compressor, ZLIBCompressor, LZMACompressor>;
62} // namespace XMLDetail
63
64namespace XML {
65
66using HeaderPrecision = std::variant<UInt32, UInt64>;
67using CoordinatePrecision = std::variant<Float32, Float64>;
68using Compressor = ExtendedVariant<XMLDetail::Compressor, None>;
69using Encoder = std::variant<Encoding::Ascii, Encoding::Base64, Encoding::RawBinary>;
70using DataFormat = std::variant<VTK::DataFormat::Inlined, VTK::DataFormat::Appended>;
71using DefaultCompressor = std::variant_alternative_t<0, XMLDetail::Compressor>;
72
73} // namespace XML
74#endif // DOXYGEN
75
99struct XMLOptions {
100 using EncoderOption = ExtendedVariant<XML::Encoder, Automatic>;
101 using CompressorOption = ExtendedVariant<XML::Compressor, Automatic>;
102 using DataFormatOption = ExtendedVariant<XML::DataFormat, Automatic>;
103 using CoordinatePrecisionOption = ExtendedVariant<XML::CoordinatePrecision, Automatic>;
104 EncoderOption encoder = automatic;
105 CompressorOption compressor = automatic;
106 DataFormatOption data_format = automatic;
107 CoordinatePrecisionOption coordinate_precision = automatic;
108 XML::HeaderPrecision header_precision = _from_size_t();
109
110 private:
111 static constexpr XML::HeaderPrecision _from_size_t() {
112 if constexpr(sizeof(std::size_t) == 8) return uint64;
113 else return uint32;
114 }
115};
116
117
118#ifndef DOXYGEN
119namespace XMLDetail {
120
121 struct XMLSettings {
122 XML::Encoder encoder;
123 XML::Compressor compressor;
124 XML::DataFormat data_format;
125 XML::CoordinatePrecision coordinate_precision;
126 XML::HeaderPrecision header_precision;
127
128 template<typename GridCoordinateType>
129 static XMLSettings from(const XMLOptions& opts) {
130 const auto _enc = _make_encoder(opts.encoder);
131 const auto _format = _make_data_format(_enc, opts.data_format);
132 const auto _comp = _make_compressor(_enc, opts.compressor);
133 return {
134 .encoder = _enc,
135 .compressor = _comp,
136 .data_format = _format,
137 .coordinate_precision =
138 Variant::is<Automatic>(opts.coordinate_precision) ?
139 XML::CoordinatePrecision{Precision<GridCoordinateType>{}} :
140 Variant::without<Automatic>(opts.coordinate_precision),
141 .header_precision = opts.header_precision
142 };
143 }
144
145 private:
146 static XML::Encoder _make_encoder(const typename XMLOptions::EncoderOption& enc) {
147 if (Variant::is<Automatic>(enc))
148 return Encoding::base64;
149 return Variant::without<Automatic>(enc);
150 }
151
152 static XML::DataFormat _make_data_format(const typename XML::Encoder& enc,
153 const typename XMLOptions::DataFormatOption& data_format) {
154 if (Variant::is<Automatic>(data_format))
155 return Variant::is<Encoding::Ascii>(enc)
156 ? XML::DataFormat{VTK::DataFormat::inlined}
157 : XML::DataFormat{VTK::DataFormat::appended};
158 return Variant::without<Automatic>(data_format);
159 }
160
161 static XML::Compressor _make_compressor(const typename XML::Encoder& enc,
162 const typename XMLOptions::CompressorOption& compressor) {
163 if (Variant::is<Automatic>(compressor))
164 return Variant::is<Encoding::Ascii>(enc)
165 ? XML::Compressor{none}
166 : XML::Compressor{XML::DefaultCompressor{}};
167 if (Variant::is<Encoding::Ascii>(enc) && !Variant::is<None>(compressor)) {
168 log_warning("Ascii output cannot be compressed. Ignoring chosen compressor...");
169 return none;
170 }
171 return Variant::without<Automatic>(compressor);
172 }
173 };
174
175} // namespace XMLDetail
176#endif // DOXYGEN
177
178
183template<Concepts::Grid G, typename Impl>
185: public GridWriter<G>
186, public GridFormat::Detail::CRTPBase<Impl> {
188 using GridCoordinateType = CoordinateType<G>;
189
190 public:
192 using Grid = G;
193
194 virtual ~XMLWriterBase() = default;
195 explicit XMLWriterBase(const Grid& grid,
196 std::string extension,
197 bool use_structured_grid_ordering,
198 XMLOptions xml_opts = {})
199 : ParentType(grid, std::move(extension), WriterOptions{use_structured_grid_ordering, true})
200 , _xml_opts{std::move(xml_opts)}
201 , _xml_settings{XMLDetail::XMLSettings::from<GridCoordinateType>(_xml_opts)}
202 {}
203
204 XMLWriterBase() = default;
205 XMLWriterBase(XMLWriterBase&&) = default;
206 XMLWriterBase(const XMLWriterBase&) = delete;
207 XMLWriterBase& operator=(XMLWriterBase&&) = default;
208 XMLWriterBase& operator=(const XMLWriterBase&) = delete;
209
210 Impl with(XMLOptions opts) const {
211 auto result = _with(std::move(opts));
212 this->copy_fields(result);
213 return result;
214 }
215
216 Impl with_data_format(const XML::DataFormat& format) const {
217 auto opts = _xml_opts;
218 Variant::unwrap_to(opts.data_format, format);
219 return with(std::move(opts));
220 }
221
222 Impl with_compression(const XML::Compressor& compressor) const {
223 auto opts = _xml_opts;
224 Variant::unwrap_to(opts.compressor, compressor);
225 return with(std::move(opts));
226 }
227
228 Impl with_encoding(const XML::Encoder& encoder) const {
229 auto opts = _xml_opts;
230 Variant::unwrap_to(opts.encoder, encoder);
231 return with(std::move(opts));
232 }
233
234 Impl with_coordinate_precision(const XML::CoordinatePrecision& prec) const {
235 auto opts = _xml_opts;
236 Variant::unwrap_to(opts.coordinate_precision, prec);
237 return with(std::move(opts));
238 }
239
240 Impl with_header_precision(const XML::HeaderPrecision& prec) const {
241 auto opts = _xml_opts;
242 opts.header_precision = prec;
243 return with(std::move(opts));
244 }
245
246 private:
247 virtual Impl _with(XMLOptions opts) const = 0;
248
249 protected:
250 XMLOptions _xml_opts;
251 XMLDetail::XMLSettings _xml_settings;
252
254 std::string vtk_grid_type;
255 XMLElement xml_representation;
256 Appendix appendix;
257 };
258
259 WriteContext _get_write_context(std::string vtk_grid_type) const {
260 return std::visit([&] (const auto& compressor) {
261 return std::visit([&] (const auto& header_precision) {
262 XMLElement xml("VTKFile");
263 xml.set_attribute("type", vtk_grid_type);
264 xml.set_attribute("version", "2.2");
265 xml.set_attribute("byte_order", attribute_name(std::endian::native));
266 xml.set_attribute("header_type", attribute_name(DynamicPrecision{header_precision}));
267 if constexpr (!is_none<decltype(compressor)>)
268 xml.set_attribute("compressor", attribute_name(compressor));
269
270 xml.add_child(vtk_grid_type).add_child("FieldData");
271 WriteContext context{
272 .vtk_grid_type = std::move(vtk_grid_type),
273 .xml_representation = std::move(xml),
274 .appendix = {}
275 };
276 _add_meta_data_fields(context);
277 return context;
278 }, _xml_settings.header_precision);
279 }, _xml_settings.compressor);
280 }
281
282 void _add_meta_data_fields(WriteContext& context) const {
283 XMLElement& field_data = context.xml_representation
284 .get_child(context.vtk_grid_type)
285 .get_child("FieldData");
286 std::visit([&] (const auto& encoder) {
287 std::visit([&] (const auto& compressor) {
288 std::visit([&] (const auto& data_format) {
289 std::visit([&] (const auto& header_precision) {
290 const auto& names = this->_meta_data_field_names();
291 std::ranges::for_each(names, [&] (const std::string& name) {
292 const auto& field = this->_get_meta_data_field(name);
293 const auto layout = field.layout();
294 const auto precision = field.precision();
295
296 auto& array = field_data.add_child("DataArray");
297 array.set_attribute("Name", name);
298 array.set_attribute("format", data_format_name(encoder, data_format));
299 if (precision.template is<char>() && layout.dimension() == 1) {
300 array.set_attribute("type", "String");
301 array.set_attribute("NumberOfTuples", 1);
302 } else {
303 array.set_attribute("NumberOfTuples", layout.extent(0));
304 array.set_attribute("type", attribute_name(precision));
305 array.set_attribute(
306 "NumberOfComponents",
307 layout.dimension() > 1
308 ? layout.sub_layout(1).number_of_entries()
309 : 1
310 );
311 }
312 DataArray content{field, encoder, compressor, header_precision};
313 _set_data_array_content(data_format, array, context.appendix, std::move(content));
314 });
315 }, _xml_settings.header_precision);
316 }, _xml_settings.data_format);
317 }, _xml_settings.compressor);
318 }, _xml_settings.encoder);
319 }
320
321 template<typename ValueType>
322 void _set_attribute(WriteContext& context,
323 std::string_view xml_group,
324 const std::string& attr_name,
325 const ValueType& attr_value) const {
326 _access_at(xml_group, context).set_attribute(attr_name, attr_value);
327 }
328
329 void _set_data_array(WriteContext& context,
330 std::string_view xml_group,
331 std::string data_array_name,
332 const Field& field) const {
333 const auto layout = field.layout();
334 XMLElement& da = _access_at(xml_group, context).add_child("DataArray");
335 da.set_attribute("Name", std::move(data_array_name));
336 da.set_attribute("type", attribute_name(field.precision()));
337 da.set_attribute("NumberOfComponents", (layout.dimension() == 1 ? 1 : layout.number_of_entries(1)));
338 std::visit([&] (const auto& encoder) {
339 std::visit([&] (const auto& compressor) {
340 std::visit([&] (const auto& data_format) {
341 std::visit([&] (const auto& header_prec) {
342 da.set_attribute("format", data_format_name(encoder, data_format));
343 DataArray content{field, encoder, compressor, header_prec};
344 _set_data_array_content(data_format, da, context.appendix, std::move(content));
345 }, _xml_settings.header_precision);
346 }, _xml_settings.data_format);
347 }, _xml_settings.compressor);
348 }, _xml_settings.encoder);
349 }
350
351 template<typename DataFormat, typename Appendix, typename Content>
352 requires(!std::is_lvalue_reference_v<Content>)
353 void _set_data_array_content(const DataFormat&,
354 XMLElement& e,
355 Appendix& app,
356 Content&& c) const {
357 static constexpr bool is_inlined = std::is_same_v<DataFormat, VTK::DataFormat::Inlined>;
358 static constexpr bool is_appended = std::is_same_v<DataFormat, VTK::DataFormat::Appended>;
359 static_assert(is_inlined || is_appended, "Unknown data format");
360
361 if constexpr (is_inlined)
362 e.set_content(std::move(c));
363 else
364 app.add(std::move(c));
365 }
366
367 XMLElement& _access_at(std::string_view path, WriteContext& context) const {
368 return access_or_create_at(path, context.xml_representation.get_child(context.vtk_grid_type));
369 }
370
371 void _write_xml(WriteContext&& context, std::ostream& s) const {
372 Indentation indentation{{.width = 2}};
373 _set_default_active_fields(context.xml_representation.get_child(context.vtk_grid_type));
374 std::visit([&] (const auto& encoder) {
375 std::visit([&] <typename DataFormat> (const DataFormat&) {
376 if constexpr (std::is_same_v<DataFormat, VTK::DataFormat::Inlined>)
377 write_xml_with_version_header(context.xml_representation, s, indentation);
378 else
379 XML::Detail::write_with_appendix(std::move(context), s, encoder, indentation);
380 }, this->_xml_settings.data_format);
381 }, this->_xml_settings.encoder);
382 }
383
384 void _set_default_active_fields(XMLElement& xml) const {
385 const auto set = [&] (std::string_view group, const auto& attr, const auto& name) -> void {
386 auto group_element = access_at(group, xml);
387 if (!group_element)
388 return;
389 group_element.unwrap().set_attribute(attr, name);
390 };
391
392 // discard vectors with more than 3 elements for active arrays
393 const auto get_vector_filter = [] (unsigned int rank) {
394 return [r=rank] (const auto& name_field_ptr_pair) {
395 if (r == 1)
396 return name_field_ptr_pair.second->layout().extent(1) <= 3;
397 return true;
398 };
399 };
400
401 for (std::string_view group : {"Piece/PointData", "PPointData"})
402 for (unsigned int i = 0; i <= 2; ++i)
403 for (const auto& [n, _] : point_fields_of_rank(i, *this)
404 | std::views::filter(get_vector_filter(i))
405 | std::views::take(1))
406 set(group, active_array_attribute_for_rank(i), n);
407
408 for (std::string_view group : {"Piece/CellData", "PCellData"})
409 for (unsigned int i = 0; i <= 2; ++i)
410 for (const auto& [n, _] : cell_fields_of_rank(i, *this)
411 | std::views::filter(get_vector_filter(i))
412 | std::views::take(1))
413 set(group, active_array_attribute_for_rank(i), n);
414 }
415};
416
417
418#ifndef DOXYGEN
419namespace XMLDetail {
420
421 struct DataArrayStreamLocation {
422 std::streamsize begin; // location where data begins
423 std::optional<std::streamsize> offset = {}; // used for appended data
424 };
425
426 template<std::integral I, std::integral O>
427 void _move_to_appendix_position(std::istream& stream, I appendix_begin, O offset_in_appendix) {
428 InputStreamHelper helper{stream};
429 helper.seek_position(appendix_begin);
430 helper.shift_until_not_any_of(" \n\t");
431 if (helper.read_chunk(1) != "_")
432 throw IOError("VTK-XML appendix must start with '_'");
433 helper.shift_by(offset_in_appendix);
434 }
435
436 void _move_to_data(const DataArrayStreamLocation& location, std::istream& s) {
437 if (location.offset)
438 _move_to_appendix_position(s, location.begin, location.offset.value());
439 else {
440 InputStreamHelper helper{s};
441 helper.seek_position(location.begin);
442 helper.shift_whitespace();
443 }
444 }
445
446 template<typename HeaderType>
447 void _decompress_with(const std::string& vtk_compressor,
448 [[maybe_unused]] Serialization& data,
449 [[maybe_unused]] const Compression::CompressedBlocks<HeaderType>& blocks) {
450 if (vtk_compressor == "vtkLZ4DataCompressor") {
451#if GRIDFORMAT_HAVE_LZ4
452 LZ4Compressor{}.decompress(data, blocks);
453#else
454 throw InvalidState("Need LZ4 to decompress the data");
455#endif
456 } else if (vtk_compressor == "vtkLZMADataCompressor") {
457#if GRIDFORMAT_HAVE_LZMA
458 LZMACompressor{}.decompress(data, blocks);
459#else
460 throw InvalidState("Need LZMA to decompress the data");
461#endif
462 } else if (vtk_compressor == "vtkZLibDataCompressor") {
463#if GRIDFORMAT_HAVE_ZLIB
464 ZLIBCompressor{}.decompress(data, blocks);
465#else
466 throw InvalidState("Need ZLib to decompress the data");
467#endif
468 } else {
469 throw NotImplemented("Unsupported vtk compressor '" + vtk_compressor + "'");
470 }
471 }
472
473 template<Concepts::Scalar TargetType, Concepts::Scalar HeaderType = std::size_t>
474 class DataArrayReader {
475 static constexpr Precision<TargetType> target_precision{};
476 static constexpr Precision<HeaderType> header_precision{};
477
478 static constexpr bool is_too_small_integral = std::integral<TargetType> && sizeof(TargetType) < 4;
479 static constexpr bool is_signed_integral = std::signed_integral<TargetType>;
480 using BufferedType = std::conditional_t<is_signed_integral, short, unsigned short>;
481
482 public:
483 using Header = std::vector<HeaderType>;
484
485 DataArrayReader(std::istream& s,
486 std::endian e = std::endian::native,
487 std::string compressor = "")
488 : _stream{s}
489 , _endian{e}
490 , _compressor{compressor}
491 {}
492
493 void read_ascii(std::size_t number_of_values, Serialization& out_values) {
494 out_values.resize(number_of_values*sizeof(TargetType));
495 std::span<TargetType> out_span = out_values.as_span_of(target_precision);
496
497 // istream_view uses operator>>, which seems to do weird stuff for e.g. uint8_t.
498 // The smallest supported integral type seems to be short. In case we deal with
499 // small integral types, we first read into a vector and then cast into the desired type.
500 if constexpr (is_too_small_integral) {
501 std::vector<BufferedType> buffer(number_of_values);
502 _read_ascii_to(std::span{buffer}, number_of_values);
503 std::ranges::copy(buffer | std::views::transform([] <typename T> (const T& value) {
504 return static_cast<TargetType>(value);
505 }), out_span.begin());
506 } else {
507 _read_ascii_to(out_span, number_of_values);
508 }
509 }
510
511 template<Concepts::Decoder Decoder>
512 void read_binary(const Decoder& decoder,
513 OptionalReference<Header> header = {},
514 OptionalReference<Serialization> values = {}) {
515 if constexpr (std::unsigned_integral<HeaderType> && sizeof(HeaderType) >= 4) {
516 if (_compressor.empty())
517 return _read_encoded(decoder, header, values);
518 else
519 return _read_encoded_compressed(decoder, header, values);
520 } else {
521 throw IOError("Unsupported header type");
522 }
523 }
524
525 private:
526 template<typename T, std::size_t size>
527 void _read_ascii_to(std::span<T, size> buffer, std::size_t expected_num_values) const {
528 const auto [_, out] = std::ranges::copy(
529 std::ranges::istream_view<T>{_stream}
530 | std::views::take(expected_num_values),
531 buffer.begin()
532 );
533 const auto number_of_values_read = std::ranges::distance(buffer.begin(), out);
534 if (number_of_values_read < 0 || static_cast<std::size_t>(number_of_values_read) < expected_num_values)
535 throw SizeError("Could not read the requested number of values from the stream");
536 }
537
538 template<typename Decoder>
539 void _read_encoded(const Decoder& decoder,
540 OptionalReference<Header> out_header = {},
541 OptionalReference<Serialization> out_values = {}) {
542 const auto pos = _stream.tellg();
543 Serialization header = decoder.decode_from(_stream, sizeof(HeaderType));
544
545 if (header.size() != sizeof(HeaderType)) { // no padding - header & values are encoded together
546 if (header.size() < sizeof(HeaderType))
547 throw SizeError("Could not read header");
548
549 header.resize(sizeof(HeaderType));
550 change_byte_order(header.as_span_of(header_precision), {.from = _endian});
551
552 if (out_header)
553 std::ranges::copy(header.as_span_of(header_precision), std::back_inserter(out_header.unwrap()));
554
555 if (out_values) {
556 _stream.seekg(pos);
557 const auto number_of_bytes = header.as_span_of(header_precision)[0];
558 const auto number_of_bytes_with_header = number_of_bytes + sizeof(HeaderType);
559 Serialization& header_and_values = out_values.unwrap();
560 header_and_values = decoder.decode_from(_stream, number_of_bytes_with_header);
561 header_and_values.cut_front(sizeof(HeaderType));
562 change_byte_order(header_and_values.as_span_of(header_precision), {.from = _endian});
563 }
564 } else { // values are encoded separately
565 change_byte_order(header.as_span_of(header_precision), {.from = _endian});
566
567 if (out_header)
568 std::ranges::copy(header.as_span_of(header_precision), std::back_inserter(out_header.unwrap()));
569
570 if (out_values) {
571 const auto number_of_bytes = header.as_span_of(header_precision)[0];
572 Serialization& values = out_values.unwrap();
573 values = decoder.decode_from(_stream, number_of_bytes);
574 change_byte_order(values.as_span_of(Precision<TargetType>{}), {.from = _endian});
575 }
576 }
577 }
578
579 template<typename Decoder>
580 void _read_encoded_compressed(const Decoder& decoder,
581 OptionalReference<Header> out_header = {},
582 OptionalReference<Serialization> out_values = {}) {
583 const auto begin_pos = _stream.tellg();
584 const auto header_bytes = sizeof(HeaderType)*3;
585 Serialization header = decoder.decode_from(_stream, header_bytes);
586
587 // if the decoded header is larger than requested, then there is no padding,
588 // which means that we'll have to decode the header together with the values.
589 const bool decode_blocks_with_header = header.size() != header_bytes;
590 if (decode_blocks_with_header)
591 header.resize(header_bytes);
592
593 auto header_data = header.as_span_of(header_precision);
594 if (header_data.size() < 3)
595 throw SizeError("Could not read data array header");
596
597 change_byte_order(header_data, {.from = _endian});
598 const auto number_of_blocks = header_data[0];
599 const auto full_block_size = header_data[1];
600 const auto residual_block_size = header_data[2];
601 const HeaderType number_of_raw_bytes = residual_block_size > 0
602 ? full_block_size*(number_of_blocks-1) + residual_block_size
603 : full_block_size*number_of_blocks;
604
605 Serialization block_sizes;
606 const std::size_t block_sizes_bytes = sizeof(HeaderType)*number_of_blocks;
607 if (decode_blocks_with_header) {
608 _stream.seekg(begin_pos);
609 block_sizes = decoder.decode_from(_stream, header_bytes + block_sizes_bytes);
610 block_sizes.cut_front(sizeof(HeaderType)*3);
611 } else {
612 block_sizes = decoder.decode_from(_stream, block_sizes_bytes);
613 }
614
615 std::vector<HeaderType> compressed_block_sizes(number_of_blocks);
616 change_byte_order(block_sizes.as_span_of(header_precision), {.from = _endian});
617 std::ranges::copy(
618 block_sizes.as_span_of(header_precision),
619 compressed_block_sizes.begin()
620 );
621
622 if (out_header) {
623 std::ranges::copy(header_data, std::back_inserter(out_header.unwrap()));
624 std::ranges::copy(compressed_block_sizes, std::back_inserter(out_header.unwrap()));
625 }
626
627 if (out_values) {
628 Serialization& values = out_values.unwrap();
629 values = decoder.decode_from(_stream, std::accumulate(
630 compressed_block_sizes.begin(),
631 compressed_block_sizes.end(),
632 HeaderType{0}
633 ));
634
635 _decompress_with(_compressor, values, Compression::CompressedBlocks{
636 {number_of_raw_bytes, full_block_size},
637 std::move(compressed_block_sizes)
638 });
639 change_byte_order(values.as_span_of(target_precision), {.from = _endian});
640 }
641 }
642
643 std::istream& _stream;
644 std::endian _endian;
645 std::string _compressor;
646 };
647
648} // namespace XMLDetail
649#endif // DOXYGEN
650
651
652namespace XML {
653
658std::ranges::range auto data_arrays(const XMLElement& e) {
659 return children(e) | std::views::filter([] (const XMLElement& child) {
660 return child.name() == "DataArray";
661 });
662}
663
668std::ranges::range auto data_array_names(const XMLElement& e) {
669 return data_arrays(e) | std::views::transform([] (const XMLElement& data_array) {
670 return data_array.get_attribute("Name");
671 });
672}
673
678const XMLElement& get_data_array(std::string_view name, const XMLElement& section) {
679 for (const auto& da
680 : data_arrays(section)
681 | std::views::filter([&] (const auto& e) { return e.get_attribute("Name") == name; }))
682 return da;
683 throw ValueError(
684 "Could not find data array with name '" + std::string{name}
685 + "' in section '" + std::string{section.name()} + "'"
686 );
687}
688
689} // namespace XML
690
691
692#ifndef DOXYGEN
693namespace XMLDetail {
694
695 // can be reused by readers to copy read field names into the storage container
696 template<typename FieldNameContainer>
697 void copy_field_names_from(const XMLElement& vtk_grid, FieldNameContainer& names) {
698 if (vtk_grid.has_child("Piece")) {
699 const XMLElement& piece = vtk_grid.get_child("Piece");
700 if (piece.has_child("PointData"))
701 std::ranges::copy(
702 XML::data_array_names(piece.get_child("PointData")),
703 std::back_inserter(names.point_fields)
704 );
705 if (piece.has_child("CellData"))
706 std::ranges::copy(
707 XML::data_array_names(piece.get_child("CellData")),
708 std::back_inserter(names.cell_fields)
709 );
710 }
711 if (vtk_grid.has_child("FieldData"))
712 std::ranges::copy(
713 XML::data_array_names(vtk_grid.get_child("FieldData")),
714 std::back_inserter(names.meta_data_fields)
715 );
716 }
717
718} // namespace XMLDetail
719#endif // DOXYGEN
720
721
727 using DataArrayStreamLocation = XMLDetail::DataArrayStreamLocation;
728
729 public:
730 explicit XMLReaderHelper(const std::string& filename)
731 : _filename{filename}
732 , _parser{filename, "ROOT", [] (const XMLElement& e) { return e.name() == "AppendedData"; }} {
733 if (!_element().has_child("VTKFile"))
734 throw IOError("Could not read " + filename + " as vtk-xml file. No root element <VTKFile> found.");
735 }
736
737 static XMLReaderHelper make_from(const std::string& filename, std::string_view vtk_type) {
738 if (!Path::exists(filename))
739 throw IOError("File '" + filename + "' does not exist.");
740 if (!Path::is_file(filename))
741 throw IOError("Given path '" + filename + "' is not a file.");
742
743 std::optional<XMLReaderHelper> helper;
744 try {
745 helper.emplace(XMLReaderHelper{filename});
746 } catch (const std::exception& e) {
747 throw IOError("Could not parse '" + filename + "' as xml file. Error: " + e.what());
748 }
749
750 if (!helper->get().has_attribute("type"))
751 throw IOError("'type' attribute missing in VTKFile root element.");
752 if (helper->get().get_attribute("type") != vtk_type)
753 throw IOError(
754 "Given vtk-xml file has type '"
755 + helper->get().get_attribute("type")
756 + "', expected '" + std::string{vtk_type} + "'"
757 );
758
759 return std::move(helper).value();
760 }
761
762 const XMLElement& get(std::string_view path = "") const {
763 OptionalReference opt_ref = access_at(path, _element().get_child("VTKFile"));
764 if (!opt_ref)
765 throw ValueError("The given path '" + std::string{path} + "' could not be found.");
766 return opt_ref.unwrap();
767 }
768
770 FieldPtr make_points_field(std::string_view section_path, std::size_t num_expected_points) const {
771 std::size_t visited = 0;
772 FieldPtr result{nullptr};
773 for (const auto& data_array : XML::data_arrays(get(section_path))) {
774 if (!result)
775 result = make_data_array_field(data_array.get_attribute("Name"), section_path, num_expected_points);
776 else {
777 log_warning("Points section contains more than one data array, using first one as point coordinates");
778 break;
779 }
780 visited++;
781 }
782 if (visited == 0)
783 throw ValueError("Points section does not contain a data array element");
784 return result;
785 }
786
788 FieldPtr make_data_array_field(std::string_view name,
789 std::string_view section_path,
790 const std::optional<std::size_t> number_of_tuples = {}) const {
791 return make_data_array_field(XML::get_data_array(name, get(section_path)), number_of_tuples);
792 }
793
795 FieldPtr make_data_array_field(const XMLElement& element,
796 const std::optional<std::size_t> number_of_tuples = {}) const {
797 if (element.name() != "DataArray")
798 throw ValueError("Given path is not a DataArray element");
799 if (!element.has_attribute("type"))
800 throw ValueError("DataArray element does not specify the data type (`type` attribute)");
801 if (!element.has_attribute("format"))
802 throw ValueError("Data array element does not specify its format (e.g. ascii/binary)");
803 if (number_of_tuples.has_value())
804 return _make_data_array_field(element, number_of_tuples.value());
805 return _make_data_array_field(element, _number_of_tuples(element));
806 }
807
808 private:
809 const XMLElement& _element() const {
810 return _parser.get_xml();
811 }
812
813 const XMLElement& _appendix() const {
814 if (!_element().get_child("VTKFile").has_child("AppendedData"))
815 throw ValueError("Read vtk file has no appendix");
816 return _element().get_child("VTKFile").get_child("AppendedData");
817 }
818
819 FieldPtr _make_data_array_field(const XMLElement& element,
820 const std::size_t number_of_tuples) const {
821 if (element.name() != "DataArray")
822 throw ValueError("Given xml element does not describe a data array");
823 if (!element.has_attribute("format"))
824 throw ValueError("Data array element does not specify its format (e.g. ascii/binary)");
825 if (!element.has_attribute("type"))
826 throw ValueError("Data array element does not specify the data type");
827 if (element.get_attribute("format") == "appended" && !element.has_attribute("offset"))
828 throw ValueError("Data array element specifies to use appended data but does not specify offset");
829 return element.get_attribute("format") == "ascii"
830 ? _make_ascii_data_array_field(element, number_of_tuples)
831 : _make_binary_data_array_field(element, number_of_tuples);
832 }
833
834 FieldPtr _make_ascii_data_array_field(const XMLElement& e, const std::size_t num_tuples) const {
835 MDLayout expected_layout = _expected_layout(e, num_tuples);
836 auto num_values = expected_layout.number_of_entries();
837 return from_precision_attribute(e.get_attribute("type")).visit([&] <typename T> (const Precision<T>& prec) {
838 return make_field_ptr(LazyField{
839 std::string{_filename},
840 std::move(expected_layout),
841 prec,
842 [_nv=num_values, _begin=_parser.get_content_bounds(e).begin_pos] (std::string filename) {
843 std::ifstream file{filename};
844 file.seekg(_begin);
845 Serialization result{_nv*sizeof(T)};
846 XMLDetail::DataArrayReader<T>{file}.read_ascii(_nv, result);
847 return result;
848 }
849 });
850 });
851 }
852
853 FieldPtr _make_binary_data_array_field(const XMLElement& e, const std::size_t num_tuples) const {
854 auto expected_layout = _expected_layout(e, num_tuples);
855 return from_precision_attribute(e.get_attribute("type")).visit([&] <typename T> (const Precision<T>& prec) {
856 FieldPtr result;
857 _apply_decoder_for(e, [&] (auto&& decoder) {
858 result = make_field_ptr(LazyField{
859 std::string{_filename},
860 std::move(expected_layout),
861 prec,
862 [
863 _loc=_stream_location_for(e),
864 _header_prec=from_precision_attribute(get().get_attribute("header_type")),
865 _endian=from_endian_attribute(get().get_attribute("byte_order")),
866 _comp=get().get_attribute_or(std::string{""}, "compressor"),
867 _decoder=std::move(decoder)
868 ] (std::string filename) {
869 std::ifstream file{filename};
870 XMLDetail::_move_to_data(_loc, file);
871 return _header_prec.visit([&] <typename H> (const Precision<H>&) {
872 Serialization result;
873 XMLDetail::DataArrayReader<T, H>{file, _endian, _comp}.read_binary(_decoder, {}, result);
874 return result;
875 });
876 }
877 });
878 });
879 return result;
880 });
881 }
882
883 MDLayout _expected_layout(const XMLElement& e, const std::size_t num_tuples) const {
884 const auto num_comps = e.get_attribute_or(std::size_t{1}, "NumberOfComponents");
885 if (num_comps > 1)
886 return MDLayout{{num_tuples, num_comps}};
887 return MDLayout{{num_tuples}};
888 }
889
890 std::size_t _number_of_tuples(const XMLElement& element) const {
891 const auto number_of_components = element.get_attribute_or(std::size_t{1}, "NumberOfComponents");
892 const auto number_of_values = [&] () {
893 if (element.get_attribute("type") != "String") {
894 if (element.has_attribute("NumberOfTuples"))
895 return from_string<std::size_t>(element.get_attribute("NumberOfTuples"));
896 } else if (element.has_attribute("NumberOfTuples")) {
897 const auto num_values = from_string<std::size_t>(element.get_attribute("NumberOfTuples"));
898 if (num_values > 1)
899 throw ValueError("Cannot read string data arrays with more than one tuple");
900 }
901 return _deduce_number_of_values(element);
902 } ();
903
904 if (number_of_values%number_of_components != 0)
905 throw ValueError(
906 "The number of components of data array '" + element.name() + "' "
907 + "(" + as_string(number_of_components) + ") "
908 + "is incompatible with the number of values it contains "
909 + "(" + as_string(number_of_values) + ")"
910 );
911 return number_of_values/number_of_components;
912 }
913
914 std::size_t _deduce_number_of_values(const XMLElement& element) const {
915 std::ifstream file{_filename};
916 XMLDetail::_move_to_data(_stream_location_for(element), file);
917
918 if (element.get_attribute("format") == "ascii") {
919 const auto precision = from_precision_attribute(element.get_attribute("type"));
920 return precision.visit([&] <typename T> (const Precision<T>&) {
921 using _T = std::conditional_t<
922 std::integral<T> && sizeof(T) < 4, // see XMLDetail::DataArrayReader::read_ascii
923 int,
924 T
925 >;
926 return std::ranges::distance(std::ranges::istream_view<_T>{file});
927 });
928 }
929
930 InputStreamHelper helper{file};
931 const auto header = _read_binary_data_array_header(helper, element);
932 if (get().has_attribute("compressor") && header.size() < 3)
933 throw ValueError("Could not read compression header");
934 const std::size_t number_of_bytes = [&] () {
935 if (get().has_attribute("compressor")) {
936 const auto num_full_blocks = header.at(0);
937 const auto full_block_size = header.at(1);
938 const auto residual_block_size = header.at(2);
939 return residual_block_size > 0
940 ? full_block_size*(num_full_blocks - 1) + residual_block_size
941 : full_block_size*num_full_blocks;
942 }
943 return header.at(0);
944 } ();
945 const std::size_t value_type_number_of_bytes = from_precision_attribute(
946 element.get_attribute("type")
947 ).size_in_bytes();
948
949 if (number_of_bytes%value_type_number_of_bytes != 0)
950 throw ValueError(
951 "The length of the data array '" + element.name() + "' "
952 + "is incompatible with the data type '" + element.get_attribute("type") + "'"
953 );
954
955 return number_of_bytes/value_type_number_of_bytes;
956 }
957
958 DataArrayStreamLocation _stream_location_for(const XMLElement& element) const {
959 if (element.get_attribute("format") == "appended")
960 return DataArrayStreamLocation{
961 .begin = _parser.get_content_bounds(_appendix()).begin_pos,
962 .offset = from_string<std::size_t>(element.get_attribute("offset"))
963 };
964 return DataArrayStreamLocation{.begin = _parser.get_content_bounds(element).begin_pos};
965 }
966
967 std::vector<std::size_t> _read_binary_data_array_header(InputStreamHelper& stream,
968 const XMLElement& element) const {
969 const std::string compressor = get().get_attribute_or(std::string{""}, "compressor");
970 const std::endian endian = from_endian_attribute(get().get_attribute("byte_order"));
971 const auto header_prec = from_precision_attribute(get().get_attribute("header_type"));
972 const auto values_prec = from_precision_attribute(element.get_attribute("type"));
973
974 return header_prec.visit([&] <typename HT> (const Precision<HT>&) {
975 return values_prec.visit([&] <typename VT> (const Precision<VT>&) {
976 std::vector<HT> _header;
977 XMLDetail::DataArrayReader<VT, HT> reader{stream, endian, compressor};
978 _apply_decoder_for(element, [&] (const auto& decoder) {
979 reader.read_binary(decoder, _header);
980 });
981
982 if (_header.empty())
983 throw IOError("Could not read header for data array '" + element.get_attribute("Name") + "'");
984
985 std::vector<std::size_t> result;
986 std::ranges::for_each(_header, [&] <typename T> (const T& value) {
987 result.push_back(static_cast<std::size_t>(value));
988 });
989 return result;
990 });
991 });
992 }
993
994 template<typename Action>
995 void _apply_decoder_for(const XMLElement& data_array, const Action& action) const {
996 if (data_array.get_attribute("format") == "binary")
997 return action(Base64Decoder{});
998 else if (data_array.get_attribute("format") == "appended")
999 return _appendix().get_attribute("encoding") == "base64"
1000 ? action(Base64Decoder{})
1001 : action(RawDecoder{});
1002 throw InvalidState("Unknown data format");
1003 }
1004
1005 std::string _filename;
1006 XMLParser _parser;
1007};
1008
1009} // namespace GridFormat::VTK
1010
1011#endif // GRIDFORMAT_VTK_XML_HPP_
Helper classes for writing VTK appendices of xml formats.
Encoder and stream using ascii.
Helper functions to get the VTK-specific names of things.
Encoder and stream using base64.
friend Concepts::RangeOf< std::pair< std::string, FieldPtr > > auto cell_fields_of_rank(unsigned int rank, const GridWriterBase &writer)
Return a range over the fields with the given rank (0=scalars, 1=vectors, 2=tensors)
Definition: writer.hpp:200
friend Concepts::RangeOf< std::pair< std::string, FieldPtr > > auto point_fields_of_rank(unsigned int rank, const GridWriterBase &writer)
Return a range over the fields with the given rank (0=scalars, 1=vectors, 2=tensors)
Definition: writer.hpp:187
Abstract base class for grid file writers.
Definition: writer.hpp:303
Stores vtk data arrays to be exported as vtk-xml appendix.
Definition: appendix.hpp:53
Helper class for VTK-XML readers to use.
Definition: xml.hpp:726
FieldPtr make_data_array_field(const XMLElement &element, const std::optional< std::size_t > number_of_tuples={}) const
Returns a field which draws the actual field values from the file upon request.
Definition: xml.hpp:795
FieldPtr make_points_field(std::string_view section_path, std::size_t num_expected_points) const
Returns the field representing the points of the grid.
Definition: xml.hpp:770
FieldPtr make_data_array_field(std::string_view name, std::string_view section_path, const std::optional< std::size_t > number_of_tuples={}) const
Returns a field which draws the actual field values from the file upon request.
Definition: xml.hpp:788
Base class for VTK-XML Writer implementations.
Definition: xml.hpp:186
G Grid
Export underlying grid type.
Definition: xml.hpp:192
Grid concepts.
Base classes for grid data writers.
std::shared_ptr< const Field > FieldPtr
Pointer type used by writers/readers for fields.
Definition: field.hpp:186
FieldPtr make_field_ptr(F &&f)
Factory function for field pointers.
Definition: field.hpp:192
std::ranges::range auto data_arrays(const XMLElement &e)
Return a range over all data array elements in the given xml section.
Definition: xml.hpp:658
std::ranges::range auto data_array_names(const XMLElement &e)
Return a range over the names of all data array elements in the given xml section.
Definition: xml.hpp:668
const XMLElement & get_data_array(std::string_view name, const XMLElement &section)
Return the data array element with the given name within the given xml section.
Definition: xml.hpp:678
Compressor using the LZ4 library.
Compressor using the LZMA library.
Encoder and stream for raw binary output.
Options for VTK-XML files for setting the desired encoding, data format and compression.
Definition: xml.hpp:99
Options that writer implementations can pass to the base class.
Definition: writer.hpp:59
Common functionality for VTK writers.
Compressor using the ZLIB library.