Skip to content
Snippets Groups Projects
ConfigTreeUtil.cpp 8.41 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * \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 <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 "Logging.h"
    
    Lars Bilke's avatar
    Lars Bilke committed
    #include "filesystem.h"
    
    
    namespace BaseLib
    {
    
    Lars Bilke's avatar
    Lars Bilke committed
    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)
    
    Lars Bilke's avatar
    Lars Bilke committed
    ConfigTree const& ConfigTreeTopLevel::operator*() const
    
    Lars Bilke's avatar
    Lars Bilke committed
    ConfigTree const* ConfigTreeTopLevel::operator->() const
    
    Lars Bilke's avatar
    Lars Bilke committed
    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,
    
    Lars Bilke's avatar
    Lars Bilke committed
    {
    
        using boost::property_tree::ptree;
    
    Lars Bilke's avatar
    Lars Bilke committed
    
    
        method(parent, child_path, child, bench_dir);
    
        for (auto& [key, tree] : child)
    
    Lars Bilke's avatar
    Lars Bilke committed
        {
    
            ptree::path_type const cur_path = child_path / ptree::path_type(key);
    
            traverse_recursive(child, cur_path, tree, bench_dir, method);
    
    void traverse(boost::property_tree::ptree& parent, const fs::path bench_dir,
    
        traverse_recursive(parent, "", parent, bench_dir, method);
    
        [[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);
    
                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! Got an "
                    "element '{:s}' on line {:d}.",
                    patch_file, node->name, node->line);
    
                OGS_FATAL(
                    "Error while patching prj file with patch file {:}. Error in "
                    "element '{:s}' on line {:d}.",
                    patch_file, node->name, node->line);
    
            }
        }
    
        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
    
        if (std::ifstream file(prj_file); file)
    
        {
            prj_stream << file.rdbuf();
    
        }
        else
        {
            if (!BaseLib::IsFileExisting(prj_file))
            {
                ERR("File {:s} does not exist.", prj_file);
            }
            DBUG("Stream state flags: {:b}.", file.rdstate());
            OGS_FATAL("Could not open project file '{:s}' for reading.", prj_file);
    
        }
    
        // apply xml patches to stream
    
        if (!patch_files.empty())
        {
            for (const auto& patch_file : patch_files)
            {
    
                patchStream(patch_file, prj_stream);
    
    }
    
    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!
    
            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)
        {
    
    Dmitri Naumov's avatar
    Dmitri Naumov committed
            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);
    
    Lars Bilke's avatar
    Lars Bilke committed
        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);
    
    Dmitri Naumov's avatar
    Dmitri Naumov committed
    }  // namespace BaseLib