Skip to content

Commit

Permalink
Handle @graph-@id and @graph-@index containers
Browse files Browse the repository at this point in the history
  • Loading branch information
rubensworks committed Mar 16, 2020
1 parent efee6d1 commit 20e73d5
Show file tree
Hide file tree
Showing 11 changed files with 1,202 additions and 80 deletions.
2 changes: 1 addition & 1 deletion lib/JsonLdParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class JsonLdParser extends Transform {

// Flush the buffer for lastDepth
// If the parent key is a special type of container, postpone flushing until that parent is handled.
if (await EntryHandlerContainer.isContainerHandler(this.parsingContext, this.lastKeys, this.lastDepth)) {
if (await EntryHandlerContainer.isBufferableContainerHandler(this.parsingContext, this.lastKeys, this.lastDepth)) {
this.parsingContext.pendingContainerFlushBuffers
.push({ depth: this.lastDepth, keys: this.lastKeys.slice(0, this.lastKeys.length) });
flushStacks = false;
Expand Down
9 changes: 1 addition & 8 deletions lib/ParsingContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class ParsingContext {
// Stack of graph flags (if they point to an @graph in a parent node)
public readonly graphStack: boolean[];
// Stack of graph overrides when in an @container: @graph
public readonly graphContainerTermStack: (RDF.NamedNode | RDF.BlankNode)[];
public readonly graphContainerTermStack: ({ [index: string]: RDF.NamedNode | RDF.BlankNode })[];
// Stack of RDF list pointers (for @list)
public readonly listPointerStack
: ({ term: RDF.Term, listRootDepth: number } | { initialPredicate: RDF.Term, listRootDepth: number })[];
Expand Down Expand Up @@ -283,13 +283,6 @@ export class ParsingContext {
delete this.idStack[depth + depthOffset];
}

// Copy the graph container term stack value up one level so that the next job can access the id.
const deeperGraphContainerTermStack = this.graphContainerTermStack[depth + depthOffset];
if (deeperGraphContainerTermStack) {
this.graphContainerTermStack[depth] = deeperGraphContainerTermStack;
delete this.graphContainerTermStack[depth + depthOffset];
}

// Shorten key stack
if (this.pendingContainerFlushBuffers.length) {
for (const buffer of this.pendingContainerFlushBuffers) {
Expand Down
59 changes: 29 additions & 30 deletions lib/Util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {ContextParser, ERROR_CODES, ErrorCoded, IJsonLdContextNormalized} from "jsonld-context-parser";
import * as RDF from "rdf-js";
import {ParsingContext} from "./ParsingContext";
import {EntryHandlerContainer} from "./entryhandler/EntryHandlerContainer";

// tslint:disable-next-line:no-var-requires
const canonicalizeJson = require('canonicalize');
Expand Down Expand Up @@ -340,7 +341,8 @@ export class Util {
return [];
} else if ('@graph' in Util.getContextValueContainer(await this.parsingContext.getContext(keys), key)) {
// We are processing a graph container
return [ this.parsingContext.graphContainerTermStack[depth + 1] || this.dataFactory.blankNode() ];
const graphContainerEntries = this.parsingContext.graphContainerTermStack[depth + 1];
return graphContainerEntries ? Object.values(graphContainerEntries) : [ this.dataFactory.blankNode() ];
} else if ("@id" in value) {
if (value["@type"] === '@vocab') {
return this.nullableTermToArray(this.createVocabOrBaseTerm(context, value["@id"]));
Expand Down Expand Up @@ -425,7 +427,7 @@ export class Util {
* @param key A JSON key.
* @return {RDF.NamedNode} An RDF named node or null.
*/
public resourceToTerm(context: IJsonLdContextNormalized, key: string): RDF.Term | null {
public resourceToTerm(context: IJsonLdContextNormalized, key: string): RDF.NamedNode | RDF.BlankNode | null {
if (key.startsWith('_:')) {
return this.dataFactory.blankNode(key.substr(2));
}
Expand Down Expand Up @@ -667,6 +669,12 @@ export class Util {
public async getDepthOffsetGraph(depth: number, keys: any[]): Promise<number> {
for (let i = depth - 1; i > 0; i--) {
if (await this.unaliasKeyword(keys[i], keys, i) === '@graph') {
// Skip further processing if we are already in an @graph-@id or @graph-@index container
const containers = (await EntryHandlerContainer.getContainerHandler(this.parsingContext, keys, i)).containers;
if (EntryHandlerContainer.isComplexGraphContainer(containers)) {
return -1;
}

return depth - i - 1;
}
}
Expand All @@ -692,29 +700,6 @@ export class Util {
return this.parsingContext.defaultGraph || this.dataFactory.defaultGraph();
}

/**
* Get the container type of the given key in the context.
*
* This will ignore any arrays in the key chain.
*
* @param {IJsonLdContextNormalized} context A JSON-LD context.
* @param {string} key A context entry key.
* @return {string} The container type.
*/
public async getContextValueContainerArrayAware(keys: any[], depth: number)
: Promise<{ [typeName: string]: boolean }> {
for (let i = depth; i > 0; i--) {
if (typeof keys[i - 1] !== 'number') { // Skip array keys
const container = Util.getContextValue(await this.parsingContext.getContext(keys),
'@container', keys[i - 1], null);
if (container) {
return container;
}
}
}
return { '@set': true };
}

/**
* Get the current graph, while taking into account a graph that can be defined via @container: @graph.
* If not within a graph container, the default graph will be returned.
Expand All @@ -724,16 +709,30 @@ export class Util {
public async getGraphContainerValue(keys: any[], depth: number)
: Promise<RDF.NamedNode | RDF.BlankNode | RDF.DefaultGraph> {
// Default to default graph
let graph = this.getDefaultGraph();
let graph: RDF.NamedNode | RDF.BlankNode | RDF.DefaultGraph | null = this.getDefaultGraph();

// Check if we are in an @container: @graph.
const container = await this.getContextValueContainerArrayAware(keys, depth);
if ('@graph' in container) {
const { containers, depth: depthContainer } = await EntryHandlerContainer
.getContainerHandler(this.parsingContext, keys, depth);
if ('@graph' in containers) {
// Get the graph from the stack.
graph = this.parsingContext.graphContainerTermStack[depth];
const graphContainerIndex = EntryHandlerContainer.getContainerGraphIndex(containers, depthContainer, keys);
const entry = this.parsingContext.graphContainerTermStack[depthContainer];
graph = entry ? entry[graphContainerIndex] : null;

// Set the graph in the stack if none has been set yet.
if (!graph) {
graph = this.parsingContext.graphContainerTermStack[depth] = this.dataFactory.blankNode();
let graphId: RDF.NamedNode | RDF.BlankNode | null = null;
if ('@id' in containers) {
graphId = await this.resourceToTerm(await this.parsingContext.getContext(keys), keys[depthContainer]);
}
if (!graphId) {
graphId = this.dataFactory.blankNode();
}
if (!this.parsingContext.graphContainerTermStack[depthContainer]) {
this.parsingContext.graphContainerTermStack[depthContainer] = {};
}
graph = this.parsingContext.graphContainerTermStack[depthContainer][graphContainerIndex] = graphId;
}
}

Expand Down
7 changes: 6 additions & 1 deletion lib/containerhandler/ContainerHandlerIdentifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {IContainerHandler} from "./IContainerHandler";
*/
export class ContainerHandlerIdentifier implements IContainerHandler {

public async handle(parsingContext: ParsingContext, util: Util, keys: string[], value: any, depth: number)
public canCombineWithGraph(): boolean {
return true;
}

public async handle(containers: { [typeName: string]: boolean }, parsingContext: ParsingContext, util: Util,
keys: string[], value: any, depth: number)
: Promise<void> {
// Create the identifier
const keyUnaliased = await util.getContainerKey(keys, depth);
Expand Down
11 changes: 9 additions & 2 deletions lib/containerhandler/ContainerHandlerIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ import {IContainerHandler} from "./IContainerHandler";
*/
export class ContainerHandlerIndex implements IContainerHandler {

public async handle(parsingContext: ParsingContext, util: Util, keys: string[], value: any, depth: number)
public canCombineWithGraph(): boolean {
return true;
}

public async handle(containers: { [typeName: string]: boolean }, parsingContext: ParsingContext, util: Util,
keys: string[], value: any, depth: number)
: Promise<void> {
await parsingContext.newOnValueJob(keys, value, depth - 1, true);
const graphContainer = '@graph' in containers;

await parsingContext.newOnValueJob(keys, value, depth - (graphContainer ? 2 : 1), true);

parsingContext.emittedStack[depth] = false; // We have emitted a level higher
}
Expand Down
7 changes: 6 additions & 1 deletion lib/containerhandler/ContainerHandlerLanguage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import {IContainerHandler} from "./IContainerHandler";
*/
export class ContainerHandlerLanguage implements IContainerHandler {

public async handle(parsingContext: ParsingContext, util: Util, keys: string[], value: any, depth: number)
public canCombineWithGraph(): boolean {
return false;
}

public async handle(containers: { [typeName: string]: boolean }, parsingContext: ParsingContext, util: Util,
keys: string[], value: any, depth: number)
: Promise<void> {
const language = await util.getContainerKey(keys, depth);

Expand Down
7 changes: 6 additions & 1 deletion lib/containerhandler/ContainerHandlerType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import {IContainerHandler} from "./IContainerHandler";
*/
export class ContainerHandlerType implements IContainerHandler {

public async handle(parsingContext: ParsingContext, util: Util, keys: string[], value: any, depth: number)
public canCombineWithGraph(): boolean {
return false;
}

public async handle(containers: { [typeName: string]: boolean }, parsingContext: ParsingContext, util: Util,
keys: string[], value: any, depth: number)
: Promise<void> {
if (!Array.isArray(value)) {
if (typeof value === 'string') {
Expand Down
9 changes: 8 additions & 1 deletion lib/containerhandler/IContainerHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@ import {Util} from "../Util";
*/
export interface IContainerHandler {

/**
* If this container type can be combined with @graph containers.
*/
canCombineWithGraph(): boolean;

/**
* Process the given value that has the given container type.
* @param containers The applicable container hash.
* @param parsingContext The parsing context.
* @param {Util} util A utility instance.
* @param {string[]} keys The array of stack keys.
* @param value The current value that is being parsed.
* @param {number} depth The current stack depth.
* @return {Promise<void>} A promise resolving when handling is done.
*/
handle(parsingContext: ParsingContext, util: Util, keys: string[], value: any, depth: number): Promise<void>;
handle(containers: { [typeName: string]: boolean }, parsingContext: ParsingContext, util: Util,
keys: string[], value: any, depth: number): Promise<void>;

}
Loading

0 comments on commit 20e73d5

Please sign in to comment.