-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
173 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@hydrofoil/shaperone-core": patch | ||
--- | ||
|
||
Implement complete shape selection based on targets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,53 @@ | ||
import { Shape } from '@rdfine/shacl' | ||
import { rdf } from '@tpluscode/rdf-ns-builders' | ||
import { rdf, sh, rdfs } from '@tpluscode/rdf-ns-builders' | ||
import TermMap from '@rdf-esm/term-map' | ||
import { FocusNode } from '../../../index' | ||
|
||
export function matchFor(focusNode: FocusNode) { | ||
return (shape: Shape) => { | ||
const { targetNode, targetClass } = shape | ||
const scores = new TermMap([ | ||
[sh.targetNode, 20], | ||
[sh.targetClass, 10], | ||
[sh.targetObjectsOf, 5], | ||
[sh.targetSubjectsOf, 5], | ||
]) | ||
|
||
function toScoring(focusNode: FocusNode) { | ||
return (matched: [Shape, number][], shape: Shape): [Shape, number][] => { | ||
let score = 0 | ||
const { targetNode, targetClass, targetObjectsOf, targetSubjectsOf } = shape | ||
if (targetNode.some(targetNode => targetNode.equals(focusNode.term))) { | ||
return true | ||
score = scores.get(sh.targetNode) || 0 | ||
} | ||
|
||
const classIds = targetClass.map(c => c.id) | ||
if (focusNode.has(rdf.type, classIds).values.some(Boolean)) { | ||
return true | ||
if (shape.types.has(rdfs.Class)) { | ||
classIds.push(shape.id) | ||
} | ||
if (focusNode.has(rdf.type, classIds).terms.length) { | ||
score = Math.max(score, scores.get(sh.targetClass) || 0) | ||
} | ||
|
||
if (targetSubjectsOf && focusNode.out(targetSubjectsOf.id).terms.length) { | ||
score = Math.max(score, scores.get(sh.targetSubjectsOf) || 0) | ||
} | ||
|
||
if (targetObjectsOf && focusNode.in(targetObjectsOf.id).terms.length) { | ||
score = Math.max(score, scores.get(sh.targetObjectsOf) || 0) | ||
} | ||
|
||
return [ | ||
...matched, | ||
[shape, score], | ||
] | ||
} | ||
} | ||
|
||
return false | ||
export function matchShapes(shapes: Shape[] = []): { to: (focusNode: FocusNode) => Shape[] } { | ||
return { | ||
to(focusNode: FocusNode) { | ||
return shapes.reduce(toScoring(focusNode), []) | ||
.filter(match => match[1] > 0) | ||
.sort((left, right) => right[1] - left[1]) | ||
.map(([shape]) => shape) | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { describe, it } from 'mocha' | ||
import clownface from 'clownface' | ||
import $rdf from 'rdf-ext' | ||
import { rdf, schema, rdfs } from '@tpluscode/rdf-ns-builders' | ||
import { expect } from 'chai' | ||
import { nodeShape } from '../../../util' | ||
import { matchShapes } from '../../../../models/shapes/lib' | ||
|
||
describe('models/shapes/lib', () => { | ||
describe('matchShapes', () => { | ||
const focusNode = clownface({ dataset: $rdf.dataset() }) | ||
.namedNode('john') | ||
.addOut(rdf.type, schema.Person) | ||
.addOut(schema.alumniOf, $rdf.namedNode('UCLA')) | ||
.addIn(schema.parent, $rdf.namedNode('jane')) | ||
|
||
const johnShape = nodeShape({ targetNode: [$rdf.namedNode('john')] }) | ||
const personClassShape = nodeShape({ targetClass: [schema.Person] }) | ||
const alumnusShape = nodeShape({ targetSubjectsOf: schema.alumniOf }) | ||
const parentShape = nodeShape({ targetObjectsOf: schema.parent }) | ||
const implicitPersonTargetShape = nodeShape(schema.Person, { types: [rdfs.Class] }) | ||
|
||
it('prefers sh:targetNode over all other shape targets', () => { | ||
// given | ||
const shapes = [ | ||
personClassShape, | ||
alumnusShape, | ||
johnShape, | ||
parentShape, | ||
] | ||
|
||
// when | ||
const matched = matchShapes(shapes).to(focusNode) | ||
|
||
// then | ||
expect(matched).to.have.length(4) | ||
expect(matched[0].id).to.deep.eq(johnShape.id) | ||
}) | ||
|
||
it('prefers sh:targetClass over property target shapes', () => { | ||
// given | ||
const shapes = [ | ||
alumnusShape, | ||
personClassShape, | ||
parentShape, | ||
] | ||
|
||
// when | ||
const matched = matchShapes(shapes).to(focusNode) | ||
|
||
// then | ||
expect(matched).to.have.length(3) | ||
expect(matched[0].id).to.deep.eq(personClassShape.id) | ||
}) | ||
|
||
it('prefers implicit rdf:Class shape over property shapes', () => { | ||
// given | ||
const shapes = [ | ||
alumnusShape, | ||
implicitPersonTargetShape, | ||
parentShape, | ||
] | ||
|
||
// when | ||
const matched = matchShapes(shapes).to(focusNode) | ||
|
||
// then | ||
expect(matched).to.have.length(3) | ||
expect(matched[0].id).to.deep.eq(implicitPersonTargetShape.id) | ||
}) | ||
|
||
it('matches shape by subject usage in graph', () => { | ||
// given | ||
const shapes = [ | ||
alumnusShape, | ||
] | ||
|
||
// when | ||
const matched = matchShapes(shapes).to(focusNode) | ||
|
||
// then | ||
expect(matched[0].id).to.deep.eq(alumnusShape.id) | ||
}) | ||
|
||
it('matches shape by object usage in graph', () => { | ||
// given | ||
const shapes = [ | ||
parentShape, | ||
] | ||
|
||
// when | ||
const matched = matchShapes(shapes).to(focusNode) | ||
|
||
// then | ||
expect(matched[0].id).to.deep.eq(parentShape.id) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters