aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--serd/serd.h13
-rw-r--r--src/n3.c25
-rw-r--r--src/reader.c2
-rw-r--r--src/writer.c353
-rw-r--r--tests/good/base.ttl1
-rw-r--r--tests/good/pretty.trig98
-rw-r--r--tests/good/qualify-out.ttl1
-rw-r--r--wscript1
9 files changed, 327 insertions, 168 deletions
diff --git a/NEWS b/NEWS
index dbf3e4d5..20805042 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,7 @@ serd (1.0.1) unstable;
* Remove half-baked serd_uri_to_path()
* 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/serd/serd.h b/serd/serd.h
index 2c40eba6..fc5ab429 100644
--- a/serd/serd.h
+++ b/serd/serd.h
@@ -165,14 +165,11 @@ typedef enum {
Flags indicating inline abbreviation information for a statement.
*/
typedef enum {
- SERD_EMPTY_S = 1 << 1, /**< Empty blank node subject */
- SERD_EMPTY_O = 1 << 2, /**< Empty blank node object */
- SERD_ANON_S_BEGIN = 1 << 3, /**< Start of anonymous subject */
- SERD_ANON_O_BEGIN = 1 << 4, /**< Start of anonymous object */
- SERD_ANON_CONT = 1 << 5, /**< Continuation of anonymous node */
- SERD_LIST_S_BEGIN = 1 << 6, /**< Start of list subject */
- SERD_LIST_O_BEGIN = 1 << 7, /**< Start of list object */
- SERD_LIST_CONT = 1 << 8 /**< Continuation of list */
+ SERD_EMPTY_S = 1 << 0, /**< Empty blank node subject */
+ SERD_ANON_S_BEGIN = 1 << 1, /**< Start of anonymous subject */
+ SERD_ANON_O_BEGIN = 1 << 2, /**< Start of anonymous object */
+ SERD_LIST_S_BEGIN = 1 << 3, /**< Start of list subject */
+ SERD_LIST_O_BEGIN = 1 << 4, /**< Start of list object */
} SerdStatementFlag;
/**
diff --git a/src/n3.c b/src/n3.c
index a3ef9020..07a95f6f 100644
--- a/src/n3.c
+++ b/src/n3.c
@@ -951,7 +951,7 @@ read_anon(SerdReader* reader, ReadContext ctx, bool subject, SerdNode** dest)
bool empty = false;
eat_byte_safe(reader, '[');
if ((empty = peek_delim(reader, ']'))) {
- *ctx.flags |= (subject) ? SERD_EMPTY_S : SERD_EMPTY_O;
+ *ctx.flags |= (subject) ? SERD_EMPTY_S : SERD_ANON_O_BEGIN;
} else {
*ctx.flags |= (subject) ? SERD_ANON_S_BEGIN : SERD_ANON_O_BEGIN;
if (peek_delim(reader, '=')) {
@@ -973,21 +973,19 @@ read_anon(SerdReader* reader, ReadContext ctx, bool subject, SerdNode** dest)
ctx.subject = *dest;
if (!empty) {
- *ctx.flags &= ~(unsigned)SERD_LIST_CONT;
- if (!subject) {
- *ctx.flags |= SERD_ANON_CONT;
- }
bool ate_dot_in_list = false;
read_predicateObjectList(reader, ctx, &ate_dot_in_list);
if (ate_dot_in_list) {
return r_err(reader, SERD_ERR_BAD_SYNTAX, "`.' inside blank\n");
}
read_ws_star(reader);
- if (reader->sink->end) {
- reader->sink->end(reader->sink->handle, *dest);
- }
*ctx.flags = old_flags;
}
+
+ if (reader->sink->end && (!subject || !empty)) {
+ reader->sink->end(reader->sink->handle, *dest);
+ }
+
return (eat_byte_check(reader, ']') == ']') ? SERD_SUCCESS
: SERD_ERR_BAD_SYNTAX;
}
@@ -1138,9 +1136,8 @@ read_predicateObjectList(SerdReader* reader, ReadContext ctx, bool* ate_dot)
}
static SerdStatus
-end_collection(SerdReader* reader, ReadContext ctx, SerdStatus st)
+end_collection(SerdReader* reader, SerdStatus st)
{
- *ctx.flags &= ~(unsigned)SERD_LIST_CONT;
if (!st) {
eat_byte_safe(reader, ')');
}
@@ -1158,13 +1155,12 @@ read_collection(SerdReader* reader, ReadContext ctx, SerdNode** dest)
// subject predicate _:head
*ctx.flags |= (end ? 0 : SERD_LIST_O_BEGIN);
TRY(st, emit_statement(reader, ctx, *dest));
- *ctx.flags |= SERD_LIST_CONT;
} else {
*ctx.flags |= (end ? 0 : SERD_LIST_S_BEGIN);
}
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,
@@ -1183,7 +1179,7 @@ read_collection(SerdReader* reader, ReadContext ctx, SerdNode** dest)
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, ')'))) {
@@ -1197,7 +1193,6 @@ read_collection(SerdReader* reader, ReadContext ctx, SerdNode** dest)
}
// _: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)));
@@ -1206,7 +1201,7 @@ read_collection(SerdReader* reader, ReadContext ctx, SerdNode** dest)
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 c7e0feab..1337a6a4 100644
--- a/src/reader.c
+++ b/src/reader.c
@@ -133,7 +133,7 @@ emit_statement(SerdReader* reader, ReadContext ctx, SerdNode* o)
const SerdStatus st = reader->sink->statement(
reader->sink->handle, *ctx.flags, &statement);
- *ctx.flags &= SERD_ANON_CONT|SERD_LIST_CONT; // Preserve only cont flags
+ *ctx.flags = 0;
return st;
}
diff --git a/src/writer.c b/src/writer.c
index 27486fb2..9ac7419e 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -32,57 +32,75 @@
#include <stdlib.h>
#include <string.h>
-typedef struct {
- SerdNode* graph;
- SerdNode* subject;
- SerdNode* predicate;
+typedef enum {
+ CTX_NAMED, ///< Normal non-anonymous context
+ CTX_BLANK, ///< Anonymous blank node
+ CTX_LIST ///< Anonymous list
+} ContextType;
+
+typedef struct
+{
+ 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, 1, 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 }
+ {"", 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},
+ {"<", 1, +0, SEP_NONE, SEP_NONE, SEP_NONE},
+ {">", 1, +0, SEP_NONE, SEP_NONE, SEP_NONE},
};
struct SerdWriterImpl {
@@ -99,8 +117,6 @@ struct SerdWriterImpl {
SerdErrorSink error_sink;
void* error_handle;
WriteContext context;
- SerdNode* list_subj;
- unsigned list_depth;
unsigned indent;
char* bprefix;
size_t bprefix_len;
@@ -130,12 +146,40 @@ supports_abbrev(const SerdWriter* writer)
return writer->syntax == SERD_TURTLE || writer->syntax == SERD_TRIG;
}
-static inline WriteContext*
-anon_stack_top(SerdWriter* 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 inline void
+push_context(SerdWriter* writer, const WriteContext new_context)
+{
+ WriteContext* top = (WriteContext*)serd_stack_push(&writer->anon_stack,
+ sizeof(WriteContext));
+
+ *top = writer->context;
+ writer->context = new_context;
+}
+
+static inline 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));
+
+ if (writer->context.indented_object && writer->indent > 0) {
+ --writer->indent;
+ }
+
+ 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 inline SerdNode*
@@ -378,19 +422,35 @@ static bool
write_sep(SerdWriter* writer, const Sep sep)
{
const SepRule* rule = &rules[sep];
- if (rule->space_before) {
+
+ // Adjust indent, but tolerate if it would become negative
+ writer->indent =
+ ((rule->indent >= 0 || writer->indent >= (unsigned)-rule->indent)
+ ? writer->indent + rule->indent
+ : 0);
+
+ // Write newline or space before separator if necessary
+ if (rule->pre_line_after & (1u << writer->last_sep)) {
write_newline(writer);
+ } else if (rule->pre_space_after & (1u << writer->last_sep)) {
+ sink(" ", 1, writer);
}
- if (rule->str) {
- sink(rule->str, rule->len, writer);
- }
- if ((writer->last_sep && rule->space_after_sep) ||
- (!writer->last_sep && rule->space_after_node)) {
+
+ // 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);
- } else if (writer->last_sep && rule->space_after_node) {
- sink(" ", 1, writer);
+ writer->last_sep = SEP_NONE;
+ } else {
+ writer->last_sep = sep;
+ }
+
+ if (sep == SEP_END_S) {
+ writer->indent = 0;
}
- writer->last_sep = sep;
+
return true;
}
@@ -406,19 +466,11 @@ reset_context(SerdWriter* writer, bool graph)
if (writer->context.predicate) {
memset(writer->context.predicate, 0, sizeof(SerdNode));
}
+ writer->context.indented_object = false;
writer->empty = false;
return SERD_SUCCESS;
}
-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 bool
is_inline_start(const SerdWriter* writer,
SerdField field,
@@ -434,6 +486,8 @@ write_literal(SerdWriter* writer,
const SerdNode* node,
SerdStatementFlags flags)
{
+ writer->last_sep = SEP_NONE;
+
const SerdNode* datatype = serd_node_get_datatype(node);
const SerdNode* lang = serd_node_get_language(node);
const char* node_str = serd_node_get_string(node);
@@ -494,19 +548,19 @@ write_uri_node(SerdWriter* const writer,
const SerdField field,
const SerdStatementFlags flags)
{
- const SerdNode* prefix = NULL;
- SerdStringView suffix = {NULL, 0};
-
if (is_inline_start(writer, field, flags)) {
- ++writer->indent;
write_sep(writer, SEP_ANON_BEGIN);
- sink("== ", 3, writer);
+ sink(" == ", 4, writer);
}
- const char* node_str = serd_node_get_string(node);
- const bool has_scheme = serd_uri_string_has_scheme(node_str);
- if (field == SERD_PREDICATE && supports_abbrev(writer)
- && serd_node_equals(node, writer->world->rdf_type)) {
+ writer->last_sep = SEP_NONE;
+
+ const SerdNode* prefix = NULL;
+ SerdStringView suffix = {NULL, 0};
+ const char* node_str = serd_node_get_string(node);
+ const bool has_scheme = serd_uri_string_has_scheme(node_str);
+ if (field == SERD_PREDICATE && supports_abbrev(writer) &&
+ serd_node_equals(node, writer->world->rdf_type)) {
return sink("a", 1, writer) == 1;
} else if (supports_abbrev(writer) &&
serd_node_equals(node, writer->world->rdf_nil)) {
@@ -521,7 +575,7 @@ write_uri_node(SerdWriter* const writer,
return true;
}
- write_sep(writer, SEP_URI_BEGIN);
+ sink("<", 1, writer);
if (serd_env_get_base_uri(writer->env)) {
const SerdURI* base_uri = serd_env_get_parsed_base_uri(writer->env);
SerdURI uri;
@@ -540,10 +594,9 @@ write_uri_node(SerdWriter* const writer,
} else {
write_uri_from_node(writer, node);
}
- write_sep(writer, SEP_URI_END);
+ sink(">", 1, writer);
if (is_inline_start(writer, field, flags)) {
sink(" ;", 2, writer);
- write_newline(writer);
}
return true;
}
@@ -554,7 +607,7 @@ write_curie(SerdWriter* const writer,
const SerdField field,
const SerdStatementFlags flags)
{
- const char* node_str = serd_node_get_string(node);
+ writer->last_sep = SEP_NONE;
SerdStringView prefix = {NULL, 0};
SerdStringView suffix = {NULL, 0};
@@ -567,25 +620,23 @@ write_curie(SerdWriter* const writer,
serd_world_errorf(writer->world,
st,
"undefined namespace prefix `%s'\n",
- node_str);
+ serd_node_get_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:
if (is_inline_start(writer, field, flags)) {
- ++writer->indent;
write_sep(writer, SEP_ANON_BEGIN);
- sink("== ", 3, writer);
+ sink(" == ", 4, writer);
}
- write_lname(writer, node_str, node->n_bytes);
+ write_lname(writer, serd_node_get_string(node), node->n_bytes);
if (is_inline_start(writer, field, flags)) {
sink(" ;", 2, writer);
- write_newline(writer);
}
}
return true;
@@ -600,20 +651,12 @@ write_blank(SerdWriter* const writer,
const char* node_str = serd_node_get_string(node);
if (supports_abbrev(writer)) {
if (is_inline_start(writer, field, flags)) {
- ++writer->indent;
return write_sep(writer, SEP_ANON_BEGIN);
- } else 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);
- } else if (field == SERD_OBJECT && (flags & SERD_LIST_O_BEGIN)) {
- ++writer->indent;
- ++writer->list_depth;
+ } else if ((field == SERD_SUBJECT && (flags & SERD_LIST_S_BEGIN)) ||
+ (field == SERD_OBJECT && (flags & SERD_LIST_O_BEGIN))) {
return write_sep(writer, SEP_LIST_BEGIN);
- } else if ((field == SERD_SUBJECT && (flags & SERD_EMPTY_S)) ||
- (field == SERD_OBJECT && (flags & SERD_EMPTY_O))) {
+ } else if (field == SERD_SUBJECT && (flags & SERD_EMPTY_S)) {
+ writer->last_sep = SEP_NONE; // Treat "[]" like anode
return sink("[]", 2, writer) == 2;
}
}
@@ -628,6 +671,7 @@ write_blank(SerdWriter* const writer,
sink(node_str, node->n_bytes, writer);
}
+ writer->last_sep = SEP_NONE;
return true;
}
@@ -637,23 +681,18 @@ write_node(SerdWriter* writer,
const SerdField field,
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, flags);
- break;
+ return write_uri_node(writer, node, field, flags);
case SERD_CURIE:
- ret = write_curie(writer, node, field, flags);
- break;
+ return write_curie(writer, node, field, flags);
case SERD_BLANK:
- ret = write_blank(writer, node, field, flags);
- default: break;
+ return write_blank(writer, node, field, flags);
}
- writer->last_sep = SEP_NONE;
- return ret;
+
+ return false;
}
static inline bool
@@ -677,13 +716,16 @@ write_list_obj(SerdWriter* writer,
const SerdNode* object)
{
if (serd_node_equals(object, writer->world->rdf_nil)) {
- --writer->indent;
write_sep(writer, SEP_LIST_END);
return true;
- } else if (serd_node_equals(predicate, writer->world->rdf_first)) {
- write_sep(writer, SEP_LIST_SEP);
+ }
+
+ if (serd_node_equals(predicate, writer->world->rdf_first)) {
write_node(writer, object, SERD_OBJECT, flags);
+ } else {
+ write_sep(writer, SEP_LIST_SEP);
}
+
return false;
}
@@ -692,6 +734,11 @@ serd_writer_write_statement(SerdWriter* writer,
SerdStatementFlags flags,
const SerdStatement* statement)
{
+ assert(!((flags & SERD_EMPTY_S) && (flags & SERD_ANON_S_BEGIN)));
+ assert(!((flags & SERD_EMPTY_S) && (flags & SERD_LIST_S_BEGIN)));
+ assert(!((flags & SERD_ANON_S_BEGIN) && (flags & SERD_LIST_S_BEGIN)));
+ assert(!((flags & SERD_ANON_O_BEGIN) && (flags & SERD_LIST_O_BEGIN)));
+
const SerdNode* const subject = serd_statement_get_subject(statement);
const SerdNode* const predicate = serd_statement_get_predicate(statement);
const SerdNode* const object = serd_statement_get_object(statement);
@@ -727,47 +774,48 @@ serd_writer_write_statement(SerdWriter* 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_BEGIN | SERD_LIST_O_BEGIN))) {
++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);
@@ -775,22 +823,29 @@ serd_writer_write_statement(SerdWriter* 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_BEGIN | SERD_LIST_S_BEGIN))) {
+ write_sep(writer, SEP_S_P);
+ } else if (flags & SERD_ANON_S_BEGIN) {
+ write_sep(writer, SEP_ANON_S_P);
+ }
} else {
- ++writer->indent;
+ write_sep(writer, SEP_ANON_S_P);
}
reset_context(writer, false);
@@ -803,16 +858,33 @@ serd_writer_write_statement(SerdWriter* writer,
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));
- *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);
- }
- writer->context = new_context;
+ if (flags & SERD_LIST_S_BEGIN) {
+ const WriteContext ctx = {CTX_LIST,
+ serd_node_copy(graph),
+ serd_node_copy(subject),
+ NULL,
+ false};
+ push_context(writer, ctx);
+ } if (flags & SERD_LIST_O_BEGIN) {
+ const WriteContext ctx = {CTX_LIST,
+ serd_node_copy(graph),
+ serd_node_copy(object),
+ NULL,
+ false};
+ push_context(writer, ctx);
+ }
+
+ if (flags & (SERD_ANON_S_BEGIN | SERD_ANON_O_BEGIN)) {
+ const bool is_list = flags & (SERD_LIST_S_BEGIN | SERD_LIST_O_BEGIN);
+ const bool is_subject = flags & SERD_ANON_S_BEGIN;
+
+ const WriteContext ctx = {is_list ? CTX_LIST : CTX_BLANK,
+ serd_node_copy(graph),
+ serd_node_copy(subject),
+ is_subject ? serd_node_copy(predicate) : NULL,
+ false};
+
+ push_context(writer, ctx);
} else {
serd_node_set(&writer->context.graph, graph);
serd_node_set(&writer->context.subject, subject);
@@ -828,21 +900,19 @@ serd_writer_end_anon(SerdWriter* writer,
{
if (writer->syntax == SERD_NTRIPLES || writer->syntax == SERD_NQUADS) {
return SERD_SUCCESS;
- }
- if (serd_stack_is_empty(&writer->anon_stack) || writer->indent == 0) {
+ } else if (serd_stack_is_empty(&writer->anon_stack)) {
return serd_world_errorf(writer->world, SERD_ERR_UNKNOWN,
"unexpected end of anonymous node\n");
}
- --writer->indent;
+
write_sep(writer, SEP_ANON_END);
- free_context(writer);
- writer->context = *anon_stack_top(writer);
- serd_stack_pop(&writer->anon_stack, sizeof(WriteContext));
- const bool is_subject = serd_node_equals(node, writer->context.subject);
- if (is_subject) {
- serd_node_set(&writer->context.subject, node);
+ pop_context(writer);
+
+ 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));
}
+
return SERD_SUCCESS;
}
@@ -881,7 +951,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;
diff --git a/tests/good/base.ttl b/tests/good/base.ttl
index 4c437937..755c1d09 100644
--- a/tests/good/base.ttl
+++ b/tests/good/base.ttl
@@ -1,3 +1,2 @@
<foo>
a <Bar> .
-
diff --git a/tests/good/pretty.trig b/tests/good/pretty.trig
new file mode 100644
index 00000000..140769ce
--- /dev/null
+++ b/tests/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/tests/good/qualify-out.ttl b/tests/good/qualify-out.ttl
index 97f67a53..42e4091a 100644
--- a/tests/good/qualify-out.ttl
+++ b/tests/good/qualify-out.ttl
@@ -2,4 +2,3 @@
eg:s
eg:p eg:o .
-
diff --git a/wscript b/wscript
index 9369a5c7..c625f1b3 100644
--- a/wscript
+++ b/wscript
@@ -527,6 +527,7 @@ def test(tst):
with tst.group('ThroughSyntax') as check:
test_syntax_io(check, 'base.ttl', 'base.ttl', 'turtle')
test_syntax_io(check, 'qualify-in.ttl', 'qualify-out.ttl', 'turtle')
+ test_syntax_io(check, 'pretty.trig', 'pretty.trig', 'trig')
with tst.group('GoodCommands') as check:
check([serdi, '%s/tests/good/manifest.ttl' % srcdir])