mirror of https://github.com/SeanOMik/libki.git
dml: Add XML input/output
<RECORD> elements can now be parsed. Tests need to be written for this.
This commit is contained in:
parent
881119e6af
commit
cad6f44208
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include "FieldBase.h"
|
||||
#include "types.h"
|
||||
#include <sstream>
|
||||
|
||||
namespace ki
|
||||
{
|
||||
|
@ -10,6 +11,7 @@ namespace dml
|
|||
class Field final : public FieldBase
|
||||
{
|
||||
friend Record;
|
||||
friend FieldBase;
|
||||
public:
|
||||
virtual ~Field() = default;
|
||||
|
||||
|
@ -23,9 +25,86 @@ namespace dml
|
|||
m_value = value;
|
||||
}
|
||||
|
||||
const char *get_type_name() const final;
|
||||
|
||||
void write_to(std::ostream &ostream) const final;
|
||||
void read_from(std::istream &istream) 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:
|
||||
Field(std::string name, const Record &record)
|
||||
: FieldBase(name, record)
|
||||
|
@ -47,6 +126,26 @@ namespace dml
|
|||
clone->m_value = m_value;
|
||||
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;
|
||||
|
@ -60,5 +159,32 @@ namespace dml
|
|||
typedef Field<FLT> FltField;
|
||||
typedef Field<DBL> DblField;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <vector>
|
||||
#include <map>
|
||||
#include <typeinfo>
|
||||
#include <rapidxml.hpp>
|
||||
#include "../util/Serializable.h"
|
||||
|
||||
namespace ki
|
||||
|
@ -29,6 +30,29 @@ namespace dml
|
|||
{
|
||||
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:
|
||||
std::string m_name;
|
||||
bool m_transferable;
|
||||
|
@ -41,6 +65,12 @@ namespace dml
|
|||
* and value but with a different owner Record.
|
||||
*/
|
||||
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:
|
||||
const Record &m_record;
|
||||
};
|
||||
|
|
|
@ -87,6 +87,25 @@ namespace dml
|
|||
void write_to(std::ostream &ostream) const final;
|
||||
void read_from(std::istream &istream) 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:
|
||||
FieldList m_fields;
|
||||
FieldNameMap m_field_map;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "ki/dml/FieldBase.h"
|
||||
#include "ki/dml/Field.h"
|
||||
|
||||
namespace ki
|
||||
{
|
||||
|
@ -26,5 +27,48 @@ namespace dml
|
|||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,10 @@ namespace dml
|
|||
|
||||
Record::~Record()
|
||||
{
|
||||
m_fields.clear();
|
||||
m_field_map.clear();
|
||||
for (auto it = m_fields.begin(); it != m_fields.end(); ++it)
|
||||
delete *it;
|
||||
m_fields.clear();
|
||||
m_field_map.clear();
|
||||
}
|
||||
|
||||
Record::Record(const Record& record)
|
||||
|
@ -78,5 +78,57 @@ namespace dml
|
|||
m_fields.push_back(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,5 +26,11 @@ namespace dml
|
|||
{
|
||||
return sizeof(BYT);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* BytField::get_type_name() const
|
||||
{
|
||||
return "BYT";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,5 +35,11 @@ namespace dml
|
|||
{
|
||||
return sizeof(DBL);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* DblField::get_type_name() const
|
||||
{
|
||||
return "DBL";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,5 +35,11 @@ namespace dml
|
|||
{
|
||||
return sizeof(FLT);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* FltField::get_type_name() const
|
||||
{
|
||||
return "FLT";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,5 +33,11 @@ namespace dml
|
|||
{
|
||||
return sizeof(GID);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* GidField::get_type_name() const
|
||||
{
|
||||
return "GID";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,5 +33,11 @@ namespace dml
|
|||
{
|
||||
return sizeof(INT);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* IntField::get_type_name() const
|
||||
{
|
||||
return "INT";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,5 +33,11 @@ namespace dml
|
|||
{
|
||||
return sizeof(USHRT);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* ShrtField::get_type_name() const
|
||||
{
|
||||
return "SHRT";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,5 +40,23 @@ namespace dml
|
|||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,5 +26,11 @@ namespace dml
|
|||
{
|
||||
return sizeof(BYT);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* UBytField::get_type_name() const
|
||||
{
|
||||
return "UBYT";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,5 +33,11 @@ namespace dml
|
|||
{
|
||||
return sizeof(UINT);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* UIntField::get_type_name() const
|
||||
{
|
||||
return "UINT";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,5 +33,11 @@ namespace dml
|
|||
{
|
||||
return sizeof(USHRT);
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* UShrtField::get_type_name() const
|
||||
{
|
||||
return "USHRT";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "ki/dml/Field.h"
|
||||
#include "ki/util/ValueBytes.h"
|
||||
#include <algorithm>
|
||||
#include <codecvt>
|
||||
|
||||
namespace ki
|
||||
{
|
||||
|
@ -46,5 +47,37 @@ namespace dml
|
|||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue