|
Tutorial
Quick Start Tutorial
IntroductionThe following describes a new, experimental API. For the old API, see HowToParseADocument or HowToEmitYAML. This API is very much in flux, and I'd love to get feedback. Please email me or leave a comment! A typical example, loading a configuration file, might look like this: YAML::Node config = YAML::LoadFile("config.yaml");
if(config["lastLogin"])
std::cout << "Last logged in: " << config["lastLogin"].as<DateTime>() << "\n";
const std::string username = config["username"].as<std::string>();
const std::string password = config["password"].as<std::string>();
login(username, password);
config["lastLogin"] = getCurrentDateTime();
std::ofstream fout("config.yaml");
fout << config;Basic Parsing and Node EditingAll nodes in a YAML document (including the root) are represented by YAML::Node. You can check what kind it is: YAML::Node node = YAML::Load("[1, 2, 3]");
assert(node.Type() == YAML::NodeType::Sequence);
assert(node.IsSequence()); // a shortcut!Collection nodes (sequences and maps) act somewhat like STL vectors and maps: YAML::Node primes = YAML::Load("[2, 3, 5, 7, 11]");
for(std::size_t i=0;i<primes.size();i++)
std::cout << primes[i].as<int>() << "\n";
// or:
for(YAML::const_iterator it=primes.begin();it!=primes.end();++it)
std::cout << it->as<int>() << "\n";
primes.push_back(13);
assert(primes.size() == 6);and YAML::Node lineup = YAML::Load("{1B: Prince Fielder, 2B: Rickie Weeks, LF: Ryan Braun}");
for(YAML::const_iterator it=lineup.begin();it!=lineup.end();++it)
std::cout << "Playing at " << it->first.as<std::string>() << " is " << it->second.as<std::string>() << "\n";
lineup["RF"] = "Corey Hart";
lineup["C"] = "Jonathan Lucroy";
assert(lineup.size() == 5);Querying for keys does not create them automatically (this makes handling optional map entries very easy) YAML::Node node = YAML::Load("{name: Brewers, city: Milwaukee}");
if(node["name"])
std::cout << node["name"].as<std::string>() << "\n";
if(node["mascot"])
std::cout << node["mascot"].as<std::string>() << "\n";
assert(node.size() == 2); // the previous call didn't create a nodeBuilding NodesYou can build YAML::Node from scratch: YAML::Node node; // starts out as null
node["key"] = "value"; // it now is a map node
node["seq"].push_back("first element"); // node["seq"] automatically becomes a sequence
node["seq"].push_back("second element");
node["mirror"] = node["seq"][0]; // this creates an alias
node["seq"][0] = "1st element"; // this also changes node["mirror"]
node["mirror"] = "element #1"; // and this changes node["seq"][0] - they're really the "same" node
node["self"] = node; // you can even create self-aliases
node[node["mirror"]] = node["seq"]; // and strange loops :)The above node is now: &1 key: value &2 seq: [&3 "element #1", second element] mirror: *3 self: *1 *3 : *2 How Sequences Turn Into MapsSequences can be turned into maps by asking for non-integer keys. For example, YAML::Node node = YAML::Load("[1, 2, 3]");
node[1] = 5; // still a sequence, [1, 5, 3]
node.push_back(-3) // still a sequence, [1, 5, 3, -3]
node["key"] = "value"; // now it's a map! {0: 1, 1: 5, 2: 3, 3: -3, key: value}Indexing a sequence node by an index that's not in its range will usually turn it into a map, but if the index is one past the end of the sequence, then the sequence will grow by one to accommodate it. (That's the only exception to this rule.) For example, YAML::Node node = YAML::Load("[1, 2, 3]");
node[3] = 4; // still a sequence, [1, 2, 3, 4]
node[10] = 10; // now it's a map! {0: 1, 1: 2, 2: 3, 3: 4, 10: 10}Converting To/From Native Data TypesYaml-cpp has built-in conversion to and from most built-in data types, as well as std::vector, std::list, and std::map. The following examples demonstrate when those conversions are used: YAML::Node node = YAML::Load("{pi: 2.718, [0, 1]: integers}");
// this needs the conversion from Node to double
double pi = node["pi"].as<double>();
// this needs the conversion from double to Node
node["e"] = 2.7818;
// this needs the conversion from Node to std::vector<int> (*not* the other way around!)
std::vector<int> v;
v.push_back(0);
v.push_back(1);
std::string str = node[v].as<std::string>();To use yaml-cpp with your own data types, you need to specialize the YAML::convert<> template class. For example, suppose you had a simple Vec3 class: struct Vec3 { double x, y, z; /* etc */ };You could write namespace YAML {
template<>
struct convert<Vec3> {
static Node encode(const Vec3& rhs) {
Node node;
node.push_back(rhs.x);
node.push_back(rhs.y);
node.push_back(rhs.z);
return node;
}
static bool decode(const Node& node, Vec3& rhs) {
if(!node.IsSequence())
return false;
if(!node.size() == 3)
return false;
rhs.x = node[0].as<double>();
rhs.y = node[1].as<double>();
rhs.z = node[2].as<double>();
return true;
}
};
}Then you could use Vec3 wherever you could use any other type: YAML::Node node = YAML::Load("start: [1, 3, 0]");
Vec3 v = node["start"].as<Vec3>();
node["end"] = Vec3(2, -1, 0);
|
Sorry, I shouldn't have used the issue category for my problem. This example is not working for me. Is there an API change in this regard?
YAML::Node node = YAML::Load("{name: Brewers, city: Milwaukee}"); if(node["name"]) std::cout << node["name"].as<std::string>() << "\n"; if(node["mascot"]) std::cout << node["mascot"].as<std::string>() << "\n"; assert(node.size() == 2); // the previous call didn't create a nodeNo problem - I answered the issue, and I'll repeat here: this was my mistake - I forgot to implement it. I've added it, r68cecbc525f9.
By the way, thanks for trying the new API! Given that this problem has been there since I started the new API and no one else has commented suggests that no one's trying it. I appreciate your help in getting the bugs out!
No I love, the struct convert<_T> ability. That's exactly what I need. Btw. push_back was replaced by append?
Yeah, I switched it to append. Which do you prefer? I've always felt like push_back is a silly name. But it might be better to keep it in line with the STL.
No no, append is definitely better. I think it's rather an advantage to not be confused with STL.
One more thing. the as() Conversion does not throw YAML::Exception but std::runtime_error. See code below, where I actually have to check for runtime_error.
Is this temporary. Is there a reason why it is not handled by YAML::Exceptions? Thanks.
template < class _T > static bool parseNodeToValue(const YAML::Node& input, _T& value) { bool success = true; try { value = input.as<_T>(); //input >> value; } catch (YAML::Exception &e) { ROS_ERROR_STREAM("Error converting from YAML! " << e.what()); success = false; } catch (std::runtime_error &e) { ROS_ERROR_STREAM("Error converting from YAML! " << e.what()); success = false; } return success; }Yeah, this was me being lazy, sorry. It will throw a YAML::Exception soon.
IMHO this API is a great improvement compared to the old one. One suggestion would be to add the possibility of a default return value to the "as" function, so that for example node.as<int> (42) doesn't throw exceptions, but returns 42 if no value could be parsed. I think that could ease things in quite some situations. Keep up the good work!
@mokaga42, great idea! I posted it as Issue 136 , and I'll get to it soon; you can keep track of it there.
While I also think push_back is a silly name, push_back will allow it to play better with the STL (e.g. algorithms for STL containers or std::back_inserter.)
@dan, you're probably right, I renamed it back to push_back
I love the new API and hope it makes it into the master branch soon!
I did need to modify the syntax for the templates as mentioned on MSDN for compiler error C2906 (http://msdn.microsoft.com/en-us/library/csxsafs9%28v=vs.100%29.aspx)
So instead, it would appear thus:
namespace YAML { template<> struct convert<Vec3> { static Node encode(const Vec3& rhs) { ... } static bool decode(const Node& node, Vec3& rhs) { ... } }; }@deledrius, thanks! Sorry about that typo - I fixed it in the tutorial.
Glad you like the new API!
Hello, I'm newbie of yaml_cpp, as well as YAML itself. first I really appreciate you for this great library. I wonder that this new API seems so different from the old one, so when you'd like to apply it as the main branch? If it's not so far from now, I want to get the new one even if it's a little buggy.
Thank you again.
@jinserk, thanks! I'm planning to release this branch as 0.5.0 soon, and shortly thereafter, I'll switch this to the main branch of the repo and create a branch old-api. As for more specific time frames, I'm not sure. You can certainly grab this branch now, though, as-is :)
One more question, should I get boost for new api? The old one didn't require boost when I did cmake, however, new api does now. Typically boost is too heavy for a lightweight application, I want to avoid to get it. In this case is the old api the unique option I can choose?
@jinserk, yes, the new API requires boost. But it only requires boost headers, no libraries, so it shouldn't be a strenuous requirement. If you do not want to include boost headers, then yes, you'll need to stick with the old API - sorry.
I think the signature of decode:
is problematic because it is impossible to use with immutable classes. I think it would be better to mirror the signature of encode, i.e.,
and have the function throw an exception if there was an error in the data. This way, Vec3 can be any copyable or moveable type, and it doesn't have to be default constructable or mutable.
@rkjnsn, you might be right, I'll think about that. I suppose the worry is that a type might be expensive to copy.
I'll consider both ways - thanks for the suggestion!
@rkjnsn, This problem occurred in my project, too. I cannot implement default constructors of required classes in some reason. So I'm unable to use the suggested solution with the
Please find a way to solve this! :)
@jbeder, on most modern compilers, wouldn't return value optimization eliminate the copy, anyway?
@rkjnsn, yes, that's probably true
The new API is much more compact and easier to use, thanks!
I could be wrong here, but doesn't YAML allow a node to be both a scalar and a sequence/map? In the new API, YAML::Node::Type() only allows one or the other. I'm having an issue in the new API where with a document like below, the node returned from Load() is only a scalar:
I can't get at the sequence 1, 2, 3 because the YAML::Node::size() function returns zero.
@stuart, thanks!
YAML doesn't allow a node to be both a scalar and a sequence/map, so your example is actually an ill-formed document.
I'm not sure what you're trying to do, but you can mimic this behavior by adding a tag to your sequence. E.g.:
Interesting. Easy enough to fix the document. I assumed since it worked in the old API, it was supported by YAML. I was using the scalar as a document title, but I just turned it into a map instead:
You explain how easy it is to construct YAML with the new API, but how do you emit (write) YAML text? When I call as<std::string>() on a map or sequence, it raises an exception that it must be a scalar. Did I miss something?
Never mind, looks like the old method still works. Example:
void invalidNode( const YAML::Node &node ) { YAML::Emitter out; out << node; printf( "Invalid node:\n%s", out.c_str() ); }@stuart, that's right, but there's a shortcut as well:
I should add that to the tutorial :)
Thanks!! BTW, the STL-like iterators and such are nice, but it doesn't quite go as far as to be compatible with things like boost Range and, by association, BOOST_FOREACH, which I use all the time to make container iteration very easy to code:
#include <boost/foreach.hpp> // This does not compile: BOOST_FOREACH( const YAML::Node &item, node ) // node is a const YAML::Node { cout << YAML::Dump(item) << std::endl; ... }@stuart, interesting! I posted this as Issue 159 , so you can follow it - I'll see if I can fix it.