/* 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 { class Node; /** Return status code. */ enum class Status { Success = SERD_SUCCESS, /**< No error */ Failure = SERD_FAILURE, /**< Non-fatal failure */ ErrUnknown = SERD_ERR_UNKNOWN, /**< Unknown error */ ErrBadSyntax = SERD_ERR_BAD_SYNTAX, /**< Invalid syntax */ ErrBadArg = SERD_ERR_BAD_ARG, /**< Invalid argument */ ErrNotFound = SERD_ERR_NOT_FOUND, /**< Not found */ ErrIdClash = SERD_ERR_ID_CLASH, /**< Clashing blank node IDs */ ErrBadCurie = SERD_ERR_BAD_CURIE, /**< Invalid CURIE */ ErrInternal = SERD_ERR_INTERNAL /**< Unexpected internal error */ }; 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) {} }; template size_t stream_sink(const void* buf, size_t size, size_t nmemb, void* stream) { using Stream = std::basic_ostream; try { Stream* const s = static_cast(stream); s->write(static_cast(buf), std::streamsize(size * nmemb)); if (s->good()) { return nmemb; } } catch (...) { } return 0; } } // namespace detail /** 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. The fact that these are interally just pointers is used 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; }; using Type = SerdType; using NodeFlags = SerdNodeFlags; using StyleFlags = SerdStyleFlags; using IndexOptions = SerdIndexOptions; using Field = SerdField; using StatementFlags = SerdStatementFlags; class Statement; /** RDF syntax. */ enum class Syntax { /** Turtle - Terse RDF Triple Language (UTF-8). @see Turtle */ Turtle = SERD_TURTLE, /** NTriples - Line-based RDF triples (ASCII). @see NTriples */ NTriples = SERD_NTRIPLES, /** NQuads - Line-based RDF quads (UTF-8). @see NQuads */ NQuads = SERD_NQUADS, /** TriG - Terse RDF quads (UTF-8). @see Trig */ TriG = SERD_TRIG }; /** 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 Style { ascii = SERD_STYLE_ASCII, /**< Escape all non-ASCII characters. */ }; inline StyleFlags operator|(const Style a, const Style b) { return (StyleFlags)(int(a) | int(b)); } inline StyleFlags operator|(const StyleFlags a, const Style b) { return (StyleFlags)(int(a) | int(b)); } /** Indexing option. */ enum class IndexOption { SPO = SERD_SPO, /**< Subject, Predicate, Object */ SOP = SERD_SOP, /**< Subject, Object, Predicate */ OPS = SERD_OPS, /**< Object, Predicate, Subject */ OSP = SERD_OSP, /**< Object, Subject, Predicate */ PSO = SERD_PSO, /**< Predicate, Subject, Object */ POS = SERD_POS /**< Predicate, Object, Subject */ }; inline IndexOptions operator|(const IndexOption a, const IndexOption b) { return (IndexOptions)(int(a) | int(b)); } inline IndexOptions operator|(const IndexOptions a, const IndexOption b) { return (IndexOptions)(int(a) | int(b)); } /** @name String Utilities @{ */ inline const char* strerror(const Status status) { return serd_strerror((SerdStatus)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) {} Type type() const { return 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 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())); } 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())); } // TODO: serd_node_resolve? 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_error_sink(SerdErrorSink error_sink, void* handle) { serd_world_set_error_sink(c_obj(), error_sink, handle); } // operator SerdWorld*() { return c_obj(); } }; /** @} @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(), 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), 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 nodes(StatementFlags flags, const Node& subject, const Node& predicate, const Node& object, const Optional& graph = {}) { return _is_user ? Status::Success : Status(serd_sink_write_nodes(_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())); } // operator const SerdSink*() const { return _sink.get(); } // operator SerdSink*() { return _sink.get(); } const SerdSink* c_sink() const { return _sink.get(); } SerdSink* c_sink() { return _sink.get(); } private: static SerdStatus s_base(void* handle, const SerdNode* uri) { return (SerdStatus)((Sink*)handle)->base(Node(uri)); } static SerdStatus s_prefix(void* handle, const SerdNode* name, const SerdNode* uri) { return (SerdStatus)((Sink*)handle)->prefix(Node(name), Node(uri)); } static SerdStatus s_statement(void* handle, SerdStatementFlags flags, const SerdStatement* statement) { return (SerdStatus)((Sink*)handle)->statement(flags, statement); } static SerdStatus s_end(void* handle, const SerdNode* node) { return (SerdStatus)((Sink*)handle)->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) { 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); } // operator SerdEnv*() { return c_obj(); } }; /** @} @name Reader @{ */ class Reader : public detail::Wrapper, public Sink { 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)) { } template ByteSink(std::basic_ostream& stream) : Wrapper(serd_byte_sink_new(detail::stream_sink, &stream, 1)) { } // operator SerdByteSink*() { return c_obj(); } }; class Writer : public detail::Wrapper, public Sink { public: Writer(World& world, const Syntax syntax, const StyleFlags style, Env& env, ByteSink& sink) : Wrapper(serd_writer_new(world.c_obj(), SerdSyntax(syntax), style, env.c_obj(), (SerdWriteFunc)serd_byte_sink_write, sink.c_obj())) , Sink(serd_writer_get_sink(c_obj())) { } Writer(World& world, const Syntax syntax, const StyleFlags style, Env& env, SerdWriteFunc write_func, void* stream) : Wrapper(serd_writer_new(world.c_obj(), SerdSyntax(syntax), style, env.c_obj(), write_func, stream)) , Sink(serd_writer_get_sink(c_obj())) { } // FIXME: test template Writer(World& world, const Syntax syntax, const StyleFlags style, Env& env, std::basic_ostream& stream) : Wrapper(serd_writer_new(world.c_obj(), SerdSyntax(syntax), style, 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) { return serd_iter_equals(c_obj(), i.c_obj()); } bool operator!=(const Iter& i) { 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) { return serd_range_equals(c_obj(), i.c_obj()); } bool operator!=(const Range& i) { 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, SerdIndexOptions indices, SerdModelFlags flags) : Copyable(serd_model_new(world.c_obj(), indices, flags)) { } Model(World& world, IndexOption indices, SerdModelFlags flags) : Copyable(serd_model_new(world.c_obj(), (SerdIndexOptions)indices, flags)) { } 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 = {}) { 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 = {}) { 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 = {}) { return Node( serd_model_get(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()); } 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())); } // operator SerdModel*() { return 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 */