From 9bfd637a1969677a1683a964c271e33815d329fd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 17 Oct 2015 00:36:29 -0700 Subject: [PATCH 1/5] Core SConscript --- lib/core/SConscript | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/core/SConscript diff --git a/lib/core/SConscript b/lib/core/SConscript new file mode 100644 index 0000000..e69de29 From af30c9e5972fc3b1ba01e7fcfd2e030390520a24 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 17 Oct 2015 00:37:17 -0700 Subject: [PATCH 2/5] Add String.hh --- lib/core/include/core/String.hh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/core/include/core/String.hh diff --git a/lib/core/include/core/String.hh b/lib/core/include/core/String.hh new file mode 100644 index 0000000..0206db5 --- /dev/null +++ b/lib/core/include/core/String.hh @@ -0,0 +1,18 @@ +/* String.hh + * vim: set tw=80: + * Eryn Wells + */ +/** + * Strings are fun. + */ + +#include + + +namespace erw { +namespace core { + +typedef std::string String; + +} /* namespace core */ +} /* namespace erw */ From 134f2aa3fb1b46ccc38f4b9694953c4d38a305ab Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 17 Oct 2015 09:16:26 -0700 Subject: [PATCH 3/5] InFile class --- lib/core/SConscript | 4 + lib/core/include/core/File.hh | 105 +++++++++++++++++++++++++ lib/core/src/File.cc | 140 ++++++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 lib/core/include/core/File.hh create mode 100644 lib/core/src/File.cc diff --git a/lib/core/SConscript b/lib/core/SConscript index e69de29..024769d 100644 --- a/lib/core/SConscript +++ b/lib/core/SConscript @@ -0,0 +1,4 @@ +# SConscript +# Eryn Wells + +Library('erw', ['src/File.cc']) diff --git a/lib/core/include/core/File.hh b/lib/core/include/core/File.hh new file mode 100644 index 0000000..5f84641 --- /dev/null +++ b/lib/core/include/core/File.hh @@ -0,0 +1,105 @@ +/* File.hh + * vim: set tw=80: + * Eryn Wells + */ +/** + * File interface. + */ + +#include +#include +#include + +#include "String.hh" + + +namespace erw { +namespace core { + +struct File +{ + enum class SeekFrom { + /** Seek from the beginning of the file. */ + Begin, + /** Seek from the current file offset. */ + Here, + /** Seek from the end of the file. */ + End, + }; + + typedef std::bitset<3> Mode; + + static constexpr Mode In = Mode(1); + static constexpr Mode Out = Mode(2); + static constexpr Mode Binary = Mode(4); + + /** Destructor. */ + virtual ~File(); + + /** Seek to an absolute position in the file. */ + virtual File& seek(size_t pos) = 0; + + /** + * Seek to an `offset` from the given anchor point in the file. + * @see SeekFrom + */ + virtual File& seek(ssize_t offset, SeekFrom from) = 0; + +protected: + /** Convert a File::Mode to an iostream openmode bitset. */ + virtual std::ios_base::openmode modeToIOSMode(Mode mode); +}; + + +/** File handle for reading. */ +struct InFile + : public File +{ + /** Open a file at `path` for reading. */ + InFile(const String& path, File::Mode mode); + + /** Deleted copy constructor. File handles cannot be copied. */ + InFile(const InFile& other) = delete; + + /** Move `other` to this InFile. */ + InFile(InFile&& other); + + virtual ~InFile(); + + /** Move `other` to this InFile. File handles cannot be copied. */ + InFile& operator=(InFile& other); + + /** Read up to `count` characters into the provided `buffer`. */ + InFile& read(char* buffer, ssize_t count); + + /** @see File::seek */ + InFile& seek(size_t pos) override; + + /** @See File::seek */ + InFile& seek(ssize_t pos, File::SeekFrom from) override; + +private: + std::ifstream mStream; + + std::ios_base::openmode modeToIOSMode(File::Mode mode) override; +}; + + +struct OutFile + : public File +{ + /** Write `count` characters from the provided `buffer` into this file. */ + InFile& write(char* buffer, ssize_t count); + + /** @see File::seek */ + InFile& seek(size_t pos) override; + + /** @See File::seek */ + InFile& seek(ssize_t pos, File::SeekFrom from) override; + +private: + std::ofstream stream; +}; + +} /* namespace core */ +} /* namespace erw */ diff --git a/lib/core/src/File.cc b/lib/core/src/File.cc new file mode 100644 index 0000000..68ec789 --- /dev/null +++ b/lib/core/src/File.cc @@ -0,0 +1,140 @@ +/* File.cc + * vim: set tw=80: + * Eryn Wells + */ +/** + * Implementation of file handling. + */ + +#include "core/File.hh" + + +namespace erw { +namespace core { + +#pragma mark - File + +File::~File() +{ } + + +/* + * InFile::modeToIOSMode -- + */ +std::ios_base::openmode +File::modeToIOSMode(Mode mode) +{ + std::ios_base::openmode openmode = 0; + if ((mode & In).any()) { + openmode |= std::ios_base::in; + } + if ((mode & Out).any()) { + openmode |= std::ios_base::out; + } + if ((mode & Binary).any()) { + openmode |= std::ios_base::binary; + } + return openmode; +} + +#pragma mark - InFile + +/* + * InFile::InFile -- + */ +InFile::InFile(const String& path, + InFile::Mode mode) + : mStream(path, modeToIOSMode(mode)) +{ } + + +/* + * InFile::InFile -- + */ +InFile::InFile(InFile&& other) + : mStream(std::move(other.mStream)) +{ } + + +/* + * InFile::~InFile -- + */ +InFile::~InFile() +{ } + + +/* + * InFile::operator= -- + */ +InFile& +InFile::operator=(InFile& other) +{ + mStream = std::move(other.mStream); + return *this; +} + + +/* + * InFile::read -- + */ +InFile& +InFile::read(char* buffer, + ssize_t count) +{ + mStream.read(buffer, count); + return *this; +} + + +/* + * InFile::seek -- + */ +InFile& +InFile::seek(size_t pos) +{ + mStream.seekg(pos); + return *this; +} + + +/* + * InFile::seek -- + */ +InFile& +InFile::seek(ssize_t off, + File::SeekFrom from) +{ + std::ios_base::seekdir dir; + switch (from) { + case File::SeekFrom::Begin: + dir = std::ios_base::beg; + break; + case File::SeekFrom::Here: + dir = std::ios_base::cur; + break; + case File::SeekFrom::End: + dir = std::ios_base::end; + break; + } + mStream.seekg(off, dir); + return *this; +} + + +/* + * InFile::modeToIOSMode -- + */ +std::ios_base::openmode +InFile::modeToIOSMode(File::Mode mode) +{ + // Ensure In flag is always set. + if (!(mode & File::In).any()) { + mode |= File::In; + } + return File::modeToIOSMode(mode); +} + +#pragma mark - OutFile + +} /* namespace core */ +} /* namespace erw */ From 864174827458e09c48293d32107c65eeba91b4aa Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 18 Oct 2015 08:23:59 -0700 Subject: [PATCH 4/5] Add File::path(), which returns the path the File was initialized with; remove core namespace --- lib/core/include/core/File.hh | 13 ++++++++++--- lib/core/include/core/String.hh | 2 -- lib/core/src/File.cc | 32 +++++++++++++++++++++++++++----- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/lib/core/include/core/File.hh b/lib/core/include/core/File.hh index 5f84641..d47ae88 100644 --- a/lib/core/include/core/File.hh +++ b/lib/core/include/core/File.hh @@ -14,7 +14,6 @@ namespace erw { -namespace core { struct File { @@ -36,6 +35,8 @@ struct File /** Destructor. */ virtual ~File(); + String path() const noexcept; + /** Seek to an absolute position in the file. */ virtual File& seek(size_t pos) = 0; @@ -46,8 +47,12 @@ struct File virtual File& seek(ssize_t offset, SeekFrom from) = 0; protected: + const String mPath; + /** Convert a File::Mode to an iostream openmode bitset. */ virtual std::ios_base::openmode modeToIOSMode(Mode mode); + + File(const String& path); }; @@ -57,7 +62,7 @@ struct InFile { /** Open a file at `path` for reading. */ InFile(const String& path, File::Mode mode); - + /** Deleted copy constructor. File handles cannot be copied. */ InFile(const InFile& other) = delete; @@ -69,6 +74,8 @@ struct InFile /** Move `other` to this InFile. File handles cannot be copied. */ InFile& operator=(InFile& other); + String path() const; + /** Read up to `count` characters into the provided `buffer`. */ InFile& read(char* buffer, ssize_t count); @@ -79,6 +86,7 @@ struct InFile InFile& seek(ssize_t pos, File::SeekFrom from) override; private: + String mPath; std::ifstream mStream; std::ios_base::openmode modeToIOSMode(File::Mode mode) override; @@ -101,5 +109,4 @@ private: std::ofstream stream; }; -} /* namespace core */ } /* namespace erw */ diff --git a/lib/core/include/core/String.hh b/lib/core/include/core/String.hh index 0206db5..2e65fe4 100644 --- a/lib/core/include/core/String.hh +++ b/lib/core/include/core/String.hh @@ -10,9 +10,7 @@ namespace erw { -namespace core { typedef std::string String; -} /* namespace core */ } /* namespace erw */ diff --git a/lib/core/src/File.cc b/lib/core/src/File.cc index 68ec789..3fa3430 100644 --- a/lib/core/src/File.cc +++ b/lib/core/src/File.cc @@ -10,16 +10,37 @@ namespace erw { -namespace core { #pragma mark - File +/* + * File::File -- + */ +File::File(const String& path) + : mPath(path) +{ } + + +/* + * File::~File -- + */ File::~File() { } /* - * InFile::modeToIOSMode -- + * File::path -- + */ +String +File::path() + const noexcept +{ + return mPath; +} + + +/* + * File::modeToIOSMode -- */ std::ios_base::openmode File::modeToIOSMode(Mode mode) @@ -44,7 +65,8 @@ File::modeToIOSMode(Mode mode) */ InFile::InFile(const String& path, InFile::Mode mode) - : mStream(path, modeToIOSMode(mode)) + : File(path), + mStream(path, modeToIOSMode(mode)) { } @@ -52,7 +74,8 @@ InFile::InFile(const String& path, * InFile::InFile -- */ InFile::InFile(InFile&& other) - : mStream(std::move(other.mStream)) + : File(other.mPath), + mStream(std::move(other.mStream)) { } @@ -136,5 +159,4 @@ InFile::modeToIOSMode(File::Mode mode) #pragma mark - OutFile -} /* namespace core */ } /* namespace erw */ From 283a5648a84e77ac848ceeb79708926af2b24fd3 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 19 Oct 2015 07:55:06 -0700 Subject: [PATCH 5/5] Breaking up xml parsing into xml namespace and classes --- lib/core/include/core/xml/Document.hh | 47 +++++ lib/core/include/core/xml/Node.hh | 41 ++++ lib/core/src/xml/Document.cc | 275 ++++++++++++++++++++++++++ lib/core/src/xml/Node.cc | 76 +++++++ 4 files changed, 439 insertions(+) create mode 100644 lib/core/include/core/xml/Document.hh create mode 100644 lib/core/include/core/xml/Node.hh create mode 100644 lib/core/src/xml/Document.cc create mode 100644 lib/core/src/xml/Node.cc diff --git a/lib/core/include/core/xml/Document.hh b/lib/core/include/core/xml/Document.hh new file mode 100644 index 0000000..bbccfa8 --- /dev/null +++ b/lib/core/include/core/xml/Document.hh @@ -0,0 +1,47 @@ +/* XMLDocument.hh + * vim: set tw=80: + * Eryn Wells + */ +/** + * An XML document and related structures. + */ + +#pragma once + +#include +#include +#include + +#include "core/File.hh" +#include "core/String.hh" + + +namespace erw { +namespace xml { + +struct Document; + + +/** An XML document. */ +struct Document +{ + /** + * Constructor. Parse an XML document out of the given file. Doing so takes + * ownership of the file. + */ + Document(InFile&& file); + + /** Constructor. Parse an XML document from the given string. */ + Document(const String& string); + + ~Document(); + + const Node& root() const noexcept; + +protected: + /** The root of the XML tree. The document owns its root. */ + Node mRoot; +}; + +} /* namespace xml */ +} /* namespace erw */ diff --git a/lib/core/include/core/xml/Node.hh b/lib/core/include/core/xml/Node.hh new file mode 100644 index 0000000..3ea27f7 --- /dev/null +++ b/lib/core/include/core/xml/Node.hh @@ -0,0 +1,41 @@ +/* Node.hh + * vim: set tw=80: + * Eryn Wells + */ +/** + * An XML node. + */ + +#pragma once + +#include +#include + +#include "String.hh" + +namespace erw { +namespace xml { + +/** A node in an XML tree. */ +struct Node +{ + typedef std::vector List; + typedef std::map AttributeMap; + + Node(); + Node(const String& name, const List& children); + Node(const Node& other); + ~Node(); + + String name() const noexcept; + List children() const noexcept; + +protected: + /** The name of the node. */ + String mName; + /** Children of this node. The node owns its children. */ + List mChildren; +}; + +} /* namespace xml */ +} /* namespace erw */ diff --git a/lib/core/src/xml/Document.cc b/lib/core/src/xml/Document.cc new file mode 100644 index 0000000..53829e9 --- /dev/null +++ b/lib/core/src/xml/Document.cc @@ -0,0 +1,275 @@ +/* XMLParser.cc + * vim: set tw=80: + * Eryn Wells + */ +/** + * Implementation of an XML parser. + */ + +#include +#include + +#include +#include + +#include "XMLParser.hh" + + + +namespace { + +/* + * initLibrary -- + */ +void +initLibrary() +{ + static std::once_flag once; + std::call_once(once, []() { + xmlInitParser(); + }); +} + + +/* + * parseFile -- + */ +XMLNode +parseFile(File&& file) +{ + const size_t kInitialBufferSize = 16; + const size_t kBufferSize = 1024; + + static_assert(kInitialBufferSize < kBufferSize, + "XML parser initial buffer size must be smaller than the " + "total buffer size"); + + initLibrary(); + + char buffer[kBufferSize]; + ssize_t bytesRead = 0; + + bytesRead = file.read(buffer, kInitialBufferSize); + xmlParserCtxtPtr context = xmlCreatePushParserCtxt(nullptr, nullptr, + buffer, bytesRead, + file.path().c_str()); + if (!context) { + // TODO: Throw an appropriate error... + throw 42; + } + + /* + * Read chunks until we're done. Once all data has been read, indicate that + * the parser should terminate by calling xmlParseChunk() with a last + * argument of 1 rather than 0. + */ + while ((bytesRead = file.read(buffer, kBufferSize)) > 0) { + xmlParseChunk(context, buffer, bytesRead, 0); + } + xmlParseChunk(context, buffer, 0, 1); + + bool succeeded = bool(context->wellFormed); + xmlDocPtr document = context->myDoc; + + xmlFreeParserCtxt(context); + + return succeeded ? XMLDocument::UCPtr(new XML2Document(document)) : nullptr; +} + +} + + +namespace erw { + +struct XML2Node + : public XMLNode +{ + XML2Node(xmlNodePtr node); + + virtual ~XML2Node(); + + virtual String name() const noexcept override; + virtual String content() const noexcept override; + virtual XMLNode::AttributeMap attributes() const noexcept override; + +private: + xmlNodePtr mNode; + + /** Make a list of children of the node, excluding text nodes. */ + XMLNode::List childrenOfXML2Node(xmlNodePtr node) const noexcept; +}; + + +/** An XML parser that uses libxml2. */ +struct XML2Document + : public XMLDocument +{ + /** + * Initialize the xml2 library, if needed. This function may be called more + * than once with no adverse affects. + */ + static void initLibrary(); + + /** + * Parse a file into a libxml2 document object. Note: because of move + * semantics related to the file UPtr, the file will be closed after this + * method completes. + * + * @param [in] file The file to parse. + * @return A libxml2 document, or nullptr if the parse fails. + */ + static XMLDocument::UCPtr parseFile(File::UPtr file); + + static XMLDocument::UCPtr parseString(const String& string); + + XML2Document(xmlDocPtr document); + + virtual ~XML2Document(); + + virtual XMLNode::WCPtr root() const noexcept override; + +private: + xmlDocPtr mDocument; +}; + +#pragma mark - erw::XMLDocument + +/* static */ XMLDocument::UCPtr +XMLDocument::parseFile(File::UPtr file) +{ + return XML2Document::parseFile(std::move(file)); +} + + +/* static */ XMLDocument::UCPtr +XMLDocument::parseString(const String& string) +{ + return XML2Document::parseString(string); +} + + +XMLDocument(InFile&& file) + : mRoot() +{ } + + +XMLDocument::~XMLDocument() +{ } + +#pragma mark - erw::XML2Document + + + +/* static */ XMLDocument::UCPtr +XML2Document::parseString(const String& string) +{ + initLibrary(); + xmlDocPtr document = xmlReadMemory(string.c_str(), string.size(), "memory.xml", NULL, 0); + return document ? XMLDocument::UCPtr(new XML2Document(document)) : nullptr; +} + + +XML2Document::XML2Document(xmlDocPtr document) + : XMLDocument(XMLNode::Ptr(new XML2Node(xmlDocGetRootElement(document)))), + mDocument(document) +{ } + + +XML2Document::~XML2Document() +{ + if (mDocument) { + xmlFreeDoc(mDocument); + mDocument = nullptr; + } +} + + +XMLNode::WCPtr +XML2Document::root() + const noexcept +{ + return mRoot; +} + +#pragma mark - XMLNode + +XMLNode::~XMLNode() +{ } + + +XMLNode::XMLNode(XMLNode::List&& children) + : mChildren(children) +{ } + + +XMLNode::WCList +XMLNode::children() + const noexcept +{ + WCList weakChildren; + for (Ptr child : mChildren) { + weakChildren.push_back(WCPtr(child)); + } + return weakChildren; +} + +#pragma mark - XML2Node + +XML2Node::XML2Node(xmlNodePtr node) + : XMLNode(childrenOfXML2Node(node)), + mNode(node) +{ } + + +XML2Node::~XML2Node() +{ } + + +String +XML2Node::name() + const noexcept +{ + return (const char *)mNode->name; +} + + +String +XML2Node::content() + const noexcept +{ + xmlChar *content = xmlNodeGetContent(mNode); + String contentString((const char *)content); + xmlFree(content); + return contentString; +} + + +XMLNode::AttributeMap +XML2Node::attributes() + const noexcept +{ + AttributeMap attrs; + for (xmlAttrPtr attr = mNode->properties; attr && attr->name && attr->children; attr = attr->next) { + xmlChar *value = xmlNodeListGetString(mNode->doc, attr->children, 1); + attrs[(const char *)attr->name] = (const char *)value; + xmlFree(value); + } + return attrs; +} + + +XMLNode::List +XML2Node::childrenOfXML2Node(xmlNodePtr node) + const noexcept +{ + XMLNode::List children; + for (xmlNodePtr c = node->children; c != nullptr; c = c->next) { + if (c->type != XML_ELEMENT_NODE) { + continue; + } + children.push_back(XMLNode::Ptr(new XML2Node(c))); + } + return children; +} + +} /* namespace erw */ diff --git a/lib/core/src/xml/Node.cc b/lib/core/src/xml/Node.cc new file mode 100644 index 0000000..7517de0 --- /dev/null +++ b/lib/core/src/xml/Node.cc @@ -0,0 +1,76 @@ +/* XMLNode.cc + * vim: set tw=80: + * Eryn Wells + */ +/** + * Implementation of a node in an XML tree. + */ + +#include "core/XMLDocument.hh" + +#include +#include + + +namespace erw { +namespace xml { + +/* + * Node::Node -- + */ +Node::Node() + : mName(), + mChildren() +{ } + + +/* + * Node::Node -- + */ +Node::Node(const String& name, + const List& children) + : mName(name), + mChildren(children) +{ } + + +/* + * Node::Node -- + */ +Node::Node(const Node& other) + : mName(other.name), + mChildren(other.children) +{ } + + +/* + * Node::~Node -- + */ +Node::~Node() +{ } + +#pragma mark Properties + +/* + * Node::name -- + */ +String +Node::name() + const +{ + return mName; +} + + +/* + * Node::children -- + */ +List +Node::children() + const +{ + return mChildren; +} + +} /* namespace xml */ +} /* namespace erw */