简体   繁体   中英

How to parse arbitrary yaml file with yaml-cpp library without knowing keys in mappings and types of the terminal scalars?

Recently, I was given the task to parse a YAML file containing parameters for some automated calculation. I didn't hear the word "YAML" before, but honestly read about it the all that I could find on the Internet.

All the parsing examples that I saw are based on the fact that they know beforehand the contents of the parsed .yaml file. They know keys in the yaml mappings and types of the terminal scalars. But with the component I have to write, this is not the case. This component is a kind of intermediary, which only should translate the .yaml file into a composite run-time structure that will serve as a data source for the end clients.

The "composite" pattern for run-time representation is chosen because the YAML text itself is a kind of "persistent composite". A draft of this run-time structure might look like this:

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;
        }
};

In this draft, CAnyAttribute is a concrete class that can be assigned any scalar value: integer and floating-point numbers of different sizes, strings and even raw "N bytes". This class has been used for years in our software product.

I understand that this whole task may look strange, since the YAML::Node from the yaml-cpp parser is itself a run-time representation of the YAML text. There are two reasons for this. Firstly, the YAML::Node interface is not standard, not documented well and takes time to understand. Assuming that many programmers in the company will have to deal with it, this is a lot of time. And the second and more important, we do not want to be closely tied to one particular library.

Now the question is: how to parse an arbitrary yaml file into the above runtime structure? We use the yaml-cpp library of the latest version 0.6.

I found a solution, and I hope it will be useful to others as well. Here it is:

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";
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM