8#ifndef GRIDFORMAT_VTK_XML_HPP_
9#define GRIDFORMAT_VTK_XML_HPP_
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>
44#include <gridformat/grid/grid.hpp>
46#include <gridformat/xml/element.hpp>
47#include <gridformat/xml/parser.hpp>
54namespace GridFormat::VTK {
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>;
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>;
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();
111 static constexpr XML::HeaderPrecision _from_size_t() {
112 if constexpr(
sizeof(std::size_t) == 8)
return uint64;
122 XML::Encoder encoder;
123 XML::Compressor compressor;
124 XML::DataFormat data_format;
125 XML::CoordinatePrecision coordinate_precision;
126 XML::HeaderPrecision header_precision;
128 template<
typename Gr
idCoordinateType>
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);
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
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);
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);
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...");
171 return Variant::without<Automatic>(compressor);
183template<Concepts::Gr
id G,
typename Impl>
186,
public GridFormat::Detail::CRTPBase<Impl> {
188 using GridCoordinateType = CoordinateType<G>;
196 std::string extension,
197 bool use_structured_grid_ordering,
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)}
204 XMLWriterBase() =
default;
205 XMLWriterBase(XMLWriterBase&&) =
default;
206 XMLWriterBase(
const XMLWriterBase&) =
delete;
207 XMLWriterBase& operator=(XMLWriterBase&&) =
default;
208 XMLWriterBase& operator=(
const XMLWriterBase&) =
delete;
210 Impl with(XMLOptions opts)
const {
211 auto result = _with(std::move(opts));
212 this->copy_fields(result);
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));
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));
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));
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));
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));
247 virtual Impl _with(XMLOptions opts)
const = 0;
250 XMLOptions _xml_opts;
251 XMLDetail::XMLSettings _xml_settings;
254 std::string vtk_grid_type;
255 XMLElement xml_representation;
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));
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),
276 _add_meta_data_fields(context);
278 }, _xml_settings.header_precision);
279 }, _xml_settings.compressor);
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();
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);
303 array.set_attribute(
"NumberOfTuples", layout.extent(0));
304 array.set_attribute(
"type", attribute_name(precision));
306 "NumberOfComponents",
307 layout.dimension() > 1
308 ? layout.sub_layout(1).number_of_entries()
312 DataArray content{field, encoder, compressor, header_precision};
313 _set_data_array_content(data_format, array, context.appendix, std::move(content));
315 }, _xml_settings.header_precision);
316 }, _xml_settings.data_format);
317 }, _xml_settings.compressor);
318 }, _xml_settings.encoder);
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);
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);
351 template<
typename DataFormat,
typename Appendix,
typename Content>
352 requires(!std::is_lvalue_reference_v<Content>)
353 void _set_data_array_content(
const DataFormat&,
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");
361 if constexpr (is_inlined)
362 e.set_content(std::move(c));
364 app.add(std::move(c));
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));
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);
379 XML::Detail::write_with_appendix(std::move(context), s, encoder, indentation);
380 }, this->_xml_settings.data_format);
381 }, this->_xml_settings.encoder);
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);
389 group_element.unwrap().set_attribute(attr, name);
393 const auto get_vector_filter = [] (
unsigned int rank) {
394 return [r=rank] (
const auto& name_field_ptr_pair) {
396 return name_field_ptr_pair.second->layout().extent(1) <= 3;
401 for (std::string_view group : {
"Piece/PointData",
"PPointData"})
402 for (
unsigned int i = 0; i <= 2; ++i)
404 | std::views::filter(get_vector_filter(i))
405 | std::views::take(1))
406 set(group, active_array_attribute_for_rank(i), n);
408 for (std::string_view group : {
"Piece/CellData",
"PCellData"})
409 for (
unsigned int i = 0; i <= 2; ++i)
411 | std::views::filter(get_vector_filter(i))
412 | std::views::take(1))
413 set(group, active_array_attribute_for_rank(i), n);
421 struct DataArrayStreamLocation {
422 std::streamsize begin;
423 std::optional<std::streamsize> offset = {};
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);
436 void _move_to_data(
const DataArrayStreamLocation& location, std::istream& s) {
438 _move_to_appendix_position(s, location.begin, location.offset.value());
440 InputStreamHelper helper{s};
441 helper.seek_position(location.begin);
442 helper.shift_whitespace();
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);
454 throw InvalidState(
"Need LZ4 to decompress the data");
456 }
else if (vtk_compressor ==
"vtkLZMADataCompressor") {
457#if GRIDFORMAT_HAVE_LZMA
458 LZMACompressor{}.decompress(data, blocks);
460 throw InvalidState(
"Need LZMA to decompress the data");
462 }
else if (vtk_compressor ==
"vtkZLibDataCompressor") {
463#if GRIDFORMAT_HAVE_ZLIB
464 ZLIBCompressor{}.decompress(data, blocks);
466 throw InvalidState(
"Need ZLib to decompress the data");
469 throw NotImplemented(
"Unsupported vtk compressor '" + vtk_compressor +
"'");
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{};
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>;
483 using Header = std::vector<HeaderType>;
485 DataArrayReader(std::istream& s,
486 std::endian e = std::endian::native,
487 std::string compressor =
"")
490 , _compressor{compressor}
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);
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());
507 _read_ascii_to(out_span, number_of_values);
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);
519 return _read_encoded_compressed(decoder, header, values);
521 throw IOError(
"Unsupported header type");
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),
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");
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));
545 if (header.size() !=
sizeof(HeaderType)) {
546 if (header.size() <
sizeof(HeaderType))
547 throw SizeError(
"Could not read header");
549 header.resize(
sizeof(HeaderType));
550 change_byte_order(header.as_span_of(header_precision), {.from = _endian});
553 std::ranges::copy(header.as_span_of(header_precision), std::back_inserter(out_header.unwrap()));
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});
565 change_byte_order(header.as_span_of(header_precision), {.from = _endian});
568 std::ranges::copy(header.as_span_of(header_precision), std::back_inserter(out_header.unwrap()));
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});
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);
589 const bool decode_blocks_with_header = header.size() != header_bytes;
590 if (decode_blocks_with_header)
591 header.resize(header_bytes);
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");
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;
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);
612 block_sizes = decoder.decode_from(_stream, block_sizes_bytes);
615 std::vector<HeaderType> compressed_block_sizes(number_of_blocks);
616 change_byte_order(block_sizes.as_span_of(header_precision), {.from = _endian});
618 block_sizes.as_span_of(header_precision),
619 compressed_block_sizes.begin()
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()));
628 Serialization& values = out_values.unwrap();
629 values = decoder.decode_from(_stream, std::accumulate(
630 compressed_block_sizes.begin(),
631 compressed_block_sizes.end(),
635 _decompress_with(_compressor, values, Compression::CompressedBlocks{
636 {number_of_raw_bytes, full_block_size},
637 std::move(compressed_block_sizes)
639 change_byte_order(values.as_span_of(target_precision), {.from = _endian});
643 std::istream& _stream;
645 std::string _compressor;
659 return children(e) | std::views::filter([] (
const XMLElement& child) {
660 return child.name() ==
"DataArray";
669 return data_arrays(e) | std::views::transform([] (
const XMLElement& data_array) {
670 return data_array.get_attribute(
"Name");
678const XMLElement&
get_data_array(std::string_view name,
const XMLElement& section) {
681 | std::views::filter([&] (
const auto& e) {
return e.get_attribute(
"Name") == name; }))
684 "Could not find data array with name '" + std::string{name}
685 +
"' in section '" + std::string{section.name()} +
"'"
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"))
702 XML::data_array_names(piece.get_child(
"PointData")),
703 std::back_inserter(names.point_fields)
705 if (piece.has_child(
"CellData"))
707 XML::data_array_names(piece.get_child(
"CellData")),
708 std::back_inserter(names.cell_fields)
711 if (vtk_grid.has_child(
"FieldData"))
713 XML::data_array_names(vtk_grid.get_child(
"FieldData")),
714 std::back_inserter(names.meta_data_fields)
727 using DataArrayStreamLocation = XMLDetail::DataArrayStreamLocation;
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.");
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.");
743 std::optional<XMLReaderHelper> helper;
746 }
catch (
const std::exception& e) {
747 throw IOError(
"Could not parse '" + filename +
"' as xml file. Error: " + e.what());
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)
754 "Given vtk-xml file has type '"
755 + helper->get().get_attribute(
"type")
756 +
"', expected '" + std::string{vtk_type} +
"'"
759 return std::move(helper).value();
762 const XMLElement& get(std::string_view path =
"")
const {
763 OptionalReference opt_ref = access_at(path, _element().get_child(
"VTKFile"));
765 throw ValueError(
"The given path '" + std::string{path} +
"' could not be found.");
766 return opt_ref.unwrap();
771 std::size_t visited = 0;
777 log_warning(
"Points section contains more than one data array, using first one as point coordinates");
783 throw ValueError(
"Points section does not contain a data array element");
789 std::string_view section_path,
790 const std::optional<std::size_t> number_of_tuples = {})
const {
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));
809 const XMLElement& _element()
const {
810 return _parser.get_xml();
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");
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);
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) {
839 std::string{_filename},
840 std::move(expected_layout),
842 [_nv=num_values, _begin=_parser.get_content_bounds(e).begin_pos] (std::string filename) {
843 std::ifstream file{filename};
845 Serialization result{_nv*
sizeof(T)};
846 XMLDetail::DataArrayReader<T>{file}.read_ascii(_nv, result);
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) {
857 _apply_decoder_for(e, [&] (
auto&& decoder) {
859 std::string{_filename},
860 std::move(expected_layout),
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);
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");
886 return MDLayout{{num_tuples, num_comps}};
887 return MDLayout{{num_tuples}};
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"));
899 throw ValueError(
"Cannot read string data arrays with more than one tuple");
901 return _deduce_number_of_values(element);
904 if (number_of_values%number_of_components != 0)
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) +
")"
911 return number_of_values/number_of_components;
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);
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,
926 return std::ranges::distance(std::ranges::istream_view<_T>{file});
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;
945 const std::size_t value_type_number_of_bytes = from_precision_attribute(
946 element.get_attribute(
"type")
949 if (number_of_bytes%value_type_number_of_bytes != 0)
951 "The length of the data array '" + element.name() +
"' "
952 +
"is incompatible with the data type '" + element.get_attribute(
"type") +
"'"
955 return number_of_bytes/value_type_number_of_bytes;
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"))
964 return DataArrayStreamLocation{.begin = _parser.get_content_bounds(element).begin_pos};
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"));
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);
983 throw IOError(
"Could not read header for data array '" + element.get_attribute(
"Name") +
"'");
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));
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");
1005 std::string _filename;
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.
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 §ion)
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.
Common functionality for VTK writers.
Compressor using the ZLIB library.