Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: graph traversals #45

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ Factory to create graph pointer objects
<td>[init.factory]</td><td><code>DataFactory</code></td><td><code>@rdfjs/data-model</code></td><td><p>an RDF/JS factory which will be used to create nodes</p>
</td>
</tr><tr>
<td>[init._context]</td><td><code>Context</code></td><td></td><td><p>an existing clownface context. takes precedence before other params</p>
<td>[init._context]</td><td><code>Context</code> | <code>Array.&lt;Context&gt;</code></td><td></td><td><p>an existing clownface context. takes precedence before other params</p>
</td>
</tr> </tbody>
</table>
Expand Down
63 changes: 62 additions & 1 deletion docs/named-graphs.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Default behavior

The examples on all other pages do not specify any graph identifier. In this mode the traversal methods are free to navigate the entire dataset (aka. [union graph](https://patterns.dataincubator.org/book/union-graph.html)) but any triples added and removed only apply to the default graph.
The examples on all other pages do not specify any graph identifier. In this mode the traversal methods are free to navigate the entire dataset (aka. [union graph][union]) but any triples added and removed only apply to the default graph.

<run-kit>

Expand Down Expand Up @@ -63,3 +63,64 @@ nquads`${leonard.dataset}`.toString()
```

</run-kit>

## Moving between graphs

?> From v1.1

It is possible to move a graph pointer to the same node in different (named) graphs using a `inGraph` method. It will assume the current pointer to be the graph URI and return a new pointer where the `graph === term`.

<run-kit>

```js
const cf = require('clownface')
const { dataset } = require('@rdfjs/dataset')
const namespace = require('@rdfjs/namespace')
const RDF = require('@rdfjs/data-model')
const { rdfs } = require('@tpluscode/rdf-ns-builders')

const ex = namespace('https://example.com/')

const quads = [
RDF.quad(ex.Apple, rdfs.label, 'Apple', ex.EnglishLabels),
RDF.quad(ex.Apple, rdfs.label, 'Apfel', ex.GermanLabels),
]

const pointer = cf({ dataset: dataset(quads) })

console.log({
de: pointer.node(ex.GermanLabels).inGraph().node(ex.Apple).out(rdfs.label).value,
en: pointer.node(ex.EnglishLabels).inGraph().node(ex.Apple).out(rdfs.label).value,
})
```

</run-kit>

The `inGraph` method takes an optional parameter which lets calling code map the pointer term to the graph URI. A common scenario could be to assume a hash base URI as frequently seen in RDF vocabularies. The example below takes the `rdf:Class` identifier and creates the named graph by removing the hash fragment from that URI.

<run-kit>

```js
const cf = require('clownface')
const { dataset } = require('@rdfjs/dataset')
const RDF = require('@rdfjs/data-model')
const { rdfs } = require('@tpluscode/rdf-ns-builders')

const quads = [
RDF.quad(rdfs.Class, rdfs.label, 'Class', rdfs()),
]

function namedNodeWithoutHash (term) {
return rdf.namedNode(term.value.split('#')[0])
}

const pointer = cf({ dataset: dataset(quads) })
.node(rdfs.Class)
.inGraph({ map: namedNodeWithoutHash })

console.log(pointer.out(rdfs.label).value)
```

</run-kit>

[union]: https://patterns.dataincubator.org/book/union-graph.html
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const Clownface = require('./lib/Clownface')
* @param {Term|Term[]} [init.term] one or more RDF/JS term(s) which will be the pointer's context
* @param {string} [init.value] one or more raw values which will create literal node as the pointer's context
* @param {DataFactory} [init.factory=@rdfjs/data-model] an RDF/JS factory which will be used to create nodes
* @param {Context} [init._context] an existing clownface context. takes precedence before other params
* @param {Context|Context[]} [init._context] an existing clownface context. takes precedence before other params
* @returns {Clownface}
*/
function factory ({ dataset, graph, term, value, factory, _context }) {
Expand Down
13 changes: 13 additions & 0 deletions lib/Clownface.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,19 @@ class Clownface {
return this
}

inGraph ({ map = term => term } = {}) {
const context = this._context.map(context => context.clone({
value: context.term,
graph: map(context.term)
}))

return Clownface.fromContext(context)
}

get defaultGraph () {
return this.node(rdf.defaultGraphInstance)
}

_toTermArray (predicates, type, languageOrDatatype) {
return toTermArray(predicates, type, languageOrDatatype, this.factory)
}
Expand Down
15 changes: 15 additions & 0 deletions test/Clownface/defaultGraph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* global describe, it */

const assert = require('assert')
const clownface = require('../..')
const rdf = require('../support/factory')

describe('defaultGraph', () => {
it('returns pointer to default graph node', () => {
const pointer = clownface({ dataset: rdf.dataset() })

const defaultGraph = pointer.defaultGraph

assert(defaultGraph.term.equals(rdf.defaultGraphInstance))
})
})
48 changes: 48 additions & 0 deletions test/Clownface/inGraph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* global describe, it */

const assert = require('assert')
const { rdfs } = require('@tpluscode/rdf-ns-builders')
const clownface = require('../..')
const rdf = require('../support/factory')
const ns = require('../support/namespace')

describe('.inGraph', () => {
it('called on null pointer keeps null pointer', () => {
const term = ns.ex.Foo
const dataset = rdf.dataset([
rdf.quad(term, rdfs.label, rdf.literal('default graph')),
rdf.quad(term, rdfs.label, rdf.literal('named graph'), ns.ex.NamedGraph)
])

const pointer = clownface({ dataset }).inGraph()

assert.deepStrictEqual(typeof pointer.term, 'undefined')
})

it('called on pointer to graph switches to that graph', () => {
const term = ns.ex.Foo
const dataset = rdf.dataset([
rdf.quad(term, rdfs.label, rdf.literal('default graph')),
rdf.quad(term, rdfs.label, rdf.literal('named graph'), ns.ex.Foo)
])
const pointer = clownface({ dataset, term })

const movedPointer = pointer.inGraph()

assert.deepStrictEqual(movedPointer.out(rdfs.label).value, 'named graph')
})

it('switches to graph returned by map parameter', () => {
const term = ns.ex.Foo
const dataset = rdf.dataset([
rdf.quad(term, rdfs.label, rdf.literal('default graph')),
rdf.quad(term, rdfs.label, rdf.literal('named graph'), ns.ex.FooGraph)
])
const pointer = clownface({ dataset, term })
const appendGraph = term => rdf.namedNode(term.value + 'Graph')

const movedPointer = pointer.inGraph({ map: appendGraph })

assert.deepStrictEqual(movedPointer.out(rdfs.label).value, 'named graph')
})
})
2 changes: 2 additions & 0 deletions test/Clownface/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ describe('Clownface', () => {
require('./deleteIn')
require('./deleteOut')
require('./deleteList')
require('./inGraph')
require('./defaultGraph')
})