Skip to content

Commit

Permalink
Update docs, remove FIXMEs, clean up some APIs.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlongley committed Aug 13, 2024
1 parent 5c42006 commit b81a65d
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 66 deletions.
10 changes: 5 additions & 5 deletions lib/Compressor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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);
}
}

Expand Down
10 changes: 5 additions & 5 deletions lib/Decompressor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand All @@ -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;
}
}

Expand Down
122 changes: 66 additions & 56 deletions lib/Transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Map|object>} - 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});
Expand All @@ -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];
Expand All @@ -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
});
}
}
Expand Down

0 comments on commit b81a65d

Please sign in to comment.