diff options
-rw-r--r-- | include/serd/serd.h | 119 | ||||
-rw-r--r-- | src/env.c | 6 | ||||
-rw-r--r-- | src/sink.c | 53 | ||||
-rw-r--r-- | src/sink.h | 9 | ||||
-rw-r--r-- | src/writer.c | 26 | ||||
-rw-r--r-- | test/test_env.c | 16 | ||||
-rw-r--r-- | test/test_overflow.c | 2 | ||||
-rw-r--r-- | test/test_read_chunk.c | 24 | ||||
-rw-r--r-- | test/test_reader_writer.c | 36 | ||||
-rw-r--r-- | test/test_sink.c | 49 | ||||
-rw-r--r-- | test/test_writer.c | 26 |
11 files changed, 228 insertions, 138 deletions
diff --git a/include/serd/serd.h b/include/serd/serd.h index 0120c73b..db03108c 100644 --- a/include/serd/serd.h +++ b/include/serd/serd.h @@ -1198,6 +1198,14 @@ serd_world_set_error_func(SerdWorld* SERD_NONNULL world, @{ */ +/// Type of a SerdEvent +typedef enum { + SERD_BASE = 1, ///< Base URI changed + SERD_PREFIX = 2, ///< New URI prefix + SERD_STATEMENT = 3, ///< Statement + SERD_END = 4 ///< End of anonymous node +} SerdEventType; + /// Flags indicating inline abbreviation information for a statement typedef enum { SERD_EMPTY_S = 1u << 1u, ///< Empty blank node subject @@ -1214,42 +1222,69 @@ typedef enum { typedef uint32_t SerdStatementFlags; /** - Sink function for base URI changes + Event for base URI changes. + + Emitted whenever the base URI changes. +*/ +typedef struct { + SerdEventType type; ///< #SERD_BASE + const SerdNode* SERD_NONNULL uri; ///< Base URI +} SerdBaseEvent; + +/** + Event for namespace definitions. - Called whenever the base URI of the serialisation changes. + Emitted whenever a prefix is defined. */ -typedef SerdStatus (*SerdBaseFunc)(void* SERD_NULLABLE handle, - const SerdNode* SERD_NONNULL uri); +typedef struct { + SerdEventType type; ///< #SERD_PREFIX + const SerdNode* SERD_NONNULL name; ///< Prefix name + const SerdNode* SERD_NONNULL uri; ///< Namespace URI +} SerdPrefixEvent; /** - Sink function for namespace definitions. + Event for statements. - Called whenever a prefix is defined in the serialisation. + Emitted for every statement. */ -typedef SerdStatus (*SerdPrefixFunc)(void* SERD_NULLABLE handle, - const SerdNode* SERD_NONNULL name, - const SerdNode* SERD_NONNULL uri); +typedef struct { + SerdEventType type; ///< #SERD_STATEMENT + SerdStatementFlags flags; ///< Flags for pretty-printing + const SerdStatement* SERD_NONNULL statement; ///< Statement +} SerdStatementEvent; /** - Sink function for statements. + Event for the end of anonymous node descriptions. - Called for every RDF statement in the serialisation. + This is emitted to indicate that the given anonymous node will no longer be + described. This is used by the writer which may, for example, need to + write a delimiter. */ -typedef SerdStatus (*SerdStatementFunc)(void* SERD_NULLABLE handle, - SerdStatementFlags flags, - const SerdStatement* SERD_NONNULL - statement); +typedef struct { + SerdEventType type; ///< #SERD_END + const SerdNode* SERD_NONNULL node; ///< Anonymous node that is finished +} SerdEndEvent; /** - Sink function for anonymous node end markers. + An event in a data stream. - This is called to indicate that the anonymous node with the given `value` - will no longer be referred to by any future statements (so the anonymous - node is finished). + Streams of data are represented as a series of events. Events represent + everything that can occur in an RDF document, and are used to plumb together + different components. For example, when parsing a document, a reader emits + a stream of events which can be sent to a writer to rewrite a document, or + to an inserter to build a model in memory. */ -typedef SerdStatus (*SerdEndFunc)(void* SERD_NULLABLE handle, - const SerdNode* SERD_NONNULL node); +typedef union { + SerdEventType type; ///< Event type (always set) + SerdBaseEvent base; ///< Base URI changed + SerdPrefixEvent prefix; ///< New namespace prefix + SerdStatementEvent statement; ///< Statement + SerdEndEvent end; ///< End of anonymous node +} SerdEvent; +/// Function for handling events +typedef SerdStatus (*SerdEventFunc)(void* SERD_NULLABLE handle, + const SerdEvent* SERD_NONNULL event); /** @} @defgroup serd_sink Sink @@ -1265,45 +1300,26 @@ typedef void (*SerdFreeFunc)(void* SERD_NULLABLE ptr); /** Create a new sink. - Initially, the sink has no set functions and will do nothing. Use the - serd_sink_set_*_func functions to set handlers for various events. - @param handle Opaque handle that will be passed to sink functions. + @param event_func Function that will be called for every event. @param free_handle Free function to call on handle in serd_sink_free(). */ SERD_API SerdSink* SERD_ALLOCATED -serd_sink_new(void* SERD_NULLABLE handle, - SerdFreeFunc SERD_NULLABLE free_handle); +serd_sink_new(void* SERD_NULLABLE handle, + SerdEventFunc SERD_NULLABLE event_func, + SerdFreeFunc SERD_NULLABLE free_handle); /// Free `sink` SERD_API void serd_sink_free(SerdSink* SERD_NULLABLE sink); -/// Set a function to be called when the base URI changes -SERD_API -SerdStatus -serd_sink_set_base_func(SerdSink* SERD_NONNULL sink, - SerdBaseFunc SERD_NULLABLE base_func); - -/// Set a function to be called when a namespace prefix is defined -SERD_API -SerdStatus -serd_sink_set_prefix_func(SerdSink* SERD_NONNULL sink, - SerdPrefixFunc SERD_NULLABLE prefix_func); - -/// Set a function to be called when a statement is emitted -SERD_API -SerdStatus -serd_sink_set_statement_func(SerdSink* SERD_NONNULL sink, - SerdStatementFunc SERD_NULLABLE statement_func); - -/// Set a function to be called when an anonymous node ends +/// Send an event to the sink SERD_API SerdStatus -serd_sink_set_end_func(SerdSink* SERD_NONNULL sink, - SerdEndFunc SERD_NULLABLE end_func); +serd_sink_write_event(const SerdSink* SERD_NONNULL sink, + const SerdEvent* SERD_NONNULL event); /// Set the base URI SERD_API @@ -1414,12 +1430,11 @@ SerdNode* SERD_ALLOCATED serd_env_expand_node(const SerdEnv* SERD_NULLABLE env, const SerdNode* SERD_NONNULL node); -/// Call `func` for each prefix defined in `env` +/// Write all prefixes in `env` to `sink` SERD_API void -serd_env_foreach(const SerdEnv* SERD_NONNULL env, - SerdPrefixFunc SERD_NONNULL func, - void* SERD_NULLABLE handle); +serd_env_write_prefixes(const SerdEnv* SERD_NONNULL env, + const SerdSink* SERD_NONNULL sink); /** @} @@ -1539,7 +1554,7 @@ serd_reader_free(SerdReader* SERD_NULLABLE reader); @{ */ -/// Streaming serialiser that writes a text stream as statements are pushed +/// Streaming writer that writes a text stream as it receives events typedef struct SerdWriterImpl SerdWriter; /** @@ -249,11 +249,9 @@ serd_env_expand_node(const SerdEnv* const env, const SerdNode* const node) } void -serd_env_foreach(const SerdEnv* const env, - const SerdPrefixFunc func, - void* const handle) +serd_env_write_prefixes(const SerdEnv* const env, const SerdSink* const sink) { for (size_t i = 0; i < env->n_prefixes; ++i) { - func(handle, env->prefixes[i].name, env->prefixes[i].uri); + serd_sink_write_prefix(sink, env->prefixes[i].name, env->prefixes[i].uri); } } @@ -24,11 +24,14 @@ #include <stdlib.h> SerdSink* -serd_sink_new(void* handle, SerdFreeFunc free_handle) +serd_sink_new(void* const handle, + SerdEventFunc event_func, + SerdFreeFunc free_handle) { SerdSink* sink = (SerdSink*)calloc(1, sizeof(SerdSink)); sink->handle = handle; + sink->on_event = event_func; sink->free_handle = free_handle; return sink; @@ -47,37 +50,18 @@ serd_sink_free(SerdSink* sink) } SerdStatus -serd_sink_set_base_func(SerdSink* sink, SerdBaseFunc base_func) +serd_sink_write_event(const SerdSink* sink, const SerdEvent* event) { - sink->base = base_func; - return SERD_SUCCESS; -} - -SerdStatus -serd_sink_set_prefix_func(SerdSink* sink, SerdPrefixFunc prefix_func) -{ - sink->prefix = prefix_func; - return SERD_SUCCESS; -} - -SerdStatus -serd_sink_set_statement_func(SerdSink* sink, SerdStatementFunc statement_func) -{ - sink->statement = statement_func; - return SERD_SUCCESS; -} - -SerdStatus -serd_sink_set_end_func(SerdSink* sink, SerdEndFunc end_func) -{ - sink->end = end_func; - return SERD_SUCCESS; + return sink->on_event ? sink->on_event(sink->handle, event) : SERD_SUCCESS; } SerdStatus serd_sink_write_base(const SerdSink* sink, const SerdNode* uri) { - return sink->base ? sink->base(sink->handle, uri) : SERD_SUCCESS; + const SerdBaseEvent ev = {SERD_BASE, uri}; + + return sink->on_event ? sink->on_event(sink->handle, (const SerdEvent*)&ev) + : SERD_SUCCESS; } SerdStatus @@ -85,7 +69,10 @@ serd_sink_write_prefix(const SerdSink* sink, const SerdNode* name, const SerdNode* uri) { - return sink->prefix ? sink->prefix(sink->handle, name, uri) : SERD_SUCCESS; + const SerdPrefixEvent ev = {SERD_PREFIX, name, uri}; + + return sink->on_event ? sink->on_event(sink->handle, (const SerdEvent*)&ev) + : SERD_SUCCESS; } SerdStatus @@ -93,8 +80,11 @@ serd_sink_write_statement(const SerdSink* sink, const SerdStatementFlags flags, const SerdStatement* statement) { - return sink->statement ? sink->statement(sink->handle, flags, statement) - : SERD_SUCCESS; + const SerdStatementEvent statement_ev = {SERD_STATEMENT, flags, statement}; + SerdEvent ev = {SERD_STATEMENT}; + ev.statement = statement_ev; + + return sink->on_event ? sink->on_event(sink->handle, &ev) : SERD_SUCCESS; } SerdStatus @@ -117,5 +107,8 @@ serd_sink_write(const SerdSink* sink, SerdStatus serd_sink_write_end(const SerdSink* sink, const SerdNode* node) { - return sink->end ? sink->end(sink->handle, node) : SERD_SUCCESS; + const SerdEndEvent ev = {SERD_END, node}; + + return sink->on_event ? sink->on_event(sink->handle, (const SerdEvent*)&ev) + : SERD_SUCCESS; } @@ -23,12 +23,9 @@ An interface that receives a stream of RDF data. */ struct SerdSinkImpl { - void* handle; - SerdFreeFunc free_handle; - SerdBaseFunc base; - SerdPrefixFunc prefix; - SerdStatementFunc statement; - SerdEndFunc end; + void* handle; + SerdFreeFunc free_handle; + SerdEventFunc on_event; }; #endif // SERD_SINK_H diff --git a/src/writer.c b/src/writer.c index 4e628cc2..66a53819 100644 --- a/src/writer.c +++ b/src/writer.c @@ -915,6 +915,25 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node) return SERD_SUCCESS; } +static SerdStatus +serd_writer_on_event(SerdWriter* writer, const SerdEvent* event) +{ + switch (event->type) { + case SERD_BASE: + return serd_writer_set_base_uri(writer, event->base.uri); + case SERD_PREFIX: + return serd_writer_set_prefix( + writer, event->prefix.name, event->prefix.uri); + case SERD_STATEMENT: + return serd_writer_write_statement( + writer, event->statement.flags, event->statement.statement); + case SERD_END: + return serd_writer_end_anon(writer, event->end.node); + } + + return SERD_ERR_BAD_ARG; +} + SerdStatus serd_writer_finish(SerdWriter* writer) { @@ -955,11 +974,8 @@ serd_writer_new(SerdWorld* world, writer->byte_sink = serd_byte_sink_new( ssink, stream, (flags & SERD_WRITE_BULK) ? SERD_PAGE_SIZE : 1); - writer->iface.handle = writer; - writer->iface.base = (SerdBaseFunc)serd_writer_set_base_uri; - writer->iface.prefix = (SerdPrefixFunc)serd_writer_set_prefix; - writer->iface.statement = (SerdStatementFunc)serd_writer_write_statement; - writer->iface.end = (SerdEndFunc)serd_writer_end_anon; + writer->iface.handle = writer; + writer->iface.on_event = (SerdEventFunc)serd_writer_on_event; return writer; } diff --git a/test/test_env.c b/test/test_env.c index 1748b4cf..e5be688e 100644 --- a/test/test_env.c +++ b/test/test_env.c @@ -22,12 +22,12 @@ #include <string.h> static SerdStatus -count_prefixes(void* handle, const SerdNode* name, const SerdNode* uri) +count_prefixes(void* handle, const SerdEvent* event) { - (void)name; - (void)uri; + if (event->type == SERD_PREFIX) { + ++*(int*)handle; + } - ++*(int*)handle; return SERD_SUCCESS; } @@ -87,10 +87,13 @@ test_env(void) const SerdNode* blank = serd_nodes_blank(nodes, SERD_STRING("b1")); assert(!serd_env_expand_node(env, blank)); - int n_prefixes = 0; + size_t n_prefixes = 0; + SerdSink* const count_prefixes_sink = + serd_sink_new(&n_prefixes, count_prefixes, NULL); + serd_env_set_prefix( env, SERD_STRING("eg.2"), SERD_STRING("http://example.org/")); - serd_env_foreach(env, count_prefixes, &n_prefixes); + serd_env_write_prefixes(env, count_prefixes_sink); assert(n_prefixes == 1); const SerdNode* shorter_uri = serd_nodes_uri(nodes, SERD_STRING("urn:foo")); @@ -107,6 +110,7 @@ test_env(void) assert(!serd_env_set_base_uri(env, SERD_EMPTY_STRING())); assert(!serd_env_base_uri(env)); + serd_sink_free(count_prefixes_sink); serd_nodes_free(nodes); serd_env_free(env); } diff --git a/test/test_overflow.c b/test/test_overflow.c index 2d7c2542..f29c8652 100644 --- a/test/test_overflow.c +++ b/test/test_overflow.c @@ -30,7 +30,7 @@ test_size(SerdWorld* const world, const SerdSyntax syntax, const size_t stack_size) { - SerdSink* sink = serd_sink_new(NULL, NULL); + SerdSink* sink = serd_sink_new(NULL, NULL, NULL); SerdReader* const reader = serd_reader_new(world, syntax, sink, stack_size); assert(reader); diff --git a/test/test_read_chunk.c b/test/test_read_chunk.c index f5ba8d93..0055ef3a 100644 --- a/test/test_read_chunk.c +++ b/test/test_read_chunk.c @@ -70,15 +70,29 @@ on_end(void* handle, const SerdNode* node) return SERD_SUCCESS; } +static SerdStatus +on_event(void* handle, const SerdEvent* event) +{ + switch (event->type) { + case SERD_BASE: + return on_base(handle, event->base.uri); + case SERD_PREFIX: + return on_prefix(handle, event->prefix.name, event->prefix.uri); + case SERD_STATEMENT: + return on_statement( + handle, event->statement.flags, event->statement.statement); + case SERD_END: + break; + } + + return on_end(handle, event->end.node); +} + int main(void) { SerdWorld* world = serd_world_new(); - SerdSink* sink = serd_sink_new(NULL, NULL); - serd_sink_set_base_func(sink, on_base); - serd_sink_set_prefix_func(sink, on_prefix); - serd_sink_set_statement_func(sink, on_statement); - serd_sink_set_end_func(sink, on_end); + SerdSink* sink = serd_sink_new(NULL, on_event, NULL); SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); assert(reader); diff --git a/test/test_reader_writer.c b/test/test_reader_writer.c index 11939e2c..706f5962 100644 --- a/test/test_reader_writer.c +++ b/test/test_reader_writer.c @@ -25,14 +25,11 @@ #include <string.h> static SerdStatus -test_sink(void* handle, - SerdStatementFlags flags, - const SerdStatement* statement) +count_statements(void* handle, const SerdEvent* event) { - (void)flags; - (void)statement; - - ++*(size_t*)handle; + if (event->type == SERD_STATEMENT) { + ++*(size_t*)handle; + } return SERD_SUCCESS; } @@ -88,13 +85,12 @@ test_read_chunks(void) size_t n_statements = 0; FILE* const f = tmpfile(); static const char null = 0; - SerdSink* sink = serd_sink_new(&n_statements, NULL); - SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); - assert(reader); + SerdSink* const sink = serd_sink_new(&n_statements, count_statements, NULL); assert(sink); - assert(f); - serd_sink_set_statement_func(sink, test_sink); + + SerdReader* const reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); + assert(reader); SerdStatus st = serd_reader_start_stream( reader, (SerdReadFunc)fread, (SerdStreamErrorFunc)ferror, f, NULL, 1); @@ -147,17 +143,14 @@ test_read_chunks(void) static void test_read_string(void) { - SerdWorld* world = serd_world_new(); - size_t n_statements = 0; - SerdSink* sink = serd_sink_new(&n_statements, NULL); - SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); + SerdWorld* world = serd_world_new(); + size_t n_statements = 0; - assert(reader); + SerdSink* sink = serd_sink_new(&n_statements, count_statements, NULL); assert(sink); - serd_sink_set_statement_func(sink, test_sink); - - serd_sink_set_statement_func(sink, test_sink); + SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); + assert(reader); // Test reading a string that ends exactly at the end of input (no newline) assert( @@ -274,9 +267,8 @@ test_reader(const char* path) { SerdWorld* world = serd_world_new(); size_t n_statements = 0; - SerdSink* const sink = serd_sink_new(&n_statements, NULL); + SerdSink* const sink = serd_sink_new(&n_statements, count_statements, NULL); assert(sink); - serd_sink_set_statement_func(sink, test_sink); // Test that too little stack space fails gracefully assert(!serd_reader_new(world, SERD_TURTLE, sink, 32)); diff --git a/test/test_sink.c b/test/test_sink.c index 8b69d1df..29bb793f 100644 --- a/test/test_sink.c +++ b/test/test_sink.c @@ -76,6 +76,24 @@ on_end(void* handle, const SerdNode* node) return state->return_status; } +static SerdStatus +on_event(void* const handle, const SerdEvent* const event) +{ + switch (event->type) { + case SERD_BASE: + return on_base(handle, event->base.uri); + case SERD_PREFIX: + return on_prefix(handle, event->prefix.name, event->prefix.uri); + case SERD_STATEMENT: + return on_statement( + handle, event->statement.flags, event->statement.statement); + case SERD_END: + return on_end(handle, event->end.node); + } + + return SERD_ERR_BAD_ARG; +} + static void test_callbacks(void) { @@ -93,23 +111,37 @@ test_callbacks(void) State state = {0, 0, 0, 0, 0, SERD_SUCCESS}; + const SerdBaseEvent base_event = {SERD_BASE, uri}; + const SerdPrefixEvent prefix_event = {SERD_PREFIX, name, uri}; + const SerdStatementEvent statement_event = {SERD_STATEMENT, 0u, statement}; + const SerdEndEvent end_event = {SERD_END, blank}; + // Call functions on a sink with no functions set - SerdSink* null_sink = serd_sink_new(&state, NULL); + SerdSink* null_sink = serd_sink_new(&state, NULL, NULL); + assert(!serd_sink_write_base(null_sink, base)); assert(!serd_sink_write_prefix(null_sink, name, uri)); assert(!serd_sink_write_statement(null_sink, 0, statement)); assert(!serd_sink_write(null_sink, 0, base, uri, blank, NULL)); assert(!serd_sink_write_end(null_sink, blank)); + + SerdEvent event = {SERD_BASE}; + + event.base = base_event; + assert(!serd_sink_write_event(null_sink, &event)); + event.prefix = prefix_event; + assert(!serd_sink_write_event(null_sink, &event)); + event.statement = statement_event; + assert(!serd_sink_write_event(null_sink, &event)); + event.end = end_event; + assert(!serd_sink_write_event(null_sink, &event)); + serd_sink_free(null_sink); // Try again with a sink that has the event handler set - SerdSink* sink = serd_sink_new(&state, NULL); - serd_sink_set_base_func(sink, on_base); - serd_sink_set_prefix_func(sink, on_prefix); - serd_sink_set_statement_func(sink, on_statement); - serd_sink_set_end_func(sink, on_end); + SerdSink* sink = serd_sink_new(&state, on_event, NULL); assert(!serd_sink_write_base(sink, base)); assert(serd_node_equals(state.last_base, base)); @@ -124,6 +156,9 @@ test_callbacks(void) assert(!serd_sink_write_end(sink, blank)); assert(serd_node_equals(state.last_end, blank)); + const SerdEvent junk = {(SerdEventType)42}; + assert(serd_sink_write_event(sink, &junk) == SERD_ERR_BAD_ARG); + serd_sink_free(sink); serd_statement_free(statement); @@ -139,7 +174,7 @@ test_free(void) // Set up a sink with dynamically allocated data and a free function uintptr_t* data = (uintptr_t*)calloc(1, sizeof(uintptr_t)); - SerdSink* sink = serd_sink_new(data, free); + SerdSink* sink = serd_sink_new(data, NULL, free); // Free the sink, which should free the data (rely on valgrind or sanitizers) serd_sink_free(sink); diff --git a/test/test_writer.c b/test/test_writer.c index 6f336ca4..d7c4eac8 100644 --- a/test/test_writer.c +++ b/test/test_writer.c @@ -23,6 +23,31 @@ #include <string.h> static void +test_write_bad_event(void) +{ + SerdWorld* world = serd_world_new(); + SerdEnv* env = serd_env_new(SERD_EMPTY_STRING()); + SerdBuffer buffer = {NULL, 0}; + SerdWriter* writer = + serd_writer_new(world, SERD_TURTLE, 0u, env, serd_buffer_sink, &buffer); + + assert(writer); + + const SerdEvent event = {(SerdEventType)42}; + assert(serd_sink_write_event(serd_writer_sink(writer), &event) == + SERD_ERR_BAD_ARG); + + char* const out = serd_buffer_sink_finish(&buffer); + + assert(!strcmp(out, "")); + serd_free(out); + + serd_writer_free(writer); + serd_env_free(env); + serd_world_free(world); +} + +static void test_write_bad_prefix(void) { SerdWorld* world = serd_world_new(); @@ -148,6 +173,7 @@ test_writer_stack_overflow(void) int main(void) { + test_write_bad_event(); test_write_bad_prefix(); test_write_long_literal(); test_writer_stack_overflow(); |