UNPKG

jsonld-streaming-parser

Version:
348 lines 15.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ParsingContext = void 0; const jsonld_context_parser_1 = require("jsonld-context-parser"); const ErrorCoded_1 = require("jsonld-context-parser/lib/ErrorCoded"); const ContextTree_1 = require("./ContextTree"); const JsonLdParser_1 = require("./JsonLdParser"); /** * Data holder for parsing information. */ class ParsingContext { constructor(options) { // Initialize settings this.contextParser = new jsonld_context_parser_1.ContextParser({ documentLoader: options.documentLoader, skipValidation: options.skipContextValidation }); this.streamingProfile = !!options.streamingProfile; this.baseIRI = options.baseIRI; this.produceGeneralizedRdf = !!options.produceGeneralizedRdf; this.allowSubjectList = !!options.allowSubjectList; this.processingMode = options.processingMode || JsonLdParser_1.JsonLdParser.DEFAULT_PROCESSING_MODE; this.strictValues = !!options.strictValues; this.validateValueIndexes = !!options.validateValueIndexes; this.defaultGraph = options.defaultGraph; this.rdfDirection = options.rdfDirection; this.normalizeLanguageTags = options.normalizeLanguageTags; this.streamingProfileAllowOutOfOrderPlainType = options.streamingProfileAllowOutOfOrderPlainType; this.rdfstar = options.rdfstar !== false; this.rdfstarReverseInEmbedded = options.rdfstarReverseInEmbedded; this.topLevelProperties = false; this.activeProcessingMode = parseFloat(this.processingMode); // Initialize stacks this.processingStack = []; this.processingType = []; this.emittedStack = []; this.idStack = []; this.graphStack = []; this.graphContainerTermStack = []; this.listPointerStack = []; this.contextTree = new ContextTree_1.ContextTree(); this.literalStack = []; this.validationStack = []; this.unaliasedKeywordCacheStack = []; this.jsonLiteralStack = []; this.unidentifiedValuesBuffer = []; this.unidentifiedGraphsBuffer = []; this.annotationsBuffer = []; this.pendingContainerFlushBuffers = []; this.parser = options.parser; if (options.context) { this.rootContext = this.parseContext(options.context); this.rootContext.then((context) => this.validateContext(context)); } else { this.rootContext = Promise.resolve(new jsonld_context_parser_1.JsonLdContextNormalized(this.baseIRI ? { '@base': this.baseIRI, '@__baseDocument': true } : {})); } } /** * Parse the given context with the configured options. * @param {JsonLdContext} context A context to parse. * @param {JsonLdContextNormalized} parentContext An optional parent context. * @param {boolean} ignoreProtection If @protected term checks should be ignored. * @return {Promise<JsonLdContextNormalized>} A promise resolving to the parsed context. */ async parseContext(context, parentContext, ignoreProtection) { return this.contextParser.parse(context, { baseIRI: this.baseIRI, ignoreProtection, normalizeLanguageTags: this.normalizeLanguageTags, parentContext, processingMode: this.activeProcessingMode, }); } /** * Check if the given context is valid. * If not, an error will be thrown. * @param {JsonLdContextNormalized} context A context. */ validateContext(context) { const activeVersion = context.getContextRaw()['@version']; if (activeVersion) { if (this.activeProcessingMode && activeVersion > this.activeProcessingMode) { throw new ErrorCoded_1.ErrorCoded(`Unsupported JSON-LD version '${activeVersion}' under active processing mode ${this.activeProcessingMode}.`, ErrorCoded_1.ERROR_CODES.PROCESSING_MODE_CONFLICT); } else { if (this.activeProcessingMode && activeVersion < this.activeProcessingMode) { throw new ErrorCoded_1.ErrorCoded(`Invalid JSON-LD version ${activeVersion} under active processing mode ${this.activeProcessingMode}.`, ErrorCoded_1.ERROR_CODES.INVALID_VERSION_VALUE); } this.activeProcessingMode = activeVersion; } } } /** * Get the context at the given path. * @param {keys} keys The path of keys to get the context at. * @param {number} offset The path offset, defaults to 1. * @return {Promise<JsonLdContextNormalized>} A promise resolving to a context. */ async getContext(keys, offset = 1) { const keysOriginal = keys; // Ignore array keys at the end while (typeof keys[keys.length - 1] === 'number') { keys = keys.slice(0, keys.length - 1); } // Handle offset on keys if (offset) { keys = keys.slice(0, -offset); } // Determine the closest context const contextData = await this.getContextPropagationAware(keys); const context = contextData.context; // Process property-scoped contexts (high-to-low) let contextRaw = context.getContextRaw(); for (let i = contextData.depth; i < keysOriginal.length - offset; i++) { const key = keysOriginal[i]; const contextKeyEntry = contextRaw[key]; if (contextKeyEntry && typeof contextKeyEntry === 'object' && '@context' in contextKeyEntry) { const scopedContext = (await this.parseContext(contextKeyEntry, contextRaw, true)).getContextRaw(); const propagate = !(key in scopedContext) || scopedContext[key]['@context']['@propagate']; // Propagation is true by default if (propagate !== false || i === keysOriginal.length - 1 - offset) { contextRaw = Object.assign({}, scopedContext); // Clean up final context delete contextRaw['@propagate']; contextRaw[key] = Object.assign({}, contextRaw[key]); if ('@id' in contextKeyEntry) { contextRaw[key]['@id'] = contextKeyEntry['@id']; } delete contextRaw[key]['@context']; if (propagate !== false) { this.contextTree.setContext(keysOriginal.slice(0, i + offset), Promise.resolve(new jsonld_context_parser_1.JsonLdContextNormalized(contextRaw))); } } } } return new jsonld_context_parser_1.JsonLdContextNormalized(contextRaw); } /** * Get the context at the given path. * Non-propagating contexts will be skipped, * unless the context at that exact depth is retrieved. * * This ONLY takes into account context propagation logic, * so this should usually not be called directly, * call {@link #getContext} instead. * * @param keys The path of keys to get the context at. * @return {Promise<{ context: JsonLdContextNormalized, depth: number }>} A context and its depth. */ async getContextPropagationAware(keys) { const originalDepth = keys.length; let contextData = null; let hasApplicablePropertyScopedContext; do { hasApplicablePropertyScopedContext = false; if (contextData && '@__propagateFallback' in contextData.context.getContextRaw()) { // If a propagation fallback context has been set, // fallback to that context and retry for the same depth. contextData.context = new jsonld_context_parser_1.JsonLdContextNormalized(contextData.context.getContextRaw()['@__propagateFallback']); } else { if (contextData) { // If we had a previous iteration, jump to the parent of context depth. // We must do this because once we get here, last context had propagation disabled, // so we check its first parent instead. keys = keys.slice(0, contextData.depth - 1); } contextData = await this.contextTree.getContext(keys) || { context: await this.rootContext, depth: 0 }; } // Allow non-propagating contexts to propagate one level deeper // if it defines a property-scoped context that is applicable for the current key. // @see https://w3c.github.io/json-ld-api/tests/toRdf-manifest#tc012 const lastKey = keys[keys.length - 1]; if (lastKey in contextData.context.getContextRaw()) { const lastKeyValue = contextData.context.getContextRaw()[lastKey]; if (lastKeyValue && typeof lastKeyValue === 'object' && '@context' in lastKeyValue) { hasApplicablePropertyScopedContext = true; } } } while (contextData.depth > 0 // Root context has a special case && contextData.context.getContextRaw()['@propagate'] === false // Stop loop if propagation is true && contextData.depth !== originalDepth // Stop loop if requesting exact depth of non-propagating && !hasApplicablePropertyScopedContext); // Special case for root context that does not allow propagation. // Fallback to empty context in that case. if (contextData.depth === 0 && contextData.context.getContextRaw()['@propagate'] === false && contextData.depth !== originalDepth) { contextData.context = new jsonld_context_parser_1.JsonLdContextNormalized({}); } return contextData; } /** * Start a new job for parsing the given value. * @param {any[]} keys The stack of keys. * @param value The value to parse. * @param {number} depth The depth to parse at. * @param {boolean} lastDepthCheck If the lastDepth check should be done for buffer draining. * @return {Promise<void>} A promise resolving when the job is done. */ async newOnValueJob(keys, value, depth, lastDepthCheck) { await this.parser.newOnValueJob(keys, value, depth, lastDepthCheck); } /** * Flush the pending container flush buffers * @return {boolean} If any pending buffers were flushed. */ async handlePendingContainerFlushBuffers() { if (this.pendingContainerFlushBuffers.length > 0) { for (const pendingFlushBuffer of this.pendingContainerFlushBuffers) { await this.parser.flushBuffer(pendingFlushBuffer.depth, pendingFlushBuffer.keys); this.parser.flushStacks(pendingFlushBuffer.depth); } this.pendingContainerFlushBuffers.splice(0, this.pendingContainerFlushBuffers.length); return true; } else { return false; } } /** * Emit the given quad into the output stream. * @param {number} depth The depth the quad was generated at. * @param {Quad} quad A quad to emit. */ emitQuad(depth, quad) { if (depth === 1) { this.topLevelProperties = true; } this.parser.push(quad); } /** * Emit the given error into the output stream. * @param {Error} error An error to emit. */ emitError(error) { this.parser.emit('error', error); } /** * Emit the given context into the output stream under the 'context' event. * @param {JsonLdContext} context A context to emit. */ emitContext(context) { this.parser.emit('context', context); } /** * Safely get or create the depth value of {@link ParsingContext.unidentifiedValuesBuffer}. * @param {number} depth A depth. * @return {{predicate: Term; object: Term; reverse: boolean}[]} An element of * {@link ParsingContext.unidentifiedValuesBuffer}. */ getUnidentifiedValueBufferSafe(depth) { let buffer = this.unidentifiedValuesBuffer[depth]; if (!buffer) { buffer = []; this.unidentifiedValuesBuffer[depth] = buffer; } return buffer; } /** * Safely get or create the depth value of {@link ParsingContext.unidentifiedGraphsBuffer}. * @param {number} depth A depth. * @return {{predicate: Term; object: Term; reverse: boolean}[]} An element of * {@link ParsingContext.unidentifiedGraphsBuffer}. */ getUnidentifiedGraphBufferSafe(depth) { let buffer = this.unidentifiedGraphsBuffer[depth]; if (!buffer) { buffer = []; this.unidentifiedGraphsBuffer[depth] = buffer; } return buffer; } /** * Safely get or create the depth value of {@link ParsingContext.annotationsBuffer}. * @param {number} depth A depth. * @return {} An element of {@link ParsingContext.annotationsBuffer}. */ getAnnotationsBufferSafe(depth) { let buffer = this.annotationsBuffer[depth]; if (!buffer) { buffer = []; this.annotationsBuffer[depth] = buffer; } return buffer; } /** * @return IExpandOptions The expand options for the active processing mode. */ getExpandOptions() { return ParsingContext.EXPAND_OPTIONS[this.activeProcessingMode]; } /** * Shift the stack at the given offset to the given depth. * * This will override anything in the stack at `depth`, * and this will remove anything at `depth + depthOffset` * * @param depth The target depth. * @param depthOffset The origin depth, relative to `depth`. */ shiftStack(depth, depthOffset) { // Copy the id stack value up one level so that the next job can access the id. const deeperIdStack = this.idStack[depth + depthOffset]; if (deeperIdStack) { this.idStack[depth] = deeperIdStack; this.emittedStack[depth] = true; delete this.idStack[depth + depthOffset]; } // Shorten key stack if (this.pendingContainerFlushBuffers.length) { for (const buffer of this.pendingContainerFlushBuffers) { if (buffer.depth >= depth + depthOffset) { buffer.depth -= depthOffset; buffer.keys.splice(depth, depthOffset); } } } // Splice stacks if (this.unidentifiedValuesBuffer[depth + depthOffset]) { this.unidentifiedValuesBuffer[depth] = this.unidentifiedValuesBuffer[depth + depthOffset]; delete this.unidentifiedValuesBuffer[depth + depthOffset]; } if (this.annotationsBuffer[depth + depthOffset - 1]) { if (!this.annotationsBuffer[depth - 1]) { this.annotationsBuffer[depth - 1] = []; } this.annotationsBuffer[depth - 1] = [ ...this.annotationsBuffer[depth - 1], ...this.annotationsBuffer[depth + depthOffset - 1], ]; delete this.annotationsBuffer[depth + depthOffset - 1]; } // TODO: also do the same for other stacks } } exports.ParsingContext = ParsingContext; ParsingContext.EXPAND_OPTIONS = { 1.0: { allowPrefixForcing: false, allowPrefixNonGenDelims: false, allowVocabRelativeToBase: false, }, 1.1: { allowPrefixForcing: true, allowPrefixNonGenDelims: false, allowVocabRelativeToBase: true, }, }; //# sourceMappingURL=ParsingContext.js.map