// Copyright 2012-2021 David Robillard // SPDX-License-Identifier: ISC #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 #include #include #include #include #define NS_RDF (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#" #define NS_XSD (const uint8_t*)"http://www.w3.org/2001/XMLSchema#" #define USTR(str) ((const uint8_t*)(str)) static const SerdStyle style = (SerdStyle)(SERD_STYLE_ABBREVIATED | SERD_STYLE_RESOLVED | SERD_STYLE_CURIED); typedef enum { MODE_SUBJECT, MODE_BODY, MODE_SEQUENCE } ReadMode; struct SratomImpl { LV2_URID_Map* map; LV2_Atom_Forge forge; SerdEnv* env; SerdNode base_uri; SerdURI base; SerdStatementSink write_statement; SerdEndSink end_anon; void* handle; LV2_URID atom_Event; LV2_URID atom_frameTime; LV2_URID atom_beatTime; LV2_URID midi_MidiEvent; unsigned next_id; SratomObjectMode object_mode; uint32_t seq_unit; struct { SordNode* atom_childType; SordNode* atom_frameTime; SordNode* atom_beatTime; SordNode* rdf_first; SordNode* rdf_rest; SordNode* rdf_type; SordNode* rdf_value; SordNode* xsd_base64Binary; } nodes; bool pretty_numbers; }; static void read_node(Sratom* sratom, LV2_Atom_Forge* forge, SordWorld* world, SordModel* model, const SordNode* node, ReadMode mode); Sratom* sratom_new(LV2_URID_Map* map) { Sratom* sratom = (Sratom*)calloc(1, sizeof(Sratom)); if (sratom) { sratom->map = map; sratom->atom_Event = map->map(map->handle, LV2_ATOM__Event); 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); sratom->object_mode = SRATOM_OBJECT_MODE_BLANK; lv2_atom_forge_init(&sratom->forge, map); } return sratom; } void sratom_free(Sratom* sratom) { if (sratom) { serd_node_free(&sratom->base_uri); free(sratom); } } void sratom_set_env(Sratom* sratom, SerdEnv* env) { sratom->env = env; } void sratom_set_sink(Sratom* sratom, const char* base_uri, SerdStatementSink sink, SerdEndSink end_sink, void* handle) { if (base_uri) { serd_node_free(&sratom->base_uri); sratom->base_uri = serd_node_new_uri_from_string(USTR(base_uri), NULL, NULL); serd_uri_parse(sratom->base_uri.buf, &sratom->base); } sratom->write_statement = sink; sratom->end_anon = end_sink; sratom->handle = handle; } void sratom_set_pretty_numbers(Sratom* sratom, bool pretty_numbers) { sratom->pretty_numbers = pretty_numbers; } void sratom_set_object_mode(Sratom* sratom, SratomObjectMode object_mode) { sratom->object_mode = object_mode; } static void gensym(SerdNode* out, char c, unsigned num) { out->n_bytes = out->n_chars = snprintf((char*)out->buf, 12, "%c%u", c, num); } static void list_append(Sratom* sratom, LV2_URID_Unmap* unmap, unsigned* flags, SerdNode* s, SerdNode* p, SerdNode* node, uint32_t size, uint32_t type, const void* body) { // Generate a list node gensym(node, 'l', sratom->next_id); sratom->write_statement(sratom->handle, *flags, NULL, s, p, node, NULL, NULL); // _:node rdf:first value *flags = SERD_LIST_CONT; *p = serd_node_from_string(SERD_URI, NS_RDF "first"); sratom_write(sratom, unmap, *flags, node, p, type, size, body); // Set subject to node and predicate to rdf:rest for next time gensym(node, 'l', ++sratom->next_id); *s = *node; *p = serd_node_from_string(SERD_URI, NS_RDF "rest"); } static void list_end(SerdStatementSink sink, void* handle, const unsigned flags, SerdNode* s, SerdNode* p) { // _:node rdf:rest rdf:nil const SerdNode nil = serd_node_from_string(SERD_URI, NS_RDF "nil"); sink(handle, flags, NULL, s, p, &nil, NULL, NULL); } static void start_object(Sratom* sratom, uint32_t* flags, const SerdNode* subject, const SerdNode* predicate, const SerdNode* node, const char* type) { if (subject && predicate) { sratom->write_statement(sratom->handle, *flags | SERD_ANON_O_BEGIN, NULL, subject, predicate, node, NULL, NULL); // Start abbreviating object properties *flags |= SERD_ANON_CONT; // Object is in a list, stop list abbreviating if necessary *flags &= ~(uint32_t)SERD_LIST_CONT; } if (type) { SerdNode p = serd_node_from_string(SERD_URI, NS_RDF "type"); SerdNode o = serd_node_from_string(SERD_URI, USTR(type)); sratom->write_statement( sratom->handle, *flags, NULL, node, &p, &o, NULL, NULL); } } static bool path_is_absolute(const char* path) { return (path[0] == '/' || (isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\'))); } static SerdNode number_type(const Sratom* sratom, const uint8_t* type) { if (sratom->pretty_numbers && (!strcmp((const char*)type, (const char*)NS_XSD "int") || !strcmp((const char*)type, (const char*)NS_XSD "long"))) { return serd_node_from_string(SERD_URI, NS_XSD "integer"); } if (sratom->pretty_numbers && (!strcmp((const char*)type, (const char*)NS_XSD "float") || !strcmp((const char*)type, (const char*)NS_XSD "double"))) { return serd_node_from_string(SERD_URI, NS_XSD "decimal"); } return serd_node_from_string(SERD_URI, type); } int sratom_write(Sratom* sratom, LV2_URID_Unmap* unmap, uint32_t flags, const SerdNode* subject, const SerdNode* predicate, uint32_t type_urid, uint32_t size, const void* body) { const char* const type = unmap->unmap(unmap->handle, type_urid); uint8_t idbuf[12] = "b0000000000"; SerdNode id = serd_node_from_string(SERD_BLANK, idbuf); uint8_t nodebuf[12] = "b0000000000"; SerdNode node = serd_node_from_string(SERD_BLANK, nodebuf); SerdNode object = SERD_NODE_NULL; SerdNode datatype = SERD_NODE_NULL; SerdNode language = SERD_NODE_NULL; bool new_node = false; if (type_urid == 0 && size == 0) { object = serd_node_from_string(SERD_URI, USTR(NS_RDF "nil")); } else if (type_urid == sratom->forge.String) { object = serd_node_from_string(SERD_LITERAL, (const uint8_t*)body); } else if (type_urid == sratom->forge.Chunk) { datatype = serd_node_from_string(SERD_URI, NS_XSD "base64Binary"); object = serd_node_new_blob(body, size, true); new_node = true; } else if (type_urid == sratom->forge.Literal) { const LV2_Atom_Literal_Body* lit = (const LV2_Atom_Literal_Body*)body; const uint8_t* str = USTR(lit + 1); object = serd_node_from_string(SERD_LITERAL, str); if (lit->datatype) { datatype = serd_node_from_string( SERD_URI, USTR(unmap->unmap(unmap->handle, lit->datatype))); } 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)) { language = serd_node_from_string(SERD_LITERAL, USTR(lang + prefix_len)); } else { fprintf(stderr, "Unknown language URID %u\n", lit->lang); } } } else if (type_urid == sratom->forge.URID) { const uint32_t urid = *(const uint32_t*)body; const uint8_t* str = USTR(unmap->unmap(unmap->handle, urid)); object = serd_node_from_string(SERD_URI, str); } else if (type_urid == sratom->forge.Path) { const uint8_t* str = USTR(body); if (path_is_absolute((const char*)str)) { new_node = true; object = serd_node_new_file_uri(str, NULL, NULL, true); } else { if (!sratom->base_uri.buf || strncmp((const char*)sratom->base_uri.buf, "file://", 7)) { fprintf(stderr, "warning: Relative path but base is not a file URI.\n"); fprintf(stderr, "warning: Writing ambiguous atom:Path literal.\n"); object = serd_node_from_string(SERD_LITERAL, str); datatype = serd_node_from_string(SERD_URI, USTR(LV2_ATOM__Path)); } else { new_node = true; SerdNode rel = serd_node_new_file_uri(str, NULL, NULL, true); object = serd_node_new_uri_from_node(&rel, &sratom->base, NULL); serd_node_free(&rel); } } } else if (type_urid == sratom->forge.URI) { object = serd_node_from_string(SERD_URI, USTR(body)); } else if (type_urid == sratom->forge.Int) { new_node = true; object = serd_node_new_integer(*(const int32_t*)body); datatype = number_type(sratom, NS_XSD "int"); } else if (type_urid == sratom->forge.Long) { new_node = true; object = serd_node_new_integer(*(const int64_t*)body); datatype = number_type(sratom, NS_XSD "long"); } else if (type_urid == sratom->forge.Float) { new_node = true; object = serd_node_new_decimal(*(const float*)body, 8); datatype = number_type(sratom, NS_XSD "float"); } else if (type_urid == sratom->forge.Double) { new_node = true; object = serd_node_new_decimal(*(const double*)body, 16); datatype = number_type(sratom, NS_XSD "double"); } else if (type_urid == sratom->forge.Bool) { const int32_t val = *(const int32_t*)body; datatype = serd_node_from_string(SERD_URI, NS_XSD "boolean"); object = serd_node_from_string(SERD_LITERAL, USTR(val ? "true" : "false")); } else if (type_urid == sratom->midi_MidiEvent) { new_node = true; datatype = serd_node_from_string(SERD_URI, USTR(LV2_MIDI__MidiEvent)); const size_t len = size * 2ul; uint8_t* const str = (uint8_t*)calloc(len + 1, 1); for (uint32_t i = 0; i < size; ++i) { snprintf((char*)str + (2ul * i), len - (2ul * i) + 1ul, "%02X", (unsigned)*((const uint8_t*)body + i)); } object = serd_node_from_string(SERD_LITERAL, USTR(str)); } else if (type_urid == sratom->atom_Event) { const LV2_Atom_Event* ev = (const LV2_Atom_Event*)body; gensym(&id, 'e', sratom->next_id++); start_object(sratom, &flags, subject, predicate, &id, NULL); SerdNode time; SerdNode p; if (sratom->seq_unit == sratom->atom_beatTime) { time = serd_node_new_decimal(ev->time.beats, 16); p = serd_node_from_string(SERD_URI, USTR(LV2_ATOM__beatTime)); datatype = number_type(sratom, NS_XSD "double"); } else { time = serd_node_new_integer(ev->time.frames); p = serd_node_from_string(SERD_URI, USTR(LV2_ATOM__frameTime)); datatype = number_type(sratom, NS_XSD "long"); } sratom->write_statement(sratom->handle, SERD_ANON_CONT, NULL, &id, &p, &time, &datatype, &language); serd_node_free(&time); p = serd_node_from_string(SERD_URI, NS_RDF "value"); sratom_write(sratom, unmap, SERD_ANON_CONT, &id, &p, ev->body.type, ev->body.size, LV2_ATOM_BODY(&ev->body)); if (sratom->end_anon) { sratom->end_anon(sratom->handle, &id); } } else if (type_urid == sratom->forge.Tuple) { gensym(&id, 't', sratom->next_id++); start_object(sratom, &flags, subject, predicate, &id, type); SerdNode p = serd_node_from_string(SERD_URI, NS_RDF "value"); flags |= SERD_LIST_O_BEGIN; LV2_ATOM_TUPLE_BODY_FOREACH (body, size, i) { list_append(sratom, unmap, &flags, &id, &p, &node, i->size, i->type, LV2_ATOM_BODY(i)); } list_end(sratom->write_statement, sratom->handle, flags, &id, &p); if (sratom->end_anon) { sratom->end_anon(sratom->handle, &id); } } else if (type_urid == sratom->forge.Vector) { const LV2_Atom_Vector_Body* vec = (const LV2_Atom_Vector_Body*)body; gensym(&id, 'v', sratom->next_id++); start_object(sratom, &flags, subject, predicate, &id, type); SerdNode p = serd_node_from_string(SERD_URI, (const uint8_t*)LV2_ATOM__childType); SerdNode child_type = serd_node_from_string( SERD_URI, (const uint8_t*)unmap->unmap(unmap->handle, vec->child_type)); sratom->write_statement( sratom->handle, flags, NULL, &id, &p, &child_type, NULL, NULL); p = serd_node_from_string(SERD_URI, NS_RDF "value"); flags |= SERD_LIST_O_BEGIN; for (const char* i = (const char*)(vec + 1); i < (const char*)vec + size; i += vec->child_size) { list_append(sratom, unmap, &flags, &id, &p, &node, vec->child_size, vec->child_type, i); } list_end(sratom->write_statement, sratom->handle, flags, &id, &p); if (sratom->end_anon) { sratom->end_anon(sratom->handle, &id); } } else if (lv2_atom_forge_is_object_type(&sratom->forge, type_urid)) { const LV2_Atom_Object_Body* obj = (const LV2_Atom_Object_Body*)body; const char* otype = unmap->unmap(unmap->handle, obj->otype); if (lv2_atom_forge_is_blank(&sratom->forge, type_urid, obj)) { gensym(&id, 'b', sratom->next_id++); start_object(sratom, &flags, subject, predicate, &id, otype); } else { id = serd_node_from_string( SERD_URI, (const uint8_t*)unmap->unmap(unmap->handle, obj->id)); flags = 0; start_object(sratom, &flags, 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_node_from_string(SERD_URI, USTR(key)); sratom_write(sratom, unmap, flags, &id, &pred, prop->value.type, prop->value.size, LV2_ATOM_BODY(&prop->value)); } if (sratom->end_anon && (flags & SERD_ANON_CONT)) { sratom->end_anon(sratom->handle, &id); } } else if (type_urid == sratom->forge.Sequence) { const LV2_Atom_Sequence_Body* seq = (const LV2_Atom_Sequence_Body*)body; gensym(&id, 'v', sratom->next_id++); start_object(sratom, &flags, subject, predicate, &id, type); SerdNode p = serd_node_from_string(SERD_URI, NS_RDF "value"); flags |= SERD_LIST_O_BEGIN; LV2_ATOM_SEQUENCE_BODY_FOREACH (seq, size, ev) { sratom->seq_unit = seq->unit; list_append(sratom, unmap, &flags, &id, &p, &node, sizeof(LV2_Atom_Event) + ev->body.size, sratom->atom_Event, ev); } list_end(sratom->write_statement, sratom->handle, flags, &id, &p); if (sratom->end_anon && subject && predicate) { sratom->end_anon(sratom->handle, &id); } } else { gensym(&id, 'b', sratom->next_id++); start_object(sratom, &flags, subject, predicate, &id, type); SerdNode p = serd_node_from_string(SERD_URI, NS_RDF "value"); SerdNode o = serd_node_new_blob(body, size, true); datatype = serd_node_from_string(SERD_URI, NS_XSD "base64Binary"); sratom->write_statement( sratom->handle, flags, NULL, &id, &p, &o, &datatype, NULL); if (sratom->end_anon && subject && predicate) { sratom->end_anon(sratom->handle, &id); } serd_node_free(&o); } if (object.buf) { SerdNode def_s = serd_node_from_string(SERD_BLANK, USTR("atom")); SerdNode def_p = serd_node_from_string(SERD_URI, USTR(NS_RDF "value")); if (!subject) { subject = &def_s; } if (!predicate) { predicate = &def_p; } sratom->write_statement(sratom->handle, flags, NULL, subject, predicate, &object, &datatype, &language); } if (new_node) { serd_node_free(&object); } return 0; } char* sratom_to_turtle(Sratom* sratom, LV2_URID_Unmap* unmap, const char* base_uri, const SerdNode* subject, const SerdNode* predicate, uint32_t type, uint32_t size, const void* body) { SerdURI buri = SERD_URI_NULL; SerdNode base = serd_node_new_uri_from_string(USTR(base_uri), &sratom->base, &buri); SerdEnv* env = sratom->env ? sratom->env : serd_env_new(NULL); SerdChunk str = {NULL, 0}; SerdWriter* writer = serd_writer_new(SERD_TURTLE, style, env, &buri, serd_chunk_sink, &str); serd_env_set_base_uri(env, &base); sratom_set_sink(sratom, base_uri, (SerdStatementSink)serd_writer_write_statement, (SerdEndSink)serd_writer_end_anon, writer); sratom_write( sratom, unmap, SERD_EMPTY_S, subject, predicate, type, size, body); serd_writer_finish(writer); serd_writer_free(writer); if (!sratom->env) { serd_env_free(env); } serd_node_free(&base); return (char*)serd_chunk_sink_finish(&str); } static void read_list_value(Sratom* sratom, LV2_Atom_Forge* forge, SordWorld* world, SordModel* model, const SordNode* node, ReadMode mode) { SordNode* fst = sord_get(model, node, sratom->nodes.rdf_first, NULL, NULL); SordNode* rst = sord_get(model, node, sratom->nodes.rdf_rest, NULL, NULL); if (fst && rst) { read_node(sratom, forge, world, model, fst, mode); read_list_value(sratom, forge, world, model, rst, mode); } sord_node_free(world, rst); sord_node_free(world, fst); } static void read_resource(Sratom* sratom, LV2_Atom_Forge* forge, SordWorld* world, SordModel* model, const SordNode* node, LV2_URID otype) { LV2_URID_Map* map = sratom->map; SordQuad q = {node, NULL, NULL, NULL}; SordIter* i = sord_find(model, q); SordQuad match; for (; !sord_iter_end(i); sord_iter_next(i)) { sord_iter_get(i, match); const SordNode* p = match[SORD_PREDICATE]; const SordNode* o = match[SORD_OBJECT]; const char* p_uri = (const char*)sord_node_get_string(p); uint32_t p_urid = map->map(map->handle, p_uri); if (!(sord_node_equals(p, sratom->nodes.rdf_type) && sord_node_get_type(o) == SORD_URI && map->map(map->handle, (const char*)sord_node_get_string(o)) == otype)) { lv2_atom_forge_key(forge, p_urid); read_node(sratom, forge, world, model, o, MODE_BODY); } } sord_iter_free(i); } static uint32_t atom_size(Sratom* sratom, uint32_t type_urid) { if (type_urid == sratom->forge.Int || type_urid == sratom->forge.Bool) { return sizeof(int32_t); } if (type_urid == sratom->forge.Long) { return sizeof(int64_t); } if (type_urid == sratom->forge.Float) { return sizeof(float); } if (type_urid == sratom->forge.Double) { return sizeof(double); } if (type_urid == sratom->forge.URID) { return sizeof(uint32_t); } return 0; } static void read_literal(Sratom* sratom, LV2_Atom_Forge* forge, const SordNode* node) { assert(sord_node_get_type(node) == SORD_LITERAL); size_t len = 0; const char* str = (const char*)sord_node_get_string_counted(node, &len); SordNode* datatype = sord_node_get_datatype(node); const char* language = sord_node_get_language(node); if (datatype) { const char* type_uri = (const char*)sord_node_get_string(datatype); if (!strcmp(type_uri, (const char*)NS_XSD "int") || !strcmp(type_uri, (const char*)NS_XSD "integer")) { lv2_atom_forge_int(forge, strtol(str, NULL, 10)); } else if (!strcmp(type_uri, (const char*)NS_XSD "long")) { lv2_atom_forge_long(forge, strtol(str, NULL, 10)); } else if (!strcmp(type_uri, (const char*)NS_XSD "float") || !strcmp(type_uri, (const char*)NS_XSD "decimal")) { lv2_atom_forge_float(forge, (float)serd_strtod(str, NULL)); } else if (!strcmp(type_uri, (const char*)NS_XSD "double")) { lv2_atom_forge_double(forge, serd_strtod(str, NULL)); } else if (!strcmp(type_uri, (const char*)NS_XSD "boolean")) { lv2_atom_forge_bool(forge, !strcmp(str, "true")); } else if (!strcmp(type_uri, (const char*)NS_XSD "base64Binary")) { size_t size = 0; void* body = serd_base64_decode(USTR(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, sratom->midi_MidiEvent); for (const char* s = str; s < str + len; s += 2) { unsigned num = 0u; 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, sratom->map->map(sratom->map->handle, type_uri), 0); } } else if (language) { static const char* const prefix = "http://lexvo.org/id/iso639-3/"; const size_t prefix_len = strlen(prefix); const size_t language_len = strlen(language); const size_t lang_uri_len = prefix_len + language_len; char* lang_uri = (char*)calloc(lang_uri_len + 1, 1); memcpy(lang_uri, prefix, prefix_len + 1); memcpy(lang_uri + prefix_len, language, language_len + 1); lv2_atom_forge_literal( forge, str, len, 0, sratom->map->map(sratom->map->handle, lang_uri)); free(lang_uri); } else { lv2_atom_forge_string(forge, str, len); } } static void read_object(Sratom* sratom, LV2_Atom_Forge* forge, SordWorld* world, SordModel* model, const SordNode* node, ReadMode mode) { LV2_URID_Map* map = sratom->map; size_t len = 0; const char* str = (const char*)sord_node_get_string_counted(node, &len); SordNode* type = sord_get(model, node, sratom->nodes.rdf_type, NULL, NULL); SordNode* value = sord_get(model, node, sratom->nodes.rdf_value, NULL, NULL); const uint8_t* type_uri = NULL; uint32_t type_urid = 0; if (type) { type_uri = sord_node_get_string(type); type_urid = map->map(map->handle, (const char*)type_uri); } LV2_Atom_Forge_Frame frame = {0, 0}; if (mode == MODE_SEQUENCE) { SordNode* time = sord_get(model, node, sratom->nodes.atom_beatTime, NULL, NULL); uint32_t seq_unit = 0u; if (time) { const char* time_str = (const char*)sord_node_get_string(time); lv2_atom_forge_beat_time(forge, serd_strtod(time_str, NULL)); seq_unit = sratom->atom_beatTime; } else { time = sord_get(model, node, sratom->nodes.atom_frameTime, NULL, NULL); const char* time_str = time ? (const char*)sord_node_get_string(time) : ""; lv2_atom_forge_frame_time(forge, serd_strtod(time_str, NULL)); seq_unit = sratom->atom_frameTime; } read_node(sratom, forge, world, model, value, MODE_BODY); sord_node_free(world, time); sratom->seq_unit = seq_unit; } else if (type_urid == sratom->forge.Tuple) { lv2_atom_forge_tuple(forge, &frame); read_list_value(sratom, forge, world, model, value, MODE_BODY); } else if (type_urid == sratom->forge.Sequence) { const LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(forge, &frame, 0); sratom->seq_unit = 0; read_list_value(sratom, forge, world, model, value, MODE_SEQUENCE); LV2_Atom_Sequence* seq = (LV2_Atom_Sequence*)lv2_atom_forge_deref(forge, ref); seq->body.unit = (sratom->seq_unit == sratom->atom_frameTime) ? 0 : sratom->seq_unit; } else if (type_urid == sratom->forge.Vector) { SordNode* child_type_node = sord_get(model, node, sratom->nodes.atom_childType, NULL, NULL); uint32_t child_type = map->map(map->handle, (const char*)sord_node_get_string(child_type_node)); uint32_t child_size = atom_size(sratom, 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(sratom, forge, world, 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); } sord_node_free(world, child_type_node); } else if (value && sord_node_equals(sord_node_get_datatype(value), sratom->nodes.xsd_base64Binary)) { size_t vlen = 0; const uint8_t* vstr = sord_node_get_string_counted(value, &vlen); 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 (sord_node_get_type(node) == SORD_URI) { lv2_atom_forge_object(forge, &frame, map->map(map->handle, str), type_urid); read_resource(sratom, forge, world, model, node, type_urid); } else { lv2_atom_forge_object(forge, &frame, 0, type_urid); read_resource(sratom, forge, world, model, node, type_urid); } if (frame.ref) { lv2_atom_forge_pop(forge, &frame); } sord_node_free(world, value); sord_node_free(world, type); } static void read_node(Sratom* sratom, LV2_Atom_Forge* forge, SordWorld* world, SordModel* model, const SordNode* node, ReadMode mode) { LV2_URID_Map* map = sratom->map; size_t len = 0; const char* str = (const char*)sord_node_get_string_counted(node, &len); if (sord_node_get_type(node) == SORD_LITERAL) { read_literal(sratom, forge, node); } else if (sord_node_get_type(node) == SORD_URI && !(sratom->object_mode == SRATOM_OBJECT_MODE_BLANK_SUBJECT && mode == MODE_SUBJECT)) { if (!strcmp(str, (const char*)NS_RDF "nil")) { lv2_atom_forge_atom(forge, 0, 0); } else if (!strncmp(str, "file://", 7)) { SerdURI uri; serd_uri_parse((const uint8_t*)str, &uri); SerdNode rel = serd_node_new_relative_uri(&uri, &sratom->base, NULL, NULL); uint8_t* path = serd_file_uri_parse(rel.buf, NULL); if (path) { lv2_atom_forge_path( forge, (const char*)path, strlen((const char*)path)); serd_free(path); } else { // FIXME: Report errors (required API change) lv2_atom_forge_atom(forge, 0, 0); } serd_node_free(&rel); } else { lv2_atom_forge_urid(forge, map->map(map->handle, str)); } } else { read_object(sratom, forge, world, model, node, mode); } } void sratom_read(Sratom* sratom, LV2_Atom_Forge* forge, SordWorld* world, SordModel* model, const SordNode* node) { sratom->nodes.atom_childType = sord_new_uri(world, USTR(LV2_ATOM__childType)); sratom->nodes.atom_frameTime = sord_new_uri(world, USTR(LV2_ATOM__frameTime)); sratom->nodes.atom_beatTime = sord_new_uri(world, USTR(LV2_ATOM__beatTime)); sratom->nodes.rdf_first = sord_new_uri(world, NS_RDF "first"); sratom->nodes.rdf_rest = sord_new_uri(world, NS_RDF "rest"); sratom->nodes.rdf_type = sord_new_uri(world, NS_RDF "type"); sratom->nodes.rdf_value = sord_new_uri(world, NS_RDF "value"); sratom->nodes.xsd_base64Binary = sord_new_uri(world, NS_XSD "base64Binary"); sratom->next_id = 1; read_node(sratom, forge, world, model, node, MODE_SUBJECT); sord_node_free(world, sratom->nodes.xsd_base64Binary); sord_node_free(world, sratom->nodes.rdf_value); sord_node_free(world, sratom->nodes.rdf_type); sord_node_free(world, sratom->nodes.rdf_rest); sord_node_free(world, sratom->nodes.rdf_first); sord_node_free(world, sratom->nodes.atom_frameTime); sord_node_free(world, sratom->nodes.atom_beatTime); sord_node_free(world, sratom->nodes.atom_childType); memset(&sratom->nodes, 0, sizeof(sratom->nodes)); } LV2_Atom_Forge_Ref sratom_forge_sink(LV2_Atom_Forge_Sink_Handle handle, const void* buf, uint32_t size) { SerdChunk* chunk = (SerdChunk*)handle; const LV2_Atom_Forge_Ref ref = chunk->len + 1; serd_chunk_sink(buf, size, chunk); return ref; } LV2_Atom* sratom_forge_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref) { SerdChunk* chunk = (SerdChunk*)handle; return (LV2_Atom*)(chunk->buf + ref - 1); } LV2_Atom* sratom_from_turtle(Sratom* sratom, const char* base_uri, const SerdNode* subject, const SerdNode* predicate, const char* str) { SerdChunk out = {NULL, 0}; SerdNode base = serd_node_new_uri_from_string(USTR(base_uri), &sratom->base, NULL); SordWorld* world = sord_world_new(); SordModel* model = sord_new(world, SORD_SPO, false); SerdEnv* env = sratom->env ? sratom->env : serd_env_new(&base); SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL); if (!serd_reader_read_string(reader, (const uint8_t*)str)) { SordNode* s = sord_node_from_serd_node(world, env, subject, 0, 0); lv2_atom_forge_set_sink( &sratom->forge, sratom_forge_sink, sratom_forge_deref, &out); if (subject && predicate) { SordNode* p = sord_node_from_serd_node(world, env, predicate, 0, 0); SordNode* o = sord_get(model, s, p, NULL, NULL); if (o) { sratom_read(sratom, &sratom->forge, world, model, o); sord_node_free(world, o); } else { fprintf(stderr, "Failed to find node\n"); } } else { sratom_read(sratom, &sratom->forge, world, model, s); } } else { fprintf(stderr, "Failed to read Turtle\n"); } serd_reader_free(reader); if (!sratom->env) { serd_env_free(env); } sord_free(model); sord_world_free(world); serd_node_free(&base); return (LV2_Atom*)out.buf; }