/* Copyright 2019-2021 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. */ #ifndef SERD_SERD_HPP #define SERD_SERD_HPP #include "serd/Flags.hpp" // IWYU pragma: export #include "serd/Optional.hpp" // IWYU pragma: export #include "serd/StringView.hpp" // IWYU pragma: export #include "serd/detail/Copyable.hpp" // IWYU pragma: export #include "serd/detail/DynamicWrapper.hpp" // IWYU pragma: export #include "serd/detail/StaticWrapper.hpp" // IWYU pragma: export #include "serd/detail/Wrapper.hpp" // IWYU pragma: export #include "serd/serd.h" #include #include #include #include #include #include #include #include #include #include #include namespace serd { /** @defgroup serdpp Serd C++ API @{ */ /** @defgroup serdpp_status Status Codes @{ */ /// @copydoc SerdStatus enum class Status { success = SERD_SUCCESS, ///< @copydoc SERD_SUCCESS failure = SERD_FAILURE, ///< @copydoc SERD_FAILURE unknown_error = SERD_UNKNOWN_ERROR, ///< @copydoc SERD_UNKNOWN_ERROR no_data = SERD_NO_DATA, ///< @copydoc SERD_NO_DATA overflow = SERD_OVERFLOW, ///< @copydoc SERD_OVERFLOW bad_alloc = SERD_BAD_ALLOC, ///< @copydoc SERD_BAD_ALLOC bad_arg = SERD_BAD_ARG, ///< @copydoc SERD_BAD_ARG bad_call = SERD_BAD_CALL, ///< @copydoc SERD_BAD_CALL bad_curie = SERD_BAD_CURIE, ///< @copydoc SERD_BAD_CURIE bad_cursor = SERD_BAD_CURSOR, ///< @copydoc SERD_BAD_CURSOR bad_event = SERD_BAD_EVENT, ///< @copydoc SERD_BAD_EVENT bad_index = SERD_BAD_INDEX, ///< @copydoc SERD_BAD_INDEX bad_label = SERD_BAD_LABEL, ///< @copydoc SERD_BAD_LABEL bad_literal = SERD_BAD_LITERAL, ///< @copydoc SERD_BAD_LITERAL bad_pattern = SERD_BAD_PATTERN, ///< @copydoc SERD_BAD_PATTERN bad_read = SERD_BAD_READ, ///< @copydoc SERD_BAD_READ bad_stack = SERD_BAD_STACK, ///< @copydoc SERD_BAD_STACK bad_syntax = SERD_BAD_SYNTAX, ///< @copydoc SERD_BAD_SYNTAX bad_text = SERD_BAD_TEXT, ///< @copydoc SERD_BAD_TEXT bad_uri = SERD_BAD_URI, ///< @copydoc SERD_BAD_URI bad_write = SERD_BAD_WRITE, ///< @copydoc SERD_BAD_WRITE bad_data = SERD_BAD_DATA, ///< @copydoc SERD_BAD_DATA }; /// @copydoc serd_strerror inline const char* strerror(const Status status) { return serd_strerror(static_cast(status)); } /** @} @defgroup serdpp_string String Utilities @{ */ /** Encode `size` bytes of `buf` into `str`, which must be large enough. @param buf Input binary data (vector-like container of bytes). @return A base64 encoded representation of the data in `buf`. */ template inline std::string base64_encode(const Container& buf) { #if 0 const size_t length{serd_base64_encoded_length(buf.size(), wrap_lines)}; std::string str(length + 1, '\0'); serd_base64_encode(&str.at(0), buf.data(), buf.size(), wrap_lines); return str; #endif (void)buf; return ""; // FIXME } // FIXME #if 0 /** Decode a base64 string. Container must be a vector-like container of bytes. @param str Base64 string to decode. @return The decoded data represented in `str`. */ template> inline Container base64_decode(StringView str) { # if 0 size_t size{serd_base64_decoded_size(str.length())}; Container buf(size, 0); serd_base64_decode(&buf.at(0), &size, str.c_str(), str.length()); buf.resize(size); return buf; # endif (void)str; return {}; // FIXME } #endif // TODO: serd_canonical_path // TODO: grouping? static inline size_t stream_write(const void* buf, size_t size, size_t nmemb, void* sink) noexcept { (void)size; assert(size == 1); std::ostream& os = *static_cast(sink); try { os.write(static_cast(buf), std::streamsize(nmemb)); return os.good() ? nmemb : 0u; } catch (...) { } return 0; } /** @} @defgroup serdpp_syntax Syntax Utilities @{ */ /// @copydoc SerdSyntax enum class Syntax { empty = SERD_SYNTAX_EMPTY, ///< @copydoc SERD_SYNTAX_EMPTY Turtle = SERD_TURTLE, ///< @copydoc SERD_TURTLE NTriples = SERD_NTRIPLES, ///< @copydoc SERD_NTRIPLES NQuads = SERD_NQUADS, ///< @copydoc SERD_NQUADS TriG = SERD_TRIG ///< @copydoc SERD_TRIG }; /// @copydoc serd_syntax_by_name inline Syntax syntax_by_name(StringView name) { return Syntax(serd_syntax_by_name(name.c_str())); } /// @copydoc serd_guess_syntax inline Syntax guess_syntax(StringView filename) { return Syntax(serd_guess_syntax(filename.c_str())); } /** Return whether a syntax can represent multiple graphs. @return True for @ref Syntax::NQuads and @ref Syntax::TriG, false otherwise. */ inline bool syntax_has_graphs(const Syntax syntax) { return serd_syntax_has_graphs(static_cast(syntax)); } /** @} @defgroup serdpp_data Data @{ @defgroup serdpp_uri URI @{ */ /** Get the unescaped path and hostname from a file URI. The returned path and `*hostname` must be freed with serd_free(). @param uri A file URI. @param hostname If non-null, set to the hostname, if present. @return A filesystem path. */ inline std::string parse_file_uri(StringView uri, std::string* hostname = nullptr) { char* c_hostname = nullptr; char* c_path = serd_parse_file_uri(nullptr, uri.data(), &c_hostname); if (hostname && c_hostname) { *hostname = c_hostname; } std::string path{c_path}; serd_free(nullptr, c_hostname); serd_free(nullptr, c_path); return path; } /// @copydoc serd_uri_string_has_scheme inline bool uri_string_has_scheme(StringView string) { return serd_uri_string_has_scheme(string.c_str()); } /** A parsed URI. This directly refers to slices in other strings, it does not own any memory itself. Thus, URIs can be parsed and/or resolved against a base URI in-place without allocating memory. */ class URI { public: /** Component of a URI. Note that there is a distinction between a component being non-present and present but empty. For example, "file:///path" has an empty authority, while "file:/path" has no authority. A non-present component has its `data()` pointer set to null, while an empty component has a data pointer, but length zero. */ using Component = StringView; /// Construct a URI by parsing a URI string explicit URI(StringView str) : _uri{serd_parse_uri(str.data())} {} /// Construct a URI from a C URI view explicit URI(const SerdURIView& uri) : _uri{uri} {} /// Return the scheme of this URI Component scheme() const { return make_component(_uri.scheme); } /// Return the authority of this URI Component authority() const { return make_component(_uri.authority); } /// Return the path prefix of this URI, which is set if it has been resolved Component path_prefix() const { return make_component(_uri.path_prefix); } /// Return the path (suffix) of this URI Component path() const { return make_component(_uri.path); } /// Return the query Component query() const { return make_component(_uri.query); } /// Return the fragment of this URI Component fragment() const { return make_component(_uri.fragment); } /// Return this URI resolved against `base` URI resolve(const URI& base) const { return URI{serd_resolve_uri(_uri, base._uri)}; } /// Return URI as a string std::string string() const { std::ostringstream ss; serd_write_uri(_uri, stream_write, &ss); return ss.str(); } /// Return this URI as a string relative to `base` std::string relative_string(const URI& base) const { std::ostringstream ss; const SerdURIView rel = serd_relative_uri(_uri, base._uri); serd_write_uri(rel, stream_write, &ss); return ss.str(); } /** Return this URI as a string relative to `base` but constrained to `root`. The returned URI string is relative iff this URI is a child of both `base` and `root`. The `root` must be a prefix of `base` and can be used keep up-references ("../") within a certain namespace. */ std::string relative_string(const URI& base, const URI& root) const { if (serd_uri_is_within(_uri, root._uri)) { return relative_string(base); } return string(); } /// Return a pointer to the underlying C object const SerdURIView* cobj() const { return &_uri; } private: static Component make_component(const SerdStringView slice) { return slice.buf ? Component{slice.buf, slice.len} : Component{}; } SerdURIView _uri; }; inline std::ostream& operator<<(std::ostream& os, const URI& uri) { serd_write_uri(*uri.cobj(), stream_write, &os); return os; } /** @} @defgroup serdpp_node Node @{ */ /// @copydoc SerdNodeType enum class NodeType { literal = SERD_LITERAL, ///< @copydoc SERD_LITERAL URI = SERD_URI, ///< @copydoc SERD_URI blank = SERD_BLANK, ///< @copydoc SERD_BLANK variable = SERD_VARIABLE, ///< @copydoc SERD_VARIABLE }; /// @copydoc SerdNodeFlag enum class NodeFlag { is_long = SERD_IS_LONG, ///< @copydoc SERD_IS_LONG has_datatype = SERD_HAS_DATATYPE, ///< @copydoc SERD_HAS_DATATYPE has_language = SERD_HAS_LANGUAGE, ///< @copydoc SERD_HAS_LANGUAGE }; /// Bitwise OR of NodeFlag values using NodeFlags = Flags; template using NodeHandle = detail::StaticAllocatedCopyable; // template // class NodeWrapper; /// A view of an immutable node // using NodeView = NodeWrapper; class NodeView; /// Common base class for any wrapped node template class NodeWrapper : public NodeHandle { public: template // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) NodeWrapper(const NodeWrapper& node) : NodeHandle{node} {} /// @copydoc serd_node_type NodeType type() const { return NodeType(serd_node_type(this->cobj())); } /// @copydoc serd_node_string const char* c_str() const { return serd_node_string(this->cobj()); } /// @copydoc serd_node_string StringView str() const { return StringView{c_str(), length()}; } /// @copydoc serd_node_length size_t size() const { return serd_node_length(this->cobj()); } /// @copydoc serd_node_length size_t length() const { return serd_node_length(this->cobj()); } /// @copydoc serd_node_datatype Optional datatype() const; /// @copydoc serd_node_language Optional language() const; /// @copydoc serd_node_string_view StringView string_view() const { return StringView{c_str(), length()}; } /// @copydoc serd_node_uri_view SerdURIView uri_view() const { return serd_node_uri_view(this->cobj()); } /// Returns a newly allocated copy of the node's string explicit operator std::string() const { return std::string{c_str(), length()}; } // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) explicit operator StringView() const { return StringView(c_str(), length()); } // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) explicit operator SerdStringView() const { return SerdStringView{c_str(), length()}; } /// Return a pointer to the first character in the node's string const char* begin() const { return c_str(); } /// Return a pointer to the null terminator at the end of the node's string const char* end() const { return c_str() + length(); } /// Return true if the node's string is empty bool empty() const { return length() == 0; } protected: explicit NodeWrapper(CObj* const ptr) : NodeHandle{ptr} {} }; /// A non-owning constant view of some other node class NodeView : public NodeWrapper { public: /// Create a view of a C node pointer // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) NodeView(const SerdNode* const ptr) : NodeWrapper{ptr} {} /// Create a view of some other node template // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) NodeView(const NodeWrapper& node) : NodeWrapper{node} {} }; /// @copydoc serd_node_datatype template inline Optional NodeWrapper::datatype() const { return NodeView{serd_node_datatype(this->cobj())}; } /// @copydoc serd_node_language template inline Optional NodeWrapper::language() const { return NodeView{serd_node_language(this->cobj())}; } /** Compare two nodes. Nodes are ordered first by type, then by string value, then by language or datatype, if present. */ inline bool operator<(const NodeView& lhs, const NodeView& rhs) { return serd_node_compare(lhs.cobj(), rhs.cobj()) < 0; } /// An RDF node class Node : public NodeWrapper { public: /// Create a node by taking ownership of a C node explicit Node(SerdNode* const node) : NodeWrapper{node} {} /// Create a node by copying another node // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) Node(const NodeView& node) : NodeWrapper{node} {} explicit Node(const SerdValue& value) : NodeWrapper{serd_new_value(nullptr, value)} {} /// Create an xsd:boolean node from a ``bool`` explicit Node(const bool b) : NodeWrapper{serd_new_value(nullptr, serd_bool(b))} {} /// Create an xsd:double node from a ``double`` explicit Node(const double d) : NodeWrapper{serd_new_value(nullptr, serd_double(d))} {} /// Create an xsd:float node from a ``float`` explicit Node(const float f) : NodeWrapper{serd_new_value(nullptr, serd_float(f))} {} /// Create an xsd:long node from a ``int64_t`` explicit Node(const int64_t i) : NodeWrapper{serd_new_value(nullptr, serd_long(i))} {} /// Create an xsd:int node from a ``int32_t`` explicit Node(const int32_t i) : NodeWrapper{serd_new_value(nullptr, serd_int(i))} {} /// Create an xsd:short node from a ``int16_t`` explicit Node(const int16_t i) : NodeWrapper{serd_new_value(nullptr, serd_short(i))} {} /// Create an xsd:byte node from a ``int8_t`` explicit Node(const int8_t i) : NodeWrapper{serd_new_value(nullptr, serd_byte(i))} {} /// Create an xsd:unsignedLong node from a ``int64_t`` explicit Node(const uint64_t i) : NodeWrapper{serd_new_value(nullptr, serd_ulong(i))} {} /// Create an xsd:unsignedInt node from a ``int32_t`` explicit Node(const uint32_t i) : NodeWrapper{serd_new_value(nullptr, serd_uint(i))} {} /// Create an xsd:unsignedShort node from a ``int16_t`` explicit Node(const uint16_t i) : NodeWrapper{serd_new_value(nullptr, serd_ushort(i))} {} /// Create an xsd:unsignedByte node from a ``int8_t`` explicit Node(const uint8_t i) : NodeWrapper{serd_new_value(nullptr, serd_ubyte(i))} {} Node(const Node& node) = default; Node& operator=(const Node& node) = default; Node(Node&& node) = default; Node& operator=(Node&& node) = default; ~Node() = default; private: friend class Optional; friend class Caret; explicit Node(std::nullptr_t) : NodeWrapper{nullptr} {} }; inline std::ostream& operator<<(std::ostream& os, const NodeView& node) { return os << node.c_str(); } /// Create a new simple "token" node inline Node make_token(const NodeType type, StringView str) { return Node{serd_new_token(nullptr, static_cast(type), str)}; } /// Create a new plain literal node with no language from `str` inline Node make_string(StringView str) { return Node{serd_new_string(nullptr, str)}; } /// @copydoc serd_new_uri inline Node make_uri(StringView uri) { return Node{serd_new_uri(nullptr, uri)}; } /// @copydoc serd_new_parsed_uri inline Node make_uri(SerdURIView uri) { return Node{serd_new_parsed_uri(nullptr, uri)}; } /// @copydoc serd_new_parsed_uri inline Node make_uri(URI uri) { return Node{serd_new_parsed_uri(nullptr, *uri.cobj())}; } /// Create a new file URI node from a local filesystem path inline Node make_file_uri(StringView path) { return Node{serd_new_file_uri(nullptr, path, SERD_EMPTY_STRING())}; } /// Create a new file URI node from a filesystem path on some host inline Node make_file_uri(StringView path, StringView hostname) { return Node{serd_new_file_uri(nullptr, path, hostname)}; } /// @copydoc serd_new_literal inline Node make_literal(StringView string, NodeFlags flags, StringView meta) { return Node{ serd_new_literal(nullptr, string, static_cast(flags), meta)}; } /// Create a new blank node from a local name inline Node make_blank(StringView str) { return Node{serd_new_token(nullptr, SERD_BLANK, str)}; } /// Create a new plain literal with an optional language tag inline Node make_plain_literal(StringView str, StringView lang) { return Node{serd_new_literal(nullptr, str, SERD_HAS_LANGUAGE, lang)}; } /// Create a new typed literal node from `str` inline Node make_typed_literal(StringView str, const StringView datatype) { return Node{serd_new_literal(nullptr, str, SERD_HAS_DATATYPE, datatype)}; } template inline SerdValue value(const T value); template<> inline SerdValue value(const bool value) { return serd_bool(value); } template<> inline SerdValue value(const double value) { return serd_double(value); } template<> inline SerdValue value(const float value) { return serd_float(value); } template<> inline SerdValue value(const int64_t value) { return serd_long(value); } template<> inline SerdValue value(const int32_t value) { return serd_int(value); } template<> inline SerdValue value(const int16_t value) { return serd_short(value); } template<> inline SerdValue value(const int8_t value) { return serd_byte(value); } template<> inline SerdValue value(const uint64_t value) { return serd_ulong(value); } template<> inline SerdValue value(const uint32_t value) { return serd_uint(value); } template<> inline SerdValue value(const uint16_t value) { return serd_ushort(value); } template<> inline SerdValue value(const uint8_t value) { return serd_ubyte(value); } template inline Node make(const T v) { return Node{value(v)}; } /// @copydoc serd_new_decimal inline Node make_decimal(double d) { return Node{serd_new_decimal(nullptr, d)}; } /// @copydoc serd_new_integer inline Node make_integer(int64_t i) { return Node{serd_new_integer(nullptr, i)}; } /** Create a new canonical xsd:base64Binary literal. This function can be used to make a node out of arbitrary binary data, which can be decoded using base64_decode(). @param buf Raw binary data to encode in node. @param size Size of `buf` in bytes. */ inline Node make_base64(const void* buf, size_t size) { return Node{serd_new_base64(nullptr, buf, size)}; } /// Prototype for Node get() templates template inline T get(NodeView node); /// @copydoc serd_get_boolean template<> inline bool get(NodeView node) { return serd_get_value_as(node.cobj(), SERD_BOOL, true).data.as_bool; } /// @copydoc serd_get_double template<> inline double get(NodeView node) { return serd_get_value_as(node.cobj(), SERD_DOUBLE, true).data.as_double; } /// @copydoc serd_get_float template<> inline float get(NodeView node) { return serd_get_value_as(node.cobj(), SERD_FLOAT, true).data.as_float; } /// @copydoc serd_get_long template<> inline int64_t get(NodeView node) { return serd_get_value_as(node.cobj(), SERD_LONG, true).data.as_long; } /// @copydoc serd_get_ulong template<> inline uint64_t get(NodeView node) { return serd_get_value_as(node.cobj(), SERD_ULONG, true).data.as_ulong; } /** @} @defgroup serdpp_nodes Nodes @{ */ // TODO /** @} @defgroup serdpp_caret Caret @{ */ /// Caret handle template using CaretHandle = detail::StaticAllocatedCopyable; /// Caret wrapper template class CaretWrapper : public CaretHandle { public: explicit CaretWrapper(CObj* caret) : CaretHandle{caret} {} template // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) CaretWrapper(const CaretWrapper& caret) : CaretHandle{caret.cobj()} {} /// @copydoc serd_caret_name NodeView name() const { return NodeView(serd_caret_name(this->cobj())); } /// @copydoc serd_caret_line unsigned line() const { return serd_caret_line(this->cobj()); } /// @copydoc serd_caret_column unsigned column() const { return serd_caret_column(this->cobj()); } }; /// Caret view using CaretView = CaretWrapper; /// Extra data managed by mutable (user created) Caret struct CaretData { Node name_node; }; /// @copydoc SerdCaret class Caret : private CaretData , public CaretWrapper { public: /** Create a new caret. @param name The name of the document or stream (usually a file URI) @param line The line number in the document (1-based) @param col The column number in the document (1-based) */ Caret(const NodeView& name, const unsigned line, const unsigned col) : CaretData{Node{name}} , CaretWrapper{serd_caret_new(nullptr, name_node.cobj(), line, col)} {} // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) Caret(const CaretView& caret) : Caret(caret.name(), caret.line(), caret.column()) {} private: friend class Optional; friend class Statement; explicit Caret(std::nullptr_t) : CaretData{Node{nullptr}} , CaretWrapper{nullptr} {} }; /** @} @defgroup serdpp_statement Statement @{ */ /// @copydoc SerdField enum class Field { subject = SERD_SUBJECT, ///< @copydoc SERD_SUBJECT predicate = SERD_PREDICATE, ///< @copydoc SERD_PREDICATE object = SERD_OBJECT, ///< @copydoc SERD_OBJECT graph = SERD_GRAPH ///< @copydoc SERD_GRAPH }; template using StatementHandle = detail::StaticAllocatedCopyable; template class StatementWrapper; /// View of a constant statement using StatementView = StatementWrapper; /// Extra data managed by mutable (user created) Statement struct StatementData { Node _subject; Node _predicate; Node _object; Optional _graph; Optional _caret; }; /// Statement wrapper template class StatementWrapper : public StatementHandle { public: explicit StatementWrapper(CObj* statement) : StatementHandle{statement} {} template // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) StatementWrapper(const StatementWrapper& statement) : StatementHandle{statement} {} /// @copydoc serd_statement_node NodeView node(Field field) const { return NodeView{ serd_statement_node(this->cobj(), static_cast(field))}; } /// @copydoc serd_statement_subject NodeView subject() const { return NodeView{serd_statement_subject(this->cobj())}; } /// @copydoc serd_statement_predicate NodeView predicate() const { return NodeView{serd_statement_predicate(this->cobj())}; } /// @copydoc serd_statement_object NodeView object() const { return NodeView{serd_statement_object(this->cobj())}; } /// @copydoc serd_statement_graph Optional graph() const { return NodeView{serd_statement_graph(this->cobj())}; } /// @copydoc serd_statement_caret Optional caret() const { return CaretView{serd_statement_caret(this->cobj())}; } /// @copydoc serd_statement_matches bool matches(Optional subject, Optional predicate, Optional object, Optional graph = {}) const { return serd_statement_matches(this->cobj(), subject.cobj(), predicate.cobj(), object.cobj(), graph.cobj()); } private: template friend class CursorWrapper; StatementWrapper() : StatementHandle{nullptr} {} }; /// @copydoc SerdStatement class Statement : public StatementData , public StatementWrapper { public: Statement(const NodeView& s, const NodeView& p, const NodeView& o, const NodeView& g, Optional caret = {}) : StatementData{s, p, o, g, caret ? *caret : Optional{}} , StatementWrapper{serd_statement_new(nullptr, _subject.cobj(), _predicate.cobj(), _object.cobj(), _graph.cobj(), _caret.cobj())} {} Statement(const NodeView& s, const NodeView& p, const NodeView& o, Optional caret = {}) : StatementData{s, p, o, {}, caret ? *caret : Optional{}} , StatementWrapper{serd_statement_new(nullptr, _subject.cobj(), _predicate.cobj(), _object.cobj(), nullptr, _caret.cobj())} {} // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) Statement(const StatementView& statement) : StatementData{statement.subject(), statement.predicate(), statement.object(), statement.graph() ? *statement.graph() : Optional{}, statement.caret() ? *statement.caret() : Optional{}} , StatementWrapper{statement} {} }; /** @} @} @defgroup serdpp_world World @{ */ /// @copydoc SerdLogLevel enum class LogLevel { emergency = SERD_LOG_LEVEL_EMERGENCY, ///< @copydoc SERD_LOG_LEVEL_EMERGENCY alert = SERD_LOG_LEVEL_ALERT, ///< @copydoc SERD_LOG_LEVEL_ALERT critical = SERD_LOG_LEVEL_CRITICAL, ///< @copydoc SERD_LOG_LEVEL_CRITICAL error = SERD_LOG_LEVEL_ERROR, ///< @copydoc SERD_LOG_LEVEL_ERROR warning = SERD_LOG_LEVEL_WARNING, ///< @copydoc SERD_LOG_LEVEL_WARNING notice = SERD_LOG_LEVEL_NOTICE, ///< @copydoc SERD_LOG_LEVEL_NOTICE info = SERD_LOG_LEVEL_INFO, ///< @copydoc SERD_LOG_LEVEL_INFO debug = SERD_LOG_LEVEL_DEBUG ///< @copydoc SERD_LOG_LEVEL_DEBUG }; /// Extended fields for a log message using LogFields = std::map; /// User-provided callback function for handling a log message using LogFunc = std::function; /// @copydoc SerdWorld class World : public detail::StaticWrapper { public: World() : StaticWrapper{serd_world_new(nullptr)} {} NodeView get_blank() { return NodeView(serd_world_get_blank(cobj())); } void set_message_func(LogFunc log_func) { _log_func = std::move(log_func); serd_set_log_func(cobj(), s_log_func, this); } SERD_LOG_FUNC(4, 5) Status log(const LogLevel level, const LogFields& fields, const char* const fmt, ...) { va_list args; va_start(args, fmt); std::vector c_fields(fields.size()); size_t index = 0; for (const auto& f : fields) { c_fields[index].key = f.first.c_str(); c_fields[index].value = f.second.c_str(); ++index; } const SerdStatus st = serd_vxlogf(cobj(), static_cast(level), fields.size(), c_fields.data(), fmt, args); va_end(args); return static_cast(st); } private: SERD_LOG_FUNC(1, 0) static std::string format(const char* fmt, va_list args) noexcept { va_list args_copy; va_copy(args_copy, args); const auto n_bytes = static_cast(vsnprintf(nullptr, 0, fmt, args_copy)); va_end(args_copy); #if __cplusplus >= 201703L std::string result(n_bytes, '\0'); vsnprintf(result.data(), n_bytes + 1u, fmt, args); #else std::vector str(n_bytes + 1u, '\0'); vsnprintf(str.data(), n_bytes + 1u, fmt, args); std::string result(str.data(), size_t(n_bytes)); #endif return result; } static SerdStatus s_log_func(void* handle, const SerdLogLevel level, const size_t n_fields, const SerdLogField* const fields, const SerdStringView message) noexcept { const auto* const self = static_cast(handle); try { LogFields cpp_fields; for (size_t i = 0; i < n_fields; ++i) { cpp_fields.emplace(fields[i].key, fields[i].value); } return static_cast( self->_log_func(static_cast(level), cpp_fields, std::string(message.buf, message.len))); } catch (...) { return SERD_UNKNOWN_ERROR; } } LogFunc _log_func{}; }; /** @} @defgroup serdpp_streaming Data Streaming @{ */ /** @defgroup serdpp_event Events @{ */ // TODO /// @copydoc SerdStatementFlag enum class StatementFlag { empty_S = SERD_EMPTY_S, ///< @copydoc SERD_EMPTY_S anon_S = SERD_ANON_S, ///< @copydoc SERD_ANON_S anon_O = SERD_ANON_O, ///< @copydoc SERD_ANON_O list_S = SERD_LIST_S, ///< @copydoc SERD_LIST_S list_O = SERD_LIST_O, ///< @copydoc SERD_LIST_O terse_S = SERD_TERSE_S, ///< @copydoc SERD_TERSE_S terse_O = SERD_TERSE_O ///< @copydoc SERD_TERSE_O }; /// Bitwise OR of StatementFlag values using StatementFlags = Flags; /// @copydoc SerdEventType enum class EventType { base = SERD_BASE, ///< @copydoc SERD_BASE prefix = SERD_PREFIX, ///< @copydoc SERD_PREFIX statement = SERD_STATEMENT, ///< @copydoc SERD_STATEMENT end = SERD_END ///< @copydoc SERD_END }; struct BaseEvent { NodeView uri; ///< Base URI }; struct PrefixEvent { NodeView name; ///< Prefix name NodeView uri; ///< Namespace URI }; struct StatementEvent { StatementFlags flags; ///< Flags for pretty-printing StatementView statement; ///< Statement }; struct EndEvent { NodeView node; ///< Anonymous node that is finished }; class Event { public: explicit Event(const SerdEvent* const e) : _event{*e} {} EventType type() const { return static_cast(_event.type); } BaseEvent base() const { assert(_event.type == SERD_BASE); return {NodeView{_event.base.uri}}; } PrefixEvent prefix() const { assert(_event.type == SERD_PREFIX); return {NodeView{_event.prefix.name}, NodeView{_event.prefix.uri}}; } StatementEvent statement() const { assert(_event.type == SERD_STATEMENT); return {StatementFlags{_event.statement.flags}, StatementView{_event.statement.statement}}; } EndEvent end() const { assert(_event.type == SERD_END); return {NodeView{_event.end.node}}; } private: SerdEvent _event; // union { // BaseEvent base; // PrefixEvent prefix; // StatementEvent statement; // EndEvent end; // } event; }; /** @} @defgroup serdpp_sink Sink @{ */ // FIXME: Document using BaseFunc = std::function; using PrefixFunc = std::function; using StatementFunc = std::function; using EndFunc = std::function; /// Common base class for any wrapped sink template class SinkWrapper : public detail::StaticWrapper { public: /// @copydoc serd_sink_write_base Status base(const NodeView& uri) const { return Status(serd_sink_write_base(this->cobj(), uri.cobj())); } /// @copydoc serd_sink_write_prefix Status prefix(NodeView name, const NodeView& uri) const { return Status( serd_sink_write_prefix(this->cobj(), name.cobj(), uri.cobj())); } /// @copydoc serd_sink_write_statement Status statement(StatementFlags flags, StatementView statement) const { return Status( serd_sink_write_statement(this->cobj(), flags, statement.cobj())); } /// @copydoc serd_sink_write Status write(StatementFlags flags, const NodeView& subject, const NodeView& predicate, const NodeView& object, Optional graph = {}) const { return Status(serd_sink_write(this->cobj(), flags, subject.cobj(), predicate.cobj(), object.cobj(), graph.cobj())); } /// @copydoc serd_sink_write_end Status end(const NodeView& node) const { return Status(serd_sink_write_end(this->cobj(), node.cobj())); } protected: explicit SinkWrapper(CSink* const ptr) : detail::StaticWrapper{ptr} {} }; /// A non-owning constant view of some other sink class SinkView final : public SinkWrapper { public: /// Create a view of a C sink explicit SinkView(const SerdSink* const ptr) : SinkWrapper{ptr} {} /// Create a view of some other sink // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) SinkView(const SinkWrapper& sink) : SinkWrapper{sink.cobj()} {} }; /// @copydoc SerdSink class Sink final : public SinkWrapper { public: explicit Sink(const World& world) : SinkWrapper{serd_sink_new(world.cobj(), this, s_event, nullptr)} {} explicit Sink(SerdSink* const ptr) : SinkWrapper{ptr} {} /// Set a function to be called when the base URI changes void set_base_func(BaseFunc base_func) { _base_func = std::move(base_func); } /// Set a function to be called when a namespace prefix changes void set_prefix_func(PrefixFunc prefix_func) { _prefix_func = std::move(prefix_func); } /// Set a function to be called for every statement void set_statement_func(StatementFunc statement_func) { _statement_func = std::move(statement_func); } /// Set a function to be called at the end of an anonymous node void set_end_func(EndFunc end_func) { _end_func = std::move(end_func); } private: static SerdStatus s_base(void* handle, const SerdNode* uri) noexcept { const auto* const sink = static_cast(handle); return sink->_base_func ? SerdStatus(sink->_base_func(NodeView(uri))) : SERD_SUCCESS; } static SerdStatus s_prefix(void* handle, const SerdNode* name, const SerdNode* uri) noexcept { const auto* const sink = static_cast(handle); return sink->_prefix_func ? SerdStatus(sink->_prefix_func(NodeView(name), NodeView(uri))) : SERD_SUCCESS; } static SerdStatus s_statement(void* handle, SerdStatementFlags flags, const SerdStatement* statement) noexcept { const auto* const sink = static_cast(handle); return sink->_statement_func ? SerdStatus(sink->_statement_func(StatementFlags(flags), StatementView(statement))) : SERD_SUCCESS; } static SerdStatus s_end(void* handle, const SerdNode* node) noexcept { const auto* const sink = static_cast(handle); return sink->_end_func ? SerdStatus(sink->_end_func(NodeView(node))) : SERD_SUCCESS; } static SerdStatus s_event(void* handle, const SerdEvent* event) noexcept { const auto* const sink = static_cast(handle); switch (event->type) { case SERD_BASE: return sink->_base_func ? SerdStatus(sink->_base_func(NodeView(event->base.uri))) : SERD_SUCCESS; case SERD_PREFIX: return sink->_prefix_func ? SerdStatus(sink->_prefix_func(NodeView(event->prefix.name), NodeView(event->prefix.uri))) : SERD_SUCCESS; case SERD_STATEMENT: return sink->_statement_func ? SerdStatus(sink->_statement_func( StatementFlags(event->statement.flags), StatementView(event->statement.statement))) : SERD_SUCCESS; case SERD_END: return sink->_end_func ? SerdStatus(sink->_end_func(NodeView(event->end.node))) : SERD_SUCCESS; } return SERD_SUCCESS; } BaseFunc _base_func{}; PrefixFunc _prefix_func{}; StatementFunc _statement_func{}; EndFunc _end_func{}; }; /** @} @defgroup serdpp_canon Canon @{ */ /// @copydoc SerdCanonFlag enum class CanonFlag { lax = SERD_CANON_LAX ///< @copydoc SERD_CANON_LAX }; /// @copydoc SerdCanonFlags using CanonFlags = Flags; /// @copydoc serd_canon_new inline Sink make_canon(const World& world, SinkView target, const CanonFlags flags) { return Sink{serd_canon_new(world.cobj(), target.cobj(), flags)}; } /** @} @defgroup serdpp_filter Filter @{ */ /// @copydoc serd_filter_new inline Sink make_filter(const World& world, SinkView target, Optional subject, Optional predicate, Optional object, Optional graph, const bool inclusive) { return Sink{serd_filter_new(world.cobj(), target.cobj(), subject.cobj(), predicate.cobj(), object.cobj(), graph.cobj(), inclusive)}; } /** @} @} @defgroup serdpp_env Environment @{ */ template using EnvHandle = detail::StaticCopyable; /// Env wrapper template class EnvWrapper : public EnvHandle { public: /// Return the base URI NodeView base_uri() const { return NodeView(serd_env_base_uri(this->cobj())); } /// Set the base URI Status set_base_uri(const StringView& uri) { return Status(serd_env_set_base_uri(this->cobj(), uri)); } /// Set a namespace prefix Status set_prefix(StringView name, StringView uri) { return Status(serd_env_set_prefix(this->cobj(), name, uri)); } /// Expand `node` into an absolute URI if possible Optional expand(const NodeView& node) const { return Node{serd_env_expand_node(this->cobj(), node.cobj())}; } /// Send all prefixes to `sink` void write_prefixes(SinkView sink) const { serd_env_write_prefixes(this->cobj(), sink.cobj()); } protected: explicit EnvWrapper(std::unique_ptr ptr) : EnvHandle{std::move(ptr)} {} explicit EnvWrapper(CObj* const ptr) : EnvHandle{ptr} {} }; /// EnvView using EnvView = EnvWrapper; /// @copydoc SerdEnv class Env : public EnvWrapper { public: explicit Env(World& world) : EnvWrapper{serd_env_new(world.cobj(), SERD_EMPTY_STRING())} {} explicit Env(World& world, const NodeView& base) : EnvWrapper{serd_env_new(world.cobj(), base.string_view())} {} }; /** @} @defgroup serdpp_syntax_io Reading and Writing @{ @defgroup serdpp_byte_source Byte Source @{ */ /// @copydoc SerdInputStream class InputStream : public SerdInputStream { public: explicit InputStream(SerdInputStream stream) : SerdInputStream{stream} {} InputStream(const InputStream&) = delete; InputStream& operator=(const InputStream&) = delete; InputStream(InputStream&&) = default; InputStream& operator=(InputStream&&) = default; ~InputStream() { serd_close_input(this); } }; static inline size_t istream_read(void* const buf, const size_t size, const size_t nmemb, void* const stream) noexcept { std::istream& is = *static_cast(stream); const size_t len = size * nmemb; try { is.read(static_cast(buf), static_cast(len)); } catch (...) { return 0u; } return is.fail() ? 0u : len; } static inline int istream_error(void* const stream) { std::istream& is = *static_cast(stream); return !is.good(); } inline InputStream open_input_stream(std::istream& is) { return InputStream{ serd_open_input_stream(istream_read, istream_error, nullptr, &is)}; } // InputStream // open_input_string(StringView string) // { // return InputStream{ // serd_open_input_string(stream(istream_read, istream_error, nullptr, // &is)}; // } /** @} @defgroup serdpp_reader Reader @{ */ /// @copydoc SerdReaderFlag enum class ReaderFlag { lax = SERD_READ_LAX, ///< @copydoc SERD_READ_LAX variables = SERD_READ_VARIABLES, ///< @copydoc SERD_READ_VARIABLES relative = SERD_READ_RELATIVE, ///< @copydoc SERD_READ_RELATIVE global = SERD_READ_GLOBAL, ///< @copydoc SERD_READ_GLOBAL }; /// @copydoc SerdReaderFlags using ReaderFlags = Flags; /// @copydoc SerdReader class Reader : public detail::StaticWrapper { public: Reader(World& world, const Syntax syntax, const ReaderFlags flags, Env& env, SinkView sink, size_t stack_size) : StaticWrapper{serd_reader_new(world.cobj(), SerdSyntax(syntax), flags, env.cobj(), sink.cobj(), stack_size)} {} Status start(SerdInputStream& in, const NodeView& input_name, const size_t block_size) { return Status( serd_reader_start(cobj(), &in, input_name.cobj(), block_size)); } /// @copydoc serd_reader_read_chunk Status read_chunk() { return Status(serd_reader_read_chunk(cobj())); } /// @copydoc serd_reader_read_document Status read_document() { return Status(serd_reader_read_document(cobj())); } /// @copydoc serd_reader_finish Status finish() { return Status(serd_reader_finish(cobj())); } private: static inline size_t s_stream_read(void* buf, size_t size, size_t nmemb, void* stream) noexcept { assert(size == 1); (void)size; try { auto* const s = static_cast(stream); s->read(static_cast(buf), std::streamsize(nmemb)); if (s->good()) { return nmemb; } } catch (...) { } return 0; } static inline int s_stream_error(void* stream) noexcept { try { auto* const s = static_cast(stream); return (!(s->good())); } catch (...) { } return 1; } }; /** @} @defgroup serdpp_byte_sink Byte Sink @{ */ /** Sink function for string output. Similar semantics to `SerdWriteFunc` (and in turn `fwrite`), but takes char* for convenience and may set errno for more informative error reporting than supported by `SerdStreamErrorFunc`. @return Number of elements (bytes) written, which is short on error. */ using WriteFunc = std::function; /// @copydoc SerdOutputStream class OutputStream : public SerdOutputStream { public: explicit OutputStream(SerdOutputStream stream) : SerdOutputStream{stream} {} OutputStream(const OutputStream&) = delete; OutputStream& operator=(const OutputStream&) = delete; OutputStream(OutputStream&&) = default; OutputStream& operator=(OutputStream&&) = default; ~OutputStream() { close(); } /// @copydoc serd_close_output Status close() { return static_cast(serd_close_output(this)); } }; static inline size_t ostream_write(const void* const buf, const size_t size, const size_t nmemb, void* const stream) noexcept { std::ostream& os = *static_cast(stream); const size_t len = size * nmemb; try { os.write(static_cast(buf), std::streamsize(len)); } catch (...) { return 0u; } return os.fail() ? 0u : len; } inline OutputStream open_output_stream(std::ostream& os) { return OutputStream{ serd_open_output_stream(ostream_write, nullptr, nullptr, &os)}; } inline OutputStream open_output_file(const StringView path) { return OutputStream{serd_open_output_file(path.c_str())}; } // /// @copydoc SerdOutputStream // class OutputStream : public SerdOutputStream // { // public: // /// Create a byte sink that writes to a function in blocks // OutputStream(WriteFunc write_func, SerdStreamCloseFunc close_func) // : StaticWrapper{serd_byte_sink_new_function(s_write, // nullptr, // this, // block_size)} // , _write_func{std::move(write_func)} // {} // OutputStream(StringView filename, const size_t block_size) // : StaticWrapper{serd_byte_sink_new_filename(filename.c_str(), // block_size)} // {} // /// Create a byte sink that writes to a function one byte at a time // explicit OutputStream(WriteFunc write_func) // : OutputStream{std::move(write_func), 1} // {} // /// Create a byte sink from a standard output stream // explicit OutputStream(std::ostream& stream) // : StaticWrapper{serd_byte_sink_new_function(s_write, nullptr, this, 1)} // , _write_func{[&](const char* str, size_t len) { // stream.write(str, std::streamsize(len)); // return stream.good() ? len : size_t(0); // }} // {} // Status close() { return static_cast(serd_byte_sink_close(cobj())); // } // private: // static inline size_t s_write(const void* buf, // size_t size, // size_t nmemb, // void* sink) noexcept // { // assert(size == 1); // (void)size; // auto* self = static_cast(sink); // try { // return self->_write_func(static_cast(buf), nmemb); // } catch (...) { // } // return 0; // } // WriteFunc _write_func; // }; /** @} @defgroup serdpp_writer Writer @{ */ /// @copydoc SerdWriterFlag enum class WriterFlag { ascii = SERD_WRITE_ASCII, ///< @copydoc SERD_WRITE_ASCII expanded = SERD_WRITE_EXPANDED, ///< @copydoc SERD_WRITE_EXPANDED verbatim = SERD_WRITE_VERBATIM, ///< @copydoc SERD_WRITE_VERBATIM terse = SERD_WRITE_TERSE, ///< @copydoc SERD_WRITE_TERSE lax = SERD_WRITE_LAX, ///< @copydoc SERD_WRITE_LAX rdf_type = SERD_WRITE_RDF_TYPE ///< @copydoc SERD_WRITE_RDF_TYPE }; /// @copydoc SerdWriterFlags using WriterFlags = Flags; /// @copydoc SerdWriter class Writer : public detail::StaticWrapper { public: /** Create a writer that writes syntax to the given byte sink. @param world The world that this writer is a part of. @param syntax Syntax to write. @param flags Flags to control writer behaviour. @param env Environment used for expansion and abbreviation. The writer uses a reference to this, so the environment must outlive the writer. @param out Stream where output is written. The writer uses a reference to this, so the stream must outlive the writer. @param block_size Number of bytes to write to the output stream at once. */ Writer(World& world, const Syntax syntax, const WriterFlags flags, Env& env, OutputStream& out, const size_t block_size = 1u) : StaticWrapper{serd_writer_new(world.cobj(), SerdSyntax(syntax), flags, env.cobj(), &out, block_size)} {} /// Return a sink that can be used to write data SinkView sink() { return SinkView{serd_writer_sink(cobj())}; } /// @copydoc serd_writer_set_root_uri Status set_root_uri(const StringView uri) { return Status(serd_writer_set_root_uri(cobj(), uri)); } /// @copydoc serd_writer_finish Status finish() { return Status(serd_writer_finish(cobj())); } }; /** @} @} @defgroup serdpp_storage Storage @{ */ /** @defgroup serdpp_iterator Iterator @{ */ /// Empty class for end sentinels to provide an iterator-like interface class EndCursor {}; template using CursorHandle = detail:: DynamicCopyable; template class CursorWrapper : public CursorHandle { public: CursorWrapper(CursorWrapper&&) noexcept = default; CursorWrapper(const CursorWrapper&) = default; CursorWrapper& operator=(CursorWrapper&&) noexcept = default; CursorWrapper& operator=(const CursorWrapper&) = default; ~CursorWrapper() = default; const StatementView& operator*() const { _statement = StatementView{serd_cursor_get(this->cobj())}; return _statement; } const StatementView* operator->() const { _statement = StatementView{serd_cursor_get(this->cobj())}; return &_statement; } CursorWrapper& operator++() { serd_cursor_advance(this->cobj()); return *this; } protected: CursorWrapper(CObj* ptr, detail::Ownership ownership) : CursorHandle{{ptr, ownership}} {} mutable StatementView _statement{}; }; /// Cursor view class CursorView : public CursorWrapper { public: // explicit CursorView(const SerdCursor* const ptr) // : CursorWrapper{ptr, detail::Ownership::view} // {} // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) CursorView(const CursorWrapper& iter) : CursorWrapper{iter.cobj(), detail::Ownership::view} {} }; // using CursorView = CursorWrapper; /// @copydoc SerdCursor class Cursor : public CursorWrapper { public: explicit Cursor(CursorView iter) : CursorWrapper{serd_cursor_copy(nullptr, iter.cobj()), detail::Ownership::owned} {} Cursor(SerdCursor* const ptr, const detail::Ownership ownership) : CursorWrapper{ptr, ownership} {} Cursor(Cursor&&) = default; Cursor(const Cursor&) = default; Cursor& operator=(Cursor&&) = default; Cursor& operator=(const Cursor&) = default; ~Cursor() = default; // Cursor& operator++() // { // serd_cursor_advance(this->cobj()); // return *this; // } }; /** @} @defgroup serdpp_range Range @{ */ /// @copydoc SerdDescribeFlag enum class DescribeFlag { no_type_first = SERD_NO_TYPE_FIRST ///< @copydoc SERD_NO_TYPE_FIRST }; /// Bitwise OR of DescribeFlag values using DescribeFlags = Flags; /** @} @defgroup serdpp_model Model @{ */ /// @copydoc SerdModelFlag enum class ModelFlag { store_graphs = SERD_STORE_GRAPHS, ///< @copydoc SERD_STORE_GRAPHS store_carets = SERD_STORE_CARETS ///< @copydoc SERD_STORE_CARETS }; /// Bitwise OR of ModelFlag values using ModelFlags = Flags; /// @copydoc SerdStatementOrder enum class StatementOrder { SPO, ///< @copydoc SERD_ORDER_SPO SOP, ///< @copydoc SERD_ORDER_SOP OPS, ///< @copydoc SERD_ORDER_OPS OSP, ///< @copydoc SERD_ORDER_OSP PSO, ///< @copydoc SERD_ORDER_PSO POS, ///< @copydoc SERD_ORDER_POS GSPO, ///< @copydoc SERD_ORDER_GSPO GSOP, ///< @copydoc SERD_ORDER_GSOP GOPS, ///< @copydoc SERD_ORDER_GOPS GOSP, ///< @copydoc SERD_ORDER_GOSP GPSO, ///< @copydoc SERD_ORDER_GPSO GPOS ///< @copydoc SERD_ORDER_GPOS }; using ModelHandle = detail::StaticCopyable; /// @copydoc SerdModel class Model : public ModelHandle { public: using value_type = Statement; using iterator = Cursor; using const_iterator = Cursor; Model(World& world, const StatementOrder default_order, const ModelFlags flags) : ModelHandle{serd_model_new(world.cobj(), static_cast(default_order), flags)} {} /// @copydoc serd_model_size size_t size() const { return serd_model_size(cobj()); } /// @copydoc serd_model_empty bool empty() const { return serd_model_empty(cobj()); } /// @copydoc serd_model_add_index Status add_index(const StatementOrder order) { return static_cast( serd_model_add_index(cobj(), static_cast(order))); } /// @copydoc serd_model_drop_index Status drop_index(const StatementOrder order) { return static_cast( serd_model_drop_index(cobj(), static_cast(order))); } /// @copydoc serd_model_insert Status insert(StatementView s) { return static_cast(serd_model_insert(cobj(), s.cobj())); } /// @copydoc serd_model_add Status insert(const NodeView& s, const NodeView& p, const NodeView& o, Optional g = {}) { return static_cast( serd_model_add(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj())); } /// @copydoc serd_model_insert_statements Status insert_statements(Cursor&& range) { return static_cast( serd_model_insert_statements(cobj(), range.cobj())); } /** Remove a statement from a model via an iterator. Calling this function invalidates all iterators on `model` except `iter`. @param iter Iterator to the element to erase. @returns An iterator to the statement following the erased statement, or the end iterator if the statement was the last or an error occurred. */ Cursor erase(Cursor iter) { if (!serd_model_erase(cobj(), iter.cobj())) { return iter; } return iter; } Cursor erase(const CursorView& iter) { Cursor iter_copy{iter}; if (!serd_model_erase(cobj(), iter_copy.cobj())) { return iter_copy; } return iter_copy; } /** Remove a range from a model. Calling this function invalidates all iterators on `model` except `iter`. @param range Range to erase. */ Status erase_statements(Cursor range) { return static_cast( serd_model_erase_statements(cobj(), range.cobj())); } class Range { public: Range(Cursor begin, Cursor end) : _begin(std::move(begin)) , _end(std::move(end)) {} const Cursor& begin() const { return _begin; } Cursor& begin() { return _begin; } const Cursor& end() const { return _end; } private: Cursor _begin; Cursor _end; }; /// @copydoc serd_model_find Range find(Optional s, Optional p, Optional o, Optional g = {}) const { return Range{ Cursor{serd_model_find(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj()), detail::Ownership::owned}, end()}; } /// @copydoc serd_model_get Optional get(Optional s, Optional p, Optional o, Optional g = {}) const { return NodeView( serd_model_get(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj())); } /// @copydoc serd_model_get_statement Optional get_statement(Optional s, Optional p, Optional o, Optional g = {}) const { return StatementView( serd_model_get_statement(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj())); } /// @copydoc serd_model_ask bool ask(Optional s, Optional p, Optional o, Optional g = {}) const { return serd_model_ask(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj()); } /// @copydoc serd_model_count size_t count(Optional s, Optional p, Optional o, Optional g = {}) const { return serd_model_count(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj()); } /// @copydoc serd_model_begin_ordered Cursor begin_ordered(StatementOrder order) const { return Cursor( serd_model_begin_ordered(cobj(), static_cast(order)), detail::Ownership::owned); } /// @copydoc serd_model_begin iterator begin() const { return iterator(serd_model_begin(cobj()), detail::Ownership::owned); } /// @copydoc serd_model_end iterator end() const { // TODO: cache? return iterator(serd_cursor_copy(nullptr, serd_model_end(cobj())), detail::Ownership::owned); // return iterator(serd_model_end(cobj()), detail::Ownership::view); } private: friend class Optional; explicit Model(std::nullptr_t) : StaticCopyable{nullptr} {} }; /** @} @defgroup serdpp_inserter Inserter @{ */ /** Create an inserter that inserts statements into a model. @param model The model to insert received statements into. */ inline Sink make_inserter(Model& model) { return Sink{serd_inserter_new(model.cobj(), nullptr)}; } /** Create an inserter that inserts statements into a specific graph in a model. @param model The model to insert received statements into. @param default_graph The default graph to set for any statements that have no graph. This allows, for example, loading a Turtle document into an isolated graph in the model. */ inline Sink make_inserter(Model& model, NodeView default_graph) { return Sink{serd_inserter_new(model.cobj(), default_graph.cobj())}; } /** @} @defgroup serdpp_validator Validator @{ */ /// @copydoc SerdValidatorCheck enum class ValidatorCheck : uint64_t { /// @copydoc SERD_CHECK_NOTHING nothing, /// @copydoc SERD_CHECK_ALL_VALUES_FROM all_values_from = SERD_CHECK_ALL_VALUES_FROM, /// @copydoc SERD_CHECK_ANY_URI any_uri = SERD_CHECK_ANY_URI, /// @copydoc SERD_CHECK_CARDINALITY_EQUAL cardinality_equal = SERD_CHECK_CARDINALITY_EQUAL, /// @copydoc SERD_CHECK_CARDINALITY_MAX cardinality_max = SERD_CHECK_CARDINALITY_MAX, /// @copydoc SERD_CHECK_CARDINALITY_MIN cardinality_min = SERD_CHECK_CARDINALITY_MIN, /// @copydoc SERD_CHECK_CLASS_CYCLE class_cycle = SERD_CHECK_CLASS_CYCLE, /// @copydoc SERD_CHECK_CLASS_LABEL class_label = SERD_CHECK_CLASS_LABEL, /// @copydoc SERD_CHECK_DATATYPE_PROPERTY datatype_property = SERD_CHECK_DATATYPE_PROPERTY, /// @copydoc SERD_CHECK_DATATYPE_TYPE datatype_type = SERD_CHECK_DATATYPE_TYPE, /// @copydoc SERD_CHECK_DEPRECATED_CLASS deprecated_class = SERD_CHECK_DEPRECATED_CLASS, /// @copydoc SERD_CHECK_DEPRECATED_PROPERTY deprecated_property = SERD_CHECK_DEPRECATED_PROPERTY, /// @copydoc SERD_CHECK_FUNCTIONAL_PROPERTY functional_property = SERD_CHECK_FUNCTIONAL_PROPERTY, /// @copydoc SERD_CHECK_INSTANCE_LITERAL instance_literal = SERD_CHECK_INSTANCE_LITERAL, /// @copydoc SERD_CHECK_INSTANCE_TYPE instance_type = SERD_CHECK_INSTANCE_TYPE, /// @copydoc SERD_CHECK_INVERSE_FUNCTIONAL_PROPERTY inverse_functional_property = SERD_CHECK_INVERSE_FUNCTIONAL_PROPERTY, /// @copydoc SERD_CHECK_LITERAL_INSTANCE literal_instance = SERD_CHECK_LITERAL_INSTANCE, /// @copydoc SERD_CHECK_LITERAL_MAX_EXCLUSIVE literal_max_exclusive = SERD_CHECK_LITERAL_MAX_EXCLUSIVE, /// @copydoc SERD_CHECK_LITERAL_MAX_INCLUSIVE literal_max_inclusive = SERD_CHECK_LITERAL_MAX_INCLUSIVE, /// @copydoc SERD_CHECK_LITERAL_MIN_EXCLUSIVE literal_min_exclusive = SERD_CHECK_LITERAL_MIN_EXCLUSIVE, /// @copydoc SERD_CHECK_LITERAL_MIN_INCLUSIVE literal_min_inclusive = SERD_CHECK_LITERAL_MIN_INCLUSIVE, /// @copydoc SERD_CHECK_LITERAL_PATTERN literal_pattern = SERD_CHECK_LITERAL_PATTERN, /// @copydoc SERD_CHECK_LITERAL_RESTRICTION literal_restriction = SERD_CHECK_LITERAL_RESTRICTION, /// @copydoc SERD_CHECK_LITERAL_VALUE literal_value = SERD_CHECK_LITERAL_VALUE, /// @copydoc SERD_CHECK_OBJECT_PROPERTY object_property = SERD_CHECK_OBJECT_PROPERTY, /// @copydoc SERD_CHECK_PLAIN_LITERAL_DATATYPE plain_literal_datatype = SERD_CHECK_PLAIN_LITERAL_DATATYPE, /// @copydoc SERD_CHECK_PREDICATE_TYPE predicate_type = SERD_CHECK_PREDICATE_TYPE, /// @copydoc SERD_CHECK_PROPERTY_CYCLE property_cycle = SERD_CHECK_PROPERTY_CYCLE, /// @copydoc SERD_CHECK_PROPERTY_DOMAIN property_domain = SERD_CHECK_PROPERTY_DOMAIN, /// @copydoc SERD_CHECK_PROPERTY_LABEL property_label = SERD_CHECK_PROPERTY_LABEL, /// @copydoc SERD_CHECK_PROPERTY_RANGE property_range = SERD_CHECK_PROPERTY_RANGE, /// @copydoc SERD_CHECK_SOME_VALUES_FROM some_values_from = SERD_CHECK_SOME_VALUES_FROM, }; /// @copydoc SerdValidator class Validator : public detail::StaticWrapper { public: /// Create a new validator with no checks enabled explicit Validator(World& world) : StaticWrapper{serd_validator_new(world.cobj())} {} /// @copydoc serd_validator_enable_checks Status enable_checks(StringView pattern) { return static_cast( serd_validator_enable_checks(cobj(), pattern.c_str())); } /// @copydoc serd_validator_disable_checks Status disable_checks(StringView pattern) { return static_cast( serd_validator_disable_checks(cobj(), pattern.c_str())); } /** Validate all statements in a specific graph in a model. This performs validation based on the XSD, RDF, RDFS, and OWL vocabularies. All necessary data, including those vocabularies and any property/class definitions that use them, are assumed to be in the model. Validation errors are reported to the world's error sink. @param model The model to validate. @return @ref Status::success if no errors are found, or @ref Status::bad_data if validation checks failed. */ Status validate(const Model& model, NodeView graph, EnvView env) { return static_cast( serd_validate(cobj(), model.cobj(), graph.cobj(), env.cobj())); } /** Validate all statements in a specific graph in a model. This performs the same validation as the other overload with a graph, but does not take an env. URIs in the log output will not be abbreviated. */ Status validate(const Model& model, NodeView graph) { return static_cast( serd_validate(cobj(), model.cobj(), graph.cobj(), nullptr)); } /** Validate all statements in a model. This overload validates all statements in any graph in the model. */ Status validate(const Model& model) { return static_cast( serd_validate(cobj(), model.cobj(), nullptr, nullptr)); } }; /** @} @} @} */ } // namespace serd #endif // SERD_SERD_HPP