Compare commits
5 commits
Author | SHA1 | Date | |
---|---|---|---|
283a5648a8 | |||
8641748274 | |||
134f2aa3fb | |||
af30c9e597 | |||
9bfd637a19 |
8 changed files with 733 additions and 0 deletions
4
lib/core/SConscript
Normal file
4
lib/core/SConscript
Normal file
|
@ -0,0 +1,4 @@
|
|||
# SConscript
|
||||
# Eryn Wells <eryn@erynwells.me>
|
||||
|
||||
Library('erw', ['src/File.cc'])
|
112
lib/core/include/core/File.hh
Normal file
112
lib/core/include/core/File.hh
Normal file
|
@ -0,0 +1,112 @@
|
|||
/* File.hh
|
||||
* vim: set tw=80:
|
||||
* Eryn Wells <eryn@erynwells.me>
|
||||
*/
|
||||
/**
|
||||
* File interface.
|
||||
*/
|
||||
|
||||
#include <bitset>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#include "String.hh"
|
||||
|
||||
|
||||
namespace erw {
|
||||
|
||||
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();
|
||||
|
||||
String path() const noexcept;
|
||||
|
||||
/** 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:
|
||||
const String mPath;
|
||||
|
||||
/** Convert a File::Mode to an iostream openmode bitset. */
|
||||
virtual std::ios_base::openmode modeToIOSMode(Mode mode);
|
||||
|
||||
File(const String& path);
|
||||
};
|
||||
|
||||
|
||||
/** 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);
|
||||
|
||||
String path() const;
|
||||
|
||||
/** 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:
|
||||
String mPath;
|
||||
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 erw */
|
16
lib/core/include/core/String.hh
Normal file
16
lib/core/include/core/String.hh
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* String.hh
|
||||
* vim: set tw=80:
|
||||
* Eryn Wells <eryn@erynwells.me>
|
||||
*/
|
||||
/**
|
||||
* Strings are fun.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace erw {
|
||||
|
||||
typedef std::string String;
|
||||
|
||||
} /* namespace erw */
|
47
lib/core/include/core/xml/Document.hh
Normal file
47
lib/core/include/core/xml/Document.hh
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* XMLDocument.hh
|
||||
* vim: set tw=80:
|
||||
* Eryn Wells <eryn@erynwells.me>
|
||||
*/
|
||||
/**
|
||||
* An XML document and related structures.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#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 */
|
41
lib/core/include/core/xml/Node.hh
Normal file
41
lib/core/include/core/xml/Node.hh
Normal file
|
@ -0,0 +1,41 @@
|
|||
/* Node.hh
|
||||
* vim: set tw=80:
|
||||
* Eryn Wells <eryn@erynwells.me>
|
||||
*/
|
||||
/**
|
||||
* An XML node.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "String.hh"
|
||||
|
||||
namespace erw {
|
||||
namespace xml {
|
||||
|
||||
/** A node in an XML tree. */
|
||||
struct Node
|
||||
{
|
||||
typedef std::vector<Node> List;
|
||||
typedef std::map<String, String> 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 */
|
162
lib/core/src/File.cc
Normal file
162
lib/core/src/File.cc
Normal file
|
@ -0,0 +1,162 @@
|
|||
/* File.cc
|
||||
* vim: set tw=80:
|
||||
* Eryn Wells <eryn@erynwells.me>
|
||||
*/
|
||||
/**
|
||||
* Implementation of file handling.
|
||||
*/
|
||||
|
||||
#include "core/File.hh"
|
||||
|
||||
|
||||
namespace erw {
|
||||
|
||||
#pragma mark - File
|
||||
|
||||
/*
|
||||
* File::File --
|
||||
*/
|
||||
File::File(const String& path)
|
||||
: mPath(path)
|
||||
{ }
|
||||
|
||||
|
||||
/*
|
||||
* File::~File --
|
||||
*/
|
||||
File::~File()
|
||||
{ }
|
||||
|
||||
|
||||
/*
|
||||
* File::path --
|
||||
*/
|
||||
String
|
||||
File::path()
|
||||
const noexcept
|
||||
{
|
||||
return mPath;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* File::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)
|
||||
: File(path),
|
||||
mStream(path, modeToIOSMode(mode))
|
||||
{ }
|
||||
|
||||
|
||||
/*
|
||||
* InFile::InFile --
|
||||
*/
|
||||
InFile::InFile(InFile&& other)
|
||||
: File(other.mPath),
|
||||
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 erw */
|
275
lib/core/src/xml/Document.cc
Normal file
275
lib/core/src/xml/Document.cc
Normal file
|
@ -0,0 +1,275 @@
|
|||
/* XMLParser.cc
|
||||
* vim: set tw=80:
|
||||
* Eryn Wells <eryn@erynwells.me>
|
||||
*/
|
||||
/**
|
||||
* Implementation of an XML parser.
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/tree.h>
|
||||
|
||||
#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 */
|
76
lib/core/src/xml/Node.cc
Normal file
76
lib/core/src/xml/Node.cc
Normal file
|
@ -0,0 +1,76 @@
|
|||
/* XMLNode.cc
|
||||
* vim: set tw=80:
|
||||
* Eryn Wells <eryn@erynwells.me>
|
||||
*/
|
||||
/**
|
||||
* Implementation of a node in an XML tree.
|
||||
*/
|
||||
|
||||
#include "core/XMLDocument.hh"
|
||||
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/tree.h>
|
||||
|
||||
|
||||
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 */
|
Loading…
Add table
Add a link
Reference in a new issue