aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--include/serd/serd.h13
-rw-r--r--src/n3.c44
-rw-r--r--src/reader.c2
-rw-r--r--src/stack.h6
-rw-r--r--src/writer.c384
-rw-r--r--test/good/base.ttl1
-rw-r--r--test/good/pretty.trig98
-rw-r--r--test/good/qualify-out.ttl1
-rw-r--r--test/test_writer.c12
10 files changed, 371 insertions, 191 deletions
diff --git a/NEWS b/NEWS
index 210e4f80..98526af7 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,7 @@ serd (1.0.1) unstable;
* Remove support for Turtle named inline nodes extension
* Remove useless character counting from API
* Rename SerdChunk to SerdStringView
+ * Simplify streaming API and improve pretty printing
* Simplify writer style options
* Use a fixed-size reader stack
* Use char* for strings in public API
diff --git a/include/serd/serd.h b/include/serd/serd.h
index da4291c6..06e11966 100644
--- a/include/serd/serd.h
+++ b/include/serd/serd.h
@@ -1246,14 +1246,11 @@ typedef enum {
/// Flags indicating inline abbreviation information for a statement
typedef enum {
- SERD_EMPTY_S = 1u << 1u, ///< Empty blank node subject
- SERD_EMPTY_O = 1u << 2u, ///< Empty blank node object
- SERD_ANON_S_BEGIN = 1u << 3u, ///< Start of anonymous subject
- SERD_ANON_O_BEGIN = 1u << 4u, ///< Start of anonymous object
- SERD_ANON_CONT = 1u << 5u, ///< Continuation of anonymous node
- SERD_LIST_S_BEGIN = 1u << 6u, ///< Start of list subject
- SERD_LIST_O_BEGIN = 1u << 7u, ///< Start of list object
- SERD_LIST_CONT = 1u << 8u ///< Continuation of list
+ SERD_EMPTY_S = 1u << 0u, ///< Empty blank node subject
+ SERD_ANON_S = 1u << 1u, ///< Start of anonymous subject
+ SERD_ANON_O = 1u << 2u, ///< Start of anonymous object
+ SERD_LIST_S = 1u << 3u, ///< Start of list subject
+ SERD_LIST_O = 1u << 4u ///< Start of list object
} SerdStatementFlag;
/// Bitwise OR of SerdStatementFlag values
diff --git a/src/n3.c b/src/n3.c
index 1883b6b2..ab0cfb37 100644
--- a/src/n3.c
+++ b/src/n3.c
@@ -1072,13 +1072,15 @@ read_anon(SerdReader* const reader,
const bool subject,
SerdNode** const dest)
{
- const SerdStatementFlags old_flags = *ctx.flags;
- bool empty = false;
eat_byte_safe(reader, '[');
- if ((empty = peek_delim(reader, ']'))) {
- *ctx.flags |= (subject) ? SERD_EMPTY_S : SERD_EMPTY_O;
+
+ const SerdStatementFlags old_flags = *ctx.flags;
+ const bool empty = peek_delim(reader, ']');
+
+ if (subject) {
+ *ctx.flags |= empty ? SERD_EMPTY_S : SERD_ANON_S;
} else {
- *ctx.flags |= (subject) ? SERD_ANON_S_BEGIN : SERD_ANON_O_BEGIN;
+ *ctx.flags |= SERD_ANON_O;
}
if (!*dest) {
@@ -1087,31 +1089,33 @@ read_anon(SerdReader* const reader,
}
}
+ // Emit statement with this anonymous object first
SerdStatus st = SERD_SUCCESS;
if (ctx.subject) {
TRY(st, emit_statement(reader, ctx, *dest));
}
+ // Switch the subject to the anonymous node and read its description
ctx.subject = *dest;
if (!empty) {
- *ctx.flags &= ~(unsigned)SERD_LIST_CONT;
- if (!subject) {
- *ctx.flags |= SERD_ANON_CONT;
- }
-
bool ate_dot_in_list = false;
- if ((st = read_predicateObjectList(reader, ctx, &ate_dot_in_list))) {
+ st = read_predicateObjectList(reader, ctx, &ate_dot_in_list);
+ if (st > SERD_FAILURE) {
return st;
}
if (ate_dot_in_list) {
return r_err(reader, SERD_ERR_BAD_SYNTAX, "`.' inside blank\n");
}
+
read_ws_star(reader);
- serd_sink_write_end(reader->sink, *dest);
*ctx.flags = old_flags;
}
+ if (!(subject && empty)) {
+ TRY(st, serd_sink_write_end(reader->sink, *dest));
+ }
+
return eat_byte_check(reader, ']');
}
@@ -1293,10 +1297,8 @@ read_predicateObjectList(SerdReader* const reader,
}
static SerdStatus
-end_collection(SerdReader* const reader, ReadContext ctx, const SerdStatus st)
+end_collection(SerdReader* const reader, const SerdStatus st)
{
- *ctx.flags &= ~(unsigned)SERD_LIST_CONT;
-
return st ? st : eat_byte_check(reader, ')');
}
@@ -1316,15 +1318,14 @@ read_collection(SerdReader* const reader,
if (ctx.subject) {
// subject predicate _:head
- *ctx.flags |= (end ? 0 : SERD_LIST_O_BEGIN);
+ *ctx.flags |= (end ? 0 : SERD_LIST_O);
TRY(st, emit_statement(reader, ctx, *dest));
- *ctx.flags |= SERD_LIST_CONT;
} else {
- *ctx.flags |= (end ? 0 : SERD_LIST_S_BEGIN);
+ *ctx.flags |= (end ? 0 : SERD_LIST_S);
}
if (end) {
- return end_collection(reader, ctx, st);
+ return end_collection(reader, st);
}
/* The order of node allocation here is necessarily not in stack order,
@@ -1344,7 +1345,7 @@ read_collection(SerdReader* const reader,
ctx.predicate = reader->rdf_first;
bool ate_dot = false;
if ((st = read_object(reader, &ctx, true, &ate_dot)) || ate_dot) {
- return end_collection(reader, ctx, st);
+ return end_collection(reader, st);
}
if (!(end = peek_delim(reader, ')'))) {
@@ -1359,7 +1360,6 @@ read_collection(SerdReader* const reader,
}
// _:node rdf:rest _:rest
- *ctx.flags |= SERD_LIST_CONT;
ctx.predicate = reader->rdf_rest;
TRY(st, emit_statement(reader, ctx, (end ? reader->rdf_nil : rest)));
@@ -1368,7 +1368,7 @@ read_collection(SerdReader* const reader,
node = ctx.subject; // invariant
}
- return end_collection(reader, ctx, st);
+ return end_collection(reader, st);
}
static SerdStatus
diff --git a/src/reader.c b/src/reader.c
index 0f97d237..e88234ac 100644
--- a/src/reader.c
+++ b/src/reader.c
@@ -134,7 +134,7 @@ emit_statement(SerdReader* const reader,
const SerdStatus st =
serd_sink_write_statement(reader->sink, *ctx.flags, &statement);
- *ctx.flags &= SERD_ANON_CONT | SERD_LIST_CONT; // Preserve only cont flags
+ *ctx.flags = 0;
return st;
}
diff --git a/src/stack.h b/src/stack.h
index 49f77843..074d43b0 100644
--- a/src/stack.h
+++ b/src/stack.h
@@ -46,6 +46,12 @@ serd_stack_new(size_t size)
return stack;
}
+static inline void
+serd_stack_clear(SerdStack* stack)
+{
+ stack->size = SERD_STACK_BOTTOM;
+}
+
static inline bool
serd_stack_is_empty(const SerdStack* stack)
{
diff --git a/src/writer.c b/src/writer.c
index 6a4dda34..27960f1e 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -32,56 +32,73 @@
#include <stdlib.h>
#include <string.h>
+typedef enum {
+ CTX_NAMED, ///< Normal non-anonymous context
+ CTX_BLANK, ///< Anonymous blank node
+ CTX_LIST ///< Anonymous list
+} ContextType;
+
typedef struct {
- SerdNode* graph;
- SerdNode* subject;
- SerdNode* predicate;
+ ContextType type;
+ SerdNode* graph;
+ SerdNode* subject;
+ SerdNode* predicate;
+ bool indented_object;
} WriteContext;
-static const WriteContext WRITE_CONTEXT_NULL = {NULL, NULL, NULL};
+static const WriteContext WRITE_CONTEXT_NULL = {CTX_NAMED,
+ NULL,
+ NULL,
+ NULL,
+ false};
typedef enum {
- SEP_NONE,
+ SEP_NONE, ///< Placeholder for nodes or nothing
SEP_END_S, ///< End of a subject ('.')
SEP_END_P, ///< End of a predicate (';')
SEP_END_O, ///< End of an object (',')
SEP_S_P, ///< Between a subject and predicate (whitespace)
SEP_P_O, ///< Between a predicate and object (whitespace)
SEP_ANON_BEGIN, ///< Start of anonymous node ('[')
+ 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_END, ///< End of list (')')
SEP_GRAPH_BEGIN, ///< Start of graph ('{')
SEP_GRAPH_END, ///< End of graph ('}')
- SEP_URI_BEGIN, ///< URI start quote ('<')
- SEP_URI_END ///< URI end quote ('>')
} Sep;
+typedef uint32_t SepMask; ///< Bitfield of separator flags
+
+#define SEP_ALL ((SepMask)-1)
+#define M(s) (1U << (s))
+
typedef struct {
- const char* str; ///< Sep string
- uint8_t len; ///< Length of sep string
- uint8_t space_before; ///< Newline before sep
- uint8_t space_after_node; ///< Newline after sep if after node
- uint8_t space_after_sep; ///< Newline after sep if after sep
+ const char* str; ///< Sep string
+ size_t len; ///< Length of sep string
+ int indent; ///< Indent delta
+ SepMask pre_space_after; ///< Leading space if after given seps
+ SepMask pre_line_after; ///< Leading newline if after given seps
+ SepMask post_line_after; ///< Trailing newline if after given seps
} SepRule;
-static const SepRule rules[] = {{NULL, 0, 0, 0, 0},
- {" .\n\n", 4, 0, 0, 0},
- {" ;", 2, 0, 1, 1},
- {" ,", 2, 0, 1, 0},
- {NULL, 0, 0, 1, 0},
- {" ", 1, 0, 0, 0},
- {"[", 1, 0, 1, 1},
- {"]", 1, 1, 0, 0},
- {"(", 1, 0, 0, 0},
- {NULL, 0, 0, 1, 0},
- {")", 1, 1, 0, 0},
- {" {", 2, 0, 1, 1},
- {" }", 2, 0, 1, 1},
- {"<", 1, 0, 0, 0},
- {">", 1, 0, 0, 0},
- {"\n", 1, 0, 1, 0}};
+static const SepRule rules[] = {
+ {"", 0, +0, SEP_NONE, SEP_NONE, SEP_NONE},
+ {".\n", 2, -1, SEP_ALL, SEP_NONE, SEP_NONE},
+ {";", 1, +0, SEP_ALL, SEP_NONE, SEP_ALL},
+ {",", 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},
+ {"", 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_ALL, SEP_NONE, SEP_NONE},
+ {"}", 1, -1, SEP_NONE, SEP_NONE, SEP_ALL},
+};
struct SerdWriterImpl {
SerdWorld* world;
@@ -95,12 +112,10 @@ struct SerdWriterImpl {
SerdWriteFunc write_func;
void* stream;
WriteContext context;
- SerdNode* list_subj;
- unsigned list_depth;
- unsigned indent;
char* bprefix;
size_t bprefix_len;
Sep last_sep;
+ int indent;
bool empty;
};
@@ -129,13 +144,41 @@ supports_uriref(const SerdWriter* writer)
return writer->syntax == SERD_TURTLE || writer->syntax == SERD_TRIG;
}
-SERD_PURE_FUNC
-static WriteContext*
-anon_stack_top(SerdWriter* const writer)
+static SerdStatus
+free_context(SerdWriter* writer)
+{
+ serd_node_free(writer->context.graph);
+ serd_node_free(writer->context.subject);
+ serd_node_free(writer->context.predicate);
+ return SERD_SUCCESS;
+}
+
+static SerdStatus
+push_context(SerdWriter* writer, const WriteContext new_context)
+{
+ WriteContext* top =
+ (WriteContext*)serd_stack_push(&writer->anon_stack, sizeof(WriteContext));
+
+ if (!top) {
+ return SERD_ERR_OVERFLOW;
+ }
+
+ *top = writer->context;
+ writer->context = new_context;
+ return SERD_SUCCESS;
+}
+
+static void
+pop_context(SerdWriter* writer)
{
assert(!serd_stack_is_empty(&writer->anon_stack));
- return (WriteContext*)(writer->anon_stack.buf + writer->anon_stack.size -
- sizeof(WriteContext));
+
+ free_context(writer);
+ writer->context =
+ *(WriteContext*)(writer->anon_stack.buf + writer->anon_stack.size -
+ sizeof(WriteContext));
+
+ serd_stack_pop(&writer->anon_stack, sizeof(WriteContext));
}
static SerdNode*
@@ -432,7 +475,7 @@ static void
write_newline(SerdWriter* writer)
{
sink("\n", 1, writer);
- for (unsigned i = 0; i < writer->indent; ++i) {
+ for (int i = 0; i < writer->indent; ++i) {
sink("\t", 1, writer);
}
}
@@ -441,47 +484,65 @@ static bool
write_sep(SerdWriter* writer, const Sep sep)
{
const SepRule* rule = &rules[sep];
- if (rule->space_before) {
- write_newline(writer);
- }
- if (rule->str) {
- sink(rule->str, rule->len, writer);
+ // Adjust indent, but tolerate if it would become negative
+ if ((rule->pre_line_after & (1u << writer->last_sep)) ||
+ (rule->post_line_after & (1u << writer->last_sep))) {
+ writer->indent = ((rule->indent >= 0 || writer->indent >= -rule->indent)
+ ? writer->indent + rule->indent
+ : 0);
}
- if ((writer->last_sep && rule->space_after_sep) ||
- (!writer->last_sep && rule->space_after_node)) {
+ // Write newline or space before separator if necessary
+ if (rule->pre_line_after & (1u << writer->last_sep)) {
write_newline(writer);
- } else if (writer->last_sep && rule->space_after_node) {
+ } else if (rule->pre_space_after & (1u << writer->last_sep)) {
sink(" ", 1, writer);
}
- writer->last_sep = sep;
+ // Write actual separator string
+ sink(rule->str, rule->len, writer);
+
+ // Write newline after separator if necessary
+ if (rule->post_line_after & (1u << writer->last_sep)) {
+ write_newline(writer);
+ writer->last_sep = SEP_NONE;
+ } else {
+ writer->last_sep = sep;
+ }
+
+ if (sep == SEP_END_S) {
+ writer->indent = 0;
+ }
+
return true;
}
static SerdStatus
reset_context(SerdWriter* writer, bool graph)
{
+ // Free any lingering contexts in case there was an error
+ while (!serd_stack_is_empty(&writer->anon_stack)) {
+ pop_context(writer);
+ }
+
if (graph && writer->context.graph) {
memset(writer->context.graph, 0, sizeof(SerdNode));
}
+
if (writer->context.subject) {
memset(writer->context.subject, 0, sizeof(SerdNode));
}
+
if (writer->context.predicate) {
memset(writer->context.predicate, 0, sizeof(SerdNode));
}
- writer->empty = false;
- return SERD_SUCCESS;
-}
-static SerdStatus
-free_context(const WriteContext* const ctx)
-{
- serd_node_free(ctx->graph);
- serd_node_free(ctx->subject);
- serd_node_free(ctx->predicate);
+ writer->context.indented_object = false;
+ writer->empty = false;
+
+ serd_stack_clear(&writer->anon_stack);
+
return SERD_SUCCESS;
}
@@ -491,8 +552,8 @@ is_inline_start(const SerdWriter* writer,
SerdStatementFlags flags)
{
return (supports_abbrev(writer) &&
- ((field == SERD_SUBJECT && (flags & SERD_ANON_S_BEGIN)) ||
- (field == SERD_OBJECT && (flags & SERD_ANON_O_BEGIN))));
+ ((field == SERD_SUBJECT && (flags & SERD_ANON_S)) ||
+ (field == SERD_OBJECT && (flags & SERD_ANON_O))));
}
static bool
@@ -500,6 +561,8 @@ write_literal(SerdWriter* const writer,
const SerdNode* const node,
const SerdStatementFlags flags)
{
+ writer->last_sep = SEP_NONE;
+
const SerdNode* datatype = serd_node_datatype(node);
const SerdNode* lang = serd_node_language(node);
const char* node_str = serd_node_string(node);
@@ -561,11 +624,10 @@ write_uri_node(SerdWriter* const writer,
const SerdNode* const node,
const SerdField field)
{
- const SerdNode* prefix = NULL;
- SerdStringView suffix = {NULL, 0};
-
- const char* node_str = serd_node_string(node);
- const bool has_scheme = serd_uri_string_has_scheme(node_str);
+ const SerdNode* prefix = NULL;
+ SerdStringView suffix = {NULL, 0};
+ const char* node_str = serd_node_string(node);
+ const bool has_scheme = serd_uri_string_has_scheme(node_str);
if (supports_abbrev(writer)) {
if (field == SERD_PREDICATE &&
serd_node_equals(node, writer->world->rdf_type)) {
@@ -596,7 +658,7 @@ write_uri_node(SerdWriter* const writer,
return false;
}
- write_sep(writer, SEP_URI_BEGIN);
+ sink("<", 1, writer);
if (!(writer->flags & SERD_WRITE_UNRESOLVED) &&
serd_env_base_uri(writer->env)) {
const SerdURIView base_uri = serd_env_base_uri_view(writer->env);
@@ -614,14 +676,15 @@ write_uri_node(SerdWriter* const writer,
} else {
write_uri_from_node(writer, node);
}
- write_sep(writer, SEP_URI_END);
+
+ sink(">", 1, writer);
return true;
}
static bool
write_curie(SerdWriter* const writer, const SerdNode* const node)
{
- const char* node_str = serd_node_string(node);
+ writer->last_sep = SEP_NONE;
SerdStringView prefix = {NULL, 0};
SerdStringView suffix = {NULL, 0};
@@ -630,18 +693,20 @@ write_curie(SerdWriter* const writer, const SerdNode* const node)
case SERD_NTRIPLES:
case SERD_NQUADS:
if ((st = serd_env_expand_in_place(writer->env, node, &prefix, &suffix))) {
- serd_world_errorf(
- writer->world, st, "undefined namespace prefix `%s'\n", node_str);
+ serd_world_errorf(writer->world,
+ st,
+ "undefined namespace prefix `%s'\n",
+ serd_node_string(node));
return false;
}
- write_sep(writer, SEP_URI_BEGIN);
+ sink("<", 1, writer);
write_uri(writer, prefix.buf, prefix.len);
write_uri(writer, suffix.buf, suffix.len);
- write_sep(writer, SEP_URI_END);
+ sink(">", 1, writer);
break;
case SERD_TURTLE:
case SERD_TRIG:
- write_lname(writer, node_str, node->length);
+ write_lname(writer, serd_node_string(node), node->length);
break;
}
@@ -657,26 +722,16 @@ write_blank(SerdWriter* const writer,
const char* node_str = serd_node_string(node);
if (supports_abbrev(writer)) {
if (is_inline_start(writer, field, flags)) {
- ++writer->indent;
return write_sep(writer, SEP_ANON_BEGIN);
}
- if (field == SERD_SUBJECT && (flags & SERD_LIST_S_BEGIN)) {
- assert(writer->list_depth == 0);
- serd_node_set(&writer->list_subj, node);
- ++writer->list_depth;
- ++writer->indent;
- return write_sep(writer, SEP_LIST_BEGIN);
- }
-
- if (field == SERD_OBJECT && (flags & SERD_LIST_O_BEGIN)) {
- ++writer->indent;
- ++writer->list_depth;
+ if ((field == SERD_SUBJECT && (flags & SERD_LIST_S)) ||
+ (field == SERD_OBJECT && (flags & SERD_LIST_O))) {
return write_sep(writer, SEP_LIST_BEGIN);
}
- if ((field == SERD_SUBJECT && (flags & SERD_EMPTY_S)) ||
- (field == SERD_OBJECT && (flags & SERD_EMPTY_O))) {
+ if (field == SERD_SUBJECT && (flags & SERD_EMPTY_S)) {
+ writer->last_sep = SEP_NONE; // Treat "[]" like a node
return sink("[]", 2, writer) == 2;
}
}
@@ -691,6 +746,7 @@ write_blank(SerdWriter* const writer,
sink(node_str, node->length, writer);
}
+ writer->last_sep = SEP_NONE;
return true;
}
@@ -700,23 +756,18 @@ write_node(SerdWriter* const writer,
const SerdField field,
const SerdStatementFlags flags)
{
- bool ret = false;
switch (node->type) {
case SERD_LITERAL:
- ret = write_literal(writer, node, flags);
- break;
+ return write_literal(writer, node, flags);
case SERD_URI:
- ret = write_uri_node(writer, node, field);
- break;
+ return write_uri_node(writer, node, field);
case SERD_CURIE:
- ret = write_curie(writer, node);
- break;
+ return write_curie(writer, node);
case SERD_BLANK:
- ret = write_blank(writer, node, field, flags);
- break;
+ return write_blank(writer, node, field, flags);
}
- writer->last_sep = SEP_NONE;
- return ret;
+
+ return false;
}
static bool
@@ -740,14 +791,14 @@ write_list_obj(SerdWriter* const writer,
const SerdNode* const object)
{
if (serd_node_equals(object, writer->world->rdf_nil)) {
- --writer->indent;
write_sep(writer, SEP_LIST_END);
return true;
}
if (serd_node_equals(predicate, writer->world->rdf_first)) {
- write_sep(writer, SEP_LIST_SEP);
write_node(writer, object, SERD_OBJECT, flags);
+ } else {
+ write_sep(writer, SEP_LIST_SEP);
}
return false;
@@ -758,6 +809,10 @@ serd_writer_write_statement(SerdWriter* const writer,
const SerdStatementFlags flags,
const SerdStatement* const statement)
{
+ assert(!((flags & SERD_ANON_S) && (flags & SERD_LIST_S)));
+ assert(!((flags & SERD_ANON_O) && (flags & SERD_LIST_O)));
+
+ SerdStatus st = SERD_SUCCESS;
const SerdNode* const subject = serd_statement_subject(statement);
const SerdNode* const predicate = serd_statement_predicate(statement);
const SerdNode* const object = serd_statement_object(statement);
@@ -790,47 +845,51 @@ serd_writer_write_statement(SerdWriter* const writer,
if ((graph && !serd_node_equals(graph, writer->context.graph)) ||
(!graph && ctx(writer, SERD_GRAPH))) {
- writer->indent = 0;
if (ctx(writer, SERD_SUBJECT)) {
write_sep(writer, SEP_END_S);
}
+
if (ctx(writer, SERD_GRAPH)) {
write_sep(writer, SEP_GRAPH_END);
}
+ if (!writer->empty) {
+ write_newline(writer); // Blank line between top level items
+ }
+
reset_context(writer, true);
+
if (graph) {
TRY(write_node(writer, graph, SERD_GRAPH, flags));
- ++writer->indent;
write_sep(writer, SEP_GRAPH_BEGIN);
serd_node_set(&writer->context.graph, graph);
}
}
- if ((flags & SERD_LIST_CONT)) {
+ if (writer->context.type == CTX_LIST) {
if (write_list_obj(writer, flags, predicate, object)) {
// Reached end of list
- if (--writer->list_depth == 0 && writer->list_subj) {
- reset_context(writer, false);
- serd_node_free(writer->context.subject);
- writer->context.subject = writer->list_subj;
- writer->list_subj = NULL;
- }
+ pop_context(writer);
return SERD_SUCCESS;
}
} else if (serd_node_equals(subject, writer->context.subject)) {
if (serd_node_equals(predicate, writer->context.predicate)) {
// Abbreviate S P
- if (!(flags & SERD_ANON_O_BEGIN)) {
+ if (!writer->context.indented_object &&
+ !(flags & (SERD_ANON_O | SERD_LIST_O))) {
++writer->indent;
+ writer->context.indented_object = true;
}
+
write_sep(writer, SEP_END_O);
write_node(writer, object, SERD_OBJECT, flags);
- if (!(flags & SERD_ANON_O_BEGIN)) {
- --writer->indent;
- }
} else {
// Abbreviate S
+ if (writer->context.indented_object && writer->indent > 0) {
+ --writer->indent;
+ writer->context.indented_object = false;
+ }
+
Sep sep = ctx(writer, SERD_PREDICATE) ? SEP_END_P : SEP_S_P;
write_sep(writer, sep);
write_pred(writer, flags, predicate);
@@ -838,49 +897,73 @@ serd_writer_write_statement(SerdWriter* const writer,
}
} else {
// No abbreviation
- if (ctx(writer, SERD_SUBJECT)) {
- assert(writer->indent > 0);
+ if (writer->context.indented_object && writer->indent > 0) {
--writer->indent;
- if (serd_stack_is_empty(&writer->anon_stack)) {
- write_sep(writer, SEP_END_S);
+ writer->context.indented_object = false;
+ }
+
+ if (serd_stack_is_empty(&writer->anon_stack)) {
+ if (ctx(writer, SERD_SUBJECT)) {
+ write_sep(writer, SEP_END_S); // Terminate last subject
+ }
+ if (!writer->empty) {
+ write_newline(writer); // Blank line between top level items
}
- } else if (!writer->empty) {
- write_sep(writer, SEP_S_P);
}
- if (!(flags & SERD_ANON_CONT)) {
+ if (serd_stack_is_empty(&writer->anon_stack)) {
write_node(writer, subject, SERD_SUBJECT, flags);
- ++writer->indent;
- write_sep(writer, SEP_S_P);
+ if (!(flags & (SERD_ANON_S | SERD_LIST_S))) {
+ write_sep(writer, SEP_S_P);
+ } else if (flags & SERD_ANON_S) {
+ write_sep(writer, SEP_ANON_S_P);
+ }
} else {
- ++writer->indent;
+ write_sep(writer, SEP_ANON_S_P);
}
reset_context(writer, false);
serd_node_set(&writer->context.subject, subject);
- if (!(flags & SERD_LIST_S_BEGIN)) {
+ if (!(flags & SERD_LIST_S)) {
write_pred(writer, flags, predicate);
}
write_node(writer, object, SERD_OBJECT, flags);
}
- if (flags & (SERD_ANON_S_BEGIN | SERD_ANON_O_BEGIN)) {
- WriteContext* ctx =
- (WriteContext*)serd_stack_push(&writer->anon_stack, sizeof(WriteContext));
- if (!ctx) {
- return SERD_ERR_OVERFLOW;
+ // Push context for anonymous or list subject if necessary
+ if (flags & (SERD_ANON_S | SERD_LIST_S)) {
+ const bool is_list = (flags & SERD_LIST_S);
+
+ const WriteContext ctx = {is_list ? CTX_LIST : CTX_BLANK,
+ serd_node_copy(graph),
+ serd_node_copy(subject),
+ is_list ? NULL : serd_node_copy(predicate),
+ false};
+ if ((st = push_context(writer, ctx))) {
+ return st;
}
+ }
- *ctx = writer->context;
- WriteContext new_context = {
- serd_node_copy(graph), serd_node_copy(subject), NULL};
- if ((flags & SERD_ANON_S_BEGIN)) {
- new_context.predicate = serd_node_copy(predicate);
+ // Push context for anonymous or list object if necessary
+ if (flags & (SERD_ANON_O | SERD_LIST_O)) {
+ const bool is_list = (flags & SERD_LIST_O);
+
+ const WriteContext ctx = {is_list ? CTX_LIST : CTX_BLANK,
+ serd_node_copy(graph),
+ serd_node_copy(object),
+ NULL,
+ false};
+ if ((st = push_context(writer, ctx))) {
+ serd_node_free(ctx.graph);
+ serd_node_free(ctx.subject);
+ return st;
}
- writer->context = new_context;
- } else {
+ }
+
+ if (!(flags & (SERD_ANON_S | SERD_LIST_S | SERD_ANON_O | SERD_LIST_O))) {
+ // Update current context to this statement
serd_node_set(&writer->context.graph, graph);
serd_node_set(&writer->context.subject, subject);
serd_node_set(&writer->context.predicate, predicate);
@@ -896,20 +979,18 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node)
return SERD_SUCCESS;
}
- if (serd_stack_is_empty(&writer->anon_stack) || writer->indent == 0) {
- return serd_world_errorf(
- writer->world, SERD_ERR_UNKNOWN, "unexpected end of anonymous node\n");
+ if (serd_stack_is_empty(&writer->anon_stack)) {
+ return serd_world_errorf(writer->world,
+ SERD_ERR_UNKNOWN,
+ "unexpected end of anonymous node `%s'\n",
+ serd_node_string(node));
}
- --writer->indent;
write_sep(writer, SEP_ANON_END);
- free_context(&writer->context);
- writer->context = *anon_stack_top(writer);
- serd_stack_pop(&writer->anon_stack, sizeof(WriteContext));
+ pop_context(writer);
- const bool is_subject = serd_node_equals(node, writer->context.subject);
- if (is_subject) {
- serd_node_set(&writer->context.subject, node);
+ if (serd_node_equals(node, writer->context.subject)) {
+ // Now-finished anonymous node is the new subject with no other context
memset(writer->context.predicate, 0, sizeof(SerdNode));
}
@@ -941,10 +1022,17 @@ serd_writer_finish(SerdWriter* writer)
if (ctx(writer, SERD_SUBJECT)) {
write_sep(writer, SEP_END_S);
}
+
if (ctx(writer, SERD_GRAPH)) {
write_sep(writer, SEP_GRAPH_END);
}
- free_context(&writer->context);
+
+ // Free any lingering contexts in case there was an error
+ while (!serd_stack_is_empty(&writer->anon_stack)) {
+ pop_context(writer);
+ }
+
+ free_context(writer);
writer->indent = 0;
writer->context = WRITE_CONTEXT_NULL;
return SERD_SUCCESS;
@@ -971,7 +1059,6 @@ serd_writer_new(SerdWorld* world,
writer->write_func = write_func;
writer->stream = stream;
writer->context = context;
- writer->list_subj = NULL;
writer->empty = true;
writer->iface.handle = writer;
@@ -1074,13 +1161,6 @@ serd_writer_free(SerdWriter* writer)
}
serd_writer_finish(writer);
-
- // Free any leaked entries in the anonymous context stack
- while (!serd_stack_is_empty(&writer->anon_stack)) {
- free_context(anon_stack_top(writer));
- serd_stack_pop(&writer->anon_stack, sizeof(WriteContext));
- }
-
serd_stack_free(&writer->anon_stack);
free(writer->bprefix);
serd_node_free(writer->root_node);
diff --git a/test/good/base.ttl b/test/good/base.ttl
index 4c437937..755c1d09 100644
--- a/test/good/base.ttl
+++ b/test/good/base.ttl
@@ -1,3 +1,2 @@
<foo>
a <Bar> .
-
diff --git a/test/good/pretty.trig b/test/good/pretty.trig
new file mode 100644
index 00000000..140769ce
--- /dev/null
+++ b/test/good/pretty.trig
@@ -0,0 +1,98 @@
+@prefix : <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+
+:a
+ :b :c ,
+ :d ,
+ :e ;
+ :f :g ,
+ :h .
+
+(
+ 1
+)
+ :isA :List .
+
+[]
+ :isA :Blank .
+
+(
+ 2
+)
+ :sameAs (
+ 2
+ ) .
+
+[]
+ :sameAs [] .
+
+(
+ 1
+ 2
+)
+ a :List ;
+ rdf:value 3 .
+
+(
+ (
+ 3
+ )
+ (
+ 4
+ )
+)
+ a :NestedList ;
+ :sum 7 .
+
+[
+ a :BlankSubject
+]
+ a rdf:Resource .
+
+[
+ a :BlankSubject
+] .
+
+[]
+ :blank [
+ :nestedEmptyBlank [] ;
+ :nestedNonEmptyBlanks [
+ rdf:value 1
+ ] , [
+ rdf:value 2
+ ]
+ ] ;
+ :lists (
+ 3
+ 4
+ ) , (
+ 5
+ 6
+ ) , (
+ [
+ rdf:value 7
+ ]
+ [
+ rdf:value 8
+ ]
+ ) .
+
+:s
+ a :Thing ;
+ :predicate1 :object1 ,
+ [
+ a :SubThing ;
+ :predicate2 :object2
+ ] , [
+ a :OtherSubThing ;
+ :p3 :o3
+ ] ;
+ :p4 :o4 .
+
+eg:graph {
+ :a
+ :b :c ;
+ :d [
+ :e :f
+ ] .
+}
diff --git a/test/good/qualify-out.ttl b/test/good/qualify-out.ttl
index 97f67a53..42e4091a 100644
--- a/test/good/qualify-out.ttl
+++ b/test/good/qualify-out.ttl
@@ -2,4 +2,3 @@
eg:s
eg:p eg:o .
-
diff --git a/test/test_writer.c b/test/test_writer.c
index d7c4eac8..0611a40d 100644
--- a/test/test_writer.c
+++ b/test/test_writer.c
@@ -103,7 +103,7 @@ test_write_long_literal(void)
static const char* const expected =
"<http://example.org/s>\n"
- "\t<http://example.org/p> \"\"\"hello \"\"\\\"world\"\"\\\"!\"\"\" .\n\n";
+ "\t<http://example.org/p> \"\"\"hello \"\"\\\"world\"\"\\\"!\"\"\" .\n";
assert(!strcmp((char*)out, expected));
serd_free(out);
@@ -141,10 +141,9 @@ test_writer_stack_overflow(void)
const SerdNode* const p =
serd_nodes_uri(nodes, SERD_STRING("http://example.org/p"));
- const SerdNode* o =
- serd_nodes_blank(nodes, SERD_STRING("http://example.org/o"));
+ const SerdNode* o = serd_nodes_blank(nodes, SERD_STRING("blank"));
- SerdStatus st = serd_sink_write(sink, SERD_ANON_O_BEGIN, s, p, o, NULL);
+ SerdStatus st = serd_sink_write(sink, SERD_ANON_O, s, p, o, NULL);
assert(!st);
// Repeatedly write nested anonymous objects until the writer stack overflows
@@ -154,8 +153,9 @@ test_writer_stack_overflow(void)
const SerdNode* next_o = serd_nodes_blank(nodes, SERD_STRING(buf));
- st = serd_sink_write(sink, SERD_ANON_O_BEGIN, o, p, next_o, NULL);
- o = next_o;
+ st = serd_sink_write(sink, SERD_ANON_O, o, p, next_o, NULL);
+
+ o = next_o;
if (st) {
assert(st == SERD_ERR_OVERFLOW);