From 0fb533abe4f1c9fdf5e9d7842f3a3f1cc9136e79 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 16 Feb 2011 18:08:45 +0000 Subject: Make serd.h C++ safe. Add C++ API (serdmm.hpp). Add serialisation API (easily write models to files). git-svn-id: http://svn.drobilla.net/sord/trunk@36 3d64ff67-21c5-427c-a301-fe4f08042e5a --- sord/sord.h | 27 +++ sord/sordmm.hpp | 504 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/syntax.c | 201 +++++++++++++++++----- wscript | 3 +- 4 files changed, 695 insertions(+), 40 deletions(-) create mode 100644 sord/sordmm.hpp diff --git a/sord/sord.h b/sord/sord.h index 6b00268..f1169f9 100644 --- a/sord/sord.h +++ b/sord/sord.h @@ -22,9 +22,15 @@ #ifndef SORD_SORD_H #define SORD_SORD_H +#ifdef __cplusplus +extern "C" { +#endif + #include #include +#include "serd/serd.h" + #if defined _WIN32 || defined __CYGWIN__ #define SORD_LIB_IMPORT __declspec(dllimport) #define SORD_LIB_EXPORT __declspec(dllexport) @@ -330,8 +336,29 @@ sord_read_file_handle(SordModel model, const SordNode graph, const uint8_t* blank_prefix); +SORD_API +bool +sord_write_file(SordModel model, + SerdEnv env, + const uint8_t* uri, + const SordNode graph, + const uint8_t* blank_prefix); + +SORD_API +bool +sord_write_file_handle(SordModel model, + SerdEnv env, + FILE* fd, + const uint8_t* base_uri, + const SordNode graph, + const uint8_t* blank_prefix); + /** @} */ /** @} */ +#ifdef __cplusplus +} /* extern "C" */ +#endif + #endif // SORD_SORD_H diff --git a/sord/sordmm.hpp b/sord/sordmm.hpp new file mode 100644 index 0000000..4661458 --- /dev/null +++ b/sord/sordmm.hpp @@ -0,0 +1,504 @@ +/* Sord, a lightweight RDF model library. + * Copyright 2010-2011 David Robillard + * + * Sord is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Sord is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +/** @file sordmm.hpp + * Public Sord C++ API. + */ + +#ifndef SORD_SORDMM_HPP +#define SORD_SORDMM_HPP + +#include +#include +#include +#include +#include + +#include +#include + +#include "serd/serd.h" +#include "sord/sord.h" + +#define SORD_NS_XSD "http://www.w3.org/2001/XMLSchema#" + +namespace Sord { + +/** C++ wrapper for a Sord object. */ +template +class Wrapper { +public: + inline Wrapper(T c_obj = NULL) : _c_obj(c_obj) {} + + inline T c_obj() { return _c_obj; } + inline const T c_obj() const { return _c_obj; } + +protected: + T _c_obj; +}; + +/** Collection of RDF namespaces with prefixes. */ +class Namespaces : public Wrapper { +public: + Namespaces() : Wrapper(serd_env_new()) {} + + static inline SerdNode string_to_node(SerdType type, const std::string& s) { + SerdNode ret = { + type, s.length() + 1, s.length(), (const uint8_t*)s.c_str() }; + return ret; + } + + inline void add(const std::string& name, + const std::string& uri) { + const SerdNode name_node = string_to_node(SERD_LITERAL, name); + const SerdNode uri_node = string_to_node(SERD_URI, uri); + serd_env_add(_c_obj, &name_node, &uri_node); + } + + inline std::string qualify(std::string uri) const { + const SerdNode uri_node = string_to_node(SERD_URI, uri); + SerdNode prefix; + SerdChunk suffix; + if (serd_env_qualify(_c_obj, &uri_node, &prefix, &suffix)) { + std::string ret((const char*)prefix.buf, prefix.n_bytes - 1); + ret.append((const char*)suffix.buf, suffix.len); + return ret; + } + return uri; + } + + inline std::string expand(const std::string& curie) const { + assert(curie.find(":") != std::string::npos); + SerdNode curie_node = string_to_node(SERD_CURIE, curie); + SerdChunk uri_prefix; + SerdChunk uri_suffix; + if (serd_env_expand(_c_obj, &curie_node, &uri_prefix, &uri_suffix)) { + std::string ret((const char*)uri_prefix.buf, uri_prefix.len); + ret.append((const char*)uri_suffix.buf, uri_suffix.len); + return ret; + } + std::cerr << "CURIE `" << curie << "' has unknown prefix." << std::endl; + return curie; + } +}; + +/** Sord library state. */ +class World : public boost::noncopyable, public Wrapper { +public: + inline World() + : _next_blank_id(0) + { + _c_obj = sord_world_new(); + add_prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + } + + inline ~World() { + sord_world_free(_c_obj); + } + + inline uint64_t blank_id() { return _next_blank_id++; } + + inline void add_prefix(const std::string& prefix, const std::string& uri) { + _prefixes.add(prefix, uri); + } + + inline const Namespaces& prefixes() const { return _prefixes; } + inline SordWorld world() { return _c_obj; } + +private: + Namespaces _prefixes; + std::set _blank_ids; + uint64_t _next_blank_id; +}; + +/** An RDF Node (resource, literal, etc) + */ +class Node : public Wrapper { +public: + enum Type { + UNKNOWN = 0, + RESOURCE = SORD_URI, + BLANK = SORD_BLANK, + LITERAL = SORD_LITERAL + }; + + inline Node() : Wrapper(NULL), _world(NULL) {} + + inline Node(World& world, Type t, const std::string& s); + inline Node(World& world); + inline Node(World& world, SordNode node); + inline Node(const Node& other); + inline ~Node(); + + inline Type type() const { + return _c_obj ? (Type)sord_node_get_type(_c_obj) : UNKNOWN; + } + + inline SordNode get_node() const { return _c_obj; } + + inline bool is_valid() const { return type() != UNKNOWN; } + + inline bool operator<(const Node& other) const { + if (type() != other.type()) { + return type() < other.type(); + } else { + return to_string() < other.to_string(); + } + } + + const Node& operator=(const Node& other) { + if (_c_obj) + sord_node_free(_c_obj); + _world = other._world; + _c_obj = other._c_obj ? sord_node_copy(other._c_obj) : NULL; + return *this; + } + + inline bool operator==(const Node& other) const { + return sord_node_equals(_c_obj, other._c_obj); + } + + inline const uint8_t* to_u_string() const; + inline const char* to_c_string() const; + inline std::string to_string() const; + + inline bool is_literal_type(const char* type_uri) const; + + inline bool is_resource() const { return _c_obj && type() == RESOURCE; } + inline bool is_blank() const { return _c_obj && type() == BLANK; } + inline bool is_int() const { return is_literal_type(SORD_NS_XSD "integer"); } + inline bool is_float() const { return is_literal_type(SORD_NS_XSD "decimal"); } + inline bool is_bool() const { return is_literal_type(SORD_NS_XSD "boolean"); } + + inline int to_int() const; + inline float to_float() const; + inline bool to_bool() const; + + inline static Node blank_id(World& world, const std::string base="b") { + const uint64_t num = world.blank_id(); + std::ostringstream ss; + ss << base << num; + return Node(world, Node::BLANK, ss.str()); + } + +private: + World* _world; +}; + +inline std::ostream& +operator<<(std::ostream& os, const Node& node) +{ + return os << node.to_string() << std::endl; +} + +class Resource : public Node { +public: + inline Resource(World& world, const std::string& s) : Node(world, Node::RESOURCE, s) {} +}; + +class Curie : public Node { +public: + inline Curie(World& world, const std::string& s) + : Node(world, Node::RESOURCE, world.prefixes().expand(s)) {} +}; + +class Literal : public Node { +public: + inline Literal(World& world, const std::string& s) : Node(world, Node::LITERAL, s) {} +}; + +inline +Node::Node(World& world, Type type, const std::string& s) + : _world(&world) +{ + switch (type) { + case RESOURCE: + assert(s.find(":") == std::string::npos + || s.substr(0, 5) == "http:" + || s.substr(0, 5) == "file:" + || s.substr(0, 4) == "urn:"); + _c_obj = sord_new_uri( + world.world(), (const unsigned char*)s.c_str()); + break; + case LITERAL: + _c_obj = sord_new_literal( + world.world(), NULL, (const unsigned char*)s.c_str(), NULL); + break; + case BLANK: + _c_obj = sord_new_blank( + world.world(), (const unsigned char*)s.c_str()); + break; + default: + _c_obj = NULL; + } + + assert(this->type() == type); +} + +inline +Node::Node(World& world) + : _world(&world) +{ + Node me = blank_id(world); + *this = me; +} + +inline +Node::Node(World& world, SordNode node) + : _world(&world) +{ + _c_obj = node ? sord_node_copy(node) : NULL; +} + +inline +Node::Node(const Node& other) + : Wrapper() + , _world(other._world) +{ + if (_world) { + _c_obj = other._c_obj ? sord_node_copy(other._c_obj) : NULL; + } + + assert((!_c_obj && !other._c_obj) || to_string() == other.to_string()); +} + +inline +Node::~Node() +{ + if (_world) { + sord_node_free(_c_obj); + } +} + + +inline std::string +Node::to_string() const +{ + return std::string(to_c_string()); +} + + +inline const char* +Node::to_c_string() const +{ + return (const char*)sord_node_get_string(_c_obj); +} + +inline const uint8_t* +Node::to_u_string() const +{ + return sord_node_get_string(_c_obj); +} + +inline bool +Node::is_literal_type(const char* type_uri) const +{ + if (_c_obj && sord_node_get_type(_c_obj) == SORD_LITERAL) { + SordNode datatype = sord_node_get_datatype(_c_obj); + if (datatype && !strcmp((const char*)sord_node_get_string(datatype), + type_uri)) + return true; + } + return false; +} + +inline int +Node::to_int() const +{ + assert(is_int()); + std::locale c_locale("C"); + std::stringstream ss((const char*)sord_node_get_string(_c_obj)); + ss.imbue(c_locale); + int i = 0; + ss >> i; + return i; +} + +inline float +Node::to_float() const +{ + assert(is_float()); + std::locale c_locale("C"); + std::stringstream ss((const char*)sord_node_get_string(_c_obj)); + ss.imbue(c_locale); + float f = 0.0f; + ss >> f; + return f; +} + +inline bool +Node::to_bool() const +{ + assert(is_bool()); + return !strcmp((const char*)sord_node_get_string(_c_obj), "true"); +} + +struct Iter : public Wrapper { + inline Iter(World& world, SordIter c_obj) + : Wrapper(c_obj), _world(world) {} + inline ~Iter() { sord_iter_free(_c_obj); } + inline bool end() const { return sord_iter_end(_c_obj); } + inline bool next() const { return sord_iter_next(_c_obj); } + inline Iter& operator++() { assert(!end()); next(); return *this; } + inline Node get_subject() const { + SordQuad quad; + sord_iter_get(_c_obj, quad); + return Node(_world, quad[SORD_SUBJECT]); + } + inline Node get_predicate() const { + SordQuad quad; + sord_iter_get(_c_obj, quad); + return Node(_world, quad[SORD_PREDICATE]); + } + inline Node get_object() const { + SordQuad quad; + sord_iter_get(_c_obj, quad); + return Node(_world, quad[SORD_OBJECT]); + } + World& _world; +}; + + +/** An RDF Model (collection of triples). + */ +class Model : public boost::noncopyable, public Wrapper { +public: + inline Model(World& world, const Glib::ustring& base_uri="."); + inline ~Model(); + + inline const Node& base_uri() const { return _base; } + + inline void load_file(const Glib::ustring& uri); + + inline void load_string(const char* str, + size_t len, + const Glib::ustring& base_uri, + const std::string lang = "turtle"); + + inline void write_to_file_handle(FILE* fd, const char* lang); + inline void write_to_file(const Glib::ustring& uri, const char* lang); + inline char* write_to_string(const char* lang); + + inline void add_statement(const Node& subject, + const Node& predicate, + const Node& object); + + inline Iter find(const Node& subject, + const Node& predicate, + const Node& object); + + inline World& world() const { return _world; } + +private: + World& _world; + Node _base; + SerdWriter _writer; + size_t _next_blank_id; +}; + +/** Create an empty in-memory RDF model. + */ +inline +Model::Model(World& world, const Glib::ustring& base_uri) + : _world(world) + , _base(world, Node::RESOURCE, base_uri) + , _writer(NULL) +{ + // FIXME: parameters + _c_obj = sord_new(_world.world(), SORD_SPO|SORD_OPS, true); +} + +inline void +Model::load_string(const char* str, + size_t len, + const Glib::ustring& base_uri, + const std::string lang) +{ + // TODO +} + +inline Model::~Model() +{ + sord_free(_c_obj); +} + +inline void +Model::load_file(const Glib::ustring& data_uri) +{ + // FIXME: blank prefix + sord_read_file(_c_obj, (const uint8_t*)data_uri.c_str(), NULL, + (const uint8_t*)"b"); +} + +inline void +Model::write_to_file_handle(FILE* fd, const char* lang) +{ + sord_write_file_handle(_c_obj, + _world.prefixes().c_obj(), + fd, + _base.to_u_string(), + NULL, + NULL); +} + +inline void +Model::write_to_file(const Glib::ustring& uri, const char* lang) +{ + sord_write_file(_c_obj, + _world.prefixes().c_obj(), + (const uint8_t*)uri.c_str(), + NULL, + NULL); +} + +inline char* +Model::write_to_string(const char* lang) +{ + std::cerr << "TODO: serialise" << std::endl; + return NULL; +} + +inline void +Model::add_statement(const Node& subject, + const Node& predicate, + const Node& object) +{ + SordQuad quad = { subject.c_obj(), + predicate.c_obj(), + object.c_obj(), + NULL }; + + sord_add(_c_obj, quad); +} + +inline Iter +Model::find(const Node& subject, + const Node& predicate, + const Node& object) +{ + SordQuad quad = { subject.c_obj(), + predicate.c_obj(), + object.c_obj(), + NULL }; + + return Iter(_world, sord_find(_c_obj, quad)); +} + +} // namespace Sord + +#endif // SORD_SORDMM_HPP + diff --git a/src/syntax.c b/src/syntax.c index 9f90e20..0666ecd 100644 --- a/src/syntax.c +++ b/src/syntax.c @@ -94,13 +94,18 @@ event_prefix(void* handle, } static inline SordNode -sord_node_from_serd_node(ReadState* state, const SerdNode* sn) +sord_node_from_serd_node(ReadState* state, const SerdNode* sn, + const SerdNode* datatype, const SerdNode* lang) { switch (sn->type) { case SERD_NOTHING: return NULL; case SERD_LITERAL: - return sord_new_literal(state->world, NULL, sn->buf, NULL); + return sord_new_literal( + state->world, + sord_node_from_serd_node(state, datatype, NULL, NULL), + sn->buf, + g_intern_string((const char*)lang->buf)); case SERD_URI: { SerdURI abs_uri; SerdNode abs_uri_node = serd_node_new_uri_from_node( @@ -134,6 +139,30 @@ sord_node_from_serd_node(ReadState* state, const SerdNode* sn) return NULL; } +static inline void +sord_node_to_serd_node(const SordNode node, SerdNode* out) +{ + if (!node) { + *out = SERD_NODE_NULL; + return; + } + switch (node->type) { + case SORD_URI: + out->type = SERD_URI; + break; + case SORD_BLANK: + out->type = SERD_BLANK_ID; + break; + case SORD_LITERAL: + out->type = SERD_LITERAL; + break; + } + size_t len; + out->buf = sord_node_get_string_counted(node, &len); + out->n_bytes = len; + out->n_chars = len - 1; // FIXME: UTF-8 +} + static bool event_statement(void* handle, const SerdNode* graph, @@ -146,25 +175,17 @@ event_statement(void* handle, ReadState* const state = (ReadState*)handle; SordQuad tup; - tup[0] = sord_node_from_serd_node(state, subject); - tup[1] = sord_node_from_serd_node(state, predicate); - - SordNode object_node = sord_node_from_serd_node(state, object); - - if (object_datatype) { - object_node->datatype = sord_node_from_serd_node(state, object_datatype); - } - if (object_lang) { - object_node->lang = g_intern_string((const char*)object_lang->buf); - } - tup[2] = object_node; + tup[0] = sord_node_from_serd_node(state, subject, NULL, NULL); + tup[1] = sord_node_from_serd_node(state, predicate, NULL, NULL); + tup[2] = sord_node_from_serd_node(state, object, + object_datatype, object_lang); if (state->graph_uri_node) { assert(graph->type == SERD_NOTHING); tup[3] = state->graph_uri_node; } else { tup[3] = (graph && graph->buf) - ? sord_node_from_serd_node(state, graph) + ? sord_node_from_serd_node(state, graph, NULL, NULL) : NULL; } @@ -173,39 +194,47 @@ event_statement(void* handle, return true; } -SORD_API -bool -sord_read_file(SordModel sord, - const uint8_t* input, - const SordNode graph, - const uint8_t* blank_prefix) +static const uint8_t* +sord_file_uri_to_path(const uint8_t* uri) { const uint8_t* filename = NULL; - if (serd_uri_string_has_scheme(input)) { - // INPUT is an absolute URI, ensure it a file and chop scheme - if (strncmp((const char*)input, "file:", 5)) { - fprintf(stderr, "unsupported URI scheme `%s'\n", input); - return 1; - } else if (!strncmp((const char*)input, "file://", 7)) { - filename = input + 7; + if (serd_uri_string_has_scheme(uri)) { + // Absolute URI, ensure it a file and chop scheme + if (strncmp((const char*)uri, "file:", 5)) { + fprintf(stderr, "unsupported URI scheme `%s'\n", uri); + return NULL; + } else if (!strncmp((const char*)uri, "file://", 7)) { + filename = uri + 7; } else { - filename = input + 5; + filename = uri + 5; } } else { - filename = input; + filename = uri; } + return filename; +} - FILE* in_fd = fopen((const char*)filename, "r"); - if (!in_fd) { - fprintf(stderr, "failed to open file %s\n", filename); - return 1; +SORD_API +bool +sord_read_file(SordModel model, + const uint8_t* uri, + const SordNode graph, + const uint8_t* blank_prefix) +{ + const uint8_t* const path = sord_file_uri_to_path(uri); + if (!path) { + return false; } - const bool success = sord_read_file_handle( - sord, in_fd, input, graph, blank_prefix); + FILE* const fd = fopen((const char*)path, "r"); + if (!fd) { + fprintf(stderr, "failed to open file %s\n", path); + return false; + } - fclose(in_fd); - return success; + const bool ret = sord_read_file_handle(model, fd, uri, graph, blank_prefix); + fclose(fd); + return ret; } SORD_API @@ -245,8 +274,102 @@ sord_read_file_handle(SordModel sord, const bool success = serd_reader_read_file(state.reader, fd, base_uri_str); serd_reader_free(state.reader); - serd_env_free(state.env); serd_node_free(&state.base_uri_node); return success; } + +SORD_API +bool +sord_write_file(SordModel model, + SerdEnv env, + const uint8_t* uri, + const SordNode graph, + const uint8_t* blank_prefix) +{ + const uint8_t* const path = sord_file_uri_to_path(uri); + if (!path) { + return false; + } + + FILE* const fd = fopen((const char*)path, "w"); + if (!fd) { + fprintf(stderr, "failed to open file %s\n", path); + return false; + } + + const bool ret = sord_write_file_handle(model, env, fd, uri, graph, blank_prefix); + fclose(fd); + return ret; +} + +static size_t +file_sink(const void* buf, size_t len, void* stream) +{ + FILE* file = (FILE*)stream; + return fwrite(buf, 1, len, file); +} + +SORD_API +bool +sord_write_file_handle(SordModel model, + SerdEnv env, + FILE* fd, + const uint8_t* base_uri_str_in, + const SordNode graph, + const uint8_t* blank_prefix) +{ + size_t base_uri_n_bytes = 0; + uint8_t* base_uri_str = copy_string(base_uri_str_in, &base_uri_n_bytes); + SerdURI base_uri; + if (!serd_uri_parse(base_uri_str, &base_uri)) { + fprintf(stderr, "invalid base URI `%s'\n", base_uri_str); + } + + SerdWriter writer = serd_writer_new(SERD_TURTLE, + SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED, + env, + &base_uri, + file_sink, + fd); + + serd_env_foreach(env, + (SerdPrefixSink)serd_writer_set_prefix, + writer); + + SerdNode s_graph; + sord_node_to_serd_node(graph, &s_graph); + for (SordIter i = sord_begin(model); !sord_iter_end(i); sord_iter_next(i)) { + SordQuad quad; + sord_iter_get(i, quad); + + SerdNode subject; + SerdNode predicate; + SerdNode object; + SerdNode datatype; + sord_node_to_serd_node(quad[SORD_SUBJECT], &subject); + sord_node_to_serd_node(quad[SORD_PREDICATE], &predicate); + sord_node_to_serd_node(quad[SORD_OBJECT], &object); + + sord_node_to_serd_node(sord_node_get_datatype(quad[SORD_OBJECT]), &datatype); + const char* lang_str = sord_node_get_language(quad[SORD_OBJECT]); + size_t lang_len = lang_str ? strlen(lang_str) : 0; + + SerdNode language = SERD_NODE_NULL; + if (lang_str) { + language.type = SERD_LITERAL; + language.n_bytes = lang_len + 1; + language.n_chars = lang_len; // FIXME: UTF-8 + language.buf = (const uint8_t*)lang_str; + }; + + serd_writer_write_statement(writer, &s_graph, + &subject, &predicate, &object, + &datatype, &language); + } + + serd_writer_free(writer); + free(base_uri_str); + + return true; +} diff --git a/wscript b/wscript index 9b033e1..f9641bb 100644 --- a/wscript +++ b/wscript @@ -61,8 +61,9 @@ def configure(conf): print def build(bld): - # C Headers + # C/C++ Headers bld.install_files('${INCLUDEDIR}/sord', bld.path.ant_glob('sord/*.h')) + bld.install_files('${INCLUDEDIR}/sord', bld.path.ant_glob('sord/*.hpp')) # Pkgconfig file autowaf.build_pc(bld, 'SORD', SORD_VERSION, []) -- cgit v1.2.1