diff options
-rw-r--r-- | doc/conf.py.in | 12 | ||||
-rw-r--r-- | include/sratom/sratom.h | 276 | ||||
-rw-r--r-- | meson.build | 42 | ||||
-rw-r--r-- | src/dumper.c | 546 | ||||
-rw-r--r-- | src/loader.c | 519 | ||||
-rw-r--r-- | src/sratom.c | 924 | ||||
-rw-r--r-- | test/meson.build | 2 | ||||
-rw-r--r-- | test/test_sratom.c | 190 |
8 files changed, 1350 insertions, 1161 deletions
diff --git a/doc/conf.py.in b/doc/conf.py.in index c3600d2..41ec55f 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -18,16 +18,16 @@ _opaque = [ "LV2_Atom_Forge", "LV2_Atom_Forge_Ref", "LV2_Atom_Forge_Sink_Handle", + "LV2_URID", "LV2_URID_Map", "LV2_URID_Unmap", - "SerdEndSink", "SerdEnv", + "SerdModel", "SerdNode", - "SerdStatementSink", - "SordModel", - "SordNode", - "SordWorld", - "SratomImpl", + "SerdSink", + "SerdWorld", + "SratomDumperImpl", + "SratomLoaderImpl", "uint32_t", ] diff --git a/include/sratom/sratom.h b/include/sratom/sratom.h index 077adff..68bff33 100644 --- a/include/sratom/sratom.h +++ b/include/sratom/sratom.h @@ -21,11 +21,10 @@ #include "lv2/atom/atom.h" #include "lv2/atom/forge.h" +#include "lv2/atom/util.h" #include "lv2/urid/urid.h" #include "serd/serd.h" -#include "sord/sord.h" -#include <stdbool.h> #include <stdint.h> #if defined(_WIN32) && !defined(SRATOM_STATIC) && defined(SRATOM_INTERNAL) @@ -44,165 +43,240 @@ extern "C" { /** @defgroup sratom Sratom C API - This is the public C API of Sratom. + This is the complete public C API of sratom. @{ */ -/// Atom serializer -typedef struct SratomImpl Sratom; +/// Free memory allocated in the sratom library +SRATOM_API +void +sratom_free(void* ptr); /** - Mode for reading resources to LV2 Objects. - - This affects how resources (which are either blank nodes or have URIs) are - read by sratom_read(), since they may be read as simple references (a URI or - blank node ID) or a complete description (an atom "Object"). - - Currently, blank nodes are always read as Objects, but support for reading - blank node IDs may be added in the future. + @defgroup sratom_dumping Dumping Atoms + Writing atoms to a statement stream or a string. + @{ */ + +/// Flags to control how atoms are written as statements typedef enum { - /// Read blank nodes as Objects, and named resources as URIs - SRATOM_OBJECT_MODE_BLANK, + /** + Write the main subject with a label. + + If set, the main subject will be written using its blank node label, + instead of as an anonymous node. + */ + SRATOM_NAMED_SUBJECT = 1u << 1u, /** - Read blank nodes and the main subject as Objects. + Write pretty numeric literals in Turtle or TriG. - Any other named resources are read as URIs. The "main subject" is the - subject parameter passed to sratom_read(); if this is a resource it will - be read as an Object, but all other named resources encountered will be - read as URIs. + If set, numbers may be written as pretty literals instead of string + literals with explicit data types. Note that enabling this means that + types will not survive a round trip. */ - SRATOM_OBJECT_MODE_BLANK_SUBJECT -} SratomObjectMode; + SRATOM_PRETTY_NUMBERS = 1u << 2u, -/// Create a new Atom serializer -SRATOM_API -Sratom* -sratom_new(LV2_URID_Map* map); + /** + Write terse output without newlines. -/// Free an Atom serializer -SRATOM_API -void -sratom_free(Sratom* sratom); + If set, top level atoms will be written as a single long line. + */ + SRATOM_TERSE = 1u << 3u, +} SratomDumperFlag; -/** - Set the environment for reading or writing Turtle. +/// Bitwise OR of SratomDumperFlag values +typedef uint32_t SratomDumperFlags; - This can be used to set namespace prefixes and a base URI for - sratom_to_turtle() and sratom_from_turtle(). -*/ -SRATOM_API -void -sratom_set_env(Sratom* sratom, SerdEnv* env); +/// Dumper that writes atoms to a statement sink +typedef struct SratomDumperImpl SratomDumper; /** - Set the sink(s) where sratom will write its output. + Create a new atom dumper. + + @param world Serd world. + @param map URID mapper, only used during construction. + @param unmap URID unmapper, used while dumping to write URI strings. - This must be called before calling sratom_write(). + @return A newly allocated object which must be freed with + sratom_dumper_free(). */ SRATOM_API +SratomDumper* +sratom_dumper_new(SerdWorld* world, LV2_URID_Map* map, LV2_URID_Unmap* unmap); + +/// Free an atom dumper created with sratom_dumper_new() +SRATOM_API void -sratom_set_sink(Sratom* sratom, - const char* base_uri, - SerdStatementSink sink, - SerdEndSink end_sink, - void* handle); +sratom_dumper_free(SratomDumper* dumper); /** - Write pretty numeric literals. - - If `pretty_numbers` is true, numbers will be written as pretty Turtle - literals, rather than string literals with precise types. The cost of this - is that the types might get fudged on a round-trip to RDF and back. + Write an atom body to a statement sink. + + This is the fundamental write function that writes an atom to a sink as a + series of statements. It can be used with any sink, such as a Turtle + writer, model inserter, or a custom sink provided by the application. + + This function takes the `type`, `size`, and `body` separately to allow + writing atoms from data in memory that does not have an atom header. For + true atom pointers, the simpler sratom_dump_atom() can be used instead. + + Since all statements must be triples, a subject and predicate can be + provided to serialise literals like `subject predicate "literal"`. If + `subject` and `predicate` are null, resources will be written as the + subject, but literals will be written as the only element of an anonymous + list. A subject and predicate should always be given for lossless + round-tripping. This corresponds to using atoms as property values. + + @param dumper Dumper instance + @param env Environment defining the base URI and any prefixes + @param sink Sink which receives the serialised statements + @param subject Subject of first statement, or NULL + @param predicate Predicate of first statement, or NULL + @param type Type of the atom + @param size Size of the atom body in bytes + @param body Atom body + @param flags Option flags + @return Zero on success */ SRATOM_API -void -sratom_set_pretty_numbers(Sratom* sratom, bool pretty_numbers); +int +sratom_dump(SratomDumper* dumper, + const SerdEnv* env, + const SerdSink* sink, + const SerdNode* subject, + const SerdNode* predicate, + LV2_URID type, + uint32_t size, + const void* body, + SratomDumperFlags flags); -/// Configure how resources will be read to form LV2 Objects +/** + Write an atom to a statement sink + + Convenience wrapper that takes a pointer to a complete atom, see the + sratom_dump() documentation for details. + + @param dumper Dumper instance + @param env Environment defining the base URI and any prefixes + @param sink Sink which receives the serialised statements + @param subject Subject of first statement, or NULL + @param predicate Predicate of first statement, or NULL + @param atom Atom to serialise + @param flags Option flags + @return Zero on success +*/ SRATOM_API -void -sratom_set_object_mode(Sratom* sratom, SratomObjectMode object_mode); +int +sratom_dump_atom(SratomDumper* dumper, + const SerdEnv* env, + const SerdSink* sink, + const SerdNode* subject, + const SerdNode* predicate, + const LV2_Atom* atom, + SratomDumperFlags flags); /** - Write an Atom to RDF. + Write an atom as a string. - The serialized atom is written to the sink set by sratom_set_sink(). + The returned string can be forged back into an atom using + sratom_from_string(). - @return 0 on success, or a non-zero error code otherwise. + @param dumper Dumper instance + @param env Environment for namespaces and relative URIs + @param atom Atom to serialise + @param flags Option flags + @return A string that must be freed using sratom_free(), or NULL on error. */ SRATOM_API -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); +char* +sratom_to_string(SratomDumper* dumper, + const SerdEnv* env, + const LV2_Atom* atom, + SratomDumperFlags flags); /** - Read an Atom from RDF. + @} + @defgroup sratom_loading Loading Atoms + @{ +*/ + +/// Atom loader that reads atoms from a document +typedef struct SratomLoaderImpl SratomLoader; + +/** + Create a new loader for forging atoms from a document. - The resulting atom will be written to `forge`. + @param world RDF world + @param map URID mapper + @return A new object that must be freed with sratom_loader_free() */ SRATOM_API +SratomLoader* +sratom_loader_new(SerdWorld* world, LV2_URID_Map* map); + +/// Free an atom loader created with sratom_loader_new() +SRATOM_API void -sratom_read(Sratom* sratom, - LV2_Atom_Forge* forge, - SordWorld* world, - SordModel* model, - const SordNode* node); +sratom_loader_free(SratomLoader* loader); /** - Serialize an Atom to a Turtle string. + Load an atom from a model by forging the binary representation. - The returned string must be free()'d by the caller. + This reads `node` in `model` as an atom, constructing the result with + `forge`. + + @param loader Loader set up with the appropriate world and URID map. + + @param base_uri Base URI for making relative URI references. This is + typically used with file URIs to construct atoms with relative paths. Any + URI within the given base URI will be written to `forge` as a relative URI + reference. For example, with base URI <file:///my/data/>, the URI + <file:///my/data/dir/file.txt> will be written to the forge as the path + "dir/file.txt". + + @param forge Atom forge where the result is written. + + @param model Model that contains a description of the atom. + + @param node Node for the atom. This can be a simple literal node for + primitive atoms, or the subject resource for more complex structures like + objects and vectors. + + @return Zero on success. */ SRATOM_API -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); +int +sratom_load(SratomLoader* loader, + const SerdNode* base_uri, + LV2_Atom_Forge* forge, + const SerdModel* model, + const SerdNode* node); /** - Read an Atom from a Turtle string. + Load an Atom from a Turtle string. The returned atom must be free()'d by the caller. */ SRATOM_API LV2_Atom* -sratom_from_turtle(Sratom* sratom, - const char* base_uri, - const SerdNode* subject, - const SerdNode* predicate, - const char* str); +sratom_from_string(SratomLoader* loader, SerdEnv* env, const char* str); /** - A convenient resizing sink for LV2_Atom_Forge. + Load an Atom from a model. - The handle must point to an initialized SerdChunk. + The returned atom must be free()'d by the caller. */ SRATOM_API -LV2_Atom_Forge_Ref -sratom_forge_sink(LV2_Atom_Forge_Sink_Handle handle, - const void* buf, - uint32_t size); - -/// The corresponding deref function for sratom_forge_sink -SRATOM_API LV2_Atom* -sratom_forge_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref); +sratom_from_model(SratomLoader* loader, + const SerdNode* base_uri, + const SerdModel* model, + const SerdNode* subject); /** @} + @} */ #ifdef __cplusplus diff --git a/meson.build b/meson.build index d18853b..c85b966 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('sratom', ['c'], - version: '0.6.9', + version: '1.0.0', license: 'ISC', meson_version: '>= 0.49.2', default_options: [ @@ -7,7 +7,7 @@ project('sratom', ['c'], 'buildtype=release', 'c_std=c99', 'default_library=shared', - 'warning_level=2', + 'warning_level=3', ]) sratom_src_root = meson.current_source_dir() @@ -19,7 +19,12 @@ versioned_name = 'sratom' + version_suffix pkg = import('pkgconfig') cc = meson.get_compiler('c') +if add_languages('cpp', required: get_option('bindings_cpp')) + cpp = meson.get_compiler('cpp') +endif + # Set ultra strict warnings for developers, if requested +c_warnings = [] if get_option('strict') subdir('meson') @@ -55,10 +60,17 @@ if get_option('strict') ] endif - add_project_arguments(cc.get_supported_arguments(c_warnings), - language: ['c']) +else + if cc.get_id() == 'clang' + c_warnings += [ + '-Wno-nullability-extension', + ] + endif endif +add_project_arguments(cc.get_supported_arguments(c_warnings), + language: ['c']) + # Add special arguments for MSVC if cc.get_id() == 'msvc' msvc_args = [ @@ -76,7 +88,8 @@ c_headers = ['include/sratom/sratom.h'] c_header_files = files(c_headers) sources = [ - 'src/sratom.c', + 'src/dumper.c', + 'src/loader.c', ] # System libraries @@ -85,17 +98,13 @@ m_dep = cc.find_library('m', required: false) # Dependencies lv2_dep = dependency('lv2', - version: '>= 1.18.3', + version: '>= 1.18.2', fallback: ['lv2', 'lv2_dep']) -serd_dep = dependency('serd-0', - version: '>= 0.30.9', +serd_dep = dependency('serd-1', + version: '>= 1.0.0', fallback: ['serd', 'serd_dep']) -sord_dep = dependency('sord-0', - version: '>= 0.16.9', - fallback: ['sord', 'sord_dep']) - # Determine library type and the flags needed to build it if get_option('default_library') == 'both' if host_machine.system() == 'windows' @@ -122,14 +131,14 @@ libsratom = build_target( version: meson.project_version(), include_directories: include_directories(['include']), c_args: library_args, - dependencies: [m_dep, lv2_dep, serd_dep, sord_dep], + dependencies: [m_dep, lv2_dep, serd_dep], gnu_symbol_visibility: 'hidden', install: true, target_type: library_type) sratom_dep = declare_dependency( include_directories: include_directories(['include']), - dependencies: [m_dep, lv2_dep, serd_dep, sord_dep], + dependencies: [m_dep, lv2_dep, serd_dep], link_with: libsratom) pkg.generate( @@ -151,6 +160,11 @@ if get_option('tests') subdir('test') endif +# C++ bindings +if is_variable('cpp') + subdir('bindings/cpp') +endif + if meson.version().version_compare('>=0.53.0') summary('Tests', get_option('tests'), bool_yn: true) 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); +} 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; +} diff --git a/src/sratom.c b/src/sratom.c deleted file mode 100644 index 692257d..0000000 --- a/src/sratom.c +++ /dev/null @@ -1,924 +0,0 @@ -/* - 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 <assert.h> -#include <ctype.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#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 &= ~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)); - - uint8_t* str = (uint8_t*)calloc(size * 2 + 1, 1); - for (uint32_t i = 0; i < size; ++i) { - snprintf((char*)str + (2 * i), - size * 2 + 1, - "%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; -} diff --git a/test/meson.build b/test/meson.build index fe37bd3..afe704a 100644 --- a/test/meson.build +++ b/test/meson.build @@ -10,7 +10,7 @@ foreach unit : unit_tests 'test_@0@.c'.format(unit), c_args: prog_args, include_directories: include_directories(['../src']), - dependencies: [sratom_dep, serd_dep, sord_dep]), + dependencies: [sratom_dep, serd_dep]), suite: 'unit') endforeach diff --git a/test/test_sratom.c b/test/test_sratom.c index 95545fc..12daea3 100644 --- a/test/test_sratom.c +++ b/test/test_sratom.c @@ -1,5 +1,5 @@ /* - Copyright 2012-2016 David Robillard <d@drobilla.net> + Copyright 2012-2016 David Robillard <http://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 @@ -14,35 +14,28 @@ 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 "sratom/sratom.h" -#include <stdarg.h> +#include <assert.h> #include <stdbool.h> -#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#define NS_ATOM "http://lv2plug.in/ns/ext/atom#" #define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" - -#define USTR(s) ((const uint8_t*)(s)) - -#if defined(__GNUC__) -# define SRATOM_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) -#else -# define SRATOM_LOG_FUNC(fmt, arg1) -#endif +#define NS_XSD "http://www.w3.org/2001/XMLSchema#" /// Simple O(n) URI map typedef struct { - char** uris; - size_t n_uris; + char** uris; + uint32_t n_uris; } Uris; static char* @@ -59,7 +52,7 @@ urid_map(LV2_URID_Map_Handle handle, const char* uri) { Uris* const uris = (Uris*)handle; - for (size_t i = 0; i < uris->n_uris; ++i) { + for (uint32_t i = 0; i < uris->n_uris; ++i) { if (!strcmp(uris->uris[i], uri)) { return i + 1; } @@ -82,20 +75,44 @@ urid_unmap(LV2_URID_Unmap_Handle handle, LV2_URID urid) return NULL; } -SRATOM_LOG_FUNC(1, 2) static int -test_fail(const char* fmt, ...) +check_round_trip(Uris* uris, + SerdEnv* env, + SratomDumper* dumper, + SratomLoader* loader, + const LV2_Atom* obj, + const SratomDumperFlags flags) { - va_list args; - va_start(args, fmt); - fprintf(stderr, "error: "); - vfprintf(stderr, fmt, args); - va_end(args); - return 1; + (void)uris; // FIXME + + // Serialise atom and print string + char* const outstr = sratom_to_string(dumper, env, obj, flags); + assert(outstr); + + printf("%s\n", outstr); + + // Parse serialised string back into an atom + LV2_Atom* const parsed = sratom_from_string(loader, env, outstr); + assert(parsed); + + if (!(flags & SRATOM_PRETTY_NUMBERS)) { + // Check that round tripped atom is identical to original + assert(obj->type == parsed->type); + assert(lv2_atom_equals(obj, parsed)); + + /* char* const instr = sratom_to_string(writer, env, parsed, flags); */ + /* if ( */ + /* printf("# Turtle => Atom\n\n%s", instr); */ + } + + sratom_free(parsed); + sratom_free(outstr); + + return 0; } static int -test(SerdEnv* env, bool top_level, bool pretty_numbers) +test(SerdEnv* env, const char* name, const SratomDumperFlags flags) { Uris uris = {NULL, 0}; LV2_URID_Map map = {&uris, urid_map}; @@ -103,12 +120,9 @@ test(SerdEnv* env, bool top_level, bool pretty_numbers) LV2_Atom_Forge forge; lv2_atom_forge_init(&forge, &map); - Sratom* sratom = sratom_new(&map); - sratom_set_env(sratom, env); - sratom_set_pretty_numbers(sratom, pretty_numbers); - sratom_set_object_mode(sratom, - top_level ? SRATOM_OBJECT_MODE_BLANK_SUBJECT - : SRATOM_OBJECT_MODE_BLANK); + SerdWorld* world = serd_world_new(); + SratomDumper* dumper = sratom_dumper_new(world, &map, &unmap); + SratomLoader* loader = sratom_loader_new(world, &map); LV2_URID eg_Object = urid_map(&uris, "http://example.org/Object"); LV2_URID eg_one = urid_map(&uris, "http://example.org/a-one"); @@ -141,15 +155,8 @@ test(SerdEnv* env, bool top_level, bool pretty_numbers) uint8_t buf[1024]; lv2_atom_forge_set_buffer(&forge, buf, sizeof(buf)); - const char* obj_uri = "http://example.org/obj"; - LV2_URID obj_id = urid_map(&uris, obj_uri); LV2_Atom_Forge_Frame obj_frame; - if (top_level) { - lv2_atom_forge_object(&forge, &obj_frame, obj_id, eg_Object); - } else { - lv2_atom_forge_object(&forge, &obj_frame, 0, eg_Object); - } - + lv2_atom_forge_object(&forge, &obj_frame, 0, eg_Object); LV2_Atom* obj = lv2_atom_forge_deref(&forge, obj_frame.ref); // eg_one = (Int32)1 @@ -182,8 +189,8 @@ test(SerdEnv* env, bool top_level, bool pretty_numbers) lv2_atom_forge_key(&forge, eg_path); lv2_atom_forge_path(&forge, pstr, pstr_len); - // eg_winpath = (Path)"C:\Stupid\File System" - const char* wpstr = "C:/Stupid/File System"; + // eg_winpath = (Path)"C:/Weird/File System" + const char* wpstr = "C:/Weird/File System"; const size_t wpstr_len = strlen(wpstr); lv2_atom_forge_key(&forge, eg_winpath); lv2_atom_forge_path(&forge, wpstr, wpstr_len); @@ -333,96 +340,49 @@ test(SerdEnv* env, bool top_level, bool pretty_numbers) lv2_atom_forge_pop(&forge, &obj_frame); - const char* base_uri = "file:///tmp/base/"; - - SerdNode s = serd_node_from_string(SERD_URI, USTR("http://example.org/obj")); - SerdNode p = serd_node_from_string(SERD_URI, USTR(NS_RDF "value")); - - SerdNode* subj = top_level ? NULL : &s; - SerdNode* pred = top_level ? NULL : &p; - - char* outstr = sratom_to_turtle(sratom, - &unmap, - base_uri, - subj, - pred, - obj->type, - obj->size, - LV2_ATOM_BODY(obj)); - - printf("# Atom => Turtle\n\n%s", outstr); - - LV2_Atom* parsed = NULL; - if (top_level) { - SerdNode o = serd_node_from_string(SERD_URI, (const uint8_t*)obj_uri); - parsed = sratom_from_turtle(sratom, base_uri, &o, NULL, outstr); - } else { - parsed = sratom_from_turtle(sratom, base_uri, subj, pred, outstr); - } - - if (!pretty_numbers) { - if (!lv2_atom_equals(obj, parsed)) { - return test_fail("Parsed atom does not match original\n"); - } - - char* instr = sratom_to_turtle(sratom, - &unmap, - base_uri, - subj, - pred, - parsed->type, - parsed->size, - LV2_ATOM_BODY(parsed)); - printf("# Turtle => Atom\n\n%s", instr); - - if (strcmp(outstr, instr)) { - return test_fail("Re-serialised string differs from original\n"); - } - free(instr); + printf("\n# %s\n\n", name); + check_round_trip(&uris, env, dumper, loader, obj, flags); + printf("\n"); + LV2_ATOM_OBJECT_FOREACH ((LV2_Atom_Object*)obj, prop) { + check_round_trip(&uris, env, dumper, loader, &prop->value, flags); } - printf("All tests passed.\n"); - - free(parsed); - free(outstr); - sratom_free(sratom); + sratom_dumper_free(dumper); + sratom_loader_free(loader); for (uint32_t i = 0; i < uris.n_uris; ++i) { free(uris.uris[i]); } + serd_world_free(world); free(uris.uris); return 0; } -static int -test_env(SerdEnv* env) -{ - if (test(env, false, false) || // - test(env, true, false) || // - test(env, false, true) || // - test(env, true, true)) { - return 1; - } - - return 0; -} - int main(void) { - // Test with no environment - if (test_env(NULL)) { - return 1; - } + SerdEnv* const env = serd_env_new(SERD_STATIC_STRING("file:///tmp/base/")); - // Test with a prefix defined - SerdEnv* env = serd_env_new(NULL); - serd_env_set_prefix_from_strings( - env, (const uint8_t*)"eg", (const uint8_t*)"http://example.org/"); + serd_env_set_prefix( + env, SERD_STATIC_STRING("eg"), SERD_STATIC_STRING("http://example.org/")); + + serd_env_set_prefix( + env, SERD_STATIC_STRING("atom"), SERD_STATIC_STRING(NS_ATOM)); + + serd_env_set_prefix( + env, SERD_STATIC_STRING("rdf"), SERD_STATIC_STRING(NS_RDF)); + + serd_env_set_prefix( + env, SERD_STATIC_STRING("xsd"), SERD_STATIC_STRING(NS_XSD)); + + const int st = + (test(env, "Default", 0) || // + test(env, "Pretty", SRATOM_PRETTY_NUMBERS) || + test(env, "Terse", SRATOM_TERSE) || + test(env, "Pretty + Terse", SRATOM_PRETTY_NUMBERS | SRATOM_TERSE)); - test_env(env); serd_env_free(env); - return 0; + return st; } |