Skip to content

Commit

Permalink
Ignore @nest properties in the stack
Browse files Browse the repository at this point in the history
  • Loading branch information
rubensworks committed Mar 16, 2020
1 parent 9ea8d52 commit 5e01165
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 15 deletions.
31 changes: 31 additions & 0 deletions lib/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,4 +735,35 @@ export class Util {
return graph;
}

/**
* Get the properties depth for retrieving properties.
*
* Typically, the properties depth will be identical to the given depth.
*
* The following exceptions apply:
* * When the parent is @reverse, the depth is decremented by one.
* * When @nest parents are found, the depth is decremented by the number of @nest parents.
* If in combination with the exceptions above an intermediary array is discovered,
* the depth is also decremented by this number of arrays.
*
* @param keys The current key chain.
* @param depth The current depth.
*/
public async getPropertiesDepth(keys: any[], depth: number): Promise<number> {
let lastValidDepth = depth;
for (let i = depth - 1; i > 0; i--) {
if (typeof keys[i] !== 'number') { // Skip array keys
const parentKey = await this.unaliasKeyword(keys[i], keys, i);
if (parentKey === '@reverse') {
return i;
} else if (parentKey === '@nest') {
lastValidDepth = i;
} else {
return lastValidDepth;
}
}
}
return lastValidDepth;
}

}
2 changes: 1 addition & 1 deletion lib/containerhandler/ContainerHandlerType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class ContainerHandlerType implements IContainerHandler {
const type = util.createVocabOrBaseTerm(context, keyOriginal);
if (type) {
// Push the type to the stack using the rdf:type predicate
await EntryHandlerPredicate.handlePredicateObject(parsingContext, util, keys, depth + 1, null,
await EntryHandlerPredicate.handlePredicateObject(parsingContext, util, keys, depth + 1,
util.rdfType, type, false);
}

Expand Down
10 changes: 4 additions & 6 deletions lib/entryhandler/EntryHandlerPredicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ export class EntryHandlerPredicate implements IEntryHandler<boolean> {
* @param {Util} util A utility instance.
* @param {any[]} keys A stack of keys.
* @param {number} depth The current depth.
* @param parentKey The parent key.
* @param {Term} predicate The predicate.
* @param {Term} object The object.
* @param {boolean} reverse If the property is reversed.
* @return {Promise<void>} A promise resolving when handling is done.
*/
public static async handlePredicateObject(parsingContext: ParsingContext, util: Util, keys: any[], depth: number,
parentKey: any, predicate: RDF.Term, object: RDF.Term, reverse: boolean) {
const depthProperties: number = depth - (parentKey === '@reverse' ? 1 : 0);
predicate: RDF.Term, object: RDF.Term, reverse: boolean) {
const depthProperties: number = await util.getPropertiesDepth(keys, depth);
const depthOffsetGraph = await util.getDepthOffsetGraph(depth, keys);
const depthPropertiesGraph: number = depth - depthOffsetGraph;

Expand Down Expand Up @@ -105,7 +104,6 @@ export class EntryHandlerPredicate implements IEntryHandler<boolean> {
public async handle(parsingContext: ParsingContext, util: Util, key: any, keys: any[], value: any, depth: number,
testResult: boolean): Promise<any> {
const keyOriginal = keys[depth];
const parentKey = await util.unaliasKeywordParent(keys, depth);
const context = await parsingContext.getContext(keys);

const predicate = await util.predicateToTerm(context, key);
Expand All @@ -114,7 +112,7 @@ export class EntryHandlerPredicate implements IEntryHandler<boolean> {
const objects = await util.valueToTerm(objectContext, key, value, depth, keys);
if (objects.length) {
for (let object of objects) {
const reverse = Util.isPropertyReverse(context, keyOriginal, parentKey);
const reverse = Util.isPropertyReverse(context, keyOriginal, await util.unaliasKeywordParent(keys, depth));

if (value) {
// Special case if our term was defined as an @list, but does not occur in an array,
Expand All @@ -138,7 +136,7 @@ export class EntryHandlerPredicate implements IEntryHandler<boolean> {
}
}

await EntryHandlerPredicate.handlePredicateObject(parsingContext, util, keys, depth, parentKey,
await EntryHandlerPredicate.handlePredicateObject(parsingContext, util, keys, depth,
predicate, object, reverse);
}
} else {
Expand Down
10 changes: 7 additions & 3 deletions lib/entryhandler/keyword/EntryHandlerKeywordId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ export class EntryHandlerKeywordId extends EntryHandlerKeyword {

public async handle(parsingContext: ParsingContext, util: Util, key: any, keys: any[], value: any, depth: number)
: Promise<any> {
// Determine the canonical place for this id.
// For example, @nest parents should be ignored.
const depthProperties: number = await util.getPropertiesDepth(keys, depth);

// Error if an @id for this node already existed.
if (parsingContext.idStack[depth] !== undefined) {
if (parsingContext.idStack[depthProperties] !== undefined) {
parsingContext.emitError(new Error(`Found duplicate @ids '${parsingContext
.idStack[depth][0].value}' and '${value}'`));
.idStack[depthProperties][0].value}' and '${value}'`));
}

// Save our @id on the stack
parsingContext.idStack[depth] = util.nullableTermToArray(await util.resourceToTerm(
parsingContext.idStack[depthProperties] = util.nullableTermToArray(await util.resourceToTerm(
await parsingContext.getContext(keys), value));
}

Expand Down
9 changes: 4 additions & 5 deletions lib/entryhandler/keyword/EntryHandlerKeywordType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ParsingContext} from "../../ParsingContext";
import {Util} from "../../Util";
import {EntryHandlerKeyword} from "./EntryHandlerKeyword";
import {EntryHandlerPredicate} from "../EntryHandlerPredicate";
import {EntryHandlerKeyword} from "./EntryHandlerKeyword";

/**
* Handles @graph entries.
Expand All @@ -15,28 +15,27 @@ export class EntryHandlerKeywordType extends EntryHandlerKeyword {
public async handle(parsingContext: ParsingContext, util: Util, key: any, keys: any[], value: any, depth: number)
: Promise<any> {
const keyOriginal = keys[depth];
const parentKey = await util.unaliasKeywordParent(keys, depth);

// The current identifier identifies an rdf:type predicate.
// But we only emit it once the node closes,
// as it's possible that the @type is used to identify the datatype of a literal, which we ignore here.
const context = await parsingContext.getContext(keys);
const predicate = util.rdfType;
const reverse = Util.isPropertyReverse(context, keyOriginal, parentKey);
const reverse = Util.isPropertyReverse(context, keyOriginal, await util.unaliasKeywordParent(keys, depth));

// Handle multiple values if the value is an array
if (Array.isArray(value)) {
for (const element of value) {
const type = util.createVocabOrBaseTerm(context, element);
if (type) {
await EntryHandlerPredicate.handlePredicateObject(parsingContext, util, keys, depth, parentKey,
await EntryHandlerPredicate.handlePredicateObject(parsingContext, util, keys, depth,
predicate, type, reverse);
}
}
} else {
const type = util.createVocabOrBaseTerm(context, value);
if (type) {
await EntryHandlerPredicate.handlePredicateObject(parsingContext, util, keys, depth, parentKey,
await EntryHandlerPredicate.handlePredicateObject(parsingContext, util, keys, depth,
predicate, type, reverse);
}
}
Expand Down
222 changes: 222 additions & 0 deletions test/JsonLdParser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6430,6 +6430,228 @@ describe('JsonLdParser', () => {

// MARKER: Add tests for new features here, wrapped in new describe blocks.
});

describe('@nest properties', () => {

it('with @id and no sub-properties', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest"
},
"@id": "http://ex.org/myid",
"nested": "This value will be ignored"
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([]);
});

it('(unaliased) with @id and one valid sub-property', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"p1": "ex:p1"
},
"@id": "http://ex.org/myid",
"@nest": {
"p1": "V1"
}
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p1'),
literal('V1')),
]);
});

it('with @id and one valid sub-property', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest",
"p1": "ex:p1"
},
"@id": "http://ex.org/myid",
"nested": {
"p1": "V1"
}
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p1'),
literal('V1')),
]);
});

it('with o-o-o @id and one valid sub-property', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest",
"p1": "ex:p1"
},
"nested": {
"p1": "V1"
},
"@id": "http://ex.org/myid"
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p1'),
literal('V1')),
]);
});

it('with @id and one valid sub-property within an array with one entry', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest",
"p1": "ex:p1"
},
"@id": "http://ex.org/myid",
"nested": [{
"p1": "V1"
}]
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p1'),
literal('V1')),
]);
});

it('with @id and one valid sub-property within an array with two entries', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest",
"p1": "ex:p1",
"p2": "ex:p2"
},
"@id": "http://ex.org/myid",
"nested": [{
"p1": "V1"
},{
"p2": "V2"
}]
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p1'),
literal('V1')),
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p2'),
literal('V2')),
]);
});

it('with @id and two valid sub-properties', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest",
"p1": "ex:p1",
"p2": "ex:p2"
},
"@id": "http://ex.org/myid",
"nested": {
"p1": "V1",
"p2": "V2"
}
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p1'),
literal('V1')),
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p2'),
literal('V2')),
]);
});

it('with @id and one valid sub-property with a sub-property', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest",
"p1": "ex:p1",
"p2": "ex:p2"
},
"@id": "http://ex.org/myid",
"nested": {
"p1": {
"@id": "http://ex.org/mysubid",
"p2": "V1"
}
}
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p1'),
namedNode('http://ex.org/mysubid')),
triple(namedNode('http://ex.org/mysubid'), namedNode('http://ex.org/p2'),
literal('V1')),
]);
});

it('with @id and one valid sub-property with a conflicting inner @id should error', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest",
"p1": "ex:p1"
},
"@id": "http://ex.org/myid",
"nested": {
"@id": "http://ex.org/conflictingid",
"p1": "V1"
}
}`);
return expect(arrayifyStream(stream.pipe(parser))).rejects
.toThrow(new Error('Found duplicate @ids \'http://ex.org/myid\' and \'http://ex.org/conflictingid\''));
});

it('with inner @id and one valid sub-property', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest",
"p1": "ex:p1"
},
"nested": {
"@id": "http://ex.org/myid",
"p1": "V1"
}
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p1'),
literal('V1')),
]);
});

it('doubly nested, with @id and one valid sub-property', async () => {
const stream = streamifyString(`
{
"@context": {
"ex": "http://ex.org/",
"nested": "@nest",
"p1": "ex:p1"
},
"@id": "http://ex.org/myid",
"nested": {
"nested": {
"p1": "V1"
}
}
}`);
return expect(await arrayifyStream(stream.pipe(parser))).toBeRdfIsomorphic([
triple(namedNode('http://ex.org/myid'), namedNode('http://ex.org/p1'),
literal('V1')),
]);
});

});
});

describe('should not parse', () => {
Expand Down
Loading

0 comments on commit 5e01165

Please sign in to comment.