dml: Add XML input/output

<RECORD> elements can now be parsed.
Tests need to be written for this.
This commit is contained in:
Joshua Scott 2018-03-31 20:22:42 +01:00
parent 881119e6af
commit cad6f44208
16 changed files with 378 additions and 2 deletions

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "FieldBase.h" #include "FieldBase.h"
#include "types.h" #include "types.h"
#include <sstream>
namespace ki namespace ki
{ {
@ -10,6 +11,7 @@ namespace dml
class Field final : public FieldBase class Field final : public FieldBase
{ {
friend Record; friend Record;
friend FieldBase;
public: public:
virtual ~Field() = default; virtual ~Field() = default;
@ -23,9 +25,86 @@ namespace dml
m_value = value; m_value = value;
} }
const char *get_type_name() const final;
void write_to(std::ostream &ostream) const final; void write_to(std::ostream &ostream) const final;
void read_from(std::istream &istream) final; void read_from(std::istream &istream) final;
size_t get_size() const final; size_t get_size() const final;
/**
* Creates an XML node from this field's data.
*
* The document is only used to allocate necessary resources, and
* so the returned node has not been appended to the document.
*/
rapidxml::xml_node<> *as_xml(rapidxml::xml_document<> &doc) const final
{
// Create the node:
// Copy our current name and value into buffers that are
// lifetime-dependant on the xml_document, rather than this Field.
char *name_buffer = doc.allocate_string(m_name.c_str(), 0);
char *value_buffer = doc.allocate_string(get_value_string().c_str(), 0);
auto *node = doc.allocate_node(
rapidxml::node_type::node_element, name_buffer, value_buffer);
// Create the TYPE attribute
char *type_attr_value_buffer = doc.allocate_string(get_type_name(), 0);
auto *type_attr = doc.allocate_attribute("TYPE", type_attr_value_buffer);
node->append_attribute(type_attr);
// If we're not transferable, set NOXFER to TRUE
// NOXFER defaults to FALSE, so we don't need to write it otherwise.
if (!m_transferable)
{
auto *noxfer_attr = doc.allocate_attribute("NOXFER", "TRUE");
node->append_attribute(noxfer_attr);
}
return node;
}
/**
* Loads data from an XML Field node into this field.
* Example: <FieldName TYPE="STR">Value</FieldName>
*
* If the field in the XML data does not have the same type
* as this field, then an exception is thrown.
*/
void from_xml(const rapidxml::xml_node<> *node) final
{
// Use the name of the node as the field name and,
// default transferable to TRUE.
m_name = node->name();
m_transferable = true;
for (auto *attr = node->first_attribute();
attr; attr = attr->next_attribute())
{
const std::string name = attr->name();
if (name != "TYPE")
{
const std::string value = attr->value();
if (value != get_type_name())
{
// TODO: Exceptions
}
}
else if (name == "NOXFER")
{
const std::string value = attr->value();
m_transferable = value != "TRUE";
}
else
{
// TODO: Exceptions
}
}
const std::string value = node->value();
if (!value.empty())
set_value_from_string(value);
}
protected: protected:
Field(std::string name, const Record &record) Field(std::string name, const Record &record)
: FieldBase(name, record) : FieldBase(name, record)
@ -47,6 +126,26 @@ namespace dml
clone->m_value = m_value; clone->m_value = m_value;
return clone; return clone;
} }
/**
* Copies the value of another Field into this one
* if the types are the same.
*/
void set_value(FieldBase *other) final
{
if (other->is_type<ValueT>())
{
auto *real_other = dynamic_cast<Field<ValueT> *>(other);
set_value(real_other->get_value());
}
else
{
// TODO: Exceptions
}
}
std::string get_value_string() const;
void set_value_from_string(std::string value);
}; };
typedef Field<BYT> BytField; typedef Field<BYT> BytField;
@ -60,5 +159,32 @@ namespace dml
typedef Field<FLT> FltField; typedef Field<FLT> FltField;
typedef Field<DBL> DblField; typedef Field<DBL> DblField;
typedef Field<GID> GidField; typedef Field<GID> GidField;
template <typename ValueT>
std::string Field<ValueT>::get_value_string() const
{
std::ostringstream oss;
oss << m_value;
return oss.str();
}
template <typename ValueT>
void Field<ValueT>::set_value_from_string(const std::string value)
{
std::istringstream iss(value);
iss >> m_value;
}
template <>
std::string StrField::get_value_string() const;
template <>
void StrField::set_value_from_string(std::string value);
template <>
std::string WStrField::get_value_string() const;
template <>
void WStrField::set_value_from_string(std::string value);
} }
} }

View File

@ -3,6 +3,7 @@
#include <vector> #include <vector>
#include <map> #include <map>
#include <typeinfo> #include <typeinfo>
#include <rapidxml.hpp>
#include "../util/Serializable.h" #include "../util/Serializable.h"
namespace ki namespace ki
@ -29,6 +30,29 @@ namespace dml
{ {
return (typeid(ValueT).hash_code() == m_type_hash); return (typeid(ValueT).hash_code() == m_type_hash);
} }
virtual const char *get_type_name() const = 0;
/**
* Creates an XML node from this field's data.
*
* The document is only used to allocate necessary resources, and
* so the returned node has not been appended to the document.
*/
virtual rapidxml::xml_node<> *as_xml(rapidxml::xml_document<> &doc) const = 0;
/**
* Loads data from an XML Field node into this field.
* Example: <FieldName TYPE="STR">Value</FieldName>
*
* If the field in the XML data does not have the same type
* as this field, then an exception is thrown.
*/
virtual void from_xml(const rapidxml::xml_node<> *node) = 0;
/**
* Creates a new Field from XML data.
*/
static FieldBase *create_from_xml(const Record& record, const rapidxml::xml_node<> *node);
protected: protected:
std::string m_name; std::string m_name;
bool m_transferable; bool m_transferable;
@ -41,6 +65,12 @@ namespace dml
* and value but with a different owner Record. * and value but with a different owner Record.
*/ */
virtual FieldBase *clone(const Record &record) const = 0; virtual FieldBase *clone(const Record &record) const = 0;
/**
* Copies the value of another Field into this one
* if the types are the same.
*/
virtual void set_value(FieldBase *other) = 0;
private: private:
const Record &m_record; const Record &m_record;
}; };

View File

@ -87,6 +87,25 @@ namespace dml
void write_to(std::ostream &ostream) const final; void write_to(std::ostream &ostream) const final;
void read_from(std::istream &istream) final; void read_from(std::istream &istream) final;
size_t get_size() const final; size_t get_size() const final;
/**
* Creates an XML node from this record's data.
*
* The document is only used to allocate necessary resources, and
* so the returned node has not been appended to the document.
*/
rapidxml::xml_node<> *as_xml(rapidxml::xml_document<> &doc) const;
/**
* Loads data from an XML Record node into this record.
*
* If a Field already exists, and the type is the same,
* then the new value is copied into the original Field.
* If the type is not the same, then the original Field
* is deleted, and replaced with the new one.
*/
void from_xml(rapidxml::xml_node<> *node);
private: private:
FieldList m_fields; FieldList m_fields;
FieldNameMap m_field_map; FieldNameMap m_field_map;

View File

@ -1,4 +1,5 @@
#include "ki/dml/FieldBase.h" #include "ki/dml/FieldBase.h"
#include "ki/dml/Field.h"
namespace ki namespace ki
{ {
@ -26,5 +27,48 @@ namespace dml
{ {
return m_transferable; return m_transferable;
} }
FieldBase* FieldBase::create_from_xml(const Record& record, const rapidxml::xml_node<>* node)
{
auto *type_attr = node->first_attribute("TYPE");
if (!type_attr)
{
// TODO: Exceptions
return nullptr;
}
const std::string type = type_attr->value();
FieldBase *field;
if (type == "BYT")
field = new BytField("", record);
else if (type == "UBYT")
field = new UBytField("", record);
else if (type == "SHRT")
field = new ShrtField("", record);
else if (type == "USHRT")
field = new UShrtField("", record);
else if (type == "INT")
field = new IntField("", record);
else if (type == "UINT")
field = new UIntField("", record);
else if (type == "STR")
field = new StrField("", record);
else if (type == "WSTR")
field = new WStrField("", record);
else if (type == "FLT")
field = new FltField("", record);
else if (type == "DBL")
field = new DblField("", record);
else if (type == "GID")
field = new GidField("", record);
else
{
// TODO: Exceptions
return nullptr;
}
field->from_xml(node);
return field;
}
} }
} }

View File

@ -12,10 +12,10 @@ namespace dml
Record::~Record() Record::~Record()
{ {
m_fields.clear();
m_field_map.clear();
for (auto it = m_fields.begin(); it != m_fields.end(); ++it) for (auto it = m_fields.begin(); it != m_fields.end(); ++it)
delete *it; delete *it;
m_fields.clear();
m_field_map.clear();
} }
Record::Record(const Record& record) Record::Record(const Record& record)
@ -78,5 +78,57 @@ namespace dml
m_fields.push_back(field); m_fields.push_back(field);
m_field_map.insert({ field->get_name(), field }); m_field_map.insert({ field->get_name(), field });
} }
rapidxml::xml_node<> *Record::as_xml(rapidxml::xml_document<> &doc) const
{
auto *node = doc.allocate_node(rapidxml::node_type::node_element, "RECORD");
for (auto it = m_fields.begin(); it != m_fields.end(); ++it)
node->append_node((*it)->as_xml(doc));
return node;
}
void Record::from_xml(rapidxml::xml_node<> *node)
{
// Make sure that we've been passed a <RECORD> element.
const std::string node_name = node->name();
if (node_name != "RECORD")
{
// TODO: Exceptions
return;
}
// Every child node inside a <RECORD> element is a Field.
for (auto *field_node = node->first_node();
field_node; field_node = field_node->next_sibling())
{
FieldBase *field = FieldBase::create_from_xml(*this, field_node);
if (has_field(field->get_name()))
{
// Is the old field the same type as the one created from
// the XML data?
FieldBase *old_field = m_field_map.at(field->get_name());
if (field->m_type_hash == old_field->m_type_hash)
{
// Set the value of the old field to the value of the new
// one.
old_field->set_value(field);
delete field;
}
else
{
// Since the types are different, we can't set the value
// of the old field to the value of the new one so,
// replace the old field with this new one instead.
ptrdiff_t index = std::find(
m_fields.begin(), m_fields.end(), old_field) - m_fields.begin();
m_fields[index] = field;
m_field_map[field->get_name()] = field;
delete old_field;
}
}
else
add_field(field);
}
}
} }
} }

View File

@ -26,5 +26,11 @@ namespace dml
{ {
return sizeof(BYT); return sizeof(BYT);
} }
template <>
const char* BytField::get_type_name() const
{
return "BYT";
}
} }
} }

View File

@ -35,5 +35,11 @@ namespace dml
{ {
return sizeof(DBL); return sizeof(DBL);
} }
template <>
const char* DblField::get_type_name() const
{
return "DBL";
}
} }
} }

View File

@ -35,5 +35,11 @@ namespace dml
{ {
return sizeof(FLT); return sizeof(FLT);
} }
template <>
const char* FltField::get_type_name() const
{
return "FLT";
}
} }
} }

View File

@ -33,5 +33,11 @@ namespace dml
{ {
return sizeof(GID); return sizeof(GID);
} }
template <>
const char* GidField::get_type_name() const
{
return "GID";
}
} }
} }

View File

@ -33,5 +33,11 @@ namespace dml
{ {
return sizeof(INT); return sizeof(INT);
} }
template <>
const char* IntField::get_type_name() const
{
return "INT";
}
} }
} }

View File

@ -33,5 +33,11 @@ namespace dml
{ {
return sizeof(USHRT); return sizeof(USHRT);
} }
template <>
const char* ShrtField::get_type_name() const
{
return "SHRT";
}
} }
} }

View File

@ -40,5 +40,23 @@ namespace dml
{ {
return sizeof(USHRT) + m_value.length(); return sizeof(USHRT) + m_value.length();
} }
template <>
const char* StrField::get_type_name() const
{
return "STR";
}
template <>
std::string StrField::get_value_string() const
{
return m_value;
}
template <>
void StrField::set_value_from_string(std::string value)
{
m_value = value;
}
} }
} }

View File

@ -26,5 +26,11 @@ namespace dml
{ {
return sizeof(BYT); return sizeof(BYT);
} }
template <>
const char* UBytField::get_type_name() const
{
return "UBYT";
}
} }
} }

View File

@ -33,5 +33,11 @@ namespace dml
{ {
return sizeof(UINT); return sizeof(UINT);
} }
template <>
const char* UIntField::get_type_name() const
{
return "UINT";
}
} }
} }

View File

@ -33,5 +33,11 @@ namespace dml
{ {
return sizeof(USHRT); return sizeof(USHRT);
} }
template <>
const char* UShrtField::get_type_name() const
{
return "USHRT";
}
} }
} }

View File

@ -1,6 +1,7 @@
#include "ki/dml/Field.h" #include "ki/dml/Field.h"
#include "ki/util/ValueBytes.h" #include "ki/util/ValueBytes.h"
#include <algorithm> #include <algorithm>
#include <codecvt>
namespace ki namespace ki
{ {
@ -46,5 +47,37 @@ namespace dml
{ {
return sizeof(USHRT) + (m_value.length() * sizeof(char16_t)); return sizeof(USHRT) + (m_value.length() * sizeof(char16_t));
} }
template <>
const char* WStrField::get_type_name() const
{
return "WSTR";
}
template <>
std::string WStrField::get_value_string() const
{
#if _MSC_VER >= 1900
std::wstring_convert<std::codecvt_utf8_utf16<int16_t>, int16_t> convert;
auto p = reinterpret_cast<const int16_t *>(m_value.data());
STR temp = convert.to_bytes(p, p + m_value.size());
#else
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
STR temp = convert.to_bytes(m_value);
#endif
return temp;
}
template <>
void WStrField::set_value_from_string(std::string value)
{
#if _MSC_VER >= 1900
std::wstring_convert<std::codecvt_utf8_utf16<int16_t>, int16_t> convert;
m_value = reinterpret_cast<const char16_t *>(convert.from_bytes(value).data());
#else
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
m_value = convert.from_bytes(value);
#endif
}
} }
} }