summaryrefslogtreecommitdiffstats
path: root/src/loader.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/loader.c')
-rw-r--r--src/loader.c519
1 files changed, 519 insertions, 0 deletions
diff --git a/src/loader.c b/src/loader.c
new file mode 100644
index 0000000..46e7c51
--- /dev/null
+++ b/src/loader.c
@@ -0,0 +1,519 @@
+/*
+ Copyright 2012-2021 David Robillard <d@drobilla.net>
+
+ 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.
+*/
+
+#include "sratom/sratom.h"
+
+#include "lv2/atom/atom.h"
+#include "lv2/atom/forge.h"
+#include "lv2/midi/midi.h"
+#include "lv2/urid/urid.h"
+#include "serd/serd.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define NS_XSD "http://www.w3.org/2001/XMLSchema#"
+
+#define LOAD_ERROR(msg) \
+ serd_world_logf(loader->world, "sratom", SERD_LOG_LEVEL_ERROR, 0, NULL, msg);
+
+typedef enum { MODE_SUBJECT, MODE_BODY, MODE_SEQUENCE } ReadMode;
+
+struct SratomLoaderImpl {
+ LV2_URID_Map* map;
+ LV2_Atom_Forge forge;
+ SerdWorld* world;
+ LV2_URID atom_frameTime;
+ LV2_URID atom_beatTime;
+ LV2_URID midi_MidiEvent;
+ struct {
+ const SerdNode* atom_beatTime;
+ const SerdNode* atom_childType;
+ const SerdNode* atom_frameTime;
+ const SerdNode* rdf_first;
+ const SerdNode* rdf_rest;
+ const SerdNode* rdf_type;
+ const SerdNode* rdf_value;
+ const SerdNode* xsd_base64Binary;
+ } nodes;
+};
+
+typedef struct {
+ SratomLoader* loader;
+ const SerdNode* base_uri;
+ LV2_URID seq_unit;
+} LoadContext;
+
+static void
+read_node(LoadContext* ctx,
+ LV2_Atom_Forge* forge,
+ const SerdModel* model,
+ const SerdNode* node,
+ ReadMode mode);
+
+SratomLoader*
+sratom_loader_new(SerdWorld* world, LV2_URID_Map* map)
+{
+ SratomLoader* sratom = (SratomLoader*)calloc(1, sizeof(SratomLoader));
+ if (!sratom) {
+ return NULL;
+ }
+
+ sratom->world = world;
+ sratom->map = map;
+ sratom->atom_frameTime = map->map(map->handle, LV2_ATOM__frameTime);
+ sratom->atom_beatTime = map->map(map->handle, LV2_ATOM__beatTime);
+ sratom->midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+ lv2_atom_forge_init(&sratom->forge, map);
+
+#define MANAGE_URI(uri) \
+ serd_nodes_manage(serd_world_nodes(world), \
+ serd_new_uri(SERD_STATIC_STRING(uri)))
+
+ sratom->nodes.atom_beatTime = MANAGE_URI(LV2_ATOM__beatTime);
+ sratom->nodes.atom_childType = MANAGE_URI(LV2_ATOM__childType);
+ sratom->nodes.atom_frameTime = MANAGE_URI(LV2_ATOM__frameTime);
+ sratom->nodes.rdf_first = MANAGE_URI(NS_RDF "first");
+ sratom->nodes.rdf_rest = MANAGE_URI(NS_RDF "rest");
+ sratom->nodes.rdf_type = MANAGE_URI(NS_RDF "type");
+ sratom->nodes.rdf_value = MANAGE_URI(NS_RDF "value");
+ sratom->nodes.xsd_base64Binary = MANAGE_URI(NS_XSD "base64Binary");
+
+#undef INTERN_URI
+
+ return sratom;
+}
+
+void
+sratom_loader_free(SratomLoader* loader)
+{
+ free(loader);
+}
+
+static void
+read_list_value(LoadContext* ctx,
+ LV2_Atom_Forge* forge,
+ const SerdModel* model,
+ const SerdNode* node,
+ ReadMode mode)
+{
+ const SerdNode* fst =
+ serd_model_get(model, node, ctx->loader->nodes.rdf_first, NULL, NULL);
+ const SerdNode* rst =
+ serd_model_get(model, node, ctx->loader->nodes.rdf_rest, NULL, NULL);
+ if (fst && rst) {
+ read_node(ctx, forge, model, fst, mode);
+ read_list_value(ctx, forge, model, rst, mode);
+ }
+}
+
+static void
+read_resource(LoadContext* ctx,
+ LV2_Atom_Forge* forge,
+ const SerdModel* model,
+ const SerdNode* node,
+ LV2_URID otype)
+{
+ LV2_URID_Map* map = ctx->loader->map;
+ SerdRange* r = serd_model_range(model, node, NULL, NULL, NULL);
+ for (; !serd_range_empty(r); serd_range_next(r)) {
+ const SerdStatement* match = serd_range_front(r);
+ const SerdNode* p = serd_statement_predicate(match);
+ const SerdNode* o = serd_statement_object(match);
+ if (p) {
+ const char* p_uri = serd_node_string(p);
+ uint32_t p_urid = map->map(map->handle, p_uri);
+ if (!(serd_node_equals(p, ctx->loader->nodes.rdf_type) &&
+ serd_node_type(o) == SERD_URI &&
+ map->map(map->handle, serd_node_string(o)) == otype)) {
+ lv2_atom_forge_key(forge, p_urid);
+ read_node(ctx, forge, model, o, MODE_BODY);
+ }
+ }
+ }
+ serd_range_free(r);
+}
+
+static uint32_t
+atom_size(SratomLoader* loader, uint32_t type_urid)
+{
+ if (type_urid == loader->forge.Int) {
+ return sizeof(int32_t);
+ } else if (type_urid == loader->forge.Long) {
+ return sizeof(int64_t);
+ } else if (type_urid == loader->forge.Float) {
+ return sizeof(float);
+ } else if (type_urid == loader->forge.Double) {
+ return sizeof(double);
+ } else if (type_urid == loader->forge.Bool) {
+ return sizeof(int32_t);
+ } else if (type_urid == loader->forge.URID) {
+ return sizeof(uint32_t);
+ }
+ return 0;
+}
+
+static void
+read_uri(LoadContext* ctx, LV2_Atom_Forge* forge, const SerdNode* node)
+{
+ SratomLoader* const loader = ctx->loader;
+ LV2_URID_Map* const map = loader->map;
+ const char* const str = serd_node_string(node);
+
+ if (!strcmp(str, NS_RDF "nil")) {
+ lv2_atom_forge_atom(forge, 0, 0);
+ } else if (!strncmp(str, "file://", 7)) {
+ const SerdURIView base_uri = serd_node_uri_view(ctx->base_uri);
+ const SerdURIView uri = serd_parse_uri(str);
+ if (serd_uri_is_within(uri, base_uri)) {
+ SerdNode* const rel = serd_new_parsed_uri(serd_relative_uri(
+ serd_parse_uri(str), serd_node_uri_view(ctx->base_uri)));
+
+ char* path = serd_parse_file_uri(serd_node_string(rel), NULL);
+ lv2_atom_forge_path(forge, path, strlen(path));
+ serd_free(path);
+ serd_node_free(rel);
+ } else {
+ char* const path = serd_parse_file_uri(str, NULL);
+ lv2_atom_forge_path(forge, path, strlen(path));
+ serd_free(path);
+ }
+ } else {
+ lv2_atom_forge_urid(forge, map->map(map->handle, str));
+ }
+}
+
+static void
+read_typed_literal(SratomLoader* const loader,
+ LV2_Atom_Forge* const forge,
+ const SerdNode* const node,
+ const SerdNode* const datatype)
+{
+ const char* const str = serd_node_string(node);
+ const size_t len = serd_node_length(node);
+ const char* const type_uri = serd_node_string(datatype);
+
+ if (!strcmp(type_uri, NS_XSD "int") || !strcmp(type_uri, NS_XSD "integer")) {
+ lv2_atom_forge_int(forge, strtol(str, NULL, 10));
+ } else if (!strcmp(type_uri, NS_XSD "long")) {
+ lv2_atom_forge_long(forge, strtol(str, NULL, 10));
+ } else if (!strcmp(type_uri, NS_XSD "float") ||
+ !strcmp(type_uri, NS_XSD "decimal")) {
+ lv2_atom_forge_float(forge, serd_get_float(node));
+ } else if (!strcmp(type_uri, NS_XSD "double")) {
+ lv2_atom_forge_double(forge, serd_get_double(node));
+ } else if (!strcmp(type_uri, NS_XSD "boolean")) {
+ lv2_atom_forge_bool(forge, serd_get_boolean(node));
+ } else if (!strcmp(type_uri, NS_XSD "base64Binary")) {
+ size_t size = 0;
+ void* body = serd_base64_decode(str, len, &size);
+ lv2_atom_forge_atom(forge, size, forge->Chunk);
+ lv2_atom_forge_write(forge, body, size);
+ free(body);
+ } else if (!strcmp(type_uri, LV2_ATOM__Path)) {
+ lv2_atom_forge_path(forge, str, len);
+ } else if (!strcmp(type_uri, LV2_MIDI__MidiEvent)) {
+ lv2_atom_forge_atom(forge, len / 2, loader->midi_MidiEvent);
+ for (const char* s = str; s < str + len; s += 2) {
+ unsigned num;
+ sscanf(s, "%2X", &num);
+ const uint8_t c = num;
+ lv2_atom_forge_raw(forge, &c, 1);
+ }
+ lv2_atom_forge_pad(forge, len / 2);
+ } else {
+ lv2_atom_forge_literal(
+ forge, str, len, loader->map->map(loader->map->handle, type_uri), 0);
+ }
+}
+
+static void
+read_plain_literal(SratomLoader* const loader,
+ LV2_Atom_Forge* const forge,
+ const SerdNode* const node,
+ const SerdNode* const language)
+{
+ const char* const str = serd_node_string(node);
+ const size_t len = serd_node_length(node);
+ const char* lang_str = serd_node_string(language);
+ const char* prefix = "http://lexvo.org/id/iso639-3/";
+ const size_t lang_len = strlen(prefix) + strlen(lang_str);
+ char* const lang_uri = (char*)calloc(lang_len + 1, 1);
+
+ snprintf(lang_uri, lang_len + 1, "%s%s", prefix, lang_str);
+
+ lv2_atom_forge_literal(
+ forge, str, len, 0, loader->map->map(loader->map->handle, lang_uri));
+
+ free(lang_uri);
+}
+
+static void
+read_literal(SratomLoader* loader, LV2_Atom_Forge* forge, const SerdNode* node)
+{
+ assert(serd_node_type(node) == SERD_LITERAL);
+
+ const SerdNode* const datatype = serd_node_datatype(node);
+ const SerdNode* const language = serd_node_language(node);
+ if (datatype) {
+ read_typed_literal(loader, forge, node, datatype);
+ } else if (language) {
+ read_plain_literal(loader, forge, node, language);
+ } else {
+ lv2_atom_forge_string(
+ forge, serd_node_string(node), serd_node_length(node));
+ }
+}
+
+static void
+read_object(LoadContext* ctx,
+ LV2_Atom_Forge* forge,
+ const SerdModel* model,
+ const SerdNode* node,
+ ReadMode mode)
+{
+ SratomLoader* const loader = ctx->loader;
+ LV2_URID_Map* const map = loader->map;
+ const char* const str = serd_node_string(node);
+
+ const SerdNode* const type =
+ serd_model_get(model, node, loader->nodes.rdf_type, NULL, NULL);
+
+ const SerdNode* const value =
+ serd_model_get(model, node, loader->nodes.rdf_value, NULL, NULL);
+
+ const char* type_uri = NULL;
+ uint32_t type_urid = 0;
+ if (type) {
+ type_uri = serd_node_string(type);
+ type_urid = map->map(map->handle, type_uri);
+ }
+
+ LV2_Atom_Forge_Frame frame = {0, 0};
+ if (mode == MODE_SEQUENCE) {
+ LV2_URID seq_unit = 0u;
+ const SerdNode* time = NULL;
+
+ if ((time = serd_model_get(
+ model, node, loader->nodes.atom_frameTime, NULL, NULL))) {
+ lv2_atom_forge_frame_time(forge, time ? serd_get_integer(time) : 0);
+ seq_unit = loader->atom_frameTime;
+
+ } else if ((time = serd_model_get(
+ model, node, loader->nodes.atom_beatTime, NULL, NULL))) {
+ lv2_atom_forge_beat_time(forge, serd_get_double(time));
+ seq_unit = loader->atom_beatTime;
+ }
+
+ read_node(ctx, forge, model, value, MODE_BODY);
+ ctx->seq_unit = seq_unit;
+
+ } else if (type_urid == loader->forge.Tuple) {
+ lv2_atom_forge_tuple(forge, &frame);
+ read_list_value(ctx, forge, model, value, MODE_BODY);
+
+ } else if (type_urid == loader->forge.Sequence) {
+ const LV2_Atom_Forge_Ref ref =
+ lv2_atom_forge_sequence_head(forge, &frame, 0);
+ ctx->seq_unit = 0;
+ read_list_value(ctx, forge, model, value, MODE_SEQUENCE);
+
+ LV2_Atom_Sequence* seq =
+ (LV2_Atom_Sequence*)lv2_atom_forge_deref(forge, ref);
+ seq->body.unit =
+ ((ctx->seq_unit == loader->atom_frameTime) ? 0 : ctx->seq_unit);
+
+ } else if (type_urid == loader->forge.Vector) {
+ const SerdNode* child_type_node =
+ serd_model_get(model, node, loader->nodes.atom_childType, NULL, NULL);
+ uint32_t child_type =
+ map->map(map->handle, serd_node_string(child_type_node));
+ uint32_t child_size = atom_size(loader, child_type);
+ if (child_size > 0) {
+ LV2_Atom_Forge_Ref ref =
+ lv2_atom_forge_vector_head(forge, &frame, child_size, child_type);
+ read_list_value(ctx, forge, model, value, MODE_BODY);
+ lv2_atom_forge_pop(forge, &frame);
+ frame.ref = 0;
+ lv2_atom_forge_pad(forge, lv2_atom_forge_deref(forge, ref)->size);
+ }
+
+ } else if (value && serd_node_equals(serd_node_datatype(value),
+ loader->nodes.xsd_base64Binary)) {
+ const char* vstr = serd_node_string(value);
+ const size_t vlen = serd_node_length(value);
+ size_t size = 0;
+ void* body = serd_base64_decode(vstr, vlen, &size);
+ lv2_atom_forge_atom(forge, size, type_urid);
+ lv2_atom_forge_write(forge, body, size);
+ free(body);
+
+ } else if (serd_node_type(node) == SERD_URI) {
+ const LV2_URID urid = map->map(map->handle, str);
+ if (serd_model_count(model, node, NULL, NULL, NULL)) {
+ lv2_atom_forge_object(forge, &frame, urid, type_urid);
+ read_resource(ctx, forge, model, node, type_urid);
+ } else {
+ read_uri(ctx, forge, node);
+ }
+
+ } else {
+ lv2_atom_forge_object(forge, &frame, 0, type_urid);
+ read_resource(ctx, forge, model, node, type_urid);
+ }
+
+ if (frame.ref) {
+ lv2_atom_forge_pop(forge, &frame);
+ }
+}
+
+static void
+read_node(LoadContext* ctx,
+ LV2_Atom_Forge* forge,
+ const SerdModel* model,
+ const SerdNode* node,
+ ReadMode mode)
+{
+ if (serd_node_type(node) == SERD_LITERAL) {
+ read_literal(ctx->loader, forge, node);
+ } else if (serd_node_type(node) == SERD_URI && mode != MODE_SUBJECT) {
+ read_uri(ctx, forge, node);
+ } else {
+ read_object(ctx, forge, model, node, mode);
+ }
+}
+
+int
+sratom_load(SratomLoader* loader,
+ const SerdNode* base_uri,
+ LV2_Atom_Forge* forge,
+ const SerdModel* model,
+ const SerdNode* node)
+{
+ LoadContext ctx = {loader, base_uri, 0};
+ read_node(&ctx, forge, model, node, MODE_SUBJECT);
+ return 0;
+}
+
+static SerdModel*
+model_from_string(SratomLoader* loader, SerdEnv* env, const char* str)
+{
+ SerdModel* const model = serd_model_new(loader->world, SERD_INDEX_SPO);
+ SerdSink* const inserter = serd_inserter_new(model, env, NULL);
+
+ SerdNode* const name = serd_new_string(SERD_STATIC_STRING("string"));
+ SerdByteSource* const source = serd_byte_source_new_string(str, name);
+ SerdReader* const reader = serd_reader_new(
+ loader->world, SERD_TURTLE, SERD_READ_LAX, env, inserter, 4096);
+
+ serd_reader_start(reader, source);
+
+ const SerdStatus st = serd_reader_read_document(reader);
+
+ serd_reader_free(reader);
+ serd_byte_source_free(source);
+ serd_node_free(name);
+ serd_sink_free(inserter);
+
+ if (st) {
+ serd_model_free(model);
+ return NULL;
+ }
+
+ return model;
+}
+
+LV2_Atom*
+sratom_from_string(SratomLoader* const loader,
+ SerdEnv* const env,
+ const char* const str)
+{
+ SerdModel* model = model_from_string(loader, env, str);
+ if (!model || serd_model_empty(model)) {
+ LOAD_ERROR("Failed to read string into model");
+ return NULL;
+ }
+
+ const SerdNode* node = NULL;
+ SerdIter* begin = serd_model_begin(model);
+ const SerdStatement* s = serd_iter_get(begin);
+ if (serd_model_size(model) == 2 &&
+ serd_node_type(serd_statement_subject(s)) == SERD_BLANK &&
+ serd_node_equals(serd_statement_predicate(s), loader->nodes.rdf_first)) {
+ // Special single-element list syntax for literals
+ node = serd_statement_object(s);
+ } else {
+ node = serd_statement_subject(s);
+ }
+
+ if (!node) {
+ LOAD_ERROR("Failed to find a node to parse");
+ return NULL;
+ }
+
+ LV2_Atom* atom =
+ sratom_from_model(loader, serd_env_base_uri(env), model, node);
+
+ serd_iter_free(begin);
+ serd_model_free(model);
+ return atom;
+}
+
+static LV2_Atom_Forge_Ref
+sratom_forge_sink(LV2_Atom_Forge_Sink_Handle handle,
+ const void* buf,
+ uint32_t size)
+{
+ SerdBuffer* chunk = (SerdBuffer*)handle;
+ const LV2_Atom_Forge_Ref ref = chunk->len + 1;
+ serd_buffer_sink(buf, 1, size, chunk);
+ return ref;
+}
+
+static LV2_Atom*
+sratom_forge_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref)
+{
+ SerdBuffer* chunk = (SerdBuffer*)handle;
+ return (LV2_Atom*)((char*)chunk->buf + ref - 1);
+}
+
+LV2_Atom*
+sratom_from_model(SratomLoader* loader,
+ const SerdNode* base_uri,
+ const SerdModel* model,
+ const SerdNode* subject)
+{
+ if (!subject) {
+ return NULL;
+ }
+
+ SerdBuffer out = {NULL, 0};
+ lv2_atom_forge_set_sink(
+ &loader->forge, sratom_forge_sink, sratom_forge_deref, &out);
+
+ int st = sratom_load(loader, base_uri, &loader->forge, model, subject);
+ if (st) {
+ sratom_free(out.buf);
+ out.buf = NULL;
+ }
+
+ return (LV2_Atom*)out.buf;
+}