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