aboutsummaryrefslogtreecommitdiff
path: root/src/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 /src/base
parentf48505c96a70e2f1d1982fea30f3015e42fcd49d (diff)
downloadcru-1a6111e3f02b0a9cff0f81fb524b4dfb7d69854b.tar.gz
cru-1a6111e3f02b0a9cff0f81fb524b4dfb7d69854b.tar.bz2
cru-1a6111e3f02b0a9cff0f81fb524b4dfb7d69854b.zip
Move xml to base.
Diffstat (limited to 'src/base')
-rw-r--r--src/base/CMakeLists.txt2
-rw-r--r--src/base/xml/XmlNode.cpp75
-rw-r--r--src/base/xml/XmlParser.cpp197
3 files changed, 274 insertions, 0 deletions
diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt
index be45d0e0..ef5afe3c 100644
--- a/src/base/CMakeLists.txt
+++ b/src/base/CMakeLists.txt
@@ -14,6 +14,8 @@ add_library(CruBase
io/MemoryStream.cpp
log/Logger.cpp
log/StdioLogTarget.cpp
+ xml/XmlNode.cpp
+ xml/XmlParser.cpp
)
target_compile_definitions(CruBase PRIVATE CRU_BASE_EXPORT_API)
target_include_directories(CruBase PUBLIC ${CRU_INCLUDE_DIR})
diff --git a/src/base/xml/XmlNode.cpp b/src/base/xml/XmlNode.cpp
new file mode 100644
index 00000000..d3e7369b
--- /dev/null
+++ b/src/base/xml/XmlNode.cpp
@@ -0,0 +1,75 @@
+#include "cru/base/xml/XmlNode.h"
+
+#include <algorithm>
+
+namespace cru::xml {
+
+XmlElementNode* XmlNode::AsElement() {
+ return static_cast<XmlElementNode*>(this);
+}
+
+XmlTextNode* XmlNode::AsText() { return static_cast<XmlTextNode*>(this); }
+
+XmlCommentNode* XmlNode::AsComment() {
+ return static_cast<XmlCommentNode*>(this);
+}
+
+const XmlElementNode* XmlNode::AsElement() const {
+ return static_cast<const XmlElementNode*>(this);
+}
+
+const XmlTextNode* XmlNode::AsText() const {
+ return static_cast<const XmlTextNode*>(this);
+}
+
+const XmlCommentNode* XmlNode::AsComment() const {
+ return static_cast<const XmlCommentNode*>(this);
+}
+
+XmlElementNode::~XmlElementNode() {
+ for (auto child : children_) {
+ delete child;
+ }
+}
+
+void XmlElementNode::AddAttribute(std::string key, std::string value) {
+ attributes_[std::move(key)] = std::move(value);
+}
+
+void XmlElementNode::AddChild(XmlNode* child) {
+ Expects(child->GetParent() == nullptr);
+ children_.push_back(child);
+ child->parent_ = this;
+}
+
+Index XmlElementNode::GetChildElementCount() const {
+ return std::count_if(
+ children_.cbegin(), children_.cend(),
+ [](xml::XmlNode* node) { return node->IsElementNode(); });
+}
+
+XmlElementNode* XmlElementNode::GetFirstChildElement() const {
+ for (auto child : children_) {
+ if (child->GetType() == XmlNode::Type::Element) {
+ return child->AsElement();
+ }
+ }
+ return nullptr;
+}
+
+XmlNode* XmlElementNode::Clone() const {
+ XmlElementNode* node = new XmlElementNode(tag_, attributes_);
+
+ for (auto child : children_) {
+ node->AddChild(child->Clone());
+ }
+
+ return node;
+}
+
+XmlNode* XmlCommentNode::Clone() const {
+ XmlCommentNode* node = new XmlCommentNode(text_);
+
+ return node;
+}
+} // namespace cru::xml
diff --git a/src/base/xml/XmlParser.cpp b/src/base/xml/XmlParser.cpp
new file mode 100644
index 00000000..c35d7a7b
--- /dev/null
+++ b/src/base/xml/XmlParser.cpp
@@ -0,0 +1,197 @@
+#include "cru/base/xml/XmlParser.h"
+#include "cru/base/StringUtil.h"
+#include "cru/base/xml/XmlNode.h"
+
+namespace cru::xml {
+XmlParser::XmlParser(std::string xml) : xml_(std::move(xml)) {}
+
+XmlParser::~XmlParser() { delete pseudo_root_node_; }
+
+XmlElementNode* XmlParser::Parse() {
+ if (!cache_) {
+ cache_ = DoParse();
+ }
+ return static_cast<XmlElementNode*>(cache_->Clone());
+}
+
+char16_t XmlParser::Read1() {
+ if (current_position_ >= xml_.size()) {
+ throw XmlParsingException("Unexpected end of xml");
+ }
+ return xml_[current_position_++];
+}
+
+std::string XmlParser::ReadWithoutAdvance(int count) {
+ if (current_position_ + count > xml_.size()) {
+ count = xml_.size() - current_position_;
+ }
+ return xml_.substr(current_position_, count);
+}
+
+void XmlParser::ReadSpacesAndDiscard() {
+ while (current_position_ < xml_.size() &&
+ (xml_[current_position_] == ' ' || xml_[current_position_] == '\t' ||
+ xml_[current_position_] == '\n' || xml_[current_position_] == '\r')) {
+ ++current_position_;
+ }
+}
+
+std::string XmlParser::ReadSpaces() {
+ std::string spaces;
+ while (current_position_ < xml_.size() &&
+ (xml_[current_position_] == ' ' || xml_[current_position_] == '\t' ||
+ xml_[current_position_] == '\n' || xml_[current_position_] == '\r')) {
+ spaces += xml_[current_position_];
+ ++current_position_;
+ }
+ return spaces;
+}
+
+std::string XmlParser::ReadIdenitifier() {
+ std::string identifier;
+ while (current_position_ < xml_.size() &&
+ (xml_[current_position_] >= 'a' && xml_[current_position_] <= 'z' ||
+ xml_[current_position_] >= 'A' && xml_[current_position_] <= 'Z' ||
+ xml_[current_position_] >= '0' && xml_[current_position_] <= '9' ||
+ xml_[current_position_] == '_')) {
+ identifier += xml_[current_position_];
+ ++current_position_;
+ }
+ return identifier;
+}
+
+std::string XmlParser::ReadAttributeString() {
+ if (Read1() != '"') {
+ throw XmlParsingException("Expected \".");
+ }
+
+ std::string string;
+
+ while (true) {
+ char16_t c = Read1();
+
+ if (c == '"') {
+ break;
+ }
+
+ string += c;
+ }
+
+ return string;
+}
+
+XmlElementNode* XmlParser::DoParse() {
+ while (current_position_ < xml_.size()) {
+ ReadSpacesAndDiscard();
+
+ if (current_position_ == xml_.size()) {
+ break;
+ }
+
+ if (ReadWithoutAdvance() == "<") {
+ current_position_ += 1;
+
+ if (ReadWithoutAdvance() == "/") {
+ current_position_ += 1;
+
+ ReadSpacesAndDiscard();
+
+ std::string tag = ReadIdenitifier();
+
+ if (tag != current_->GetTag()) {
+ throw XmlParsingException("Tag mismatch.");
+ }
+
+ ReadSpacesAndDiscard();
+
+ if (Read1() != '>') {
+ throw XmlParsingException("Expected >.");
+ }
+
+ current_ = current_->GetParent();
+ } else if (ReadWithoutAdvance(3) == "!--") {
+ current_position_ += 3;
+
+ std::string text;
+ while (true) {
+ auto str = ReadWithoutAdvance(3);
+ if (str == "-->") break;
+ if (str.empty()) throw XmlParsingException("Unexpected end of xml");
+ text += Read1();
+ }
+
+ current_position_ += 3;
+ current_->AddChild(new XmlCommentNode(cru::string::Trim(text)));
+ } else {
+ ReadSpacesAndDiscard();
+
+ std::string tag = ReadIdenitifier();
+
+ XmlElementNode* node = new XmlElementNode(tag);
+
+ bool is_self_closing = false;
+
+ while (true) {
+ ReadSpacesAndDiscard();
+ auto c = ReadWithoutAdvance();
+ if (c == ">") {
+ current_position_ += 1;
+ break;
+ } else if (c == "/") {
+ current_position_ += 1;
+
+ if (Read1() != '>') {
+ throw XmlParsingException("Expected >.");
+ }
+
+ is_self_closing = true;
+ break;
+ } else {
+ std::string attribute_name = ReadIdenitifier();
+
+ ReadSpacesAndDiscard();
+
+ if (Read1() != '=') {
+ throw XmlParsingException("Expected '='");
+ }
+
+ ReadSpacesAndDiscard();
+
+ std::string attribute_value = ReadAttributeString();
+
+ node->AddAttribute(attribute_name, attribute_value);
+ }
+ }
+
+ current_->AddChild(node);
+
+ if (!is_self_closing) {
+ current_ = node;
+ }
+ }
+
+ } else {
+ std::string text;
+
+ while (ReadWithoutAdvance() != "<") {
+ char16_t c = Read1();
+
+ text += c;
+ }
+
+ if (!text.empty())
+ current_->AddChild(new XmlTextNode(cru::string::TrimEnd(text)));
+ }
+ }
+
+ if (current_ != pseudo_root_node_) {
+ throw XmlParsingException("Unexpected end of xml");
+ }
+
+ if (pseudo_root_node_->GetChildren().size() != 1) {
+ throw XmlParsingException("Expected 1 node as root.");
+ }
+
+ return static_cast<XmlElementNode*>(pseudo_root_node_->GetChildren()[0]);
+}
+} // namespace cru::xml