xmlbase.cpp 10.5 KB
/* ****************************************************************************
 * Copyright 2019 Open Systems Development BV                                 *
 *                                                                            *
 * Permission is hereby granted, free of charge, to any person obtaining a    *
 * copy of this software and associated documentation files (the "Software"), *
 * to deal in the Software without restriction, including without limitation  *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
 * and/or sell copies of the Software, and to permit persons to whom the      *
 * Software is furnished to do so, subject to the following conditions:       *
 *                                                                            *
 * The above copyright notice and this permission notice shall be included in *
 * all copies or substantial portions of the Software.                        *
 *                                                                            *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    *
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        *
 * DEALINGS IN THE SOFTWARE.                                                  *
 * ***************************************************************************/

#include "xmlbase.h"
#include "log.h"

/*
 *   ________________________________________
 *  / Distance doesn't make you any smaller, \
 *  | but it does make you part of a larger  |
 *  \ picture.                               /
 *   ----------------------------------------
 *     \
 *      \
 *      .--.
 *     |o_o |
 *     |:_/ |
 *    //   \ \
 *   (|     | )
 *  /'\_   _/`\
 *  \___)=(___/
 *
 **********************************************************/

using namespace osdev::components::xml;
using namespace osdev::components::log;

void xml_string_writer::write(const void* data, size_t size)
{
    result += std::string(static_cast<const char*>(data), size);
}

XmlBase::XmlBase(const QString& xmlFile)
    : m_xmldoc()
    , m_xPathHash()
{
    if (!xmlFile.isNull() || !xmlFile.isEmpty()) {
        if (parseFile(xmlFile)) {
            LogDebug("[XmlBase::XmlBase]", QString("File : %1 ..............[OK].").arg(xmlFile).toStdString());
        }
        else {
            LogError("[XmlBase::XmlBase]", QString("File : %1 ..............[Failed].").arg(xmlFile).toStdString());
            throw std::runtime_error("[XmlBase::XmlBase] parseFile failed");
        }
    }
}

XmlBase::~XmlBase() = default;

bool XmlBase::parseString(const QString& qsXml)
{
    bool bResult = false;

    if (!qsXml.isEmpty()) {
        pugi::xml_parse_status parseStatus = m_xmldoc.load_buffer(qsXml.toStdString().c_str(),
                                                         qsXml.toStdString().size())
                                                 .status;
        bResult = checkError(parseStatus);
    }

    return bResult;
}

bool XmlBase::parseFile(const QString& qsXml)
{
    bool bResult = false;

    if (!qsXml.isEmpty()) {
        pugi::xml_parse_status parseStatus = m_xmldoc.load_file(qsXml.toStdString().c_str()).status;
        bResult = checkError(parseStatus);
    }

    return bResult;
}

bool XmlBase::checkError(pugi::xml_parse_status _parseStatus)
{
    bool bResult = false;
    QString sLogMessage = "An unknown error occured.";

/*
* GCC 5.3.1 insits on all enumeration cases for this switch to be specified.
* The enumeration cases that throws -Wswitch warning are not available in pugixml for FC21.
* For backward compatibility with older pugixml version "-Wswitch" warnings are temporarily supressed.
*/
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch"
    switch (_parseStatus) {
        case pugi::status_ok:
            sLogMessage = "File parsed successfully.";
            bResult = true;
            break;
        case pugi::status_file_not_found:
            sLogMessage = "File not found.";
            break;
        case pugi::status_io_error:
            sLogMessage = "Some I/O Error occured during reading the file / stream. Check the hardware for errors.";
            break;
        case pugi::status_out_of_memory:
            sLogMessage = "Out of Memory while parsing the file.";
            break;
        case pugi::status_internal_error:
            sLogMessage = "Oh dear... That's different. Something went horribly wrong. It is unrecoverable. We're exiting the whole shabang..";
            break;
        case pugi::status_unrecognized_tag:
            sLogMessage = "Parsing stopping due to a tag with either an empty name or a name which starts with an incorrect character (Left a '#' somewhere?)";
            break;
        case pugi::status_bad_pi:
            sLogMessage = "Parsing stopped due to incorrect document declaration/processing instruction. ";
            break;
        case pugi::status_bad_comment:
            sLogMessage = "Parsing stopped due to the invalid construct of a Comment.";
            break;
        case pugi::status_bad_cdata:
            sLogMessage = "Parsing stopped due to the invalid construct of a CDATA section.";
            break;
        case pugi::status_bad_doctype:
            sLogMessage = "Parsing stopped due to the invalid construct of a DocType.";
            break;
        case pugi::status_bad_pcdata:
            sLogMessage = "Parsing stopped due to the invalid construct of a PCDATA section.";
            break;
        case pugi::status_bad_attribute:
            sLogMessage = "Parsing stopped because there was an incorrect attribute, such as an attribute without value or with value that is not quoted (note that <node attr=1> is incorrect in XML).";
            break;
        case pugi::status_bad_start_element:
            sLogMessage = "Parsing stopped because a starting tag either had no closing > symbol or contained some incorrect symbol.";
            break;
        case pugi::status_bad_end_element:
            sLogMessage = "Parsing stopped because ending tag had incorrect syntax (i.e. extra non-whitespace symbols between tag name and >).";
            break;
        case pugi::status_end_element_mismatch:
            sLogMessage = "parsing stopped because the closing tag did not match the opening one (i.e. <node></nedo>) or because some tag was not closed at all.";
            break;
            // DISABLED ON FEDORA 21 and CentOS 7. Not present in pugixml 1.0-8.fc21
            // case pugi::status_append_invalid_root:
            // case pugi::status_no_document_element:
    }
#pragma GCC diagnostic pop
    LogDebug("[XmlBase::checkError]", sLogMessage.toStdString());
    return bResult;
}

void XmlBase::addXPath(const QString& qsName, const QString& qsXPath)
{
    if (m_xPathHash.contains(qsName)) {
        LogWarning("[XmlBase::addXPath]", QString( "XPath already registered : " + qsName).toStdString());
    }
    m_xPathHash.insert(qsName, qsXPath);

    LogDebug("[XmlBase::addXPath]", QString("XPath" + qsXPath + " registered with key : " + qsName).toStdString());
}

QString XmlBase::getXPath(const QString& qsXPathSelect) const
{
    QString qsXPath = m_xPathHash.value(qsXPathSelect);

    if (qsXPath.isEmpty()) {
        LogWarning("[XmlBase::getXPath]", QString("XPath not registered : " + qsXPathSelect).toStdString());
    }

    return qsXPath;
}

QString XmlBase::evaluateXPath(const QString& qsXPathSelect,
    const QList<QVariant>& arguments) const
{
    QString qsResult = getXPath(qsXPathSelect);

    LogDebug( "[XmlBase::evaluateXPath]", QString( "Found XPathExpression : " + qsResult + " for selection : " + qsXPathSelect ).toStdString() );

    for (auto& value : arguments)
    {
        qsResult = qsResult.arg(value.toString());
    }

    LogInfo("[XmlBase::evaluateXPath]", QString("Resulting XPathExpression : " + qsResult).toStdString());
    return qsResult;
}

void XmlBase::setSimpleData(const QString& qsXPathSelect,
    const QList<QVariant>& arguments,
    const QVariant& data)
{
    QString qsXPath = evaluateXPath(qsXPathSelect, arguments);
    setNodeData(qsXPath, data);
}

void XmlBase::setSimpleData(const QString& qsXPathSelect, const QVariant& data)
{
    QString qsXPath = getXPath(qsXPathSelect);

    setNodeData(qsXPath, data);
}

QVariant XmlBase::getSimpleData(const QString& qsXPathSelect,
    const QList<QVariant>& arguments) const
{
    QString qsXPath = evaluateXPath(qsXPathSelect, arguments);

    QVariant qvResult = getNodeData(qsXPath);

    return qvResult;
}

// static
bool XmlBase::getBoolean(const QVariant& value)
{
    bool b_result = false;

    QString l_result = value.toString().toUpper();
    if ( // ------------------------
        ("Y" == l_result) ||
        ("YES" == l_result) ||
        ("TRUE" == l_result) ||
        ("ON" == l_result) ||
        ("1" == l_result)
        // ------------------------
    ) {
        b_result = true;
    }

    return b_result;
}

// static
QString XmlBase::getAttributeValue(const pugi::xml_node& xmlNode, const char* attributeName)
{
    const auto attr = xmlNode.attribute(attributeName);
    if (attr.empty()) {
        return {};
    }
    return attr.value();
}

pugi::xpath_node XmlBase::selectNode(const QString& qsXPath) const
{
    return m_xmldoc.select_node(qsXPath.toStdString().c_str());
}

QList<pugi::xpath_node> XmlBase::selectNodes(const QString& qsXPath) const
{
    QList<pugi::xpath_node> lstResult;

    pugi::xpath_node_set nodes = m_xmldoc.select_nodes(qsXPath.toStdString().c_str());
    for (auto& node : nodes) {
        lstResult.append(node);
    }

    return lstResult;
}

void XmlBase::setNodeData(const QString& qsXPath, const QVariant& qsData)
{
    pugi::xml_node selectedNode;
    selectedNode = selectNode(qsXPath).node();

    if (!selectedNode.empty()) {
        selectedNode.set_value(qsData.toString().toStdString().c_str());
    }
    else {
        LogError("[XmlBase::setNodeData]", QString("No node(s) found for XPath expression : '%1'").arg(qsXPath).toStdString());
    }
}

QVariant XmlBase::getNodeData(const QString& qsXPath) const
{
    QVariant qvResult;
    pugi::xml_node selectedNode = selectNode(qsXPath).node();
    if (!selectedNode.empty()) {
        qvResult = QString(selectedNode.value());
    }
    return qvResult;
}

QString XmlBase::asString() const
{
    xml_string_writer writer;
    m_xmldoc.save(writer);

    return QString(writer.result.c_str());
}