From 002585aaebbb1105de0935a52a04eda2e4950508 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 21 Oct 2018 20:57:33 +0200 Subject: Simplify streaming API and improve pretty printing This removes the obligation from the caller to correctly maintain flags to describe the current anonymous context, instead making the writer handle this itself as much as possible. Flags remain for the cases the writer can not infer from context: the start of anonymous subject and object nodes. --- NEWS | 1 + serd/serd.h | 13 +- src/n3.c | 25 ++-- src/reader.c | 2 +- src/writer.c | 353 +++++++++++++++++++++++++++------------------ tests/good/base.ttl | 1 - tests/good/pretty.trig | 98 +++++++++++++ tests/good/qualify-out.ttl | 1 - wscript | 1 + 9 files changed, 327 insertions(+), 168 deletions(-) create mode 100644 tests/good/pretty.trig 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 #include -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 @@ a . - 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 : . +@prefix rdf: . + +: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]) -- cgit v1.2.1