Branch data Line data Source code
1 : : #pragma once
2 : :
3 : : /*
4 : : MIT License
5 : :
6 : : Copyright (c) 2014-2024 Stephane Cuillerdier (aka aiekick)
7 : :
8 : : Permission is hereby granted, free of charge, to any person obtaining a copy
9 : : of this software and associated documentation files (the "Software"), to deal
10 : : in the Software without restriction, including without limitation the rights
11 : : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : : copies of the Software, and to permit persons to whom the Software is
13 : : furnished to do so, subject to the following conditions:
14 : :
15 : : The above copyright notice and this permission notice shall be included in all
16 : : copies or substantial portions of the Software.
17 : :
18 : : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 : : SOFTWARE.
25 : : */
26 : :
27 : : // ezXml is part of the ezLibs project : https://github.com/aiekick/ezLibs.git
28 : : // ezXml is part of the ezLibs project : https://github.com/aiekick/ezLibs.git
29 : :
30 : : #include <map>
31 : : #include <stack>
32 : : #include <string>
33 : : #include <vector>
34 : : #include <cstdint>
35 : : #include <cassert>
36 : : #include <sstream>
37 : : #include <ostream>
38 : : #include <fstream>
39 : :
40 : : namespace ez {
41 : : class Xml;
42 : :
43 : : namespace xml {
44 : :
45 : : class Node;
46 : :
47 : : typedef std::vector<Node> Nodes;
48 : :
49 : : class Node {
50 : : friend class ez::Xml;
51 : :
52 : : public:
53 : : enum class Type {
54 : : None = 0,
55 : : Token,
56 : : Comment
57 : : };
58 : :
59 : : class Attribute {
60 : : private:
61 : : std::string m_value;
62 : : public:
63 : 24 : Attribute() = default;
64 : :
65 : 24 : explicit Attribute(const std::string &vValue) : m_value(vValue) {}
66 : :
67 : : template<typename T>
68 : 0 : Attribute &operator<<(const T &vValue) {
69 : 0 : std::ostringstream vOut;
70 : 0 : vOut << vValue;
71 : 0 : m_value = vOut.str();
72 : 0 : return *this;
73 : 0 : }
74 : :
75 : 24 : friend std::ostream& operator<<(std::ostream& os, const Attribute& attr) {
76 : 24 : os << attr.m_value;
77 : 24 : return os;
78 : 24 : }
79 : :
80 : 24 : const std::string& getValue() const {
81 : 24 : return m_value;
82 : 24 : }
83 : : };
84 : :
85 : : private:
86 : : std::string m_name;
87 : : std::map<std::string, Attribute> m_attributes;
88 : : std::string m_content;
89 : : std::string m_parentNodeName;
90 : : Nodes m_children;
91 : : Type m_type = Type::None;
92 : :
93 : : public:
94 : 52 : static std::string escapeXml(const std::string &vDatas) {
95 : 52 : std::string escaped = vDatas;
96 : 52 : replaceAll(escaped, "&", "&");
97 : 52 : replaceAll(escaped, "<", "<");
98 : 52 : replaceAll(escaped, "\"", """);
99 : 52 : replaceAll(escaped, "'", "'");
100 : 52 : replaceAll(escaped, ">", ">");
101 : 52 : return escaped;
102 : 52 : }
103 : :
104 : : // replace xml excaped pattern by corresponding good pattern
105 : 46 : static std::string unEscapeXml(const std::string &vDatas) {
106 : 46 : std::string unescaped = vDatas;
107 : 46 : replaceAll(unescaped, "<", "<");
108 : 46 : replaceAll(unescaped, "&", "&");
109 : 46 : replaceAll(unescaped, """, "\"");
110 : 46 : replaceAll(unescaped, "'", "'");
111 : 46 : replaceAll(unescaped, ">", ">");
112 : 46 : return unescaped;
113 : 46 : }
114 : :
115 : 490 : static void replaceAll(std::string &vStr, const std::string &vFrom, const std::string &vTo) {
116 [ - + ]: 490 : if (vFrom.empty()) return;
117 : 490 : size_t startPos = 0;
118 [ + + ]: 522 : while ((startPos = vStr.find(vFrom, startPos)) != std::string::npos) {
119 : 32 : vStr.replace(startPos, vFrom.length(), vTo);
120 : 32 : startPos += vTo.length();
121 : 32 : }
122 : 490 : }
123 : :
124 : : public:
125 : 42 : Node(const std::string &vName = "") : m_name(vName) {
126 : 42 : }
127 : :
128 : 0 : Node &setName(const std::string &vName) {
129 : 0 : m_name = vName;
130 : 0 : return *this;
131 : 0 : }
132 : :
133 : 34 : Node &addChild(const Node &vChild) {
134 : 34 : m_children.push_back(vChild);
135 : 34 : m_children.back().m_setParentNodeName(getName());
136 : 34 : return m_children.back();
137 : 34 : }
138 : :
139 : 0 : Node &addChild(const std::string &vName) {
140 : 0 : Node node(vName);
141 : 0 : return addChild(node);
142 : 0 : }
143 : :
144 : 0 : Node &addComment(const std::string &vComment) {
145 : 0 : Node node;
146 : 0 : node.setContent(vComment).m_setType(Type::Comment);
147 : 0 : return addChild(node);
148 : 0 : }
149 : :
150 : 0 : Node *getChild(const std::string &vName) {
151 : 0 : for (auto &child: m_children) {
152 : 0 : if (child.m_name == vName) {
153 : 0 : return &child;
154 : 0 : }
155 : 0 : }
156 : 0 : return nullptr;
157 : 0 : }
158 : :
159 : 0 : Node &getOrAddChild(const std::string &vName) {
160 : 0 : Node *ret = getChild(vName);
161 : 0 : if (ret == nullptr) {
162 : 0 : ret = &addChild(vName);
163 : 0 : }
164 : 0 : return *ret;
165 : 0 : }
166 : :
167 : 0 : Node &addChilds(const Nodes &vChilds) {
168 : 0 : for (const auto &node: vChilds) {
169 : 0 : addChild(node);
170 : 0 : }
171 : 0 : return *this;
172 : 0 : }
173 : : template<typename T>
174 : 0 : Node& addAttribute(const std::string& vKey, const T& vValue) {
175 : 0 : std::stringstream ss;
176 : 0 : ss << vValue;
177 : 0 : m_attributes[vKey] = Attribute(ss.str());
178 : 0 : return *this;
179 : 0 : }
180 : :
181 : 0 : Attribute& addAttribute(const std::string& vKey) {
182 : 0 : m_attributes[vKey] = Attribute();
183 : 0 : return m_attributes[vKey];
184 : 0 : }
185 : :
186 : 1 : bool isAttributeExist(const std::string &vKey) const {
187 : 1 : return (m_attributes.find(vKey) != m_attributes.end());
188 : 1 : }
189 : :
190 : : template<typename T = std::string>
191 : 23 : T getAttribute(const std::string &vKey) const {
192 : 23 : T ret;
193 : 23 : std::stringstream ss;
194 : 23 : auto it = m_attributes.find(vKey);
195 [ + - ][ + - ]: 23 : if (it != m_attributes.end()) {
196 : 23 : ss << it->second;
197 : 23 : }
198 : 23 : ss >> ret;
199 : 23 : return ret;
200 : 23 : }
201 : :
202 : : template<typename T>
203 : 0 : Node &setContent(const T &vContent) {
204 : 0 : std::stringstream ss;
205 : 0 : ss << vContent;
206 : 0 : m_content = ss.str();
207 : 0 : return *this;
208 : 0 : }
209 : :
210 : 14 : Node &setContent(const std::string &vContent) {
211 : 14 : m_content = escapeXml(vContent);
212 : 14 : return *this;
213 : 14 : }
214 : :
215 : : // specific case for std::string
216 : : template <typename T = std::string>
217 : 46 : typename std::enable_if<std::is_same<T, std::string>::value, T>::type getContent() const {
218 : 46 : return unEscapeXml(m_content);
219 : 46 : }
220 : :
221 : : // specific case for bool
222 : : template <typename T>
223 : 2 : typename std::enable_if<std::is_same<T, bool>::value, T>::type getContent() const {
224 : 2 : return (m_content == "true");
225 : 2 : }
226 : :
227 : : // general cases (exclude std::string and bool)
228 : : template <typename T>
229 : : typename std::enable_if<!std::is_same<T, std::string>::value && !std::is_same<T, bool>::value, T>::type getContent() const {
230 : : T ret;
231 : : std::stringstream ss;
232 : : ss << m_content;
233 : : ss >> ret;
234 : : return ret;
235 : : }
236 : :
237 : 22 : Nodes &getChildren() { return m_children; }
238 : :
239 : 46 : const Nodes &getChildren() const { return m_children; }
240 : :
241 : 1 : const std::string &getParentNodeName() const {
242 : 1 : return m_parentNodeName;
243 : 1 : }
244 : :
245 : 108 : const std::string &getName() const {
246 : 108 : return m_name;
247 : 108 : }
248 : :
249 : 40 : std::string dump(const xml::Node &vNode, const uint32_t vLevel = 0) const {
250 : 40 : std::string indent(vLevel * 2, ' '); // Indentation based on the depth level
251 : 40 : std::ostringstream oss;
252 : :
253 : 40 : oss << indent;
254 [ + + ]: 40 : if (vNode.m_getType() != xml::Node::Type::Comment) {
255 : 36 : oss << "<" << vNode.getName();
256 [ + + ]: 36 : for (const auto &attr: vNode.m_attributes) {
257 : 24 : oss << " " << attr.first << "=\"" << xml::Node::escapeXml(attr.second.getValue()) << "\"";
258 : 24 : }
259 : 36 : } else {
260 : 4 : oss << "<!-- ";
261 : 4 : }
262 : :
263 : 40 : const auto &content = vNode.getContent();
264 : 40 : const auto &children = vNode.getChildren();
265 : :
266 [ + + ][ + + ]: 40 : if (content.empty() && children.empty()) {
267 : 16 : oss << "/>" << std::endl;
268 : 24 : } else {
269 [ + + ]: 24 : if (vNode.m_getType() != xml::Node::Type::Comment) {
270 : 20 : oss << ">";
271 : 20 : }
272 [ + + ]: 24 : if (!content.empty()) {
273 : 14 : oss << xml::Node::escapeXml(content);
274 : 14 : }
275 [ + + ]: 24 : if (vNode.m_getType() == xml::Node::Type::Comment) {
276 : 4 : oss << " -->" << std::endl;
277 : 4 : }
278 [ + + ]: 24 : if (!children.empty()) {
279 : 14 : oss << std::endl;
280 [ + + ]: 34 : for (const auto &child: children) {
281 : 34 : oss << dump(child, vLevel + 1);
282 : 34 : }
283 : 14 : oss << indent;
284 : 14 : }
285 [ + + ]: 24 : if (vNode.m_getType() != xml::Node::Type::Comment) {
286 : 20 : oss << "</" << vNode.getName() << ">" << std::endl;
287 : 20 : }
288 : 24 : }
289 : :
290 : 40 : return oss.str();
291 : 40 : }
292 : :
293 : 6 : std::string dump() const {
294 : 6 : return dump(*this);
295 : 6 : }
296 : :
297 : : private:
298 : :
299 : 24 : void m_setAttribute(const std::string &vKey, const std::string &vValue) {
300 : 24 : m_attributes[vKey] = Attribute(vValue);
301 : 24 : }
302 : :
303 : 40 : void m_setType(Type vType) {
304 : 40 : m_type = vType;
305 : 40 : }
306 : :
307 : 112 : Type m_getType() const {
308 : 112 : return m_type;
309 : 112 : }
310 : :
311 : 34 : void m_setParentNodeName(const std::string &vName) {
312 : 34 : m_parentNodeName = vName;
313 : 34 : }
314 : : };
315 : :
316 : : template <>
317 : 0 : inline Node &Node::setContent(const bool &vContent) {
318 : 0 : m_content = vContent ? "true" : "false";
319 : 0 : return *this;
320 : 0 : }
321 : :
322 : : #if __cplusplus >= 201703L // c++17
323 : : template <>
324 : : inline bool Node::getContent<bool>() const {
325 : : return (m_content == "true");
326 : : }
327 : : #endif
328 : :
329 : : } // namespace xml
330 : :
331 : : class Xml {
332 : : private:
333 : : xml::Node m_Root;
334 : : // just during parsing,
335 : : // for know what is the current node
336 : : std::stack<xml::Node *> m_NodeStack;
337 : : enum class TokenType { //
338 : : OPENED = 0,
339 : : CLOSED,
340 : : OPENED_CLOSED,
341 : : COMMENT,
342 : : CONTENT,
343 : : Count
344 : : };
345 : :
346 : : public:
347 : 6 : Xml(const std::string &vRootName = "root") : m_Root(vRootName) {
348 : 6 : m_NodeStack.push(&m_Root);
349 : 6 : }
350 : :
351 : 4 : xml::Node &getRoot() {
352 : 4 : return m_Root;
353 : 4 : }
354 : :
355 : 0 : bool parseFile(const std::string &vFilePathName) {
356 : 0 : std::ifstream docFile(vFilePathName, std::ios::in);
357 : 0 : if (docFile.is_open()) {
358 : 0 : std::stringstream strStream;
359 : 0 : strStream << docFile.rdbuf(); // read the file
360 : 0 : auto xml_content = strStream.str();
361 : 0 : m_replaceString(xml_content, "\r\n", "\n");
362 : 0 : m_replaceString(xml_content, "\r", "\n");
363 : 0 : docFile.close();
364 : 0 : return parseString(xml_content);
365 : 0 : }
366 : 0 : return false;
367 : 0 : }
368 : :
369 : 6 : bool parseString(const std::string &vDoc) {
370 : 6 : auto tokens = m_tokenize(vDoc);
371 [ - + ]: 6 : if (tokens.empty()) {
372 : 0 : return false;
373 : 0 : }
374 [ + + ]: 100 : for (const auto &token: tokens) {
375 : 100 : std::string tagName;
376 : 100 : std::map<std::string, std::string> attributes;
377 [ - + ]: 100 : if (token.first.empty()) {
378 : 0 : continue;
379 : 0 : }
380 [ + + ]: 100 : if (token.second == TokenType::CLOSED) {
381 : 12 : m_NodeStack.pop();
382 [ + + ]: 88 : } else if (token.second == TokenType::OPENED || //
383 [ + + ]: 88 : token.second == TokenType::OPENED_CLOSED || //
384 [ + + ]: 88 : token.second == TokenType::COMMENT) {
385 [ + + ]: 36 : if (token.second != TokenType::COMMENT) {
386 : 32 : tagName = m_extractTagName(token.first);
387 : 32 : }
388 : 36 : xml::Node newNode(tagName);
389 : 36 : newNode.m_setType(xml::Node::Type::Token);
390 [ + + ]: 36 : if (token.second == TokenType::COMMENT) {
391 : 4 : newNode.m_setType(xml::Node::Type::Comment);
392 : 4 : newNode.setContent(token.first);
393 : 32 : } else {
394 [ + + ]: 32 : if (!m_extractAttributes(tagName, token.first, attributes)) {
395 : 2 : return false;
396 : 2 : }
397 [ + + ]: 30 : for (const auto &kv: attributes) {
398 : 24 : newNode.m_setAttribute(kv.first, kv.second);
399 : 24 : }
400 : 30 : }
401 : 34 : m_NodeStack.top()->addChild(newNode);
402 [ + + ]: 34 : if (token.second == TokenType::OPENED) {
403 : 18 : m_NodeStack.push(const_cast<xml::Node *>(&m_NodeStack.top()->getChildren().back()));
404 : 18 : }
405 [ + - ]: 52 : } else if (token.second == TokenType::CONTENT) {
406 [ + - ]: 52 : if (!m_NodeStack.empty()) {
407 [ + + ]: 52 : if (token.first[0] != '\n') {
408 : 10 : m_NodeStack.top()->setContent(token.first);
409 : 10 : }
410 : 52 : }
411 : 52 : }
412 : 100 : }
413 : 4 : return true;
414 : 6 : }
415 : :
416 : 6 : std::string dump() const {
417 : 6 : return m_Root.dump();
418 : 6 : }
419 : :
420 : : private:
421 : 0 : bool m_replaceString(std::string &str, const std::string &oldStr, const std::string &newStr) {
422 : 0 : bool found = false;
423 : 0 : size_t pos = 0;
424 : 0 : while ((pos = str.find(oldStr, pos)) != std::string::npos) {
425 : 0 : found = true;
426 : 0 : str.replace(pos, oldStr.length(), newStr);
427 : 0 : pos += newStr.length();
428 : 0 : }
429 : 0 : return found;
430 : 0 : }
431 : :
432 : 6 : std::vector<std::pair<std::string, TokenType>> m_tokenize(const std::string &vDoc) {
433 : 6 : std::vector<std::pair<std::string, TokenType>> tokens;
434 : 6 : size_t pos = 0;
435 : 6 : size_t length = vDoc.length();
436 : 6 : TokenType type = TokenType::Count;
437 [ + + ]: 112 : while (pos < length) {
438 [ + + ]: 106 : if (vDoc[pos] == '<') {
439 : 50 : type = TokenType::OPENED;
440 [ + + ]: 50 : if (vDoc[pos + 1] == '/') {
441 : 14 : type = TokenType::CLOSED;
442 [ + + ]: 36 : } else if (vDoc[pos + 1] == '!') {
443 : 4 : type = TokenType::COMMENT;
444 : 4 : }
445 : 50 : size_t end = vDoc.find(">", pos);
446 [ - + ]: 50 : if (end == std::string::npos) {
447 : 0 : break;
448 : 0 : }
449 [ + + ]: 50 : if (vDoc[end - 1] == '/') {
450 : 14 : type = TokenType::OPENED_CLOSED;
451 : 14 : }
452 : 50 : const auto ss = vDoc.substr(pos, end + 1 - pos);
453 : 50 : tokens.push_back(std::make_pair(ss, type));
454 : 50 : pos = end + 1;
455 : 56 : } else {
456 : 56 : size_t end = vDoc.find('<', pos);
457 : 56 : const auto ss = vDoc.substr(pos, end - pos);
458 : 56 : tokens.push_back(std::make_pair(ss, TokenType::CONTENT));
459 : 56 : pos = end;
460 : 56 : }
461 : 106 : }
462 : 6 : return tokens;
463 : 6 : }
464 : :
465 : 32 : std::string m_extractTagName(const std::string &vLine) {
466 : 32 : std::string ret;
467 : 32 : size_t startPos = vLine.find('<') + 1;
468 [ + + ][ + - ]: 34 : while (vLine.at(startPos) == ' ' && vLine.size() > startPos) {
469 : 2 : ++startPos;
470 : 2 : }
471 : 32 : size_t endPos = vLine.find_first_of(" \t/>", startPos);
472 : 32 : return vLine.substr(startPos, endPos - startPos);
473 : 32 : }
474 : :
475 : : // will remove space outside of <> and ""
476 : 0 : std::string m_trim1(const std::string &vToken) {
477 : 0 : std::string res;
478 : 0 : int32_t scope = 0;
479 : 0 : for (const auto c: vToken) {
480 : 0 : if (scope == 1) {
481 : 0 : res += c;
482 : 0 : } else if (c == '"') {
483 : 0 : if (scope == 0) {
484 : 0 : ++scope;
485 : 0 : } else {
486 : 0 : --scope;
487 : 0 : }
488 : 0 : }
489 : 0 : }
490 : 0 : return res;
491 : 0 : }
492 : :
493 : : // will remove spaces from start and from end
494 : : // if the token is like tk0 tk1 tk2, only tk0 will be returned
495 : 28 : std::string m_trim2(const std::string &vToken) {
496 : 28 : std::string res;
497 [ + + ]: 144 : for (const auto c: vToken) {
498 [ + + ]: 144 : if (c != ' ') {
499 : 140 : res += c;
500 [ + - ]: 140 : } else if (!res.empty()) {
501 : 4 : break;
502 : 4 : }
503 : 144 : }
504 : 28 : return res;
505 : 28 : }
506 : :
507 : : bool m_extractAttributes(const std::string &vTagName, const std::string &vLine,
508 : 32 : std::map<std::string, std::string> &attributes) {
509 : 32 : size_t startPos = vLine.find(vTagName);
510 [ + - ]: 32 : if (startPos != std::string::npos) {
511 : 32 : startPos += vTagName.size();
512 : 32 : startPos = vLine.find(' ', startPos);
513 [ + + ]: 58 : while (startPos != std::string::npos) {
514 : 30 : startPos = vLine.find_first_not_of(" \t", startPos);
515 [ + + ]: 30 : if (vLine.at(startPos) == '>') {
516 : 2 : break;
517 : 2 : }
518 : 28 : size_t equalsPos = vLine.find('=', startPos);
519 [ - + ]: 28 : if (equalsPos == std::string::npos) {
520 : 0 : return false;
521 : 0 : }
522 : 28 : std::string key = m_trim2(vLine.substr(startPos, equalsPos - startPos));
523 : 28 : startPos = equalsPos + 1;
524 : 28 : char quoteChar = vLine[startPos];
525 [ + + ]: 32 : while (quoteChar == ' ') {
526 : 4 : quoteChar = vLine[++startPos];
527 : 4 : }
528 [ + + ][ - + ]: 28 : if (quoteChar == '"' || quoteChar == '\'') {
529 : 26 : startPos++;
530 : 26 : size_t endPos = vLine.find(quoteChar, startPos);
531 [ + - ]: 26 : if (endPos != std::string::npos) {
532 : 26 : std::string value = vLine.substr(startPos, endPos - startPos);
533 : 26 : attributes[key] = value;
534 : 26 : startPos = vLine.find(' ', endPos); // Passer ? l'attribut suivant
535 : 26 : } else {
536 : : #ifdef LogVarError
537 : : LogVarError("The attribut '%s' have invalid value", key.c_str());
538 : : #endif
539 : 0 : return false; // Erreur : guillemets/apostrophes non ferm?s
540 : 0 : }
541 : 26 : } else {
542 : : #ifdef LogVarError
543 : : LogVarError("The attribut '%s' have invalid value", key.c_str());
544 : : #endif
545 : 2 : return false; // Erreur : attribut sans guillemets ou apostrophes
546 : 2 : }
547 : 28 : }
548 : 30 : return true;
549 : 32 : }
550 : 0 : return false;
551 : 32 : }
552 : : };
553 : :
554 : : } // namespace ez
|