Skip to content

Commit

Permalink
Merge pull request #14 from Adamska1008/13-feature-add-support-for-pa…
Browse files Browse the repository at this point in the history
…rsing-cdata-sections

feat(cdata.cpp): support parsing CDATA
  • Loading branch information
Adamska1008 authored Aug 29, 2024
2 parents 009a884 + 27929ea commit 52cf642
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 115 deletions.
19 changes: 19 additions & 0 deletions include/myxml/cdata.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once
#include "myxml/node.hpp"

namespace myxml
{
class CData : public Node
{
private:
std::string inner;

public:
explicit CData(std::string);

virtual ~CData() = default;
virtual std::string ExportRaw() const override;
virtual std::string ExportFormatted(int indentLevel = 0, int indentSize = 4) const override;
virtual void SetEntityEncoding(bool) override;
};
}
15 changes: 4 additions & 11 deletions include/myxml/element.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,16 @@ namespace myxml
};

private:
// std::shared_ptr<Node> firstChild;
// std::shared_ptr<Node> lastChild;
std::string name;
std::map<std::string, std::string, std::less<>> attributes;
// std::map<std::string, std::weak_ptr<Element>, std::less<>> nameToElemBuffer;

/* Set nitializer as private to avoid using Element without share_ptr*/
Element(std::string_view name);
/* Set initializer as private to avoid using Element without share_ptr*/
explicit Element(std::string_view name);
Element() = default;

public:
virtual ~Element() = default;

/* Builder */
// Wraps creating shared_ptr
static std::shared_ptr<Element> New(std::string_view name);
Expand All @@ -45,12 +44,6 @@ namespace myxml
void SetAttribute(std::string key, std::string value);
void ExtendAttributes(std::map<std::string, std::string>);

/* Implement Node */
virtual NodeType Type() override;
virtual bool IsType(NodeType) override;
virtual std::optional<std::shared_ptr<Element>> AsElement() override;
virtual std::optional<std::shared_ptr<Text>> AsText() override;

/* Implement Exportable */
virtual std::string ExportRaw() const override;
virtual std::string ExportFormatted(int indentLevel = 0, int indentSize = 4) const override;
Expand Down
28 changes: 15 additions & 13 deletions include/myxml/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,8 @@ namespace myxml
// defined below
class CompositeNode;

enum class NodeType
{
Text,
Element,
Declaration,
};

// Element, Text are Node.
class Node : public Exportable
class Node : public std::enable_shared_from_this<Node>, public Exportable
{
public:
virtual ~Node() = default;
Expand All @@ -31,14 +24,14 @@ namespace myxml
std::shared_ptr<Node> prev;
std::shared_ptr<Node> next;

virtual NodeType Type() = 0;
virtual bool IsType(NodeType) = 0;
virtual std::optional<std::shared_ptr<Element>> AsElement() = 0;
virtual std::optional<std::shared_ptr<Text>> AsText() = 0;
template <typename T>
std::enable_if_t<std::is_base_of_v<Node, T>,
std::optional<std::shared_ptr<T>>>
As();
};

// Element are Composite Node.
class CompositeNode : public Node, public std::enable_shared_from_this<CompositeNode>
class CompositeNode : public Node
{
private:
std::shared_ptr<Node> firstChild;
Expand All @@ -60,4 +53,13 @@ namespace myxml
std::shared_ptr<Node> InsertAtEnd(const std::shared_ptr<Node> &);
void Unlink(const std::shared_ptr<Node> &);
};

template <typename T>
std::enable_if_t<std::is_base_of_v<Node, T>,
std::optional<std::shared_ptr<T>>>
Node::As()
{
auto derivedPtr = std::dynamic_pointer_cast<T>(this->shared_from_this());
return derivedPtr ? std::optional(derivedPtr) : std::nullopt;
}
}
13 changes: 8 additions & 5 deletions include/myxml/parser.hpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
#pragma once
#include "myxml/element.hpp"
#include "myxml/document.hpp"
#include "myxml/cdata.hpp"

namespace myxml
{
// No effect currently. Just use it to mark what is a tag.
struct Tag
{
};

struct ElementTag
{
enum class ClosingType
Expand Down Expand Up @@ -59,7 +55,14 @@ namespace myxml
* @throws `SyntaxError` if the following chars do not confront to `key="value"` format
*/
std::optional<std::pair<std::string, std::string>> parseAttribute();
/**
* @throws `SyntaxError` if faild to find `<`
*/
std::shared_ptr<Text> parseText();
/**
* @returns `std::nullopt` if not start with `<!CDATA[`
*/
std::optional<std::shared_ptr<CData>> parseCData();
/**
* @throws `UnexpectedEndOfInput`
* @throws `SyntaxError`
Expand Down
12 changes: 4 additions & 8 deletions include/myxml/text.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace myxml
{
class Text : public Node, public std::enable_shared_from_this<Text>
class Text : public Node
{
private:
std::string inner;
Expand All @@ -13,18 +13,14 @@ namespace myxml
public:
explicit Text(std::string_view str);

virtual ~Text() = default;

// may used in Export
bool IsAllSpace() const;

/* implement Node */
virtual NodeType Type() override;
virtual bool IsType(NodeType) override;
virtual std::optional<std::shared_ptr<Element>> AsElement() override;
virtual std::optional<std::shared_ptr<Text>> AsText() override;

/* Implment Exportable*/
virtual std::string ExportRaw() const override;
virtual std::string ExportFormatted(int indentLevel = 0, int indentSize = 4) const override;
virtual void SetEntityEncoding(bool) override;
};
}
}
24 changes: 24 additions & 0 deletions src/cdata.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <fmt/core.h>
#include "myxml/cdata.hpp"

namespace myxml
{
CData::CData(std::string str)
: inner(str)
{
}

std::string CData::ExportRaw() const
{
return fmt::format("<![CDATA[{}]]>\n", this->inner);
}

std::string CData::ExportFormatted(int indentLevel, int indentSize) const
{
return std::string(indentLevel * indentSize, ' ') + this->ExportRaw();
}

void CData::SetEntityEncoding(bool)
{ // do nothing
}
}
20 changes: 0 additions & 20 deletions src/element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,6 @@ namespace myxml
this->attributes.insert(attris.begin(), attris.end());
}

NodeType Element::Type()
{
return NodeType::Element;
}

bool Element::IsType(NodeType type)
{
return type == NodeType::Element;
}

std::optional<std::shared_ptr<Element>> Element::AsElement()
{
return std::dynamic_pointer_cast<Element>(this->shared_from_this());
}

std::optional<std::shared_ptr<Text>> Element::AsText()
{
return std::nullopt;
}

std::string Element::ExportRaw() const
{

Expand Down
8 changes: 5 additions & 3 deletions src/node.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#include <type_traits>
#include "myxml/node.hpp"
#include "myxml/element.hpp"

namespace myxml
{


std::shared_ptr<Node> CompositeNode::LastChild()
{
return this->lastChild;
Expand Down Expand Up @@ -40,7 +42,7 @@ namespace myxml
}
for (auto child = this->firstChild; child != nullptr; child = child->next)
{
if (auto elem = child->AsElement(); elem && (*elem)->GetName() == name)
if (auto elem = child->As<Element>(); elem && (*elem)->GetName() == name)
{
this->nameToElemBuffer.emplace(name, *elem);
return *elem;
Expand All @@ -55,7 +57,7 @@ namespace myxml
{
elem->parent->Unlink(elem);
}
elem->parent = this->shared_from_this();
elem->parent = *this->shared_from_this()->As<CompositeNode>();
if (this->firstChild == nullptr)
{
this->firstChild = elem;
Expand All @@ -76,7 +78,7 @@ namespace myxml
{
elem->parent->Unlink(elem);
}
elem->parent = this->shared_from_this();
elem->parent = *this->shared_from_this()->As<CompositeNode>();
if (this->firstChild == nullptr)
{
this->firstChild = elem;
Expand Down
31 changes: 29 additions & 2 deletions src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,36 @@ namespace myxml
{
len++;
}
if (this->buffer[begin + len] != '<')
if (begin + len >= this->buffer.length())
{ // if jump out of while loop due to length limit
throw SyntaxError(fmt::format("expected '<' after text"));
}
this->offset += len;
return std::shared_ptr<Text>(new Text(this->buffer.substr(begin, len)));
}

std::optional<std::shared_ptr<CData>> Parser::parseCData()
{
if (this->peekNextNChars(9) != "<![CDATA[")
{
return std::nullopt;
}
this->nextNChars(9);
std::size_t begin = this->offset;
std::size_t len = 0;
while (begin + len + 2 < this->buffer.length() &&
std::string_view(this->buffer.data() + begin + len, 3) != "]]>")
{
len++;
}
if (begin + len + 2 >= this->buffer.length())
{
throw SyntaxError(fmt::format("expected \"]]>\" after CDATA"));
}
this->offset += len + 2;
return std::shared_ptr<CData>(new CData(this->buffer.substr(begin, len)));
}

std::optional<ElementTag> Parser::ParseTag()
{
if (this->nextChar() != '<')
Expand Down Expand Up @@ -194,6 +216,11 @@ namespace myxml
{
case '<':
{
if (auto cdata = this->parseCData(); cdata)
{
elem->InsertAtEnd(*cdata);
continue;
}
auto tag = this->ParseTag(); // impossible to be std::nullopt
assert(tag);
switch (tag->type)
Expand All @@ -218,7 +245,7 @@ namespace myxml
case ElementTag::ClosingType::Closing:
if (tag->name != elem->GetName())
{
throw SyntaxError(fmt::format(""));
throw SyntaxError(fmt::format("elem name in closing tag is mismatched with the header"));
}
if (!header.attris.empty())
{
Expand Down
20 changes: 0 additions & 20 deletions src/text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,6 @@

namespace myxml
{
NodeType Text::Type()
{
return NodeType::Text;
}

bool Text::IsType(NodeType type)
{
return type == NodeType::Text;
}

std::optional<std::shared_ptr<Element>> Text::AsElement()
{
return std::nullopt;
}

std::optional<std::shared_ptr<Text>> Text::AsText()
{
Text *test = dynamic_cast<Text *>(this);
return std::dynamic_pointer_cast<Text>(this->shared_from_this());
}

Text::Text(std::string_view input)
: encodeOnExport(true)
Expand Down
2 changes: 1 addition & 1 deletion tests/document_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ TEST_CASE("Simple document", "[document]")
auto doc = myxml::Document::Parse(input);
REQUIRE(doc->GetRoot()->GetName() == "root");
REQUIRE(doc->GetRoot()->Elem("child")->GetName() == "child");
REQUIRE(doc->GetRoot()->Elem("child")->FirstChild()->AsText().value()->ExportRaw() == "Value");
REQUIRE(doc->GetRoot()->Elem("child")->FirstChild()->As<myxml::Text>().value()->ExportRaw() == "Value");
}

SECTION("With decl")
Expand Down
8 changes: 4 additions & 4 deletions tests/element_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ TEST_CASE("Element Functionality", "[element]")
SECTION("Basic Insertion")
{
root->InsertAtFront(child);
REQUIRE(root->FirstChild()->AsElement().value()->GetName() == "child");
REQUIRE(root->LastChild()->AsElement().value()->GetName() == "child");
REQUIRE(root->FirstChild()->As<myxml::Element>().value()->GetName() == "child");
REQUIRE(root->LastChild()->As<myxml::Element>().value()->GetName() == "child");
}

SECTION("Get child by name after insert it")
Expand All @@ -33,7 +33,7 @@ TEST_CASE("Element Functionality", "[element]")
root->InsertAtEnd(child);
root->InsertAtEnd(sibiling);
REQUIRE(root->Elem("child")->GetName() == "child");
REQUIRE(root->Elem("child")->next->AsElement().value()->GetName() == "sibiling");
REQUIRE(root->Elem("sibiling")->prev->AsElement().value()->GetName() == "child");
REQUIRE(root->Elem("child")->next->As<myxml::Element>().value()->GetName() == "sibiling");
REQUIRE(root->Elem("sibiling")->prev->As<myxml::Element>().value()->GetName() == "child");
}
}
Loading

0 comments on commit 52cf642

Please sign in to comment.