diff options
-rw-r--r-- | include/cru/common/String.hpp | 4 | ||||
-rw-r--r-- | include/cru/xml/XmlNode.hpp | 94 | ||||
-rw-r--r-- | include/cru/xml/XmlParser.hpp | 22 | ||||
-rw-r--r-- | src/xml/XmlNode.cpp | 23 | ||||
-rw-r--r-- | src/xml/XmlParser.cpp | 114 |
5 files changed, 201 insertions, 56 deletions
diff --git a/include/cru/common/String.hpp b/include/cru/common/String.hpp index 4dab5b5a..ade2d84b 100644 --- a/include/cru/common/String.hpp +++ b/include/cru/common/String.hpp @@ -166,6 +166,10 @@ class CRU_BASE_API String { inline void append(StringView str); public: + String& operator+=(value_type value) { + this->append(value); + return *this; + } String& operator+=(StringView other); operator std::u16string_view() const { diff --git a/include/cru/xml/XmlNode.hpp b/include/cru/xml/XmlNode.hpp index 1787d6a0..0cbb6756 100644 --- a/include/cru/xml/XmlNode.hpp +++ b/include/cru/xml/XmlNode.hpp @@ -7,61 +7,87 @@ #include <vector> namespace cru::xml { +class XmlElementNode; + class XmlNode { + friend XmlElementNode; + public: enum class Type { Text, Element }; - static XmlNode CreateText(String text) { - XmlNode node(Type::Text); - node.SetText(std::move(text)); - return node; - } - - static XmlNode CreateElement( - String tag, std::unordered_map<String, String> attributes = {}, - std::vector<XmlNode> children = {}) { - XmlNode node(Type::Element); - node.SetTag(std::move(tag)); - node.SetAttributes(std::move(attributes)); - node.SetChildren(std::move(children)); - return node; - } - + protected: explicit XmlNode(Type type) : type_(type) {} - CRU_DEFAULT_COPY(XmlNode) - CRU_DEFAULT_MOVE(XmlNode) + public: + CRU_DELETE_COPY(XmlNode) + CRU_DELETE_MOVE(XmlNode) - ~XmlNode() = default; + virtual ~XmlNode() = default; Type GetType() const { return type_; } + XmlElementNode* GetParent() const { return parent_; } + + virtual XmlNode* Clone() const = 0; + + private: + const Type type_; + XmlElementNode* parent_ = nullptr; +}; + +class XmlTextNode : public XmlNode { + public: + XmlTextNode() : XmlNode(Type::Text) {} + explicit XmlTextNode(String text) + : XmlNode(Type::Text), text_(std::move(text)) {} + + CRU_DELETE_COPY(XmlTextNode) + CRU_DELETE_MOVE(XmlTextNode) + + ~XmlTextNode() override = default; + + public: String GetText() const { return text_; } + void SetText(String text) { text_ = std::move(text); } + + XmlNode* Clone() const override { return new XmlTextNode(text_); } + + private: + String text_; +}; + +class XmlElementNode : public XmlNode { + public: + XmlElementNode() : XmlNode(Type::Element) {} + explicit XmlElementNode(String tag, + std::unordered_map<String, String> attributes = {}) + : XmlNode(Type::Element), + tag_(std::move(tag)), + attributes_(std::move(attributes)) {} + + CRU_DELETE_COPY(XmlElementNode) + CRU_DELETE_MOVE(XmlElementNode) + + ~XmlElementNode() override = default; + + public: String GetTag() const { return tag_; } - std::unordered_map<String, String> GetAttributes() { return attributes_; } + void SetTag(String tag) { tag_ = std::move(tag); } const std::unordered_map<String, String>& GetAttributes() const { return attributes_; } - std::vector<XmlNode>& GetChildren() { return children_; } - const std::vector<XmlNode>& GetChildren() const { return children_; } - - void SetType(Type type) { type_ = type; } - void SetText(String text) { text_ = std::move(text); } - void SetTag(String tag) { tag_ = std::move(tag); } void SetAttributes(std::unordered_map<String, String> attributes) { attributes_ = std::move(attributes); } - void SetChildren(std::vector<XmlNode> children) { - children_ = std::move(children); - } + + void AddAttribute(String key, String value); + void AddChild(XmlNode* child); + + XmlNode* Clone() const override; private: - Type type_; - String text_; String tag_; std::unordered_map<String, String> attributes_; - std::vector<XmlNode> children_; + std::vector<XmlNode*> children_; }; -bool operator==(const XmlNode& lhs, const XmlNode& rhs); -bool operator!=(const XmlNode& lhs, const XmlNode& rhs); } // namespace cru::xml diff --git a/include/cru/xml/XmlParser.hpp b/include/cru/xml/XmlParser.hpp index 19f569d1..1d44c46f 100644 --- a/include/cru/xml/XmlParser.hpp +++ b/include/cru/xml/XmlParser.hpp @@ -2,11 +2,17 @@ #include "XmlNode.hpp" +#include "cru/common/Exception.hpp" #include "cru/common/String.hpp" #include <optional> namespace cru::xml { +class XmlParsingException : public Exception { + public: + using Exception::Exception; +}; + class XmlParser { public: explicit XmlParser(String xml); @@ -16,14 +22,24 @@ class XmlParser { ~XmlParser(); - XmlNode Parse(); + XmlElementNode* Parse(); private: - XmlNode DoParse(); + XmlElementNode* DoParse(); + + char16_t Read1(); + void ReadSpacesAndDiscard(); + String ReadSpaces(); + String ReadIdenitifier(); + String ReadAttributeString(); private: String xml_; - std::optional<XmlNode> root_node_; + XmlElementNode* cache_; + + XmlElementNode* pseudo_root_node_ = new XmlElementNode(u"$root"); + XmlElementNode* current_ = pseudo_root_node_; + int current_position_ = 0; }; } // namespace cru::xml diff --git a/src/xml/XmlNode.cpp b/src/xml/XmlNode.cpp index cfeb5cf9..f4b43ea6 100644 --- a/src/xml/XmlNode.cpp +++ b/src/xml/XmlNode.cpp @@ -1,14 +1,23 @@ #include "cru/xml/XmlNode.hpp" namespace cru::xml { -bool operator==(const XmlNode& lhs, const XmlNode& rhs) { - return lhs.GetType() == rhs.GetType() && lhs.GetText() == rhs.GetText() && - lhs.GetTag() == rhs.GetTag() && - lhs.GetAttributes() == rhs.GetAttributes() && - lhs.GetChildren() == rhs.GetChildren(); +void XmlElementNode::AddAttribute(String key, String value) { + attributes_[std::move(key)] = std::move(value); } -bool operator!=(const XmlNode& lhs, const XmlNode& rhs) { - return !(lhs == rhs); +void XmlElementNode::AddChild(XmlNode* child) { + assert(child->GetParent() == nullptr); + children_.push_back(child); + child->parent_ = this; +} + +XmlNode* XmlElementNode::Clone() const { + XmlElementNode* node = new XmlElementNode(tag_, attributes_); + + for (auto child : children_) { + node->AddChild(child->Clone()); + } + + return node; } } // namespace cru::xml diff --git a/src/xml/XmlParser.cpp b/src/xml/XmlParser.cpp index 23407d11..f24a7f68 100644 --- a/src/xml/XmlParser.cpp +++ b/src/xml/XmlParser.cpp @@ -1,27 +1,117 @@ #include "cru/xml/XmlParser.hpp" +#include "cru/xml/XmlNode.hpp" namespace cru::xml { -XmlNode XmlParser::Parse() { - if (!root_node_) { - root_node_ = DoParse(); +XmlElementNode* XmlParser::Parse() { + if (!cache_) { + cache_ = DoParse(); } - return *root_node_; + return static_cast<XmlElementNode*>(cache_->Clone()); } -XmlNode XmlParser::DoParse() { - XmlNode root(XmlNode::Type::Element); - XmlNode* current = &root; - int current_position = 0; +char16_t XmlParser::Read1() { + if (current_position_ >= xml_.size()) { + throw XmlParsingException(u"Unexpected end of xml"); + } + return xml_[current_position_++]; +} + +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_; + } +} + +String XmlParser::ReadSpaces() { + 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; +} + +String XmlParser::ReadIdenitifier() { + 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; +} + +String XmlParser::ReadAttributeString() { + if (Read1() != '"') { + throw XmlParsingException(u"Expected \"."); + } + + String string; + + while (true) { + char16_t c = Read1(); + + if (c == '"') { + break; + } + + string += c; + } + + return string; +} - // TODO: Implement this. - while (current_position < xml_.size()) { - switch (xml_[current_position]) { +XmlElementNode* XmlParser::DoParse() { + while (current_position_ < xml_.size()) { + switch (xml_[current_position_]) { case '<': { + ++current_position_; + + if (Read1() == '/') { + } else { + ReadSpacesAndDiscard(); + + String tag = ReadIdenitifier(); + + XmlElementNode* node = new XmlElementNode(tag); + + while (true) { + ReadSpacesAndDiscard(); + if (Read1() == '>') { + break; + } else { + String attribute_name = ReadIdenitifier(); + + ReadSpacesAndDiscard(); + + if (Read1() != '=') { + throw XmlParsingException(u"Expected '='"); + } + + ReadSpacesAndDiscard(); + + String attribute_value = ReadAttributeString(); + + node->AddAttribute(attribute_name, attribute_value); + } + } + + current_->AddChild(node); + current_ = node; + } + break; } } } - return root; + return pseudo_root_node_; } } // namespace cru::xml |