aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/serd/node_syntax.h84
-rw-r--r--include/serd/serd.h1
-rw-r--r--meson.build2
-rw-r--r--src/node_syntax.c189
-rw-r--r--src/writer.c8
-rw-r--r--src/writer.h9
-rw-r--r--test/meson.build1
-rw-r--r--test/test_node.c4
-rw-r--r--test/test_node_syntax.c222
9 files changed, 518 insertions, 2 deletions
diff --git a/include/serd/node_syntax.h b/include/serd/node_syntax.h
new file mode 100644
index 00000000..7529c954
--- /dev/null
+++ b/include/serd/node_syntax.h
@@ -0,0 +1,84 @@
+// Copyright 2011-2022 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef SERD_NODE_SYNTAX_H
+#define SERD_NODE_SYNTAX_H
+
+#include "serd/attributes.h"
+#include "serd/env.h"
+#include "serd/memory.h"
+#include "serd/node.h"
+#include "serd/syntax.h"
+#include "zix/attributes.h"
+
+SERD_BEGIN_DECLS
+
+/**
+ @defgroup serd_node_syntax Node Syntax
+ @ingroup serd_reading_writing
+ @{
+*/
+
+/**
+ Create a node from a string representation in `syntax`.
+
+ The string should be a node as if written as an object in the given syntax,
+ without any extra quoting or punctuation, which is the format returned by
+ serd_node_to_syntax(). These two functions, when used with #SERD_TURTLE,
+ can be used to round-trip any node to a string and back.
+
+ @param allocator Allocator used for the returned node, and any temporary
+ objects if `env` is null.
+
+ @param str String representation of a node.
+
+ @param syntax Syntax to use. Should be either SERD_TURTLE or SERD_NTRIPLES
+ (the others are redundant). Note that namespaced (CURIE) nodes and relative
+ URIs can not be expressed in NTriples.
+
+ @param env Environment of `str`. This must define any abbreviations needed
+ to parse the string.
+
+ @return A newly allocated node that must be freed with serd_node_free()
+ using the world allocator.
+*/
+SERD_API SerdNode* ZIX_ALLOCATED
+serd_node_from_syntax(SerdAllocator* ZIX_NULLABLE allocator,
+ const char* ZIX_NONNULL str,
+ SerdSyntax syntax,
+ SerdEnv* ZIX_NULLABLE env);
+
+/**
+ Return a string representation of `node` in `syntax`.
+
+ The returned string represents that node as if written as an object in the
+ given syntax, without any extra quoting or punctuation.
+
+ @param allocator Allocator used for the returned node, and any temporary
+ objects if `env` is null.
+
+ @param node Node to write as a string.
+
+ @param syntax Syntax to use. Should be either SERD_TURTLE or SERD_NTRIPLES
+ (the others are redundant). Note that namespaced (CURIE) nodes and relative
+ URIs can not be expressed in NTriples.
+
+ @param env Environment for the output string. This can be used to
+ abbreviate things nicely by setting namespace prefixes.
+
+ @return A newly allocated string that must be freed with serd_free() using
+ the world allocator.
+*/
+SERD_API char* ZIX_ALLOCATED
+serd_node_to_syntax(SerdAllocator* ZIX_NULLABLE allocator,
+ const SerdNode* ZIX_NONNULL node,
+ SerdSyntax syntax,
+ const SerdEnv* ZIX_NULLABLE env);
+
+/**
+ @}
+*/
+
+SERD_END_DECLS
+
+#endif // SERD_NODE_SYNTAX_H
diff --git a/include/serd/serd.h b/include/serd/serd.h
index 4f1d97b5..f874a0cc 100644
--- a/include/serd/serd.h
+++ b/include/serd/serd.h
@@ -81,6 +81,7 @@
*/
#include "serd/input_stream.h"
+#include "serd/node_syntax.h"
#include "serd/output_stream.h"
#include "serd/reader.h"
#include "serd/stream.h"
diff --git a/meson.build b/meson.build
index 3e8500ec..e877fc9c 100644
--- a/meson.build
+++ b/meson.build
@@ -137,6 +137,7 @@ c_headers = files(
'include/serd/log.h',
'include/serd/memory.h',
'include/serd/node.h',
+ 'include/serd/node_syntax.h',
'include/serd/nodes.h',
'include/serd/output_stream.h',
'include/serd/reader.h',
@@ -167,6 +168,7 @@ sources = files(
'src/log.c',
'src/memory.c',
'src/node.c',
+ 'src/node_syntax.c',
'src/nodes.c',
'src/output_stream.c',
'src/read_nquads.c',
diff --git a/src/node_syntax.c b/src/node_syntax.c
new file mode 100644
index 00000000..b40bc0dc
--- /dev/null
+++ b/src/node_syntax.c
@@ -0,0 +1,189 @@
+// Copyright 2011-2020 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "memory.h"
+#include "writer.h"
+
+#include "serd/buffer.h"
+#include "serd/env.h"
+#include "serd/event.h"
+#include "serd/input_stream.h"
+#include "serd/memory.h"
+#include "serd/node.h"
+#include "serd/node_syntax.h"
+#include "serd/output_stream.h"
+#include "serd/reader.h"
+#include "serd/sink.h"
+#include "serd/statement.h"
+#include "serd/status.h"
+#include "serd/string_view.h"
+#include "serd/syntax.h"
+#include "serd/world.h"
+#include "serd/writer.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+typedef struct {
+ SerdAllocator* allocator;
+ SerdNode* object;
+} NodeSyntaxContext;
+
+static SerdStatus
+on_syntax_event(void* const handle, const SerdEvent* const event)
+{
+ NodeSyntaxContext* const ctx = (NodeSyntaxContext*)handle;
+
+ if (event->type == SERD_STATEMENT) {
+ ctx->object = serd_node_copy(
+ ctx->allocator, serd_statement_object(event->statement.statement));
+ }
+
+ return SERD_SUCCESS;
+}
+
+static SerdNode*
+serd_node_from_syntax_in(SerdWorld* const world,
+ const char* const str,
+ const SerdSyntax syntax,
+ SerdEnv* const env)
+{
+ assert(str);
+
+ static const char* const prelude =
+ "_:s <http://www.w3.org/2000/01/rdf-schema#object>";
+
+ SerdAllocator* const alloc = serd_world_allocator(world);
+
+ const size_t str_len = strlen(str);
+ const size_t doc_len = strlen(prelude) + str_len + 5;
+ NodeSyntaxContext ctx = {serd_world_allocator(world), NULL};
+ char* const doc = (char*)serd_wcalloc(world, doc_len + 2, 1);
+ SerdSink* const sink = serd_sink_new(alloc, &ctx, on_syntax_event, NULL);
+
+ if (doc && sink) {
+ snprintf(doc, doc_len + 1, "%s %s .", prelude, str);
+
+ const SerdLimits old_limits = serd_world_limits(world);
+ const SerdLimits limits = {1024 + doc_len, 8U};
+ serd_world_set_limits(world, limits);
+
+ SerdReader* const reader = serd_reader_new(
+ world,
+ syntax,
+ SERD_READ_RELATIVE | SERD_READ_GLOBAL | SERD_READ_GENERATED,
+ env,
+ sink);
+
+ if (reader) {
+ const char* position = doc;
+ SerdInputStream in = serd_open_input_string(&position);
+ serd_reader_start(reader, &in, NULL, 1);
+ serd_reader_read_document(reader);
+ serd_reader_finish(reader);
+ serd_close_input(&in);
+ }
+
+ serd_reader_free(reader);
+ serd_world_set_limits(world, old_limits);
+ }
+
+ serd_sink_free(sink);
+ serd_wfree(world, doc);
+
+ return ctx.object;
+}
+
+SerdNode*
+serd_node_from_syntax(SerdAllocator* const allocator,
+ const char* const str,
+ const SerdSyntax syntax,
+ SerdEnv* const env)
+{
+ assert(str);
+
+ SerdWorld* const temp_world = serd_world_new(allocator);
+ if (!temp_world) {
+ return NULL;
+ }
+
+ SerdNode* node = NULL;
+ if (env) {
+ node = serd_node_from_syntax_in(temp_world, str, syntax, env);
+ } else {
+ SerdEnv* const temp_env = serd_env_new(allocator, serd_empty_string());
+ if (temp_env) {
+ node = serd_node_from_syntax_in(temp_world, str, syntax, temp_env);
+ }
+ serd_env_free(temp_env);
+ }
+
+ serd_world_free(temp_world);
+ return node;
+}
+
+static char*
+serd_node_to_syntax_in(SerdWorld* const world,
+ const SerdNode* const node,
+ const SerdSyntax syntax,
+ const SerdEnv* const env)
+{
+ SerdBuffer buffer = {serd_world_allocator(world), NULL, 0};
+ SerdOutputStream out = serd_open_output_buffer(&buffer);
+
+ const SerdLimits old_limits = serd_world_limits(world);
+ const SerdLimits limits = {0U, 4U};
+ serd_world_set_limits(world, limits);
+
+ SerdWriter* const writer = serd_writer_new(world, syntax, 0, env, &out, 1);
+ serd_world_set_limits(world, old_limits);
+ if (!writer) {
+ return NULL;
+ }
+
+ char* result = NULL;
+ if (!serd_writer_write_node(writer, node) && !serd_writer_finish(writer)) {
+ if (!serd_close_output(&out)) {
+ result = (char*)buffer.buf;
+ }
+ } else {
+ serd_close_output(&out);
+ }
+
+ serd_writer_free(writer);
+
+ if (!result) {
+ serd_wfree(world, buffer.buf);
+ }
+
+ return result;
+}
+
+char*
+serd_node_to_syntax(SerdAllocator* const allocator,
+ const SerdNode* const node,
+ const SerdSyntax syntax,
+ const SerdEnv* const env)
+{
+ assert(node);
+
+ SerdWorld* const temp_world = serd_world_new(allocator);
+ if (!temp_world) {
+ return NULL;
+ }
+
+ char* string = NULL;
+ if (env) {
+ string = serd_node_to_syntax_in(temp_world, node, syntax, env);
+ } else {
+ SerdEnv* const temp_env = serd_env_new(allocator, serd_empty_string());
+ if (temp_env) {
+ string = serd_node_to_syntax_in(temp_world, node, syntax, temp_env);
+ }
+ serd_env_free(temp_env);
+ }
+
+ serd_world_free(temp_world);
+ return string;
+}
diff --git a/src/writer.c b/src/writer.c
index 88c544d4..0b0574d5 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -1,6 +1,8 @@
// Copyright 2011-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
+#include "writer.h"
+
#include "block_dumper.h"
#include "env.h"
#include "memory.h"
@@ -1304,6 +1306,12 @@ serd_writer_on_event(SerdWriter* writer, const SerdEvent* event)
}
SerdStatus
+serd_writer_write_node(SerdWriter* writer, const SerdNode* node)
+{
+ return write_node(writer, node, SERD_OBJECT, 0);
+}
+
+SerdStatus
serd_writer_finish(SerdWriter* writer)
{
assert(writer);
diff --git a/src/writer.h b/src/writer.h
new file mode 100644
index 00000000..83c84302
--- /dev/null
+++ b/src/writer.h
@@ -0,0 +1,9 @@
+// Copyright 2019-2020 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "serd/node.h"
+#include "serd/status.h"
+#include "serd/writer.h"
+
+SerdStatus
+serd_writer_write_node(SerdWriter* writer, const SerdNode* node);
diff --git a/test/meson.build b/test/meson.build
index 2528ef66..0e887ccb 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -130,6 +130,7 @@ unit_tests = [
'free_null',
'log',
'node',
+ 'node_syntax',
'nodes',
'overflow',
'reader',
diff --git a/test/test_node.c b/test/test_node.c
index 6b159007..c1b83140 100644
--- a/test/test_node.c
+++ b/test/test_node.c
@@ -632,7 +632,7 @@ test_node_equals(void)
}
static void
-test_node_from_string(void)
+test_node_from_syntax(void)
{
SerdNode* const hello = serd_node_new(NULL, serd_a_string("hello\""));
assert(serd_node_length(hello) == 6);
@@ -826,7 +826,7 @@ main(void)
test_base64();
test_decode();
test_node_equals();
- test_node_from_string();
+ test_node_from_syntax();
test_node_from_substring();
test_literal();
test_blank();
diff --git a/test/test_node_syntax.c b/test/test_node_syntax.c
new file mode 100644
index 00000000..d1d913ec
--- /dev/null
+++ b/test/test_node_syntax.c
@@ -0,0 +1,222 @@
+// Copyright 2020 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#undef NDEBUG
+
+#include "failing_allocator.h"
+
+#include "serd/env.h"
+#include "serd/memory.h"
+#include "serd/node.h"
+#include "serd/node_syntax.h"
+#include "serd/nodes.h"
+#include "serd/string_view.h"
+#include "serd/syntax.h"
+#include "serd/value.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+static void
+test_failed_alloc(void)
+{
+ SerdFailingAllocator allocator = serd_failing_allocator();
+
+ SerdNode* const node = serd_node_new(&allocator.base, serd_a_string("node"));
+
+ // Successfully convert a node to count the number of allocations
+
+ const size_t n_setup_allocs = allocator.n_allocations;
+
+ char* const str =
+ serd_node_to_syntax(&allocator.base, node, SERD_TURTLE, NULL);
+
+ SerdNode* const copy =
+ serd_node_from_syntax(&allocator.base, str, SERD_TURTLE, NULL);
+
+ // Test that each allocation failing is handled gracefully
+ const size_t n_new_allocs = allocator.n_allocations - n_setup_allocs;
+ for (size_t i = 0; i < n_new_allocs; ++i) {
+ allocator.n_remaining = i;
+
+ char* const s =
+ serd_node_to_syntax(&allocator.base, node, SERD_TURTLE, NULL);
+
+ SerdNode* const c =
+ serd_node_from_syntax(&allocator.base, str, SERD_TURTLE, NULL);
+
+ assert(!s || !c);
+
+ serd_node_free(&allocator.base, c);
+ serd_free(&allocator.base, s);
+ }
+
+ serd_node_free(&allocator.base, copy);
+ serd_free(&allocator.base, str);
+ serd_node_free(&allocator.base, node);
+}
+
+static bool
+check(const SerdSyntax syntax,
+ const SerdNode* const node,
+ const char* const expected)
+{
+ SerdEnv* const env =
+ serd_env_new(NULL, serd_string("http://example.org/base/"));
+
+ char* const str = serd_node_to_syntax(NULL, node, syntax, env);
+ SerdNode* const copy = serd_node_from_syntax(NULL, str, syntax, env);
+
+ const bool success = !strcmp(str, expected) && serd_node_equals(copy, node);
+
+ serd_node_free(NULL, copy);
+ serd_free(NULL, str);
+ serd_env_free(env);
+ return success;
+}
+
+static void
+test_common(const SerdSyntax syntax)
+{
+ static const uint8_t data[] = {19U, 17U, 13U, 7U};
+
+ const SerdStringView datatype = serd_string("http://example.org/Datatype");
+
+ SerdNodes* const nodes = serd_nodes_new(NULL);
+
+ assert(
+ check(syntax, serd_nodes_get(nodes, serd_a_string("node")), "\"node\""));
+
+ assert(check(
+ syntax,
+ serd_nodes_get(
+ nodes, serd_a_plain_literal(serd_string("hallo"), serd_string("de"))),
+ "\"hallo\"@de"));
+
+ assert(check(
+ syntax,
+ serd_nodes_get(nodes, serd_a_typed_literal(serd_string("X"), datatype)),
+ "\"X\"^^<http://example.org/Datatype>"));
+
+ assert(check(syntax,
+ serd_nodes_get(nodes, serd_a_blank(serd_string("blank"))),
+ "_:blank"));
+
+ assert(check(
+ syntax, serd_nodes_get(nodes, serd_a_blank(serd_string("b0"))), "_:b0"));
+
+ assert(check(syntax,
+ serd_nodes_get(nodes, serd_a_blank(serd_string("named1"))),
+ "_:named1"));
+
+ assert(check(syntax,
+ serd_nodes_get(nodes, serd_a_uri_string("http://example.org/")),
+ "<http://example.org/>"));
+
+ assert(check(syntax,
+ serd_nodes_get(nodes, serd_a_primitive(serd_double(1.25))),
+ "\"1.25E0\"^^<http://www.w3.org/2001/XMLSchema#double>"));
+
+ assert(check(syntax,
+ serd_nodes_get(nodes, serd_a_primitive(serd_float(1.25f))),
+ "\"1.25E0\"^^<http://www.w3.org/2001/XMLSchema#float>"));
+
+ assert(check(syntax,
+ serd_nodes_get(nodes, serd_a_hex(sizeof(data), data)),
+ "\"13110D07\"^^<http://www.w3.org/2001/XMLSchema#hexBinary>"));
+
+ assert(
+ check(syntax,
+ serd_nodes_get(nodes, serd_a_base64(sizeof(data), data)),
+ "\"ExENBw==\"^^<http://www.w3.org/2001/XMLSchema#base64Binary>"));
+
+ serd_nodes_free(nodes);
+}
+
+static void
+test_ntriples(void)
+{
+ SerdNodes* const nodes = serd_nodes_new(NULL);
+
+ test_common(SERD_NTRIPLES);
+
+ {
+ // No relative URIs in NTriples, so converting one fails without an env
+ const SerdNode* const rel =
+ serd_nodes_get(nodes, serd_a_uri_string("rel/uri"));
+ assert(!serd_node_to_syntax(NULL, rel, SERD_NTRIPLES, NULL));
+ assert(!serd_node_from_syntax(NULL, "<rel/uri>", SERD_NTRIPLES, NULL));
+
+ // If a relative URI can be expanded then all's well
+ SerdEnv* const env =
+ serd_env_new(NULL, serd_string("http://example.org/base/"));
+ char* const str = serd_node_to_syntax(NULL, rel, SERD_NTRIPLES, env);
+ assert(!strcmp(str, "<http://example.org/base/rel/uri>"));
+
+ SerdNode* const copy = serd_node_from_syntax(NULL, str, SERD_NTRIPLES, env);
+
+ assert(!strcmp(serd_node_string(copy), "http://example.org/base/rel/uri"));
+
+ serd_node_free(NULL, copy);
+ serd_env_free(env);
+ serd_free(NULL, str);
+ }
+
+ assert(check(SERD_NTRIPLES,
+ serd_nodes_get(nodes, serd_a_decimal(1.25)),
+ "\"1.25\"^^<http://www.w3.org/2001/XMLSchema#decimal>"));
+
+ assert(check(SERD_NTRIPLES,
+ serd_nodes_get(nodes, serd_a_integer(1234)),
+ "\"1234\"^^<http://www.w3.org/2001/XMLSchema#integer>"));
+
+ assert(check(SERD_NTRIPLES,
+ serd_nodes_get(nodes, serd_a_primitive(serd_bool(true))),
+ "\"true\"^^<http://www.w3.org/2001/XMLSchema#boolean>"));
+
+ assert(check(SERD_NTRIPLES,
+ serd_nodes_get(nodes, serd_a_primitive(serd_bool(false))),
+ "\"false\"^^<http://www.w3.org/2001/XMLSchema#boolean>"));
+
+ serd_nodes_free(nodes);
+}
+
+static void
+test_turtle(void)
+{
+ SerdNodes* const nodes = serd_nodes_new(NULL);
+
+ test_common(SERD_TURTLE);
+
+ check(SERD_TURTLE,
+ serd_nodes_get(nodes, serd_a_uri_string("rel/uri")),
+ "<rel/uri>");
+
+ assert(
+ check(SERD_TURTLE, serd_nodes_get(nodes, serd_a_decimal(1.25)), "1.25"));
+
+ assert(
+ check(SERD_TURTLE, serd_nodes_get(nodes, serd_a_integer(1234)), "1234"));
+
+ assert(check(SERD_TURTLE,
+ serd_nodes_get(nodes, serd_a_primitive(serd_bool(true))),
+ "true"));
+
+ assert(check(SERD_TURTLE,
+ serd_nodes_get(nodes, serd_a_primitive(serd_bool(false))),
+ "false"));
+
+ serd_nodes_free(nodes);
+}
+
+int
+main(void)
+{
+ test_failed_alloc();
+ test_ntriples();
+ test_turtle();
+
+ return 0;
+}