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