preCICE
Loading...
Searching...
No Matches
ConfigParser.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <cstddef>
3#include <exception>
4#include <filesystem>
5#include <fstream>
6#include <iterator>
7#include <libxml/SAX2.h>
8#include <memory>
9#include <sstream>
10#include <string>
11#include <unordered_set>
12#include <utility>
13
14#include "logging/LogMacros.hpp"
15#include "logging/Logger.hpp"
16#include "profiling/Event.hpp"
17#include "utils/Hash.hpp"
18#include "utils/String.hpp"
19#include "xml/ConfigParser.hpp"
20#include "xml/XMLTag.hpp"
21
22namespace precice::xml {
23
24std::string decodeXML(std::string_view xml)
25{
26 static const std::map<std::string_view, char> escapes{{"&lt;", '<'}, {"&gt;", '>'}, {"&amp;", '&'}, {"&quot;", '"'}, {"&apos;", '\''}};
27 std::string decodedXml(xml);
28 while (true) {
29 bool changes{false};
30 for (const auto &kv : escapes) {
31 auto position = decodedXml.find(kv.first);
32 if (position != std::string::npos) {
33 decodedXml.replace(position, kv.first.length(), 1, kv.second);
34 changes = true;
35 }
36 }
37 if (!changes) {
38 break;
39 }
40 };
41 return decodedXml;
42}
43
44// ------------------------- Callback functions for libxml2 -------------------------
45
47 void *ctx,
48 const xmlChar *localname,
49 const xmlChar *prefix,
50 const xmlChar *URI,
51 int nb_namespaces,
52 const xmlChar **namespaces,
53 int nb_attributes,
54 int nb_defaulted,
55 const xmlChar **attributes)
56{
58 unsigned int index = 0;
59 for (int indexAttribute = 0; indexAttribute < nb_attributes; ++indexAttribute, index += 5) {
60 std::string attributeName(reinterpret_cast<const char *>(attributes[index]));
61
62 auto valueBegin = reinterpret_cast<const char *>(attributes[index + 3]);
63 auto valueEnd = reinterpret_cast<const char *>(attributes[index + 4]);
64 std::string_view value(valueBegin,
65 valueEnd - valueBegin);
66
67 attributesMap[attributeName] = decodeXML(value);
68 }
69
70 auto pParser = static_cast<ConfigParser *>(ctx);
71
72 std::string_view sPrefix(prefix == nullptr ? "" : reinterpret_cast<const char *>(prefix));
73
74 pParser->OnStartElement(reinterpret_cast<const char *>(localname), sPrefix, attributesMap);
75}
76
78 void *ctx,
79 const xmlChar *localname,
80 const xmlChar *prefix,
81 const xmlChar *URI)
82{
83 ConfigParser *pParser = static_cast<ConfigParser *>(ctx);
84 pParser->OnEndElement();
85}
86
87void OnCharacters(void *ctx, const xmlChar *ch, int len)
88{
89 ConfigParser *pParser = static_cast<ConfigParser *>(ctx);
90 pParser->OnTextSection(std::string(reinterpret_cast<const char *>(ch), len));
91}
92
93void OnStructuredErrorFunc(void *userData, const xmlError *error)
94{
95 const std::string_view message{error->message};
96
97 // Ignore all namespace-related messages
98 if (message.find("Namespace") != std::string::npos) {
99 return;
100 }
101
102 ConfigParser::MessageProxy(error->level, message);
103}
104
105// Required for versions before 2.12.0 of libxml
106void OnStructuredErrorFunc(void *userData, xmlError *error)
107{
108 OnStructuredErrorFunc(userData, static_cast<const xmlError *>(error));
109}
110
111void OnErrorFunc(void *userData, const char *error, ...)
112{
113 ConfigParser::MessageProxy(XML_ERR_ERROR, error);
114}
115
116void OnFatalErrorFunc(void *userData, const char *error, ...)
117{
118 ConfigParser::MessageProxy(XML_ERR_FATAL, error);
119}
120
121// ------------------------- ConfigParser implementation -------------------------
122
124
125ConfigParser::ConfigParser(std::string_view filePath, const ConfigurationContext &context, std::shared_ptr<precice::xml::XMLTag> pXmlTag)
126 : m_pXmlTag(std::move(pXmlTag))
127{
128 readXmlFile(std::string(filePath));
129
130 std::vector<std::shared_ptr<XMLTag>> DefTags{m_pXmlTag};
131 CTagPtrVec SubTags;
132 // Initialize with the root tag, if any.
133 if (not m_AllTags.empty())
134 SubTags.push_back(m_AllTags[0]);
135
136 try {
137 connectTags(context, DefTags, SubTags);
138 } catch (::precice::Error &) {
139 throw;
140 } catch (const std::exception &e) {
141 PRECICE_ERROR("An unexpected exception occurred during configuration: {}.", e.what());
142 }
143}
144
145ConfigParser::ConfigParser(std::string_view filePath)
146{
147 readXmlFile(std::string(filePath));
148}
149
150void ConfigParser::MessageProxy(int level, std::string_view mess)
151{
152 switch (level) {
153 case (XML_ERR_FATAL):
154 case (XML_ERR_ERROR):
155 PRECICE_ERROR(mess);
156 break;
157 case (XML_ERR_WARNING):
158 PRECICE_WARN(mess);
159 break;
160 default:
161 PRECICE_INFO(mess);
162 }
163}
164
165std::string ConfigParser::hash() const
166{
167 return _hash;
168}
169
170std::string ConfigParser::readFileContent(std::string const &filePath) const
171{
172 // We get the filesystem, preallocate a string and then read the entire file in one go.
173 profiling::Event e("readConfiguration", profiling::Fundamental);
174
175 std::ifstream ifs{filePath};
176 PRECICE_CHECK(ifs, "XML parser was unable to open configuration file \"{}\"", filePath);
177
178#ifdef _WIN32
179 // On windows, the returned file_size is an upper bound. So we have to read buffered here.
180 std::string content{std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>()};
181#else
182 std::error_code ec;
183 auto size = std::filesystem::file_size(filePath, ec);
184 PRECICE_CHECK(!ec, "XML parser was unable to get the size of the configuration file \"{}\": {}", filePath, ec.message());
185
186 std::string content(size, '\0');
187 ifs.read(content.data(), size);
188#endif
189
190 PRECICE_CHECK(!content.empty(), "The configuration file \"{}\" is empty.", filePath);
191
192 return content;
193}
194
195int ConfigParser::readXmlFile(std::string const &filePath)
196{
197 xmlSAXHandler SAXHandler;
198
199 memset(&SAXHandler, 0, sizeof(xmlSAXHandler));
200
201 SAXHandler.initialized = XML_SAX2_MAGIC;
202 SAXHandler.startElementNs = OnStartElementNs;
203 SAXHandler.endElementNs = OnEndElementNs;
204 SAXHandler.characters = OnCharacters;
205 SAXHandler.serror = OnStructuredErrorFunc;
206 SAXHandler.error = OnErrorFunc;
207 SAXHandler.fatalError = OnFatalErrorFunc;
208
209 auto content = readFileContent(filePath);
210
211 _hash = utils::preciceHash(content);
212
213 auto ctxt = std::unique_ptr<xmlParserCtxt, void (*)(xmlParserCtxtPtr)>(
214 xmlCreatePushParserCtxt(&SAXHandler, static_cast<void *>(this),
215 content.c_str(), content.size(), nullptr),
216 xmlFreeParserCtxt);
217
218 PRECICE_CHECK(ctxt != nullptr, "XML parser was unable to create a push parser context for file \"{}\"", filePath);
219
220 xmlParseChunk(ctxt.get(), nullptr, 0, 1);
221
222 return 0;
223}
224
225namespace {
226struct Distance {
227 std::size_t distance;
228 std::string name;
229
230 bool operator<(const Distance &other) const
231 {
232 return distance < other.distance;
233 }
234};
235auto gatherCandidates(const std::vector<std::shared_ptr<XMLTag>> &DefTags, std::string_view prefix)
236{
237 bool validPrefix = std::any_of(DefTags.begin(), DefTags.end(), [prefix](const auto &tag) { return tag->getNamespace() == prefix; });
238
239 std::set<std::string> entries;
240 for (const auto &tag : DefTags) {
241 if (!validPrefix || (tag->getNamespace() == prefix)) {
242 entries.insert(tag->getFullName());
243 }
244 }
245 return entries;
246}
247} // namespace
248
249void ConfigParser::connectTags(const ConfigurationContext &context, std::vector<std::shared_ptr<XMLTag>> &DefTags, CTagPtrVec &SubTags)
250{
251 std::unordered_set<std::string> usedTags;
252
253 for (auto &subtag : SubTags) {
254 std::string expectedName = (subtag->m_Prefix.length() ? subtag->m_Prefix + ":" : "") + subtag->m_Name;
255 PRECICE_CHECK(expectedName != "solver-interface",
256 "This configuration contains the tag <solver-interface>, meaning it was created for a preCICE version prior to version 3. "
257 "Are you using the correct version of your simulation case? Has this simulation case been updated to this version of preCICE?");
258 const auto tagPosition = std::find_if(
259 DefTags.begin(),
260 DefTags.end(),
261 [expectedName](const std::shared_ptr<XMLTag> &pTag) {
262 return pTag->_fullName == expectedName;
263 });
264
265 if (tagPosition == DefTags.end()) {
266 // Tag not found
267 auto names = gatherCandidates(DefTags, subtag->m_Prefix);
268
269 auto matches = utils::computeMatches(expectedName, names);
270 if (!matches.empty() && matches.front().distance < 3) {
271 matches.erase(std::remove_if(matches.begin(), matches.end(), [](auto &m) { return m.distance > 2; }), matches.end());
272 std::vector<std::string> stringMatches;
273 std::transform(matches.begin(), matches.end(), std::back_inserter(stringMatches), [](auto &m) { return m.name; });
274 PRECICE_ERROR("The configuration contains an unknown tag <{}>. Did you mean <{}>?", expectedName, fmt::join(stringMatches, ">,<"));
275 } else {
276 PRECICE_ERROR("The configuration contains an unknown tag <{}>. Expected tags are {}.", expectedName, fmt::join(names, ", "));
277 }
278 }
279
280 auto pDefSubTag = *tagPosition;
281 pDefSubTag->resetAttributes();
282
283 if ((pDefSubTag->_occurrence == XMLTag::OCCUR_ONCE) || (pDefSubTag->_occurrence == XMLTag::OCCUR_NOT_OR_ONCE)) {
284 PRECICE_CHECK(usedTags.count(pDefSubTag->_fullName) == 0,
285 "Tag <{}> is not allowed to occur multiple times.", pDefSubTag->_fullName);
286 usedTags.emplace(pDefSubTag->_fullName);
287 }
288
289 pDefSubTag->_configuredNamespaces[pDefSubTag->_namespace] = true;
290 pDefSubTag->readAttributes(subtag->m_aAttributes);
291 pDefSubTag->_listener.xmlTagCallback(context, *pDefSubTag);
292 pDefSubTag->_configured = true;
293
294 connectTags(context, pDefSubTag->_subtags, subtag->m_aSubTags);
295
296 pDefSubTag->areAllSubtagsConfigured();
297 pDefSubTag->_listener.xmlEndTagCallback(context, *pDefSubTag);
298 }
299}
300
302 std::string_view localname,
303 std::string_view prefix,
304 CTag::AttributePair attributes)
305{
306 auto pTag = std::make_shared<CTag>();
307
308 pTag->m_Prefix = prefix;
309 pTag->m_Name = localname;
310 pTag->m_aAttributes = std::move(attributes);
311
312 if (not m_CurrentTags.empty()) {
313 auto pParentTag = m_CurrentTags.back();
314 pParentTag->m_aSubTags.push_back(pTag);
315 }
316
317 m_AllTags.push_back(pTag);
318 m_CurrentTags.push_back(pTag);
319}
320
322{
323 m_CurrentTags.pop_back();
324}
325
326void ConfigParser::OnTextSection(const std::string &)
327{
328 // This page intentionally left blank
329}
330} // namespace precice::xml
#define PRECICE_ERROR(...)
Definition LogMacros.hpp:16
#define PRECICE_WARN(...)
Definition LogMacros.hpp:12
#define PRECICE_INFO(...)
Definition LogMacros.hpp:14
#define PRECICE_CHECK(check,...)
Definition LogMacros.hpp:32
This class provides a lightweight logger.
Definition Logger.hpp:17
void OnTextSection(const std::string &ch)
Callback for text sections in xml file.
std::vector< std::shared_ptr< CTag > > CTagPtrVec
void OnEndElement()
Callback for End-Tag.
std::shared_ptr< precice::xml::XMLTag > m_pXmlTag
ConfigParser(std::string_view filePath, const ConfigurationContext &context, std::shared_ptr< XMLTag > pXmlTag)
Parser ctor for Callback init.
void OnStartElement(std::string_view localname, std::string_view prefix, CTag::AttributePair attributes)
Callback for Start-Tag.
void connectTags(const ConfigurationContext &context, std::vector< std::shared_ptr< precice::xml::XMLTag > > &DefTags, CTagPtrVec &SubTags)
Connects the actual tags of an xml layer with the predefined tags.
std::string readFileContent(std::string const &filePath) const
Reads and returns the content of the file.
std::string hash() const
returns the hash of the processed XML file
static void MessageProxy(int level, std::string_view mess)
Proxy for error and warning messages from libxml2.
int readXmlFile(std::string const &filePath)
Reads the xml file.
static precice::logging::Logger _log
std::string _hash
the hash of the last processed config
static constexpr Group Fundamental
Convenience instance of the Cat::Fundamental.
Definition Event.hpp:22
std::string preciceHash(std::string_view s)
creates a portable hash of the given input
Definition Hash.cpp:10
std::vector< StringMatch > computeMatches(std::string_view given, const Container &expected)
Definition String.hpp:95
contains the XML configuration parser.
void OnEndElementNs(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI)
void OnFatalErrorFunc(void *userData, const char *error,...)
void OnStartElementNs(void *ctx, const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes)
void OnStructuredErrorFunc(void *userData, const xmlError *error)
void OnCharacters(void *ctx, const xmlChar *ch, int len)
void OnErrorFunc(void *userData, const char *error,...)
std::string decodeXML(std::string_view xml)
Decodes escape sequences of a given xml.
STL namespace.
std::map< std::string, std::string > AttributePair
Tightly coupled to the parameters of Participant()
Definition XMLTag.hpp:21