c++ - 如何在不知道终端标量的映射和类型的情况下使用 yaml-cpp 库解析任意 yaml 文件?
问题描述
最近,我的任务是解析一个 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 库。
解决方案
我找到了一个解决方案,我希望它对其他人也有用。这里是:
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";
}
推荐阅读
- keras - Keras 中的 ResNet50v2
- html - 如何在 Angular 8 中显示值显示错误类型错误:无法读取未定义的属性“项目名称”
- unity3d - 为什么 BoxColliders 碰撞不起作用?
- python - 被称为请求的 python 库仅适用于 web url 还是本地 html 文件?
- c# - 如何检测进程是否移动了鼠标光标而不是用户?
- sql - 通过 Oracle 外部表读取 CSV 文件时跳过标题
- python-3.x - 在python中编写一个特定的函数
- python - How do I exclude the last line of user input?
- c - CMake:另一个具有相同名称的目标存在于一个链接库中
- excel - 无法使用 vba 解析网页中某个项目的特定值