UNPKG

rdf-string

Version:

Convenience functions for creating and serializing RDF terms and quads

219 lines 9.56 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.termToString = termToString; exports.getLiteralValue = getLiteralValue; exports.getLiteralType = getLiteralType; exports.getLiteralLanguage = getLiteralLanguage; exports.getLiteralDirection = getLiteralDirection; exports.stringToTerm = stringToTerm; exports.quadToStringQuad = quadToStringQuad; exports.stringQuadToQuad = stringQuadToQuad; const rdf_data_factory_1 = require("rdf-data-factory"); const FACTORY = new rdf_data_factory_1.DataFactory(); /** * Utility methods for converting between string-based RDF representations and RDFJS objects. * * RDF Terms are represented as follows: * * Blank nodes: '_:myBlankNode' * * Variables: '?myVariable' * * Literals: '"myString"', '"myLanguageString"@en-us', '"myLanguageString"@en-us--ltr', '"3"^^xsd:number' * * URIs: 'http://example.org' * * Quads/triples are represented as hashes with 'subject', 'predicate', 'object' and 'graph' (optional) * as keys, and string-based RDF terms as values. */ /** * Convert an RDFJS term to a string-based representation. * @param {RDF.Term} term An RDFJS term. * @return {string} A string-based term representation. */ function termToString(term) { // TODO: remove nasty any casts when this TS bug has been fixed: https://github.com/microsoft/TypeScript/issues/26933 if (!term) { return undefined; } switch (term.termType) { case 'NamedNode': return term.value; case 'BlankNode': return ('_:' + term.value); case 'Literal': const literalValue = term; return ('"' + literalValue.value + '"' + (literalValue.datatype && literalValue.datatype.value !== 'http://www.w3.org/2001/XMLSchema#string' && literalValue.datatype.value !== 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' && literalValue.datatype.value !== 'http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString' ? '^^' + literalValue.datatype.value : '') + (literalValue.language ? '@' + literalValue.language : '') + (literalValue.direction ? '--' + literalValue.direction : '')); case 'Quad': return `<<${termToString(term.subject)} ${termToString(term.predicate)} ${termToString(term.object)}${term.graph.termType === 'DefaultGraph' ? '' : ' ' + termToString(term.graph)}>>`; case 'Variable': return ('?' + term.value); case 'DefaultGraph': return term.value; } } /** * Get the string value of a literal. * @param {string} literalValue An RDF literal enclosed by '"'. * @return {string} The literal value inside the '"'. */ function getLiteralValue(literalValue) { const match = /^"([^]*)"/.exec(literalValue); if (!match) { throw new Error(literalValue + ' is not a literal'); } return match[1]; } /** * Get the datatype of the given literal. * @param {string} literalValue An RDF literal. * @return {string} The datatype of the literal. */ function getLiteralType(literalValue) { const match = /^"[^]*"(?:\^\^([^"]+)|(@)[^@"]+)?$/.exec(literalValue); if (!match) { throw new Error(literalValue + ' is not a literal'); } return match[1] || (match[2] ? 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' : 'http://www.w3.org/2001/XMLSchema#string'); } /** * Get the language of the given literal. * @param {string} literalValue An RDF literal. * @return {string} The language of the literal. */ function getLiteralLanguage(literalValue) { const match = /^"[^]*"(?:@([^@"]+)|\^\^[^"]+)?$/.exec(literalValue); if (!match) { throw new Error(literalValue + ' is not a literal'); } if (match[1]) { let ret = match[1].toLowerCase(); // Remove everything after --, since this indicates the base direction, which will be parsed later. const doubleDashPos = ret.indexOf('--'); if (doubleDashPos >= 0) { ret = ret.slice(0, doubleDashPos); } return ret; } return ''; } /** * Get the direction of the given literal. * @param {string} literalValue An RDF literal. * @return {string} The direction of the literal. */ function getLiteralDirection(literalValue) { const doubleDashPos = literalValue.indexOf('--', literalValue.lastIndexOf('"')); if (doubleDashPos >= 0) { const direction = literalValue.slice(doubleDashPos + 2, literalValue.length); if (direction === 'ltr' || direction === 'rtl') { return direction; } throw new Error(literalValue + ' is not a literal with a valid direction'); } return ''; } /** * Transform a string-based RDF term to an RDFJS term. * @param {string} value A string-based RDF-term. * @param {RDF.DataFactory} dataFactory An optional datafactory to create terms with. * @return {RDF.Term} An RDF-JS term. */ function stringToTerm(value, dataFactory) { dataFactory = dataFactory || FACTORY; if (!value || !value.length) { return dataFactory.defaultGraph(); } switch (value[0]) { case '_': return dataFactory.blankNode(value.substr(2)); case '?': if (!dataFactory.variable) { throw new Error(`Missing 'variable()' method on the given DataFactory`); } return dataFactory.variable(value.substr(1)); case '"': const language = getLiteralLanguage(value); const direction = getLiteralDirection(value); const type = dataFactory.namedNode(getLiteralType(value)); return dataFactory.literal(getLiteralValue(value), language ? { language, direction } : type); case '<': default: if (value[0] === '<' && value.length > 4 && value[1] === '<' && value[value.length - 1] === '>' && value[value.length - 2] === '>') { // Iterate character-by-character to detect spaces that are *not* wrapped in <<>> const terms = value.slice(2, -2).trim(); let stringTerms = []; let ignoreTags = 0; let lastIndex = 0; let inQuote = false; for (let i = 0; i < terms.length; i++) { const char = terms[i]; if (char === '<') ignoreTags++; if (char === '>') { if (ignoreTags === 0) { throw new Error('Found closing tag without opening tag in ' + value); } else { ignoreTags--; } } if (char === '"') { let escaped = false; let j = i; while (j-- > 0 && terms[j] === '\\') { escaped = !escaped; } if (!escaped) { // We have reached an unescaped quote inQuote = !inQuote; } } if (char === ' ' && !inQuote && ignoreTags === 0) { stringTerms.push(terms.slice(lastIndex, i)); while (terms[i + 1] === ' ') { i += 1; } lastIndex = i + 1; } } if (ignoreTags !== 0) { throw new Error('Found opening tag without closing tag in ' + value); } stringTerms.push(terms.slice(lastIndex, terms.length)); // We require 3 or 4 components if (stringTerms.length !== 3 && stringTerms.length !== 4) { throw new Error('Nested quad syntax error ' + value); } stringTerms = stringTerms.map(term => term.startsWith('<') && !term.includes(' ') ? term.slice(1, -1) : term); return dataFactory.quad(stringToTerm(stringTerms[0]), stringToTerm(stringTerms[1]), stringToTerm(stringTerms[2]), stringTerms[3] ? stringToTerm(stringTerms[3]) : undefined); } return dataFactory.namedNode(value); } } /** * Convert an RDFJS quad to a string-based quad representation. * @param {Quad} q An RDFJS quad. * @return {IStringQuad} A hash with string-based quad terms. * @template Q The type of quad, defaults to RDF.Quad. */ function quadToStringQuad(q) { // tslint:disable:object-literal-sort-keys return { subject: termToString(q.subject), predicate: termToString(q.predicate), object: termToString(q.object), graph: termToString(q.graph), }; // tslint:enable:object-literal-sort-keys } /** * Convert a string-based quad representation to an RDFJS quad. * @param {IStringQuad} stringQuad A hash with string-based quad terms. * @param {RDF.DataFactory} dataFactory An optional datafactory to create terms with. * @return {Q} An RDFJS quad. * @template Q The type of quad, defaults to RDF.Quad. */ function stringQuadToQuad(stringQuad, dataFactory) { dataFactory = dataFactory || FACTORY; return dataFactory.quad(stringToTerm(stringQuad.subject, dataFactory), stringToTerm(stringQuad.predicate, dataFactory), stringToTerm(stringQuad.object, dataFactory), stringToTerm(stringQuad.graph, dataFactory)); } //# sourceMappingURL=TermUtil.js.map