diff options
-rw-r--r-- | serd/serd.h | 74 | ||||
-rw-r--r-- | src/reader.c | 90 | ||||
-rw-r--r-- | src/writer.c | 137 |
3 files changed, 159 insertions, 142 deletions
diff --git a/serd/serd.h b/serd/serd.h index 977c9e51..277096fe 100644 --- a/serd/serd.h +++ b/serd/serd.h @@ -109,17 +109,30 @@ typedef enum { } SerdSyntax; /** + Flags indication 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_ANON_END = 1 << 6, /**< End of anonymous subject */ +} SerdStatementFlag; + +/** + Bitwise OR of SerdNodeFlag values. +*/ +typedef uint32_t SerdStatementFlags; + +/** Type of a syntactic RDF node. This is more precise than the type of an abstract RDF node. An abstract node is either a resource, literal, or blank. In syntax there are two ways to refer to a resource (by URI or CURIE) and two ways to refer to a blank - (by ID or anonymously). - - Serd represents a node as a string "value" associated with a @ref SerdType, - which is precise enough to support streaming abbreviation. If abbreviation - is not applicable, @ref SERD_ANON_BEGIN and @ref SERD_ANON may simply be - considered equivalent to @ref SERD_BLANK_ID. + (by ID or anonymously). Anonymous (inline) blank nodes are expressed using + SerdStatementFlags rather than this type. */ typedef enum { /** @@ -156,30 +169,15 @@ typedef enum { SERD_CURIE = 3, /** - A blank node ID. + A blank node. Value is a blank node ID, e.g. "id3", which is meaningful only within this serialisation. @see <a href="http://www.w3.org/TeamSubmission/turtle#nodeID">Turtle <tt>nodeID</tt></a> */ - SERD_BLANK_ID = 4, - - /** - The first reference to an anonymous (inlined) blank node. - - Value is identical to a @ref SERD_BLANK_ID value (i.e. this type may be - safely considered equivalent to @ref SERD_BLANK_ID). - */ - SERD_ANON_BEGIN = 5, - - /** - An anonymous blank node. + SERD_BLANK = 4, - Value is identical to a @ref SERD_BLANK_ID value (i.e. this type may be - safely considered equivalent to @ref SERD_BLANK_ID). - */ - SERD_ANON = 6 } SerdType; /** @@ -399,13 +397,14 @@ typedef SerdStatus (*SerdPrefixSink)(void* handle, Called for every RDF statement in the serialisation. */ -typedef SerdStatus (*SerdStatementSink)(void* handle, - const SerdNode* graph, - const SerdNode* subject, - const SerdNode* predicate, - const SerdNode* object, - const SerdNode* object_datatype, - const SerdNode* object_lang); +typedef SerdStatus (*SerdStatementSink)(void* handle, + SerdStatementFlags flags, + const SerdNode* graph, + const SerdNode* subject, + const SerdNode* predicate, + const SerdNode* object, + const SerdNode* object_datatype, + const SerdNode* object_lang); /** Sink (callback) for anonymous node end markers. @@ -632,13 +631,14 @@ serd_writer_set_prefix(SerdWriter* writer, */ SERD_API SerdStatus -serd_writer_write_statement(SerdWriter* writer, - const SerdNode* graph, - const SerdNode* subject, - const SerdNode* predicate, - const SerdNode* object, - const SerdNode* object_datatype, - const SerdNode* object_lang); +serd_writer_write_statement(SerdWriter* writer, + SerdStatementFlags flags, + const SerdNode* graph, + const SerdNode* subject, + const SerdNode* predicate, + const SerdNode* object, + const SerdNode* object_datatype, + const SerdNode* object_lang); /** Mark the end of an anonymous node's description. diff --git a/src/reader.c b/src/reader.c index 12109ad3..e76e40e1 100644 --- a/src/reader.c +++ b/src/reader.c @@ -49,9 +49,10 @@ typedef struct { } Node; typedef struct { - const Node* graph; - const Node* subject; - const Node* predicate; + const Node* graph; + const Node* subject; + const Node* predicate; + SerdStatementFlags* flags; } ReadContext; /** Measured UTF-8 string. */ @@ -305,7 +306,7 @@ public_node(SerdReader* reader, const Node* private) } static inline bool -emit_statement(SerdReader* reader, +emit_statement(SerdReader* reader, SerdStatementFlags* flags, const Node* g, const Node* s, const Node* p, const Node* o, const Node* d, Ref l, uint32_t f) { @@ -318,17 +319,20 @@ emit_statement(SerdReader* reader, const SerdNode datatype = public_node(reader, d); const SerdNode lang = public_node_from_ref(reader, SERD_LITERAL, l); object.flags = f; - return !reader->statement_sink(reader->handle, - &graph, - &subject, - &predicate, - &object, - &datatype, - &lang); + bool ret = !reader->statement_sink(reader->handle, + *flags, + &graph, + &subject, + &predicate, + &object, + &datatype, + &lang); + *flags = (*flags & SERD_ANON_CONT) ? SERD_ANON_CONT : 0; + return ret; } static bool read_collection(SerdReader* reader, ReadContext ctx, Node* dest); -static bool read_predicateObjectList(SerdReader* reader, ReadContext ctx); +static bool read_predicateObjectList(SerdReader* reader, ReadContext ctx, bool blank); // [40] hex ::= [#x30-#x39] | [#x41-#x46] static inline uint8_t @@ -1022,45 +1026,53 @@ blank_id(SerdReader* reader) // Impl: [21] blank ::= nodeID | '[ ws* ]' // | '[' ws* predicateObjectList ws* ']' | collection static bool -read_blank(SerdReader* reader, ReadContext ctx, Node* dest) +read_blank(SerdReader* reader, ReadContext ctx, bool subject, Node* dest) { + const bool was_anon_subject = (*ctx.flags | SERD_ANON_CONT); switch (peek_byte(reader)) { case '_': - *dest = make_node(SERD_BLANK_ID, read_nodeID(reader)); + *dest = make_node(SERD_BLANK, read_nodeID(reader)); return true; case '[': eat_byte(reader, '['); read_ws_star(reader); + *dest = make_node(SERD_BLANK, blank_id(reader)); if (peek_byte(reader) == ']') { eat_byte(reader, ']'); - *dest = make_node(SERD_BLANK_ID, blank_id(reader)); + *ctx.flags |= (subject) ? SERD_EMPTY_S : SERD_EMPTY_O; if (ctx.subject) { - TRY_RET(emit_statement(reader, + TRY_RET(emit_statement(reader, ctx.flags, ctx.graph, ctx.subject, ctx.predicate, dest, NULL, 0, 0)); } return true; } - *dest = make_node(SERD_ANON_BEGIN, blank_id(reader)); + + *ctx.flags |= (subject) ? SERD_ANON_S_BEGIN : SERD_ANON_O_BEGIN; if (ctx.subject) { - TRY_RET(emit_statement(reader, + TRY_RET(emit_statement(reader, ctx.flags, ctx.graph, ctx.subject, ctx.predicate, dest, NULL, 0, 0)); - dest->type = SERD_ANON; } ctx.subject = dest; - read_predicateObjectList(reader, ctx); + if (!subject) { + *ctx.flags |= SERD_ANON_CONT; + } + read_predicateObjectList(reader, ctx, true); read_ws_star(reader); eat_byte(reader, ']'); if (reader->end_sink) { const SerdNode end = public_node(reader, dest); reader->end_sink(reader->handle, &end); } + if (subject && !was_anon_subject) { + *ctx.flags &= ~SERD_ANON_CONT; + } return true; case '(': if (read_collection(reader, ctx, dest)) { if (ctx.subject) { - TRY_RET(emit_statement(reader, + TRY_RET(emit_statement(reader, ctx.flags, ctx.graph, ctx.subject, ctx.predicate, dest, NULL, 0, 0)); } @@ -1113,17 +1125,14 @@ read_object(SerdReader* reader, ReadContext ctx) emit = false; // fall through case '_': - TRY_THROW(ret = read_blank(reader, ctx, &o)); + TRY_THROW(ret = read_blank(reader, ctx, false, &o)); break; case '<': case ':': TRY_THROW(ret = read_resource(reader, &o)); break; case '\"': case '+': case '-': case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - TRY_THROW(ret = read_literal(reader, &o, &datatype, &lang, &flags)); - break; - case '.': + case '5': case '6': case '7': case '8': case '9': case '.': TRY_THROW(ret = read_literal(reader, &o, &datatype, &lang, &flags)); break; default: @@ -1150,7 +1159,7 @@ read_object(SerdReader* reader, ReadContext ctx) if (ret && emit) { assert(o.value); - ret = emit_statement(reader, + ret = emit_statement(reader, ctx.flags, ctx.graph, ctx.subject, ctx.predicate, &o, &datatype, lang, flags); } @@ -1168,7 +1177,7 @@ except: // Spec: [8] objectList ::= object ( ',' object )* // Impl: [8] objectList ::= object ( ws* ',' ws* object )* static bool -read_objectList(SerdReader* reader, ReadContext ctx) +read_objectList(SerdReader* reader, ReadContext ctx, bool blank) { TRY_RET(read_object(reader, ctx)); read_ws_star(reader); @@ -1186,7 +1195,7 @@ read_objectList(SerdReader* reader, ReadContext ctx) // Impl: [7] predicateObjectList ::= verb ws+ objectList // (ws* ';' ws* verb ws+ objectList)* (';')? static bool -read_predicateObjectList(SerdReader* reader, ReadContext ctx) +read_predicateObjectList(SerdReader* reader, ReadContext ctx, bool blank) { if (reader->eof) { return false; @@ -1195,7 +1204,7 @@ read_predicateObjectList(SerdReader* reader, ReadContext ctx) TRY_RET(read_verb(reader, &predicate)); TRY_THROW(read_ws_plus(reader)); ctx.predicate = &predicate; - TRY_THROW(read_objectList(reader, ctx)); + TRY_THROW(read_objectList(reader, ctx, blank)); pop_string(reader, predicate.value); predicate.value = 0; read_ws_star(reader); @@ -1209,7 +1218,7 @@ read_predicateObjectList(SerdReader* reader, ReadContext ctx) TRY_THROW(read_verb(reader, &predicate)); ctx.predicate = &predicate; TRY_THROW(read_ws_plus(reader)); - TRY_THROW(read_objectList(reader, ctx)); + TRY_THROW(read_objectList(reader, ctx, blank)); pop_string(reader, predicate.value); predicate.value = 0; read_ws_star(reader); @@ -1229,18 +1238,20 @@ read_collection_rec(SerdReader* reader, ReadContext ctx) read_ws_star(reader); if (peek_byte(reader) == ')') { eat_byte(reader, ')'); - TRY_RET(emit_statement(reader, NULL, + TRY_RET(emit_statement(reader, ctx.flags, + NULL, ctx.subject, &reader->rdf_rest, &reader->rdf_nil, NULL, 0, 0)); return false; } else { - const Node rest = make_node(SERD_BLANK_ID, blank_id(reader)); - TRY_RET(emit_statement(reader, ctx.graph, + const Node rest = make_node(SERD_BLANK, blank_id(reader)); + TRY_RET(emit_statement(reader, ctx.flags, + ctx.graph, ctx.subject, &reader->rdf_rest, &rest, NULL, 0, 0)); - ctx.subject = &rest; + ctx.subject = &rest; ctx.predicate = &reader->rdf_first; if (read_object(reader, ctx)) { read_collection_rec(reader, ctx); @@ -1266,7 +1277,7 @@ read_collection(SerdReader* reader, ReadContext ctx, Node* dest) return true; } - *dest = make_node(SERD_BLANK_ID, blank_id(reader)); + *dest = make_node(SERD_BLANK, blank_id(reader)); ctx.subject = dest; ctx.predicate = &reader->rdf_first; if (!read_object(reader, ctx)) { @@ -1284,7 +1295,7 @@ read_subject(SerdReader* reader, ReadContext ctx) Node subject = INTERNAL_NODE_NULL; switch (peek_byte(reader)) { case '[': case '(': case '_': - read_blank(reader, ctx, &subject); + read_blank(reader, ctx, true, &subject); break; default: read_resource(reader, &subject); @@ -1302,7 +1313,7 @@ read_triples(SerdReader* reader, ReadContext ctx) if (subject.value != 0) { ctx.subject = &subject; TRY_RET(read_ws_plus(reader)); - ret = read_predicateObjectList(reader, ctx); + ret = read_predicateObjectList(reader, ctx, false); pop_string(reader, subject.value); } ctx.subject = ctx.predicate = 0; @@ -1370,7 +1381,8 @@ read_directive(SerdReader* reader) static bool read_statement(SerdReader* reader) { - ReadContext ctx = { 0, 0, 0 }; + SerdStatementFlags flags = 0; + ReadContext ctx = { 0, 0, 0, &flags }; read_ws_star(reader); if (reader->eof) { return true; diff --git a/src/writer.c b/src/writer.c index fafde07c..550337a7 100644 --- a/src/writer.c +++ b/src/writer.c @@ -184,46 +184,47 @@ reset_context(SerdWriter* writer) writer->empty = false; } +typedef enum { + FIELD_NONE, + FIELD_SUBJECT, + FIELD_PREDICATE, + FIELD_OBJECT +} Field; + static bool -write_node(SerdWriter* writer, - const SerdNode* node, - const SerdNode* datatype, - const SerdNode* lang, - bool is_predicate) +write_node(SerdWriter* writer, + const SerdNode* node, + const SerdNode* datatype, + const SerdNode* lang, + Field field, + SerdStatementFlags flags) { SerdChunk uri_prefix; SerdChunk uri_suffix; switch (node->type) { case SERD_NOTHING: return false; - case SERD_ANON_BEGIN: - if (writer->syntax != SERD_NTRIPLES) { + case SERD_BLANK: + if (writer->syntax != SERD_NTRIPLES + && ((field == FIELD_SUBJECT && (flags & SERD_ANON_S_BEGIN)) + || (field == FIELD_OBJECT && (flags & SERD_ANON_O_BEGIN)))) { ++writer->indent; serd_writer_write_delim(writer, '['); - WriteContext* ctx = (WriteContext*)serd_stack_push( - &writer->anon_stack, sizeof(WriteContext)); - ctx->graph = serd_node_copy(&writer->context.graph); - ctx->subject = serd_node_copy(&writer->context.subject); - ctx->predicate = serd_node_copy(&writer->context.predicate); - ctx->object = serd_node_copy(&writer->context.object); - reset_context(writer); - writer->context.subject = serd_node_copy(node); - break; - } - case SERD_ANON: - if (writer->syntax != SERD_NTRIPLES) { - break; - } // else fall through - case SERD_BLANK_ID: - writer->sink("_:", 2, writer->stream); - if (writer->bprefix - && !strncmp((const char*)node->buf, (const char*)writer->bprefix, - writer->bprefix_len)) { - writer->sink(node->buf + writer->bprefix_len, - node->n_bytes - writer->bprefix_len, - writer->stream); + } else if (writer->syntax != SERD_NTRIPLES + && ((field == FIELD_SUBJECT && (flags & SERD_EMPTY_S)) + || (field == FIELD_OBJECT && (flags & SERD_EMPTY_O)))) { + writer->sink("[]", 2, writer->stream); } else { - writer->sink(node->buf, node->n_bytes, writer->stream); + writer->sink("_:", 2, writer->stream); + if (writer->bprefix + && !strncmp((const char*)node->buf, (const char*)writer->bprefix, + writer->bprefix_len)) { + writer->sink(node->buf + writer->bprefix_len, + node->n_bytes - writer->bprefix_len, + writer->stream); + } else { + writer->sink(node->buf, node->n_bytes, writer->stream); + } } break; case SERD_CURIE: @@ -268,7 +269,7 @@ write_node(SerdWriter* writer, writer->sink(lang->buf, lang->n_bytes, writer->stream); } else if (datatype && datatype->buf) { writer->sink("^^", 2, writer->stream); - write_node(writer, datatype, NULL, NULL, false); + write_node(writer, datatype, NULL, NULL, FIELD_NONE, flags); } break; case SERD_URI: @@ -308,22 +309,24 @@ write_node(SerdWriter* writer, SERD_API SerdStatus -serd_writer_write_statement(SerdWriter* writer, - const SerdNode* graph, - const SerdNode* subject, - const SerdNode* predicate, - const SerdNode* object, - const SerdNode* object_datatype, - const SerdNode* object_lang) +serd_writer_write_statement(SerdWriter* writer, + SerdStatementFlags flags, + const SerdNode* graph, + const SerdNode* subject, + const SerdNode* predicate, + const SerdNode* object, + const SerdNode* object_datatype, + const SerdNode* object_lang) { assert(subject && predicate && object); switch (writer->syntax) { case SERD_NTRIPLES: - write_node(writer, subject, NULL, NULL, false); + write_node(writer, subject, NULL, NULL, FIELD_SUBJECT, flags); writer->sink(" ", 1, writer->stream); - write_node(writer, predicate, NULL, NULL, true); + write_node(writer, predicate, NULL, NULL, FIELD_PREDICATE, flags); writer->sink(" ", 1, writer->stream); - if (!write_node(writer, object, object_datatype, object_lang, false)) { + if (!write_node(writer, object, object_datatype, object_lang, + FIELD_OBJECT, flags)) { return SERD_ERR_UNKNOWN; } writer->sink(" .\n", 3, writer->stream); @@ -334,24 +337,19 @@ serd_writer_write_statement(SerdWriter* writer, if (serd_node_equals(subject, &writer->context.subject)) { if (serd_node_equals(predicate, &writer->context.predicate)) { // Abbreviate S P - if (writer->context.object.type == SERD_ANON_BEGIN) { - writer->sink(" , ", 3, writer->stream); - write_node(writer, object, object_datatype, object_lang, false); - } else { - ++writer->indent; - serd_writer_write_delim(writer, ','); - write_node(writer, object, object_datatype, object_lang, false); - --writer->indent; - } + ++writer->indent; + serd_writer_write_delim(writer, ','); + write_node(writer, object, object_datatype, object_lang, + FIELD_OBJECT, flags); + --writer->indent; } else { // Abbreviate S if (writer->context.predicate.buf) { serd_writer_write_delim(writer, ';'); } else { - ++writer->indent; serd_writer_write_delim(writer, '\n'); } - write_node(writer, predicate, NULL, NULL, true); + write_node(writer, predicate, NULL, NULL, FIELD_PREDICATE, flags); if (writer->context.predicate.buf) serd_node_free(&writer->context.predicate); writer->context.predicate = serd_node_copy(predicate); @@ -359,7 +357,8 @@ serd_writer_write_statement(SerdWriter* writer, serd_node_free(&writer->context.object); writer->context.object = serd_node_copy(object); writer->sink(" ", 1, writer->stream); - write_node(writer, object, object_datatype, object_lang, false); + write_node(writer, object, object_datatype, object_lang, + FIELD_OBJECT, flags); } } else { if (writer->context.subject.buf) { @@ -374,29 +373,33 @@ serd_writer_write_statement(SerdWriter* writer, serd_writer_write_delim(writer, '\n'); } - if (subject->type == SERD_ANON_BEGIN) { - writer->sink("[ ", 2, writer->stream); - ++writer->indent; - WriteContext* ctx = (WriteContext*)serd_stack_push( - &writer->anon_stack, sizeof(WriteContext)); - *ctx = writer->context; - } else { - write_node(writer, subject, NULL, NULL, false); + if (!(flags & SERD_ANON_CONT)) { + write_node(writer, subject, NULL, NULL, FIELD_SUBJECT, flags); ++writer->indent; - if (subject->type != SERD_ANON_BEGIN && subject->type != SERD_ANON) { + if (!(flags & SERD_ANON_S_BEGIN)) { serd_writer_write_delim(writer, '\n'); } + } else { + ++writer->indent; } reset_context(writer); writer->context.subject = serd_node_copy(subject); writer->context.predicate = SERD_NODE_NULL; - write_node(writer, predicate, NULL, NULL, true); + write_node(writer, predicate, NULL, NULL, FIELD_PREDICATE, flags); writer->context.predicate = serd_node_copy(predicate); writer->sink(" ", 1, writer->stream); - write_node(writer, object, object_datatype, object_lang, false); + write_node(writer, object, object_datatype, object_lang, + FIELD_OBJECT, flags); + } + + if (writer->syntax != SERD_NTRIPLES + && ((flags & SERD_ANON_S_BEGIN) || (flags & SERD_ANON_O_BEGIN))) { + WriteContext* ctx = (WriteContext*)serd_stack_push( + &writer->anon_stack, sizeof(WriteContext)); + *ctx = writer->context; } const WriteContext new_context = { serd_node_copy(graph), @@ -424,11 +427,13 @@ serd_writer_end_anon(SerdWriter* writer, --writer->indent; serd_writer_write_delim(writer, '\n'); writer->sink("]", 1, writer->stream); + const bool is_subject = serd_node_equals(node, &writer->context.subject); reset_context(writer); writer->context = *anon_stack_top(writer); serd_stack_pop(&writer->anon_stack, sizeof(WriteContext)); - if (!writer->context.subject.buf) { // End of anonymous subject - writer->context.subject = serd_node_copy(node); + if (is_subject) { + writer->context.subject = serd_node_copy(node); + writer->context.predicate = SERD_NODE_NULL; } return SERD_SUCCESS; } |