如何在不知道终端标量的映射和类型中的键的情况下使用 yaml-cpp 库解析任意 yaml 文件?
Posted
技术标签:
【中文标题】如何在不知道终端标量的映射和类型中的键的情况下使用 yaml-cpp 库解析任意 yaml 文件?【英文标题】:How to parse arbitrary yaml file with yaml-cpp library without knowing keys in mappings and types of the terminal scalars? 【发布时间】:2020-03-04 12:31:03 【问题描述】:最近,我的任务是解析一个 YAML 文件,其中包含一些自动计算的参数。我以前没听过“YAML”这个词,但老实说,我在互联网上找到了所有关于它的信息。
我看到的所有解析示例都是基于他们事先知道已解析的 .yaml 文件的内容这一事实。他们知道 yaml 映射中的键和终端标量的类型。但是对于我必须编写的组件,情况并非如此。该组件是一种中介,它只应将 .yaml 文件转换为复合运行时结构,作为最终客户端的数据源。
选择运行时表示的“复合”模式是因为 YAML 文本本身是一种“持久复合”。此运行时结构的草稿可能如下所示:
class CAnyAttribute;
class CAttrBaseNode
std::wstring m_Name;
std::string m_Tag;
public:
CAttrBaseNode(const std::wstring& Name) : m_Name(Name)
std::wstring Name() const return m_Name;
std::string Tag() const return m_Tag;
void SetTag(const std::string& TagVal) m_Tag = TagVal;
virtual ~CAttrBaseNode()
virtual bool IsScalar() const = 0;
virtual CAnyAttribute Value() const
return CAnyAttribute();
virtual bool SetValue(const CAnyAttribute& Val)
return false;
virtual CAttrBaseNode* Child(int Idx) const
return 0; // Get node by index
virtual CAttrBaseNode* Child(const std::wstring& Key) const
return 0; // Get node by key
virtual bool AddChild(CAttrBaseNode* pNode, int Idx = -1)
return false; // Add node by index
;
class CAttrLeafNode : public CAttrBaseNode
CAnyAttribute m_Value;
public:
CAttrLeafNode(const std::wstring& Name, const CAnyAttribute& Val) :
CAttrBaseNode(Name), m_Value(Val)
~CAttrLeafNode()
bool IsScalar() const override return true;
CAnyAttribute Value() const override
return m_Value;
bool SetValue(const CAnyAttribute& Val) override
m_Value = Val; return true;
;
class CAttrCompositeNode : public CAttrBaseNode
std::vector<CAttrBaseNode*> m_Children;
public:
CAttrCompositeNode(const std::wstring& Name) : CAttrBaseNode(Name)
~CAttrCompositeNode() for (auto& pChild : m_Children) delete pChild;
bool IsScalar() const override return false;
CAttrBaseNode* Child(int Idx) const override
return (0 <= Idx && Idx < (int)m_Children.size()) ? m_Children[Idx] : 0;
CAttrBaseNode* Child(const std::wstring& Key) const override
auto it = std::find_if(m_Children.begin(), m_Children.end(),
[Key](CAttrBaseNode* pNode)->bool
return pNode->Name() == Key; );
return (it != m_Children.end()) ? *it : 0;
bool AddChild(CAttrBaseNode* pNode, int Idx = -1) override
if (pNode == 0 || Idx >= (int)m_Children.size())
return false;
if (Idx < 0)
m_Children.push_back(pNode);
else
m_Children.insert(m_Children.begin() + Idx, pNode);
return true;
;
在本草案中,CAnyAttribute
是一个具体的类,可以分配任何标量值:不同大小的整数和浮点数、字符串,甚至是原始的“N 字节”。此类已在我们的软件产品中使用多年。
我知道整个任务可能看起来很奇怪,因为来自 yaml-cpp 解析器的 YAML::Node 本身就是 YAML 文本的运行时表示。有两个原因。首先,YAML::Node 接口不是标准的,没有很好的文档记录并且需要时间来理解。假设公司中的许多程序员都必须处理它,这是很多时间。第二个也是更重要的一点,我们不希望与一个特定的库紧密联系。
现在的问题是:如何将任意yaml文件解析成上述运行时结构?我们使用最新版本0.6的yaml-cpp库。
【问题讨论】:
您可以询问YAML::Node
它是什么:node.Type()
- 或者您可以询问它是否是特定类型:node.IsSequence()
等。
@Ted Lyngmo,是的,但我无法询问 SCALAR 节点是整数、浮点还是字符串。
不,据我所知不是直接的。您可以使用node.as<float>()
来尝试使用float
,然后是int
,然后是string
。
@Ilia 是的,你可以,node.Tag()
。我不确定它是否会给你!!str
或tag:yaml.org,2002:str
一个字符串(YAML 实现在这里不同),但你可以测试它。
@Ilia 好的,这似乎是open issue。我建议在core schema 中使用带有为标签解析定义的正则表达式的正则表达式引擎,并以此决定类型。请注意,带引号的标量将得到 !
而不是 ?
,并且应始终解析为字符串。
【参考方案1】:
我找到了一个解决方案,我希望它也对其他人有用。这里是:
void Scalar2AnyAttribute(const YAML::Node& ScalarNode, CAnyAttribute& AnyAttr)
assert(ScalarNode.IsScalar());
//
// Parse the scalar value using the @flyx's advice.
//
CAttrBaseNode* Translate(const YAML::Node& YNode, const std::string& Name = std::string())
CAttrBaseNode* pRet = 0;
switch (YNode.Type())
case YAML::NodeType::Null:
pRet = new CAttrLeafNode(Name, CAnyAttribute());
break;
case YAML::NodeType::Scalar:
CAnyAttribute Value;
Scalar2AnyAttribute(YNode, Value);
pRet = new CAttrLeafNode(Name, Value);
break;
case YAML::NodeType::Sequence:
CAttrCompositeNode* pComp = new CAttrCompositeNode(Name);
for (YAML::const_iterator it = YNode.begin(); it != YNode.end(); ++it)
pComp->AddChild(Translate(*it));
pRet = pComp;
break;
case YAML::NodeType::Map:
CAttrCompositeNode* pComp = new CAttrCompositeNode(Name);
for (YAML::const_iterator it = YNode.begin(); it != YNode.end(); ++it)
std::string MappingKey = it->first.as<std::string>();
pComp->AddChild(Translate(it->second, MappingKey));
pRet = pComp;
break;
default:
break;
if (pRet)
pRet->SetTag(YNode.Tag());
return pRet;
int main()
std::string file("....\\a.yaml");
YAML::Node baseNode = YAML::LoadFile(file);
CAttrBaseNode* pComposite = Translate(baseNode);
// ...
// Work with pComposite.
// ...
delete pComposite;
std::cout << "Hello, my first translation from YAML!\n";
【讨论】:
以上是关于如何在不知道终端标量的映射和类型中的键的情况下使用 yaml-cpp 库解析任意 yaml 文件?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不知道 json 键的情况下使用 JsonReader 从 json 读取值
c#Dictionary<string,string>如何在不知道键的情况下遍历项目[重复]