https://gitlab.opengeosys.org/ogs/ogs.git
Raw File
Tip revision: 3bf620c5e6d91e8ab75ec74e5111e5fd35c8e49f authored by Tom Fischer on 28 May 2021, 08:41:38 UTC
Merge branch 'DataExplorerFixWarnings' into 'master'
Tip revision: 3bf620c
ConfigTreeUtil.cpp
/**
 * \file
 * \copyright
 * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
 *            Distributed under a Modified BSD License.
 *              See accompanying file LICENSE.txt or
 *              http://www.opengeosys.org/project/license
 *
 */

#include "ConfigTreeUtil.h"

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <spdlog/spdlog.h>
#include <xml_patch.h>

#include <boost/property_tree/xml_parser.hpp>
#include <regex>

#include "BaseLib/FileTools.h"
#include "Error.h"
#include "Logging.h"
#include "filesystem.h"

namespace BaseLib
{
ConfigTreeTopLevel::ConfigTreeTopLevel(const std::string& filepath,
                                       const bool be_ruthless,
                                       ConfigTree::PTree&& ptree)
    : ptree_(std::move(ptree)),
      ctree_(ptree_, filepath, ConfigTree::onerror,
             be_ruthless ? ConfigTree::onerror : ConfigTree::onwarning)
{
}

ConfigTree const& ConfigTreeTopLevel::operator*() const
{
    return ctree_;
}

ConfigTree const* ConfigTreeTopLevel::operator->() const
{
    return &ctree_;
}

void ConfigTreeTopLevel::checkAndInvalidate()
{
    ::BaseLib::checkAndInvalidate(ctree_);
}

// Adapted from
// https://stackoverflow.com/questions/8154107/how-do-i-merge-update-a-boostproperty-treeptree/8175833
template <typename T>
void traverse_recursive(
    boost::property_tree::ptree& parent,
    boost::property_tree::ptree::path_type const& child_path,
    boost::property_tree::ptree& child,
    fs::path const& bench_dir,
    T& method)
{
    using boost::property_tree::ptree;

    method(parent, child_path, child, bench_dir);
    for (auto& [key, tree] : child)
    {
        ptree::path_type const cur_path = child_path / ptree::path_type(key);
        traverse_recursive(child, cur_path, tree, bench_dir, method);
    }
}

template <typename T>
void traverse(boost::property_tree::ptree& parent, const fs::path bench_dir,
              T& method)
{
    traverse_recursive(parent, "", parent, bench_dir, method);
}

void replaceIncludes(
    [[maybe_unused]] boost::property_tree::ptree const& parent,
    [[maybe_unused]] boost::property_tree::ptree::path_type const& child_path,
    boost::property_tree::ptree& child,
    fs::path const& bench_dir)
{
    using boost::property_tree::ptree;
    for (auto& [key, tree] : child)
    {
        if (key == "include")
        {
            auto filename = tree.get<std::string>("<xmlattr>.file");
            if (auto const filepath = fs::path(filename);
                filepath.is_relative())
            {
                filename = (bench_dir / filepath).string();
            }
            INFO("Including {:s} into project file.", filename);

            ptree include_tree;
            read_xml(filename, include_tree,
                     boost::property_tree::xml_parser::no_comments |
                         boost::property_tree::xml_parser::trim_whitespace);

            // Can only insert subtree at child
            auto& tmp_tree = child.put_child("include", include_tree);

            // Move subtree above child
            std::move(tmp_tree.begin(), tmp_tree.end(), back_inserter(child));

            // Erase child
            child.erase("include");

            // There can only be one include under a parent element!
            break;
        }
    }
}

// Applies a patch file to the prj content in prj_stream.
void patchStream(std::string patch_file, std::stringstream& prj_stream)
{
    auto patch = xmlParseFile(patch_file.c_str());
    if (patch == NULL)
    {
        OGS_FATAL("Error reading XML diff file {:s}.", patch_file);
    }

    auto doc =
        xmlParseMemory(prj_stream.str().c_str(), prj_stream.str().size());
    if (doc == NULL)
    {
        OGS_FATAL("Error reading project file from memory.");
    }

    auto node = xmlDocGetRootElement(patch);
    int rc = 0;
    for (node = node ? node->children : NULL; node; node = node->next)
    {
        if (node->type != XML_ELEMENT_NODE)
        {
            continue;
        }

        if (!strcmp(reinterpret_cast<const char*>(node->name), "add"))
        {
            rc = xml_patch_add(doc, node);
        }
        else if (!strcmp(reinterpret_cast<const char*>(node->name), "replace"))
        {
            rc = xml_patch_replace(doc, node);
        }
        else if (!strcmp(reinterpret_cast<const char*>(node->name), "remove"))
        {
            rc = xml_patch_remove(doc, node);
        }
        else
        {
            OGS_FATAL(
                "Error while patching prj file with patch file {:}. Only "
                "'add', 'replace' and 'remove' elements are allowed!",
                patch_file);
        }

        if (rc)
        {
            OGS_FATAL("Error while patching prj file with patch file {:}.",
                      patch_file);
        }
    }

    xmlChar* xmlbuff;
    int buffersize;
    xmlDocDumpMemory(doc, &xmlbuff, &buffersize);
    prj_stream.str("");  // Empty stream
    prj_stream << xmlbuff;

    xmlFree(xmlbuff);
    xmlFreeDoc(doc);
    xmlFreeDoc(patch);
}

// Will set prj_file to the actual .prj file and returns the final prj file
// content in prj_stream.
void readAndPatchPrj(std::stringstream& prj_stream, std::string& prj_file,
                     std::vector<std::string> patch_files)
{
    // Extract base project file path if an xml (patch) file is given as prj
    // file and it contains the base_file attribute.
    if (BaseLib::getFileExtension(prj_file) == ".xml")
    {
        if (!patch_files.empty())
        {
            OGS_FATAL(
                "It is not allowed to specify additional patch files "
                "if a patch file was already specified as the "
                "prj-file.");
        }
        auto patch = xmlParseFile(prj_file.c_str());
        auto node = xmlDocGetRootElement(patch);
        auto base_file = xmlGetProp(node, (const xmlChar*)"base_file");
        if (base_file == nullptr)
        {
            OGS_FATAL(
                "Error reading base prj file (base_file attribute) in given "
                "patch file {:s}.",
                prj_file);
        }
        patch_files = {prj_file};
        std::stringstream ss;
        ss << base_file;
        prj_file = BaseLib::joinPaths(BaseLib::extractPath(prj_file), ss.str());
    }

    // read base prj file into stream
    std::ifstream file(prj_file);
    if (file)
    {
        prj_stream << file.rdbuf();
        file.close();
    }

    // apply xml patches to stream
    if (!patch_files.empty())
    {
        for (const auto& patch_file : patch_files)
        {
            patchStream(patch_file, prj_stream);
        }
        xmlCleanupParser();
    }
}

ConfigTreeTopLevel makeConfigTree(const std::string& filepath,
                                  const bool be_ruthless,
                                  const std::string& toplevel_tag,
                                  const std::vector<std::string>& patch_files)
{
    std::string prj_file = filepath;
    std::stringstream prj_stream;
    readAndPatchPrj(prj_stream, prj_file, patch_files);

    ConfigTree::PTree ptree;

    // note: Trimming whitespace and ignoring comments is crucial in order
    //       for our configuration tree implementation to work!
    try
    {
        read_xml(prj_stream, ptree,
                 boost::property_tree::xml_parser::no_comments |
                     boost::property_tree::xml_parser::trim_whitespace);

        if (toplevel_tag == "OpenGeoSysProject")
        {
            traverse(ptree, fs::path(prj_file).parent_path(), replaceIncludes);
        }
    }
    catch (boost::property_tree::xml_parser_error const& e)
    {
        OGS_FATAL("Error while parsing XML file `{:s}' at line {:d}: {:s}.",
                  e.filename(), e.line(), e.message());
    }

    DBUG("Project configuration from file '{:s}' read.", filepath);

    if (auto child = ptree.get_child_optional(toplevel_tag))
    {
        return ConfigTreeTopLevel(filepath, be_ruthless, std::move(*child));
    }
    OGS_FATAL("Tag <{:s}> has not been found in file `{:s}'.", toplevel_tag,
              filepath);
}

}  // namespace BaseLib
back to top