UNPKG

@tpluscode/rdfine

Version:
211 lines (210 loc) 7.47 kB
import { enumerateList, isList } from './rdf-list.js'; import { mixins } from './mixins.js'; function objectCanBeJsonified(term) { switch (term.termType) { case 'NamedNode': case 'BlankNode': case 'Literal': return true; default: return false; } } function getObjectMap(resource) { const graph = resource.pointer._context[0].graph; return [...resource.pointer.dataset.match(resource.id, null, null, graph)].reduce((map, quad) => { if (resource.env.ns.rdf.type.equals(quad.predicate)) { return map; } const quads = map.get(quad.predicate) || []; if (objectCanBeJsonified(quad.object)) { quads.push(resource.pointer.node(quad.object)); } map.set(quad.predicate, quads); return map; }, resource.env.termMap()); } function alreadyMapped(parentContext, name, prop) { if (parentContext && name in parentContext) { return parentContext[name] === prop.value; } return false; } function literalToJSON(obj, env) { if (env.ns.xsd.string.equals(obj.datatype)) { return obj.value; } if (env.ns.xsd.integer.equals(obj.datatype)) { const int = Number.parseInt(obj.value); if (int.toString() === obj.value) { return int; } } if (env.ns.xsd.double.equals(obj.datatype)) { const dbl = Number.parseFloat(obj.value); if (dbl.toString() === obj.value) { return dbl; } } if (env.ns.xsd.boolean.equals(obj.datatype)) { if (obj.value === 'true') { return true; } if (obj.value === 'false') { return false; } } if (env.ns.rdf.langString.equals(obj.datatype)) { return { '@value': obj.value, '@language': obj.language, }; } if (!obj.datatype) { return obj.value; } return { '@value': obj.value, '@type': obj.datatype.value, }; } function jsonifyQuads(resource, context) { return (json, [predicate, objects]) => { const jsonifyObject = (pointer) => { if (pointer.term.termType === 'Literal') { return literalToJSON(pointer.term, resource.env); } const list = pointer.list(); if (list) { return [...list].map(jsonifyObject); } return toJSON(resource._create(resource.pointer.node(pointer.term)), context); }; const mapped = objects.map(jsonifyObject); if (mapped.length === 1) { json[predicate.value] = mapped[0]; } else { json[predicate.value] = mapped; } return json; }; } function jsonifyProperties(params) { const { parentContexts, visitedResources, remainingObjects, context, resource, namespace } = params; return ({ json, contextPopulated = false }, [name, { options }]) => { const path = options.path ? options.path : namespace ? namespace(name) : null; if (!path || Array.isArray(path) || typeof path === 'function' || options.subjectFromAllGraphs) { return { json, contextPopulated }; } let propertyAddedToContext = false; const predicate = typeof path === 'string' ? params.resource.env.namedNode(path) : path; const terms = remainingObjects.get(predicate); if (!terms) { return { json, contextPopulated }; } const objectPointers = resource.pointer.out(predicate); if (!alreadyMapped(parentContexts, name, predicate)) { if (isList(objectPointers)) { context[name] = { '@container': '@list', '@id': predicate.value, }; } else { context[name] = predicate.value; } propertyAddedToContext = true; } function fromTerm(pointer) { switch (pointer.term.termType) { case 'BlankNode': case 'NamedNode': return options.fromTerm.call(resource, pointer); default: return pointer.term; } } const propertyObjects = objectPointers .map(obj => { if (obj.term.termType === 'Literal') { return obj.term; } if (isList(obj)) { return enumerateList(resource, obj, fromTerm); } return fromTerm(obj); }); const jsonValues = propertyObjects.map(function valueToJSON(obj) { if (Array.isArray(obj)) { return obj.map(arrElement => valueToJSON(arrElement)); } if ('termType' in obj) { switch (obj.termType) { case 'Literal': return literalToJSON(obj, resource.env); case 'BlankNode': case 'NamedNode': return toJSON(resource._create(resource.pointer.node(obj)), { parentContexts: { ...parentContexts, ...context }, visitedResources, }); default: return null; } } return toJSON(obj, { parentContexts: { ...parentContexts, ...context }, visitedResources, }); }); if (options.values.includes('array') && jsonValues.length !== 1) { json[name] = jsonValues; } else if (options.values.includes('list') && Array.isArray(jsonValues[0])) { json[name] = jsonValues[0] || []; } else if (jsonValues.length > 1) { json[name] = jsonValues; } else { json[name] = options.values.includes('single') ? jsonValues[0] : jsonValues; } remainingObjects.delete(predicate); return { json, contextPopulated: contextPopulated || propertyAddedToContext }; }; } export function toJSON(resource, { parentContexts, visitedResources = resource.env.termSet() } = {}) { const id = resource.id.termType === 'NamedNode' ? resource.id.value : `_:${resource.id.value}`; const json = { id }; const types = [...resource.types]; if (types.length > 0) { json.type = types.map(type => type.id.value); } if (visitedResources.has(resource.id)) { return json; } visitedResources.add(resource.id); const remainingObjects = getObjectMap(resource); let contextEmpty = true; let context; if (parentContexts) { context = {}; } else { contextEmpty = false; context = { id: '@id', type: '@type', }; } let contextPopulated = false; for (const { __properties: properties, __ns: namespace } of mixins(resource)) { ({ contextPopulated } = [...properties].reduce(jsonifyProperties({ parentContexts, visitedResources, resource, remainingObjects, context, namespace }), { json, contextPopulated })); } [...remainingObjects].reduce(jsonifyQuads(resource, { parentContexts: { ...parentContexts, ...context }, visitedResources }), json); if (!contextEmpty || contextPopulated) { json['@context'] = context; } return json; }