rapidyaml 0.16.0
parse and emit YAML, and do it fast
Loading...
Searching...
No Matches
Overview

Functions

void sample_lightning_overview ()
 lightning overview of most common features
void sample_quick_overview ()
 quick overview of most common features

Detailed Description

Function Documentation

◆ sample_lightning_overview()

void sample_lightning_overview ( )

lightning overview of most common features

a lightning tour over most features see sample_quick_overview

Definition at line 409 of file quickstart.cpp.

410{
411 // Parse YAML code in place, potentially mutating the buffer:
412 char yml_buf[] = "{foo: 1, bar: [2, 3], john: doe}";
413 ryml::Tree tree = ryml::parse_in_place(yml_buf);
414
415 // read from the tree:
416 ryml::NodeRef bar = tree["bar"];
417 CHECK(bar[0].val() == "2");
418 CHECK(bar[1].val() == "3");
419 CHECK(bar[0].val().str == yml_buf + 15); // points at the source buffer
420 CHECK(bar[1].val().str == yml_buf + 18);
421
422 // deserializing:
423 int bar0 = 0, bar1 = 0;
424 bar[0].load(&bar0); // also checks the node is readable, and conversion succeeded
425 bar[1].load(&bar1); // see also .deserialize()
426 CHECK(bar0 == 2);
427 CHECK(bar1 == 3);
428
429 // serializing:
430 bar[0].save(10); // creates a string in the tree's arena
431 bar[1].save(11); // see also .set_serialized()
432 CHECK(bar[0].val() == "10");
433 CHECK(bar[1].val() == "11");
434
435 // add nodes
436 tree["new"].set_val("node");
437 bar.append_child().save(12);
438 CHECK(bar[2].val() == "12");
439
440 // emit tree
441 std::string expected = "{foo: 1,bar: [10,11,12],john: doe,new: node}";
442 // emit tree to std::string
443 CHECK(ryml::emitrs_yaml<std::string>(tree) == expected);
444 // emit tree to FILE*
445 ryml::emit_yaml(tree, stdout); printf("\n");
446 // emit tree to ostream
447 std::cout << tree << "\n";
448
449 // emit node
450 ryml::ConstNodeRef foo = tree["foo"];
451 expected = "foo: 1\n";
452 // emit node to std::string
453 CHECK(ryml::emitrs_yaml<std::string>(foo) == expected);
454 // emit node to FILE*
455 ryml::emit_yaml(foo, stdout);
456 // emit node to ostream
457 std::cout << foo;
458}
Holds a pointer to an existing tree, and a node id.
Definition node.hpp:737
A reference to a node in an existing yaml tree, offering a more convenient API than the index-based A...
Definition node.hpp:1063
void save(T const &k)
Definition node.hpp:1337
NodeRef append_child()
Definition node.hpp:1715
void set_val(id_type node, csubstr val) RYML_NOEXCEPT
Definition tree.hpp:688
substr emit_yaml(Tree const &t, EmitOptions const &opts, substr buf, bool error_on_excess)
(1) emit YAML to the given buffer.
Definition emit_buf.cpp:28
substr emitrs_yaml(Tree const &t, id_type id, EmitOptions const &opts, CharOwningContainer *cont, bool append=false)
(1) emit+resize: emit YAML to the given std::string/std::vector<char>-like container,...
void parse_in_place(Parser *parser, csubstr filename, substr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node.
Definition parse.cpp:165
#define CHECK(predicate)
a testing assertion, used only in this quickstart
void load(T *v, bool check_readable=true) const
(1) deserialize the node's contents (val or container) to the given variable, forwarding to the user-...
Definition node.hpp:345

Referenced by main().

◆ sample_quick_overview()

void sample_quick_overview ( )

quick overview of most common features

a brief tour over most features

Definition at line 464 of file quickstart.cpp.

465{
466 // Parse YAML code in place, potentially mutating the buffer:
467 char yml_buf[] = ""
468 "foo: 1" "\n"
469 "bar: [2, 3]" "\n"
470 "john: doe" "\n"
471 "";
472 ryml::Tree tree = ryml::parse_in_place(yml_buf);
473
474 // The resulting tree contains only views to the parsed string. If
475 // the string was parsed in place, then the string must outlive
476 // the tree! This works here because both `yml_buf` and `tree`
477 // live on the same scope, so have the same lifetime.
478
479 // It is also possible to:
480 //
481 // - parse a read-only buffer using parse_in_arena(). This
482 // copies the YAML buffer to the tree's arena, and spares the
483 // headache of the string's lifetime.
484 //
485 // - reuse an existing tree (advised)
486 //
487 // - reuse an existing parser (advised)
488 //
489 // - parse into an existing node deep in a tree
490 //
491 // Note: it will always be significantly faster to parse in place
492 // and reuse tree+parser.
493 //
494 // Below you will find samples that show how to achieve reuse; but
495 // please note that for brevity and clarity, many of the examples
496 // here are parsing in the arena, and not reusing tree or parser.
497
498
499 //------------------------------------------------------------------
500 // API overview
501
502 // The ryml tree has a two-level API:
503 //
504 // The lower level index API is based on the indices of nodes,
505 // where the node's id is the node's position in the tree's data
506 // array. This API is very efficient, but somewhat difficult to use:
507 ryml::id_type root_id = tree.root_id();
508 ryml::id_type bar_id = tree.find_child(root_id, "bar"); // need to get the index right
509 CHECK(tree.is_map(root_id)); // all of the index methods are in the tree
510 CHECK(tree.is_seq(bar_id)); // ... and receive the subject index
511
512 // The node API is a lightweight abstraction sitting on top of the
513 // index API, but offering a much more convenient interaction:
514 ryml::ConstNodeRef root = tree.rootref(); // a const node reference
515 ryml::ConstNodeRef bar = tree["bar"];
516 CHECK(root.is_map());
517 CHECK(bar.is_seq());
518
519 // A node ref is a lightweight handle to the tree and associated id:
520 CHECK(root.tree() == &tree); // a node ref points at its tree, WITHOUT refcount
521 CHECK(root.id() == root_id); // a node ref's id is the index of the node
522 CHECK(bar.id() == bar_id); // a node ref's id is the index of the node
523
524 // The node API translates very cleanly to the index API, so most
525 // of the code examples below are using the node API.
526
527 // WARNING. A node ref holds a raw pointer to the tree. Care must
528 // be taken to ensure the lifetimes match, so that a node will
529 // never access the tree after the tree goes out of scope.
530
531
532 //------------------------------------------------------------------
533 // To read the parsed tree
534
535 // ConstNodeRef::operator[] does a lookup, is O(num_children[node]).
536 CHECK(tree["foo"].is_keyval());
537 CHECK(tree["foo"].val() == "1"); // get the val of a node (must be leaf node, otherwise it is a container and has no val)
538 CHECK(tree["foo"].key() == "foo"); // get the key of a node (must be child of a map, otherwise it has no key)
539 CHECK(tree["bar"].is_seq());
540 CHECK(tree["bar"].has_key());
541 CHECK(tree["bar"].key() == "bar");
542 // maps use string keys, seqs use index keys:
543 CHECK(tree["bar"][0].val() == "2");
544 CHECK(tree["bar"][1].val() == "3");
545 CHECK(tree["john"].val() == "doe");
546 // An index key is the position of the child within its parent,
547 // so even maps can also use int keys, if the key position is
548 // known.
549 CHECK(tree[0].id() == tree["foo"].id());
550 CHECK(tree[1].id() == tree["bar"].id());
551 CHECK(tree[2].id() == tree["john"].id());
552 // Tree::operator[](int) searches a ***root*** child by its position.
553 CHECK(tree[0].id() == tree["foo"].id()); // 0: first child of root
554 CHECK(tree[1].id() == tree["bar"].id()); // 1: second child of root
555 CHECK(tree[2].id() == tree["john"].id()); // 2: third child of root
556 // NodeRef::operator[](int) searches a ***node*** child by its position:
557 CHECK(bar[0].val() == "2"); // 0 means first child of bar
558 CHECK(bar[1].val() == "3"); // 1 means second child of bar
559 // NodeRef::operator[](string):
560 // A string key is the key of the node: lookup is by name. So it
561 // is only available for maps, and it is NOT available for seqs,
562 // since seq members do not have keys.
563 CHECK(tree["foo"].key() == "foo");
564 CHECK(tree["bar"].key() == "bar");
565 CHECK(tree["john"].key() == "john");
566 CHECK(bar.is_seq());
567 // CHECK(bar["BOOM!"].is_seed()); // error, seqs do not have key lookup
568
569 // Note that maps can also use index keys as well as string keys:
570 CHECK(root["foo"].id() == root[0].id());
571 CHECK(root["bar"].id() == root[1].id());
572 CHECK(root["john"].id() == root[2].id());
573
574 // IMPORTANT. The ryml tree uses an index-based linked list for
575 // storing children, so the complexity of
576 // `Tree::operator[csubstr]` and `Tree::operator[id_type]` is O(n),
577 // linear on the number of root children. If you use
578 // `Tree::operator[]` with a large tree where the root has many
579 // children, you will see a performance hit.
580 //
581 // To avoid this hit, you can create your own accelerator
582 // structure. For example, before doing a lookup, do a single
583 // traverse at the root level to fill an `map<csubstr,id_type>`
584 // mapping key names to node indices; with a node index, a lookup
585 // (via `Tree::get()`) is O(1), so this way you can get O(log n)
586 // lookup from a key. (But please do not use `std::map` if you
587 // care about performance; use something else like a flat map or
588 // sorted vector).
589 //
590 // As for node refs, the difference from `NodeRef::operator[]` and
591 // `ConstNodeRef::operator[]` to `Tree::operator[]` is that the
592 // latter refers to the root node, whereas the former are invoked
593 // on their target node. But the lookup process works the same for
594 // both and their algorithmic complexity is the same: they are
595 // both linear in the number of direct children. But of course,
596 // depending on the data, that number may be very different from
597 // one to another.
598
599
600 //------------------------------------------------------------------
601 // Hierarchy:
602
603 {
604 ryml::ConstNodeRef foo = root.first_child();
605 ryml::ConstNodeRef john = root.last_child();
606 CHECK(tree.size() == 6); // O(1) number of nodes in the tree
607 CHECK(root.num_children() == 3); // O(num_children[root])
608 CHECK(foo.num_siblings() == 3); // O(num_children[parent(foo)])
609 CHECK(foo.parent().id() == root.id()); // parent() is O(1)
610 CHECK(root.first_child().id() == root["foo"].id()); // first_child() is O(1)
611 CHECK(root.last_child().id() == root["john"].id()); // last_child() is O(1)
612 CHECK(john.first_sibling().id() == foo.id());
613 CHECK(foo.last_sibling().id() == john.id());
614 // prev_sibling(), next_sibling(): (both are O(1))
615 CHECK(foo.num_siblings() == root.num_children());
616 CHECK(foo.prev_sibling().id() == ryml::NONE); // foo is the first_child()
617 CHECK(foo.next_sibling().key() == "bar");
618 CHECK(foo.next_sibling().next_sibling().key() == "john");
619 CHECK(foo.next_sibling().next_sibling().next_sibling().id() == ryml::NONE); // john is the last_child()
620 }
621
622
623 //------------------------------------------------------------------
624 // Iterating:
625 {
626 ryml::csubstr expected_keys[] = {"foo", "bar", "john"};
627 // iterate children using the high-level node API:
628 {
629 ryml::id_type count = 0;
630 for(ryml::ConstNodeRef const& child : root.children())
631 CHECK(child.key() == expected_keys[count++]);
632 }
633 // iterate siblings using the high-level node API:
634 {
635 ryml::id_type count = 0;
636 for(ryml::ConstNodeRef const& child : root["foo"].siblings())
637 CHECK(child.key() == expected_keys[count++]);
638 }
639 // iterate children using the lower-level tree index API:
640 {
641 ryml::id_type count = 0;
642 for(ryml::id_type child_id = tree.first_child(root_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
643 CHECK(tree.key(child_id) == expected_keys[count++]);
644 }
645 // iterate siblings using the lower-level tree index API:
646 // (notice the only difference from above is in the loop
647 // preamble, which calls tree.first_sibling(bar_id) instead of
648 // tree.first_child(root_id))
649 {
650 ryml::id_type count = 0;
651 for(ryml::id_type child_id = tree.first_sibling(bar_id); child_id != ryml::NONE; child_id = tree.next_sibling(child_id))
652 CHECK(tree.key(child_id) == expected_keys[count++]);
653 }
654 }
655
656
657 //------------------------------------------------------------------
658 // Gotchas:
659
660 // ryml uses assertions to prevent you from trying to obtain
661 // things that do not exist. For example:
662
663 {
664 ryml::ConstNodeRef seq_node = tree["bar"];
665 ryml::ConstNodeRef val_node = seq_node[0];
666
667 CHECK(seq_node.is_seq()); // seq is a container
668 CHECK(!seq_node.has_val()); // ... so it has no val
669 //CHECK(seq_node.val() == BOOM!); // ... so attempting to get a val is undefined behavior
670
671 CHECK(val_node.parent() == seq_node); // belongs to a seq
672 CHECK(!val_node.has_key()); // ... so it has no key
673 //CHECK(val_node.key() == BOOM!); // ... so attempting to get a key is undefined behavior
674
675 CHECK(val_node.is_val()); // this node is a val
676 //CHECK(val_node.first_child() == BOOM!); // ... so attempting to get a child is undefined behavior
677
678 // assertions are also present in methods that /may/ read the val:
679 CHECK(seq_node.is_seq()); // seq is a container
680 //CHECK(seq_node.val_is_null() BOOM!); // so cannot get the val to check
681 }
682
683 // By default, assertions are enabled unless the NDEBUG macro is
684 // defined (which happens in release builds).
685 //
686 // This adheres to the pay-only-for-what-you-use philosophy: if
687 // you are sure that your intent is correct, why would you need to
688 // pay the runtime cost for the assertions?
689 //
690 // The downside, of course, is that you may be unsure, or your
691 // code may be wrong; and then release builds may end up doing
692 // something wrong.
693 //
694 // So in that case, you can use the appropriate ryml predicates to
695 // check your intent (as in the examples above), or you can
696 // explicitly enable/disable assertions, by defining the macro
697 // RYML_USE_ASSERT to a proper value (see c4/yml/common.hpp). This
698 // will make problematic code trigger a call to the appropriate
699 // error callback.
700 //
701 // Also, to be clear, this does not apply to parse errors
702 // occurring when the YAML is parsed. Parse errors are always
703 // detected by the parser: this is not done through assertions,
704 // but through the appropriate error checking mechanism. So
705 // checking for these errors is always enabled and cannot be
706 // switched off. As for deserialization errors, see below.
707
708
709 //------------------------------------------------------------------
710 // Deserializing:
711 //
712 // - use .load(T*)/.load_key(T*) for lazy code with checked reads
713 // - ryml checks preconditions (node readability, etc), or triggers error
714 // - ryml checks deserialization, or triggers error
715 // - more expensive because of all the checks and error marshalling
716 // - optionally, can disable precondition checks with bool parameter
717 //
718 // - use .deserialize()/.deserialize_key() for sure reads
719 // - ryml asserts preconditions (readability, etc) (err on debug builds, UB otherwise)
720 // - ryml returns bool for deserialization status, [[nodiscard]]
721 // - cheaper but potentially dangerous
722 //
723 {
724 // fundamental types: ryml provides deserialization facilities
725 // .load() checks for readability + conversion errors
726 {
727 int foo = 0, bar0 = 0, bar1 = 0;
728 root["foo"].load(&foo); // calls err_visit() on failure
729 root["bar"][0].load(&bar0);
730 root["bar"][1].load(&bar1);
731 CHECK(foo == 1);
732 CHECK(bar0 == 2);
733 CHECK(bar1 == 3);
734 // .load() can also be called from the tree
735 }
736 // .deserialize() asserts readability, user checks conversion errors
737 {
738 int foo = 0, bar0 = 0, bar1 = 0;
739 CHECK(root["foo"].deserialize(&foo)); // user must check return value
740 CHECK(root["bar"][0].deserialize(&bar0));
741 CHECK(root["bar"][1].deserialize(&bar1));
742 CHECK(foo == 1);
743 CHECK(bar0 == 2);
744 CHECK(bar1 == 3);
745 // .deserialize() can also be called from the tree
746 }
747 // std containers: ryml optionally provides deserialization
748 // facilities. see serialization samples below.
749 {
750 std::string john_str, bar_str;
751 root["john"].load(&john_str);
752 root["bar"].load_key(&bar_str);
753 CHECK(john_str == "doe");
754 CHECK(bar_str == "bar");
755 std::vector<int> bar_actual, bar_expected{{2, 3}};
756 root["bar"].load(&bar_actual);
757 CHECK(bar_actual == bar_expected);
758 }
759 }
760
761
762 //------------------------------------------------------------------
763 // Modifying existing nodes: assigning vs serializing
764
765 // As implied by its name, ConstNodeRef is a reference to a const
766 // node. It can be used to read from the node, but not write to it
767 // or modify the hierarchy of the node. If any modification is
768 // desired then a NodeRef must be used instead:
769 ryml::NodeRef wroot = tree.rootref(); // writeable root
770
771 // .set_val() assigns an existing string to the receiving node.
772 // The contents are NOT copied, and the string pointer will be in
773 // effect until the tree goes out of scope! So BEWARE to only
774 // assign from strings outliving the tree.
775 wroot["foo"].set_val("says you");
776 wroot["bar"][0].set_val("-2");
777 wroot["bar"][1].set_val("-3");
778 wroot["john"].set_val("ron");
779 // Now the tree is _pointing_ at the memory of the strings above.
780 // In this case it is OK because those are static strings, located
781 // in the executable's static section, and will outlive the tree.
782 CHECK(root["foo"].val() == "says you");
783 CHECK(root["bar"][0].val() == "-2");
784 CHECK(root["bar"][1].val() == "-3");
785 CHECK(root["john"].val() == "ron");
786 // But WATCHOUT: do not assign from temporary objects:
787 // {
788 // std::string bad("will dangle");
789 // root["john"] = bad;
790 // }
791 // CHECK(root["john"] == "dangling"); // CRASH! the string was deallocated
792
793 // For non-string types, or for strings whose lifetime is
794 // problematic WRT the tree, you can create and save a string in
795 // the tree using .set_serialized() (or its equivalent .save() if
796 // you want to have ryml do the error checking). It first
797 // serializes values to a string arena owned by the tree, then
798 // assigns the serialized string to the receiving node. This
799 // avoids constraints with the lifetime, since the arena lives
800 // with the tree.
801 CHECK(tree.arena().empty());
802 wroot["foo"].set_serialized("says who"); // requires to_chars(). see serialization samples below.
803 wroot["bar"][0].set_serialized(20);
804 wroot["bar"][1].set_serialized(30);
805 wroot["john"].set_serialized("deere");
806 CHECK(root["foo"].val() == "says who");
807 CHECK(root["bar"][0].val() == "20");
808 CHECK(root["bar"][1].val() == "30");
809 CHECK(root["john"].val() == "deere");
810 CHECK(tree.arena() == "says who2030deere"); // the result of serializations to the tree arena
811 // with .set_serialize()/.save(), the crash above is avoided:
812 {
813 std::string ok("in_scope");
814 // root["john"].set_val(ok); // don't, will dangle
815 wroot["john"].set_serialized(ok); // OK, copy to the tree's arena
816 }
817 CHECK(root["john"].val() == "in_scope"); // OK! val is now in the tree's arena
818 // serializing floating points:
819 wroot["float"].set_serialized(2.4f);
820 // to force a particular precision or float format:
821 // (see sample_float_precision() and sample_formatting())
822 wroot["digits"].set_serialized(ryml::fmt::real(2.4, /*num_digits*/6, ryml::FTOA_FLOAT));
823 CHECK(tree.arena() == "says who2030deerein_scope2.42.400000"); // the result of serializations to the tree arena
824
825
826 //------------------------------------------------------------------
827 // Adding new nodes:
828
829 // adding a keyval node to a map:
830 CHECK(root.num_children() == 5);
831 wroot["newkeyval"].set_val("shiny and new"); // using these strings
832 c4::yml::NodeRef chsrl = wroot.append_child();
833 chsrl.set_key_serialized("newkeyval (serialized)"); // serializes and assigns the serialization
834 chsrl.set_serialized("shiny and new (serialized)"); // serializes and assigns the serialization
835 CHECK(root.num_children() == 7);
836 CHECK(root["newkeyval"].key() == "newkeyval");
837 CHECK(root["newkeyval"].val() == "shiny and new");
838 CHECK(root["newkeyval (serialized)"].key() == "newkeyval (serialized)");
839 CHECK(root["newkeyval (serialized)"].val() == "shiny and new (serialized)");
840 CHECK( ! tree.in_arena(root["newkeyval"].key())); // it's using directly the static string above
841 CHECK( ! tree.in_arena(root["newkeyval"].val())); // it's using directly the static string above
842 CHECK( tree.in_arena(root["newkeyval (serialized)"].key())); // it's using a serialization of the string above
843 CHECK( tree.in_arena(root["newkeyval (serialized)"].val())); // it's using a serialization of the string above
844 // adding a val node to a seq:
845 CHECK(root["bar"].num_children() == 2);
846 wroot["bar"][2].set_val("oh so nice");
847 wroot["bar"][3].set_serialized("oh so nice (serialized)");
848 CHECK(root["bar"].num_children() == 4);
849 CHECK(root["bar"][2].val() == "oh so nice");
850 CHECK(root["bar"][3].val() == "oh so nice (serialized)");
851 // adding a seq node:
852 CHECK(root.num_children() == 7);
853 wroot["newseq"].set_seq();
854 chsrl = wroot.append_child();
855 chsrl.set_key_serialized("newseq (serialized)");
856 chsrl.set_seq();
857 CHECK(root.num_children() == 9);
858 CHECK(root["newseq"].num_children() == 0);
859 CHECK(root["newseq"].is_seq());
860 CHECK(root["newseq (serialized)"].num_children() == 0);
861 CHECK(root["newseq (serialized)"].is_seq());
862 // adding a map node:
863 CHECK(root.num_children() == 9);
864 wroot["newmap"].set_map();
865 chsrl = wroot.append_child();
866 chsrl.set_key_serialized("newmap (serialized)");
867 chsrl.set_map();
868 CHECK(root.num_children() == 11);
869 CHECK(root["newmap"].num_children() == 0);
870 CHECK(root["newmap"].is_map());
871 CHECK(root["newmap (serialized)"].num_children() == 0);
872 CHECK(root["newmap (serialized)"].is_map());
873 //
874 // When the tree is const, operator[] searches the tree for a
875 // matching key/position, and returns it, or raises an error if
876 // none is found.
877 //
878 // When the tree is mutable, operator[] will not mutate the tree
879 // until the returned node is written to. operator[] first
880 // searches the tree for a node matching the key/position and
881 // returns it if it exists; otherwise, the result is a NodeRef
882 // object which keeps in itself the required information to write
883 // to the proper place in the tree. This is called being in a
884 // "seed" state.
885 //
886 // This means that passing a key/position which does not exist
887 // will not mutate the tree, but will instead store (in the
888 // returned NodeRef) the proper place of the tree to be able to do
889 // so, if and when it is required. This is why the node is said to
890 // be in "seed" state - it allows creating the entry in the tree
891 // in the future.
892 //
893 // This is a significant difference from eg, the behavior of
894 // std::map, which mutates the map immediately within the call to
895 // operator[].
896 //
897 // All of the points above apply only if the tree is mutable. If
898 // the tree is const, then a NodeRef cannot be obtained from it;
899 // only a ConstNodeRef, which can never be used to mutate the
900 // tree.
901 //
902 CHECK(!root.has_child("I am not nothing"));
903 ryml::NodeRef nothing;
904 CHECK(nothing.invalid()); // invalid because it points at nothing
905 nothing = wroot["I am nothing"];
906 CHECK(!nothing.invalid()); // points at the tree, and a specific place in the tree
907 CHECK(nothing.is_seed()); // ... but nothing is there yet.
908 CHECK(!root.has_child("I am nothing")); // same as above
909 CHECK(!nothing.readable()); // ... and this node cannot be used to
910 // read anything from the tree
911 ryml::NodeRef something = wroot["I am something"];
912 ryml::ConstNodeRef constsomething = wroot["I am something"];
913 CHECK(!root.has_child("I am something")); // same as above
914 CHECK(!something.invalid());
915 CHECK(something.is_seed()); // same as above
916 CHECK(!something.readable()); // same as above
917 CHECK(constsomething.invalid()); // NOTE: because a ConstNodeRef cannot be
918 // used to mutate a tree, it is only valid()
919 // if it is pointing at an existing node.
920 something.set_val("indeed"); // this will commit the seed to the tree, mutating at the proper place
921 CHECK(root.has_child("I am something"));
922 CHECK(root["I am something"].val() == "indeed");
923 CHECK(!something.invalid()); // it was already valid
924 CHECK(!something.is_seed()); // now the tree has this node, so the
925 // ref is no longer a seed
926 CHECK(something.readable()); // and it is now readable
927 //
928 // now the constref is also valid (but it needs to be reassigned):
929 ryml::ConstNodeRef constsomethingnew = wroot["I am something"];
930 CHECK(!constsomethingnew.invalid());
931 CHECK(constsomethingnew.readable());
932 // note that the old constref is now stale, because it only keeps
933 // the state at creation:
934 CHECK(constsomething.invalid());
935 CHECK(!constsomething.readable());
936 //
937 // -----------------------------------------------------------
938 // Remember: a seed node cannot be used to read from the tree!
939 // -----------------------------------------------------------
940 //
941 // The seed node needs to be created and become readable first.
942 //
943 // Trying to invoke any tree-reading method on a node that is not
944 // readable will cause an assertion (see RYML_USE_ASSERT).
945 //
946 // It is your responsibility to verify that the preconditions are
947 // met. If you are not sure about the structure of your data,
948 // write your code defensively to signify your full intent:
949 //
950 ryml::NodeRef wbar = wroot["bar"];
951 if(wbar.readable() && wbar.is_seq()) // .is_seq() requires .readable()
952 {
953 CHECK(wbar[0].readable() && wbar[0].val() == "20");
954 CHECK( ! wbar[100].readable());
955 CHECK( ! wbar[100].readable() || wbar[100].val() == "100"); // <- no crash because it is not .readable(), so never tries to call .val()
956 // this would work as well:
957 CHECK( ! wbar[0].is_seed() && wbar[0].val() == "20");
958 CHECK(wbar[100].is_seed() || wbar[100].val() == "100");
959 }
960
961
962 //------------------------------------------------------------------
963 // .operator[]() vs .at()
964
965 // (Const)NodeRef::operator[] is an analogue to std::vector::operator[].
966 // (Const)NodeRef::at() is an analogue to std::vector::at()
967 //
968 // at() will always check the subject node is .readable().
969 //
970 // [] is meant for the happy path: unverified in Release builds,
971 // and asserts .readable() in Debug builds (more specifically when
972 // RYML_USE_ASSERT is defined and NDEBUG is not defined).
973 {
974 // in this example we will be checking errors, so set up a
975 // temporary error handler to catch them:
976 ScopedErrorHandlerExample errh; // calls ryml::set_callbacks()
977 // instantiate the tree after errh
978 ryml::Tree err_tree = ryml::parse_in_arena("{foo: bar}");
979 // ... so that the tree uses the current callbacks:
980 CHECK(err_tree.callbacks() == errh.callbacks());
981 // node does not exist, only a seed node
982 ryml::NodeRef seed_node = err_tree["this"];
983 // ... therefore not .readable()
984 CHECK(!seed_node.readable());
985 // using .at() reliably produces an error:
986 CHECK(errh.check_error_occurs([&]{
987 return seed_node.at("is").at("an").at("invalid").at("operation");
988 // ^
989 // error occurs here because it is unreadable
990 }));
991 // ... but using [] fails only when RYML_USE_ASSERT is
992 // defined. otherwise, it's the dreaded Undefined Behavior:
994 return seed_node["is"]["an"]["invalid"]["operation"];
995 // ^
996 // assertion occurs here because it is unreadable
997 }));
998 }
999
1000
1001 //------------------------------------------------------------------
1002 // Emitting:
1003
1004 ryml::csubstr expected_result =
1005 "foo: says who" "\n"
1006 "bar: [20,30,oh so nice,oh so nice (serialized)]" "\n"
1007 "john: in_scope" "\n"
1008 "float: 2.4" "\n"
1009 "digits: 2.400000" "\n"
1010 "newkeyval: shiny and new" "\n"
1011 "newkeyval (serialized): shiny and new (serialized)" "\n"
1012 "newseq: []" "\n"
1013 "newseq (serialized): []" "\n"
1014 "newmap: {}" "\n"
1015 "newmap (serialized): {}" "\n"
1016 "I am something: indeed" "\n"
1017 "";
1018
1019 // emit to a FILE*
1020 ryml::emit_yaml(tree, stdout);
1021 // emit to a stream
1022 // (this is templated on the stream, so it works both with
1023 // standard streams like std::ostream/std::stringstream, or with
1024 // user-defined streams not inheriting from ostream)
1025 std::stringstream ss;
1026 ss << tree;
1027 std::string stream_result = ss.str();
1028 // emit to a buffer:
1029 std::string str_result = ryml::emitrs_yaml<std::string>(tree);
1030 // can emit to any given buffer:
1031 char buf[1024];
1032 ryml::csubstr buf_result = ryml::emit_yaml(tree, buf);
1033 // now check
1034 CHECK(buf_result == expected_result);
1035 CHECK(str_result == expected_result);
1036 CHECK(stream_result == expected_result);
1037 // There are many possibilities to emit to buffer;
1038 // please look at the emit sample functions below.
1039
1040
1041 //------------------------------------------------------------------
1042 // ConstNodeRef vs NodeRef
1043
1044 ryml::NodeRef noderef = tree["bar"][0];
1045 ryml::ConstNodeRef constnoderef = tree["bar"][0];
1046
1047 // ConstNodeRef cannot be used to mutate the tree:
1048 //constnoderef.set_val("21"); // compile error: method does not exist
1049 //constnoderef.set_serialized("22"); // compile error
1050 // ... but a NodeRef can:
1051 noderef.set_val("21"); // ok, can assign because it's not const
1052 CHECK(tree["bar"][0].val() == "21");
1053 noderef.set_serialized("22"); // ok, can serialize and assign because it's not const
1054 CHECK(tree["bar"][0].val() == "22");
1055
1056 // it is not possible to obtain a NodeRef from a ConstNodeRef:
1057 // noderef = constnoderef; // compile error
1058
1059 // it is always possible to obtain a ConstNodeRef from a NodeRef:
1060 constnoderef = noderef; // ok can assign const <- nonconst
1061
1062 // If a tree is const, then only ConstNodeRef's can be
1063 // obtained from that tree:
1064 ryml::Tree const& consttree = tree;
1065 //noderef = consttree["bar"][0]; // compile error
1066 noderef = tree["bar"][0]; // ok
1067 constnoderef = consttree["bar"][0]; // ok
1068
1069 // ConstNodeRef and NodeRef can be compared for equality.
1070 // Equality means they point at the same node.
1071 CHECK(constnoderef == noderef);
1072 CHECK(!(constnoderef != noderef));
1073
1074
1075 //------------------------------------------------------------------
1076 // Getting the location of nodes in the source:
1077 //
1078 // Location tracking is opt-in:
1079 ryml::EventHandlerTree evt_handler = {};
1080 ryml::Parser parser(&evt_handler, ryml::ParserOptions().locations(true));
1081 // Now the parser will start by building the accelerator structure:
1082 ryml::Tree tree2 = parse_in_arena(&parser, "expected.yml", expected_result);
1083 // ... and use it when querying
1084 ryml::ConstNodeRef subject_node = tree2["bar"][1];
1085 CHECK(subject_node.val() == "30");
1086 ryml::Location loc = subject_node.location(parser);
1087 CHECK(parser.location_contents(loc).begins_with("30"));
1088 CHECK(loc.line == 1u);
1089 CHECK(loc.col == 9u);
1090 // For further details in location tracking,
1091 // refer to the sample function below.
1092
1093
1094 //------------------------------------------------------------------
1095 // Dealing with UTF8
1097 "#" "\n"
1098 "# UTF8/16/32 can be placed directly in any scalar:" "\n"
1099 "#" "\n"
1100 "en: Planet (Gas)" "\n"
1101 "fr: Planète (Gazeuse)" "\n"
1102 "ru: Планета (Газ)" "\n"
1103 "ja: 惑星(ガス)" "\n"
1104 "zh: 行星(气体)" "\n"
1105 "#" "\n"
1106 "# UTF8 decoding only happens in double-quoted strings," "\n"
1107 "# as per the YAML standard" "\n"
1108 "#" "\n"
1109 "decode this: \"\\u263A c\\x61f\\xE9\"" "\n"
1110 "and this as well: \"\\u2705 \\U0001D11E\"" "\n"
1111 "not decoded: '\\u263A \\xE2\\x98\\xBA'" "\n"
1112 "neither this: '\\u2705 \\U0001D11E'" "\n");
1113 // in-place UTF8 just works:
1114 CHECK(langs["en"].val() == "Planet (Gas)");
1115 CHECK(langs["fr"].val() == "Planète (Gazeuse)");
1116 CHECK(langs["ru"].val() == "Планета (Газ)");
1117 CHECK(langs["ja"].val() == "惑星(ガス)");
1118 CHECK(langs["zh"].val() == "行星(气体)");
1119 // and \x \u \U codepoints are decoded, but only when they appear
1120 // inside double-quoted strings, as dictated by the YAML
1121 // standard:
1122 CHECK(langs["decode this"].val() == "☺ café");
1123 CHECK(langs["and this as well"].val() == "✅ 𝄞");
1124 CHECK(langs["not decoded"].val() == "\\u263A \\xE2\\x98\\xBA");
1125 CHECK(langs["neither this"].val() == "\\u2705 \\U0001D11E");
1126}
ConstNodeRef parent() const RYML_NOEXCEPT
Forward to Tree::parent().
Definition node.hpp:853
ConstNodeRef first_sibling() const RYML_NOEXCEPT
Forward to Tree::first_sibling().
Definition node.hpp:860
id_type id() const noexcept
Definition node.hpp:822
ConstNodeRef first_child() const RYML_NOEXCEPT
Forward to Tree::first_child().
Definition node.hpp:854
ConstNodeRef last_child() const RYML_NOEXCEPT
Forward to Tree::last_child().
Definition node.hpp:855
bool invalid() const noexcept
Definition node.hpp:790
const_children_view children() const RYML_NOEXCEPT
get an iterable view over children
Definition node.hpp:987
ConstNodeRef last_sibling() const RYML_NOEXCEPT
Forward to Tree::last_sibling().
Definition node.hpp:861
Tree const * tree() const noexcept
Definition node.hpp:821
ConstNodeRef next_sibling() const RYML_NOEXCEPT
Forward to Tree::next_sibling().
Definition node.hpp:859
ConstNodeRef prev_sibling() const RYML_NOEXCEPT
Forward to Tree::prev_sibling().
Definition node.hpp:858
bool readable() const noexcept
because a ConstNodeRef cannot be used to write to the tree, readable() has the same meaning as !...
Definition node.hpp:793
void set_serialized(T const &v)
serialize a variable to this node.
Definition node.hpp:1375
void set_val(csubstr val)
Definition node.hpp:1251
void set_key_serialized(T const &k)
serialize a variable, then assign the result to the node's key
Definition node.hpp:1396
bool invalid() const noexcept
true if the object is not referring to any existing or seed node.
Definition node.hpp:1134
bool readable() const noexcept
true if the object is not invalid and not in seed state.
Definition node.hpp:1138
bool is_seed() const noexcept
true if the object is not invalid and in seed state.
Definition node.hpp:1136
csubstr const & key(id_type node) const
Definition tree.hpp:454
id_type first_child(id_type node) const
Definition tree.hpp:577
id_type root_id() const
Get the id of the root node. The tree must not be empty. The tree can be empty only when constructed ...
Definition tree.hpp:386
NodeRef rootref()
Get the root as a NodeRef . Note that a non-const Tree implicitly converts to NodeRef.
Definition tree.cpp:56
bool is_map(id_type node) const
Definition tree.hpp:480
bool in_arena(csubstr s) const
return true if the given substring is part of the tree's string arena
Definition tree.hpp:1327
id_type next_sibling(id_type node) const
Definition tree.hpp:572
Callbacks const & callbacks() const
Definition tree.hpp:349
bool is_seq(id_type node) const
Definition tree.hpp:481
id_type find_child(id_type node, csubstr const &key) const
find child by name, or NONE if no child is found with this key like Tree::child(),...
Definition tree.cpp:1249
id_type first_sibling(id_type node) const
Definition tree.hpp:592
csubstr arena() const
get the current arena
Definition tree.hpp:1317
id_type size() const
Definition tree.hpp:345
@ FTOA_FLOAT
print the real number in floating point format (like f)
Definition charconv.hpp:194
void parse_in_arena(Parser *parser, csubstr filename, csubstr yaml, Tree *tree, id_type node_id)
(1) parse YAML into an existing tree node. The filename will be used in any error messages arising du...
Definition parse.cpp:209
ParseEngine< EventHandlerTree > Parser
This is the main ryml parser, where the parser events are handled to create a ryml tree (see Event Ha...
Definition fwd.hpp:19
bool check_assertion_occurs(Fn &&fn)
checking that an assertion occurs while calling fn.
bool check_error_occurs(Fn &&fn)
checking that an error occurs while calling fn
ryml::Callbacks callbacks()
a helper to create the Callbacks object for the custom error handler
basic_substring< const char > csubstr
an immutable string view
Definition substr.hpp:2356
@ NONE
an index to none
Definition common.hpp:131
RYML_ID_TYPE id_type
The type of a node id in the YAML tree; to override the default type, define the macro RYML_ID_TYPE t...
Definition common.hpp:124
Shows how to create a scoped error handler.
bool empty() const noexcept
Definition substr.hpp:356
The event handler to create a ryml Tree.
holds a source or yaml file position, for example when an error is detected; See also location_format...
Definition common.hpp:229
size_t col
column
Definition common.hpp:232
size_t line
line
Definition common.hpp:231
Options to give to the ParseEngine to control its behavior.
bool is_val() const RYML_NOEXCEPT
Forward to Tree::is_val().
Definition node.hpp:211
void load_key(T *k, bool check_readable=true) const
(1) deserialize the node's key (necessarily a scalar) to the given variable, forwarding to the user-o...
Definition node.hpp:385
bool has_child(ConstImpl const &n) const RYML_NOEXCEPT
Forward to Tree::has_child().
Definition node.hpp:282
bool is_map() const RYML_NOEXCEPT
Forward to Tree::is_map().
Definition node.hpp:207
id_type num_siblings() const RYML_NOEXCEPT
O(num_children).
Definition node.hpp:303
bool has_key() const RYML_NOEXCEPT
Forward to Tree::has_key().
Definition node.hpp:210
csubstr key() const RYML_NOEXCEPT
Forward to Tree::key().
Definition node.hpp:174
csubstr val() const RYML_NOEXCEPT
Forward to Tree::val().
Definition node.hpp:179
id_type num_children() const RYML_NOEXCEPT
O(num_children).
Definition node.hpp:302
Location location(Parser const &parser) const
Definition node.hpp:318
bool is_seq() const RYML_NOEXCEPT
Forward to Tree::is_seq().
Definition node.hpp:208
bool has_val() const RYML_NOEXCEPT
Forward to Tree::has_val().
Definition node.hpp:209

Referenced by main().