/* Copyright 2018 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /// @file serd.hpp C++ API for Serd, a lightweight RDF syntax library #ifndef SERD_SERD_HPP #define SERD_SERD_HPP #include #include #include #include #include #include /** @defgroup serdxx Serdxx C++ bindings for Serd, a lightweight RDF syntax library. @{ */ namespace serd { namespace detail { template using FreeFunc = void (*)(T*); template using CopyFunc = T* (*)(const T*); template void no_free(T*) { } template struct Deleter { Deleter(FreeFunc free_func) : _free_func(free_func) {} void operator()(T* ptr) { if (_free_func) { _free_func(ptr); } } FreeFunc _free_func; }; /// C++ wrapper for a C object template class Wrapper { public: using CType = T; Wrapper(T* ptr) : _ptr(ptr, Deleter(FreeFunc)) {} Wrapper(const T* ptr) : _ptr(const_cast(ptr), nullptr) {} Wrapper(Wrapper&& wrapper) = default; Wrapper& operator=(Wrapper&& wrapper) = default; Wrapper(const Wrapper&) = delete; Wrapper& operator=(const Wrapper&) = delete; bool operator==(const Wrapper&) = delete; bool operator!=(const Wrapper&) = delete; operator const T*() const { return _ptr.get(); } inline T* c_obj() { return _ptr.get(); } inline const T* c_obj() const { return _ptr.get(); } protected: Wrapper(std::nullptr_t) : _ptr(nullptr, nullptr) {} std::unique_ptr> _ptr; }; template class Copyable : public Wrapper { public: Copyable(T* ptr) : Wrapper(ptr) {} Copyable(const T* ptr) : Wrapper(ptr) {} Copyable(const Copyable& wrapper) : Wrapper(CopyFunc(wrapper.c_obj())) { } Copyable(Copyable&&) = default; Copyable& operator=(Copyable&&) = default; Copyable& operator=(const Copyable& wrapper) { this->_ptr = std::unique_ptr>(CopyFunc(wrapper.c_obj()), FreeFunc); return *this; } protected: Copyable(std::nullptr_t) : Wrapper(nullptr) {} }; static inline size_t stream_sink(const void* buf, size_t size, size_t nmemb, void* stream) { try { std::ostream* const s = static_cast(stream); s->write(static_cast(buf), std::streamsize(size * nmemb)); if (s->good()) { return nmemb; } } catch (...) { } return 0; } /// Type-safe bit flags template class Flags { public: static_assert(std::is_enum::value, ""); using FlagUnderlyingType = typename std::underlying_type::type; using Value = typename std::make_unsigned::type; Flags() : _value(0) {} Flags(const Flag f) : _value(static_cast(f)) {} explicit Flags(const Value value) : _value{value} {} Flags operator|(const Flag rhs) const { return Flags{_value | static_cast(rhs)}; } Flags operator|(const Flags rhs) const { return Flags{_value | rhs._value}; } operator Value() const { return _value; } private: Value _value{}; }; } // namespace detail template inline typename std::enable_if::value, detail::Flags>::type operator|(const Flag lhs, const Flag rhs) { return detail::Flags{lhs} | rhs; } /// Return status code enum class Status { success = SERD_SUCCESS, ///< No error failure = SERD_FAILURE, ///< Non-fatal failure err_unknown = SERD_ERR_UNKNOWN, ///< Unknown error err_bad_syntax = SERD_ERR_BAD_SYNTAX, ///< Invalid syntax err_bad_arg = SERD_ERR_BAD_ARG, ///< Invalid argument err_bad_iter = SERD_ERR_BAD_ITER, ///< Use of invalidated iterator err_not_found = SERD_ERR_NOT_FOUND, ///< Not found err_id_clash = SERD_ERR_ID_CLASH, ///< Clashing blank node IDs err_bad_curie = SERD_ERR_BAD_CURIE, ///< Invalid CURIE err_internal = SERD_ERR_INTERNAL, ///< Unexpected internal error err_overflow = SERD_ERR_OVERFLOW, ///< Stack overflow err_invalid = SERD_ERR_INVALID ///< Invalid data }; /// RDF syntax type enum class Syntax { Turtle = SERD_TURTLE, ///< Terse RDF Triple Language NTriples = SERD_NTRIPLES, ///< Line-based RDF triples NQuads = SERD_NQUADS, ///< Line-based RDF quads (NTriples with graphs) TriG = SERD_TRIG ///< Terse RDF quads (Turtle with graphs) }; /// Flags indicating inline abbreviation information for a statement enum class StatementFlag { empty_S = SERD_EMPTY_S, ///< Empty blank node subject anon_S = SERD_ANON_S, ///< Start of anonymous subject anon_O = SERD_ANON_O, ///< Start of anonymous object list_S = SERD_LIST_S, ///< Start of list subject list_O = SERD_LIST_O ///< Start of list object }; using StatementFlags = detail::Flags; /// Flags that control style for a model serialisation enum class SerialisationFlag { no_inline_objects = SERD_NO_INLINE_OBJECTS, ///< Do not inline objects where possible }; using SerialisationFlags = detail::Flags; /** Type of a syntactic RDF node This is more precise than the type of an abstract RDF node. An abstract node is either a resource, literal, or blank. In syntax there are two ways to refer to a resource (by URI or CURIE) and two ways to refer to a blank (by ID or anonymously). Anonymous (inline) blank nodes are expressed using SerdStatementFlags rather than this type. */ enum class NodeType { /** The type of a nonexistent node This type is useful as a sentinel, but is never emitted by the reader. */ nothing = SERD_NOTHING, /** Literal value A literal optionally has either a language, or a datatype (not both). */ literal = SERD_LITERAL, /** URI (absolute or relative) Value is an unquoted URI string, which is either a relative reference with respect to the current base URI (e.g. "foo/bar"), or an absolute URI (e.g. "http://example.org/foo"). @see RFC3986. */ URI = SERD_URI, /** CURIE, a shortened URI Value is an unquoted CURIE string relative to the current environment, e.g. "rdf:type". @see CURIE Syntax 1.0 */ CURIE = SERD_CURIE, /** A blank node Value is a blank node ID, e.g. "id3", which is meaningful only within this serialisation. @see Turtle nodeID */ blank = SERD_BLANK }; /// Flags indicating certain string properties relevant to serialisation enum class NodeFlag { has_newline = SERD_HAS_NEWLINE, ///< Contains line breaks has_quote = SERD_HAS_QUOTE, ///< Contains quotes has_datatype = SERD_HAS_DATATYPE, ///< Literal node has datatype has_language = SERD_HAS_LANGUAGE ///< Literal node has language }; using NodeFlags = detail::Flags; /// Field in a statement enum class Field { subject = SERD_SUBJECT, ///< Subject predicate = SERD_PREDICATE, ///< Predicate ("key") object = SERD_OBJECT, ///< Object ("value") graph = SERD_GRAPH ///< Graph ("context") }; /** Syntax style options The style of the writer output can be controlled by ORing together values from this enumeration. Note that some options are only supported for some syntaxes (e.g. NTriples does not support abbreviation and is always ASCII). */ enum class WriterFlag { ascii = SERD_STYLE_ASCII, ///< Escape all non-ASCII characters }; using WriterFlags = detail::Flags; /// Indexing option enum class ModelFlag { index_SPO = SERD_INDEX_SPO, ///< Subject, Predicate, Object index_SOP = SERD_INDEX_SOP, ///< Subject, Object, Predicate index_OPS = SERD_INDEX_OPS, ///< Object, Predicate, Subject index_OSP = SERD_INDEX_OSP, ///< Object, Subject, Predicate index_PSO = SERD_INDEX_PSO, ///< Predicate, Subject, Object index_POS = SERD_INDEX_POS, ///< Predicate, Object, Subject index_graphs = SERD_INDEX_GRAPHS ///< Support multiple graphs in model }; using ModelFlags = detail::Flags; /** A simple optional wrapper around a wrapped type with a pointer-like API This works like a typical optional type, but only works with Wrapper types, and exploits the fact that these are interally just pointers to avoid adding space overhead for an "is_set" flag, like a generic optional class would. */ template class Optional { public: using CType = typename T::CType; Optional() : _value() {} Optional(std::nullptr_t) : _value() {} Optional(const T& value) : _value(value) {} Optional(T&& value) : _value(std::move(value)) {} Optional(const Optional&) = default; Optional(Optional&&) = default; Optional& operator=(const Optional&) = default; Optional& operator=(Optional&&) = default; void reset() { *this = nullptr; } const T& operator*() const { assert(_value.c_obj()); return _value; } T& operator*() { assert(_value.c_obj()); return _value; } const T* operator->() const { assert(_value.c_obj()); return &_value; } T* operator->() { assert(_value.c_obj()); return &_value; } bool operator==(const Optional& optional) { return (!*this && !optional) || (*this && optional && _value == optional._value); } bool operator!=(const Optional& optional) { return !operator==(optional); } explicit operator bool() const { return _value.c_obj(); } bool operator!() const { return !_value.c_obj(); } inline CType* ptr() { return _value.c_obj(); } inline const CType* ptr() const { return _value.c_obj(); } private: T _value; }; /** A temporary reference to a string This is used as a function parameter type to allow passing either `std::string` or `const char*` without requiring multiple wrapper definitions or the copying overhead of `std::string`. */ struct StringRef { StringRef(const char* str) : _ptr(str) {} StringRef(const std::string& str) : _ptr(str.c_str()) {} StringRef(const StringRef&) = delete; StringRef& operator=(const StringRef&) = delete; operator const char*() const { return _ptr; } private: const char* const _ptr; }; /** @name String Utilities @{ */ inline const char* strerror(const Status status) { return serd_strerror(static_cast(status)); } /** @} @name URI @{ */ inline std::string file_uri_parse(const StringRef& uri, std::string* hostname = nullptr) { char* c_hostname = nullptr; char* c_path = serd_file_uri_parse(uri, &c_hostname); if (hostname && c_hostname) { *hostname = c_hostname; } const std::string path(c_path); free(c_hostname); free(c_path); return path; } /** @} @name Node @{ */ class Node : public detail::Copyable { public: explicit Node(SerdNode* ptr) : Copyable(ptr) {} explicit Node(const SerdNode* ptr) : Copyable(ptr) {} NodeType type() const { return NodeType(serd_node_get_type(c_obj())); } const char* c_str() const { return serd_node_get_string(c_obj()); } std::string str() const { return c_str(); } size_t size() const { return serd_node_get_length(c_obj()); } size_t length() const { return serd_node_get_length(c_obj()); } NodeFlags flags() const { return NodeFlags(serd_node_get_flags(c_obj())); } Optional datatype() const { return Node(serd_node_get_datatype(c_obj())); } Optional language() const { return Node(serd_node_get_language(c_obj())); } Node resolve(const Node& base) const { return Node(serd_node_resolve(c_obj(), base.c_obj())); } bool operator==(const Node& node) const { return serd_node_equals(c_obj(), node.c_obj()); } bool operator!=(const Node& node) const { return !serd_node_equals(c_obj(), node.c_obj()); } bool operator<(const Node& node) const { return serd_node_compare(c_obj(), node.c_obj()) < 0; } operator std::string() const { return std::string(serd_node_get_string(c_obj()), serd_node_get_length(c_obj())); } private: friend class Optional; Node() : Copyable(nullptr) {} }; inline std::ostream& operator<<(std::ostream& os, const Node& node) { return os << node.c_str(); } inline Node make_string(const StringRef& str) { return Node(serd_new_string(str)); } inline Node make_plain_literal(const StringRef& str, const StringRef& lang) { return Node(serd_new_plain_literal(str, lang)); } inline Node make_typed_literal(const StringRef& str, const Node& datatype) { return Node(serd_new_typed_literal(str, datatype.c_obj())); } inline Node make_blank(const StringRef& str) { return Node(serd_new_blank(str)); } inline Node make_curie(const StringRef& str) { return Node(serd_new_curie(str)); } inline Node make_uri(const StringRef& str) { return Node(serd_new_uri(str)); } inline Node make_resolved_uri(const StringRef& str, const Node& base) { return Node(serd_new_resolved_uri(str, base.c_obj())); } inline Node make_file_uri(const StringRef& path) { return Node(serd_new_file_uri(path, nullptr)); } inline Node make_file_uri(const StringRef& path, const StringRef& hostname) { return Node(serd_new_file_uri(path, hostname)); } inline Node make_relative_uri(const StringRef& str, const Node& base, const Optional& root = {}) { return Node(serd_new_relative_uri(str, base.c_obj(), root.ptr())); } inline Node make_decimal(double d, unsigned frac_digits, const Optional& datatype = {}) { return Node(serd_new_decimal(d, frac_digits, datatype.ptr())); } inline Node make_integer(int64_t i, const Optional& datatype = {}) { return Node(serd_new_integer(i, datatype.ptr())); } inline Node make_blob(const void* buf, size_t size, bool wrap_lines, const Optional& datatype = {}) { return Node(serd_new_blob(buf, size, wrap_lines, datatype.ptr())); } /** @} @name World @{ */ class World : public detail::Wrapper { public: World() : Wrapper(serd_world_new()) {} explicit World(SerdWorld* ptr) : Wrapper(ptr) {} Node get_blank() { return Node(serd_world_get_blank(c_obj())); } void set_message_sink(SerdMessageSink msg_sink, void* handle) { serd_world_set_message_sink(c_obj(), msg_sink, handle); } }; /** @} @name Statement @{ */ class Statement : public detail::Wrapper { public: Statement(const SerdStatement* statement) : Wrapper(statement) {} Node node(Field field) { return Node(serd_statement_get_node(c_obj(), static_cast(field))); } Node subject() const { return Node(serd_statement_get_subject(c_obj())); } Node predicate() const { return Node(serd_statement_get_predicate(c_obj())); } Node object() const { return Node(serd_statement_get_object(c_obj())); } Node graph() const { return Node(serd_statement_get_graph(c_obj())); } }; /** @} @name Sink @{ */ class Sink { public: Sink() : _sink(serd_sink_new(this, NULL), detail::Deleter(serd_sink_free)) , _is_user(true) { serd_sink_set_base_func(_sink.get(), s_base); serd_sink_set_prefix_func(_sink.get(), s_prefix); serd_sink_set_statement_func(_sink.get(), s_statement); serd_sink_set_end_func(_sink.get(), s_end); } explicit Sink(const SerdSink* ptr) : _sink(const_cast(ptr), detail::Deleter(nullptr)) , _is_user(false) { } virtual ~Sink() = default; virtual Status base(const Node& uri) { return _is_user ? Status::success : Status(serd_sink_set_base(_sink.get(), uri.c_obj())); } virtual Status prefix(const Node& name, const Node& uri) { return _is_user ? Status::success : Status(serd_sink_set_prefix( _sink.get(), name.c_obj(), uri.c_obj())); } virtual Status statement(StatementFlags flags, const Statement& statement) { return _is_user ? Status::success : Status(serd_sink_write_statement( _sink.get(), flags, statement.c_obj())); } virtual Status write(StatementFlags flags, const Node& subject, const Node& predicate, const Node& object, const Optional& graph = {}) { return _is_user ? Status::success : Status(serd_sink_write(_sink.get(), flags, subject.c_obj(), predicate.c_obj(), object.c_obj(), graph.ptr())); } virtual Status end(const Node& node) { return _is_user ? Status::success : Status(serd_sink_end(_sink.get(), node.c_obj())); } const SerdSink* c_sink() const { return _sink.get(); } SerdSink* c_sink() { return _sink.get(); } private: static SerdStatus s_base(void* handle, const SerdNode* uri) { Sink* const sink = static_cast(handle); return static_cast(sink->base(Node(uri))); } static SerdStatus s_prefix(void* handle, const SerdNode* name, const SerdNode* uri) { Sink* const sink = static_cast(handle); return static_cast(sink->prefix(Node(name), Node(uri))); } static SerdStatus s_statement(void* handle, SerdStatementFlags flags, const SerdStatement* statement) { Sink* const sink = static_cast(handle); return static_cast( sink->statement(StatementFlags(flags), statement)); } static SerdStatus s_end(void* handle, const SerdNode* node) { Sink* const sink = static_cast(handle); return static_cast(sink->end(Node(node))); } std::unique_ptr> _sink; bool _is_user; }; /** @} @name Environment @{ */ class Env : public detail::Copyable { public: explicit Env(const Optional& base = {}) : Copyable(serd_env_new(base.ptr())) { } /// Return the base URI Node base_uri() const { return Node(serd_env_get_base_uri(c_obj())); } /// Set the base URI Status set_base_uri(const Node& uri) { return Status(serd_env_set_base_uri(c_obj(), uri.c_obj())); } /// Set a namespace prefix Status set_prefix(const Node& name, const Node& uri) { return Status(serd_env_set_prefix(c_obj(), name.c_obj(), uri.c_obj())); } /// Set a namespace prefix Status set_prefix(const StringRef& name, const StringRef& uri) { return Status(serd_env_set_prefix_from_strings(c_obj(), name, uri)); } /// Qualify `uri` into a CURIE if possible Optional qualify(const Node& uri) const { return Node(serd_env_qualify(c_obj(), uri.c_obj())); } /// Expand `node` into an absolute URI if possible Optional expand(const Node& node) const { return Node(serd_env_expand(c_obj(), node.c_obj())); } /// Send all prefixes to `sink` void send_prefixes(Sink& sink) const { serd_env_send_prefixes(c_obj(), sink.c_sink()); } bool operator==(const Env& env) { return serd_env_equals(c_obj(), env.c_obj()); } bool operator!=(const Env& env) { return !operator==(env); } }; /** @} @name Reader @{ */ class Reader : public detail::Wrapper { public: Reader(World& world, Syntax syntax, const Sink& sink, size_t stack_size) : Wrapper(serd_reader_new(world.c_obj(), SerdSyntax(syntax), sink.c_sink(), stack_size)) { } void set_strict(bool strict) { serd_reader_set_strict(c_obj(), strict); } void add_blank_prefix(const StringRef& prefix) { serd_reader_add_blank_prefix(c_obj(), prefix); } SerdStatus start_file(const StringRef& uri, bool bulk = true) { return serd_reader_start_file(c_obj(), uri, bulk); } SerdStatus start_stream(SerdReadFunc read_func, SerdStreamErrorFunc error_func, void* stream, const Node& name, size_t page_size) { return serd_reader_start_stream(c_obj(), read_func, error_func, stream, name.c_obj(), page_size); } SerdStatus start_string(const StringRef& utf8, const Optional& name = {}) { return serd_reader_start_string(c_obj(), utf8, name.ptr()); } SerdStatus read_chunk() { return serd_reader_read_chunk(c_obj()); } SerdStatus read_document() { return serd_reader_read_document(c_obj()); } SerdStatus finish() { return serd_reader_finish(c_obj()); } }; /** @} @name Writer @{ */ class ByteSink : public detail::Wrapper { public: ByteSink(SerdWriteFunc sink, void* stream, size_t block_size) : Wrapper(serd_byte_sink_new(sink, stream, block_size)) { } ByteSink(std::ostream& stream) : Wrapper(serd_byte_sink_new(detail::stream_sink, &stream, 1)) { } }; class Writer : public detail::Wrapper, public Sink { public: Writer(World& world, const Syntax syntax, const WriterFlags writer_flags, Env& env, ByteSink& sink) : Wrapper(serd_writer_new( world.c_obj(), SerdSyntax(syntax), writer_flags, env.c_obj(), reinterpret_cast(serd_byte_sink_write), sink.c_obj())) , Sink(serd_writer_get_sink(c_obj())) { } Writer(World& world, const Syntax syntax, const WriterFlags writer_flags, Env& env, SerdWriteFunc write_func, void* stream) : Wrapper(serd_writer_new(world.c_obj(), SerdSyntax(syntax), writer_flags, env.c_obj(), write_func, stream)) , Sink(serd_writer_get_sink(c_obj())) { } Writer(World& world, const Syntax syntax, const WriterFlags writer_flags, Env& env, std::ostream& stream) : Wrapper(serd_writer_new(world.c_obj(), SerdSyntax(syntax), writer_flags, env.c_obj(), detail::stream_sink, &stream)) , Sink(serd_writer_get_sink(c_obj())) { } Status set_root_uri(const Node& uri) { return Status(serd_writer_set_root_uri(c_obj(), uri.c_obj())); } SerdStatus finish() { return serd_writer_finish(c_obj()); } }; /** @} */ class Iter : public detail::Copyable { public: Iter(SerdIter* i) : Copyable(i) {} Iter(const SerdIter* i) : Copyable(i) {} Iter& operator++() { serd_iter_next(c_obj()); return *this; } Statement operator*() const { return Statement(serd_iter_get(c_obj())); } bool operator==(const Iter& i) const { return serd_iter_equals(c_obj(), i.c_obj()); } bool operator!=(const Iter& i) const { return !serd_iter_equals(c_obj(), i.c_obj()); } }; class Range : public detail::Copyable { public: Range(SerdRange* r) : Copyable(r) {} Range(const SerdRange* r) : Copyable(r) {} Iter begin() const { return Iter(serd_range_begin(c_obj())); } Iter end() const { return Iter(serd_range_end(c_obj())); } bool operator==(const Range& i) const { return serd_range_equals(c_obj(), i.c_obj()); } bool operator!=(const Range& i) const { return !serd_range_equals(c_obj(), i.c_obj()); } }; class Model : public detail::Copyable { public: using value_type = Statement; using iterator = Iter; using const_iterator = Iter; Model(World& world, ModelFlags flags) : Copyable(serd_model_new(world.c_obj(), flags)) { } Model(World& world, ModelFlag flag) : Copyable(serd_model_new(world.c_obj(), ModelFlags(flag))) { } size_t size() const { return serd_model_size(c_obj()); } bool empty() const { return serd_model_empty(c_obj()); } void insert(const Statement& s) { serd_model_insert(c_obj(), s.c_obj()); } void insert(const Node& s, const Node& p, const Node& o, const Optional& g = {}) { serd_model_add(c_obj(), s.c_obj(), p.c_obj(), o.c_obj(), g.ptr()); } Iter find(const Optional& s, const Optional& p, const Optional& o, const Optional& g = {}) const { return Iter( serd_model_find(c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr())); } Range range(const Optional& s, const Optional& p, const Optional& o, const Optional& g = {}) const { return Range( serd_model_range(c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr())); } Optional get(const Optional& s, const Optional& p, const Optional& o, const Optional& g = {}) const { return Node( serd_model_get(c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr())); } Optional get_statement(const Optional& s, const Optional& p, const Optional& o, const Optional& g = {}) const { return Statement(serd_model_get_statement( c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr())); } bool ask(const Optional& s, const Optional& p, const Optional& o, const Optional& g = {}) const { return serd_model_ask(c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr()); } size_t count(const Optional& s, const Optional& p, const Optional& o, const Optional& g = {}) const { return serd_model_count(c_obj(), s.ptr(), p.ptr(), o.ptr(), g.ptr()); } bool operator==(const Model& model) const { return serd_model_equals(c_obj(), model.c_obj()); } bool operator!=(const Model& model) const { return !serd_model_equals(c_obj(), model.c_obj()); } Range all() const { return Range(serd_model_all(c_obj())); } iterator begin() const { return iterator(serd_model_begin(c_obj())); } iterator end() const { return iterator(serd_model_end(c_obj())); } }; class Inserter : public detail::Wrapper, public Sink { public: Inserter(Model& model, Env& env, const Optional& default_graph = {}) : Wrapper(serd_inserter_new(model.c_obj(), env.c_obj(), default_graph.ptr())) , Sink(serd_inserter_get_sink(c_obj())) { } }; } // namespace serd /** @} */ #endif /* SERD_SERD_HPP */