summaryrefslogtreecommitdiffstats
path: root/src/dumper.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dumper.c')
-rw-r--r--src/dumper.c546
1 files changed, 546 insertions, 0 deletions
diff --git a/src/dumper.c b/src/dumper.c
new file mode 100644
index 0000000..b2bb852
--- /dev/null
+++ b/src/dumper.c
@@ -0,0 +1,546 @@
+/*
+ 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/atom/util.h"
+#include "lv2/midi/midi.h"
+#include "lv2/urid/urid.h"
+#include "serd/serd.h"
+
+#include <ctype.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 STREAM_WARN(msg) \
+ serd_world_logf( \
+ writer->world, "sratom", SERD_LOG_LEVEL_WARNING, 0, NULL, msg);
+
+#define STREAM_ERRORF(msg, ...) \
+ serd_world_logf( \
+ writer->world, "sratom", SERD_LOG_LEVEL_ERROR, 0, NULL, msg, __VA_ARGS__);
+
+struct SratomDumperImpl {
+ LV2_URID_Unmap* unmap;
+ LV2_Atom_Forge forge;
+ SerdWorld* world;
+ LV2_URID atom_Event;
+ LV2_URID atom_beatTime;
+ LV2_URID midi_MidiEvent;
+ struct {
+ const SerdNode* atom_Path;
+ const SerdNode* atom_beatTime;
+ const SerdNode* atom_childType;
+ const SerdNode* atom_frameTime;
+ const SerdNode* midi_MidiEvent;
+ const SerdNode* rdf_first;
+ const SerdNode* rdf_nil;
+ const SerdNode* rdf_rest;
+ const SerdNode* rdf_type;
+ const SerdNode* rdf_value;
+ const SerdNode* xsd_base64Binary;
+ const SerdNode* xsd_boolean;
+ const SerdNode* xsd_decimal;
+ const SerdNode* xsd_double;
+ const SerdNode* xsd_float;
+ const SerdNode* xsd_int;
+ const SerdNode* xsd_integer;
+ const SerdNode* xsd_long;
+ } nodes;
+};
+
+typedef struct {
+ SratomDumper* writer;
+ const SerdEnv* env;
+ const SerdSink* sink;
+ const SratomDumperFlags flags;
+ SerdStatementFlags sflags;
+ LV2_URID seq_unit;
+} StreamContext;
+
+void
+sratom_free(void* ptr)
+{
+ /* The only sratom memory the user frees comes from sratom_from_model(),
+ which is allocated by serd_buffer_sink. */
+ serd_free(ptr);
+}
+
+SratomDumper*
+sratom_dumper_new(SerdWorld* const world,
+ LV2_URID_Map* const map,
+ LV2_URID_Unmap* const unmap)
+{
+ SratomDumper* writer = (SratomDumper*)calloc(1, sizeof(SratomDumper));
+ if (!writer) {
+ return NULL;
+ }
+
+ writer->world = world;
+ writer->unmap = unmap;
+ writer->atom_Event = map->map(map->handle, LV2_ATOM__Event);
+ writer->atom_beatTime = map->map(map->handle, LV2_ATOM__beatTime);
+ writer->midi_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+ lv2_atom_forge_init(&writer->forge, map);
+
+#define MANAGE_URI(uri) \
+ serd_nodes_manage(serd_world_nodes(world), \
+ serd_new_uri(SERD_STATIC_STRING(uri)))
+
+ writer->nodes.atom_Path = MANAGE_URI(LV2_ATOM__Path);
+ writer->nodes.atom_beatTime = MANAGE_URI(LV2_ATOM__beatTime);
+ writer->nodes.atom_childType = MANAGE_URI(LV2_ATOM__childType);
+ writer->nodes.atom_frameTime = MANAGE_URI(LV2_ATOM__frameTime);
+ writer->nodes.midi_MidiEvent = MANAGE_URI(LV2_MIDI__MidiEvent);
+ writer->nodes.rdf_first = MANAGE_URI(NS_RDF "first");
+ writer->nodes.rdf_nil = MANAGE_URI(NS_RDF "nil");
+ writer->nodes.rdf_rest = MANAGE_URI(NS_RDF "rest");
+ writer->nodes.rdf_type = MANAGE_URI(NS_RDF "type");
+ writer->nodes.rdf_value = MANAGE_URI(NS_RDF "value");
+ writer->nodes.xsd_base64Binary = MANAGE_URI(NS_XSD "base64Binary");
+ writer->nodes.xsd_boolean = MANAGE_URI(NS_XSD "boolean");
+ writer->nodes.xsd_decimal = MANAGE_URI(NS_XSD "decimal");
+ writer->nodes.xsd_double = MANAGE_URI(NS_XSD "double");
+ writer->nodes.xsd_float = MANAGE_URI(NS_XSD "float");
+ writer->nodes.xsd_int = MANAGE_URI(NS_XSD "int");
+ writer->nodes.xsd_integer = MANAGE_URI(NS_XSD "integer");
+ writer->nodes.xsd_long = MANAGE_URI(NS_XSD "long");
+
+#undef MANAGE_URI
+
+ return writer;
+}
+
+void
+sratom_dumper_free(SratomDumper* writer)
+{
+ free(writer);
+}
+
+static int
+write_atom(StreamContext* ctx,
+ const SerdNode* subject,
+ const SerdNode* predicate,
+ LV2_URID type,
+ uint32_t size,
+ const void* body);
+
+static void
+list_append(StreamContext* ctx,
+ SerdNode** s,
+ const SerdNode** p,
+ uint32_t size,
+ uint32_t type,
+ const void* body)
+{
+ // Generate a list node
+ SerdNode* node = serd_node_copy(serd_world_get_blank(ctx->writer->world));
+ serd_sink_write(ctx->sink, ctx->sflags, *s, *p, node, NULL);
+
+ // _:node rdf:first value
+ ctx->sflags = 0;
+ *p = ctx->writer->nodes.rdf_first;
+ write_atom(ctx, node, *p, type, size, body);
+
+ // Set subject to node and predicate to rdf:rest for next time
+ serd_node_free(*s);
+ *s = node;
+ *p = ctx->writer->nodes.rdf_rest;
+}
+
+static void
+list_end(StreamContext* ctx, const SerdNode* s, const SerdNode* p)
+{
+ // _:node rdf:rest rdf:nil
+ serd_sink_write(
+ ctx->sink, ctx->sflags, s, p, ctx->writer->nodes.rdf_nil, NULL);
+}
+
+static void
+start_object(StreamContext* ctx,
+ const SerdNode* subject,
+ const SerdNode* predicate,
+ const SerdNode* node,
+ const char* type)
+{
+ if (subject && predicate) {
+ serd_sink_write(
+ ctx->sink, ctx->sflags | SERD_ANON_O, subject, predicate, node, NULL);
+ } else {
+ ctx->sflags |= SERD_EMPTY_S;
+ }
+
+ if (type) {
+ SerdNode* o = serd_new_uri(SERD_MEASURE_STRING(type));
+
+ serd_sink_write(
+ ctx->sink, ctx->sflags, node, ctx->writer->nodes.rdf_type, o, NULL);
+
+ serd_node_free(o);
+ }
+}
+
+static void
+end_object(StreamContext* ctx,
+ const SerdNode* subject,
+ const SerdNode* predicate,
+ const SerdNode* node)
+{
+ if (subject && predicate) {
+ serd_sink_write_end(ctx->sink, node);
+ }
+}
+
+static bool
+path_is_absolute(const char* path)
+{
+ return (path[0] == '/' || (isalpha(path[0]) && path[1] == ':' &&
+ (path[2] == '/' || path[2] == '\\')));
+}
+
+static const SerdNode*
+number_type(StreamContext* ctx, const SerdNode* type)
+{
+ SratomDumper* const writer = ctx->writer;
+ const bool pretty = (ctx->flags & SRATOM_PRETTY_NUMBERS);
+ if (pretty) {
+ if (type == writer->nodes.xsd_int || type == writer->nodes.xsd_long) {
+ return writer->nodes.xsd_integer;
+ } else if (type == writer->nodes.xsd_float ||
+ type == writer->nodes.xsd_double) {
+ return writer->nodes.xsd_decimal;
+ }
+ }
+ return type;
+}
+
+static bool
+is_primitive_type(StreamContext* ctx, const LV2_URID type)
+{
+ SratomDumper* const writer = ctx->writer;
+ return (!type || type == writer->forge.Bool || type == writer->forge.Double ||
+ type == writer->forge.Float || type == writer->forge.Int ||
+ type == writer->forge.Literal || type == writer->forge.Long ||
+ type == writer->forge.Path || type == writer->forge.String ||
+ type == writer->forge.URI || type == writer->forge.URID);
+}
+
+static int
+write_atom(StreamContext* const ctx,
+ const SerdNode* subject,
+ const SerdNode* predicate,
+ const LV2_URID type,
+ const uint32_t size,
+ const void* const body)
+{
+ SratomDumper* writer = ctx->writer;
+ LV2_URID_Unmap* unmap = writer->unmap;
+ const SerdSink* sink = ctx->sink;
+ const SerdEnv* env = ctx->env;
+ const char* const type_uri = unmap->unmap(unmap->handle, type);
+ SerdNode* object = NULL;
+ if (type == 0 && size == 0) {
+ object = serd_node_copy(writer->nodes.rdf_nil);
+ } else if (type == writer->forge.String) {
+ object = serd_new_string(SERD_MEASURE_STRING((const char*)body));
+ } else if (type == writer->forge.Chunk) {
+ object = serd_new_blob(body, size, NULL);
+ } else if (type == writer->forge.Literal) {
+ const LV2_Atom_Literal_Body* lit = (const LV2_Atom_Literal_Body*)body;
+ const char* str = (const char*)(lit + 1);
+ if (lit->datatype) {
+ const SerdStringView datatype_uri =
+ SERD_MEASURE_STRING(unmap->unmap(unmap->handle, lit->datatype));
+ object = serd_new_typed_literal(SERD_MEASURE_STRING(str), datatype_uri);
+ } else if (lit->lang) {
+ const char* lang = unmap->unmap(unmap->handle, lit->lang);
+ const char* prefix = "http://lexvo.org/id/iso639-3/";
+ const size_t prefix_len = strlen(prefix);
+ if (lang && !strncmp(lang, prefix, prefix_len)) {
+ object = serd_new_plain_literal(SERD_MEASURE_STRING(str),
+ SERD_MEASURE_STRING(lang + prefix_len));
+ } else {
+ STREAM_ERRORF("Unknown language URID %u\n", lit->lang);
+ }
+ }
+ } else if (type == writer->forge.URID) {
+ const uint32_t urid = *(const uint32_t*)body;
+ const char* str = unmap->unmap(unmap->handle, urid);
+
+ object = serd_new_uri(SERD_MEASURE_STRING(str));
+ } else if (type == writer->forge.Path) {
+ const SerdStringView str = SERD_MEASURE_STRING((const char*)body);
+ if (path_is_absolute(str.buf)) {
+ object = serd_new_file_uri(str, SERD_EMPTY_STRING());
+ } else {
+ const SerdNode* base_uri = serd_env_base_uri(env);
+ if (!base_uri || strncmp(serd_node_string(base_uri), "file://", 7)) {
+ STREAM_WARN("Relative path but base is not a file URI.\n");
+ STREAM_WARN("Writing ambiguous atom:Path literal.\n");
+ object = serd_new_typed_literal(
+ str, serd_node_string_view(writer->nodes.atom_Path));
+ } else {
+ SerdNode* const rel = serd_new_file_uri(str, SERD_EMPTY_STRING());
+
+ object = serd_new_parsed_uri(serd_resolve_uri(
+ serd_node_uri_view(rel), serd_node_uri_view(base_uri)));
+
+ serd_node_free(rel);
+ }
+ }
+ } else if (type == writer->forge.URI) {
+ object = serd_new_uri(SERD_MEASURE_STRING((const char*)body));
+ } else if (type == writer->forge.Int) {
+ object = serd_new_integer(*(const int32_t*)body,
+ number_type(ctx, writer->nodes.xsd_int));
+ } else if (type == writer->forge.Long) {
+ object = serd_new_integer(*(const int64_t*)body,
+ number_type(ctx, writer->nodes.xsd_long));
+ } else if (type == writer->forge.Float) {
+ object = serd_new_decimal(*(const float*)body,
+ number_type(ctx, writer->nodes.xsd_float));
+ } else if (type == writer->forge.Double) {
+ object = serd_new_decimal(*(const double*)body,
+ number_type(ctx, writer->nodes.xsd_double));
+ } else if (type == writer->forge.Bool) {
+ object = serd_new_boolean(*(const int32_t*)body);
+ } else if (type == writer->midi_MidiEvent) {
+ const size_t len = 2 * size;
+ char* const str = (char*)calloc(len + 1, 1);
+ for (uint32_t i = 0; i < size; ++i) {
+ snprintf(str + (2 * i),
+ size * 2 + 1,
+ "%02X",
+ (unsigned)*((const uint8_t*)body + i));
+ }
+
+ object = serd_new_typed_literal(
+ SERD_STRING_VIEW(str, len),
+ serd_node_string_view(writer->nodes.midi_MidiEvent));
+
+ free(str);
+ } else if (type == writer->atom_Event) {
+ const LV2_Atom_Event* ev = (const LV2_Atom_Event*)body;
+ const SerdNode* id = serd_world_get_blank(writer->world);
+ start_object(ctx, subject, predicate, id, NULL);
+ SerdNode* time = NULL;
+ const SerdNode* p = NULL;
+ if (ctx->seq_unit == writer->atom_beatTime) {
+ p = writer->nodes.atom_beatTime;
+ time = serd_new_double(ev->time.beats);
+ } else {
+ p = writer->nodes.atom_frameTime;
+ time = serd_new_integer(ev->time.frames,
+ number_type(ctx, writer->nodes.xsd_long));
+ }
+ serd_sink_write(sink, 0, id, p, time, NULL);
+ serd_node_free(time);
+
+ p = writer->nodes.rdf_value;
+ write_atom(
+ ctx, id, p, ev->body.type, ev->body.size, LV2_ATOM_BODY_CONST(&ev->body));
+ end_object(ctx, subject, predicate, id);
+ } else if (type == writer->forge.Tuple) {
+ SerdNode* id = serd_node_copy(serd_world_get_blank(writer->world));
+ start_object(ctx, subject, predicate, id, type_uri);
+ const SerdNode* p = writer->nodes.rdf_value;
+ ctx->sflags |= SERD_LIST_O | SERD_TERSE_O;
+ LV2_ATOM_TUPLE_BODY_FOREACH (body, size, i) {
+ if (!is_primitive_type(ctx, i->type)) {
+ ctx->sflags &= ~SERD_TERSE_O;
+ }
+ }
+ LV2_ATOM_TUPLE_BODY_FOREACH (body, size, i) {
+ list_append(ctx, &id, &p, i->size, i->type, LV2_ATOM_BODY(i));
+ }
+ list_end(ctx, id, p);
+ end_object(ctx, subject, predicate, id);
+ serd_node_free(id);
+ } else if (type == writer->forge.Vector) {
+ const LV2_Atom_Vector_Body* vec = (const LV2_Atom_Vector_Body*)body;
+ SerdNode* id = serd_node_copy(serd_world_get_blank(writer->world));
+ start_object(ctx, subject, predicate, id, type_uri);
+ const SerdNode* p = writer->nodes.atom_childType;
+ SerdNode* child_type = serd_new_uri(
+ SERD_MEASURE_STRING(unmap->unmap(unmap->handle, vec->child_type)));
+ serd_sink_write(sink, ctx->sflags, id, p, child_type, NULL);
+ p = writer->nodes.rdf_value;
+ serd_node_free(child_type);
+ ctx->sflags |= SERD_LIST_O;
+ if (is_primitive_type(ctx, vec->child_type)) {
+ ctx->sflags |= SERD_TERSE_O;
+ }
+ for (const char* i = (const char*)(vec + 1); i < (const char*)vec + size;
+ i += vec->child_size) {
+ list_append(ctx, &id, &p, vec->child_size, vec->child_type, i);
+ }
+ list_end(ctx, id, p);
+ end_object(ctx, subject, predicate, id);
+ serd_node_free(id);
+ } else if (lv2_atom_forge_is_object_type(&writer->forge, type)) {
+ const LV2_Atom_Object_Body* obj = (const LV2_Atom_Object_Body*)body;
+ const char* otype = unmap->unmap(unmap->handle, obj->otype);
+
+ SerdNode* id = NULL;
+ if (lv2_atom_forge_is_blank(&writer->forge, type, obj)) {
+ id = serd_node_copy(serd_world_get_blank(writer->world));
+ start_object(ctx, subject, predicate, id, otype);
+ } else {
+ id =
+ serd_new_uri(SERD_MEASURE_STRING(unmap->unmap(unmap->handle, obj->id)));
+ ctx->sflags = 0;
+ start_object(ctx, NULL, NULL, id, otype);
+ }
+ LV2_ATOM_OBJECT_BODY_FOREACH (obj, size, prop) {
+ const char* const key = unmap->unmap(unmap->handle, prop->key);
+ SerdNode* pred = serd_new_uri(SERD_MEASURE_STRING(key));
+ write_atom(ctx,
+ id,
+ pred,
+ prop->value.type,
+ prop->value.size,
+ LV2_ATOM_BODY(&prop->value));
+ serd_node_free(pred);
+ }
+ end_object(ctx, subject, predicate, id);
+ serd_node_free(id);
+ } else if (type == writer->forge.Sequence) {
+ const LV2_Atom_Sequence_Body* seq = (const LV2_Atom_Sequence_Body*)body;
+ SerdNode* id = serd_node_copy(serd_world_get_blank(writer->world));
+ start_object(ctx, subject, predicate, id, type_uri);
+ const SerdNode* p = writer->nodes.rdf_value;
+ ctx->sflags |= SERD_LIST_O;
+ LV2_ATOM_SEQUENCE_BODY_FOREACH (seq, size, ev) {
+ ctx->seq_unit = seq->unit;
+ list_append(ctx,
+ &id,
+ &p,
+ sizeof(LV2_Atom_Event) + ev->body.size,
+ writer->atom_Event,
+ ev);
+ }
+ list_end(ctx, id, p);
+ end_object(ctx, subject, predicate, id);
+ serd_node_free(id);
+ } else {
+ const SerdNode* id = serd_world_get_blank(writer->world);
+ start_object(ctx, subject, predicate, id, type_uri);
+ const SerdNode* p = writer->nodes.rdf_value;
+ SerdNode* o = serd_new_blob(body, size, NULL);
+ serd_sink_write(sink, ctx->sflags, id, p, o, NULL);
+ end_object(ctx, subject, predicate, id);
+ serd_node_free(o);
+ }
+
+ if (object) {
+ if (!subject && !predicate) {
+ subject = serd_world_get_blank(writer->world);
+ predicate = writer->nodes.rdf_first;
+ serd_sink_write(sink,
+ ctx->sflags | SERD_LIST_S | SERD_TERSE_S,
+ subject,
+ predicate,
+ object,
+ NULL);
+ serd_sink_write(sink,
+ SERD_TERSE_S,
+ subject,
+ writer->nodes.rdf_rest,
+ writer->nodes.rdf_nil,
+ NULL);
+ } else {
+ serd_sink_write(sink, ctx->sflags, subject, predicate, object, NULL);
+ }
+ }
+
+ serd_node_free(object);
+
+ return 0;
+}
+
+int
+sratom_dump(SratomDumper* const writer,
+ const SerdEnv* const env,
+ const SerdSink* const sink,
+ const SerdNode* const subject,
+ const SerdNode* const predicate,
+ const LV2_URID type,
+ const uint32_t size,
+ const void* const body,
+ const SratomDumperFlags flags)
+{
+ StreamContext ctx = {writer,
+ env,
+ sink,
+ flags,
+ (flags & SRATOM_NAMED_SUBJECT) ? 0 : SERD_EMPTY_S,
+ 0};
+
+ return write_atom(&ctx, subject, predicate, type, size, body);
+}
+
+int
+sratom_dump_atom(SratomDumper* const writer,
+ const SerdEnv* const env,
+ const SerdSink* const sink,
+ const SerdNode* const subject,
+ const SerdNode* const predicate,
+ const LV2_Atom* const atom,
+ const SratomDumperFlags flags)
+{
+ return sratom_dump(writer,
+ env,
+ sink,
+ subject,
+ predicate,
+ atom->type,
+ atom->size,
+ atom + 1,
+ flags);
+}
+
+char*
+sratom_to_string(SratomDumper* const writer,
+ const SerdEnv* const env,
+ const LV2_Atom* const atom,
+ const SratomDumperFlags flags)
+{
+ SerdBuffer buffer = {NULL, 0};
+ SerdByteSink* const out = serd_byte_sink_new_buffer(&buffer);
+
+ SerdWriter* const ttl_writer =
+ serd_writer_new(writer->world,
+ SERD_TURTLE,
+ flags & SRATOM_TERSE ? SERD_WRITE_TERSE : 0,
+ env,
+ out);
+
+ const SerdSink* const sink = serd_writer_sink(ttl_writer);
+
+ sratom_dump_atom(writer, env, sink, NULL, NULL, atom, flags);
+ serd_writer_finish(ttl_writer);
+ serd_writer_free(ttl_writer);
+
+ return serd_buffer_sink_finish(&buffer);
+}