From 1c5287339bb40cbe796b7b40646e736d983b66e7 Mon Sep 17 00:00:00 2001 From: Peter M. Groen Date: Sun, 24 Jul 2022 13:41:29 +0200 Subject: [PATCH] Setting up a first attempt --- .gitignore | 4 ++++ CMakeLists.txt | 16 ++++++++++++++++ scripts/setup_submodules | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ scripts/submodules.list | 4 ++++ src/CMakeLists.txt | 35 +++++++++++++++++++++++++++++++++++ src/dcxmlbase.cpp | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/dcxmlbase.h | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 712 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100755 scripts/setup_submodules create mode 100644 scripts/submodules.list create mode 100644 src/CMakeLists.txt create mode 100644 src/dcxmlbase.cpp create mode 100644 src/dcxmlbase.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fac0377 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/CMakeLists.txt.user +build/ +submodules/ +.gitmodules diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ed82044 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.10) +project(osdev_xml) +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/submodules/cmake) + +# ================================================================= +# = Include build information +include(projectheader) +project_header(osdev_xml) + +add_subdirectory(src) +#add_subdirectory(test) +#add_subdirectory(examples) + +include(packaging) +package_component() + diff --git a/scripts/setup_submodules b/scripts/setup_submodules new file mode 100755 index 0000000..0aad384 --- /dev/null +++ b/scripts/setup_submodules @@ -0,0 +1,170 @@ +#!/bin/bash + +# =============================================== +# == Setting some environment variables +# =============================================== +GIT_URL_OPEN="http://gitlab.osdev.nl/open_source" +GIT_URL_CLOSED="git@gitlab.osdev.nl:closed_source" + +FUNC_RESULT="-1" + +# Name : print_usage_exit() +# Description : Print the way this script is intended to be used and exit. +# Parameters : None. +# Returns : err_code 1 to the Operating System +# -------------------------------------------------------------------------------------- +function print_usage_exit() +{ + echo "Usage $0 -i|--install|-u|--update" + echo " -i or --install Install the submodules mentioned in the submodules.list" + echo " -u or --update Update the submodules mentioned in the submodules.list" + echo " " + exit 1 +} + +# Name : check_top_or_sub +# Description : Determine if we're running in a "single" lib-build or part of a +# "meta"-repository ( submodule ). +# Parameters : None +# Returns : Updates the value FUNC_RESULT. +# -1 - We're neither a git-repo or submodule. +# 0 - We're a submodule +# 1 - We're a top-repo ( Single library ) +# -------------------------------------------------------------------------------------- +function check_top_or_sub() +{ + # This function checks if we're the top-repository. + # In that case we need the submodules.. If we're already a submodule, + # we simply exit this script with a message + if [ -e ./.git ]; then + FUNC_RESULT="1" + return + elif [ -e ../.git ]; then + if [ -e ../.submodules ]; then + echo "Seems like we're already a submodule. Nothing to do here." + FUNC_RESULT="0" + return + fi + fi + FUNC_RESULT="-1" + return +} + +# Name : check_working_dir +# Description : If we're in the top of our repo, we can run this script further. +# Parameters : None. +# Returns : Updates the value FUNC_RESULT. +# -1 - Not used. +# 0 - We're not on the top-level +# 1 - We're at the top-level. Good to go. +# -------------------------------------------------------------------------------------- +function check_working_dir() +{ + FUNC_RESULT="-1" + # Check if we're in the top-level directory of our repository. + if [ -f ./scripts/submodules.list ]; then + # We're good to go + FUNC_RESULT="1" + return + fi + FUNC_RESULT="0" + return +} + +# Name : read_submodules +# Description : Read the list of submodules needed for this project +# Parameters : None +# Returns : Updates the value FUNC_RESULT +# 0 - Module list was not found +# 1 - Module list was found and read. +# -------------------------------------------------------------------------------------- +function read_submodules() +{ + FUNC_RESULT="-1" + if [ -e ./scripts/submodules.list ]; then + source ./scripts/submodules.list + FUNC_RESULT="1" + return + fi + + echo "Submodules list not found...." + FUNC_RESULT="0" + return +} + +# Name : add_submodules +# Description : Configure the repo to add the submodules. +# Parameters : None. +# Returns : None. +# -------------------------------------------------------------------------------------- +function add_submodules() +{ + echo -e "Adding SubModule(s)." + for SUB_MODULE in ${SUB_MODULES_OPEN} + do + git submodule add -f ${GIT_URL_OPEN}/${SUB_MODULE}.git submodules/${SUB_MODULE} + git config submodule.${SUB_MODULE}.url ${GIT_URL_OPEN}/${SUB_MODULE}.git + done + + for SUB_MODULE in ${SUB_MODULES_CLOSED} + do + echo {GIT_URL_CLOSED}/${SUB_MODULE}.git + git submodule add -f ${GIT_URL_CLOSED}/${SUB_MODULE}.git submodules/${SUB_MODULE} + git config submodule.${SUB_MODULE}.url ${GIT_URL_CLOSED}/${SUB_MODULE}.git + done + +} + +# Name : get_submodules +# Description : Actually get the submodules from gitlab and add them. +# Parameters : None +# Returns : None +# -------------------------------------------------------------------------------------- +function get_submodules() +{ + git submodule update --init --recursive +} + +# Name : update_submodules +# Description : Update the submodules already added. +# Parameters : None +# Returns : None +# -------------------------------------------------------------------------------------- +function update_submodules() +{ + git submodule update --recursive +} + +# ============================================================================= +# == T H E M A I N E N T R Y O F T H I S S C R I P T == +# ============================================================================= +check_top_or_sub +if [ "${FUNC_RESULT}" == "0" ]; then + echo "Seems like we're a submodule already or not part of a repository." + exit 0 +fi + +check_working_dir +if [ "${FUNC_RESULT}" == "0" ]; then + echo "Go to the top of this repository and type : scripts/setup_submodules [-i|--install]" + exit 0 +fi + +read_submodules + +case "$1" in + -i*|--install*) + echo "Installing submodules for this repository ( ${PWD} )" + add_submodules + get_submodules + ;; + -u*|--update*) + echo "Update submodules : ${SUB_MODULES}" + update_submodules + ;; + *) + echo "No parameters found..." + print_usage_exit + ;; +esac + diff --git a/scripts/submodules.list b/scripts/submodules.list new file mode 100644 index 0000000..a05b06e --- /dev/null +++ b/scripts/submodules.list @@ -0,0 +1,4 @@ +SUB_MODULES_OPEN="cmake +logger" + +SUB_MODULES_CLOSED="" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..e614610 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.0) +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake) +include(projectheader) +project_header(config) + +find_package( Qt5Core REQUIRED ) + +include_directories( SYSTEM + ${Qt5Core_INCLUDE_DIRS} +) + +include(compiler) + +include_directories( + ${CMAKE_SOURCE_DIR}/submodules/logger/src +) + +set(SRC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/dcxmlbase.h + ${CMAKE_CURRENT_SOURCE_DIR}/dcxmlbase.cpp +) + +link_directories( + ${CMAKE_BINARY_DIR}/lib +) + +include(library) +add_libraries( + ${Qt5Core_LIBRARIES} + pugixml + logger +) + +include(installation) +install_component() diff --git a/src/dcxmlbase.cpp b/src/dcxmlbase.cpp new file mode 100644 index 0000000..8aa81fa --- /dev/null +++ b/src/dcxmlbase.cpp @@ -0,0 +1,280 @@ +#include "dcxmlbase.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(data), size); +} + +DcXmlBase::DcXmlBase(const QString& xmlFile) + : m_xmldoc() + , m_xPathHash() +{ + if (!xmlFile.isNull() || !xmlFile.isEmpty()) { + if (parseFile(xmlFile)) { + LogDebug("[DcXmlBase::DcXmlBase]", QString("File : %1 ..............[OK].").arg(xmlFile).toStdString()); + } + else { + LogError("[DcXmlBase::DcXmlBase]", QString("File : %1 ..............[Failed].").arg(xmlFile).toStdString()); + throw std::runtime_error("[DcXmlBase::DcXmlBase] parseFile failed"); + } + } +} + +DcXmlBase::~DcXmlBase() = default; + +bool DcXmlBase::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 DcXmlBase::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 DcXmlBase::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 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. ) 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("[DcXmlBase::checkError]", sLogMessage.toStdString()); + return bResult; +} + +void DcXmlBase::addXPath(const QString& qsName, const QString& qsXPath) +{ + if (m_xPathHash.contains(qsName)) { + LogWarning("[DcXmlBase::addXPath]", std::string( "XPath already registered : " + qsName.toStdString())); + } + m_xPathHash.insert(qsName, qsXPath); + + LogDebug("[DcXmlBase::addXPath]", std::string("XPath" + qsXPath.toStdString() + " registered with key : " + qsName.toStdString())); +} + +QString DcXmlBase::getXPath(const QString& qsXPathSelect) const +{ + QString qsXPath = m_xPathHash.value(qsXPathSelect); + + if (qsXPath.isEmpty()) { + LogWarning("dcxml", "XPath not registered : " + qsXPathSelect); + } + + return qsXPath; +} + +QString DcXmlBase::evaluateXPath(const QString& qsXPathSelect, + const QList& arguments) const +{ + QString qsResult = getXPath(qsXPathSelect); + + // LogInfo( "DcXmlBase::evaluateXPath", QString( "Found XPathExpression : " + qsResult + " for selection : " + qsXPathSelect ) ); + + for (auto& value : arguments) { + qsResult = qsResult.arg(value.toString()); + } + + // LogInfo( "DcXmlBase::evaluateXPath", QString( "Resulting XPathExpression : " + qsResult ) ); + return qsResult; +} + +void DcXmlBase::setSimpleData(const QString& qsXPathSelect, + const QList& arguments, + const QVariant& data) +{ + QString qsXPath = evaluateXPath(qsXPathSelect, arguments); + setNodeData(qsXPath, data); +} + +void DcXmlBase::setSimpleData(const QString& qsXPathSelect, const QVariant& data) +{ + QString qsXPath = getXPath(qsXPathSelect); + + setNodeData(qsXPath, data); +} + +QVariant DcXmlBase::getSimpleData(const QString& qsXPathSelect, + const QList& arguments) const +{ + QString qsXPath = evaluateXPath(qsXPathSelect, arguments); + + QVariant qvResult = getNodeData(qsXPath); + + return qvResult; +} + +// static +bool DcXmlBase::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 DcXmlBase::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 DcXmlBase::selectNode(const QString& qsXPath) const +{ + return m_xmldoc.select_node(qsXPath.toStdString().c_str()); +} + +QList DcXmlBase::selectNodes(const QString& qsXPath) const +{ + QList lstResult; + + pugi::xpath_node_set nodes = m_xmldoc.select_nodes(qsXPath.toStdString().c_str()); + for (auto& node : nodes) { + lstResult.append(node); + } + + return lstResult; +} + +void DcXmlBase::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("dcxml", + QString("No node(s) found for XPath expression : '%1'") + .arg(qsXPath)); + } +} + +QVariant DcXmlBase::getNodeData(const QString& qsXPath) const +{ + QVariant qvResult; + pugi::xml_node selectedNode = selectNode(qsXPath).node(); + if (!selectedNode.empty()) { + qvResult = QString(selectedNode.value()); + } + return qvResult; +} + +QString DcXmlBase::asString() const +{ + xml_string_writer writer; + m_xmldoc.save(writer); + + return QString(writer.result.c_str()); +} diff --git a/src/dcxmlbase.h b/src/dcxmlbase.h new file mode 100644 index 0000000..beba9bc --- /dev/null +++ b/src/dcxmlbase.h @@ -0,0 +1,203 @@ +/* **************************************************************************** + * Copyright 2022 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. * + * ***************************************************************************/ + +#pragma once + +#include +#include + +#include + +namespace osdev { +namespace components { +namespace xml { + +/*! + * \brief xml_string_writer + * This struct is used to write a pugiXL DOM document to a std::string (QString) + * pugiXML is not able to this on its own, but we use the possibility to dump + * an XML-document to std::cout + */ +struct xml_string_writer : pugi::xml_writer +{ + std::string result = std::string(); + virtual void write(const void* data, size_t size) override; +}; + +/*! + * \brief The dcXmlBase class describes the base class for all xml related classes + * The basic functionality is implemented here. This class is intended to + * be inherited and specialized and not to be used directly. ( Nothing bad + * will happen, just to make life easier ) + */ +/* + * ________________________________________ + * / I'm serious about thinking through all \ + * | the possibilities before we settle on | + * | anything. All things have the | + * | advantages of their disadvantages, and | + * | vice versa. | + * | | + * | -- Larry Wall in | + * \ <199709032332.QAA21669@wall.org> / + * ---------------------------------------- + * \ + * \ + * .--. + * |o_o | + * |:_/ | + * // \ \ + * (| | ) + * /'\_ _/`\ + * \___)=(___/ + */ + +class DcXmlBase +{ +public: + /*! Default constructor */ + explicit DcXmlBase(const QString& xmlFile = QString()); + virtual ~DcXmlBase(); + + /*! + * \brief Parses the XML contents of the string. + * \param qsXml - String containing the contents of the XML-string + * \return true if successfull, false if not... + */ + bool parseString(const QString& qsXml); + + /*! + * \brief Parses the contents of the given XML-file. + * \param qsXml - String with the filepath & -name of the XML file. + * \return true if successfull, false if not... + */ + bool parseFile(const QString& qsXml); + + /*! + * \brief Adds an XPath expression to the internal structure. + * \param qsName - Name of the XPath expression. This should be descriptive to make life easier + * \param qsXPath - The XPath expression to the specific data we're interested in. + */ + void addXPath(const QString& qsName, const QString& qsXPath); + + /*! + * \brief Retrieves an XPath expression from the internal structure + * \param qsXPathSelect - The name of the XPath expression. + * \return The XPath expression as stored in the internal Hash + */ + QString getXPath(const QString& qsXPathSelect) const; + + /*! + * \brief Interprets the XPath expression and adds values to the variables. + * \param qsXPathSelect - The XPath expression as given by getXPath. + * \param arguments - The list of variables to be added to the XPath expression. + * \return The interpreted XPath expression. + */ + QString evaluateXPath(const QString& qsXPathSelect, const QList& arguments) const; + + /*! + * \brief Set a simple node in the xml-doc. + * \param qsXPathSelect - The XPath expression of the node we want to set. + * \param arguments - the list of arguments in the node + * \param data - The nodetext. + */ + void setSimpleData(const QString& qsXPathSelect, const QList& arguments, const QVariant& data); + + /*! + * \brief Set a simple node in the xml-doc. + * \param qsXPathSelect - The XPath expression of the node we want to set. + * \param data - The nodetext. + */ + void setSimpleData(const QString& qsXPathSelect, const QVariant& data); + + QVariant getSimpleData(const QString& qsXPathSelect, const QList& arguments) const; + + /*! + * \brief Retreive a single node based on the XPath expression + * \param qsXPath - The XPath expression + * \return - a single xpath-node + */ + pugi::xpath_node selectNode(const QString& qsXPath) const; + + /*! + * \brief Retreives a list of nodes based on the given XPath expression + * \param qsXPath - The XPath expression + * \return - The list of nodes + */ + QList selectNodes(const QString& qsXPath) const; + + /*! Added for convenience. The DOMTree is exported as QString */ + QString asString() const; + + /*! Helper method for the setSimpleData */ + void setNodeData(const QString& qsXPath, const QVariant& qsData); + + //! Helper method for the getSimpleData. Get data from the node that + //! is selected with the XPath expression. + virtual QVariant getNodeData(const QString& qsXPath) const; + + /** + * @brief Turns a value into a boolean. + * @param value Value to be interpreted. + * @return boolean representation of value + * + * The following (capitalised) items convert to "true" : + * - Y + * - YES + * - TRUE + * - ON + * - 1 + * + * Everything else converts to false. + */ + static bool getBoolean(const QVariant& value); + + /** + * @param xmlNode The node to query for an attribute value. + * @param attributeName The name of the attribute to query. + * @return an attribute value. + * @retval value as QString + * @retval null QString when the attribute is not found. + */ + static QString getAttributeValue(const pugi::xml_node& xmlNode, const char* attributeName); + +private: + /*! + * \brief Translates the pugiXML status into Human Readable messages + * \param parseStatus - The pugi::XML status enum from the parser. + * \return True if there was no error whatsoever. False if not.. 8-| + */ + bool checkError(pugi::xml_parse_status parseStatus); + + /*! + * \brief The internal representation of the XML document. + */ + pugi::xml_document m_xmldoc; + + //! Storage for the XPath expressions + QHash m_xPathHash; +}; + +} // End namespace xml +} // End namespace osdev +} // End namespace components + -- libgit2 0.21.4