From 69189e0fbaa6f953662b1fd2f050fdef93f0f57f Mon Sep 17 00:00:00 2001 From: Joshua Scott Date: Mon, 7 Jan 2019 22:49:12 +0000 Subject: [PATCH] serialization: Add FileSerializer and XmlSerializer --- include/ki/pclass/Casters.h | 149 +++++- include/ki/pclass/EnumType.h | 2 +- include/ki/pclass/PrimitiveType.h | 4 +- include/ki/pclass/Property.h | 2 +- include/ki/pclass/TypeSystem.h | 16 +- include/ki/pclass/Value.h | 2 + include/ki/serialization/BinarySerializer.h | 34 +- include/ki/serialization/FileSerializer.h | 32 ++ include/ki/serialization/JsonSerializer.h | 12 +- include/ki/serialization/XmlSerializer.h | 36 ++ src/pclass/EnumType.cpp | 2 +- src/pclass/Property.cpp | 2 +- src/pclass/TypeSystem.cpp | 7 + src/serialization/CMakeLists.txt | 2 + src/serialization/FileSerializer.cpp | 133 ++++++ src/serialization/JsonSerializer.cpp | 2 +- src/serialization/XmlSerializer.cpp | 241 ++++++++++ test/samples/serialization/file.bin | Bin 1285 -> 1289 bytes test/samples/serialization/file.json | 2 +- test/samples/serialization/file.xml | 257 ++++++++++ .../samples/serialization/file_compressed.bin | Bin 573 -> 577 bytes test/samples/serialization/regular.xml | 256 ++++++++++ test/src/unit-serialization.cpp | 371 +++++++++++---- third_party/rapidxml_print.hpp | 448 ++++++++++++++++++ third_party/rapidxml_utils.hpp | 122 +++++ 25 files changed, 2013 insertions(+), 121 deletions(-) create mode 100644 include/ki/serialization/FileSerializer.h create mode 100644 include/ki/serialization/XmlSerializer.h create mode 100644 src/serialization/FileSerializer.cpp create mode 100644 src/serialization/XmlSerializer.cpp create mode 100644 test/samples/serialization/file.xml create mode 100644 test/samples/serialization/regular.xml create mode 100644 third_party/rapidxml_print.hpp create mode 100644 third_party/rapidxml_utils.hpp diff --git a/include/ki/pclass/Casters.h b/include/ki/pclass/Casters.h index 0cfbaeb..73d4851 100644 --- a/include/ki/pclass/Casters.h +++ b/include/ki/pclass/Casters.h @@ -5,6 +5,9 @@ #include "ki/pclass/Value.h" #include "ki/util/BitTypes.h" #include "ki/pclass/EnumType.h" +#include +#include +#include namespace ki { @@ -138,6 +141,11 @@ namespace detail { using type = uint16_t; }; + template <> + struct string_cast_t + { + using type = int16_t; + }; /** * Enums should be written as 32-bit integers. @@ -151,12 +159,45 @@ namespace detail using type = enum_value_t; }; + /** + * A utility to enforce that a template parameter has a minimum + * value. + */ + template + struct minimum + { + static constexpr uint8_t value = N; + }; + + template + struct minimum< + N, MinN, + typename std::enable_if::type + > + { + static constexpr uint8_t value = MinN; + }; + + /** + * BitIntegers should be written as the most suitable integer type + * based on signedness and number of bits. + */ + template + struct string_cast_t> + { + using type = typename BitInteger< + minimum::value, Unsigned>::type; + }; + /** * Caster implementation for casting any type to string * via std::ostringstream. */ template - struct value_caster + struct value_caster< + SrcT, std::string, + typename std::enable_if::value>::type + > : value_caster_impl { std::string cast_value(const SrcT &value) const override @@ -169,6 +210,90 @@ namespace detail } }; + /** + * Caster implementation for casting floating point integers + * to string via std::ostringstream. + */ + template + struct value_caster< + SrcT, std::string, + typename std::enable_if::value>::type + > + : value_caster_impl + { + std::string cast_value(const SrcT &value) const override + { + std::ostringstream oss; + oss << std::setprecision(std::numeric_limits::max_digits10) << value; + return oss.str(); + } + }; + + /** + * Caster implementation for casting std::string to std::u16string + * via std::wstring_convert. + */ + template <> + struct value_caster + : value_caster_impl + { + std::u16string cast_value(const std::string &value) const override + { +#if _MSC_VER >= 1900 + std::wstring_convert, int16_t> convert; + auto converted_string = convert.from_bytes(value.data()); + return std::u16string( + reinterpret_cast(converted_string.data()), + converted_string.size() + ); +#else + std::wstring_convert, char16_t> convert; + return convert.from_bytes(value.data()); +#endif + } + }; + + /** + * Caster implementation for casting std::u16string to std::string + * via std::wstring_convert. + */ + template <> + struct value_caster + : value_caster_impl + { + std::string cast_value(const std::u16string &value) const override + { +#if _MSC_VER >= 1900 + std::wstring_convert, int16_t> convert; + auto *p = reinterpret_cast(value.data()); + return convert.to_bytes(p, p + value.size()); +#else + std::wstring_convert, char16_t> convert; + return convert.to_bytes(value); +#endif + } + }; + + /** + * Caster implementation for casting from string to any type + * via std::istringstream. + */ + template + struct value_caster< + std::string, DestT, + typename std::enable_if::value>::type + > + : value_caster_impl + { + DestT cast_value(const std::string &value) const override + { + typename string_cast_t::type casted_value; + std::istringstream iss(value); + iss >> casted_value; + return static_cast(casted_value); + } + }; + /** * Caster implementation for casting from json to any * primitive type. @@ -249,7 +374,15 @@ namespace detail template struct caster_declarer { - static void declare() {} + static void declare() + { + // These casters are required for JsonSerializer and + // XmlSerializer to work. + ValueCaster::declare(); + ValueCaster::declare(); + ValueCaster::declare(); + ValueCaster::declare(); + } }; /** @@ -280,7 +413,9 @@ namespace detail ValueCaster::declare(); ValueCaster::declare(); ValueCaster::declare(); + ValueCaster::declare(); ValueCaster::declare(); + ValueCaster::declare(); } }; @@ -312,7 +447,9 @@ namespace detail ValueCaster::declare(); ValueCaster::declare(); ValueCaster::declare(); + ValueCaster::declare(); ValueCaster::declare(); + ValueCaster::declare(); } }; @@ -342,7 +479,9 @@ namespace detail ValueCaster::declare(); ValueCaster::declare(); ValueCaster::declare(); + ValueCaster::declare(); ValueCaster::declare(); + ValueCaster::declare(); } }; @@ -355,8 +494,9 @@ namespace detail { static void declare() { - // TODO: Casting string to u16string + ValueCaster::declare(); ValueCaster::declare(); + ValueCaster::declare(); } }; @@ -369,8 +509,9 @@ namespace detail { static void declare() { - // TODO: Casting u16string to string + ValueCaster::declare(); ValueCaster::declare(); + ValueCaster::declare(); } }; } diff --git a/include/ki/pclass/EnumType.h b/include/ki/pclass/EnumType.h index cbd40aa..cb8e284 100644 --- a/include/ki/pclass/EnumType.h +++ b/include/ki/pclass/EnumType.h @@ -23,7 +23,7 @@ namespace pclass public: explicit Element(const std::string &name, enum_value_t value); - std::string get_name() const; + const std::string &get_name() const; enum_value_t get_value() const; private: diff --git a/include/ki/pclass/PrimitiveType.h b/include/ki/pclass/PrimitiveType.h index 2109a2c..ad29185 100644 --- a/include/ki/pclass/PrimitiveType.h +++ b/include/ki/pclass/PrimitiveType.h @@ -157,10 +157,10 @@ namespace pclass { try { - Value deref_value = value.dereference(); + Value casted_value = value.as(); detail::primitive_type_helper::write_to( stream, - deref_value.get() + casted_value.get() ); } catch (runtime_error &e) diff --git a/include/ki/pclass/Property.h b/include/ki/pclass/Property.h index a1a1954..622d1a5 100644 --- a/include/ki/pclass/Property.h +++ b/include/ki/pclass/Property.h @@ -30,7 +30,7 @@ namespace pclass virtual ~IProperty() = default; - std::string get_name() const; + const std::string &get_name() const; hash_t get_name_hash() const; hash_t get_full_hash() const; const Type &get_type() const; diff --git a/include/ki/pclass/TypeSystem.h b/include/ki/pclass/TypeSystem.h index 4d15644..262fb39 100644 --- a/include/ki/pclass/TypeSystem.h +++ b/include/ki/pclass/TypeSystem.h @@ -21,6 +21,7 @@ namespace pclass { public: explicit TypeSystem(std::unique_ptr &hash_calculator); + virtual ~TypeSystem() = default; /** * @returns The IHashCalculator instance this TypeSystem uses to calculate @@ -64,8 +65,6 @@ namespace pclass PrimitiveType &define_primitive(const std::string &name) { detail::caster_declarer::declare(); - ValueCaster::declare(); - auto *type = new PrimitiveType(name, *this); define_type(std::unique_ptr( dynamic_cast(type) @@ -116,8 +115,6 @@ namespace pclass CppEnumType &define_enum(const std::string &name) { detail::caster_declarer::declare(); - ValueCaster::declare(); - auto *type = new CppEnumType(name, *this); define_type(std::unique_ptr( dynamic_cast(type) @@ -125,6 +122,12 @@ namespace pclass return *type; } + /** + * Create a new instance of a PropertyClass-derived class. + * @param[in] name The name of the class type to instantiate. + */ + virtual std::unique_ptr instantiate(const std::string &name) const; + /** * Create a new instance of a PropertyClass-derived class. * @tparam ClassT The expected compile-time class. @@ -133,8 +136,7 @@ namespace pclass template std::unique_ptr instantiate(const std::string &name) const { - const auto &type = get_type(name); - auto object = type.instantiate(); + auto object = instantiate(name); return std::unique_ptr( dynamic_cast(object.release()) ); @@ -153,8 +155,6 @@ namespace pclass ClassType &define_class( const std::string &name, const Type *base_class) { - detail::caster_declarer::declare(); - // If the caller does not specify a base class, automatically make // ki::pclass::PropertyClass the base class (if it has been defined) if (base_class == nullptr && has_type("class PropertyClass")) diff --git a/include/ki/pclass/Value.h b/include/ki/pclass/Value.h index 642e621..484ce76 100644 --- a/include/ki/pclass/Value.h +++ b/include/ki/pclass/Value.h @@ -222,6 +222,8 @@ namespace pclass void add_caster() { const auto dest_type_hash = typeid(DestT).hash_code(); + if (m_casts.find(dest_type_hash) != m_casts.end()) + delete m_casts[dest_type_hash]; m_casts[dest_type_hash] = new detail::value_caster(); } }; diff --git a/include/ki/serialization/BinarySerializer.h b/include/ki/serialization/BinarySerializer.h index 63cc476..2177483 100644 --- a/include/ki/serialization/BinarySerializer.h +++ b/include/ki/serialization/BinarySerializer.h @@ -11,7 +11,8 @@ namespace ki namespace serialization { /** - * TODO: Documentation + * A serializer class used to serialize PropertyClass instances into their binary + * representations, and deserialize binary data into PropertyClass instances. */ class BinarySerializer { @@ -30,47 +31,50 @@ namespace serialization WRITE_SERIALIZER_FLAGS = 0x01, /** - * When enabled, the serialized data (after the flags, if present) is compressed. + * When enabled, the serialized data (after the flags, if present) is + * potentially compressed. This is based on an added compression header. */ COMPRESSED = 0x08 }; /** * Construct a new binary serializer. - * @param type_system The TypeSystem instance to acquire Type information from. - * @param is_file Determines whether or not to write type sizes, and property headers. - * @param flags Determines how serialized data is formatted. + * @param[in] type_system The TypeSystem instance to acquire Type information from. + * @param[in] is_file Determines whether or not to write type sizes, and property headers. + * @param[in] flags Determines how serialized data is formatted. */ explicit BinarySerializer(const pclass::TypeSystem &type_system, bool is_file, flags flags); - virtual ~BinarySerializer() {} + virtual ~BinarySerializer() = default; /** - * @param object The object to write to the stream. - * @param stream The stream to write the object to. + * Serialize an object into a BitStream. + * @param[in] object The object to write to the stream. + * @param[in] stream The stream to write the object to. */ void save(const pclass::PropertyClass *object, BitStream &stream); /** - * @param dest Where to load the PropertyClass instance into. - * @param stream The stream to read the object from. - * @param size The size of the stream's available data. + * Deserialize the contents of a BitStream. + * @param[out] dest Where to load the PropertyClass instance into. + * @param[in] stream The stream to read the object from. + * @param[in] size The size of the stream's available data. */ void load(std::unique_ptr &dest, BitStream &stream, std::size_t size); protected: /** - * @param object The object that is being saved. - * @param stream The stream to write the object header to. + * @param[in] object The object that is being saved. + * @param[in] stream The stream to write the object header to. * @returns Whether or not the object is null. */ virtual bool presave_object(const pclass::PropertyClass *object, BitStream &stream) const; /** * Read an object header, and instantiate the necessary PropertyClass. - * @param dest Where to instantiate the PropertyClass. - * @param stream The stream to read the object header from. + * @param[out] dest Where to instantiate the PropertyClass. + * @param[in] stream The stream to read the object header from. */ virtual void preload_object( std::unique_ptr &dest, BitStream &stream) const; diff --git a/include/ki/serialization/FileSerializer.h b/include/ki/serialization/FileSerializer.h new file mode 100644 index 0000000..b289938 --- /dev/null +++ b/include/ki/serialization/FileSerializer.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include "ki/pclass/TypeSystem.h" +#include "ki/serialization/BinarySerializer.h" + +namespace ki +{ +namespace serialization +{ + /** + * TODO: Documentation + */ + class FileSerializer + { + static constexpr const char *BINARY_HEADER = "BINd"; + static constexpr const char *JSON_HEADER = "JSON"; + + public: + explicit FileSerializer(pclass::TypeSystem &type_system); + + void save_binary(pclass::PropertyClass *object, + BinarySerializer::flags flags, const std::string &filepath) const; + void save_xml(pclass::PropertyClass *object, const std::string &filepath) const; + void save_json(pclass::PropertyClass *object, const std::string &filepath) const; + + void load(std::unique_ptr &dest, const std::string &filepath) const; + + private: + const pclass::TypeSystem *m_type_system; + }; +} +} diff --git a/include/ki/serialization/JsonSerializer.h b/include/ki/serialization/JsonSerializer.h index 7af326a..9b37ee7 100644 --- a/include/ki/serialization/JsonSerializer.h +++ b/include/ki/serialization/JsonSerializer.h @@ -16,21 +16,23 @@ namespace serialization static const int FILE_INDENT_VALUE = 2; public: - explicit JsonSerializer(pclass::TypeSystem &type_system, bool is_file); - virtual ~JsonSerializer() {} + explicit JsonSerializer(const pclass::TypeSystem &type_system, bool is_file); + virtual ~JsonSerializer() = default; std::string save(const pclass::PropertyClass *object) const; void load(std::unique_ptr &dest, const std::string &json_string) const; + protected: + virtual bool presave_object(nlohmann::json &j, const pclass::PropertyClass *object) const; + virtual bool preload_object(std::unique_ptr &dest, nlohmann::json &j) const; + private: - pclass::TypeSystem *m_type_system; + const pclass::TypeSystem *m_type_system; bool m_is_file; - virtual bool presave_object(nlohmann::json &j, const pclass::PropertyClass *object) const; nlohmann::json save_object(const pclass::PropertyClass *object) const; void save_property(nlohmann::json &j, const pclass::IProperty &prop) const; - virtual bool preload_object(std::unique_ptr &dest, nlohmann::json &j) const; void load_object(std::unique_ptr &dest, nlohmann::json &j) const; void load_property(pclass::IProperty &prop, nlohmann::json &j) const; }; diff --git a/include/ki/serialization/XmlSerializer.h b/include/ki/serialization/XmlSerializer.h new file mode 100644 index 0000000..785cfef --- /dev/null +++ b/include/ki/serialization/XmlSerializer.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include "ki/pclass/TypeSystem.h" + +namespace ki +{ +namespace serialization +{ + /** + * TODO: Documentation + */ + class XmlSerializer + { + public: + explicit XmlSerializer(const pclass::TypeSystem &type_system); + ~XmlSerializer() = default; + + std::string save(pclass::PropertyClass *object); + void load(std::unique_ptr &dest, const std::string &xml_string); + + protected: + virtual rapidxml::xml_node<> *presave_object(const pclass::PropertyClass *object); + virtual void preload_object(std::unique_ptr &dest, rapidxml::xml_node<> *node); + + private: + const pclass::TypeSystem *m_type_system; + rapidxml::xml_document<> m_document; + + void save_object(rapidxml::xml_node<> *root, const pclass::PropertyClass *object); + void save_property(rapidxml::xml_node<> *object, const pclass::IProperty &prop); + + void load_object(std::unique_ptr &dest, rapidxml::xml_node<> *root); + void load_property(pclass::IProperty &prop, rapidxml::xml_node<> *node); + }; +} +} diff --git a/src/pclass/EnumType.cpp b/src/pclass/EnumType.cpp index 3941bbc..4212346 100644 --- a/src/pclass/EnumType.cpp +++ b/src/pclass/EnumType.cpp @@ -14,7 +14,7 @@ namespace pclass m_value = value; } - std::string EnumType::Element::get_name() const + const std::string &EnumType::Element::get_name() const { return m_name; } diff --git a/src/pclass/Property.cpp b/src/pclass/Property.cpp index 6bf8e17..72b0c89 100644 --- a/src/pclass/Property.cpp +++ b/src/pclass/Property.cpp @@ -41,7 +41,7 @@ namespace pclass return *m_instance; } - std::string IProperty::get_name() const + const std::string &IProperty::get_name() const { return m_name; } diff --git a/src/pclass/TypeSystem.cpp b/src/pclass/TypeSystem.cpp index 0ab0f0c..2d599f2 100644 --- a/src/pclass/TypeSystem.cpp +++ b/src/pclass/TypeSystem.cpp @@ -128,6 +128,13 @@ namespace pclass return *type; } + std::unique_ptr TypeSystem::instantiate( + const std::string& name) const + { + const auto &type = get_type(name); + return type.instantiate(); + } + void TypeSystem::define_type(std::unique_ptr type) { // Does a type with this name already exist? diff --git a/src/serialization/CMakeLists.txt b/src/serialization/CMakeLists.txt index 2ad21fd..3d5f367 100644 --- a/src/serialization/CMakeLists.txt +++ b/src/serialization/CMakeLists.txt @@ -1,5 +1,7 @@ target_sources(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/src/serialization/BinarySerializer.cpp + ${PROJECT_SOURCE_DIR}/src/serialization/FileSerializer.cpp ${PROJECT_SOURCE_DIR}/src/serialization/JsonSerializer.cpp + ${PROJECT_SOURCE_DIR}/src/serialization/XmlSerializer.cpp ) \ No newline at end of file diff --git a/src/serialization/FileSerializer.cpp b/src/serialization/FileSerializer.cpp new file mode 100644 index 0000000..2957303 --- /dev/null +++ b/src/serialization/FileSerializer.cpp @@ -0,0 +1,133 @@ +#include "ki/serialization/FileSerializer.h" +#include +#include "ki/serialization/JsonSerializer.h" +#include "ki/serialization/XmlSerializer.h" +#include "rapidxml_utils.hpp" + +namespace ki +{ +namespace serialization +{ + FileSerializer::FileSerializer(pclass::TypeSystem &type_system) + { + m_type_system = &type_system; + } + + void FileSerializer::save_binary(pclass::PropertyClass *object, + BinarySerializer::flags flags, const std::string &filepath) const + { + // Force the WRITE_SERIALIZER_FLAGS flag so that the correct flags + // can be loaded later + flags |= BinarySerializer::flags::WRITE_SERIALIZER_FLAGS; + + // Create the buffer, stream, and serializer, and save the object + BitBuffer buffer; + BitStream stream(buffer); + BinarySerializer serializer(*m_type_system, true, flags); + serializer.save(object, stream); + + // Write to file + std::ofstream ofs(filepath, std::ios::binary); + ofs << BINARY_HEADER; + ofs.write( + reinterpret_cast(buffer.data()), + stream.tell().as_bytes() + ); + } + + void FileSerializer::save_xml( + pclass::PropertyClass *object, const std::string &filepath) const + { + // Serialize the object into an XML string + XmlSerializer serializer(*m_type_system); + const auto xml_string = serializer.save(object); + + // Write the XML to a file + std::ofstream ofs(filepath, std::ios::binary); + if (!ofs.is_open()) + { + std::ostringstream oss; + oss << "Failed to open filepath for writing: " << filepath; + throw runtime_error(oss.str()); + } + ofs << R"()" << std::endl + << xml_string; + } + + void FileSerializer::save_json( + pclass::PropertyClass *object, const std::string &filepath) const + { + // Serialize the object into a JSON string + JsonSerializer serializer(*m_type_system, true); + const auto json_string = serializer.save(object); + std::ofstream ofs(filepath, std::ios::binary); + if (!ofs.is_open()) + { + std::ostringstream oss; + oss << "Failed to open filepath for writing: " << filepath; + throw runtime_error(oss.str()); + } + ofs << JSON_HEADER << json_string; + } + + void FileSerializer::load( + std::unique_ptr &dest, const std::string &filepath) const + { + // Open the specified file for reading + std::ifstream ifs(filepath, std::ios::binary | std::ios::ate); + if (!ifs.is_open()) + { + std::ostringstream oss; + oss << "Failed to open filepath for reading: " << filepath; + throw runtime_error(oss.str()); + } + + // Make sure there is at least enough data to determine which + // serializer was used + const size_t file_size = ifs.tellg(); + ifs.seekg(std::ios::beg); + if (file_size < 4) + { + std::ostringstream oss; + oss << "Not enough data to determine serializer used in file: " + << filepath; + throw runtime_error(oss.str()); + } + + // Load the contents of the file into a buffer + std::vector file_data(file_size); + ifs.read(file_data.data(), file_size); + ifs.close(); + + // Use the first 4 bytes to distinguish which serializer was used + if (strncmp(file_data.data(), BINARY_HEADER, 4) == 0) + { + // Create and populate a BitStream with the file's data + BitBuffer buffer(file_size - 4); + buffer.write_copy( + reinterpret_cast(&file_data[4]), + BitBuffer::buffer_pos(0, 0), + (file_size - 4) * 8 + ); + BitStream stream(buffer); + + // Deserialize the contents of the stream + BinarySerializer serializer(*m_type_system, true, + BinarySerializer::flags::WRITE_SERIALIZER_FLAGS); + serializer.load(dest, stream, file_size - 4); + } + else if (strncmp(file_data.data(), JSON_HEADER, 4) == 0) + { + auto json_string = std::string(&file_data[4], file_size - 4); + JsonSerializer serializer(*m_type_system, true); + serializer.load(dest, json_string); + } + else + { + rapidxml::file file(filepath.c_str()); + XmlSerializer serializer(*m_type_system); + serializer.load(dest, file.data()); + } + } +} +} diff --git a/src/serialization/JsonSerializer.cpp b/src/serialization/JsonSerializer.cpp index 454e574..70974ca 100644 --- a/src/serialization/JsonSerializer.cpp +++ b/src/serialization/JsonSerializer.cpp @@ -7,7 +7,7 @@ namespace ki { namespace serialization { - JsonSerializer::JsonSerializer(pclass::TypeSystem& type_system, + JsonSerializer::JsonSerializer(const pclass::TypeSystem& type_system, const bool is_file) { m_type_system = &type_system; diff --git a/src/serialization/XmlSerializer.cpp b/src/serialization/XmlSerializer.cpp new file mode 100644 index 0000000..cd67476 --- /dev/null +++ b/src/serialization/XmlSerializer.cpp @@ -0,0 +1,241 @@ +#include "ki/serialization/XmlSerializer.h" +#include +#include + +namespace ki +{ +namespace serialization +{ + XmlSerializer::XmlSerializer(const pclass::TypeSystem &type_system) + { + m_type_system = &type_system; + } + + std::string XmlSerializer::save(pclass::PropertyClass *object) + { + // Create the XML document and root node + m_document.clear(); + auto *root_node = m_document.allocate_node( + rapidxml::node_type::node_element, "Objects"); + m_document.append_node(root_node); + + // Save the object into the root node + save_object(root_node, object); + + // Print the XML document into the string and return it + std::string s; + print(std::back_inserter(s), m_document, 0); + return s; + } + + rapidxml::xml_node<> *XmlSerializer::presave_object(const pclass::PropertyClass *object) + { + if (!object) + return nullptr; + + auto type_name = object->get_type().get_name(); + auto *object_node = m_document.allocate_node( + rapidxml::node_type::node_element, "Class" + ); + + auto *class_attribute_value = m_document.allocate_string(type_name.data()); + auto *class_attribute = m_document.allocate_attribute( + "Name", class_attribute_value + ); + object_node->append_attribute(class_attribute); + return object_node; + } + + void XmlSerializer::save_object(rapidxml::xml_node<> *root, const pclass::PropertyClass* object) + { + auto *object_node = presave_object(object); + if (!object_node) + return; + + auto &property_list = object->get_properties(); + for (auto it = property_list.begin(); + it != property_list.end(); ++it) + { + auto &prop = *it; + save_property(object_node, prop); + } + + root->append_node(object_node); + } + + void XmlSerializer::save_property(rapidxml::xml_node<> *object, const pclass::IProperty& prop) + { + for (std::size_t i = 0; i < prop.get_element_count(); ++i) + { + auto *property_name = m_document.allocate_string(prop.get_name().data()); + auto *property_node = m_document.allocate_node( + rapidxml::node_element, property_name + ); + + if (prop.is_array()) + { + std::ostringstream oss; + oss << i; + auto key = oss.str(); + auto *key_value = m_document.allocate_string(key.data()); + auto *key_attribute = m_document.allocate_attribute("key", key_value); + property_node->append_attribute(key_attribute); + } + + if (prop.get_type().get_kind() == pclass::Type::Kind::CLASS) + { + auto *other_object = prop.get_object(i); + if (other_object) + save_object(property_node, other_object); + else + property_node->value("0"); + } + else + { + auto value = prop.get_value(i).as().get(); + auto *property_value = m_document.allocate_string(value.data()); + property_node->value(property_value); + } + + object->append_node(property_node); + } + } + + void XmlSerializer::load(std::unique_ptr &dest, const std::string &xml_string) + { + m_document.clear(); + auto *c_xml_string = m_document.allocate_string(xml_string.data()); + try + { + m_document.parse<0>(c_xml_string); + } + catch (rapidxml::parse_error &e) + { + std::ostringstream oss; + oss << "Failed to parse given XML string: " << e.what(); + throw runtime_error(oss.str()); + } + + // Get the root node + auto *root_node = m_document.first_node("Objects"); + load_object(dest, root_node); + } + + void XmlSerializer::preload_object(std::unique_ptr &dest, rapidxml::xml_node<> *node) + { + auto *name_attribute = node->first_attribute("Name"); + if (!name_attribute) + throw runtime_error("'Class' element was missing 'Name' attribute."); + + const auto type_name = std::string( + name_attribute->value(), name_attribute->value_size()); + const auto &type = m_type_system->get_type(type_name); + dest = type.instantiate(); + } + + void XmlSerializer::load_object( + std::unique_ptr &dest, rapidxml::xml_node<> *root) + { + auto *object_node = root->first_node("Class"); + if (!object_node) + { + dest = nullptr; + return; + } + + preload_object(dest, object_node); + auto &property_list = dest->get_properties(); + for (auto it = property_list.begin(); + it != property_list.end(); ++it) + { + auto &prop = *it; + load_property(prop, object_node); + } + + // All properties on this object have been set, let the new + // instance react to this change + dest->on_created(); + } + + void XmlSerializer::load_property(pclass::IProperty &prop, rapidxml::xml_node<> *node) + { + auto *property_name = prop.get_name().data(); + + // Get the node that contains data for this property and + // check if it exists (because it must exist at least once) + auto *property_node = node->first_node(property_name); + if (!property_node) + { + std::ostringstream oss; + oss << "Missing parameter element: '" << prop.get_name() << "'."; + throw runtime_error(oss.str()); + } + + // Get a list of element nodes that have values for this property + // in the order of their "key" attribute + std::vector *> property_entries; + while (property_node) + { + // Get the key attribute if the property is an array + if (prop.is_array()) + { + auto *key_attribute = property_node->first_attribute("key"); + if (!key_attribute) + { + std::ostringstream oss; + oss << "Parameter element '" << prop.get_name() + << "' is missing 'key' attribute."; + throw runtime_error(oss.str()); + } + + // Get the value of the key attribute + auto key_value_str = std::string( + key_attribute->value(), key_attribute->value_size() + ); + std::size_t key_value; + std::istringstream iss(key_value_str); + iss >> key_value; + + // Store this node at the key value + property_entries.insert(property_entries.begin() + key_value, property_node); + } + else + property_entries.push_back(property_node); + property_node = property_node->next_sibling(property_name); + } + + // If the property is dynamic, make sure the element count has been set, + // otherwise, make sure we have the correct number of elements + if (prop.is_dynamic()) + prop.set_element_count(property_entries.size()); + else if (property_entries.size() != prop.get_element_count()) + { + std::ostringstream oss; + oss << "Expected " << prop.get_element_count() << " values for '" + << prop.get_name() << "' but got " << property_entries.size() << "."; + throw runtime_error(oss.str()); + } + + // Update the value(s) of the property + for (std::size_t i = 0; i < prop.get_element_count(); ++i) + { + auto *element_node = property_entries.at(i); + if (prop.get_type().get_kind() == pclass::Type::Kind::CLASS) + { + std::unique_ptr other_object = nullptr; + load_object(other_object, element_node); + prop.set_object(other_object, i); + } + else + { + auto element_value = std::string( + element_node->value(), element_node->value_size() + ); + prop.set_value( + pclass::Value::make_reference(element_value), i + ); + } + } + } +} +} diff --git a/test/samples/serialization/file.bin b/test/samples/serialization/file.bin index 81c052e394f89b53a01ee88a5e38389bc7e21a6d..348843f3b86ec6a00873a403ed5134267c3edae5 100644 GIT binary patch delta 12 TcmZqW>f~Z^^7KpDz{&~$6+;6a delta 8 PcmeC=YUSF + + + -6 + 5 + 1 + 515 + 263430 + 117967114 + 796025588171149586 + 1 + 515 + 263430 + 117967114 + 796025588171149586 + This is a test value + ᵗʰⁱˢ ⁱˢ ᵃ ᵗᵉˢᵗ ᵛᵃˡᵘᵉ + 3.14159274 + 3.1415926535897931 + 24 61 3.62 + 52 + 0 + 1 + 2 + 3 + 4 + 0 + 1 + 2 + 3 + 4 + + + 2 + 20 + + + + + 1 + + + 0 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 + + + 2 + 10 + + + + + 3 + + + + + diff --git a/test/samples/serialization/file_compressed.bin b/test/samples/serialization/file_compressed.bin index 665148100d072cac7a8d31873717f8df874541f7..57817788d2cd2f55411cc3f41688c839f83a8379 100644 GIT binary patch delta 12 TcmdnXa*&0^$ + + -6 + 5 + 1 + 515 + 263430 + 117967114 + 796025588171149586 + 1 + 515 + 263430 + 117967114 + 796025588171149586 + This is a test value + ᵗʰⁱˢ ⁱˢ ᵃ ᵗᵉˢᵗ ᵛᵃˡᵘᵉ + 3.14159274 + 3.1415926535897931 + 24 61 3.62 + 52 + 0 + 1 + 2 + 3 + 4 + 0 + 1 + 2 + 3 + 4 + + + 2 + 20 + + + + + 1 + + + 0 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 + + + 2 + 10 + + + + + 3 + + + + + diff --git a/test/src/unit-serialization.cpp b/test/src/unit-serialization.cpp index 251aaea..8ba1206 100644 --- a/test/src/unit-serialization.cpp +++ b/test/src/unit-serialization.cpp @@ -11,6 +11,8 @@ #include #include #include "ki/pclass/Enum.h" +#include "ki/serialization/XmlSerializer.h" +#include "ki/serialization/FileSerializer.h" using namespace ki; @@ -23,14 +25,18 @@ struct Vector3D friend pclass::detail::value_caster; friend pclass::detail::value_caster; + // Allow string caster to access private members + friend pclass::detail::value_caster; + friend pclass::detail::value_caster; + explicit Vector3D( const float x = 0.0f, const float y = 0.0f, const float z = 0.0f) { - this->m_x = x; - this->m_y = y; - this->m_z = z; + m_x = x; + m_y = y; + m_z = z; } Vector3D &operator=(const Vector3D &that) @@ -133,6 +139,41 @@ namespace detail ); } }; + + /** + * value_caster specialization for casting Vector3D to std::string. + */ + template <> + struct value_caster + : value_caster_impl + { + std::string cast_value(const Vector3D &value) const override + { + std::ostringstream oss; + oss << value.m_x << " " + << value.m_y << " " + << value.m_z; + return oss.str(); + } + }; + + /** + * value_caster specialization for casting std::string to Vector3D. + */ + template <> + struct value_caster + : value_caster_impl + { + Vector3D cast_value(const std::string &value) const override + { + Vector3D result; + std::istringstream iss(value); + iss >> result.m_x; + iss >> result.m_y; + iss >> result.m_z; + return result; + } + }; } } } @@ -465,7 +506,7 @@ void validate_test_object(TestObject &object) */ void test_serializer( std::unique_ptr &test_object, - serialization::BinarySerializer &serializer, + serialization::BinarySerializer::flags flags, const std::string &file_suffix) { BitBuffer buffer; @@ -475,20 +516,18 @@ void test_serializer( // Open the sample data std::ifstream sample( "samples/serialization/" + file_suffix + ".bin", - std::ios::binary + std::ios::binary | std::ios::ate ); REQUIRE(sample.is_open()); // Load the sample data - const auto begin = sample.tellg(); - sample.seekg(0, std::ios::end); - const auto end = sample.tellg(); - const size_t sample_size = end - begin; + const size_t sample_size = sample.tellg(); sample.seekg(std::ios::beg); auto *sample_data = new char[sample_size]; sample.read(sample_data, sample_size); sample.close(); + serialization::BinarySerializer serializer(*g_type_system, false, flags); SECTION("Saving objects") { // Create a test object, configure it, and write it to our stream @@ -512,7 +551,6 @@ void test_serializer( // Cleanup delete[] stream_data; } - SECTION("Loading objects") { // Write the sample data to the bit stream @@ -536,61 +574,96 @@ void test_serializer( } /** -* Conduct save/load tests with a BinarySerializer instance. -*/ -void test_serializer( + * + */ +void test_file_serializer_load( std::unique_ptr &test_object, - serialization::JsonSerializer &serializer, - const std::string &file_suffix) + const std::string &filename) { - // Open the sample data - std::ifstream sample_file( - "samples/serialization/" + file_suffix + ".json", - std::ios::binary + serialization::FileSerializer serializer(*g_type_system); + + // Load an object from a pre-made sample + const auto sample_filepath = "samples/serialization/" + filename; + std::unique_ptr object = nullptr; + serializer.load(object, sample_filepath); + + // Set test_object so that it is validated by the caller + REQUIRE(object != nullptr); + test_object = std::unique_ptr( + dynamic_cast(object.release()) ); - REQUIRE(sample_file.is_open()); + REQUIRE(test_object != nullptr); +} - // Load the sample data into a buffer - const auto begin = sample_file.tellg(); - sample_file.seekg(0, std::ios::end); - const auto end = sample_file.tellg(); - const size_t sample_size = end - begin; - sample_file.seekg(std::ios::beg); +/** + * + */ +void test_file_serializer_save( + const std::string &out_filepath, + const std::string &sample_filepath) +{ + // Load sample data + std::ifstream sample_ifs( + sample_filepath, + std::ios::binary | std::ios::ate + ); + REQUIRE(sample_ifs.is_open()); + const auto sample_size = sample_ifs.tellg(); + sample_ifs.seekg(std::ios::beg); auto *sample_data = new char[sample_size]; - sample_file.read(sample_data, sample_size); - sample_file.close(); + sample_ifs.read(sample_data, sample_size); + sample_ifs.close(); - // Load the sample data into a string - const auto sample = std::string(sample_data, sample_size); + // Load output file that was just created + std::ifstream output_ifs( + out_filepath, + std::ios::binary | std::ios::ate + ); + REQUIRE(output_ifs.is_open()); + const auto output_size = output_ifs.tellg(); + output_ifs.seekg(std::ios::beg); + auto *output_data = new char[output_size]; + output_ifs.read(output_data, output_size); + output_ifs.close(); + + // Validate the output + REQUIRE(sample_size == output_size); + REQUIRE(strncmp(sample_data, output_data, sample_size) == 0); + + // Cleanup delete[] sample_data; + delete[] output_data; +} +/** + * + */ +void test_binary_file_serializer( + std::unique_ptr &test_object, + const serialization::BinarySerializer::flags flags, + const std::string &filename) +{ SECTION("Saving objects") { - // Create a test object, configure it, and write it to our stream + // Create a test object, configure it, and write it a file + serialization::FileSerializer serializer(*g_type_system); test_object = g_type_system->instantiate("class TestObject"); configure_test_object(*test_object); - const auto json_string = serializer.save(test_object.get()); + const auto out_filepath = "out_" + filename + ".bin"; + serializer.save_binary(test_object.get(), flags, out_filepath); + test_file_serializer_save( + out_filepath, + "samples/serialization/" + filename + ".bin" + ); // Delete the test object here so that it is not // unnecessarily validated by the caller test_object = nullptr; - - // Validate the JSON string - REQUIRE(json_string == sample); } - SECTION("Loading objects") { - // Load an object from the sample - std::unique_ptr object = nullptr; - serializer.load(object, sample); - - // Set test_object so that it is validated by the caller - REQUIRE(object != nullptr); - test_object = std::unique_ptr( - dynamic_cast(object.release()) - ); - REQUIRE(test_object != nullptr); + test_file_serializer_load( + test_object, filename + ".bin"); } } @@ -601,53 +674,189 @@ TEST_CASE("Serialization tests", "[serialization]") SECTION("BinarySerializer") { - SECTION("Regular format without compression") + SECTION("Without compression") { - serialization::BinarySerializer serializer( - *g_type_system, false, - serialization::BinarySerializer::flags::NONE + test_serializer( + test_object, + serialization::BinarySerializer::flags::NONE, + "regular" ); - test_serializer(test_object, serializer, "regular"); } - SECTION("File format without compression") + SECTION("With compression") { - serialization::BinarySerializer serializer( - *g_type_system, true, - serialization::BinarySerializer::flags::WRITE_SERIALIZER_FLAGS + test_serializer( + test_object, + serialization::BinarySerializer::flags::COMPRESSED, + "regular_compressed" ); - test_serializer(test_object, serializer, "file"); - } - SECTION("Regular format with compression") - { - serialization::BinarySerializer serializer( - *g_type_system, false, - serialization::BinarySerializer::flags::COMPRESSED - ); - test_serializer(test_object, serializer, "regular_compressed"); - } - SECTION("File format with compression") - { - serialization::BinarySerializer serializer( - *g_type_system, true, - serialization::BinarySerializer::flags::WRITE_SERIALIZER_FLAGS | - serialization::BinarySerializer::flags::COMPRESSED - ); - test_serializer(test_object, serializer, "file_compressed"); } } SECTION("JsonSerializer") { - SECTION("Regular format") - { - serialization::JsonSerializer serializer(*g_type_system, false); - test_serializer(test_object, serializer, "regular"); - } + // Open the sample data + std::ifstream sample_file( + "samples/serialization/regular.json", + std::ios::binary | std::ios::ate + ); + REQUIRE(sample_file.is_open()); - SECTION("File format") + // Load the sample data into a buffer + const size_t sample_size = sample_file.tellg(); + sample_file.seekg(std::ios::beg); + auto *sample_data = new char[sample_size]; + sample_file.read(sample_data, sample_size); + sample_file.close(); + + // Load the sample data into a string + const auto sample = std::string(sample_data, sample_size); + delete[] sample_data; + + serialization::JsonSerializer serializer(*g_type_system, false); + SECTION("Saving objects") { - serialization::JsonSerializer serializer(*g_type_system, true); - test_serializer(test_object, serializer, "file"); + // Create a test object, configure it, and write it to our stream + test_object = g_type_system->instantiate("class TestObject"); + configure_test_object(*test_object); + const auto json_string = serializer.save(test_object.get()); + + // Delete the test object here so that it is not + // unnecessarily validated by the caller + test_object = nullptr; + + // Validate the JSON string + REQUIRE(json_string == sample); + } + SECTION("Loading objects") + { + // Load an object from the sample + std::unique_ptr object = nullptr; + serializer.load(object, sample); + + // Set test_object so that it is validated by the caller + REQUIRE(object != nullptr); + test_object = std::unique_ptr( + dynamic_cast(object.release()) + ); + REQUIRE(test_object != nullptr); + } + } + + SECTION("XmlSerializer") + { + // Open the sample data + std::ifstream sample_file( + "samples/serialization/regular.xml", + std::ios::binary | std::ios::ate + ); + REQUIRE(sample_file.is_open()); + + // Load the sample data into a buffer + const size_t sample_size = sample_file.tellg(); + sample_file.seekg(std::ios::beg); + auto *sample_data = new char[sample_size]; + sample_file.read(sample_data, sample_size); + sample_file.close(); + + // Load the sample data into a string + auto sample = std::string(sample_data, sample_size); + delete[] sample_data; + + serialization::XmlSerializer serializer(*g_type_system); + SECTION("Saving objects") + { + // Create a test object, configure it, and write it to our stream + test_object = g_type_system->instantiate("class TestObject"); + configure_test_object(*test_object); + auto xml_string = serializer.save(test_object.get()); + + // Delete the test object here so that it is not + // unnecessarily validated by the caller + test_object = nullptr; + + // Validate the JSON string + REQUIRE(xml_string == sample); + } + SECTION("Loading objects") + { + // Load an object from the sample + std::unique_ptr object = nullptr; + serializer.load(object, sample); + + // Set test_object so that it is validated by the caller + REQUIRE(object != nullptr); + test_object = std::unique_ptr( + dynamic_cast(object.release()) + ); + REQUIRE(test_object != nullptr); + } + } + + SECTION("FileSerializer") + { + SECTION("Binary") + { + test_binary_file_serializer( + test_object, + serialization::BinarySerializer::flags::NONE, + "file" + ); + } + SECTION("Compressed Binary") + { + test_binary_file_serializer( + test_object, + serialization::BinarySerializer::flags::COMPRESSED, + "file_compressed" + ); + } + SECTION("JSON") + { + SECTION("Saving objects") + { + // Create a test object, configure it, and write it a file + serialization::FileSerializer serializer(*g_type_system); + test_object = g_type_system->instantiate("class TestObject"); + configure_test_object(*test_object); + const auto out_filepath = "out_file.json"; + serializer.save_json(test_object.get(), out_filepath); + test_file_serializer_save( + out_filepath, + "samples/serialization/file.json" + ); + + // Delete the test object here so that it is not + // unnecessarily validated by the caller + test_object = nullptr; + } + SECTION("Loading objects") + { + test_file_serializer_load(test_object, "file.json"); + } + } + SECTION("XML") + { + SECTION("Saving objects") + { + // Create a test object, configure it, and write it a file + serialization::FileSerializer serializer(*g_type_system); + test_object = g_type_system->instantiate("class TestObject"); + configure_test_object(*test_object); + const auto out_filepath = "out_file.xml"; + serializer.save_xml(test_object.get(), out_filepath); + test_file_serializer_save( + out_filepath, + "samples/serialization/file.xml" + ); + + // Delete the test object here so that it is not + // unnecessarily validated by the caller + test_object = nullptr; + } + SECTION("Loading objects") + { + test_file_serializer_load(test_object, "file.xml"); + } } } diff --git a/third_party/rapidxml_print.hpp b/third_party/rapidxml_print.hpp new file mode 100644 index 0000000..0d5ccd3 --- /dev/null +++ b/third_party/rapidxml_print.hpp @@ -0,0 +1,448 @@ +#ifndef RAPIDXML_PRINT_HPP_INCLUDED +#define RAPIDXML_PRINT_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml_print.hpp This file contains rapidxml printer implementation + +#include "rapidxml.hpp" + +// Only include streams if not disabled +#ifndef RAPIDXML_NO_STREAMS + #include + #include +#endif + +namespace rapidxml +{ + + /////////////////////////////////////////////////////////////////////// + // Printing flags + + const int print_no_indenting = 0x1; //!< Printer flag instructing the printer to suppress indenting of XML. See print() function. + + /////////////////////////////////////////////////////////////////////// + // Internal + + //! \cond internal + namespace internal + { + + /////////////////////////////////////////////////////////////////////////// + // Internal character operations + + // Copy characters from given range to given output iterator + template + inline OutIt copy_chars(const Ch *begin, const Ch *end, OutIt out) + { + while (begin != end) + *out++ = *begin++; + return out; + } + + // Copy characters from given range to given output iterator and expand + // characters into references (< > ' " &) + template + inline OutIt copy_and_expand_chars(const Ch *begin, const Ch *end, Ch noexpand, OutIt out) + { + while (begin != end) + { + if (*begin == noexpand) + { + *out++ = *begin; // No expansion, copy character + } + else + { + switch (*begin) + { + case Ch('<'): + *out++ = Ch('&'); *out++ = Ch('l'); *out++ = Ch('t'); *out++ = Ch(';'); + break; + case Ch('>'): + *out++ = Ch('&'); *out++ = Ch('g'); *out++ = Ch('t'); *out++ = Ch(';'); + break; + case Ch('\''): + *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('p'); *out++ = Ch('o'); *out++ = Ch('s'); *out++ = Ch(';'); + break; + case Ch('"'): + *out++ = Ch('&'); *out++ = Ch('q'); *out++ = Ch('u'); *out++ = Ch('o'); *out++ = Ch('t'); *out++ = Ch(';'); + break; + case Ch('&'): + *out++ = Ch('&'); *out++ = Ch('a'); *out++ = Ch('m'); *out++ = Ch('p'); *out++ = Ch(';'); + break; + default: + *out++ = *begin; // No expansion, copy character + } + } + ++begin; // Step to next character + } + return out; + } + + // Fill given output iterator with repetitions of the same character + template + inline OutIt fill_chars(OutIt out, int n, Ch ch) + { + for (int i = 0; i < n; ++i) + *out++ = ch; + return out; + } + + // Find character + template + inline bool find_char(const Ch *begin, const Ch *end) + { + while (begin != end) + if (*begin++ == ch) + return true; + return false; + } + + /////////////////////////////////////////////////////////////////////////// + // Internal printing operations + + template + inline OutIt print_children(OutIt out, const xml_node *node, int flags, int indent); + + template + inline OutIt print_attributes(OutIt out, const xml_node *node, int flags); + + template + inline OutIt print_data_node(OutIt out, const xml_node *node, int flags, int indent); + + template + inline OutIt print_cdata_node(OutIt out, const xml_node *node, int flags, int indent); + + template + inline OutIt print_element_node(OutIt out, const xml_node *node, int flags, int indent); + + template + inline OutIt print_declaration_node(OutIt out, const xml_node *node, int flags, int indent); + + template + inline OutIt print_comment_node(OutIt out, const xml_node *node, int flags, int indent); + + template + inline OutIt print_doctype_node(OutIt out, const xml_node *node, int flags, int indent); + + template + inline OutIt print_pi_node(OutIt out, const xml_node *node, int flags, int indent); + + // Print node + template + inline OutIt print_node(OutIt out, const xml_node *node, int flags, int indent) + { + // Print proper node type + switch (node->type()) + { + + // Document + case node_document: + out = print_children(out, node, flags, indent); + break; + + // Element + case node_element: + out = print_element_node(out, node, flags, indent); + break; + + // Data + case node_data: + out = print_data_node(out, node, flags, indent); + break; + + // CDATA + case node_cdata: + out = print_cdata_node(out, node, flags, indent); + break; + + // Declaration + case node_declaration: + out = print_declaration_node(out, node, flags, indent); + break; + + // Comment + case node_comment: + out = print_comment_node(out, node, flags, indent); + break; + + // Doctype + case node_doctype: + out = print_doctype_node(out, node, flags, indent); + break; + + // Pi + case node_pi: + out = print_pi_node(out, node, flags, indent); + break; + + // Unknown + default: + assert(0); + break; + } + + // If indenting not disabled, add line break after node + if (!(flags & print_no_indenting)) + *out = Ch('\n'), ++out; + + // Return modified iterator + return out; + } + + // Print children of the node + template + inline OutIt print_children(OutIt out, const xml_node *node, int flags, int indent) + { + for (xml_node *child = node->first_node(); child; child = child->next_sibling()) + out = print_node(out, child, flags, indent); + return out; + } + + // Print attributes of the node + template + inline OutIt print_attributes(OutIt out, const xml_node *node, int flags) + { + for (xml_attribute *attribute = node->first_attribute(); attribute; attribute = attribute->next_attribute()) + { + if (attribute->name() && attribute->value()) + { + // Print attribute name + *out = Ch(' '), ++out; + out = copy_chars(attribute->name(), attribute->name() + attribute->name_size(), out); + *out = Ch('='), ++out; + // Print attribute value using appropriate quote type + if (find_char(attribute->value(), attribute->value() + attribute->value_size())) + { + *out = Ch('\''), ++out; + out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('"'), out); + *out = Ch('\''), ++out; + } + else + { + *out = Ch('"'), ++out; + out = copy_and_expand_chars(attribute->value(), attribute->value() + attribute->value_size(), Ch('\''), out); + *out = Ch('"'), ++out; + } + } + } + return out; + } + + // Print data node + template + inline OutIt print_data_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_data); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out); + return out; + } + + // Print data node + template + inline OutIt print_cdata_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_cdata); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'); ++out; + *out = Ch('!'); ++out; + *out = Ch('['); ++out; + *out = Ch('C'); ++out; + *out = Ch('D'); ++out; + *out = Ch('A'); ++out; + *out = Ch('T'); ++out; + *out = Ch('A'); ++out; + *out = Ch('['); ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch(']'); ++out; + *out = Ch(']'); ++out; + *out = Ch('>'); ++out; + return out; + } + + // Print element node + template + inline OutIt print_element_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_element); + + // Print element name and attributes, if any + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + out = copy_chars(node->name(), node->name() + node->name_size(), out); + out = print_attributes(out, node, flags); + + // If node is childless + if (node->value_size() == 0 && !node->first_node()) + { + // Print childless node tag ending + *out = Ch('/'), ++out; + *out = Ch('>'), ++out; + } + else + { + // Print normal node tag ending + *out = Ch('>'), ++out; + + // Test if node contains a single data node only (and no other nodes) + xml_node *child = node->first_node(); + if (!child) + { + // If node has no children, only print its value without indenting + out = copy_and_expand_chars(node->value(), node->value() + node->value_size(), Ch(0), out); + } + else if (child->next_sibling() == 0 && child->type() == node_data) + { + // If node has a sole data child, only print its value without indenting + out = copy_and_expand_chars(child->value(), child->value() + child->value_size(), Ch(0), out); + } + else + { + // Print all children with full indenting + if (!(flags & print_no_indenting)) + *out = Ch('\n'), ++out; + out = print_children(out, node, flags, indent + 1); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + } + + // Print node end + *out = Ch('<'), ++out; + *out = Ch('/'), ++out; + out = copy_chars(node->name(), node->name() + node->name_size(), out); + *out = Ch('>'), ++out; + } + return out; + } + + // Print declaration node + template + inline OutIt print_declaration_node(OutIt out, const xml_node *node, int flags, int indent) + { + // Print declaration start + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('?'), ++out; + *out = Ch('x'), ++out; + *out = Ch('m'), ++out; + *out = Ch('l'), ++out; + + // Print attributes + out = print_attributes(out, node, flags); + + // Print declaration end + *out = Ch('?'), ++out; + *out = Ch('>'), ++out; + + return out; + } + + // Print comment node + template + inline OutIt print_comment_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_comment); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('!'), ++out; + *out = Ch('-'), ++out; + *out = Ch('-'), ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch('-'), ++out; + *out = Ch('-'), ++out; + *out = Ch('>'), ++out; + return out; + } + + // Print doctype node + template + inline OutIt print_doctype_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_doctype); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('!'), ++out; + *out = Ch('D'), ++out; + *out = Ch('O'), ++out; + *out = Ch('C'), ++out; + *out = Ch('T'), ++out; + *out = Ch('Y'), ++out; + *out = Ch('P'), ++out; + *out = Ch('E'), ++out; + *out = Ch(' '), ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch('>'), ++out; + return out; + } + + // Print pi node + template + inline OutIt print_pi_node(OutIt out, const xml_node *node, int flags, int indent) + { + assert(node->type() == node_pi); + if (!(flags & print_no_indenting)) + out = fill_chars(out, indent, Ch('\t')); + *out = Ch('<'), ++out; + *out = Ch('?'), ++out; + out = copy_chars(node->name(), node->name() + node->name_size(), out); + *out = Ch(' '), ++out; + out = copy_chars(node->value(), node->value() + node->value_size(), out); + *out = Ch('?'), ++out; + *out = Ch('>'), ++out; + return out; + } + + } + //! \endcond + + /////////////////////////////////////////////////////////////////////////// + // Printing + + //! Prints XML to given output iterator. + //! \param out Output iterator to print to. + //! \param node Node to be printed. Pass xml_document to print entire document. + //! \param flags Flags controlling how XML is printed. + //! \return Output iterator pointing to position immediately after last character of printed text. + template + inline OutIt print(OutIt out, const xml_node &node, int flags = 0) + { + return internal::print_node(out, &node, flags, 0); + } + +#ifndef RAPIDXML_NO_STREAMS + + //! Prints XML to given output stream. + //! \param out Output stream to print to. + //! \param node Node to be printed. Pass xml_document to print entire document. + //! \param flags Flags controlling how XML is printed. + //! \return Output stream. + template + inline std::basic_ostream &print(std::basic_ostream &out, const xml_node &node, int flags = 0) + { + print(std::ostream_iterator(out), node, flags); + return out; + } + + //! Prints formatted XML to given output stream. Uses default printing flags. Use print() function to customize printing process. + //! \param out Output stream to print to. + //! \param node Node to be printed. + //! \return Output stream. + template + inline std::basic_ostream &operator <<(std::basic_ostream &out, const xml_node &node) + { + return print(out, node); + } + +#endif + +} + +#endif diff --git a/third_party/rapidxml_utils.hpp b/third_party/rapidxml_utils.hpp new file mode 100644 index 0000000..37c2953 --- /dev/null +++ b/third_party/rapidxml_utils.hpp @@ -0,0 +1,122 @@ +#ifndef RAPIDXML_UTILS_HPP_INCLUDED +#define RAPIDXML_UTILS_HPP_INCLUDED + +// Copyright (C) 2006, 2009 Marcin Kalicinski +// Version 1.13 +// Revision $DateTime: 2009/05/13 01:46:17 $ +//! \file rapidxml_utils.hpp This file contains high-level rapidxml utilities that can be useful +//! in certain simple scenarios. They should probably not be used if maximizing performance is the main objective. + +#include "rapidxml.hpp" +#include +#include +#include +#include + +namespace rapidxml +{ + + //! Represents data loaded from a file + template + class file + { + + public: + + //! Loads file into the memory. Data will be automatically destroyed by the destructor. + //! \param filename Filename to load. + file(const char *filename) + { + using namespace std; + + // Open stream + basic_ifstream stream(filename, ios::binary); + if (!stream) + throw runtime_error(string("cannot open file ") + filename); + stream.unsetf(ios::skipws); + + // Determine stream size + stream.seekg(0, ios::end); + size_t size = stream.tellg(); + stream.seekg(0); + + // Load data and add terminating 0 + m_data.resize(size + 1); + stream.read(&m_data.front(), static_cast(size)); + m_data[size] = 0; + } + + //! Loads file into the memory. Data will be automatically destroyed by the destructor + //! \param stream Stream to load from + file(std::basic_istream &stream) + { + using namespace std; + + // Load data and add terminating 0 + stream.unsetf(ios::skipws); + m_data.assign(istreambuf_iterator(stream), istreambuf_iterator()); + if (stream.fail() || stream.bad()) + throw runtime_error("error reading stream"); + m_data.push_back(0); + } + + //! Gets file data. + //! \return Pointer to data of file. + Ch *data() + { + return &m_data.front(); + } + + //! Gets file data. + //! \return Pointer to data of file. + const Ch *data() const + { + return &m_data.front(); + } + + //! Gets file data size. + //! \return Size of file data, in characters. + std::size_t size() const + { + return m_data.size(); + } + + private: + + std::vector m_data; // File data + + }; + + //! Counts children of node. Time complexity is O(n). + //! \return Number of children of node + template + inline std::size_t count_children(xml_node *node) + { + xml_node *child = node->first_node(); + std::size_t count = 0; + while (child) + { + ++count; + child = child->next_sibling(); + } + return count; + } + + //! Counts attributes of node. Time complexity is O(n). + //! \return Number of attributes of node + template + inline std::size_t count_attributes(xml_node *node) + { + xml_attribute *attr = node->first_attribute(); + std::size_t count = 0; + while (attr) + { + ++count; + attr = attr->next_attribute(); + } + return count; + } + +} + +#endif