Как проанализировать произвольный файл yaml с библиотекой yaml- cpp, не зная ключей в отображениях и типах терминальных скаляров? - PullRequest
0 голосов
/ 04 марта 2020

Недавно мне было дано задание проанализировать файл YAML, содержащий параметры, для некоторого автоматического вычисления. Я не слышал слово «YAML» раньше, но честно прочитал об этом все, что я мог найти в Inte rnet.

Все примеры разбора, которые я видел, основаны на том факте, что они заранее знать содержимое проанализированного файла .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 :: Node из синтаксического анализатора yaml- cpp сам по себе является представлением времени выполнения текст YAML. Для этого есть две причины. Во-первых, интерфейс YAML :: Node не является стандартным, плохо документирован и требует времени для понимания. Предполагая, что многим программистам в компании придется иметь дело с этим, это много времени. И второе, и более важное, мы не хотим быть тесно привязанными к одной конкретной библиотеке.

Теперь возникает вопрос: как проанализировать произвольный файл yaml в приведенной выше структуре времени выполнения? Мы используем библиотеку yaml- cpp последней версии 0.6.

1 Ответ

0 голосов
/ 09 марта 2020

Я нашел решение и надеюсь, что оно будет полезно и другим. Вот оно:

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