@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
188 lines • 7.51 kB
JavaScript
;
/**
* As other project partners want the produced data structures as rdf quads, this module provides
* serialization capabilities for this purpose.
* <p>
* At the time of writing this module I am unaware of any sophisticated rdf library for typescript which allows to serialize objects
* directly as rdf quads. Therefore, this module provides a simple serialization mechanism based on the popular n3.js library.
*
* @module
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultQuadSerializationConfiguration = void 0;
exports.defaultQuadIdGenerator = defaultQuadIdGenerator;
exports.defaultQuadIgnoreIf = defaultQuadIgnoreIf;
exports.serialize2quads = serialize2quads;
exports.graph2quads = graph2quads;
const n3_1 = require("n3");
const namedNode = (v) => n3_1.DataFactory.namedNode(v);
const quad = (s, p, o, g) => n3_1.DataFactory.quad(s, p, o, g);
const objects_1 = require("./objects");
const assert_1 = require("./assert");
const defaultmap_1 = require("./defaultmap");
const literal = (v, n) => n3_1.DataFactory.literal(v, n);
const log_1 = require("./log");
const domain = 'https://uni-ulm.de/r-ast/';
/**
* A deterministic counting id generator for quads.
*/
function defaultQuadIdGenerator() {
let counter = 0;
const idMap = new defaultmap_1.DefaultMap(() => counter++);
return (elem, context) => `${context}/${idMap.get(elem)}`;
}
const ignoredKeysArray = ['complexNumber', 'markedAsInt', 'info'];
function defaultQuadIgnoreIf() {
return (key, value) => value === undefined || ignoredKeysArray.includes(key);
}
exports.DefaultQuadSerializationConfiguration = {
ignore: defaultQuadIgnoreIf(),
context: 'unknown-context',
getId: defaultQuadIdGenerator(),
domain: 'https://uni-ulm.de/r-ast/'
};
function retrieveContext(context, obj) {
return typeof context === 'string' ? context : context(obj);
}
const writer = new n3_1.Writer({ format: 'N-Quads' });
/**
* Serializes the given object or array to rdf quads.
*
* @param obj - The object to serialize (must be a Record and no array etc.)
* @param config - Further configuration options
*
* @returns the serialized quads
*
* @see graph2quads
*/
function serialize2quads(obj, config) {
const useConfig = (0, objects_1.deepMergeObject)(exports.DefaultQuadSerializationConfiguration, config);
(0, assert_1.guard)((0, objects_1.isObjectOrArray)(obj), 'cannot serialize non-object to rdf!');
(0, assert_1.guard)(!Array.isArray(obj), 'cannot serialize arrays (must wrap in object)!');
store = new Set();
const quads = [];
serializeObject(obj, quads, useConfig);
return writer.quadsToString(quads);
}
/**
* Serializes the given directed graph to rdf quads.
* This is a mere (type-)convenience wrapper for {@link serialize2quads}.
*
* @see serialize2quads
*/
function graph2quads(graph, config) {
return serialize2quads(graph, config);
}
function processArrayEntries(key, values, obj, quads, config) {
for (const [index, element] of values.entries()) {
if (element !== null && element !== undefined && (0, objects_1.isObjectOrArray)(element)) {
const context = retrieveContext(config.context, obj);
quads.push(quad(namedNode(domain + config.getId(obj, context)), namedNode(domain + key), namedNode(domain + config.getId(element, context)), namedNode(context)));
// we now add a next link to the next vertex
const next = values[index + 1];
if (next !== undefined) {
const nextId = config.getId(next, context);
quads.push(quad(namedNode(domain + config.getId(element, context)), namedNode(domain + 'next'), namedNode(domain + nextId), namedNode(context)));
}
serializeObject(element, quads, config);
}
else {
// for the time being, the index does not seem of interest for the graph summary team.
processLiteralEntry(element, key, obj, quads, config, undefined);
}
}
}
function processObjectEntries(key, value, obj, quads, config) {
const context = retrieveContext(config.context, obj);
quads.push(quad(namedNode(domain + config.getId(obj, context)), namedNode(domain + key), namedNode(domain + config.getId(value, context)), namedNode(context)));
serializeObject(value, quads, config);
}
function objToType(value) {
let suffix;
switch (typeof value) {
case 'string':
suffix = 'string';
break;
case 'number':
suffix = Number.isInteger(value) ? 'integer' : 'decimal';
break;
case 'boolean':
suffix = 'boolean';
break;
case 'bigint':
suffix = 'integer';
break;
default:
log_1.log.warn(`unknown ${typeof value} with ${JSON.stringify(value)}`);
break;
}
return suffix ? namedNode(`http://www.w3.org/2001/XMLSchema#${suffix}`) : undefined;
}
function processLiteralEntry(value, key, obj, quads, config, index) {
const context = retrieveContext(config.context, obj);
// we have to create an object if there is an index, so that we can link the index!
if (index !== undefined) {
const newId = `${key}-${index}`;
quads.push(quad(namedNode(domain + config.getId(obj, context)), namedNode(domain + key), namedNode(domain + config.getId(newId, context)), namedNode(context)));
quads.push(quad(namedNode(domain + config.getId(newId, context)), namedNode(domain + 'value'), literal(String(value), objToType(value)), namedNode(context)));
quads.push(quad(namedNode(domain + newId), namedNode(domain + 'order'), literal(String(index), namedNode('http://www.w3.org/2001/XMLSchema#integer')), namedNode(context)));
}
else {
quads.push(quad(namedNode(domain + config.getId(obj, context)), namedNode(domain + key), literal(String(value), objToType(value)), namedNode(context)));
}
}
function processObjectEntry(key, value, obj, quads, config) {
if (config.ignore(key, value)) {
return;
}
if (guardCycle(value)) {
return;
}
if ((0, objects_1.isObjectOrArray)(value)) {
if (Array.isArray(value)) {
processArrayEntries(key, value, obj, quads, config);
}
else {
processObjectEntries(key, value, obj, quads, config);
}
}
else {
processLiteralEntry(value, key, obj, quads, config, undefined);
}
}
let store = new Set();
function guardCycle(obj) {
// @ts-expect-error we do not care about the type here
if ((0, objects_1.isObjectOrArray)(obj) && 'id' in obj) {
if (store.has(obj.id)) {
return true;
}
store.add(obj.id);
}
return false;
}
function serializeObject(obj, quads, config) {
if (obj === null || obj === undefined) {
return;
}
if (guardCycle(obj)) {
return;
}
if (obj instanceof Map) {
for (const [key, value] of obj.entries()) {
processObjectEntry('key-' + String(key), value, obj, quads, config);
}
}
else if (obj instanceof Set) {
let i = 0;
for (const value of obj.values()) {
processObjectEntry('idx-' + String(i++), value, obj, quads, config);
}
}
else {
for (const [key, value] of Object.entries(obj)) {
processObjectEntry(key, value, obj, quads, config);
}
}
}
//# sourceMappingURL=quads.js.map