Loading [MathJax]/extensions/tex2jax.js
OGS
PrjProcessing.cpp
Go to the documentation of this file.
1
11#include "PrjProcessing.h"
12
13#include <libxml/parser.h>
14#include <libxml/xmlstring.h>
15#include <xml_patch.h>
16
17#include <filesystem>
18#include <fstream>
19#include <regex>
20#include <sstream>
21
22#include "DisableFPE.h"
23#include "Error.h"
24#include "FileTools.h"
25#include "Logging.h"
26
27namespace
28{
29std::string iostateToString(std::ios_base::iostate const state)
30{
31 std::string result;
32
33 if (state == std::ios_base::goodbit)
34 {
35 result = "goodbit";
36 }
37 else
38 {
39 if (state & std::ios_base::eofbit)
40 {
41 result += "eofbit ";
42 }
43 if (state & std::ios_base::failbit)
44 {
45 result += "failbit ";
46 }
47 if (state & std::ios_base::badbit)
48 {
49 result += "badbit";
50 }
51 // Remove trailing space if there is one
52 if (!result.empty() && result.back() == ' ')
53 {
54 result.pop_back();
55 }
56 }
57 return result;
58}
59} // namespace
60
61namespace BaseLib
62{
63void traverseIncludes(xmlDoc* doc, xmlNode* node,
64 std::filesystem::path const& prj_dir)
65{
66 xmlNode* cur_node = nullptr;
67 for (cur_node = node; cur_node; cur_node = cur_node->next)
68 {
69 if (cur_node->type != XML_ELEMENT_NODE)
70 {
71 continue;
72 }
73 if (xmlStrEqual(cur_node->name, xmlCharStrdup("include")))
74 {
75 auto include_file_char_pointer =
76 xmlGetProp(cur_node, xmlCharStrdup("file"));
77 if (include_file_char_pointer == nullptr)
78 {
80 "Error while processing includes in prj file. Error in "
81 "element '{:s}' on line {:d}: no file attribute given!",
82 reinterpret_cast<const char*>(cur_node->name),
83 cur_node->line);
84 }
85 auto filename_length = xmlStrlen(include_file_char_pointer);
86 std::string filename(
87 reinterpret_cast<char*>(include_file_char_pointer),
88 filename_length);
89 if (auto const filepath = std::filesystem::path(filename);
90 filepath.is_relative())
91 {
92 filename = (prj_dir / filepath).string();
93 }
94
95 if (!std::filesystem::exists(filename))
96 {
98 "Error while processing includes in prj file. Error in "
99 "element '{:s}' on line {:d}: Include file is not "
100 "existing: "
101 "{:s}!",
102 reinterpret_cast<const char*>(cur_node->name),
103 cur_node->line,
104 reinterpret_cast<const char*>(include_file_char_pointer));
105 }
106 INFO("Including {:s} into project file.", filename);
107
108 const std::ifstream input_stream(filename, std::ios_base::binary);
109 if (input_stream.fail())
110 {
111 OGS_FATAL("Failed to open file {}!", filename);
112 }
113 std::stringstream buffer;
114 buffer << input_stream.rdbuf();
115 const std::string xml = buffer.str();
116
117 // Replace lines containing <?xml ... ?>
118 std::regex xmlDeclaration("<\\?xml.*?>");
119 const std::string xml_filtered =
120 std::regex_replace(xml, xmlDeclaration, "");
121
122 xmlNodePtr pNewNode = nullptr;
123 xmlParseInNodeContext(cur_node->parent, xml_filtered.c_str(),
124 (int)xml_filtered.length(), 0, &pNewNode);
125 if (pNewNode != nullptr)
126 {
127 // add new xml node to parent
128 xmlNode* pChild = pNewNode;
129 while (pChild != nullptr)
130 {
131 xmlAddChild(cur_node->parent, xmlCopyNode(pChild, 1));
132 pChild = pChild->next;
133 }
134 xmlFreeNode(pNewNode);
135 }
136
137 // Cleanup and continue on next node
138 auto next_node = cur_node->next;
139 xmlUnlinkNode(cur_node);
140 xmlFreeNode(cur_node);
141 cur_node = next_node;
142 }
143 traverseIncludes(doc, cur_node->children, prj_dir);
144 }
145 xmlFreeNode(cur_node);
146}
147
148void replaceIncludes(std::stringstream& prj_stream,
149 std::filesystem::path const& prj_dir)
150{
151 // Parsing the XML triggers floating point exceptions. Because we are not
152 // debugging libxml2 (or other libraries) at this point, the floating point
153 // exceptions are temporarily disabled and are restored at the end of the
154 // function.
155 [[maybe_unused]] DisableFPE disable_fpe;
156
157 auto doc = xmlReadMemory(prj_stream.str().c_str(), prj_stream.str().size(),
158 nullptr, nullptr, 0);
159 if (doc == nullptr)
160 {
161 OGS_FATAL("Error reading project file from memory.");
162 }
163
164 auto root_node = xmlDocGetRootElement(doc);
165 traverseIncludes(doc, root_node, prj_dir);
166
167 xmlChar* xmlbuff;
168 int buffersize;
169 xmlDocDumpMemory(doc, &xmlbuff, &buffersize);
170 prj_stream.str(""); // Empty stream
171 prj_stream << xmlbuff;
172
173 xmlFree(xmlbuff);
174 xmlFreeDoc(doc);
175}
176
177// Applies a patch file to the prj content in prj_stream.
178void patchStream(std::string const& patch_file, std::stringstream& prj_stream,
179 bool after_includes = false)
180{
181 // xmlReadFile(patch_file.c_str(), nullptr, 0); leads to malloc errors in
182 // xmlFreeDoc(doc) below
183 auto patch = xmlParseFile(patch_file.c_str());
184 if (patch == nullptr)
185 {
186 OGS_FATAL("Error reading XML diff file {:s}.", patch_file);
187 }
188
189 auto doc = xmlReadMemory(prj_stream.str().c_str(), prj_stream.str().size(),
190 nullptr, nullptr, 0);
191 if (doc == nullptr)
192 {
193 OGS_FATAL("Error reading project file from memory.");
194 }
195
196 auto node = xmlDocGetRootElement(patch);
197 int rc = 0;
198 for (node = node ? node->children : nullptr; node; node = node->next)
199 {
200 if (node->type != XML_ELEMENT_NODE)
201 {
202 continue;
203 }
204 bool node_after_includes = false;
205 xmlAttr* attribute = node->properties;
206 while (attribute)
207 {
208 // Check for after_includes-attribute
209 xmlChar* value =
210 xmlNodeListGetString(node->doc, attribute->children, 1);
211 if (xmlStrEqual(attribute->name, xmlCharStrdup("after_includes")) &&
212 xmlStrEqual(value, xmlCharStrdup("true")))
213 {
214 node_after_includes = true;
215 }
216
217 xmlFree(value);
218 attribute = attribute->next;
219 }
220
221 if (after_includes != node_after_includes)
222 {
223 continue;
224 }
225
226 if (xmlStrEqual(node->name, xmlCharStrdup("add")))
227 {
228 rc = xml_patch_add(doc, node);
229 }
230 else if (xmlStrEqual(node->name, xmlCharStrdup("replace")))
231 {
232 rc = xml_patch_replace(doc, node);
233 }
234 else if (xmlStrEqual(node->name, xmlCharStrdup("remove")))
235 {
236 rc = xml_patch_remove(doc, node);
237 }
238 else
239 {
240 OGS_FATAL(
241 "Error while patching prj file with patch file {:}. Only "
242 "'add', 'replace' and 'remove' elements are allowed! Got an "
243 "element '{:s}' on line {:d}.",
244 patch_file, reinterpret_cast<const char*>(node->name),
245 node->line);
246 }
247
248 if (rc)
249 {
250 OGS_FATAL(
251 "Error while patching prj file with patch file {:}. Error in "
252 "element '{:s}' on line {:d}.",
253 patch_file, reinterpret_cast<const char*>(node->name),
254 node->line);
255 }
256 }
257
258 xmlChar* xmlbuff;
259 int buffersize;
260 xmlDocDumpMemory(doc, &xmlbuff, &buffersize);
261 prj_stream.str(""); // Empty stream
262 prj_stream << xmlbuff;
263
264 xmlFree(xmlbuff);
265 xmlFreeDoc(doc);
266 xmlFreeDoc(patch);
267}
268
269// Will set prj_file to the actual .prj file and returns the final prj file
270// content in prj_stream.
271void readAndPatchPrj(std::stringstream& prj_stream, std::string& prj_file,
272 std::vector<std::string>& patch_files)
273{
274 // Extract base project file path if an xml (patch) file is given as prj
275 // file and it contains the base_file attribute.
276 if (BaseLib::getFileExtension(prj_file) == ".xml")
277 {
278 if (!patch_files.empty())
279 {
280 OGS_FATAL(
281 "It is not allowed to specify additional patch files "
282 "if a patch file was already specified as the "
283 "prj-file.");
284 }
285 auto patch = xmlReadFile(prj_file.c_str(), nullptr, 0);
286 auto node = xmlDocGetRootElement(patch);
287 xmlChar const base_file_string[] = "base_file";
288 auto base_file = xmlGetProp(node, base_file_string);
289 if (base_file == nullptr)
290 {
291 OGS_FATAL(
292 "Error reading base prj file (base_file attribute) in given "
293 "patch file {:s}.",
294 prj_file);
295 }
296 patch_files = {prj_file};
297 std::stringstream ss;
298 ss << base_file;
299 prj_file = BaseLib::joinPaths(BaseLib::extractPath(prj_file), ss.str());
300 }
301
302 // read base prj file into stream
303 if (std::ifstream file(prj_file); file)
304 {
305 prj_stream << file.rdbuf();
306 }
307 else
308 {
309 if (!BaseLib::IsFileExisting(prj_file))
310 {
311 ERR("File {:s} does not exist.", prj_file);
312 }
313 DBUG("Stream state flags: {:s}.", iostateToString(file.rdstate()));
314 OGS_FATAL("Could not open project file '{:s}' for reading.", prj_file);
315 }
316
317 // apply xml patches to stream
318 for (const auto& patch_file : patch_files)
319 {
320 patchStream(patch_file, prj_stream);
321 }
322}
323
324void prepareProjectFile(std::stringstream& prj_stream,
325 const std::string& filepath,
326 const std::vector<std::string>& patch_files,
327 bool write_prj, const std::string& out_directory)
328{
329 std::string prj_file = filepath;
330
331 std::vector<std::string> patch_files_copy = patch_files;
332 readAndPatchPrj(prj_stream, prj_file, patch_files_copy);
333 replaceIncludes(prj_stream,
334 std::filesystem::absolute(std::filesystem::path(prj_file))
335 .parent_path());
336 // re-apply xml patches to stream
337 for (const auto& patch_file : patch_files_copy)
338 {
339 patchStream(patch_file, prj_stream, true);
340 }
341
342 if (write_prj)
343 {
344 // The following two lines should set indenting to 4 spaces but it does
345 // not work. 2 spaces are default.
346 //
347 // xmlThrDefIndentTreeOutput(1);
348 // xmlThrDefTreeIndentString(" "); // 4 spaces indent
349 // XML_PARSE_NOBLANKS -> pretty-print
350 auto doc =
351 xmlReadMemory(prj_stream.str().c_str(), prj_stream.str().size(),
352 nullptr, nullptr, XML_PARSE_NOBLANKS);
353 auto prj_out = (std::filesystem::path(out_directory) /
354 std::filesystem::path(filepath).stem())
355 .string() +
356 "_processed.prj";
357 xmlSaveFormatFileEnc(prj_out.c_str(), doc, "utf-8", 1);
358 INFO("Processed project file written to {:s}.", prj_out);
359 xmlFreeDoc(doc);
360 }
361 xmlCleanupParser();
362}
363} // namespace BaseLib
#define OGS_FATAL(...)
Definition Error.h:26
Filename manipulation routines.
void INFO(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:35
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:30
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:45
void prepareProjectFile(std::stringstream &prj_stream, const std::string &filepath, const std::vector< std::string > &patch_files, bool write_prj, const std::string &out_directory)
Applies includes and patch files to project file.
void traverseIncludes(xmlDoc *doc, xmlNode *node, std::filesystem::path const &prj_dir)
void readAndPatchPrj(std::stringstream &prj_stream, std::string &prj_file, std::vector< std::string > &patch_files)
void patchStream(std::string const &patch_file, std::stringstream &prj_stream, bool after_includes=false)
std::string getFileExtension(const std::string &path)
std::string extractPath(std::string const &pathname)
bool IsFileExisting(const std::string &strFilename)
Returns true if given file exists.
Definition FileTools.cpp:48
std::string joinPaths(std::string const &pathA, std::string const &pathB)
void replaceIncludes(std::stringstream &prj_stream, std::filesystem::path const &prj_dir)
std::string iostateToString(std::ios_base::iostate const state)