diff --git a/include/ki/dml/Field.h b/include/ki/dml/Field.h index 901c42b..9d704b3 100644 --- a/include/ki/dml/Field.h +++ b/include/ki/dml/Field.h @@ -1,6 +1,7 @@ #pragma once #include "FieldBase.h" #include "types.h" +#include 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: Value + * + * 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()) + { + auto *real_other = dynamic_cast *>(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 BytField; @@ -60,5 +159,32 @@ namespace dml typedef Field FltField; typedef Field DblField; typedef Field GidField; + + template + std::string Field::get_value_string() const + { + std::ostringstream oss; + oss << m_value; + return oss.str(); + } + + template + void Field::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); } } diff --git a/include/ki/dml/FieldBase.h b/include/ki/dml/FieldBase.h index a9e789f..9451a00 100644 --- a/include/ki/dml/FieldBase.h +++ b/include/ki/dml/FieldBase.h @@ -3,6 +3,7 @@ #include #include #include +#include #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: Value + * + * 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; }; diff --git a/include/ki/dml/Record.h b/include/ki/dml/Record.h index f48695d..b3c412a 100644 --- a/include/ki/dml/Record.h +++ b/include/ki/dml/Record.h @@ -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; diff --git a/src/dml/FieldBase.cpp b/src/dml/FieldBase.cpp index 2593bdc..3155fd1 100644 --- a/src/dml/FieldBase.cpp +++ b/src/dml/FieldBase.cpp @@ -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; + } } } diff --git a/src/dml/Record.cpp b/src/dml/Record.cpp index 28085ff..8b204da 100644 --- a/src/dml/Record.cpp +++ b/src/dml/Record.cpp @@ -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 element. + const std::string node_name = node->name(); + if (node_name != "RECORD") + { + // TODO: Exceptions + return; + } + + // Every child node inside a 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); + } + } } } diff --git a/src/dml/types/BytField.cpp b/src/dml/types/BytField.cpp index 5a6f7a4..6c73577 100644 --- a/src/dml/types/BytField.cpp +++ b/src/dml/types/BytField.cpp @@ -26,5 +26,11 @@ namespace dml { return sizeof(BYT); } + + template <> + const char* BytField::get_type_name() const + { + return "BYT"; + } } } \ No newline at end of file diff --git a/src/dml/types/DblField.cpp b/src/dml/types/DblField.cpp index 86e389a..3b181ce 100644 --- a/src/dml/types/DblField.cpp +++ b/src/dml/types/DblField.cpp @@ -35,5 +35,11 @@ namespace dml { return sizeof(DBL); } + + template <> + const char* DblField::get_type_name() const + { + return "DBL"; + } } } diff --git a/src/dml/types/FltField.cpp b/src/dml/types/FltField.cpp index 59ae031..5a304d9 100644 --- a/src/dml/types/FltField.cpp +++ b/src/dml/types/FltField.cpp @@ -35,5 +35,11 @@ namespace dml { return sizeof(FLT); } + + template <> + const char* FltField::get_type_name() const + { + return "FLT"; + } } } diff --git a/src/dml/types/GidField.cpp b/src/dml/types/GidField.cpp index d38a69f..600f9c8 100644 --- a/src/dml/types/GidField.cpp +++ b/src/dml/types/GidField.cpp @@ -33,5 +33,11 @@ namespace dml { return sizeof(GID); } + + template <> + const char* GidField::get_type_name() const + { + return "GID"; + } } } diff --git a/src/dml/types/IntField.cpp b/src/dml/types/IntField.cpp index aa4995e..f664ed0 100644 --- a/src/dml/types/IntField.cpp +++ b/src/dml/types/IntField.cpp @@ -33,5 +33,11 @@ namespace dml { return sizeof(INT); } + + template <> + const char* IntField::get_type_name() const + { + return "INT"; + } } } diff --git a/src/dml/types/ShrtField.cpp b/src/dml/types/ShrtField.cpp index 17bcb60..b8c0054 100644 --- a/src/dml/types/ShrtField.cpp +++ b/src/dml/types/ShrtField.cpp @@ -33,5 +33,11 @@ namespace dml { return sizeof(USHRT); } + + template <> + const char* ShrtField::get_type_name() const + { + return "SHRT"; + } } } diff --git a/src/dml/types/StrField.cpp b/src/dml/types/StrField.cpp index 88e22dc..01e249c 100644 --- a/src/dml/types/StrField.cpp +++ b/src/dml/types/StrField.cpp @@ -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; + } } } diff --git a/src/dml/types/UBytField.cpp b/src/dml/types/UBytField.cpp index f1e3166..72fb537 100644 --- a/src/dml/types/UBytField.cpp +++ b/src/dml/types/UBytField.cpp @@ -26,5 +26,11 @@ namespace dml { return sizeof(BYT); } + + template <> + const char* UBytField::get_type_name() const + { + return "UBYT"; + } } } diff --git a/src/dml/types/UIntField.cpp b/src/dml/types/UIntField.cpp index 40326d0..ad5f05c 100644 --- a/src/dml/types/UIntField.cpp +++ b/src/dml/types/UIntField.cpp @@ -33,5 +33,11 @@ namespace dml { return sizeof(UINT); } + + template <> + const char* UIntField::get_type_name() const + { + return "UINT"; + } } } diff --git a/src/dml/types/UShrtField.cpp b/src/dml/types/UShrtField.cpp index 88aabfa..f0fb73d 100644 --- a/src/dml/types/UShrtField.cpp +++ b/src/dml/types/UShrtField.cpp @@ -33,5 +33,11 @@ namespace dml { return sizeof(USHRT); } + + template <> + const char* UShrtField::get_type_name() const + { + return "USHRT"; + } } } diff --git a/src/dml/types/WStrField.cpp b/src/dml/types/WStrField.cpp index 3cb5dad..4d44e75 100644 --- a/src/dml/types/WStrField.cpp +++ b/src/dml/types/WStrField.cpp @@ -1,6 +1,7 @@ #include "ki/dml/Field.h" #include "ki/util/ValueBytes.h" #include +#include 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, int16_t> convert; + auto p = reinterpret_cast(m_value.data()); + STR temp = convert.to_bytes(p, p + m_value.size()); +#else + std::wstring_convert, 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, int16_t> convert; + m_value = reinterpret_cast(convert.from_bytes(value).data()); +#else + std::wstring_convert, char16_t> convert; + m_value = convert.from_bytes(value); +#endif + } } }