diff options
| author | Yuqian Yang <crupest@crupest.life> | 2025-11-04 21:59:42 +0800 |
|---|---|---|
| committer | Yuqian Yang <crupest@crupest.life> | 2025-11-04 21:59:42 +0800 |
| commit | 1a6111e3f02b0a9cff0f81fb524b4dfb7d69854b (patch) | |
| tree | 3a92e1bf61fc165c2148f38ca6602f5066011f06 /src/base | |
| parent | f48505c96a70e2f1d1982fea30f3015e42fcd49d (diff) | |
| download | cru-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.txt | 2 | ||||
| -rw-r--r-- | src/base/xml/XmlNode.cpp | 75 | ||||
| -rw-r--r-- | src/base/xml/XmlParser.cpp | 197 |
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 |
