aboutsummaryrefslogtreecommitdiff
path: root/include/cru/base
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-11-04 21:59:42 +0800
committerYuqian Yang <crupest@crupest.life>2025-11-04 21:59:42 +0800
commit1a6111e3f02b0a9cff0f81fb524b4dfb7d69854b (patch)
tree3a92e1bf61fc165c2148f38ca6602f5066011f06 /include/cru/base
parentf48505c96a70e2f1d1982fea30f3015e42fcd49d (diff)
downloadcru-1a6111e3f02b0a9cff0f81fb524b4dfb7d69854b.tar.gz
cru-1a6111e3f02b0a9cff0f81fb524b4dfb7d69854b.tar.bz2
cru-1a6111e3f02b0a9cff0f81fb524b4dfb7d69854b.zip
Move xml to base.
Diffstat (limited to 'include/cru/base')
-rw-r--r--include/cru/base/xml/XmlNode.h138
-rw-r--r--include/cru/base/xml/XmlParser.h43
2 files changed, 181 insertions, 0 deletions
diff --git a/include/cru/base/xml/XmlNode.h b/include/cru/base/xml/XmlNode.h
new file mode 100644
index 00000000..e9090b40
--- /dev/null
+++ b/include/cru/base/xml/XmlNode.h
@@ -0,0 +1,138 @@
+#pragma once
+
+#include "../Base.h"
+#include "../StringUtil.h"
+
+namespace cru::xml {
+class XmlElementNode;
+class XmlTextNode;
+class XmlCommentNode;
+
+class CRU_BASE_API XmlNode : public Object {
+ friend XmlElementNode;
+
+ public:
+ enum class Type { Text, Element, Comment };
+
+ protected:
+ explicit XmlNode(Type type) : type_(type) {}
+
+ public:
+ Type GetType() const { return type_; }
+ XmlElementNode* GetParent() const { return parent_; }
+
+ virtual XmlNode* Clone() const = 0;
+
+ bool IsTextNode() const { return type_ == Type::Text; }
+ bool IsElementNode() const { return type_ == Type::Element; }
+ bool IsCommentNode() const { return type_ == Type::Comment; }
+
+ XmlElementNode* AsElement();
+ XmlTextNode* AsText();
+ XmlCommentNode* AsComment();
+ const XmlElementNode* AsElement() const;
+ const XmlTextNode* AsText() const;
+ const XmlCommentNode* AsComment() const;
+
+ private:
+ const Type type_;
+ XmlElementNode* parent_ = nullptr;
+};
+
+class CRU_BASE_API XmlTextNode : public XmlNode {
+ public:
+ XmlTextNode() : XmlNode(Type::Text) {}
+ explicit XmlTextNode(std::string text)
+ : XmlNode(Type::Text), text_(std::move(text)) {}
+
+ public:
+ std::string GetText() const { return text_; }
+ void SetText(std::string text) { text_ = std::move(text); }
+
+ XmlNode* Clone() const override { return new XmlTextNode(text_); }
+
+ private:
+ std::string text_;
+};
+
+class CRU_BASE_API XmlElementNode : public XmlNode {
+ public:
+ XmlElementNode() : XmlNode(Type::Element) {}
+ explicit XmlElementNode(
+ std::string tag,
+ std::unordered_map<std::string, std::string> attributes = {})
+ : XmlNode(Type::Element),
+ tag_(std::move(tag)),
+ attributes_(std::move(attributes)) {}
+
+ ~XmlElementNode() override;
+
+ public:
+ std::string GetTag() const { return tag_; }
+ void SetTag(std::string tag) { tag_ = std::move(tag); }
+ const std::unordered_map<std::string, std::string>& GetAttributes() const {
+ return attributes_;
+ }
+ void SetAttributes(std::unordered_map<std::string, std::string> attributes) {
+ attributes_ = std::move(attributes);
+ }
+ const std::vector<XmlNode*> GetChildren() const { return children_; }
+
+ Index GetChildCount() const { return children_.size(); }
+ std::string GetAttributeValue(const std::string& key) const {
+ return attributes_.at(key);
+ }
+ std::string GetAttributeValueCaseInsensitive(const std::string& key) const {
+ return *GetOptionalAttributeValueCaseInsensitive(key);
+ }
+ std::optional<std::string> GetOptionalAttributeValue(
+ const std::string& key) const {
+ auto it = attributes_.find(key);
+ if (it == attributes_.end()) {
+ return std::nullopt;
+ }
+
+ return it->second;
+ }
+ std::optional<std::string> GetOptionalAttributeValueCaseInsensitive(
+ const std::string& key) const {
+ for (auto it = attributes_.begin(); it != attributes_.end(); ++it) {
+ if (cru::string::CaseInsensitiveCompare(it->first, key) == 0) {
+ return it->second;
+ }
+ }
+
+ return std::nullopt;
+ }
+
+ XmlNode* GetChildAt(Index index) const { return children_[index]; }
+
+ void AddAttribute(std::string key, std::string value);
+ void AddChild(XmlNode* child);
+
+ Index GetChildElementCount() const;
+ XmlElementNode* GetFirstChildElement() const;
+
+ XmlNode* Clone() const override;
+
+ private:
+ std::string tag_;
+ std::unordered_map<std::string, std::string> attributes_;
+ std::vector<XmlNode*> children_;
+};
+
+class CRU_BASE_API XmlCommentNode : public XmlNode {
+ public:
+ XmlCommentNode() : XmlNode(Type::Comment) {}
+ explicit XmlCommentNode(std::string text)
+ : XmlNode(Type::Comment), text_(std::move(text)) {}
+
+ std::string GetText() const { return text_; }
+ void SetText(std::string text) { text_ = std::move(text); }
+
+ XmlNode* Clone() const override;
+
+ private:
+ std::string text_;
+};
+} // namespace cru::xml
diff --git a/include/cru/base/xml/XmlParser.h b/include/cru/base/xml/XmlParser.h
new file mode 100644
index 00000000..22b529d7
--- /dev/null
+++ b/include/cru/base/xml/XmlParser.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "../Exception.h"
+#include "XmlNode.h"
+
+namespace cru::xml {
+class CRU_BASE_API XmlParsingException : public Exception {
+ public:
+ using Exception::Exception;
+};
+
+class CRU_BASE_API XmlParser {
+ public:
+ explicit XmlParser(std::string xml);
+
+ CRU_DELETE_COPY(XmlParser)
+ CRU_DELETE_MOVE(XmlParser)
+
+ ~XmlParser();
+
+ XmlElementNode* Parse();
+
+ private:
+ XmlElementNode* DoParse();
+
+ char16_t Read1();
+ std::string ReadWithoutAdvance(int count = 1);
+ void ReadSpacesAndDiscard();
+ std::string ReadSpaces();
+ std::string ReadIdenitifier();
+ std::string ReadAttributeString();
+
+ private:
+ std::string xml_;
+
+ XmlElementNode* cache_ = nullptr;
+
+ // Consider the while file enclosed by a single tag called $root.
+ XmlElementNode* pseudo_root_node_ = new XmlElementNode("$root");
+ XmlElementNode* current_ = pseudo_root_node_;
+ int current_position_ = 0;
+};
+} // namespace cru::xml