aboutsummaryrefslogtreecommitdiffstats
path: root/src/describe.c
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2021-08-12 22:33:47 -0400
committerDavid Robillard <d@drobilla.net>2022-01-28 21:57:07 -0500
commit196db2ef0cd44c3fb542b86be7929bd01d83e138 (patch)
tree97ad345394e394c0d42b43fcf3d464794f719ccb /src/describe.c
parentfdf837b4b3baffc65e429c2e6ecc2e764bfed0ac (diff)
downloadserd-196db2ef0cd44c3fb542b86be7929bd01d83e138.tar.gz
serd-196db2ef0cd44c3fb542b86be7929bd01d83e138.tar.bz2
serd-196db2ef0cd44c3fb542b86be7929bd01d83e138.zip
Put rdf:type properties first when pretty-printing
This is a common convention in Turtle and TriG because the special "a" syntax for rdf type as the first property looks nice, makes things easier to read, and can be useful for streaming implementations because the type of the instance is known before reading its properties. Also significantly clean up the pretty-printing implementation in the process.
Diffstat (limited to 'src/describe.c')
-rw-r--r--src/describe.c180
1 files changed, 114 insertions, 66 deletions
diff --git a/src/describe.c b/src/describe.c
index 718eaf1c..9328f261 100644
--- a/src/describe.c
+++ b/src/describe.c
@@ -29,17 +29,24 @@
#include <assert.h>
#include <stdbool.h>
-#include <stdlib.h>
+#include <stddef.h>
typedef enum { NAMED, ANON_S, ANON_O, LIST_S, LIST_O } NodeStyle;
+typedef struct {
+ const SerdModel* model; // Model to read from
+ const SerdSink* sink; // Sink to write description to
+ ZixHash* list_subjects; // Nodes written in the current list or null
+ SerdDescribeFlags flags; // Flags to control description
+} DescribeContext;
+
static SerdStatus
-write_range_statement(const SerdSink* sink,
- const SerdModel* model,
- ZixHash* list_subjects,
- unsigned depth,
- SerdStatementFlags flags,
- const SerdStatement* statement);
+write_range_statement(const DescribeContext* ctx,
+ unsigned depth,
+ SerdStatementFlags statement_flags,
+ const SerdStatement* statement,
+ const SerdNode* last_subject,
+ bool write_types);
static NodeStyle
get_node_style(const SerdModel* const model, const SerdNode* const node)
@@ -81,39 +88,41 @@ ptr_equals(const SerdNode* const a, const SerdNode* const b)
}
static SerdStatus
-write_pretty_range(const SerdSink* const sink,
- const unsigned depth,
- const SerdModel* const model,
- SerdCursor* const range)
+write_pretty_range(const DescribeContext* const ctx,
+ const unsigned depth,
+ SerdCursor* const range,
+ const SerdNode* last_subject,
+ bool write_types)
{
- ZixHash* const list_subjects = zix_hash_new(identity, ptr_hash, ptr_equals);
- SerdStatus st = SERD_SUCCESS;
-
- while (!st && !serd_cursor_is_end(range)) {
- const SerdStatement* const statement = serd_cursor_get(range);
- assert(statement);
+ SerdStatus st = SERD_SUCCESS;
+ const SerdStatement* statement = serd_cursor_get(range);
- if (!(st = write_range_statement(
- sink, model, list_subjects, depth, 0, statement))) {
- st = serd_cursor_advance(range);
+ while (statement) {
+ // Write this statement (and possibly more to describe anonymous nodes)
+ if ((st = write_range_statement(
+ ctx, depth, 0u, statement, last_subject, write_types))) {
+ break;
}
- }
- zix_hash_free(list_subjects);
+ // Update the last subject and advance the cursor
+ last_subject = serd_statement_subject(statement);
+ st = serd_cursor_advance(range);
+ statement = serd_cursor_get(range);
+ }
return st > SERD_FAILURE ? st : SERD_SUCCESS;
}
static SerdStatus
-write_list(const SerdSink* const sink,
- const SerdModel* const model,
- ZixHash* const list_subjects,
- const unsigned depth,
- SerdStatementFlags flags,
- const SerdNode* object,
- const SerdNode* const graph)
+write_list(const DescribeContext* const ctx,
+ const unsigned depth,
+ SerdStatementFlags flags,
+ const SerdNode* object,
+ const SerdNode* const graph)
{
+ const SerdModel* const model = ctx->model;
const SerdWorld* const world = model->world;
+ const SerdSink* const sink = ctx->sink;
const SerdNode* const rdf_first = world->rdf_first;
const SerdNode* const rdf_rest = world->rdf_rest;
const SerdNode* const rdf_nil = world->rdf_nil;
@@ -126,8 +135,7 @@ write_list(const SerdSink* const sink,
while (!st && !serd_node_equals(object, rdf_nil)) {
// Write rdf:first statement for this node
- if ((st = write_range_statement(
- sink, model, list_subjects, depth, flags, fs))) {
+ if ((st = write_range_statement(ctx, depth, flags, fs, NULL, false))) {
return st;
}
@@ -177,24 +185,47 @@ skip_range_statement(const SerdModel* const model,
}
static SerdStatus
-write_range_statement(const SerdSink* const sink,
- const SerdModel* const model,
- ZixHash* const list_subjects,
- const unsigned depth,
- SerdStatementFlags flags,
- const SerdStatement* SERD_NONNULL statement)
+write_subject_types(const DescribeContext* const ctx,
+ const unsigned depth,
+ const SerdNode* const subject,
+ const SerdNode* const graph)
{
- const SerdNode* const subject = serd_statement_subject(statement);
- const NodeStyle subject_style = get_node_style(model, subject);
- const SerdNode* const object = serd_statement_object(statement);
- const NodeStyle object_style = get_node_style(model, object);
- const SerdNode* const graph = serd_statement_graph(statement);
- SerdStatus st = SERD_SUCCESS;
+ SerdStatus st = SERD_SUCCESS;
+ SerdCursor* const t = serd_model_find(
+ ctx->model, subject, ctx->model->world->rdf_type, NULL, graph);
- if (subject_style == ANON_S) { // Write anonymous subject like "[] p o"
- flags |= SERD_EMPTY_S;
+ if (t) {
+ st = write_pretty_range(ctx, depth + 1, t, subject, true);
}
+ serd_cursor_free(t);
+ return st;
+}
+
+static bool
+types_first_for_subject(const DescribeContext* const ctx, const NodeStyle style)
+{
+ return style != LIST_S && !(ctx->flags & SERD_NO_TYPE_FIRST);
+}
+
+static SerdStatus
+write_range_statement(const DescribeContext* const ctx,
+ const unsigned depth,
+ SerdStatementFlags statement_flags,
+ const SerdStatement* SERD_NONNULL statement,
+ const SerdNode* SERD_NULLABLE last_subject,
+ const bool write_types)
+{
+ const SerdModel* const model = ctx->model;
+ const SerdSink* const sink = ctx->sink;
+ const SerdNode* const subject = serd_statement_subject(statement);
+ const NodeStyle subject_style = get_node_style(model, subject);
+ const SerdNode* const predicate = serd_statement_predicate(statement);
+ const SerdNode* const object = serd_statement_object(statement);
+ const NodeStyle object_style = get_node_style(model, object);
+ const SerdNode* const graph = serd_statement_graph(statement);
+ SerdStatus st = SERD_SUCCESS;
+
if (depth == 0u) {
if (skip_range_statement(model, statement)) {
return SERD_SUCCESS; // Skip subject that will be inlined elsewhere
@@ -202,9 +233,8 @@ write_range_statement(const SerdSink* const sink,
if (subject_style == LIST_S) {
// First write inline list subject, which this statement will follow
- if (zix_hash_insert(list_subjects, subject) != ZIX_STATUS_EXISTS) {
- st = write_list(
- sink, model, list_subjects, 2, flags | SERD_LIST_S, subject, graph);
+ if (zix_hash_insert(ctx->list_subjects, subject) != ZIX_STATUS_EXISTS) {
+ st = write_list(ctx, 2, statement_flags | SERD_LIST_S, subject, graph);
}
}
}
@@ -213,29 +243,42 @@ write_range_statement(const SerdSink* const sink,
return st;
}
- if (object_style == ANON_O) { // Write anonymous object like "[ ... ]"
- SerdCursor* const iter = serd_model_find(model, object, NULL, NULL, NULL);
+ // If this is a new subject, write types first if necessary
+ const bool types_first = types_first_for_subject(ctx, subject_style);
+ if (subject != last_subject && types_first) {
+ st = write_subject_types(ctx, depth, subject, graph);
+ }
- flags |= SERD_ANON_O;
- if (!(st = serd_sink_write_statement(sink, flags, statement))) {
- if (!(st = write_pretty_range(sink, depth + 1, model, iter))) {
- st = serd_sink_write_end(sink, object);
- }
- }
+ // Skip type statement if it would be written another time (just above)
+ if (subject_style != LIST_S && !write_types &&
+ serd_node_equals(predicate, model->world->rdf_type)) {
+ return st;
+ }
- serd_cursor_free(iter);
+ // Set up the flags for this statement
+ statement_flags |=
+ (((subject_style == ANON_S) * (SerdStatementFlags)SERD_EMPTY_S) |
+ ((object_style == ANON_O) * (SerdStatementFlags)SERD_ANON_O) |
+ ((object_style == LIST_O) * (SerdStatementFlags)SERD_LIST_O));
- } else if (object_style == LIST_O) { // Write list object like "( ... )"
- if (!(st =
- serd_sink_write_statement(sink, flags | SERD_LIST_O, statement))) {
- flags = flags & ~((unsigned)SERD_LIST_S);
+ // Finally write this statement
+ if ((st = serd_sink_write_statement(sink, statement_flags, statement))) {
+ return st;
+ }
- st =
- write_list(sink, model, list_subjects, depth + 1, flags, object, graph);
+ if (object_style == ANON_O) {
+ // Follow an anonymous object with its description like "[ ... ]"
+ SerdCursor* const iter = serd_model_find(model, object, NULL, NULL, NULL);
+
+ if (!(st = write_pretty_range(ctx, depth + 1, iter, last_subject, false))) {
+ st = serd_sink_write_end(sink, object);
}
- } else {
- st = serd_sink_write_statement(sink, flags, statement);
+ serd_cursor_free(iter);
+
+ } else if (object_style == LIST_O) {
+ // Follow a list object with its description like "( ... )"
+ st = write_list(ctx, depth + 1, 0u, object, graph);
}
return st;
@@ -264,7 +307,12 @@ serd_describe_range(const SerdCursor* const range,
}
}
} else {
- st = write_pretty_range(sink, 0, range->model, &copy);
+ DescribeContext ctx = {
+ range->model, sink, zix_hash_new(identity, ptr_hash, ptr_equals), flags};
+
+ st = write_pretty_range(&ctx, 0, &copy, NULL, (flags & SERD_NO_TYPE_FIRST));
+
+ zix_hash_free(ctx.list_subjects);
}
return st;