SERD_API SerdNode serd_node_new_uri(const SerdURI* uri, const SerdURI* base, SerdURI* out) { SerdURI abs_uri = *uri; if (base) { serd_uri_resolve(uri, base, &abs_uri); } const size_t len = serd_uri_string_length(&abs_uri); uint8_t* buf = (uint8_t*)malloc(len + 1); SerdNode node = { buf, len, len, 0, SERD_URI }; // FIXME: UTF-8 uint8_t* ptr = buf; const size_t actual_len = serd_uri_serialise(&abs_uri, string_sink, &ptr); buf[actual_len] = '\0'; node.n_bytes = actual_len; node.n_chars = actual_len; if (out) { serd_uri_parse(buf, out); // TODO: cleverly avoid double parse } return node; }
SocketWriter::SocketWriter(URIMap& map, URIs& uris, const Raul::URI& uri, SPtr<Raul::Socket> sock) : AtomWriter(map, uris, *this) , _map(map) , _sratom(sratom_new(&map.urid_map_feature()->urid_map)) , _uri(uri) , _socket(sock) { // Use <ingen:/root/> as base URI so e.g. </foo/bar> will be a path _base = serd_node_from_string(SERD_URI, (const uint8_t*)"ingen:/root/"); serd_uri_parse(_base.buf, &_base_uri); _env = serd_env_new(&_base); _writer = serd_writer_new( SERD_TURTLE, (SerdStyle)(SERD_STYLE_RESOLVED|SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED), _env, &_base_uri, socket_sink, this); sratom_set_sink(_sratom, (const char*)_base.buf, (SerdStatementSink)serd_writer_write_statement, (SerdEndSink)serd_writer_end_anon, _writer); }
SERD_API SerdNode serd_node_new_uri_from_string(const uint8_t* str, const SerdURI* base, SerdURI* out) { if (!str || str[0] == '\0') { return serd_node_new_uri(base, NULL, out); // Empty URI => Base URI } SerdURI uri; serd_uri_parse(str, &uri); return serd_node_new_uri(&uri, base, out); // Resolve/Serialise }
SERD_API SerdStatus serd_writer_set_root_uri(SerdWriter* writer, const SerdNode* uri) { serd_node_free(&writer->root_node); if (uri && uri->buf) { writer->root_node = serd_node_copy(uri); serd_uri_parse(uri->buf, &writer->root_uri); } else { writer->root_node = SERD_NODE_NULL; writer->root_uri = SERD_URI_NULL; } return SERD_SUCCESS; }
SocketWriter::SocketWriter(URIMap& map, URIs& uris, const Raul::URI& uri, SPtr<Raul::Socket> sock) : AtomWriter(map, uris, *this) , _map(map) , _sratom(sratom_new(&map.urid_map_feature()->urid_map)) , _uri(uri) , _socket(sock) { // Use <ingen:/> as base URI, so relative URIs are like bundle paths _base = serd_node_from_string(SERD_URI, (const uint8_t*)"ingen:/"); serd_uri_parse(_base.buf, &_base_uri); // Set up serialisation environment _env = serd_env_new(&_base); serd_env_set_prefix_from_strings(_env, USTR("atom"), USTR("http://lv2plug.in/ns/ext/atom#")); serd_env_set_prefix_from_strings(_env, USTR("patch"), USTR("http://lv2plug.in/ns/ext/patch#")); serd_env_set_prefix_from_strings(_env, USTR("doap"), USTR("http://usefulinc.com/ns/doap#")); serd_env_set_prefix_from_strings(_env, USTR("ingen"), USTR(INGEN_NS)); serd_env_set_prefix_from_strings(_env, USTR("lv2"), USTR("http://lv2plug.in/ns/lv2core#")); serd_env_set_prefix_from_strings(_env, USTR("midi"), USTR("http://lv2plug.in/ns/ext/midi#")); serd_env_set_prefix_from_strings(_env, USTR("owl"), USTR("http://www.w3.org/2002/07/owl#")); serd_env_set_prefix_from_strings(_env, USTR("rdf"), USTR("http://www.w3.org/1999/02/22-rdf-syntax-ns#")); serd_env_set_prefix_from_strings(_env, USTR("rdfs"), USTR("http://www.w3.org/2000/01/rdf-schema#")); serd_env_set_prefix_from_strings(_env, USTR("xsd"), USTR("http://www.w3.org/2001/XMLSchema#")); // Make a Turtle writer that writes directly to the socket _writer = serd_writer_new( SERD_TURTLE, (SerdStyle)(SERD_STYLE_RESOLVED|SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED), _env, &_base_uri, socket_sink, this); // Write namespace prefixes to reduce traffic serd_env_foreach(_env, write_prefix, this); // Configure sratom to write directly to the writer (and thus the socket) sratom_set_sink(_sratom, (const char*)_base.buf, (SerdStatementSink)serd_writer_write_statement, (SerdEndSink)serd_writer_end_anon, _writer); }
SERD_API SerdNode serd_node_new_file_uri(const uint8_t* path, const uint8_t* hostname, SerdURI* out, bool escape) { const size_t path_len = strlen((const char*)path); const size_t hostname_len = hostname ? strlen((const char*)hostname) : 0; const bool evil = is_windows_path(path); size_t uri_len = 0; uint8_t* uri = NULL; if (path[0] == '/' || is_windows_path(path)) { uri_len = strlen("file://") + hostname_len + evil; uri = (uint8_t*)malloc(uri_len + 1); snprintf((char*)uri, uri_len + 1, "file://%s%s", hostname ? (const char*)hostname : "", evil ? "/" : ""); } SerdChunk chunk = { uri, uri_len }; for (size_t i = 0; i < path_len; ++i) { if (evil && path[i] == '\\') { serd_chunk_sink("/", 1, &chunk); } else if (path[i] == '%') { serd_chunk_sink("%%", 2, &chunk); } else if (!escape || is_uri_path_char(path[i])) { serd_chunk_sink(path + i, 1, &chunk); } else { char escape_str[4] = { '%', 0, 0, 0 }; snprintf(escape_str + 1, sizeof(escape_str) - 1, "%X", path[i]); serd_chunk_sink(escape_str, 3, &chunk); } } serd_chunk_sink_finish(&chunk); if (out) { serd_uri_parse(chunk.buf, out); } return serd_node_from_string(SERD_URI, chunk.buf); }
static SerdWriter* ttl_writer(SerdSink sink, void* stream, const SerdNode* base, SerdEnv** new_env) { SerdURI base_uri = SERD_URI_NULL; if (base && base->buf) { serd_uri_parse(base->buf, &base_uri); } SerdEnv* env = serd_env_new(base); set_prefixes(env); SerdWriter* writer = serd_writer_new( SERD_TURTLE, (SerdStyle)(SERD_STYLE_RESOLVED|SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED), env, &base_uri, sink, stream); *new_env = env; return writer; }
SRATOM_API int sratom_write(Sratom* sratom, LV2_URID_Unmap* unmap, uint32_t flags, const SerdNode* subject, const SerdNode* predicate, uint32_t type_urid, uint32_t size, const void* body) { const char* const type = unmap->unmap(unmap->handle, type_urid); uint8_t idbuf[12] = "b0000000000"; SerdNode id = serd_node_from_string(SERD_BLANK, idbuf); uint8_t nodebuf[12] = "b0000000000"; SerdNode node = serd_node_from_string(SERD_BLANK, nodebuf); SerdNode object = SERD_NODE_NULL; SerdNode datatype = SERD_NODE_NULL; SerdNode language = SERD_NODE_NULL; bool new_node = false; if (type_urid == 0 && size == 0) { object = serd_node_from_string(SERD_URI, USTR(NS_RDF "nil")); } else if (type_urid == sratom->forge.String) { object = serd_node_from_string(SERD_LITERAL, (const uint8_t*)body); } else if (type_urid == sratom->forge.Chunk) { datatype = serd_node_from_string(SERD_URI, NS_XSD "base64Binary"); object = serd_node_new_blob(body, size, true); new_node = true; } else if (type_urid == sratom->forge.Literal) { const LV2_Atom_Literal_Body* lit = (const LV2_Atom_Literal_Body*)body; const uint8_t* str = USTR(lit + 1); object = serd_node_from_string(SERD_LITERAL, str); if (lit->datatype) { datatype = serd_node_from_string( SERD_URI, USTR(unmap->unmap(unmap->handle, lit->datatype))); } else if (lit->lang) { const char* lang = unmap->unmap(unmap->handle, lit->lang); const char* prefix = "http://lexvo.org/id/iso639-3/"; const size_t prefix_len = strlen(prefix); if (lang && !strncmp(lang, prefix, prefix_len)) { language = serd_node_from_string( SERD_LITERAL, USTR(lang + prefix_len)); } else { fprintf(stderr, "Unknown language URID %d\n", lit->lang); } } } else if (type_urid == sratom->forge.URID) { const uint32_t urid = *(const uint32_t*)body; const uint8_t* str = USTR(unmap->unmap(unmap->handle, urid)); object = serd_node_from_string(SERD_URI, str); } else if (type_urid == sratom->forge.Path) { const uint8_t* str = USTR(body); if (path_is_absolute((const char*)str)) { new_node = true; object = serd_node_new_file_uri(str, NULL, NULL, false); } else { SerdURI base_uri = SERD_URI_NULL; if (!sratom->base_uri.buf || strncmp((const char*)sratom->base_uri.buf, "file://", 7)) { fprintf(stderr, "warning: Relative path but base is not a file URI.\n"); fprintf(stderr, "warning: Writing ambiguous atom:Path literal.\n"); object = serd_node_from_string(SERD_LITERAL, str); datatype = serd_node_from_string(SERD_URI, USTR(LV2_ATOM__Path)); } else { if (sratom->base_uri.buf) { serd_uri_parse(sratom->base_uri.buf, &base_uri); } new_node = true; SerdNode rel = serd_node_new_file_uri(str, NULL, NULL, false); object = serd_node_new_uri_from_node(&rel, &base_uri, NULL); serd_node_free(&rel); } } } else if (type_urid == sratom->forge.URI) { const uint8_t* str = USTR(body); object = serd_node_from_string(SERD_URI, str); } else if (type_urid == sratom->forge.Int) { new_node = true; object = serd_node_new_integer(*(const int32_t*)body); datatype = serd_node_from_string(SERD_URI, (sratom->pretty_numbers) ? NS_XSD "integer" : NS_XSD "int"); } else if (type_urid == sratom->forge.Long) { new_node = true; object = serd_node_new_integer(*(const int64_t*)body); datatype = serd_node_from_string(SERD_URI, (sratom->pretty_numbers) ? NS_XSD "integer" : NS_XSD "long"); } else if (type_urid == sratom->forge.Float) { new_node = true; object = serd_node_new_decimal(*(const float*)body, 8); datatype = serd_node_from_string(SERD_URI, (sratom->pretty_numbers) ? NS_XSD "decimal" : NS_XSD "float"); } else if (type_urid == sratom->forge.Double) { new_node = true; object = serd_node_new_decimal(*(const double*)body, 16); datatype = serd_node_from_string(SERD_URI, (sratom->pretty_numbers) ? NS_XSD "decimal" : NS_XSD "double"); } else if (type_urid == sratom->forge.Bool) { const int32_t val = *(const int32_t*)body; datatype = serd_node_from_string(SERD_URI, NS_XSD "boolean"); object = serd_node_from_string(SERD_LITERAL, USTR(val ? "true" : "false")); } else if (type_urid == sratom->midi_MidiEvent) { new_node = true; datatype = serd_node_from_string(SERD_URI, USTR(LV2_MIDI__MidiEvent)); uint8_t* str = (uint8_t*)calloc(size * 2 + 1, 1); for (uint32_t i = 0; i < size; ++i) { snprintf((char*)str + (2 * i), size * 2 + 1, "%02X", (unsigned)(uint8_t)*((const uint8_t*)body + i)); } object = serd_node_from_string(SERD_LITERAL, USTR(str)); } else if (type_urid == sratom->atom_Event) { const LV2_Atom_Event* ev = (const LV2_Atom_Event*)body; gensym(&id, 'e', sratom->next_id++); start_object(sratom, &flags, subject, predicate, &id, NULL); // TODO: beat time SerdNode time = serd_node_new_integer(ev->time.frames); SerdNode p = serd_node_from_string(SERD_URI, USTR(LV2_ATOM__frameTime)); datatype = serd_node_from_string(SERD_URI, NS_XSD "decimal"); sratom->write_statement(sratom->handle, SERD_ANON_CONT, NULL, &id, &p, &time, &datatype, &language); serd_node_free(&time); p = serd_node_from_string(SERD_URI, NS_RDF "value"); sratom_write(sratom, unmap, SERD_ANON_CONT, &id, &p, ev->body.type, ev->body.size, LV2_ATOM_BODY(&ev->body)); if (sratom->end_anon) { sratom->end_anon(sratom->handle, &id); } } else if (type_urid == sratom->forge.Tuple) { gensym(&id, 't', sratom->next_id++); start_object(sratom, &flags, subject, predicate, &id, type); SerdNode p = serd_node_from_string(SERD_URI, NS_RDF "value"); flags |= SERD_LIST_O_BEGIN; LV2_ATOM_TUPLE_BODY_FOREACH(body, size, i) { list_append(sratom, unmap, &flags, &id, &p, &node, i->size, i->type, LV2_ATOM_BODY(i)); }
static bool write_node(SerdWriter* writer, const SerdNode* node, const SerdNode* datatype, const SerdNode* lang, Field field, SerdStatementFlags flags) { SerdChunk uri_prefix; SerdChunk uri_suffix; bool has_scheme; switch (node->type) { 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; write_sep(writer, SEP_ANON_BEGIN); } else if (writer->syntax != SERD_NTRIPLES && (field == FIELD_SUBJECT && (flags & SERD_LIST_S_BEGIN))) { assert(writer->list_depth == 0); copy_node(&writer->list_subj, node); ++writer->list_depth; ++writer->indent; write_sep(writer, SEP_LIST_BEGIN); } else if (writer->syntax != SERD_NTRIPLES && (field == FIELD_OBJECT && (flags & SERD_LIST_O_BEGIN))) { ++writer->indent; ++writer->list_depth; write_sep(writer, SEP_LIST_BEGIN); } else if (writer->syntax != SERD_NTRIPLES && ((field == FIELD_SUBJECT && (flags & SERD_EMPTY_S)) || (field == FIELD_OBJECT && (flags & SERD_EMPTY_O)))) { sink("[]", 2, writer); } else { sink("_:", 2, writer); if (writer->bprefix && !strncmp((const char*)node->buf, (const char*)writer->bprefix, writer->bprefix_len)) { sink(node->buf + writer->bprefix_len, node->n_bytes - writer->bprefix_len, writer); } else { sink(node->buf, node->n_bytes, writer); } } break; case SERD_CURIE: switch (writer->syntax) { case SERD_NTRIPLES: if (serd_env_expand(writer->env, node, &uri_prefix, &uri_suffix)) { w_err(writer, SERD_ERR_BAD_CURIE, "undefined namespace prefix `%s'\n", node->buf); return false; } sink("<", 1, writer); write_uri(writer, uri_prefix.buf, uri_prefix.len); write_uri(writer, uri_suffix.buf, uri_suffix.len); sink(">", 1, writer); break; case SERD_TURTLE: write_lname(writer, node->buf, node->n_bytes); } break; case SERD_LITERAL: if (writer->syntax == SERD_TURTLE && datatype && datatype->buf) { const char* type_uri = (const char*)datatype->buf; if (!strncmp(type_uri, NS_XSD, sizeof(NS_XSD) - 1) && ( !strcmp(type_uri + sizeof(NS_XSD) - 1, "boolean") || !strcmp(type_uri + sizeof(NS_XSD) - 1, "integer"))) { sink(node->buf, node->n_bytes, writer); break; } else if (!strcmp(type_uri + sizeof(NS_XSD) - 1, "decimal") && strchr((const char*)node->buf, '.') && node->buf[node->n_bytes - 1] != '.') { /* xsd:decimal literals without trailing digits, e.g. "5.", can not be written bare in Turtle. We could add a 0 which is prettier, but changes the text and breaks round tripping. */ sink(node->buf, node->n_bytes, writer); break; } } if (writer->syntax != SERD_NTRIPLES && (node->flags & (SERD_HAS_NEWLINE|SERD_HAS_QUOTE))) { sink("\"\"\"", 3, writer); write_text(writer, WRITE_LONG_STRING, node->buf, node->n_bytes); sink("\"\"\"", 3, writer); } else { sink("\"", 1, writer); write_text(writer, WRITE_STRING, node->buf, node->n_bytes); sink("\"", 1, writer); } if (lang && lang->buf) { sink("@", 1, writer); sink(lang->buf, lang->n_bytes, writer); } else if (datatype && datatype->buf) { sink("^^", 2, writer); write_node(writer, datatype, NULL, NULL, FIELD_NONE, flags); } break; case SERD_URI: has_scheme = serd_uri_string_has_scheme(node->buf); if (field == FIELD_PREDICATE && (writer->syntax == SERD_TURTLE) && !strcmp((const char*)node->buf, NS_RDF "type")) { sink("a", 1, writer); break; } else if ((writer->syntax == SERD_TURTLE) && !strcmp((const char*)node->buf, NS_RDF "nil")) { sink("()", 2, writer); break; } else if (has_scheme && (writer->style & SERD_STYLE_CURIED)) { SerdNode prefix; SerdChunk suffix; if (serd_env_qualify(writer->env, node, &prefix, &suffix)) { write_uri(writer, prefix.buf, prefix.n_bytes); sink(":", 1, writer); write_uri(writer, suffix.buf, suffix.len); break; } } sink("<", 1, writer); if (writer->style & SERD_STYLE_RESOLVED) { SerdURI in_base_uri, uri, abs_uri; serd_env_get_base_uri(writer->env, &in_base_uri); serd_uri_parse(node->buf, &uri); serd_uri_resolve(&uri, &in_base_uri, &abs_uri); bool rooted = uri_is_under(&writer->base_uri, &writer->root_uri); SerdURI* root = rooted ? &writer->root_uri : & writer->base_uri; if (!uri_is_under(&abs_uri, root) || writer->syntax == SERD_NTRIPLES) { serd_uri_serialise(&abs_uri, uri_sink, writer); } else { serd_uri_serialise_relative( &uri, &writer->base_uri, root, uri_sink, writer); } } else { write_uri(writer, node->buf, node->n_bytes); } sink(">", 1, writer); default: break; } writer->last_sep = SEP_NONE; return true; }
static bool 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_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, '['); } 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("_:", 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: switch (writer->syntax) { case SERD_NTRIPLES: if (serd_env_expand(writer->env, node, &uri_prefix, &uri_suffix)) { fprintf(stderr, "Undefined namespace prefix `%s'\n", node->buf); return false; } writer->sink("<", 1, writer->stream); write_text(writer, WRITE_URI, uri_prefix.buf, uri_prefix.len, '>'); write_text(writer, WRITE_URI, uri_suffix.buf, uri_suffix.len, '>'); writer->sink(">", 1, writer->stream); break; case SERD_TURTLE: writer->sink(node->buf, node->n_bytes, writer->stream); } break; case SERD_LITERAL: if (writer->syntax == SERD_TURTLE && datatype && datatype->buf) { // TODO: compare against NS_XSD prefix once if (!strcmp((const char*)datatype->buf, NS_XSD "boolean") || !strcmp((const char*)datatype->buf, NS_XSD "decimal") || !strcmp((const char*)datatype->buf, NS_XSD "integer")) { writer->sink(node->buf, node->n_bytes, writer->stream); break; } } if (writer->syntax != SERD_NTRIPLES && ((node->flags & SERD_HAS_NEWLINE) || (node->flags & SERD_HAS_QUOTE))) { writer->sink("\"\"\"", 3, writer->stream); write_text(writer, WRITE_LONG_STRING, node->buf, node->n_bytes, '\0'); writer->sink("\"\"\"", 3, writer->stream); } else { writer->sink("\"", 1, writer->stream); write_text(writer, WRITE_STRING, node->buf, node->n_bytes, '"'); writer->sink("\"", 1, writer->stream); } if (lang && lang->buf) { writer->sink("@", 1, writer->stream); 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, FIELD_NONE, flags); } break; case SERD_URI: if ((writer->syntax == SERD_TURTLE) && !strcmp((const char*)node->buf, NS_RDF "type")) { writer->sink("a", 1, writer->stream); return true; } else if ((writer->style & SERD_STYLE_CURIED) && serd_uri_string_has_scheme(node->buf)) { SerdNode prefix; SerdChunk suffix; if (serd_env_qualify(writer->env, node, &prefix, &suffix)) { write_text(writer, WRITE_URI, prefix.buf, prefix.n_bytes, '>'); writer->sink(":", 1, writer->stream); write_text(writer, WRITE_URI, suffix.buf, suffix.len, '>'); return true; } } else if ((writer->style & SERD_STYLE_RESOLVED) && !serd_uri_string_has_scheme(node->buf)) { SerdURI uri; if (!serd_uri_parse(node->buf, &uri)) { SerdURI abs_uri; serd_uri_resolve(&uri, &writer->base_uri, &abs_uri); writer->sink("<", 1, writer->stream); serd_uri_serialise(&abs_uri, writer->sink, writer->stream); writer->sink(">", 1, writer->stream); return true; } } writer->sink("<", 1, writer->stream); write_text(writer, WRITE_URI, node->buf, node->n_bytes, '>'); writer->sink(">", 1, writer->stream); return true; } return true; }