@reactodia/workspace
Version:
Reactodia Workspace -- library for visual interaction with graphs in a form of a diagram.
161 lines (149 loc) • 4.91 kB
text/typescript
import * as RdfJs from '@rdfjs/types';
import { chainHash, dropHighestNonSignBit, hashString } from '@reactodia/hashmap';
import * as N3 from 'n3';
import { escapeRdfValue } from './rdfEscape';
export type NamedNode<T extends string = string> = RdfJs.NamedNode<T>;
export type BlankNode = RdfJs.BlankNode;
export type Literal = RdfJs.Literal;
export type Variable = RdfJs.Variable;
export type DefaultGraph = RdfJs.DefaultGraph;
export type Quad = RdfJs.Quad;
export type Term = NamedNode | BlankNode | Literal | Variable | DefaultGraph | Quad;
export type DataFactory = RdfJs.DataFactory;
export const DefaultDataFactory: RdfJs.DataFactory = N3.DataFactory;
export function looksLikeTerm(value: unknown): value is Term {
if (!(
typeof value === 'object' && value &&
'termType' in value &&
'equals' in value &&
typeof value.equals === 'function'
)) {
return false;
}
const {termType} = value as Term;
switch (termType) {
case 'NamedNode':
case 'Literal':
case 'BlankNode':
case 'DefaultGraph':
case 'Variable':
case 'Quad':
return true;
default:
return false;
}
}
export function termToString(node: Term): string {
switch (node.termType) {
case 'NamedNode':
return `<${escapeRdfValue(node.value)}>`;
case 'BlankNode':
return `_:${node.value}`;
case 'Literal': {
const { value, language, datatype } = node;
const stringLiteral = `"${escapeRdfValue(value)}"`;
if (language) {
return stringLiteral + `@${language}`;
} else if (datatype) {
return stringLiteral + '^^' + termToString(datatype);
} else {
return stringLiteral;
}
}
case 'DefaultGraph':
return '(default graph)';
case 'Variable':
return `?${node.value}`;
case 'Quad': {
let str = '<< ';
str += termToString(node.subject) + ' ';
str += termToString(node.predicate) + ' ';
str += termToString(node.object) + ' ';
if (node.graph.termType !== 'DefaultGraph') {
str += termToString(node.graph) + ' ';
}
str += '>>';
return str;
}
}
}
export function hashTerm(node: Term): number {
let hash = 0;
switch (node.termType) {
case 'NamedNode':
case 'BlankNode':
hash = hashString(node.value);
break;
case 'Literal':
hash = hashString(node.value);
if (node.datatype) {
hash = chainHash(hash, hashString(node.datatype.value));
}
if (node.language) {
hash = chainHash(hash, hashString(node.language));
}
break;
case 'Variable':
hash = hashString(node.value);
break;
case 'Quad': {
hash = chainHash(hash, hashTerm(node.subject));
hash = chainHash(hash, hashTerm(node.predicate));
hash = chainHash(hash, hashTerm(node.object));
hash = chainHash(hash, hashTerm(node.graph));
break;
}
}
return dropHighestNonSignBit(hash);
}
export function equalTerms(a: Term, b: Term): boolean {
if (a.termType !== b.termType) {
return false;
}
switch (a.termType) {
case 'NamedNode':
case 'BlankNode':
case 'Variable':
case 'DefaultGraph': {
const { value } = b as NamedNode | BlankNode | Variable | DefaultGraph;
return a.value === value;
}
case 'Literal': {
const { value, language, datatype } = b as Literal;
return a.value === value
&& a.datatype.value === datatype.value
&& a.language === language;
}
case 'Quad': {
const { subject, predicate, object, graph } = b as Quad;
return (
equalTerms(a.subject, subject) &&
equalTerms(a.predicate, predicate) &&
equalTerms(a.object, object) &&
equalTerms(a.graph, graph)
);
}
}
}
export function hashQuad(quad: Quad): number {
return hashTerm(quad);
}
export function equalQuads(a: Quad, b: Quad): boolean {
return equalTerms(a, b);
}
/**
* Extracts local name for URI the same way as it's done in [RDF4J](https://github.com/eclipse-rdf4j/rdf4j).
*/
export function getLocalName(uri: string): string | undefined {
let index = uri.indexOf('#');
if (index < 0) {
index = uri.lastIndexOf('/');
}
if (index < 0) {
index = uri.lastIndexOf(':');
}
if (index < 0) {
return undefined;
}
return uri.substring(index + 1);
}