如何在不知道终端标量的映射和类型中的键的情况下使用 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&lt;float&gt;() 来尝试使用float,然后是int,然后是string @Ilia 是的,你可以,node.Tag()。我不确定它是否会给你!!strtag: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>如何在不知道键的情况下遍历项目[重复]

INSERT IF NOT EXISTS 类型函数供我在不将列转换为主键的情况下使用?

如何在不重新打印的情况下更新终端中的打印消息

如何在不使用复合文字的情况下从标量初始化 AltiVec 寄存器