diff --git a/lib/Compressor.js b/lib/Compressor.js index 6b9bfc81..73c6bc47 100644 --- a/lib/Compressor.js +++ b/lib/Compressor.js @@ -100,15 +100,15 @@ export class Compressor { // encode `@context`... const {transformer} = this; - const entries = []; + const encodedContexts = []; const isArray = Array.isArray(context); const contexts = isArray ? context : [context]; for(const value of contexts) { const encoder = ContextEncoder.createEncoder({value, transformer}); - entries.push(encoder || value); + encodedContexts.push(encoder || value); } const id = isArray ? CONTEXT_TERM_ID_PLURAL : CONTEXT_TERM_ID; - transformMap.set(id, isArray ? entries : entries[0]); + transformMap.set(id, isArray ? encodedContexts : encodedContexts[0]); return activeCtx; } @@ -129,8 +129,8 @@ export class Compressor { return new Map(); } - _addNewOutputs({outputs, termInfo, output: transformMap}) { - transformMap.set(termInfo.termId, outputs); + _addOutputEntry({termInfo, values, output: transformMap}) { + transformMap.set(termInfo.termId, values); } } diff --git a/lib/Decompressor.js b/lib/Decompressor.js index 413a7678..9a189a52 100644 --- a/lib/Decompressor.js +++ b/lib/Decompressor.js @@ -133,14 +133,14 @@ export class Decompressor { 'ERR_INVALID_ENCODED_CONTEXT', 'Encoded plural context value must be an array.'); } - const entries = []; + const contexts = []; for(const value of encodedContexts) { const decoder = ContextDecoder.createDecoder({ value, transformer }); - entries.push(decoder ? decoder.decode({value}) : value); + contexts.push(decoder ? decoder.decode({value}) : value); } - obj['@context'] = entries; + obj['@context'] = contexts; } return activeCtx.applyEmbeddedContexts({obj}); @@ -164,8 +164,8 @@ export class Decompressor { return {}; } - _addNewOutputs({outputs, termInfo, output: obj}) { - obj[termInfo.term] = outputs; + _addOutputEntry({termInfo, values, output: obj}) { + obj[termInfo.term] = values; } } diff --git a/lib/Transformer.js b/lib/Transformer.js index 45a5b5dd..1da5b4a5 100644 --- a/lib/Transformer.js +++ b/lib/Transformer.js @@ -48,54 +48,31 @@ export class Transformer { } } - async convert({input} = {}) { - // handle single or multiple inputs - const isArray = Array.isArray(input); - const inputs = isArray ? input : [input]; - const outputs = []; - for(const input of inputs) { - const output = this.strategy._createNewOutput(); - await this._transform({input, output}); - outputs.push(output); - } - return isArray ? outputs : outputs[0]; - } - /** * CBOR-LD is a semantic compression format; it uses the contextual * information provided by JSON-LD contexts to compress JSON-LD objects * to CBOR-LD (and vice versa). * - * The purpose of this internal transform function is to either enable - * transformation of a JSON-LD object to CBOR-LD (compression) or vice versa - * (decompression). It does this by: + * This `convert()` function is used to either convert from JSON-LD to + * CBOR-LD (compression) or vice versa (decompression). The type of + * conversion (compression or decompression) is called a `strategy`; this + * `strategy` is passed to the `Converter` constructor so it can be used + * during conversion. * - * FIXME: Update as needed according to new implementation. + * When compressing, the conversion maps JSON keys (strings) encountered in + * the JSON-LD object to CBOR-LD IDs (integers) and value encoders. * - * 1. Finding the matching term definition from the active JSON-LD - * context for each encountered term in the data that is being - * transformed. - * 2. Mapping each term to the target format (compressed / decompressed) and - * creating value codec (encoder / decoder) for each value associate with - * the term. + * When decompressing, the conversion maps CBOR-LD IDs (integers) and + * decoded values from value decoders. * - * When compressing, the transform function maps JSON keys (strings) - * encountered in the JSON-LD object to term IDs (integers) and value - * encoders. + * A follow-on process can then serialize these abstract outputs to either + * JSON or CBOR by using the `cborg` library. * - * When decompressing, the transform function maps term IDs (integers) to - * JSON keys (strings) and value decoders. - * - * The transform process will populate the given `transformMap` (a `Map`) - * with keys (strings or IDs based on compression / decompression) and values - * (the associated encoders / decoders). This `transformMap` can then be - * passed to the `cborg` library to output JSON or CBOR. - * - * In order to match each JSON key / term ID with the right term + * In order to match each JSON key / CBOR-LD ID with the right context term * definition, it's important to understand how context is applied in - * JSON-LD, i.e., which context is "active" at a certain place in the - * object. There are three ways contexts become active to consider in - * JSON-LD: + * JSON-LD, i.e., which context is "active" at a certain place in the input. + * + * There are three ways contexts become active: * * 1. Embedded contexts. An embedded context is expressed by using the * `@context` keyword in a JSON-LD object. It is active on the object @@ -112,25 +89,60 @@ export class Transformer { * propagates, i.e., all terms defined by the context will be applicable * to the whole subtree associated with the property in the JSON object. * - * The transform function maintains a stack of active contexts based on the - * above rules to ensure that the right term definitions are matched up with - * each JSON key in the JSON-LD object (when compressing) and each term ID in - * the CBOR-LD object (when decompressing). + * The internal conversion process follows this basic algorithm, which takes + * an input and an output (to be populated): + * + * 1. Converting any contexts in the input (i.e., "embedded contexts") and + * producing an active context for converting other elements in the input. + * Every term in the top-level contexts (excludes scoped-contexts) will be + * auto-assigned CBOR-LD IDs. + * 2. Getting all type information associated with the input and using it + * to update the active context. Any terms from any type-scoped contexts + * will be auto-assigned CBOR-LD IDs. + * 3. For every term => value(s) entry in the input: + * 3.1. Update the active context using any property-scoped contextt + * associated with the term. Any terms in this property-scoped + * context will be auto-assigned CBOR-LD IDs. + * 3.2. Create an array of outputs to be populated from converting + * all of the value(s). + * 3.3. For every value in value(s), perform conversion: + * 3.3.1. If the value is `null`, add it to the outputs as-is and + * continue. + * 3.3.2. Try to use the strategy to convert the value; this involves + * checking the value type and using value-specific codecs, + * only values that must be recursed (e.g., objects and + * arrays) will not be converted by a strategy; add any + * successfully converted value to the outputs and continue. + * 3.2.3. If the value is an array, create an output array and + * recursively transform each of its elements; add the output + * array to the outputs and continue. + * 3.2.4. Create a new output according to the strategy, add it to + * the outputs and recurse with the value as the new input + * and the new output as the new output. + * 3.4. Add an term => value(s) entry to the output using the current term + * information and the outputs as the value(s). * * @param {object} options - The options to use. - * @param {ActiveContext} [options.activeCtx] - The current active context. - * @param {Map|object} options.input - The input to transform. - * @param {Map|object} options.output - The output to populate. + * @param {Map|object} options.input - The input to convert. + * + * @returns {Promise} - The output. */ + async convert({input} = {}) { + // handle single or multiple inputs + const isArray = Array.isArray(input); + const inputs = isArray ? input : [input]; + const outputs = []; + for(const input of inputs) { + const output = this.strategy._createNewOutput(); + await this._transform({input, output}); + outputs.push(output); + } + return isArray ? outputs : outputs[0]; + } + async _transform({ activeCtx = this.initialActiveCtx, input, output }) { - // FIXME: rename `obj` and `transformMap`, first is JSON-LD element, - // second is CBOR-LD element - // FIXME: consider renaming `Tranformer` to `Converter`, it is only - // ever used for JSON-LD <=> CBOR-LD, and it converts using abstract - // data model elements (JSON-LD element <=> CBOR-LD element) - // transform contexts according to strategy const {strategy} = this; activeCtx = await strategy._transformContexts({activeCtx, input, output}); @@ -154,7 +166,7 @@ export class Transformer { term }); - // iterate through all values for the current term + // iterate through all values for the current term to produce new outputs const {plural, def} = termInfo; const termType = def['@type']; const values = plural ? value : [value]; @@ -164,10 +176,8 @@ export class Transformer { activeCtx: propertyActiveCtx, outputs, termType, value, termInfo }); } - - strategy._addNewOutputs({ - outputs: plural ? outputs : outputs[0], - termInfo, input, output + strategy._addOutputEntry({ + termInfo, values: plural ? outputs : outputs[0], output }); } }