uzuki2
Recovering R lists faithfully from HDF5 or JSON
Loading...
Searching...
No Matches
parse_hdf5.hpp
Go to the documentation of this file.
1#ifndef UZUKI2_PARSE_HPP
2#define UZUKI2_PARSE_HPP
3
4#include <memory>
5#include <vector>
6#include <cctype>
7#include <string>
8#include <cstring>
9#include <stdexcept>
10#include <cstdint>
11#include <unordered_set>
12
13#include "H5Cpp.h"
14
15#include "ritsuko/ritsuko.hpp"
17
18#include "interfaces.hpp"
19#include "Dummy.hpp"
20#include "ExternalTracker.hpp"
21#include "Version.hpp"
22#include "ParsedList.hpp"
23
29namespace uzuki2 {
30
39namespace hdf5 {
40
44struct Options {
50
54 bool strict_list = true;
55};
56
60inline void validate_numeric_missing_placeholder(const H5::Attribute& attr, const H5::DataSet& data, const Version& version) {
61 if (attr.getSpace().getSimpleExtentNdims() != 0) {
62 throw std::runtime_error("expected the '" + ritsuko::hdf5::get_name(attr) + "' attribute to be a scalar");
63 }
64 if (version.lt(1, 2)) {
65 if (attr.getDataType().getClass() != data.getDataType().getClass()) {
66 throw std::runtime_error("expected the '" + ritsuko::hdf5::get_name(attr) + "' attribute to have the same type class as its dataset");
67 }
68 } else {
69 if (attr.getDataType() != data.getDataType()) {
70 throw std::runtime_error("expected the '" + ritsuko::hdf5::get_name(attr) + "' attribute to have the same type as its dataset");
71 }
72 }
73}
74
75inline void validate_string_missing_placeholder(const H5::Attribute& attr) {
76 if (attr.getSpace().getSimpleExtentNdims() != 0) {
77 throw std::runtime_error("expected the '" + ritsuko::hdf5::get_name(attr) + "' attribute to be a scalar");
78 }
79 if (!ritsuko::hdf5::is_utf8_string(attr)) {
80 throw std::runtime_error("expected the '" + ritsuko::hdf5::get_name(attr) + "' attribute to be a UTF-8 string");
81 }
82}
83
84template<typename Type_, class Stream_, class Action_>
85void iterate_stream(Stream_& stream, Action_ action) {
86 auto buffer = sanisizer::create<std::vector<Type_> >(stream.chunk_size());
87 while (true) {
88 auto available = stream.load(buffer.data());
89 if (available == 0) {
90 break;
91 }
92 for (I<decltype(available)> i = 0; i < available; ++i) {
93 action(i + stream.start(), std::move(buffer[i]));
94 }
95 }
96}
97
98template<class Host_, class Function_>
99void parse_integer_like(const H5::DataSet& handle, Host_* ptr, bool is_scalar, Function_ check, const Version& version, const Options& options) try {
100 if (ritsuko::hdf5::exceeds_integer_limit(handle, 32, true)) {
101 throw std::runtime_error("dataset cannot be represented by 32-bit signed integers");
102 }
103
104 bool has_missing = false;
105 std::int32_t missing_value = -2147483648;
106 if (version.equals(1, 0)) {
107 has_missing = true;
108 } else {
109 const char* placeholder_name = "missing-value-placeholder";
110 has_missing = handle.attrExists(placeholder_name);
111 if (has_missing) {
112 auto attr = handle.openAttribute(placeholder_name);
113 validate_numeric_missing_placeholder(attr, handle, version);
114 attr.read(H5::PredType::NATIVE_INT32, &missing_value);
115 }
116 }
117
118 auto set = [&](hsize_t i, std::int32_t x) -> void {
119 if (has_missing && x == missing_value) {
120 ptr->set_missing(i);
121 } else {
122 check(x);
123 ptr->set(i, x);
124 }
125 };
126
127 if (is_scalar) {
128 std::int32_t value;
129 handle.read(&value, H5::PredType::NATIVE_INT32);
130 set(0, value);
131 } else {
132 ritsuko::hdf5::Stream1dNumericDataset<std::int32_t> stream(
133 &handle,
134 static_cast<hsize_t>(ptr->size()), // cast is safe, as ptr would have been initially allocated iwith an hsize_t length.
135 [&]{
136 ritsuko::hdf5::Stream1dNumericDatasetOptions opt;
137 opt.contiguous_chunk_size = options.buffer_size;
138 return opt;
139 }()
140 );
141 iterate_stream<std::int32_t>(stream, set);
142 }
143
144} catch (std::exception& e) {
145 throw std::runtime_error("failed to load integer dataset at '" + ritsuko::hdf5::get_name(handle) + "'; " + std::string(e.what()));
146}
147
148template<class Host_, class Function_>
149void parse_string_like(const H5::DataSet& handle, Host_* ptr, bool is_scalar, Function_ check, const Options& options) try {
150 if (!ritsuko::hdf5::is_utf8_string(handle)) {
151 throw std::runtime_error("expected a datatype that can be represented by a UTF-8 string");
152 }
153
154 std::optional<std::string> missingness;
155 const char* placeholder_name = "missing-value-placeholder";
156 if (handle.attrExists(placeholder_name)) {
157 auto attr = handle.openAttribute(placeholder_name);
158 validate_string_missing_placeholder(attr);
159 missingness = ritsuko::hdf5::read_scalar_string(attr);
160 }
161
162 auto set = [&](hsize_t i, std::string x) -> void {
163 if (missingness.has_value() && x == *missingness) {
164 ptr->set_missing(i);
165 } else {
166 check(x);
167 ptr->set(i, std::move(x));
168 }
169 };
170
171 if (is_scalar) {
172 auto x = ritsuko::hdf5::read_scalar_string(handle);
173 set(0, std::move(x));
174 } else {
175 ritsuko::hdf5::Stream1dStringDataset stream(
176 &handle,
177 static_cast<hsize_t>(ptr->size()), // cast is safe, as ptr would have been initially allocated iwith an hsize_t length.
178 [&]{
179 ritsuko::hdf5::Stream1dStringDatasetOptions opt;
180 opt.contiguous_chunk_size = options.buffer_size;
181 return opt;
182 }()
183 );
184 iterate_stream<std::string>(stream, set);
185 }
186
187} catch (std::exception& e) {
188 throw std::runtime_error("failed to load string dataset at '" + ritsuko::hdf5::get_name(handle) + "'; " + std::string(e.what()));
189}
190
191inline double r_missing_value() {
192 std::uint32_t tmp_value = 1;
193 auto tmp_ptr = reinterpret_cast<unsigned char*>(&tmp_value);
194
195 // Mimic R's generation of these values, but we can't use type punning as
196 // this is not legal in C++, and we don't have bit_cast yet.
197 double missing_value = 0;
198 auto missing_ptr = reinterpret_cast<unsigned char*>(&missing_value);
199
200 int step = 1;
201 if (tmp_ptr[0] == 1) { // little-endian.
202 missing_ptr += sizeof(double) - 1;
203 step = -1;
204 }
205
206 *missing_ptr = 0x7f;
207 *(missing_ptr += step) = 0xf0;
208 *(missing_ptr += step) = 0x00;
209 *(missing_ptr += step) = 0x00;
210 *(missing_ptr += step) = 0x00;
211 *(missing_ptr += step) = 0x00;
212 *(missing_ptr += step) = 0x07;
213 *(missing_ptr += step) = 0xa2;
214
215 return missing_value;
216}
217
218template<class Host_, class Function_>
219void parse_numbers(const H5::DataSet& handle, Host_* ptr, bool is_scalar, Function_ check, const Version& version, const Options& options) try {
220 if (version.lt(1, 3)) {
221 if (handle.getTypeClass() != H5T_FLOAT) {
222 throw std::runtime_error("expected a floating-point dataset");
223 }
224 } else {
225 if (ritsuko::hdf5::exceeds_float_limit(handle, 64)) {
226 throw std::runtime_error("dataset cannot be represented by 64-bit floats");
227 }
228 }
229
230 // Check that we support IEEE754-compliant floats.
231 static_assert(std::numeric_limits<double>::is_iec559);
232
233 bool has_missing = false;
234 double missing_value = 0;
235 if (version.equals(1, 0)) {
236 has_missing = true;
237 missing_value = r_missing_value();
238 } else {
239 const char* placeholder_name = "missing-value-placeholder";
240 has_missing = handle.attrExists(placeholder_name);
241 if (has_missing) {
242 auto attr = handle.openAttribute(placeholder_name);
243 validate_numeric_missing_placeholder(attr, handle, version);
244 attr.read(H5::PredType::NATIVE_DOUBLE, &missing_value);
245 }
246 }
247
248 bool should_compare_nan = version.lt(1, 3);
249 bool is_placeholder_nan = std::isnan(missing_value);
250 auto is_missing_value = [&](double val) -> bool {
251 if (should_compare_nan) {
252 auto xptr = reinterpret_cast<const unsigned char*>(&missing_value);
253 auto yptr = reinterpret_cast<const unsigned char*>(&val);
254 return std::memcmp(xptr, yptr, sizeof(double)) == 0;
255 } else if (is_placeholder_nan) {
256 return std::isnan(val);
257 } else {
258 return val == missing_value;
259 }
260 };
261
262 auto set = [&](hsize_t i, double x) -> void {
263 if (has_missing && is_missing_value(x)) {
264 ptr->set_missing(i);
265 } else {
266 check(x);
267 ptr->set(i, x);
268 }
269 };
270
271 if (is_scalar) {
272 double val;
273 handle.read(&val, H5::PredType::NATIVE_DOUBLE);
274 set(0, val);
275 } else {
276 ritsuko::hdf5::Stream1dNumericDataset<double> stream(
277 &handle,
278 static_cast<hsize_t>(ptr->size()), // cast is safe, as ptr would have been initially allocated iwith an hsize_t length.
279 [&]{
280 ritsuko::hdf5::Stream1dNumericDatasetOptions opt;
281 opt.contiguous_chunk_size = options.buffer_size;
282 return opt;
283 }()
284 );
285 iterate_stream<double>(stream, set);
286 }
287
288} catch (std::exception& e) {
289 throw std::runtime_error("failed to load floating-point dataset at '" + ritsuko::hdf5::get_name(handle) + "'; " + std::string(e.what()));
290}
291
292template<class Host_>
293void extract_names(const H5::Group& handle, Host_* ptr, const Options& options) try {
294 auto nhandle = handle.openDataSet("names");
295 if (!ritsuko::hdf5::is_utf8_string(nhandle)) {
296 throw std::runtime_error("expected 'names' to use a datatype that can be represented by a UTF-8 string");
297 }
298
299 const auto space = nhandle.getSpace();
300 if (space.getSimpleExtentNdims() != 1) {
301 throw std::runtime_error("expected 'names' to be a 1-dimensional dataset");
302 }
303 hsize_t nlen;
304 space.getSimpleExtentDims(&nlen);
305 if (!sanisizer::is_equal(nlen, ptr->size())) {
306 throw std::runtime_error("number of names should be equal to the object length");
307 }
308
309 ritsuko::hdf5::Stream1dStringDataset stream(
310 &nhandle,
311 nlen,
312 [&]{
313 ritsuko::hdf5::Stream1dStringDatasetOptions opt;
314 opt.contiguous_chunk_size = options.buffer_size;
315 return opt;
316 }()
317 );
318 iterate_stream<std::string>(
319 stream,
320 [&](hsize_t pos, std::string val) -> void {
321 ptr->set_name(pos, std::move(val));
322 }
323 );
324
325} catch (std::exception& e) {
326 throw std::runtime_error("failed to load names at '" + ritsuko::hdf5::get_name(handle) + "'; " + std::string(e.what()));
327}
328
329inline std::string read_uzuki_attr(const H5::Group& handle, const char* name) {
330 const auto attr = handle.openAttribute(name);
331 if (attr.getSpace().getSimpleExtentNdims() != 0) {
332 throw std::runtime_error("'" + std::string(name) + "' should be a scalar attribute in '" + ritsuko::hdf5::get_name(handle) + "'");
333 }
334 if (!ritsuko::hdf5::is_utf8_string(attr)) {
335 throw std::runtime_error("'" + std::string(name) + "' should be stored as a UTF-8 string in '" + ritsuko::hdf5::get_name(handle) + "'");
336 }
337 return ritsuko::hdf5::read_scalar_string(attr);
338}
339
340template<class Provisioner_, class Externals_>
341std::shared_ptr<Base> parse_inner(const H5::Group& handle, Externals_& ext, const Version& version, const Options& options) try {
342 auto object_type = read_uzuki_attr(handle, "uzuki_object");
343 std::shared_ptr<Base> output;
344
345 if (object_type == "list") {
346 auto dhandle = handle.openGroup("data");
347 const auto len = dhandle.getNumObjs();
348
349 bool named = handle.exists("names");
350 auto lptr = Provisioner_::new_List(sanisizer::cast<std::size_t>(len), named);
351 output.reset(lptr);
352
353 for (I<decltype(len)> i = 0; i < len; ++i) {
354 const auto istr = std::to_string(i);
355 try {
356 auto lhandle = dhandle.openGroup(istr);
357 lptr->set(i, parse_inner<Provisioner_>(lhandle, ext, version, options));
358 } catch (std::exception& e) {
359 throw std::runtime_error("failed to parse list element " + istr + "; " + std::string(e.what()));
360 }
361 }
362
363 if (named) {
364 extract_names(handle, lptr, options);
365 }
366
367 } else if (object_type == "vector") {
368 auto dhandle = handle.openDataSet("data");
369 const auto dspace = dhandle.getSpace();
370 const auto ndims = dspace.getSimpleExtentNdims();
371
372 hsize_t len = 1;
373 bool is_scalar = false;
374 if (ndims == 0) {
375 is_scalar = true;
376 } else if (ndims == 1) {
377 dspace.getSimpleExtentDims(&len);
378 } else {
379 throw std::runtime_error("expected a scalar or 1-dimensional dataset in 'data'");
380 }
381
382 const bool named = handle.exists("names");
383 auto vector_type = read_uzuki_attr(handle, "uzuki_type");
384 if (vector_type == "integer") {
385 auto iptr = Provisioner_::new_Integer(sanisizer::cast<std::size_t>(len), named, is_scalar);
386 output.reset(iptr);
387 parse_integer_like(
388 dhandle,
389 iptr,
390 is_scalar,
391 [](std::int32_t) -> void {},
392 version,
393 options
394 );
395
396 } else if (vector_type == "boolean") {
397 auto bptr = Provisioner_::new_Boolean(sanisizer::cast<std::size_t>(len), named, is_scalar);
398 output.reset(bptr);
399 parse_integer_like(
400 dhandle,
401 bptr,
402 is_scalar,
403 [&](std::int32_t x) -> void {
404 if (x != 0 && x != 1) {
405 throw std::runtime_error("boolean values should be 0 or 1");
406 }
407 },
408 version,
409 options
410 );
411
412 } else if (vector_type == "factor" || (version.equals(1, 0) && vector_type == "ordered")) {
413 auto levhandle = handle.openDataSet("levels");
414 if (!ritsuko::hdf5::is_utf8_string(levhandle)) {
415 throw std::runtime_error("expected a datatype that can be represented by a UTF-8 string for 'levels'");
416 }
417
418 hsize_t levlen;
419 auto lspace = levhandle.getSpace();
420 if (lspace.getSimpleExtentNdims() != 1) {
421 throw std::runtime_error("expected a 1-dimensional dataset for 'levels'");
422 }
423 lspace.getSimpleExtentDims(&levlen);
424
425 bool ordered = false;
426 if (vector_type == "ordered") {
427 ordered = true;
428 } else if (handle.exists("ordered")) {
429 auto ohandle = handle.openDataSet("ordered");
430 if (ohandle.getSpace().getSimpleExtentNdims() != 0) {
431 throw std::runtime_error("expected 'ordered' to be a scalar dataset");
432 }
433 if (ritsuko::hdf5::exceeds_integer_limit(ohandle, 32, true)) {
434 throw std::runtime_error("'ordered' value cannot be represented by a 32-bit integer");
435 }
436 std::int32_t tmp_ordered = 0;
437 ohandle.read(&tmp_ordered, H5::PredType::NATIVE_INT32);
438 ordered = tmp_ordered > 0;
439 }
440
441 auto fptr = Provisioner_::new_Factor(sanisizer::cast<std::size_t>(len), named, is_scalar, sanisizer::cast<std::size_t>(levlen), ordered);
442 output.reset(fptr);
443 parse_integer_like(
444 dhandle,
445 fptr,
446 is_scalar,
447 [&](std::int32_t x) -> void {
448 if (x < 0) {
449 throw std::runtime_error("factor codes should be non-negative");
450 } else if (sanisizer::is_greater_than_or_equal(x, levlen)) {
451 throw std::runtime_error("factor codes should be less than the number of levels");
452 }
453 },
454 version,
455 options
456 );
457
458 std::unordered_set<std::string> present;
459 ritsuko::hdf5::Stream1dStringDataset stream(
460 &levhandle,
461 levlen,
462 [&]{
463 ritsuko::hdf5::Stream1dStringDatasetOptions opt;
464 opt.contiguous_chunk_size = options.buffer_size;
465 return opt;
466 }()
467 );
468 iterate_stream<std::string>(
469 stream,
470 [&](hsize_t pos, std::string val) -> void {
471 if (present.find(val) != present.end()) {
472 throw std::runtime_error("levels should be unique (multiple occurrences of '" + val + "')");
473 }
474 fptr->set_level(pos, val);
475 present.insert(std::move(val));
476 }
477 );
478
479 } else if (vector_type == "vls" && !version.lt(1, 4)) {
480 constexpr auto precision = std::numeric_limits<std::uint64_t>::digits;
481 ritsuko::cvls::validate_pointer_datatype(dhandle, precision, precision);
482 auto hhandle = handle.openDataSet("heap");
483 auto hlen = ritsuko::cvls::validate_heap(hhandle);
484
485 const char* placeholder_name = "missing-value-placeholder";
486 std::optional<std::string> missingness;
487 if (dhandle.attrExists(placeholder_name)) {
488 auto attr = dhandle.openAttribute(placeholder_name);
489 validate_string_missing_placeholder(attr);
490 missingness = ritsuko::hdf5::read_scalar_string(attr);
491 }
492
493 auto ptr = Provisioner_::new_String(sanisizer::cast<std::size_t>(len), named, is_scalar, StringVector::NONE);
494 output.reset(ptr);
495
496 auto set = [&](hsize_t i, std::string x) -> void {
497 if (missingness.has_value() && x == *missingness) {
498 ptr->set_missing(i);
499 } else {
500 ptr->set(i, std::move(x));
501 }
502 };
503
504 if (is_scalar) {
505 ritsuko::cvls::Pointer<std::uint64_t, std::uint64_t> vlsptr;
506 dhandle.read(&vlsptr, ritsuko::cvls::define_pointer_datatype<std::uint64_t, std::uint64_t>());
507 if (ritsuko::cvls::is_Pointer_out_of_range(vlsptr, hlen)) {
508 throw std::runtime_error("compressed VLS pointer in '" + ritsuko::hdf5::get_name(dhandle) + "' is out of range of the heap");
509 }
510
511 H5::DataSpace dspace(1, &hlen);
512 const hsize_t len = vlsptr.length; // cast is safe if pointer is within range.
513 const hsize_t offset = vlsptr.offset;
514 dspace.selectHyperslab(H5S_SELECT_SET, &len, &offset);
515 H5::DataSpace mspace(1, &len);
516
517 std::vector<std::uint8_t> buffer(vlsptr.length);
518 hhandle.read(buffer.data(), H5::PredType::NATIVE_UINT8, mspace, dspace);
519 auto cptr = reinterpret_cast<const char*>(buffer.data());
520 set(0, std::string(cptr, cptr + ritsuko::hdf5::strnlen(cptr, vlsptr.length)));
521
522 } else {
523 ritsuko::cvls::Stream1dArray<std::uint64_t, std::uint64_t> stream(
524 &dhandle,
525 len,
526 &hhandle,
527 hlen,
528 [&]{
529 ritsuko::cvls::Stream1dArrayOptions opt;
530 opt.contiguous_chunk_size = options.buffer_size;
531 return opt;
532 }()
533 );
534 iterate_stream<std::string>(stream, set);
535 }
536
537 } else if (vector_type == "string" || (version.equals(1, 0) && (vector_type == "date" || vector_type == "date-time"))) {
538 StringVector::Format format = StringVector::NONE;
539 if (version.equals(1, 0)) {
540 if (vector_type == "date") {
541 format = StringVector::DATE;
542 } else if (vector_type == "date-time") {
543 format = StringVector::DATETIME;
544 }
545
546 } else if (handle.exists("format")) {
547 auto fhandle = handle.openDataSet("format");
548 if (fhandle.getSpace().getSimpleExtentNdims() != 0) {
549 throw std::runtime_error("expected 'format' to be a scalar dataset");
550 }
551 if (!ritsuko::hdf5::is_utf8_string(fhandle)) {
552 throw std::runtime_error("expected 'format' to use a datatype that can be represented by a UTF-8 encoded string");
553 }
554 auto x = ritsuko::hdf5::read_scalar_string(fhandle);
555 if (x == "date") {
556 format = StringVector::DATE;
557 } else if (x == "date-time") {
558 format = StringVector::DATETIME;
559 } else {
560 throw std::runtime_error("unsupported format '" + x + "'");
561 }
562 }
563
564 auto sptr = Provisioner_::new_String(sanisizer::cast<std::size_t>(len), named, is_scalar, format);
565 output.reset(sptr);
566 if (format == StringVector::NONE) {
567 parse_string_like(
568 dhandle,
569 sptr,
570 is_scalar,
571 [](const std::string&) -> void {},
572 options
573 );
574
575 } else if (format == StringVector::DATE) {
576 parse_string_like(
577 dhandle,
578 sptr,
579 is_scalar,
580 [&](const std::string& x) -> void {
581 if (!ritsuko::is_date(x.c_str(), x.size())) {
582 throw std::runtime_error("dates should follow YYYY-MM-DD formatting");
583 }
584 },
585 options
586 );
587
588 } else if (format == StringVector::DATETIME) {
589 parse_string_like(
590 dhandle,
591 sptr,
592 is_scalar,
593 [&](const std::string& x) -> void {
594 if (!ritsuko::is_rfc3339(x.c_str(), x.size())) {
595 throw std::runtime_error("date-times should follow the Internet Date/Time format");
596 }
597 },
598 options
599 );
600 }
601
602 } else if (vector_type == "number") {
603 auto dptr = Provisioner_::new_Number(sanisizer::cast<std::size_t>(len), named, is_scalar);
604 output.reset(dptr);
605 parse_numbers(
606 dhandle,
607 dptr,
608 is_scalar,
609 [](double) -> void {},
610 version,
611 options
612 );
613
614 } else {
615 throw std::runtime_error("unknown vector type '" + vector_type + "'");
616 }
617
618 if (named) {
619 auto vptr = static_cast<Vector*>(output.get());
620 extract_names(handle, vptr, options);
621 }
622
623 } else if (object_type == "nothing") {
624 output.reset(Provisioner_::new_Nothing());
625
626 } else if (object_type == "external") {
627 auto ihandle = handle.openDataSet("index");
628 if (ritsuko::hdf5::exceeds_integer_limit(ihandle, 32, true)) {
629 throw std::runtime_error("external index at 'index' cannot be represented by a 32-bit signed integer");
630 }
631
632 if (ihandle.getSpace().getSimpleExtentNdims() != 0) {
633 throw std::runtime_error("expected scalar dataset at 'index'");
634 }
635
636 std::int32_t idx;
637 ihandle.read(&idx, H5::PredType::NATIVE_INT32);
638 if (idx < 0) {
639 throw std::runtime_error("external index at 'index' should be non-negative");
640 } else if (sanisizer::is_greater_than_or_equal(idx, ext.size())) {
641 throw std::runtime_error("external index at 'index' is out of range");
642 }
643
644 output.reset(Provisioner_::new_External(ext.get(idx)));
645
646 } else {
647 throw std::runtime_error("unknown uzuki2 object type '" + object_type + "'");
648 }
649
650 return output;
651} catch (std::exception& e) {
652 throw std::runtime_error("failed to load object at '" + ritsuko::hdf5::get_name(handle) + "'; " + std::string(e.what()));
653 return nullptr; // for consistency.
654}
704template<class Provisioner_, class Externals_>
705ParsedList parse(const H5::Group& group, Externals_ ext, const Options& options) {
706 Version version;
707 if (group.attrExists("uzuki_version")) {
708 auto ver_str = read_uzuki_attr(group, "uzuki_version");
709 auto vraw = ritsuko::parse_version_string(ver_str.c_str(), ver_str.size(), /* skip_patch = */ true);
710 version.major = vraw.major;
711 version.minor = vraw.minor;
712 }
713
714 ExternalTracker etrack(std::move(ext));
715 auto ptr = parse_inner<Provisioner_>(group, etrack, version, options);
716
717 if (options.strict_list && ptr->type() != LIST) {
718 throw std::runtime_error("top-level object should represent an R list");
719 }
720 etrack.validate();
721
722 return ParsedList(std::move(ptr), std::move(version));
723}
724
741template<class Provisioner_, class Externals_>
742ParsedList parse(const std::string& file, const std::string& name, Externals_ ext, Options options = Options()) {
743 H5::H5File fhandle(file, H5F_ACC_RDONLY);
744 return parse<Provisioner_>(fhandle.openGroup(name), std::move(ext), options);
745}
746
755inline void validate(const H5::Group& group, int num_external, const Options& options) {
756 parse<DummyProvisioner>(group, DummyExternals(num_external), options);
757}
758
768inline void validate(const std::string& file, const std::string& name, int num_external, const Options& options) {
769 parse<DummyProvisioner>(file, name, DummyExternals(num_external), options);
770}
771
772}
773
774}
775
776#endif
Dummy classes for parsing without storing the results.
Class to hold the parsed list.
Dummy class satisfying the Externals_ interface of hdf5::parse().
Definition Dummy.hpp:131
Format
Definition interfaces.hpp:159
Defines the interfaces to use in HDF5 parsing.
Container_ create(Value_ x, Args_ &&... args)
constexpr bool is_equal(Left_ left, Right_ right)
constexpr bool is_greater_than_or_equal(Left_ left, Right_ right)
constexpr Dest_ cap(Value_ x)
constexpr Dest_ cast(Value_ x)
ParsedList parse(const H5::Group &group, Externals_ ext, const Options &options)
Definition parse_hdf5.hpp:705
void validate(const H5::Group &group, int num_external, const Options &options)
Definition parse_hdf5.hpp:755
Parse an R list from a HDF5 or JSON file.
Definition parse_json.hpp:31
Results of parsing a list from file.
Definition ParsedList.hpp:19
Options for HDF5 file parsing.
Definition parse_hdf5.hpp:44
hsize_t buffer_size
Definition parse_hdf5.hpp:49
bool strict_list
Definition parse_hdf5.hpp:54