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