【发布时间】: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 中使用带有为标签解析定义的正则表达式的正则表达式引擎,并以此决定类型。请注意,带引号的标量将得到
!而不是?,并且应始终解析为字符串。