OGS
ConfigTreeUtil.cpp
Go to the documentation of this file.
1 
11 #include "ConfigTreeUtil.h"
12 
13 #include <libxml/parser.h>
14 #include <libxml/tree.h>
15 #include <spdlog/spdlog.h>
16 #include <xml_patch.h>
17 
18 #include <boost/property_tree/xml_parser.hpp>
19 #include <regex>
20 
21 #include "BaseLib/FileTools.h"
22 #include "Error.h"
23 #include "Logging.h"
24 #include "filesystem.h"
25 
26 namespace BaseLib
27 {
28 ConfigTreeTopLevel::ConfigTreeTopLevel(const std::string& filepath,
29  const bool be_ruthless,
30  ConfigTree::PTree&& ptree)
31  : ptree_(std::move(ptree)),
32  ctree_(ptree_, filepath, ConfigTree::onerror,
33  be_ruthless ? ConfigTree::onerror : ConfigTree::onwarning)
34 {
35 }
36 
38 {
39  return ctree_;
40 }
41 
43 {
44  return &ctree_;
45 }
46 
48 {
50 }
51 
52 // Adapted from
53 // https://stackoverflow.com/questions/8154107/how-do-i-merge-update-a-boostproperty-treeptree/8175833
54 template <typename T>
56  boost::property_tree::ptree& parent,
57  boost::property_tree::ptree::path_type const& child_path,
58  boost::property_tree::ptree& child,
59  fs::path const& bench_dir,
60  T& method)
61 {
62  using boost::property_tree::ptree;
63 
64  method(parent, child_path, child, bench_dir);
65  for (auto& [key, tree] : child)
66  {
67  ptree::path_type const cur_path = child_path / ptree::path_type(key);
68  traverse_recursive(child, cur_path, tree, bench_dir, method);
69  }
70 }
71 
72 template <typename T>
73 void traverse(boost::property_tree::ptree& parent, const fs::path bench_dir,
74  T& method)
75 {
76  traverse_recursive(parent, "", parent, bench_dir, method);
77 }
78 
80  [[maybe_unused]] boost::property_tree::ptree const& parent,
81  [[maybe_unused]] boost::property_tree::ptree::path_type const& child_path,
82  boost::property_tree::ptree& child,
83  fs::path const& bench_dir)
84 {
85  using boost::property_tree::ptree;
86  for (auto& [key, tree] : child)
87  {
88  if (key == "include")
89  {
90  auto filename = tree.get<std::string>("<xmlattr>.file");
91  if (auto const filepath = fs::path(filename);
92  filepath.is_relative())
93  {
94  filename = (bench_dir / filepath).string();
95  }
96  INFO("Including {:s} into project file.", filename);
97 
98  ptree include_tree;
99  read_xml(filename, include_tree,
100  boost::property_tree::xml_parser::no_comments |
101  boost::property_tree::xml_parser::trim_whitespace);
102 
103  // Can only insert subtree at child
104  auto& tmp_tree = child.put_child("include", include_tree);
105 
106  // Move subtree above child
107  std::move(tmp_tree.begin(), tmp_tree.end(), back_inserter(child));
108 
109  // Erase child
110  child.erase("include");
111 
112  // There can only be one include under a parent element!
113  break;
114  }
115  }
116 }
117 
118 // Applies a patch file to the prj content in prj_stream.
119 void patchStream(std::string patch_file, std::stringstream& prj_stream)
120 {
121  auto patch = xmlParseFile(patch_file.c_str());
122  if (patch == NULL)
123  {
124  OGS_FATAL("Error reading XML diff file {:s}.", patch_file);
125  }
126 
127  auto doc =
128  xmlParseMemory(prj_stream.str().c_str(), prj_stream.str().size());
129  if (doc == NULL)
130  {
131  OGS_FATAL("Error reading project file from memory.");
132  }
133 
134  auto node = xmlDocGetRootElement(patch);
135  int rc = 0;
136  for (node = node ? node->children : NULL; node; node = node->next)
137  {
138  if (node->type != XML_ELEMENT_NODE)
139  {
140  continue;
141  }
142 
143  if (!strcmp(reinterpret_cast<const char*>(node->name), "add"))
144  {
145  rc = xml_patch_add(doc, node);
146  }
147  else if (!strcmp(reinterpret_cast<const char*>(node->name), "replace"))
148  {
149  rc = xml_patch_replace(doc, node);
150  }
151  else if (!strcmp(reinterpret_cast<const char*>(node->name), "remove"))
152  {
153  rc = xml_patch_remove(doc, node);
154  }
155  else
156  {
157  OGS_FATAL(
158  "Error while patching prj file with patch file {:}. Only "
159  "'add', 'replace' and 'remove' elements are allowed! Got an "
160  "element '{:s}' on line {:d}.",
161  patch_file, node->name, node->line);
162  }
163 
164  if (rc)
165  {
166  OGS_FATAL(
167  "Error while patching prj file with patch file {:}. Error in "
168  "element '{:s}' on line {:d}.",
169  patch_file, node->name, node->line);
170  }
171  }
172 
173  xmlChar* xmlbuff;
174  int buffersize;
175  xmlDocDumpMemory(doc, &xmlbuff, &buffersize);
176  prj_stream.str(""); // Empty stream
177  prj_stream << xmlbuff;
178 
179  xmlFree(xmlbuff);
180  xmlFreeDoc(doc);
181  xmlFreeDoc(patch);
182 }
183 
184 // Will set prj_file to the actual .prj file and returns the final prj file
185 // content in prj_stream.
186 void readAndPatchPrj(std::stringstream& prj_stream, std::string& prj_file,
187  std::vector<std::string> patch_files)
188 {
189  // Extract base project file path if an xml (patch) file is given as prj
190  // file and it contains the base_file attribute.
191  if (BaseLib::getFileExtension(prj_file) == ".xml")
192  {
193  if (!patch_files.empty())
194  {
195  OGS_FATAL(
196  "It is not allowed to specify additional patch files "
197  "if a patch file was already specified as the "
198  "prj-file.");
199  }
200  auto patch = xmlParseFile(prj_file.c_str());
201  auto node = xmlDocGetRootElement(patch);
202  auto base_file = xmlGetProp(node, (const xmlChar*)"base_file");
203  if (base_file == nullptr)
204  {
205  OGS_FATAL(
206  "Error reading base prj file (base_file attribute) in given "
207  "patch file {:s}.",
208  prj_file);
209  }
210  patch_files = {prj_file};
211  std::stringstream ss;
212  ss << base_file;
213  prj_file = BaseLib::joinPaths(BaseLib::extractPath(prj_file), ss.str());
214  }
215 
216  // read base prj file into stream
217  if (std::ifstream file(prj_file); file)
218  {
219  prj_stream << file.rdbuf();
220  }
221  else
222  {
223  if (!BaseLib::IsFileExisting(prj_file))
224  {
225  ERR("File {:s} does not exist.", prj_file);
226  }
227  DBUG("Stream state flags: {:b}.", file.rdstate());
228  OGS_FATAL("Could not open project file '{:s}' for reading.", prj_file);
229  }
230 
231  // apply xml patches to stream
232  if (!patch_files.empty())
233  {
234  for (const auto& patch_file : patch_files)
235  {
236  patchStream(patch_file, prj_stream);
237  }
238  xmlCleanupParser();
239  }
240 }
241 
242 ConfigTreeTopLevel makeConfigTree(const std::string& filepath,
243  const bool be_ruthless,
244  const std::string& toplevel_tag,
245  const std::vector<std::string>& patch_files)
246 {
247  std::string prj_file = filepath;
248  std::stringstream prj_stream;
249  readAndPatchPrj(prj_stream, prj_file, patch_files);
250 
251  ConfigTree::PTree ptree;
252 
253  // note: Trimming whitespace and ignoring comments is crucial in order
254  // for our configuration tree implementation to work!
255  try
256  {
257  read_xml(prj_stream, ptree,
258  boost::property_tree::xml_parser::no_comments |
259  boost::property_tree::xml_parser::trim_whitespace);
260 
261  if (toplevel_tag == "OpenGeoSysProject")
262  {
263  traverse(ptree, fs::path(prj_file).parent_path(), replaceIncludes);
264  }
265  }
266  catch (boost::property_tree::xml_parser_error const& e)
267  {
268  OGS_FATAL("Error while parsing XML file `{:s}' at line {:d}: {:s}.",
269  e.filename(), e.line(), e.message());
270  }
271 
272  DBUG("Project configuration from file '{:s}' read.", filepath);
273 
274  if (auto child = ptree.get_child_optional(toplevel_tag))
275  {
276  return ConfigTreeTopLevel(filepath, be_ruthless, std::move(*child));
277  }
278  OGS_FATAL("Tag <{:s}> has not been found in file `{:s}'.", toplevel_tag,
279  filepath);
280 }
281 
282 } // namespace BaseLib
#define OGS_FATAL(...)
Definition: Error.h:26
Filename manipulation routines.
void INFO(char const *fmt, Args const &... args)
Definition: Logging.h:32
void ERR(char const *fmt, Args const &... args)
Definition: Logging.h:42
void DBUG(char const *fmt, Args const &... args)
Definition: Logging.h:27
ConfigTree ctree_
ConfigTree depending on ptree_.
ConfigTree const & operator*() const
ConfigTree const * operator->() const
ConfigTreeTopLevel(std::string const &filepath, bool const be_ruthless, ConfigTree::PTree &&ptree)
boost::property_tree::ptree PTree
The tree being wrapped by this class.
Definition: ConfigTree.h:241
void patchStream(std::string patch_file, std::stringstream &prj_stream)
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)
void traverse(boost::property_tree::ptree &parent, const fs::path bench_dir, T &method)
void checkAndInvalidate(ConfigTree &conf)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: ConfigTree.cpp:470
std::string getFileExtension(const std::string &path)
Definition: FileTools.cpp:186
std::string extractPath(std::string const &pathname)
Definition: FileTools.cpp:207
void readAndPatchPrj(std::stringstream &prj_stream, std::string &prj_file, std::vector< std::string > patch_files)
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)
bool IsFileExisting(const std::string &strFilename)
Returns true if given file exists.
Definition: FileTools.cpp:43
std::string joinPaths(std::string const &pathA, std::string const &pathB)
Definition: FileTools.cpp:212
ConfigTreeTopLevel makeConfigTree(const std::string &filepath, const bool be_ruthless, const std::string &toplevel_tag, const std::vector< std::string > &patch_files)