aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2019-03-10 22:50:31 +0100
committerDavid Robillard <d@drobilla.net>2019-04-13 19:48:23 +0200
commit5878f2aeb5e069947761e0c9629b7741b63d78f9 (patch)
tree4290e7bee9f1a1234831652f6feafd5fff19ff41
parent1ced0a4938fa11e981f652505cd9dfc79c3c629a (diff)
downloadserd-5878f2aeb5e069947761e0c9629b7741b63d78f9.tar.gz
serd-5878f2aeb5e069947761e0c9629b7741b63d78f9.tar.bz2
serd-5878f2aeb5e069947761e0c9629b7741b63d78f9.zip
WIP: Add support for writing terse collections
-rw-r--r--NEWS3
-rw-r--r--serd/serd.h4
-rw-r--r--src/writer.c35
-rw-r--r--tests/terse_write_test.c109
-rw-r--r--wscript2
5 files changed, 139 insertions, 14 deletions
diff --git a/NEWS b/NEWS
index 3da2dbb3..2d560d1c 100644
--- a/NEWS
+++ b/NEWS
@@ -16,8 +16,9 @@ serd (1.0.0) unstable;
* Add model for storing statements in memory
* Add support for validation
* Add option for writing terse output without newlines
+ * Add support for writing terse collections
- -- David Robillard <d@drobilla.net> Sun, 10 Mar 2019 13:21:37 +0100
+ -- David Robillard <d@drobilla.net> Sun, 10 Mar 2019 18:09:18 +0100
serd (0.30.1) unstable;
diff --git a/serd/serd.h b/serd/serd.h
index 770c201d..3e2f8f45 100644
--- a/serd/serd.h
+++ b/serd/serd.h
@@ -128,7 +128,9 @@ typedef enum {
SERD_ANON_S = 1 << 1, ///< Start of anonymous subject
SERD_ANON_O = 1 << 2, ///< Start of anonymous object
SERD_LIST_S = 1 << 3, ///< Start of list subject
- SERD_LIST_O = 1 << 4 ///< Start of list object
+ SERD_LIST_O = 1 << 4, ///< Start of list object
+ SERD_TERSE_S = 1 << 5, ///< Terse serialisation of new subject
+ SERD_TERSE_O = 1 << 6 ///< Terse serialisation of new object
} SerdStatementFlag;
/// Bitwise OR of SerdStatementFlag values
diff --git a/src/writer.c b/src/writer.c
index 241ff69f..685db34a 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -61,8 +61,11 @@ typedef enum {
SEP_ANON_S_P, ///< Between start of anonymous node and predicate
SEP_ANON_END, ///< End of anonymous node (']')
SEP_LIST_BEGIN, ///< Start of list ('(')
- SEP_LIST_SEP, ///< List separator (whitespace)
+ SEP_LIST_SEP, ///< List separator (newline)
SEP_LIST_END, ///< End of list (')')
+ SEP_TLIST_BEGIN, ///< Start of terse list ('(')
+ SEP_TLIST_SEP, ///< Terse list separator (space)
+ SEP_TLIST_END, ///< End of terse list (')')
SEP_GRAPH_BEGIN, ///< Start of graph ('{')
SEP_GRAPH_END, ///< End of graph ('}')
} Sep;
@@ -89,12 +92,15 @@ static const SepRule rules[] = {
{",", 1, +0, SEP_ALL, SEP_NONE, ~(M(SEP_ANON_END) | M(SEP_LIST_END))},
{"", 0, +1, SEP_NONE, SEP_NONE, SEP_ALL},
{" ", 1, +0, SEP_NONE, SEP_NONE, SEP_NONE},
- {"[", 1, +1, M(SEP_END_O), SEP_NONE, SEP_NONE},
+ {"[", 1, +1, M(SEP_END_O), M(SEP_TLIST_BEGIN)|M(SEP_TLIST_SEP), SEP_NONE},
{"", 0, +0, SEP_NONE, SEP_ALL, SEP_NONE},
{"]", 1, -1, SEP_NONE, ~M(SEP_ANON_BEGIN), SEP_NONE},
{"(", 1, +1, M(SEP_END_O), SEP_NONE, SEP_ALL},
{"", 0, +0, SEP_NONE, SEP_ALL, SEP_NONE},
{")", 1, -1, SEP_NONE, SEP_ALL, SEP_NONE},
+ {"(", 1, +1, SEP_NONE, SEP_NONE, SEP_NONE},
+ {"", 0, +0, SEP_ALL, SEP_NONE, SEP_NONE},
+ {")", 1, -1, SEP_NONE, SEP_NONE, SEP_NONE},
{"{", 1, +1, SEP_ALL, SEP_NONE, SEP_NONE},
{"}", 1, -1, SEP_NONE, SEP_NONE, SEP_ALL},
{"<", 1, +0, SEP_NONE, SEP_NONE, SEP_NONE},
@@ -381,9 +387,9 @@ uri_sink(const void* buf, size_t size, size_t nmemb, void* stream)
}
static void
-write_newline(SerdWriter* writer)
+write_newline(SerdWriter* writer, bool terse)
{
- if (writer->flags & SERD_WRITE_TERSE) {
+ if (terse || (writer->flags & SERD_WRITE_TERSE)) {
sink(" ", 1, writer);
} else {
sink("\n", 1, writer);
@@ -397,16 +403,19 @@ static void
write_top_level_sep(SerdWriter* writer)
{
if (!writer->empty && !(writer->flags & SERD_WRITE_TERSE)) {
- write_newline(writer);
+ write_newline(writer, false);
}
}
static bool
write_sep(SerdWriter* writer, const SerdStatementFlags flags, Sep sep)
{
- (void)flags;
-
- const SepRule* rule = &rules[sep];
+ const SepRule* rule = &rules[sep];
+ const bool terse = (((flags & SERD_TERSE_S) && (flags & SERD_LIST_S)) ||
+ ((flags & SERD_TERSE_O) && (flags & SERD_LIST_O)));
+ if (terse && sep >= SEP_LIST_BEGIN && sep <= SEP_LIST_END) {
+ sep = (Sep)((int)sep + 3); // Switch to corresponding terse separator
+ }
// Adjust indent, but tolerate if it would become negative
writer->indent =
@@ -416,7 +425,7 @@ write_sep(SerdWriter* writer, const SerdStatementFlags flags, Sep sep)
// Write newline or space before separator if necessary
if (rule->pre_line_after & (1 << writer->last_sep)) {
- write_newline(writer);
+ write_newline(writer, terse);
} else if (rule->pre_space_after & (1 << writer->last_sep)) {
sink(" ", 1, writer);
}
@@ -426,7 +435,7 @@ write_sep(SerdWriter* writer, const SerdStatementFlags flags, Sep sep)
// Write newline after separator if necessary
if (rule->post_line_after & (1 << writer->last_sep)) {
- write_newline(writer);
+ write_newline(writer, terse);
}
writer->last_sep = sep;
@@ -754,8 +763,10 @@ serd_writer_write_statement(SerdWriter* writer,
const SerdNode* const graph = serd_statement_get_graph(statement);
if (!is_resource(subject) || !is_resource(predicate) || !object ||
- ((flags & SERD_ANON_S) && (flags & SERD_LIST_S)) ||
- ((flags & SERD_ANON_O) && (flags & SERD_LIST_O))) {
+ ((flags & SERD_ANON_S) && (flags & SERD_LIST_S)) || // Nonsense
+ ((flags & SERD_ANON_O) && (flags & SERD_LIST_O)) || // Nonsense
+ ((flags & SERD_ANON_S) && (flags & SERD_TERSE_S)) || // Unsupported
+ ((flags & SERD_ANON_O) && (flags & SERD_TERSE_O))) { // Unsupported
return SERD_ERR_BAD_ARG;
}
diff --git a/tests/terse_write_test.c b/tests/terse_write_test.c
new file mode 100644
index 00000000..28fc39ac
--- /dev/null
+++ b/tests/terse_write_test.c
@@ -0,0 +1,109 @@
+/*
+ Copyright 2019 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
+ 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.
+*/
+
+#undef NDEBUG
+
+#include "serd/serd.h"
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+
+static void
+check_output(SerdWriter* writer, SerdBuffer* buffer, const char* expected)
+{
+ serd_writer_finish(writer);
+ serd_buffer_sink_finish(buffer);
+
+ const char* output = (const char*)buffer->buf;
+ const int valid = !strcmp(output, expected);
+ if (valid) {
+ fprintf(stderr, "%s", output);
+ } else {
+ fprintf(stderr, "error: Invalid output:\n%s", output);
+ fprintf(stderr, "note: Expected output:\n%s", expected);
+ }
+ assert(valid);
+ buffer->len = 0;
+}
+
+static int
+test(void)
+{
+ SerdBuffer buffer = { NULL, 0 };
+ SerdWorld* world = serd_world_new();
+ SerdEnv* env = serd_env_new(NULL);
+ SerdNodes* nodes = serd_nodes_new();
+ const SerdNode* b1 = serd_new_blank("b1");
+ const SerdNode* l1 = serd_new_blank("l1");
+ const SerdNode* l2 = serd_new_blank("l2");
+ const SerdNode* s1 = serd_new_string("s1");
+ const SerdNode* s2 = serd_new_string("s2");
+ const SerdNode* rdf_first = serd_new_uri(NS_RDF "first");
+ const SerdNode* rdf_rest = serd_new_uri(NS_RDF "rest");
+ const SerdNode* rdf_nil = serd_new_uri(NS_RDF "nil");
+ const SerdNode* rdf_value = serd_new_uri(NS_RDF "value");
+
+ serd_env_set_prefix_from_strings(env, "rdf", NS_RDF);
+
+ SerdWriter* writer = serd_writer_new(world,
+ SERD_TURTLE,
+ 0,
+ env,
+ (SerdWriteFunc)serd_buffer_sink,
+ &buffer);
+
+ const SerdSink* sink = serd_writer_get_sink(writer);
+
+ // Simple lone list
+ serd_sink_write(sink, SERD_TERSE_S | SERD_LIST_S, l1, rdf_first, s1, NULL);
+ serd_sink_write(sink, 0, l1, rdf_rest, l2, NULL);
+ serd_sink_write(sink, 0, l2, rdf_first, s2, NULL);
+ serd_sink_write(sink, 0, l2, rdf_rest, rdf_nil, NULL);
+ check_output(writer, &buffer, "( \"s1\" \"s2\" ) .\n");
+
+ // Nested terse lists
+ serd_sink_write(sink, SERD_TERSE_S|SERD_LIST_S|SERD_TERSE_O|SERD_LIST_O,
+ l1, rdf_first, l2, NULL);
+ serd_sink_write(sink, 0, l2, rdf_first, s1, NULL);
+ serd_sink_write(sink, 0, l1, rdf_rest, rdf_nil, NULL);
+ serd_sink_write(sink, 0, l2, rdf_rest, rdf_nil, NULL);
+ check_output(writer, &buffer, "( ( \"s1\" ) ) .\n");
+
+ // List as object
+ serd_sink_write(sink, SERD_EMPTY_S|SERD_LIST_O|SERD_TERSE_O,
+ b1, rdf_value, l1, NULL);
+ serd_sink_write(sink, 0, l1, rdf_first, s1, NULL);
+ serd_sink_write(sink, 0, l1, rdf_rest, l2, NULL);
+ serd_sink_write(sink, 0, l2, rdf_first, s2, NULL);
+ serd_sink_write(sink, 0, l2, rdf_rest, rdf_nil, NULL);
+ check_output(writer, &buffer, "[]\n\trdf:value ( \"s1\" \"s2\" ) .\n");
+
+ serd_buffer_sink_finish(&buffer);
+ serd_nodes_free(nodes);
+ serd_writer_free(writer);
+
+ return 0;
+}
+
+int
+main(void)
+{
+ return test();
+}
diff --git a/wscript b/wscript
index c4aa1b16..5a4c1f64 100644
--- a/wscript
+++ b/wscript
@@ -179,6 +179,7 @@ def build(bld):
('sink_test', 'tests/sink_test.c'),
('serd_test', 'tests/serd_test.c'),
('read_chunk_test', 'tests/read_chunk_test.c'),
+ ('terse_write_test', 'tests/terse_write_test.c'),
('nodes_test', 'tests/nodes_test.c'),
('overflow_test', 'tests/overflow_test.c'),
('model_test', 'tests/model_test.c')]:
@@ -521,6 +522,7 @@ def test(tst):
check(['./nodes_test'])
check(['./overflow_test'])
check(['./serd_test'])
+ check(['./terse_write_test'])
check(['./read_chunk_test'])
def test_syntax_io(check, in_name, check_name, lang):