[Input] Implement construction of AnyMap from YAML string/file
This commit is contained in:
parent
e85e0e2108
commit
00f6b88fa4
3 changed files with 268 additions and 0 deletions
|
|
@ -175,6 +175,15 @@ class AnyMap
|
|||
public:
|
||||
AnyMap() {};
|
||||
|
||||
//! Create an AnyMap from a YAML file.
|
||||
/*!
|
||||
* Searches the Cantera include path for the file.
|
||||
*/
|
||||
static AnyMap fromYamlFile(const std::string& name);
|
||||
|
||||
//! Create an AnyMap from a string containing a YAML document
|
||||
static AnyMap fromYamlString(const std::string& yaml);
|
||||
|
||||
AnyValue& operator[](const std::string& key);
|
||||
|
||||
const AnyValue& at(const std::string& key) const;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,224 @@
|
|||
// at http://www.cantera.org/license.txt for license and copyright information.
|
||||
|
||||
#include "cantera/base/AnyMap.h"
|
||||
#include "cantera/base/yaml.h"
|
||||
#include "cantera/base/stringUtils.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace ba = boost::algorithm;
|
||||
|
||||
namespace { // helper functions
|
||||
|
||||
bool isFloat(const std::string& val)
|
||||
{
|
||||
// This function duplicates the logic of fpValueCheck, but doesn't throw
|
||||
// exceptions if the string isn't a float
|
||||
std::string str = ba::trim_copy(val);
|
||||
if (str.empty()) {
|
||||
return false;
|
||||
}
|
||||
int numDot = 0;
|
||||
int numExp = 0;
|
||||
int istart = 0;
|
||||
char ch = str[0];
|
||||
if (ch == '+' || ch == '-') {
|
||||
istart = 1;
|
||||
if (str.size() == 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (size_t i = istart; i < str.size(); i++) {
|
||||
ch = str[i];
|
||||
if (isdigit(ch)) {
|
||||
} else if (ch == '.') {
|
||||
numDot++;
|
||||
if (numDot > 1) {
|
||||
return false;
|
||||
}
|
||||
if (numExp > 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (ch == 'e' || ch == 'E') {
|
||||
numExp++;
|
||||
if (numExp > 1 || i == str.size() - 1) {
|
||||
return false;
|
||||
}
|
||||
ch = str[i+1];
|
||||
if (ch == '+' || ch == '-') {
|
||||
if (i + 1 == str.size() - 1) {
|
||||
return false;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isInt(const std::string& val)
|
||||
{
|
||||
std::string str = ba::trim_copy(val);
|
||||
if (str.empty()) {
|
||||
return false;
|
||||
}
|
||||
int istart = 0;
|
||||
char ch = str[0];
|
||||
if (ch == '+' || ch == '-') {
|
||||
istart = 1;
|
||||
if (str.size() == 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (size_t i = istart; i < str.size(); i++) {
|
||||
if (!isdigit(str[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isBool(const std::string& val) {
|
||||
std::string str = ba::trim_copy(val);
|
||||
return (val == "true" || val == "True" || val == "false" || val == "False");
|
||||
}
|
||||
|
||||
enum class Type : char {
|
||||
Unknown = 0,
|
||||
Integer = 1,
|
||||
Double = 2,
|
||||
String = 4,
|
||||
Bool = 8,
|
||||
Map = 16,
|
||||
Sequence = 32
|
||||
};
|
||||
|
||||
Type operator|(Type lhs, Type rhs)
|
||||
{
|
||||
return Type(static_cast<char>(lhs) | static_cast<char>(rhs));
|
||||
}
|
||||
|
||||
Type elementTypes(const YAML::Node& node)
|
||||
{
|
||||
// See what kinds of elements we have:
|
||||
Type types = Type::Unknown;
|
||||
for (const auto& el : node) {
|
||||
if (el.IsMap()) {
|
||||
types = types | Type::Map;
|
||||
} else if (el.IsSequence()) {
|
||||
types = types | Type::Sequence;
|
||||
} else if (el.IsScalar()) {
|
||||
std::string nodestr = el.as<std::string>();
|
||||
if (isInt(nodestr)) {
|
||||
types = types | Type::Integer;
|
||||
} else if (isFloat(nodestr)) {
|
||||
types = types | Type::Double;
|
||||
} else if (isBool(nodestr)) {
|
||||
types = types | Type::Bool;
|
||||
} else {
|
||||
types = types | Type::String;
|
||||
}
|
||||
}
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
namespace YAML { // YAML converters
|
||||
|
||||
using namespace Cantera;
|
||||
|
||||
template<>
|
||||
struct convert<Cantera::AnyMap> {
|
||||
static Node encode(const Cantera::AnyMap& rhs) {
|
||||
throw NotImplementedError("AnyMap::encode");
|
||||
}
|
||||
static bool decode(const Node& node, Cantera::AnyMap& target) {
|
||||
if (!node.IsMap()) {
|
||||
std::string text = YAML::Dump(node);
|
||||
if (text.size() > 300) {
|
||||
text.resize(300);
|
||||
}
|
||||
throw CanteraError("YAML::convert<AnyMap>",
|
||||
"YAML node is not a map. Node begins with:\n'''\n{}\n'''", text);
|
||||
}
|
||||
for (const auto& child : node) {
|
||||
std::string key = child.first.as<std::string>();
|
||||
if (child.second.IsMap()) {
|
||||
target[key] = child.second.as<AnyMap>();
|
||||
} else {
|
||||
target[key] = child.second.as<AnyValue>();
|
||||
target[key].setKey(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct convert<Cantera::AnyValue> {
|
||||
static Node encode(const Cantera::AnyValue& rhs) {
|
||||
throw NotImplementedError("AnyValue::encode");
|
||||
}
|
||||
|
||||
static bool decode(const Node& node, Cantera::AnyValue& target) {
|
||||
if (node.IsScalar()) {
|
||||
// Scalar nodes are int, doubles, or strings
|
||||
std::string nodestr = node.as<std::string>();
|
||||
if (isInt(nodestr)) {
|
||||
target = intValue(nodestr);
|
||||
} else if (isFloat(nodestr)) {
|
||||
target = fpValue(nodestr);
|
||||
} else if (isBool(nodestr)) {
|
||||
target = node.as<bool>();
|
||||
} else {
|
||||
target = nodestr;
|
||||
}
|
||||
return true;
|
||||
} else if (node.IsSequence()) {
|
||||
// Convert sequences of the same element type to vectors of that type
|
||||
Type types = elementTypes(node);
|
||||
if (types == Type::Integer) {
|
||||
target = node.as<std::vector<long int>>();
|
||||
} else if (types == (Type::Integer | Type::Double) || types == Type::Double) {
|
||||
target = node.as<vector_fp>();
|
||||
} else if (types == Type::String) {
|
||||
target = node.as<std::vector<std::string>>();
|
||||
} else if (types == Type::Bool) {
|
||||
target = node.as<std::vector<bool>>();
|
||||
} else if (types == Type::Map) {
|
||||
target = node.as<std::vector<AnyMap>>();
|
||||
} else if (types == Type::Sequence) {
|
||||
// Create nested vectors when data types are compatible
|
||||
Type subtypes = Type::Unknown;
|
||||
for (const auto& el : node) {
|
||||
subtypes = subtypes | elementTypes(el);
|
||||
}
|
||||
if (subtypes == Type::Integer) {
|
||||
target = node.as<std::vector<std::vector<long int>>>();
|
||||
} else if (subtypes == (Type::Integer | Type::Double) || subtypes == Type::Double) {
|
||||
target = node.as<std::vector<std::vector<double>>>();
|
||||
} else if (types == Type::String) {
|
||||
target = node.as<std::vector<std::vector<std::string>>>();
|
||||
} else if (types == Type::Bool) {
|
||||
target = node.as<std::vector<std::vector<bool>>>();
|
||||
} else {
|
||||
target = node.as<std::vector<AnyValue>>();
|
||||
}
|
||||
} else {
|
||||
// If types are different, create a vector of generic values
|
||||
target = node.as<std::vector<AnyValue>>();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace Cantera
|
||||
{
|
||||
|
|
@ -192,4 +410,14 @@ bool AnyMap::hasKey(const std::string& key) const
|
|||
}
|
||||
}
|
||||
|
||||
AnyMap AnyMap::fromYamlString(const std::string& yaml) {
|
||||
YAML::Node node = YAML::Load(yaml);
|
||||
return node.as<AnyMap>();
|
||||
}
|
||||
|
||||
AnyMap AnyMap::fromYamlFile(const std::string& name) {
|
||||
YAML::Node node = YAML::LoadFile(findInputFile(name));
|
||||
return node.as<AnyMap>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,3 +88,34 @@ TEST(AnyMap, vector)
|
|||
// Should have modified the copy in the map
|
||||
EXPECT_EQ(m["nested/item"].asVector<double>().size(), (size_t) 4);
|
||||
}
|
||||
|
||||
TEST(AnyMap, loadYaml)
|
||||
{
|
||||
AnyMap m = AnyMap::fromYamlString(
|
||||
"name: NO2\n"
|
||||
"composition: {N: 1, O: 2}\n"
|
||||
"thermo:\n"
|
||||
" model: NASA7\n"
|
||||
" reference-pressure: 1 atm\n"
|
||||
" temperature-ranges: [200, 1000, 6000]\n"
|
||||
" data:\n"
|
||||
" - [3.944031200E+00, -1.585429000E-03, 1.665781200E-05, -2.047542600E-08,\n"
|
||||
" 7.835056400E-12, 2.896617900E+03, 6.311991700E+00]\n"
|
||||
" - [4.884754200E+00, 2.172395600E-03, -8.280690600E-07, 1.574751000E-10,\n"
|
||||
" -1.051089500E-14, 2.316498300E+03, -1.174169500E-01]\n"
|
||||
"transport: # this is a comment\n"
|
||||
" model: gas\n"
|
||||
" geometry: nonlinear\n"
|
||||
" flag: true\n");
|
||||
|
||||
EXPECT_EQ(m["name"].asString(), "NO2");
|
||||
EXPECT_EQ(m["composition/N"].asInt(), 1);
|
||||
EXPECT_EQ(m["thermo/reference-pressure"].asString(), "1 atm");
|
||||
EXPECT_EQ(m["thermo/temperature-ranges"].asVector<long int>()[0], 200);
|
||||
EXPECT_EQ(m["transport/geometry"].asString(), "nonlinear");
|
||||
EXPECT_TRUE(m["transport/flag"].asBool());
|
||||
auto coeffs = m["thermo/data"].asVector<vector_fp>();
|
||||
EXPECT_EQ(coeffs.size(), (size_t) 2);
|
||||
EXPECT_EQ(coeffs[0].size(), (size_t) 7);
|
||||
EXPECT_DOUBLE_EQ(coeffs[1][2], -8.280690600E-07);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue