├── .changeset ├── README.md └── config.json ├── .eslintrc.json ├── .github └── workflows │ ├── add-to-backlog.yml │ ├── ci.yaml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Factory.js ├── README.md ├── codecov.yml ├── docs ├── .nojekyll ├── README.md ├── _sidebar.md ├── api.md ├── context.md ├── deep-dive.md ├── index.html ├── manipulation.md ├── named-graphs.md ├── rdf-lists.md ├── tagged-literals.md └── traversal.md ├── examples └── namespace.js ├── filter.js ├── index.js ├── lib ├── Clownface.js ├── Context.js ├── environment.js ├── fromPrimitive.js ├── languageTag.js ├── namespace.js ├── term.js ├── toArray.js └── toTermArray.js ├── package-lock.json ├── package.json └── test ├── Clownface ├── addIn.test.js ├── addList.test.js ├── addOut.test.js ├── any.test.js ├── blankNode.test.js ├── constructor.test.js ├── dataset.test.js ├── datasets.test.js ├── deleteIn.test.js ├── deleteList.test.js ├── deleteOut.test.js ├── factory.test.js ├── filter.test.js ├── forEach.test.js ├── has.test.js ├── in.test.js ├── isList.test.js ├── list.test.js ├── literal.test.js ├── map.test.js ├── namedNode.test.js ├── node.test.js ├── out.test.js ├── term.test.js ├── terms.test.js ├── toArray.test.js ├── toString.test.js ├── value.test.js └── values.test.js ├── Context.test.js ├── Factory.test.js ├── filter.test.js ├── fromPrimitive.test.js ├── support ├── CustomDataFactory.js ├── example.js ├── factory.js ├── namespace.js └── parse.js └── term.test.js /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ "@tpluscode/eslint-config/js" ], 3 | "env": { 4 | "mocha": true 5 | }, 6 | "overrides": [ 7 | { 8 | "files": ["examples/**"], 9 | "rules": { 10 | "no-console": "off" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/add-to-backlog.yml: -------------------------------------------------------------------------------- 1 | name: Add issues to shared backlog 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | pull_request: 8 | branches-ignore: 9 | - "dependabot/**/*" 10 | types: 11 | - opened 12 | 13 | jobs: 14 | add-to-project: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/add-to-project@v0.5.0 18 | with: 19 | project-url: https://github.com/orgs/zazuko/projects/23 20 | github-token: ${{ secrets.BACKLOG_PAT }} 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node: 13 | - "18" 14 | - "20" 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.node }} 20 | - run: npm ci 21 | - run: npm test 22 | - uses: codecov/codecov-action@v2 23 | 24 | lint: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: actions/setup-node@v3 29 | with: 30 | node-version: 20 31 | - run: npm install --ci 32 | - run: npm run lint 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repo 14 | uses: actions/checkout@v4 15 | with: 16 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 17 | fetch-depth: 0 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 20 23 | 24 | - name: Install Dependencies 25 | run: npm ci 26 | 27 | - name: Create Release Pull Request or Publish to npm 28 | id: changesets 29 | uses: changesets/action@v1 30 | with: 31 | # This expects you to have a script called release which does a build for your packages and calls changeset publish 32 | publish: npm run release 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | docs/api/ 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # clownface 2 | 3 | ## 2.0.3 4 | 5 | ### Patch Changes 6 | 7 | - e9da738: chore: update package links 8 | 9 | ## 2.0.2 10 | 11 | ### Patch Changes 12 | 13 | - 25f4bf9: Adjust to `@rdfjs/environment@v1` (still compatible with v0) 14 | 15 | ## 2.0.1 16 | 17 | ### Patch Changes 18 | 19 | - 810ede5: Fixes a problem that in some scenarios when operating on a `MultiPointer` would use clownface's default environment which causes clashing blank node being generated 20 | 21 | ## 2.0.0 22 | 23 | ### Major Changes 24 | 25 | - c1441a4: Update to ESM 26 | 27 | ### Minor Changes 28 | 29 | - c1441a4: Uses `@rdfjs/environment` instead of directly importing individual packages 30 | - 1caa1ae: Added RDF/JS environment factory module 31 | -------------------------------------------------------------------------------- /Factory.js: -------------------------------------------------------------------------------- 1 | import clownface from './index.js' 2 | 3 | class ClownfaceFactory { 4 | clownface({ ...args } = {}) { 5 | if (!args.dataset && typeof this.dataset === 'function') { 6 | args.dataset = this.dataset() 7 | } 8 | 9 | return clownface({ ...args, factory: this }) 10 | } 11 | } 12 | 13 | ClownfaceFactory.exports = ['clownface'] 14 | 15 | export default ClownfaceFactory 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clownface 2 | 3 | [![Build Status](https://travis-ci.org/zazuko/clownface.svg?branch=master)](https://travis-ci.org/github/zazuko/clownface) 4 | [![NPM Version](https://img.shields.io/npm/v/clownface.svg?style=flat)](https://npm.im/clownface) 5 | 6 | Clownface is a graph traversal library inspired by [Gremlin](https://tinkerpop.apache.org/gremlin.html) which allows to query any [RDF dataset](https://rdf.js.org/dataset-spec/) in a concise and readable way. 7 | 8 | Clownface greatly simplifies interacting with RDF data in JavaScript. 9 | 10 | # Quick start 11 | 12 | The recommended way is to use clownface with and RDF/JS environment. 13 | It also requires [`DataFactory`](https://rdf.js.org/data-model-spec/#datafactory-interface) and [`DatasetFactory`](https://rdf.js.org/dataset-spec/#datasetfactory-interface), for example those provided by [`@rdfjs/data-model`](https://npm.im/@rdfjs/data-model) and [`@rdfjs/dataset`](https://npm.im/@rdfjs/dataset) packages respectively, as well as [`@rdfjs/namespace`](https://npm.im/@rdfjs/namespace). 14 | 15 | ```shell 16 | npm install clownface @rdfjs/environment @rdfjs/data-model @rdfjs/dataset @rdfjs/namespace 17 | ```` 18 | 19 | ```js 20 | import Environment from '@rdfjs/environment/Environment.js' 21 | import NamespaceFactory from '@rdfjs/namespace/Factory.js' 22 | import DatasetFactory from '@rdfjs/dataset/Factory.js' 23 | import DataFactory from '@rdfjs/data-model/Factory.js' 24 | import ClownfaceFactory from 'clownface/Factory.js' 25 | 26 | const $rdf = new Environment([ 27 | NamespaceFactory, 28 | DatasetFactory, 29 | DataFactory, 30 | ClownfaceFactory 31 | ]) 32 | 33 | const graph = $rdf.clownface() 34 | ``` 35 | 36 | Alternatively, if you already use [@zazuko/env](https://npm.im/@zazuko/env), it comes bundled with clownface and its dependencies. 37 | 38 | ```js 39 | import $rdf from '@zazuko/env' 40 | 41 | const graph = $rdf.clownface() 42 | ``` 43 | 44 | # Learn more 45 | 46 | If you are new to RDF and JavaScript, consider our [Getting Started](https://zazuko.com/get-started/developers/#traverse-an-rdf-graph) guide that also covers Clownface basics. 47 | 48 | For API documentation and examples, see http://zazuko.github.io/clownface/. 49 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - examples 3 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zazuko/clownface/70e6375aef8a1ce56ee0aabaa0d6f11f47dbf73e/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # clownface 2 | 3 | [![Build Status](https://travis-ci.org/rdf-ext/clownface.svg?branch=master)](https://travis-ci.org/rdf-ext/clownface) 4 | [![NPM Version](https://img.shields.io/npm/v/clownface.svg?style=flat)](https://npm.im/clownface) 5 | 6 | Clownface is a graph traversal library inspired by [Gremlin](http://tinkerpop.apache.org/) 7 | which allows to query any [RDF dataset](https://rdf.js.org/dataset-spec/) in a concise and readable way. 8 | 9 | ## Introduction 10 | 11 | Querying an RDF graph can be challenging. Apart from trivial queries, e.g. simply listing all the 12 | objects connected to a subject, it is usually necessary to write multiple loop and validation 13 | conditions to retrieve an answer. Clownface is there to help. 14 | 15 | ## Getting started 16 | 17 | The package is installed from npm: 18 | 19 | ``` 20 | npm i -S clownface 21 | ``` 22 | 23 | To start using it, an instance of [RDF/JS `DatasetCore`](https://rdf.js.org/dataset-spec/#datasetcore-interface) must be 24 | provided to the exported factory method 25 | 26 | 27 | 28 | ```js 29 | const rdf = require('@zazuko/env-bundle') 30 | 31 | const firstName = rdf.namedNode('http://xmlns.com/foaf/0.1/firstName') 32 | const lastName = rdf.namedNode('http://xmlns.com/foaf/0.1/lastName') 33 | 34 | // initialize 35 | const ptr = rdf.clownface() 36 | 37 | // add some resources 38 | ptr 39 | .namedNode('http://example.com/person/stuart-bloom') 40 | .addOut(firstName, 'Stuart') 41 | .addOut(lastName, 'Bloom') 42 | .namedNode('http://example.com/person/penny') 43 | .addOut(firstName, 'Penny') 44 | 45 | // and now retrieve the first names of those who have a last name 46 | ptr 47 | .has(lastName) 48 | .out(firstName) 49 | .values 50 | ``` 51 | 52 | 53 | 54 | ## Details 55 | 56 | At the core of clownface sits an object called a "graph pointer". A graph pointer can be backed by zero or more RDF/JS terms. It is being created by calling the function exported by the packages's default module. 57 | 58 | The current context node or nodes can be accessed by `term`/`value` and `terms`/`values` pairs of properties. The first pair return only a single RDF/JS and its string value. They will return `undefined` if the context points at 0 or >1 nodes. The second pair of properties always return an array of the context terms and their string values. 59 | 60 | Graph pointers provides a set of chainable methods. The most important ones are `.in(predicate)` and `.out(predicate)` which allow the traversal through the graph. It is possible to chain as many of these methods to extract a sub-graph from the available dataset. 61 | 62 | ## More examples 63 | 64 | Check out the [deep dive](deep-dive.md) and other pages for a running examples which show how to use the graph pointer to traverse and manipulate RDF graphs. 65 | 66 | We use the well known `` `` `` nomenclature for a triple in the following description. All examples are based on the toy dataset [tbbt-ld][tbbt]. 67 | 68 | [tbbt]: https://github.com/zazuko/tbbt-ld 69 | 70 | # API 71 | 72 | A Typescript declaration package [`@types/clownface`](https://npm.im/@types/clownface) is available. 73 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * Getting started 2 | * [About](/) 3 | * [Deep dive](deep-dive.md) 4 | * User guide 5 | * [The context](context.md) 6 | * [Navigating the graph](traversal.md) 7 | * [Manipulating data](manipulation.md) 8 | * [Working with named graphs](named-graphs.md) 9 | * [RDF Lists](rdf-lists.md) 10 | * [Tagged literals](tagged-literals.md) 11 | * Reference 12 | * [JSDoc](api.md) 13 | * [TypeScript](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/clownface) 14 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | ## Members 2 | 3 |
4 |
termundefined | Term
5 |

Gets the current RDF/JS term or undefined if pointer has no context

6 |
7 |
termsArray.<Term>
8 |

Gets the current terms or an empty array if the pointer has no context

9 |
10 |
valueundefined | string
11 |

Gets the string representation of term

12 |
13 |
valuesArray.<string>
14 |

Gets the string representation of terms

15 |
16 |
datasetundefined | DatasetCore
17 |

Gets the current context's dataset, or undefined if there are multiple

18 |
19 |
datasetsArray.<DatasetCore>
20 |

Gets the current context's datasets

21 |
22 |
23 | 24 | ## Functions 25 | 26 |
27 |
any()Clownface
28 |

Removes current pointers from the context and return an "any pointer". 29 | The returned object can be used to find any nodes in the dataset

30 |
31 |
isList()boolean
32 |

Returns true if the current term is a rdf:List

33 |
34 |
list()Iterable | null
35 |

Creates an iterator which iterates and rdf:List of the current term

36 |
37 |
toArray()Array.<Clownface>
38 |

Returns an array of graph pointers where each one has a single _context

39 |
40 |
filter(callback)Clownface
41 |

Returns graph pointers which meet the condition specified in a callback function

42 |
43 |
forEach(callback)Clownface
44 |

Performs the specified action on every graph pointer

45 |
46 |
map(callback)Array.<T>
47 |

Calls a defined callback function on each graph pointer, and returns an array that contains the results.

48 |
49 |
node(values, [options])Clownface
50 |

Creates graph pointer to one or more node(s)

51 |

Depending on the value creates pointers to:

52 |
    53 |
  • blank node context for null values
  • 54 |
  • literal for string values and no options paramter
  • 55 |
  • matching RDF/JS term
  • 56 |
  • term created according to options.type parameter
  • 57 |
58 |
59 |
blankNode([values])Clownface
60 |

Creates graph pointer to one or more blank nodes

61 |
62 |
literal(values, [languageOrDatatype])Clownface
63 |

Creates graph pointer to one or more literal nodes

64 |
65 |
namedNode(values)Clownface
66 |

Creates graph pointer to one or more named nodes

67 |
68 |
in([predicates])Clownface
69 |

Creates a graph pointer to nodes which are linked to the current pointer by predicates

70 |
71 |
out([predicates], [options])Clownface
72 |

Creates a graph pointer to the result nodes after following a predicate, or after 73 | following any predicates in an array, starting from the subject(s) (current graph pointer) to the objects.

74 |
75 |
has(predicates, [objects])Clownface
76 |

Creates a graph pointer to nodes which are subjects of predicates, optionally also with specific objects

77 |

If the current context is empty, will check all potential subjects

78 |
79 |
addIn(predicates, subjects, [callback])Clownface
80 |

Creates a new quad(s) in the dataset where the current context is the object

81 |
82 |
addOut(predicates, objects, [callback])Clownface
83 |

Creates a new quad(s) in the dataset where the current context is the subject

84 |
85 |
addList(predicates, items)Clownface
86 |

Creates a new RDF list or lists containing the given items

87 |
88 |
deleteIn([predicates], [subjects])Clownface
89 |

Deletes all quads where the current graph pointer contexts are the objects

90 |
91 |
deleteOut([predicates], [objects])Clownface
92 |

Deletes all quads where the current graph pointer contexts are the subjects

93 |
94 |
deleteList(predicates)Clownface
95 |

Deletes entire RDF lists where the current graph pointer is the subject

96 |
97 |
filterTaggedLiterals(terms, [options])Array.<Term>
98 |
99 |
100 | 101 | ## Typedefs 102 | 103 |
104 |
GraphPointerCallback : function
105 |
106 |
FilterCallbackboolean
107 |
108 |
ForEachCallback : function
109 |
110 |
MapCallbackT
111 |
112 |
113 | 114 | 115 | 116 | ## term ⇒ undefined \| Term 117 | Gets the current RDF/JS term or undefined if pointer has no context 118 | 119 | **Kind**: global variable 120 | 121 | 122 | ## terms ⇒ Array.<Term> 123 | Gets the current terms or an empty array if the pointer has no context 124 | 125 | **Kind**: global variable 126 | 127 | 128 | ## value ⇒ undefined \| string 129 | Gets the string representation of term 130 | 131 | **Kind**: global variable 132 | 133 | 134 | ## values ⇒ Array.<string> 135 | Gets the string representation of terms 136 | 137 | **Kind**: global variable 138 | 139 | 140 | ## dataset ⇒ undefined \| DatasetCore 141 | Gets the current context's dataset, or undefined if there are multiple 142 | 143 | **Kind**: global variable 144 | 145 | 146 | ## datasets ⇒ Array.<DatasetCore> 147 | Gets the current context's datasets 148 | 149 | **Kind**: global variable 150 | 151 | 152 | ## any() ⇒ Clownface 153 | Removes current pointers from the context and return an "any pointer". 154 | The returned object can be used to find any nodes in the dataset 155 | 156 | **Kind**: global function 157 | 158 | 159 | ## isList() ⇒ boolean 160 | Returns true if the current term is a rdf:List 161 | 162 | **Kind**: global function 163 | 164 | 165 | ## list() ⇒ Iterable \| null 166 | Creates an iterator which iterates and rdf:List of the current term 167 | 168 | **Kind**: global function 169 | 170 | 171 | ## toArray() ⇒ Array.<Clownface> 172 | Returns an array of graph pointers where each one has a single _context 173 | 174 | **Kind**: global function 175 | 176 | 177 | ## filter(callback) ⇒ Clownface 178 | Returns graph pointers which meet the condition specified in a callback function 179 | 180 | **Kind**: global function 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 |
ParamType
callbackFilterCallback
192 | 193 | 194 | 195 | ## forEach(callback) ⇒ Clownface 196 | Performs the specified action on every graph pointer 197 | 198 | **Kind**: global function 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 |
ParamType
callbackForEachCallback
210 | 211 | 212 | 213 | ## map(callback) ⇒ Array.<T> 214 | Calls a defined callback function on each graph pointer, and returns an array that contains the results. 215 | 216 | **Kind**: global function 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 |
ParamType
callbackMapCallback.<T>
228 | 229 | 230 | 231 | ## node(values, [options]) ⇒ Clownface 232 | Creates graph pointer to one or more node(s) 233 | 234 | Depending on the value creates pointers to: 235 | 236 | - blank node context for null `values` 237 | - literal for string `values` and no `options` paramter 238 | - matching RDF/JS term 239 | - term created according to `options.type` parameter 240 | 241 | **Kind**: global function 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 256 | 257 | 259 | 260 | 262 | 263 |
ParamTypeDescription
valuesnull | string | Array.<string> | Term | Array.<Term> | Clownface | Array.<Clownface>
[options]Object
[options.type]"NamedNode" | "BlankNode" | "Literal"

explicit type for nodes

255 |
[options.language]string

language tag of literals

258 |
[options.datatype]string

datatype of literals

261 |
264 | 265 | 266 | 267 | ## blankNode([values]) ⇒ Clownface 268 | Creates graph pointer to one or more blank nodes 269 | 270 | **Kind**: global function 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 281 | 282 |
ParamTypeDescription
[values]null | string | Array.<string> | BlankNode | Array.<BlankNode> | Clownface | Array.<Clownface>

blank node identifiers (generates it when falsy) or existing RDF/JS blank node(s)

280 |
283 | 284 | 285 | 286 | ## literal(values, [languageOrDatatype]) ⇒ Clownface 287 | Creates graph pointer to one or more literal nodes 288 | 289 | **Kind**: global function 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 300 | 301 | 303 | 304 |
ParamTypeDescription
valuesstring | Array.<string> | boolean | Array.<boolean> | number | Array.<number> | Literal | Array.<Literal> | Clownface | Array.<Clownface>

literal values as JS objects or RDF/JS Literal(s)

299 |
[languageOrDatatype]string | Term

a language tag string or datatype term

302 |
305 | 306 | 307 | 308 | ## namedNode(values) ⇒ Clownface 309 | Creates graph pointer to one or more named nodes 310 | 311 | **Kind**: global function 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 322 | 323 |
ParamTypeDescription
valuesstring | Array.<string> | NamedNode | Array.<NamedNode> | Clownface | Array.<Clownface>

URI(s) or RDF/JS NamedNode(s)

321 |
324 | 325 | 326 | 327 | ## in([predicates]) ⇒ Clownface 328 | Creates a graph pointer to nodes which are linked to the current pointer by `predicates` 329 | 330 | **Kind**: global function 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 341 | 342 |
ParamTypeDescription
[predicates]Term | Array.<Term> | Clownface | Array.<Clownface>

one or more RDF/JS term identifying a property

340 |
343 | 344 | 345 | 346 | ## out([predicates], [options]) ⇒ Clownface 347 | Creates a graph pointer to the result nodes after following a predicate, or after 348 | following any predicates in an array, starting from the subject(s) (current graph pointer) to the objects. 349 | 350 | **Kind**: global function 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 361 | 362 | 363 | 364 | 365 | 366 |
ParamTypeDescription
[predicates]Term | Array.<Term> | Clownface | Array.<Clownface>

any predicates to follow

360 |
[options]object
[options.language]string | Array.<string> | undefined
367 | 368 | 369 | 370 | ## has(predicates, [objects]) ⇒ Clownface 371 | Creates a graph pointer to nodes which are subjects of predicates, optionally also with specific objects 372 | 373 | If the current context is empty, will check all potential subjects 374 | 375 | **Kind**: global function 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 386 | 387 | 389 | 390 |
ParamTypeDescription
predicatesTerm | Array.<Term> | Clownface | Array.<Clownface>

RDF property identifiers

385 |
[objects]*

object values to match

388 |
391 | 392 | 393 | 394 | ## addIn(predicates, subjects, [callback]) ⇒ Clownface 395 | Creates a new quad(s) in the dataset where the current context is the object 396 | 397 | **Kind**: global function 398 | **Returns**: Clownface - current graph pointer 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 411 | 412 | 414 | 415 |
ParamTypeDescription
predicatesTerm | Array.<Term> | Clownface | Array.<Clownface>
subjectsNamedNode | Array.<NamedNode> | Clownface | Array.<Clownface>

one or more nodes to use as subjects

410 |
[callback]GraphPointerCallback

called for each object, with subject pointer as parameter

413 |
416 | 417 | 418 | 419 | ## addOut(predicates, objects, [callback]) ⇒ Clownface 420 | Creates a new quad(s) in the dataset where the current context is the subject 421 | 422 | **Kind**: global function 423 | **Returns**: Clownface - current graph pointer 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 436 | 437 | 439 | 440 |
ParamTypeDescription
predicatesTerm | Array.<Term> | Clownface | Array.<Clownface>
objects*

one or more values to use for objects

435 |
[callback]GraphPointerCallback

called for each subject, with object pointer as parameter

438 |
441 | 442 | 443 | 444 | ## addList(predicates, items) ⇒ Clownface 445 | Creates a new RDF list or lists containing the given items 446 | 447 | **Kind**: global function 448 | **Returns**: Clownface - current graph pointer 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 461 | 462 |
ParamTypeDescription
predicatesTerm | Array.<Term> | Clownface | Array.<Clownface>
items*

one or more values to use for subjects

460 |
463 | 464 | 465 | 466 | ## deleteIn([predicates], [subjects]) ⇒ Clownface 467 | Deletes all quads where the current graph pointer contexts are the objects 468 | 469 | **Kind**: global function 470 | **Returns**: Clownface - current graph pointer 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 |
ParamType
[predicates]Term | Array.<Term> | Clownface | Array.<Clownface>
[subjects]Term | Array.<Term> | Clownface | Array.<Clownface>
484 | 485 | 486 | 487 | ## deleteOut([predicates], [objects]) ⇒ Clownface 488 | Deletes all quads where the current graph pointer contexts are the subjects 489 | 490 | **Kind**: global function 491 | **Returns**: Clownface - current graph pointer 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 |
ParamType
[predicates]Term | Array.<Term> | Clownface | Array.<Clownface>
[objects]Term | Array.<Term> | Clownface | Array.<Clownface>
505 | 506 | 507 | 508 | ## deleteList(predicates) ⇒ Clownface 509 | Deletes entire RDF lists where the current graph pointer is the subject 510 | 511 | **Kind**: global function 512 | **Returns**: Clownface - current graph pointer 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 |
ParamType
predicatesTerm | Array.<Term> | Clownface | Array.<Clownface>
524 | 525 | 526 | 527 | ## filterTaggedLiterals(terms, [options]) ⇒ Array.<Term> 528 | **Kind**: global function 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 |
ParamType
termsArray.<Term>
[options]object
[options.language]string | Array.<string>
544 | 545 | 546 | 547 | ## GraphPointerCallback : function 548 | **Kind**: global typedef 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 559 | 560 |
ParamTypeDescription
pointerClownface

graph pointer to the new or existing node

558 |
561 | 562 | 563 | 564 | ## FilterCallback ⇒ boolean 565 | **Kind**: global typedef 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 |
ParamType
pointerClownface
indexnumber
pointersArray.<Clownface>
581 | 582 | 583 | 584 | ## ForEachCallback : function 585 | **Kind**: global typedef 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 |
ParamType
pointerClownface
597 | 598 | 599 | 600 | ## MapCallback ⇒ T 601 | **Kind**: global typedef 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 |
ParamType
pointerClownface
613 | 614 | -------------------------------------------------------------------------------- /docs/context.md: -------------------------------------------------------------------------------- 1 | # Clownface context 2 | 3 | A `Clownface` object represents 0 or more nodes in an underlying dataset. There are two pairs of properties (getters) which return those nodes: 4 | 5 | 1. `term`/`terms` which return RDF/JS terms 6 | 3. `value`/`values` which return string representation of said terms 7 | 8 | It is important to understand their semantics. 9 | 10 | The singular `term` and `value` return an object only when the `Clownface` instance represents a single term. If the context is 0 nodes or >1 nodes, they return `undefined` 11 | 12 | The plural `terms` and `values` always return an array of objects, or an empty array. 13 | 14 | ## Initialising the context 15 | 16 | As presented in the [getting started](/) and [deep dive](deep-dive.md) examples, a clownface instance is initially created using a factory function exported by the module. Depending on the parameters passed to it, various contexts can be created. 17 | 18 | ### Empty initial context 19 | 20 | An empty context is created by providing only a `dataset` parameter. 21 | 22 | This state is quite unique, because this is the only circumstance in which a context will be empty. The moment any node gets pointed, the context will never return to this state. 23 | 24 | 25 | 26 | ```js 27 | const rdf = require('@zazuko/env-bundle') 28 | 29 | // only dataset is a required parameter 30 | const graphPointer = rdf.clownface() 31 | 32 | // term/value are undefined 33 | // terms/values are empty arrays 34 | const context = { 35 | term: graphPointer.term, 36 | value: graphPointer.value, 37 | terms: graphPointer.terms, 38 | values: graphPointer.values 39 | } 40 | ``` 41 | 42 | 43 | 44 | ### A single pointer 45 | 46 | The context can represent a single graph pointer, that is a single node in the graph. 47 | 48 | 49 | 50 | ```js 51 | const rdf = require('@zazuko/env-bundle') 52 | 53 | // the initial term can be initialized in the factory method 54 | const graphPointer = rdf.clownface({ term: rdf.ns.schema.Person }) 55 | 56 | // term/value are defined 57 | // terms/values are single-element arrays 58 | const context = { 59 | term: graphPointer.term, 60 | value: graphPointer.value, 61 | terms: graphPointer.terms, 62 | values: graphPointer.values 63 | } 64 | ``` 65 | 66 | 67 | 68 | ### Multiple pointers 69 | 70 | A multi-pointer state is also possible. The pointer can point to multiple nodes simultaneously. In that case `term` and `value` are going to be undefined and only their array variants will return those nodes. 71 | 72 | The return value of all [graph traversals](traversal.md) potentially returns a multi-pointer, thus always remember to check if `term/value` is defined. 73 | 74 | 75 | 76 | ```js 77 | const rdf = require('@zazuko/env-bundle') 78 | 79 | // the term can also be an array of RDF/JS nodes 80 | const graphPointer = rdf.clownface({ term: [ rdf.ns.schema.Person, rdf.ns.foaf.Person ] }) 81 | 82 | // term/value are undefined 83 | // terms/values are arrays 84 | const context = { 85 | term: graphPointer.term, 86 | value: graphPointer.value, 87 | terms: graphPointer.terms, 88 | values: graphPointer.values 89 | } 90 | ``` 91 | 92 | 93 | 94 | ## Switching context 95 | 96 | At any time the graph pointer can be moved to another node using one of a few methods of self-descriptive names: 97 | 98 | - `node` 99 | - `namedNode` 100 | - `blankNode` 101 | - `literal` 102 | 103 | Note that switching the pointer always returns a new `Clownface` object. Also, the new pointed node does not have to exist in the dataset. Simply changing the pointer also does not modify the dataset. 104 | 105 | 106 | 107 | ```js 108 | const rdf = require('@zazuko/env-bundle') 109 | 110 | const ex = rdf.namespace('http://example.com/') 111 | 112 | // the term can also be an array of RDF/JS nodes 113 | const graphPointer = rdf.clownface({ term: ex.foo }) 114 | 115 | const contexts = { 116 | // namedNode treat string as URI 117 | named: graphPointer.namedNode('http://example.com/bar').term, 118 | 119 | // literal can have language or datatype 120 | literal: graphPointer.literal('10', rdf.ns.xsd.nonNegativeInteger).term, 121 | 122 | // blank node does not require parameters 123 | blank: graphPointer.blankNode().term, 124 | 125 | // `node` will attempt to detect the term type 126 | // but be careful with URI strings 127 | detected: graphPointer.node('http://example.com/bar').term, 128 | 129 | // the original object still points at the original node 130 | original: graphPointer.term, 131 | } 132 | ``` 133 | 134 | 135 | 136 | Each one of those methods also accept RDF/JS terms and instances of `Clownface` itself as well as array to create a multiple pointer context. Do refer to [the API page](api.md) for details about their parameters. 137 | -------------------------------------------------------------------------------- /docs/deep-dive.md: -------------------------------------------------------------------------------- 1 | # Deep dive 2 | 3 | If we would like to get the given name and family name of all persons known by e.g. Stuart Bloom 4 | (from [tbbt-ld][tbbt]) we can write: 5 | 6 | 7 | 8 | ```js 9 | const rdf = require('@zazuko/env-bundle') 10 | 11 | // load the tbbt-ld graph 12 | const dataset = await rdf.fetch('http://zazuko.github.io/tbbt-ld/dist/tbbt.nt') 13 | .then(response => response.dataset()) 14 | 15 | // turn the RDF/JS dataset into a Clownface context 16 | const tbbt = rdf.clownface({ dataset }) 17 | 18 | // get a starting node inside the dataset 19 | const stuartBloom = tbbt.namedNode('http://localhost:8080/data/person/stuart-bloom') 20 | 21 | // query for all people Stuart knows and print their full name 22 | stuartBloom 23 | // get all nodes connected through schema:knows 24 | .out(rdf.ns.schema.knows) 25 | // for every result 26 | .map((person) => { 27 | // get their schema:givenName and schema:familyName 28 | const personalInformation = person.out([ 29 | rdf.ns.schema.givenName, 30 | rdf.ns.schema.familyName 31 | ]) 32 | // join the givenName and familyName with a space 33 | return personalInformation.values.join(' ') 34 | }) 35 | // join the list of names with a comma 36 | .join(', ') 37 | ``` 38 | 39 | 40 | 41 | [tbbt]: https://github.com/zazuko/tbbt-ld 42 | 43 | ## Notes 44 | 45 | See that unlike the [Getting started](/#getting-started) example, which uses absolute URIs to refer to terms, the code above imports the [@tpluscode/rdf-ns-builders](https://npm.im/@tpluscode/rdf-ns-builders) library which exports a number of common RDF vocabularies wrapped as [namespace builder objects](https://npm.im/@rdfjs/namespace). 46 | 47 | Thus, typing `schema.givenName` is equivalent to `namedNode('http://schema.org/givenName')` 48 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | clownface - Simple but powerful graph traversing library 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/manipulation.md: -------------------------------------------------------------------------------- 1 | # Manipulating graph 2 | 3 | Clownface can not only navigate existing quads in an in-memory dataset but also add and remove new nodes and edges. 4 | 5 | ## Adding relations 6 | 7 | The basic functionality is to add triples. This can be done both where the graph pointer is the subject (**out**) and where it is the object (**in**). 8 | 9 | They also can be chained as the return value is the original context. 10 | 11 | 12 | 13 | ```js 14 | const rdf = require('@zazuko/env-bundle') 15 | const { turtle } = require('@tpluscode/rdf-string@0.2.26') 16 | 17 | // Create a pointer for Leonard 18 | const pointer = rdf.clownface() 19 | .namedNode('https://bigbangtheory.tv/Leonard') 20 | 21 | // Add a triple where Leonard is the subject 22 | // Then add a triple where Leonard is the object 23 | pointer 24 | .addOut(rdf.ns.rdf.type, rdf.ns.schema.Person) 25 | .addIn(rdf.ns.schema.spouse, pointer.namedNode('https://bigbangtheory.tv/Penny')) 26 | 27 | turtle`${pointer.dataset}`.toString() 28 | ``` 29 | 30 | 31 | 32 | Note that due to the nature of RDF graphs, simply calling `namedNode` does not yet create any data. That happens only when `addOut` is called because the graph cannot represent disconnected nodes. Every node has to participate in a relation with another node. 33 | 34 | ## Creating deep graphs 35 | 36 | The `addIn`/`addOut` methods accept an optional callback parameter which gets called with the created pointer. This allows chaining multiple levels of triple, which will produce a deep structure. 37 | 38 | 39 | 40 | ```js 41 | const rdf = require('@zazuko/env-bundle') 42 | const { turtle } = require('@tpluscode/rdf-string@0.2.26') 43 | 44 | // Create a pointer for Leonard 45 | const pointer = rdf.clownface() 46 | .namedNode('https://bigbangtheory.tv/Leonard') 47 | 48 | pointer 49 | .addOut(rdf.ns.schema.spouse, pointer.namedNode('https://bigbangtheory.tv/Penny'), penny => { 50 | penny.addOut(rdf.ns.rdf.type, rdf.ns.schema.Person) 51 | }) 52 | 53 | // the callback can also replace the value to generate a blank node 54 | pointer 55 | .addOut(rdf.ns.schema.address, address => { 56 | address.addOut(rdf.ns.schema.addressLocality, 'Pasadena') 57 | }) 58 | 59 | turtle`${pointer.dataset}`.toString() 60 | ``` 61 | 62 | 63 | 64 | ## Removing relations 65 | 66 | Analogous to adding, there are two methods for removing quads: `deleteIn` and `deleteOut`. They only take a single parameter for the predicate and will remove all subject's/object's triples where that predicate is the property. 67 | 68 | 69 | 70 | ```js 71 | const rdf = require('@zazuko/env-bundle') 72 | const { turtle } = require('@tpluscode/rdf-string@0.2.26') 73 | 74 | const leonard = rdf.namedNode('https://bigbangtheory.tv/Leonard') 75 | const quads = [ 76 | rdf.quad(leonard, rdf.ns.schema.name, 'Leonard'), 77 | rdf.quad(leonard, rdf.ns.schema.name, 'Leonard Hofstadder'), 78 | rdf.quad(leonard, rdf.ns.schema.familyName, 'Hofstadder'), 79 | ] 80 | 81 | // initially contains 3 quads 82 | const pointer = rdf.clownface({ dataset: rdf.dataset(quads) }) 83 | 84 | // remove all schema:name triples 85 | pointer.deleteOut(rdf.ns.schema.name) 86 | 87 | // one triples remains 88 | turtle`${pointer.dataset}`.toString() 89 | ``` 90 | 91 | 92 | 93 | ## Multiple pointers 94 | 95 | A Clownface instance which represents multiple graph pointers can also be used the same way with the `addOut`/`addIn` methods. 96 | 97 | - A triple will asserted for every subject/object respectively 98 | - The callback gets invoked for every graph pointer of the originating instance 99 | 100 | 101 | 102 | ```js 103 | const rdf = require('@zazuko/env-bundle') 104 | const { turtle } = require('@tpluscode/rdf-string@0.2.26') 105 | 106 | const tbbt = rdf.namespace('https://bigbangtheory.tv/') 107 | 108 | const dataset = rdf.dataset() 109 | const characters = rdf.clownface({ dataset }) 110 | .node([tbbt.Leonard, tbbt.Penny, tbbt.Stewart]) 111 | 112 | // every character is a Person 113 | characters 114 | .addOut(rdf.ns.rdf.type, rdf.ns.schema.Person) 115 | .addOut([rdf.ns.schema.knows, rdf.ns.foaf.knows], [tbbt.Penny, tbbt.Amy], girl => { 116 | // this will be called 3 times 117 | girl.addOut(rdf.ns.rdf.type, rdf.ns.schema.Person) 118 | }) 119 | 120 | console.log(`The dataset now has ${dataset.size} triples`) 121 | 122 | turtle`${dataset}`.toString() 123 | ``` 124 | 125 | 126 | 127 | This example also shows how passing multiple properties, multiple objects (or subjects if it was `addIn`) and multiple graph pointers represented by `characters` will result in cartesian product triples for every subject/predicat/object combination. 128 | -------------------------------------------------------------------------------- /docs/named-graphs.md: -------------------------------------------------------------------------------- 1 | # Working with named graphs 2 | 3 | ## Default behavior 4 | 5 | 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. 6 | 7 | 8 | 9 | ```js 10 | const rdf = require('@zazuko/env-bundle') 11 | const { nquads } = require('@tpluscode/rdf-string@0.2.26') 12 | 13 | const tbbt = rdf.namespace('https://bigbangtheory.tv/') 14 | 15 | const quads = [ 16 | rdf.quad(tbbt.Leonard, rdf.ns.schema.knows, tbbt.Amy, tbbt.Amy), 17 | rdf.quad(tbbt.Leonard, rdf.ns.schema.knows, tbbt.Sheldon, tbbt.Sheldon), 18 | ] 19 | 20 | const leonard = rdf.clownface({ dataset: rdf.dataset(quads) }) 21 | .namedNode(tbbt.Leonard) 22 | .addOut(rdf.ns.schema.name, 'Leonard') 23 | 24 | console.log('Leonard knows ' + leonard.out(rdf.ns.schema.knows).values.join(', ')) 25 | 26 | nquads`${leonard.dataset}`.toString() 27 | ``` 28 | 29 | 30 | 31 | ## Single graph 32 | 33 | A graph identifier can be passed to the factory call, which narrows down the context to only a specific graph. 34 | 35 | 36 | 37 | ```js 38 | const rdf = require('@zazuko/env-bundle') 39 | const { nquads } = require('@tpluscode/rdf-string@0.2.26') 40 | 41 | const tbbt = rdf.namespace('https://bigbangtheory.tv/') 42 | 43 | const quads = [ 44 | rdf.quad(tbbt.Leonard, rdf.ns.schema.name, 'Leonard', tbbt.Leonard), 45 | rdf.quad(tbbt.Sheldon, rdf.ns.schema.name, 'Sheldon', tbbt.Sheldon), 46 | ] 47 | 48 | // pass a NamedNode 49 | // or use RDF.defaultGraph() for the default graph 50 | const leonard = rdf.clownface({ dataset: rdf.dataset(quads), graph: tbbt.Leonard }) 51 | .node(tbbt.Leonard) 52 | .addOut(rdf.ns.schema.knows, tbbt.Sheldon) 53 | 54 | nquads`${leonard.dataset}`.toString() 55 | ``` 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/rdf-lists.md: -------------------------------------------------------------------------------- 1 | # RDF Lists 2 | 3 | RDF Lists are unavoidable when working with RDF data and their bare triples representation make it necessary to provide specialized programmatic APIs to handle. 4 | 5 | Clownface comes with handy methods to create, iterate and remove lists from RDF datasets. 6 | 7 | - `addList` method is similar to `addOut` but requires an array of object which it links as an `rdf:List` 8 | - `list` method returns an `Iterator` of the values of an `rdf:List` 9 | - finally `deleteList` removes the list nodes 10 | 11 | 12 | 13 | ```js 14 | const rdf = require('@zazuko/env-bundle') 15 | const { turtle } = require('@tpluscode/rdf-string@0.2.26') 16 | 17 | const ex = rdf.namespace('http://example.com/') 18 | 19 | const game = rdf.clownface() 20 | .namedNode(ex.game) 21 | 22 | // Create a list with 3 elements 23 | game.addList(ex.score, [ex.score1, ex.score2, ex.score3]) 24 | 25 | // Add statements about the list elements 26 | let scoreIndex = 1 27 | for (const score of game.out(ex.score).list()) { 28 | score.addOut(rdf.ns.dtype.orderIndex, scoreIndex++) 29 | } 30 | 31 | // Remove the list but not statements about the individual scores 32 | game.deleteList(ex.score) 33 | 34 | turtle`${game.dataset}`.toString() 35 | ``` 36 | 37 | 38 | 39 | ## Handling non-lists 40 | 41 | The `list()` method will return null when the object is not a list (such as a literal or without a `rdf:first`). A little bit of defensive programming can be employed to conditionally iterate a list or get the non-list objects of a property. 42 | 43 | 44 | 45 | ```js 46 | const rdf = require('@zazuko/env-bundle') 47 | const { turtle } = require('@tpluscode/rdf-string@0.2.26') 48 | 49 | const ex = rdf.namespace('http://example.com/') 50 | 51 | const game = rdf.clownface() 52 | .namedNode(ex.game) 53 | 54 | // add object directly to ex:score 55 | game.addOut(ex.score, [ex.score1, ex.score2, ex.score3]) 56 | 57 | // pretend we don't know if the value is a list of scores 58 | const scores = game.out(ex.score) 59 | const list = scores.list() 60 | 61 | // iterate the raw objects as alternative 62 | ;(list && [...list]) || scores.toArray() 63 | ``` 64 | 65 | 66 | -------------------------------------------------------------------------------- /docs/tagged-literals.md: -------------------------------------------------------------------------------- 1 | # Literals with language tags 2 | 3 | Using the `.out()` method it is possible to only find literals in specific languages by passing a second `{ language }` parameter to the method. 4 | 5 | When that parameter is defined, only string literal nodes will be returned. 6 | 7 | For any given subject, all strings in the chosen language will be returned. 8 | 9 | ## Finding specific language 10 | 11 | To find string literal in a given language, pass a second object argument with a string `language` key. 12 | 13 | 14 | 15 | ```js 16 | const rdf = require('@zazuko/env-bundle') 17 | 18 | // create two labels for a resource 19 | const apple = rdf.clownface() 20 | .node(rdf.ns.rdf.Resource) 21 | .addOut(rdf.ns.rdfs.label, rdf.literal('apple', 'en')) 22 | .addOut(rdf.ns.rdfs.label, rdf.literal('Apfel', 'de')) 23 | 24 | // find German label 25 | apple.out(rdf.ns.rdfs.label, { language: 'de' }).value 26 | ``` 27 | 28 | 29 | 30 | ## Finding plain literals 31 | 32 | Using an empty string for the `language` parameter will find strings without a language. 33 | 34 | 35 | 36 | ```js 37 | const rdf = require('@zazuko/env-bundle') 38 | 39 | // create two labels for a resource 40 | const apple = rdf.clownface() 41 | .node(rdf.ns.rdf.Resource) 42 | .addOut(rdf.ns.rdfs.label, rdf.literal('apple')) 43 | .addOut(rdf.ns.rdfs.label, rdf.literal('Apfel', 'de')) 44 | 45 | // find literal without language tag 46 | apple.out(rdf.ns.rdfs.label, { language: '' }).value 47 | ``` 48 | 49 | 50 | 51 | ## Finding from a choice of potential languages 52 | 53 | It is possible to look up the literals in multiple alternatives byt providing an array of languages instead. The first language which gets matched to the literals will be used. 54 | 55 | 56 | 57 | ```js 58 | const rdf = require('@zazuko/env-bundle') 59 | 60 | // create two labels for a resource 61 | const apple = rdf.clownface() 62 | .node(rdf.ns.rdf.Resource) 63 | .addOut(rdf.ns.rdfs.label, rdf.literal('apple', 'en')) 64 | .addOut(rdf.ns.rdfs.label, rdf.literal('Apfel', 'de')) 65 | 66 | // there is no French translation so English will be returned 67 | apple.out(rdf.ns.rdfs.label, { language: ['fr', 'en'] }).value 68 | ``` 69 | 70 | 71 | 72 | A wildcard (asterisk) can also be used to choose any other (random) literal if the preceding choices did not yield any results. It would look similarly to previous example. 73 | 74 | ```js 75 | apple.out(rdf.ns.rdfs.label, { language: ['fr', '*'] }).value 76 | ``` 77 | 78 | !> The result can be either English or German with equal probability. 79 | 80 | ## Matching subtags 81 | 82 | In specific cases [subtags](https://tools.ietf.org/html/bcp47#section-2.2), such as `de-CH` can be matched to a given language. By analogy, it is also possible to find a subtag of any length by applying a "starts with" match. 83 | 84 | For example, in the snippet below the more specific subtag `de-CH-1996` will indeed be matched to the more general Swiss German `de-CH` 85 | 86 | 87 | 88 | ```js 89 | const rdf = require('@zazuko/env-bundle') 90 | 91 | // create two labels for a resource 92 | const bicycle = rdf.clownface() 93 | .node(rdf.ns.rdf.Resource) 94 | .addOut(rdf.ns.rdfs.label, rdf.literal('Fahrrad', 'de')) 95 | .addOut(rdf.ns.rdfs.label, rdf.literal('Velo', 'de-CH-1996')) 96 | 97 | // finds a Swiss translation 98 | bicycle.out(rdf.ns.rdfs.label, { language: 'de-CH' }).value 99 | ``` 100 | 101 | 102 | 103 | !> However, any exact match will always take precedence before the subtag match 104 | 105 | 106 | 107 | ```js 108 | const rdf = require('@zazuko/env-bundle') 109 | 110 | // create two labels for a resource 111 | const bicycle = rdf.clownface() 112 | .node(rdf.ns.rdf.Resource) 113 | .addOut(rdf.ns.rdfs.label, rdf.literal('Fahrrad', 'de')) 114 | .addOut(rdf.ns.rdfs.label, rdf.literal('Velo', 'de-CH-1996')) 115 | 116 | // finds the standard German label 117 | bicycle.out(rdf.ns.rdfs.label, { language: 'de' }).value 118 | ``` 119 | 120 | 121 | -------------------------------------------------------------------------------- /docs/traversal.md: -------------------------------------------------------------------------------- 1 | # Graph traversal 2 | 3 | A core functionality of clownface is moving between nodes along properties, or edges in graph lingo. That is done by chainable `out`/`in` methods which accept zero more properties as arguments. These methods move the graph pointer(s) from the current context by following properties where it is the `subject` or `object` respectively. 4 | 5 | When called with no properties (or empty array), all properties are traversed and returned as a context of multiple graph pointers (if multiple nodes were found that is). 6 | 7 | 8 | 9 | ```js 10 | const rdf = require('@zazuko/env-bundle') 11 | 12 | const dataset = await rdf.fetch('http://zazuko.github.io/tbbt-ld/dist/tbbt.nt') 13 | .then(response => response.dataset()) 14 | 15 | rdf.clownface({ dataset }) 16 | .namedNode('http://localhost:8080/data/person/howard-wolowitz') 17 | .in(rdf.ns.schema.spouse) 18 | .out(rdf.ns.schema.knows) 19 | .out(rdf.ns.schema.address) 20 | .out(rdf.ns.schema.addressLocality) 21 | .values 22 | ``` 23 | 24 | 25 | 26 | It is important that every call to `in` or `out` may return multiple pointers and they may be duplicate instances of the same node but are indistinguishable. If multiple predicates are used it is currently impossible to tell which one was traversed to reach any given node. 27 | 28 | The above traversal would be equivalent to a SPARQL property path `^schema:spouse/schema:knows/schema:address/schema:addressLocality`. 29 | 30 | ## Node lookup 31 | 32 | Another way for getting to the desired nodes is to find them using the `has` method. It has two parameters: properties and objects. The first one is required and narrows down the context to only those subjects where the property is used. The results can be narrowed down further by passing a second argument which filter to specific object. 33 | 34 | Both those parameters and be a single value or an array. 35 | 36 | When the context does not represent any pointer, all subjects from the dataset are considered. 37 | 38 | 39 | 40 | ```js 41 | const rdf = require('@zazuko/env-bundle') 42 | 43 | const dataset = await rdf.fetch('http://zazuko.github.io/tbbt-ld/dist/tbbt.nt') 44 | .then(response => response.dataset()) 45 | 46 | const howard = rdf.namedNode('http://localhost:8080/data/person/howard-wolowitz') 47 | 48 | // all people whose spouse is Howard 49 | const hasSpouse = rdf.clownface({ dataset }) 50 | .has(rdf.ns.schema.spouse, howard) 51 | .values 52 | ``` 53 | 54 | 55 | -------------------------------------------------------------------------------- /examples/namespace.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import namespace from '@rdfjs/namespace' 3 | import clownface from '../index.js' 4 | import initExample from '../test/support/example.js' 5 | 6 | initExample().then(dataset => { 7 | const people = namespace('http://localhost:8080/data/person/') 8 | const schema = namespace('http://schema.org/') 9 | 10 | const tbbt = clownface({ dataset }) 11 | 12 | const stuartBloom = tbbt.node(people('stuart-bloom')) 13 | 14 | console.log(`name of ${stuartBloom.values[0]}:`) 15 | console.log( 16 | stuartBloom 17 | .out([schema.givenName, schema.familyName]) 18 | .values.join(' '), 19 | ) 20 | 21 | console.log(`people ${stuartBloom.values[0]} knows:`) 22 | console.log( 23 | stuartBloom 24 | .out(schema.knows) 25 | .map(person => person 26 | .out([schema.givenName, schema.familyName]) 27 | .values.join(' '), 28 | ) 29 | .join(', '), 30 | ) 31 | 32 | const apartment4a = tbbt.node('2311 North Los Robles Avenue, Aparment 4A') 33 | 34 | console.log(`people who live in ${apartment4a.values[0]}`) 35 | console.log( 36 | apartment4a 37 | .in(schema.streetAddress) 38 | .in(schema.address) 39 | .map(person => person.out(schema.givenName).value) 40 | .join(', '), 41 | ) 42 | 43 | const neurobiologist = tbbt.has(schema.jobTitle, 'neurobiologist') 44 | 45 | console.log('people with the job title neurobiologist') 46 | console.log( 47 | neurobiologist 48 | .out(schema.givenName) 49 | .values.join(', '), 50 | ) 51 | }) 52 | -------------------------------------------------------------------------------- /filter.js: -------------------------------------------------------------------------------- 1 | import { filterTaggedLiterals } from './lib/languageTag.js' 2 | 3 | /** 4 | * Returns a function to be used as callback to `Clownface#filter`. 5 | * It will return only literals which match the given `language(s)` 6 | * 7 | * @param {string | string[]} language 8 | * @returns {function(Clownface, number, Clownface[]): boolean} 9 | */ 10 | export function taggedLiteral(language) { 11 | return (current, index, pointers) => { 12 | const found = filterTaggedLiterals(pointers.map(ptr => ptr.term), { language }) 13 | 14 | return found.some(term => current.term.equals(term)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Clownface from './lib/Clownface.js' 2 | import environment from './lib/environment.js' 3 | 4 | /** 5 | * Factory to create graph pointer objects 6 | * 7 | * @param {Object} init 8 | * @param {DatasetCore} init.dataset an RDF/JS dataset 9 | * @param {string|Term} [init.graph] graph URI 10 | * @param {Term|Term[]} [init.term] one or more RDF/JS term(s) which will be the pointer's context 11 | * @param {string} [init.value] one or more raw values which will create literal node as the pointer's context 12 | * @param {DataFactory} [init.factory=@rdfjs/environment] an RDF/JS factory which will be used to create nodes 13 | * @param {Context} [init._context] an existing clownface context. takes precedence before other params 14 | * @returns {Clownface} 15 | */ 16 | export default function factory({ dataset, graph, term, value, factory = environment, _context }) { 17 | return new Clownface({ dataset, graph, term, value, factory, _context }) 18 | } 19 | -------------------------------------------------------------------------------- /lib/Clownface.js: -------------------------------------------------------------------------------- 1 | import ns from './namespace.js' 2 | import toArray from './toArray.js' 3 | import toTermArray from './toTermArray.js' 4 | import Context from './Context.js' 5 | 6 | /** 7 | * A graph pointer object, which points at 0..N nodes within a dataset 8 | */ 9 | export default class Clownface { 10 | constructor({ dataset, graph, term, value, factory, _context }) { 11 | this.factory = factory 12 | this.namespace = ns(factory) 13 | 14 | if (_context) { 15 | this._context = _context 16 | 17 | return 18 | } 19 | 20 | const terms = (term && toArray(term)) || (value && toArray(value)) || [null] 21 | 22 | /** 23 | * The underlying context which references actual node being pointed 24 | * 25 | * @type {Context[]} 26 | * @private 27 | */ 28 | this._context = terms.map(term => { 29 | return new Context({ dataset, graph, value: term, factory: this.factory, namespace: this.namespace }) 30 | }) 31 | } 32 | 33 | /** 34 | * Gets the current RDF/JS term or undefined if pointer has no context 35 | * 36 | * @returns {undefined|Term} 37 | */ 38 | get term() { 39 | const terms = this.terms 40 | 41 | if (terms.length !== 1) { 42 | return undefined 43 | } 44 | 45 | return terms[0] 46 | } 47 | 48 | /** 49 | * Gets the current terms or an empty array if the pointer has no context 50 | * 51 | * @returns {Term[]} 52 | */ 53 | get terms() { 54 | return this._context.map(node => node.term).filter(Boolean) 55 | } 56 | 57 | /** 58 | * Gets the string representation of term 59 | * 60 | * @returns {undefined|string} 61 | */ 62 | get value() { 63 | const term = this.term 64 | 65 | return term && term.value 66 | } 67 | 68 | /** 69 | * Gets the string representation of terms 70 | * 71 | * @returns {string[]} 72 | */ 73 | get values() { 74 | return this.terms.map(term => term.value) 75 | } 76 | 77 | /** 78 | * Gets the current context's dataset, or undefined if there are multiple 79 | * 80 | * @returns {undefined|DatasetCore} 81 | */ 82 | get dataset() { 83 | const datasets = this.datasets 84 | 85 | if (datasets.length !== 1) { 86 | return undefined 87 | } 88 | 89 | return datasets[0] 90 | } 91 | 92 | /** 93 | * Gets the current context's datasets 94 | * 95 | * @returns {DatasetCore[]} 96 | */ 97 | get datasets() { 98 | return this._context.map(node => node.dataset).filter(Boolean) 99 | } 100 | 101 | /** 102 | * Removes current pointers from the context and return an "any pointer". 103 | * The returned object can be used to find any nodes in the dataset 104 | * 105 | * @returns {Clownface} 106 | */ 107 | any() { 108 | return Clownface.fromContext(this._context.map(current => current.clone({ })), this) 109 | } 110 | 111 | /** 112 | * Returns true if the current term is a rdf:List 113 | * 114 | * @returns {boolean} 115 | */ 116 | isList() { 117 | if (!this.term) { 118 | return false 119 | } 120 | 121 | // empty list 122 | if (this.term.equals(this.namespace.nil)) { 123 | return true 124 | } 125 | 126 | // list element 127 | if (this.out(this.namespace.first).term) { 128 | return true 129 | } 130 | 131 | return false 132 | } 133 | 134 | /** 135 | * Creates an iterator which iterates and rdf:List of the current term 136 | * 137 | * @returns {Iterable | null} 138 | */ 139 | list() { 140 | if (this.terms.length > 1) { 141 | throw new Error('iterator over multiple terms is not supported') 142 | } 143 | 144 | if (this.term) { 145 | if (this.term.termType !== 'NamedNode' && this.term.termType !== 'BlankNode') { 146 | return null 147 | } 148 | 149 | if (!this.term.equals(this.namespace.nil) && !this.out(this.namespace.first).term) { 150 | return null 151 | } 152 | } 153 | 154 | let item = this 155 | 156 | return { 157 | [Symbol.iterator]: () => { 158 | return { 159 | next: () => { 160 | if (!item.term || item.term.equals(this.namespace.nil)) { 161 | return { done: true } 162 | } 163 | 164 | const value = item.out(this.namespace.first) 165 | if (value.terms.length > 1) { 166 | throw new Error(`Invalid list: multiple values for rdf:first on ${item.value}`) 167 | } 168 | 169 | const rest = item.out(this.namespace.rest) 170 | if (rest.terms.length > 1) { 171 | throw new Error(`Invalid list: multiple values for rdf:rest on ${item.value}`) 172 | } 173 | 174 | item = rest 175 | 176 | return { done: false, value } 177 | }, 178 | } 179 | }, 180 | } 181 | } 182 | 183 | /** 184 | * Returns an array of graph pointers where each one has a single _context 185 | * 186 | * @returns {Clownface[]} 187 | */ 188 | toArray() { 189 | return this._context 190 | .map(context => Clownface.fromContext(context, this)) 191 | .filter(context => context.terms.some(Boolean)) 192 | } 193 | 194 | /** 195 | * Returns graph pointers which meet the condition specified in a callback function 196 | * @param {FilterCallback} callback 197 | * @returns {Clownface} 198 | */ 199 | filter(callback) { 200 | const pointers = this._context.map(context => Clownface.fromContext(context, this)) 201 | 202 | return Clownface.fromContext(this._context.filter((context, index) => callback(Clownface.fromContext(context, this), index, pointers)), this) 203 | } 204 | 205 | /** 206 | * Performs the specified action on every graph pointer 207 | * @param {ForEachCallback} callback 208 | * @returns {Clownface} 209 | */ 210 | forEach(callback) { 211 | this.toArray().forEach(callback) 212 | return this 213 | } 214 | 215 | /** 216 | * Calls a defined callback function on each graph pointer, and returns an array that contains the results. 217 | * @template T 218 | * @param {MapCallback} callback 219 | * @returns {T[]} 220 | */ 221 | map(callback) { 222 | return this.toArray().map(callback) 223 | } 224 | 225 | toString() { 226 | return this.values.join() 227 | } 228 | 229 | /** 230 | * Creates graph pointer to one or more node(s) 231 | * 232 | * Depending on the value creates pointers to: 233 | * 234 | * - blank node context for null `values` 235 | * - literal for string `values` and no `options` paramter 236 | * - matching RDF/JS term 237 | * - term created according to `options.type` parameter 238 | * 239 | * @param {null|string|string[]|Term|Term[]|Clownface|Clownface[]} values 240 | * @param {Object} [options] 241 | * @param {"NamedNode"|"BlankNode"|"Literal"} [options.type] explicit type for nodes 242 | * @param {string} [options.language] language tag of literals 243 | * @param {string} [options.datatype] datatype of literals 244 | * @returns {Clownface} 245 | */ 246 | node(values, { type, datatype, language } = {}) { 247 | values = this._toTermArray(values, type, datatype || language) || [null] 248 | 249 | const context = values.reduce((context, value) => { 250 | return context.concat(this._context.reduce((all, current) => { 251 | return all.concat([current.clone({ value })]) 252 | }, [])) 253 | }, []) 254 | 255 | return Clownface.fromContext(context, { factory: this.factory }) 256 | } 257 | 258 | /** 259 | * Creates graph pointer to one or more blank nodes 260 | * @param {null|string|string[]|BlankNode|BlankNode[]|Clownface|Clownface[]} [values] blank node identifiers (generates it when falsy) or existing RDF/JS blank node(s) 261 | * @returns {Clownface} 262 | */ 263 | blankNode(values) { 264 | return this.node(values, { type: 'BlankNode' }) 265 | } 266 | 267 | /** 268 | * Creates graph pointer to one or more literal nodes 269 | * @param {string|string[]|boolean|boolean[]|number|number[]|Literal|Literal[]|Clownface|Clownface[]} values literal values as JS objects or RDF/JS Literal(s) 270 | * @param {string|Term} [languageOrDatatype] a language tag string or datatype term 271 | * @returns {Clownface} 272 | */ 273 | literal(values, languageOrDatatype) { 274 | return this.node(values, { type: 'Literal', datatype: languageOrDatatype }) 275 | } 276 | 277 | /** 278 | * Creates graph pointer to one or more named nodes 279 | * @param {string|string[]|NamedNode|NamedNode[]|Clownface|Clownface[]} values URI(s) or RDF/JS NamedNode(s) 280 | * @returns {Clownface} 281 | */ 282 | namedNode(values) { 283 | return this.node(values, { type: 'NamedNode' }) 284 | } 285 | 286 | /** 287 | * Creates a graph pointer to nodes which are linked to the current pointer by `predicates` 288 | * @param {Term|Term[]|Clownface|Clownface[]} [predicates] one or more RDF/JS term identifying a property 289 | * @returns {Clownface} 290 | */ 291 | in(predicates) { 292 | predicates = this._toTermArray(predicates) 293 | 294 | const context = this._context.reduce((all, current) => all.concat(current.in(predicates)), []) 295 | 296 | return Clownface.fromContext(context, this) 297 | } 298 | 299 | /** 300 | * Creates a graph pointer to the result nodes after following a predicate, or after 301 | * following any predicates in an array, starting from the subject(s) (current graph pointer) to the objects. 302 | * @param {Term|Term[]|Clownface|Clownface[]} [predicates] any predicates to follow 303 | * @param {object} [options] 304 | * @param {string | string[] | undefined} [options.language] 305 | * @returns {Clownface} 306 | */ 307 | out(predicates, options = {}) { 308 | predicates = this._toTermArray(predicates) 309 | 310 | const context = this._context.reduce((all, current) => all.concat(current.out(predicates, options)), []) 311 | 312 | return Clownface.fromContext(context, this) 313 | } 314 | 315 | /** 316 | * Creates a graph pointer to nodes which are subjects of predicates, optionally also with specific objects 317 | * 318 | * If the current context is empty, will check all potential subjects 319 | * 320 | * @param {Term|Term[]|Clownface|Clownface[]} predicates RDF property identifiers 321 | * @param {*} [objects] object values to match 322 | * @returns {Clownface} 323 | */ 324 | has(predicates, objects) { 325 | predicates = this._toTermArray(predicates) 326 | objects = this._toTermArray(objects) 327 | 328 | const context = this._context.reduce((all, current) => all.concat(current.has(predicates, objects)), []) 329 | 330 | return Clownface.fromContext(context, this) 331 | } 332 | 333 | /** 334 | * Creates a new quad(s) in the dataset where the current context is the object 335 | * 336 | * @param {Term|Term[]|Clownface|Clownface[]} predicates 337 | * @param {NamedNode|NamedNode[]|Clownface|Clownface[]} subjects one or more nodes to use as subjects 338 | * @param {GraphPointerCallback} [callback] called for each object, with subject pointer as parameter 339 | * @returns {Clownface} current graph pointer 340 | */ 341 | addIn(predicates, subjects, callback) { 342 | if (!predicates) { 343 | throw new Error('predicate parameter is required') 344 | } 345 | 346 | if (typeof subjects === 'function') { 347 | callback = subjects 348 | subjects = null 349 | } 350 | 351 | predicates = this._toTermArray(predicates) 352 | subjects = this._toTermArray(subjects) || [this.factory.blankNode()] 353 | 354 | const context = this._context.map(context => context.addIn(predicates, subjects)) 355 | 356 | if (callback) { 357 | Clownface.fromContext(context, this).forEach(callback) 358 | } 359 | 360 | return this 361 | } 362 | 363 | /** 364 | * Creates a new quad(s) in the dataset where the current context is the subject 365 | * 366 | * @param {Term|Term[]|Clownface|Clownface[]} predicates 367 | * @param {*} objects one or more values to use for objects 368 | * @param {GraphPointerCallback} [callback] called for each subject, with object pointer as parameter 369 | * @returns {Clownface} current graph pointer 370 | */ 371 | addOut(predicates, objects, callback) { 372 | if (!predicates) { 373 | throw new Error('predicate parameter is required') 374 | } 375 | 376 | if (typeof objects === 'function') { 377 | callback = objects 378 | objects = null 379 | } 380 | 381 | predicates = this._toTermArray(predicates) 382 | objects = this._toTermArray(objects) || [this.factory.blankNode()] 383 | 384 | const context = this._context.map(context => context.addOut(predicates, objects)) 385 | 386 | if (callback) { 387 | Clownface.fromContext(context, this).forEach(callback) 388 | } 389 | 390 | return this 391 | } 392 | 393 | /** 394 | * Creates a new RDF list or lists containing the given items 395 | * 396 | * @param {Term|Term[]|Clownface|Clownface[]} predicates 397 | * @param {*} items one or more values to use for subjects 398 | * @returns {Clownface} current graph pointer 399 | */ 400 | addList(predicates, items) { 401 | if (!predicates || !items) { 402 | throw new Error('predicate and items parameter is required') 403 | } 404 | 405 | predicates = this._toTermArray(predicates) 406 | items = this._toTermArray(items) 407 | 408 | this._context.forEach(context => context.addList(predicates, items)) 409 | 410 | return this 411 | } 412 | 413 | /** 414 | * Deletes all quads where the current graph pointer contexts are the objects 415 | * 416 | * @param {Term|Term[]|Clownface|Clownface[]} [predicates] 417 | * @param {Term|Term[]|Clownface|Clownface[]} [subjects] 418 | * @returns {Clownface} current graph pointer 419 | */ 420 | deleteIn(predicates, subjects) { 421 | predicates = this._toTermArray(predicates) 422 | subjects = this._toTermArray(subjects) 423 | 424 | this._context.forEach(context => context.deleteIn(predicates, subjects)) 425 | 426 | return this 427 | } 428 | 429 | /** 430 | * Deletes all quads where the current graph pointer contexts are the subjects 431 | * 432 | * @param {Term|Term[]|Clownface|Clownface[]} [predicates] 433 | * @param {Term|Term[]|Clownface|Clownface[]} [objects] 434 | * @returns {Clownface} current graph pointer 435 | */ 436 | deleteOut(predicates, objects) { 437 | predicates = this._toTermArray(predicates) 438 | objects = this._toTermArray(objects) 439 | 440 | this._context.forEach(context => context.deleteOut(predicates, objects)) 441 | 442 | return this 443 | } 444 | 445 | /** 446 | * Deletes entire RDF lists where the current graph pointer is the subject 447 | * 448 | * @param {Term|Term[]|Clownface|Clownface[]} predicates 449 | * @returns {Clownface} current graph pointer 450 | */ 451 | deleteList(predicates) { 452 | if (!predicates) { 453 | throw new Error('predicate parameter is required') 454 | } 455 | 456 | predicates = this._toTermArray(predicates) 457 | 458 | this._context.forEach(context => context.deleteList(predicates)) 459 | 460 | return this 461 | } 462 | 463 | _toTermArray(predicates, type, languageOrDatatype) { 464 | return toTermArray(predicates, type, languageOrDatatype, this.factory) 465 | } 466 | 467 | static fromContext(context, { factory }) { 468 | return new Clownface({ _context: toArray(context), factory }) 469 | } 470 | } 471 | 472 | /** 473 | * @callback GraphPointerCallback 474 | * @param {Clownface} pointer graph pointer to the new or existing node 475 | */ 476 | 477 | /** 478 | * @callback FilterCallback 479 | * @param {Clownface} pointer 480 | * @param {number} index 481 | * @param {Clownface[]} pointers 482 | * @return {boolean} 483 | */ 484 | 485 | /** 486 | * @callback ForEachCallback 487 | * @param {Clownface} pointer 488 | */ 489 | 490 | /** 491 | * @callback MapCallback 492 | * @template T 493 | * @param {Clownface} pointer 494 | * @return {T} 495 | */ 496 | -------------------------------------------------------------------------------- /lib/Context.js: -------------------------------------------------------------------------------- 1 | import { filterTaggedLiterals } from '../lib/languageTag.js' 2 | import term from './term.js' 3 | import toArray from './toArray.js' 4 | 5 | export default class Context { 6 | constructor({ dataset, graph, value, factory, namespace }) { 7 | this.dataset = dataset 8 | this.graph = graph 9 | this.factory = factory 10 | this.namespace = namespace 11 | this.term = term(value, undefined, undefined, factory) 12 | } 13 | 14 | clone({ dataset = this.dataset, graph = this.graph, value, factory = this.factory, namespace = this.namespace }) { 15 | return new Context({ dataset, graph, value, factory, namespace }) 16 | } 17 | 18 | has(predicate, object) { 19 | return this.matchProperty(toArray(this.term), predicate, object, toArray(this.graph), 'subject').map(subject => { 20 | return this.clone({ value: subject }) 21 | }) 22 | } 23 | 24 | in(predicate) { 25 | return this.matchProperty(null, predicate, toArray(this.term), toArray(this.graph), 'subject').map(subject => { 26 | return this.clone({ value: subject }) 27 | }) 28 | } 29 | 30 | out(predicate, { language } = {}) { 31 | let objects = this.matchProperty(toArray(this.term), predicate, null, toArray(this.graph), 'object') 32 | 33 | if (typeof language !== 'undefined') { 34 | objects = filterTaggedLiterals(objects, { language }) 35 | } 36 | 37 | return objects.map(object => { 38 | return this.clone({ value: object }) 39 | }) 40 | } 41 | 42 | addIn(predicates, subjects) { 43 | const context = [] 44 | 45 | if (this.term) { 46 | subjects.forEach(subject => { 47 | predicates.forEach(predicate => { 48 | this.dataset.add(this.factory.quad(subject, predicate, this.term, this.graph)) 49 | }) 50 | 51 | context.push(this.clone({ value: subject })) 52 | }) 53 | } 54 | 55 | return context 56 | } 57 | 58 | addOut(predicates, objects) { 59 | const context = [] 60 | 61 | if (this.term) { 62 | objects.forEach(object => { 63 | predicates.forEach(predicate => { 64 | this.dataset.add(this.factory.quad(this.term, predicate, object, this.graph)) 65 | }) 66 | 67 | context.push(this.clone({ value: object })) 68 | }) 69 | } 70 | 71 | return context 72 | } 73 | 74 | addList(predicates, items) { 75 | if (!this.term) { 76 | return 77 | } 78 | 79 | predicates.forEach(predicate => { 80 | const nodes = items.map(() => this.factory.blankNode()) 81 | 82 | this.dataset.add(this.factory.quad(this.term, predicate, nodes[0] || this.namespace.nil, this.graph)) 83 | 84 | for (let index = 0; index < nodes.length; index++) { 85 | this.dataset.add(this.factory.quad(nodes[index], this.namespace.first, items[index], this.graph)) 86 | this.dataset.add(this.factory.quad(nodes[index], this.namespace.rest, nodes[index + 1] || this.namespace.nil, this.graph)) 87 | } 88 | }) 89 | } 90 | 91 | deleteIn(predicate, subject) { 92 | this.deleteMatch(subject, predicate, toArray(this.term), toArray(this.graph)) 93 | } 94 | 95 | deleteOut(predicate, objects) { 96 | this.deleteMatch(toArray(this.term), predicate, objects, toArray(this.graph)) 97 | } 98 | 99 | deleteList(predicates) { 100 | predicates.forEach(predicate => { 101 | for (const quad of this.dataset.match(this.term, predicate)) { 102 | this.deleteItems(quad) 103 | } 104 | }) 105 | } 106 | 107 | deleteItems(start) { 108 | let quads = [start] 109 | 110 | while (!quads[quads.length - 1].object.equals(this.namespace.nil)) { 111 | const node = quads[quads.length - 1].object 112 | 113 | quads = quads.concat([...this.dataset.match(node)]) 114 | } 115 | 116 | quads.forEach(quad => { 117 | this.dataset.delete(quad) 118 | }) 119 | } 120 | 121 | match(subject, predicate, object, graph) { 122 | // if no parts are given, there is nothing to filter 123 | if (!subject && !predicate && !object && !graph) { 124 | return [...this.dataset] 125 | } 126 | 127 | subject = subject || [null] 128 | predicate = predicate || [null] 129 | object = object || [null] 130 | graph = graph || [null] 131 | 132 | const matches = [] 133 | 134 | // use all gspo permutations and combine matches in an array 135 | for (const g of graph) { 136 | for (const s of subject) { 137 | for (const p of predicate) { 138 | for (const o of object) { 139 | for (const quad of this.dataset.match(s, p, o, g)) { 140 | matches.push(quad) 141 | } 142 | } 143 | } 144 | } 145 | } 146 | 147 | return matches 148 | } 149 | 150 | matchProperty(subject, predicate, object, graph, property) { 151 | return this.match(subject, predicate, object, graph).map(quad => quad[property]) 152 | } 153 | 154 | deleteMatch(subject, predicate, object, graph) { 155 | this.match(subject, predicate, object, graph).forEach(quad => { 156 | this.dataset.delete(quad) 157 | }) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /lib/environment.js: -------------------------------------------------------------------------------- 1 | import Environment from '@rdfjs/environment' 2 | import NamespaceFactory from '@rdfjs/namespace/Factory.js' 3 | import DataFactory from '@rdfjs/data-model/Factory.js' 4 | 5 | export default new Environment([ 6 | NamespaceFactory, 7 | DataFactory, 8 | ]) 9 | -------------------------------------------------------------------------------- /lib/fromPrimitive.js: -------------------------------------------------------------------------------- 1 | import rdf from './environment.js' 2 | import namespace from './namespace.js' 3 | 4 | const { xsd } = namespace(rdf) 5 | 6 | export function booleanToLiteral(value, factory = rdf) { 7 | if (typeof value !== 'boolean') { 8 | return null 9 | } 10 | 11 | return factory.literal(value.toString(), xsd('boolean')) 12 | } 13 | 14 | export function numberToLiteral(value, factory = rdf) { 15 | if (typeof value !== 'number') { 16 | return null 17 | } 18 | 19 | if (Number.isInteger(value)) { 20 | return factory.literal(value.toString(10), xsd('integer')) 21 | } 22 | 23 | return factory.literal(value.toString(10), xsd('double')) 24 | } 25 | 26 | export function stringToLiteral(value, factory = rdf) { 27 | if (typeof value !== 'string') { 28 | return null 29 | } 30 | 31 | return factory.literal(value) 32 | } 33 | 34 | export function toLiteral(value, factory = rdf) { 35 | return booleanToLiteral(value, factory) || 36 | numberToLiteral(value, factory) || 37 | stringToLiteral(value, factory) 38 | } 39 | -------------------------------------------------------------------------------- /lib/languageTag.js: -------------------------------------------------------------------------------- 1 | import RDF from './environment.js' 2 | import namespace from './namespace.js' 3 | 4 | const ns = namespace(RDF) 5 | 6 | function mapLiteralsByLanguage(map, current) { 7 | const notLiteral = current.termType !== 'Literal' 8 | const notStringLiteral = ns.langString.equals(current.datatype) || ns.xsd.string.equals(current.datatype) 9 | 10 | if (notLiteral || !notStringLiteral) return map 11 | 12 | const language = current.language.toLowerCase() 13 | 14 | if (map.has(language)) { 15 | map.get(language).push(current) 16 | } else { 17 | map.set(language, [current]) 18 | } 19 | 20 | return map 21 | } 22 | 23 | function createLanguageMapper(objects) { 24 | const literalsByLanguage = objects.reduce(mapLiteralsByLanguage, new Map()) 25 | const langMapEntries = [...literalsByLanguage.entries()] 26 | 27 | return language => { 28 | const languageLowerCase = language.toLowerCase() 29 | 30 | if (languageLowerCase === '*') { 31 | return langMapEntries[0] && langMapEntries[0][1] 32 | } 33 | 34 | const exactMatch = literalsByLanguage.get(languageLowerCase) 35 | if (exactMatch) { 36 | return exactMatch 37 | } 38 | 39 | const secondaryMatches = langMapEntries.find(([entryLanguage]) => entryLanguage.startsWith(languageLowerCase)) 40 | 41 | return secondaryMatches && secondaryMatches[1] 42 | } 43 | } 44 | 45 | /** 46 | * 47 | * @param {Term[]} terms 48 | * @param {object} [options] 49 | * @param {string | string[]} [options.language] 50 | * @returns {Term[]} 51 | */ 52 | export function filterTaggedLiterals(terms, { language }) { 53 | const languages = (typeof language === 'string' ? [language] : language) 54 | const getLiteralsForLanguage = createLanguageMapper(terms) 55 | 56 | return languages.map(getLiteralsForLanguage).find(Boolean) || [] 57 | } 58 | -------------------------------------------------------------------------------- /lib/namespace.js: -------------------------------------------------------------------------------- 1 | export default (factory) => { 2 | const xsd = factory.namespace('http://www.w3.org/2001/XMLSchema#') 3 | const rdf = factory.namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#') 4 | 5 | return { 6 | first: rdf.first, 7 | nil: rdf.nil, 8 | rest: rdf.rest, 9 | langString: rdf.langString, 10 | xsd, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/term.js: -------------------------------------------------------------------------------- 1 | import { toLiteral } from './fromPrimitive.js' 2 | 3 | function blankNode(value, factory) { 4 | if (value && typeof value !== 'string') { 5 | throw new Error('Blank node identifier must be a string') 6 | } 7 | 8 | return factory.blankNode(value) 9 | } 10 | 11 | function literal(value, languageOrDatatype, factory) { 12 | if (typeof value === 'string') { 13 | // check if it's given, if given try RDF/JS Term value otherwise convert it to a string 14 | languageOrDatatype = languageOrDatatype && (languageOrDatatype.value || languageOrDatatype.toString()) 15 | 16 | if (languageOrDatatype && languageOrDatatype.indexOf(':') !== -1) { 17 | languageOrDatatype = factory.namedNode(languageOrDatatype) 18 | } 19 | 20 | return factory.literal(value.toString(), languageOrDatatype) 21 | } 22 | 23 | const term = toLiteral(value, factory) 24 | 25 | if (!term) { 26 | throw new Error('The value cannot be converted to a literal node') 27 | } 28 | 29 | return term 30 | } 31 | 32 | function namedNode(value, factory) { 33 | if (typeof value !== 'string') { 34 | throw new Error('Named node must be an IRI string') 35 | } 36 | 37 | return factory.namedNode(value) 38 | } 39 | 40 | export default function term(value, type = 'Literal', languageOrDatatype, factory) { 41 | // it's already a RDF/JS Term 42 | if (value && typeof value === 'object' && value.termType) { 43 | return value 44 | } 45 | 46 | // check if it's a URL object 47 | if (value && value.constructor.name === 'URL') { 48 | return namedNode(value.toString(), factory) 49 | } 50 | 51 | // check if it's a blank node... 52 | if (type === 'BlankNode') { 53 | return blankNode(value, factory) 54 | } 55 | 56 | // ...cause that's the only type that doesn't require a value 57 | if (value === null || typeof value === 'undefined') { 58 | return undefined 59 | } 60 | 61 | if (type === 'Literal') { 62 | return literal(value, languageOrDatatype, factory) 63 | } 64 | 65 | if (type === 'NamedNode') { 66 | return namedNode(value, factory) 67 | } 68 | 69 | throw new Error('unknown type') 70 | } 71 | -------------------------------------------------------------------------------- /lib/toArray.js: -------------------------------------------------------------------------------- 1 | export default function toArray(value, defaultValue) { 2 | if (typeof value === 'undefined' || value === null) { 3 | return defaultValue 4 | } 5 | 6 | if (Array.isArray(value)) { 7 | return value 8 | } 9 | 10 | if (typeof value !== 'string' && value[Symbol.iterator]) { 11 | return [...value] 12 | } 13 | 14 | return [value] 15 | } 16 | -------------------------------------------------------------------------------- /lib/toTermArray.js: -------------------------------------------------------------------------------- 1 | import term from './term.js' 2 | import toArray from './toArray.js' 3 | 4 | export default function toTermArray(items, type, languageOrDatatype, factory) { 5 | if ((typeof items === 'undefined' || items === null) && !type) { 6 | return items 7 | } 8 | 9 | return (toArray(items) || [undefined]).reduce((all, item) => { 10 | if (typeof item === 'object' && item.terms) { 11 | return all.concat(item.terms) 12 | } 13 | 14 | all.push(term(item, type, languageOrDatatype, factory)) 15 | 16 | return all 17 | }, []) 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clownface", 3 | "version": "2.0.3", 4 | "description": "Simple but powerful graph traversing library", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "docs:build": "jsdoc2md --no-gfm -f index.js lib/* > docs/api.md; git add docs/api.md", 9 | "docs:serve": "docsify serve docs", 10 | "lint": "eslint . --quiet --ignore-path .gitignore", 11 | "test": "c8 --all --reporter=lcovonly mocha --recursive test", 12 | "release": "changeset publish" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/zazuko/clownface.git" 17 | }, 18 | "keywords": [ 19 | "rdf", 20 | "graph", 21 | "traversing" 22 | ], 23 | "author": "Thomas Bergwinkl (https://www.bergnet.org/people/bergi/card#me)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/zazuko/clownface/issues" 27 | }, 28 | "homepage": "https://zazuko.github.io/clownface/#/", 29 | "dependencies": { 30 | "@rdfjs/data-model": "^2.0.1", 31 | "@rdfjs/environment": "0 - 1", 32 | "@rdfjs/namespace": "^2.0.0" 33 | }, 34 | "devDependencies": { 35 | "@changesets/cli": "^2.26.1", 36 | "@rdfjs/dataset": "^2.0.1", 37 | "@rdfjs/parser-n3": "^2.0.0", 38 | "@tpluscode/eslint-config": "^0.4.4", 39 | "c8": "^7.14.0", 40 | "chai": "^4.3.7", 41 | "docsify-cli": "^4.4.0", 42 | "husky": "^4.2.5", 43 | "jsdoc-to-markdown": "^5.0.3", 44 | "mocha": "^10.2.0", 45 | "rdf-dataset-ext": "^1.0.0", 46 | "rdf-ext": "^2.2.0", 47 | "rimraf": "^3.0.2", 48 | "sinon": "^9.0.2", 49 | "string-to-stream": "^3.0.1", 50 | "tbbt-ld": "^1.1.0" 51 | }, 52 | "husky": { 53 | "hooks": { 54 | "pre-commit": "npm run docs:build" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/Clownface/addIn.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { describe, it } from 'mocha' 3 | import sinon from 'sinon' 4 | import { addAll } from 'rdf-dataset-ext' 5 | import Environment from '@rdfjs/environment' 6 | import NamespaceFactory from '@rdfjs/namespace/Factory.js' 7 | import clownface from '../../index.js' 8 | import rdf from '../support/factory.js' 9 | import CustomDataFactory from '../support/CustomDataFactory.js' 10 | 11 | describe('.addIn', () => { 12 | it('should be a function', () => { 13 | const cf = clownface({ dataset: rdf.dataset() }) 14 | 15 | assert.strictEqual(typeof cf.addIn, 'function') 16 | }) 17 | 18 | it('should throw an error if predicate parameter is missing', () => { 19 | const subject = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 20 | const object = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 21 | const cf = clownface({ dataset: rdf.dataset(), term: object }) 22 | 23 | let touched = false 24 | 25 | try { 26 | cf.addIn(null, subject) 27 | } catch (err) { 28 | touched = true 29 | } 30 | 31 | assert(touched) 32 | }) 33 | 34 | it('should add quads using the context term as object and the given predicate and subject', () => { 35 | const dataset = rdf.dataset() 36 | const subject = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 37 | const predicate = rdf.namedNode('http://schema.org/knows') 38 | const object = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 39 | const cf = clownface({ dataset, term: object }) 40 | 41 | cf.addIn(predicate, subject) 42 | 43 | const result = dataset.match(subject, predicate, object) 44 | 45 | assert.strictEqual(result.size, 1) 46 | }) 47 | 48 | it('should create a Blank Node subject if no subject was given', () => { 49 | const dataset = rdf.dataset() 50 | const predicate = rdf.namedNode('http://schema.org/knows') 51 | const object = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 52 | const cf = clownface({ dataset, term: object }) 53 | 54 | cf.addIn(predicate) 55 | 56 | const result = dataset.match(null, predicate, object) 57 | 58 | assert.strictEqual(result.size, 1) 59 | assert.strictEqual([...result][0].subject.termType, 'BlankNode') 60 | }) 61 | 62 | it('should support array values as predicate', () => { 63 | const dataset = rdf.dataset() 64 | const subject = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 65 | const predicateA = rdf.namedNode('http://schema.org/knows') 66 | const predicateB = rdf.namedNode('http://schema.org/saw') 67 | const object = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 68 | const cf = clownface({ dataset, term: object }) 69 | 70 | cf.addIn([predicateA, predicateB], subject) 71 | 72 | const result = addAll( 73 | dataset.match(subject, predicateA, object), 74 | dataset.match(subject, predicateB, object)) 75 | 76 | assert.strictEqual(result.size, 2) 77 | }) 78 | 79 | it('should support array values as subject', () => { 80 | const dataset = rdf.dataset() 81 | const subjectA = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 82 | const subjectB = rdf.namedNode('http://localhost:8080/data/person/penny') 83 | const predicate = rdf.namedNode('http://schema.org/saw') 84 | const object = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 85 | const cf = clownface({ dataset, term: object }) 86 | 87 | cf.addIn(predicate, [subjectA, subjectB]) 88 | 89 | const result = addAll( 90 | dataset.match(subjectA, predicate, object), 91 | dataset.match(subjectB, predicate, object)) 92 | 93 | assert.strictEqual(result.size, 2) 94 | }) 95 | 96 | it('should call the given function with a context for all added subjects', () => { 97 | const dataset = rdf.dataset() 98 | const subjectA = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 99 | const subjectB = rdf.namedNode('http://localhost:8080/data/person/penny') 100 | const predicateA = rdf.namedNode('http://schema.org/knows') 101 | const predicateB = rdf.namedNode('http://schema.org/saw') 102 | const object = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 103 | const cf = clownface({ dataset, term: object }) 104 | 105 | let result = null 106 | 107 | cf.addIn([predicateA, predicateB], [subjectA, subjectB], child => { 108 | result = child.values 109 | }) 110 | 111 | assert.deepStrictEqual(result, [subjectA.value, subjectB.value]) 112 | }) 113 | 114 | it('should call the given function with a Blank Node context for the created subject', () => { 115 | const dataset = rdf.dataset() 116 | const predicate = rdf.namedNode('http://schema.org/knows') 117 | const object = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 118 | const cf = clownface({ dataset, term: object }) 119 | 120 | let result = null 121 | 122 | cf.addIn(predicate, child => { 123 | result = child 124 | }) 125 | 126 | assert.strictEqual(result._context.length, 1) 127 | assert.strictEqual(result._context[0].term.termType, 'BlankNode') 128 | }) 129 | 130 | it('should support clownface objects as predicate and subject', () => { 131 | const dataset = rdf.dataset() 132 | const subject = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 133 | const predicate = rdf.namedNode('http://schema.org/knows') 134 | const object = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 135 | const cf = clownface({ dataset, term: object }) 136 | 137 | cf.addIn(cf.node(predicate), cf.node(subject)) 138 | 139 | const result = dataset.match(subject, predicate, object) 140 | 141 | assert.strictEqual(result.size, 1) 142 | }) 143 | 144 | it('should return the called object', () => { 145 | const dataset = rdf.dataset() 146 | const subject = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 147 | const predicate = rdf.namedNode('http://schema.org/knows') 148 | const object = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 149 | const cf = clownface({ dataset, term: object }) 150 | 151 | assert.strictEqual(cf.addIn(predicate, subject), cf) 152 | }) 153 | 154 | it('should use the provided factory', () => { 155 | const dataset = rdf.dataset() 156 | const predicate = rdf.namedNode('http://schema.org/knows') 157 | const term = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 158 | const factory = new Environment([NamespaceFactory, CustomDataFactory]) 159 | 160 | const cf = clownface({ dataset, factory, term }).addIn(predicate, 'test') 161 | 162 | assert.strictEqual(cf.in(predicate).term.testProperty, 'test') 163 | cf.dataset.match(null, predicate, null).forEach((quad) => { 164 | assert.strictEqual(quad.testProperty, 'test') 165 | }) 166 | }) 167 | 168 | it('should not add quads if context is undefined', () => { 169 | const dataset = rdf.dataset() 170 | const cf = clownface({ dataset }) 171 | const subject = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 172 | const predicate = rdf.namedNode('http://schema.org/knows') 173 | 174 | cf.addIn(predicate, subject) 175 | 176 | assert.strictEqual(dataset.size, 0) 177 | }) 178 | 179 | it('should not call callback function if context is undefined', () => { 180 | const dataset = rdf.dataset() 181 | const cf = clownface({ dataset }) 182 | const subject = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 183 | const predicate = rdf.namedNode('http://schema.org/knows') 184 | const callback = sinon.spy() 185 | 186 | cf.addIn(predicate, subject, callback) 187 | 188 | assert.strictEqual(callback.called, false) 189 | }) 190 | }) 191 | -------------------------------------------------------------------------------- /test/Clownface/addList.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { describe, it } from 'mocha' 3 | import Environment from '@rdfjs/environment' 4 | import NamespaceFactory from '@rdfjs/namespace/Factory.js' 5 | import clownface from '../../index.js' 6 | import rdf from '../support/factory.js' 7 | import * as ns from '../support/namespace.js' 8 | import CustomDataFactory from '../support/CustomDataFactory.js' 9 | 10 | describe('.addList', () => { 11 | it('should be a function', () => { 12 | const cf = clownface({ dataset: rdf.dataset() }) 13 | 14 | assert.strictEqual(typeof cf.addList, 'function') 15 | }) 16 | 17 | it('should throw an error if object parameter is missing', () => { 18 | const dataset = rdf.dataset() 19 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 20 | const predicate = rdf.namedNode('http://schema.org/knows') 21 | const cf = clownface({ dataset, term: subject }) 22 | 23 | let touched = false 24 | 25 | try { 26 | cf.addList(predicate) 27 | } catch (err) { 28 | touched = true 29 | } 30 | 31 | assert(touched) 32 | }) 33 | 34 | it('should throw an error if predicate parameter is missing', () => { 35 | const dataset = rdf.dataset() 36 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 37 | const object = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 38 | const cf = clownface({ dataset, term: subject }) 39 | 40 | let touched = false 41 | 42 | try { 43 | cf.addList(null, object) 44 | } catch (err) { 45 | touched = true 46 | } 47 | 48 | assert(touched) 49 | }) 50 | 51 | it('should add list quads using the context term as subject and the given predicate and items', () => { 52 | const dataset = rdf.dataset() 53 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 54 | const predicate = rdf.namedNode('http://schema.org/counts') 55 | const item0 = rdf.literal('0') 56 | const item1 = rdf.literal('1') 57 | const cf = clownface({ dataset, term: subject }) 58 | 59 | cf.addList(predicate, [item0, item1]) 60 | 61 | const entry = [...dataset.match(subject, predicate)][0] 62 | const first0 = [...dataset.match(entry.object, ns.first, item0)][0] 63 | const rest0 = [...dataset.match(entry.object, ns.rest)][0] 64 | const first1 = [...dataset.match(rest0.object, ns.first, item1)][0] 65 | const rest1 = [...dataset.match(rest0.object, ns.rest, ns.nil)][0] 66 | 67 | assert(entry) 68 | assert.strictEqual(entry.object.termType, 'BlankNode') 69 | assert(first0) 70 | assert(rest0) 71 | assert.strictEqual(rest0.object.termType, 'BlankNode') 72 | assert(first1) 73 | assert(rest1) 74 | }) 75 | 76 | it('should use the provided factory', () => { 77 | const dataset = rdf.dataset() 78 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 79 | const predicate = rdf.namedNode('http://schema.org/knows') 80 | const item0 = rdf.literal('0') 81 | const factory = new Environment([NamespaceFactory, CustomDataFactory]) 82 | 83 | clownface({ dataset, term: subject, factory }).addList(predicate, [item0]) 84 | 85 | const entry = [...dataset.match(subject, predicate)][0] 86 | const first0 = [...dataset.match(entry.object, ns.first, item0)][0] 87 | const rest0 = [...dataset.match(entry.object, ns.rest, ns.nil)][0] 88 | 89 | assert.strictEqual(entry.testProperty, 'test') 90 | assert.strictEqual(entry.object.testProperty, 'test') 91 | assert.strictEqual(first0.testProperty, 'test') 92 | assert.strictEqual(first0.predicate.testProperty, 'test') 93 | assert.strictEqual(rest0.testProperty, 'test') 94 | assert.strictEqual(rest0.predicate.testProperty, 'test') 95 | }) 96 | 97 | it('should not add quads if context is undefined', () => { 98 | const dataset = rdf.dataset() 99 | const graph = clownface({ dataset }) 100 | const predicate = rdf.namedNode('http://example.org/foo') 101 | 102 | graph.addList(predicate, ['bar', 'baz']) 103 | 104 | assert.strictEqual(dataset.size, 0) 105 | }) 106 | }) 107 | -------------------------------------------------------------------------------- /test/Clownface/addOut.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { describe, it } from 'mocha' 3 | import sinon from 'sinon' 4 | import { addAll } from 'rdf-dataset-ext' 5 | import Environment from '@rdfjs/environment' 6 | import NamespaceFactory from '@rdfjs/namespace/Factory.js' 7 | import clownface from '../../index.js' 8 | import loadExample from '../support/example.js' 9 | import rdf from '../support/factory.js' 10 | import CustomDataFactory from '../support/CustomDataFactory.js' 11 | 12 | describe('.addOut', () => { 13 | it('should be a function', () => { 14 | const cf = clownface({ dataset: rdf.dataset() }) 15 | 16 | assert.strictEqual(typeof cf.addOut, 'function') 17 | }) 18 | 19 | it('should throw an error if predicate parameter is missing', async () => { 20 | const dataset = addAll(rdf.dataset(), await loadExample()) 21 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 22 | const object = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 23 | const cf = clownface({ dataset, term: subject }) 24 | 25 | let touched = false 26 | 27 | try { 28 | cf.addOut(null, object) 29 | } catch (err) { 30 | touched = true 31 | } 32 | 33 | assert(touched) 34 | }) 35 | 36 | it('should add quads using the context term as subject and the given predicate and object', async () => { 37 | const dataset = addAll(rdf.dataset(), await loadExample()) 38 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 39 | const predicate = rdf.namedNode('http://schema.org/knows') 40 | const object = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 41 | const cf = clownface({ dataset, term: subject }) 42 | 43 | cf.addOut(predicate, object) 44 | 45 | const result = dataset.match(subject, predicate, object) 46 | 47 | assert.strictEqual(result.size, 1) 48 | }) 49 | 50 | it('should create a Blank Node subject if no subject was given', () => { 51 | const dataset = rdf.dataset() 52 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 53 | const predicate = rdf.namedNode('http://schema.org/knows') 54 | const cf = clownface({ dataset, term: subject }) 55 | 56 | cf.addOut(predicate) 57 | 58 | const result = dataset.match(subject, predicate) 59 | 60 | assert.strictEqual(result.size, 1) 61 | assert.strictEqual([...result][0].object.termType, 'BlankNode') 62 | }) 63 | 64 | it('should add a string Literal Node object when objects are falsy literals', () => { 65 | const dataset = rdf.dataset() 66 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 67 | const predicate = rdf.namedNode('http://schema.org/knows') 68 | const cf = clownface({ dataset, term: subject }) 69 | 70 | cf.addOut(predicate, ['', 0]) 71 | 72 | const result = dataset.match(subject, predicate) 73 | 74 | assert.strictEqual(result.size, 2) 75 | assert.strictEqual([...result][0].object.termType, 'Literal') 76 | assert.strictEqual([...result][1].object.termType, 'Literal') 77 | }) 78 | 79 | it('should support array values as predicate', () => { 80 | const dataset = rdf.dataset() 81 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 82 | const predicateA = rdf.namedNode('http://schema.org/knows') 83 | const predicateB = rdf.namedNode('http://schema.org/saw') 84 | const object = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 85 | const cf = clownface({ dataset, term: subject }) 86 | 87 | cf.addOut([predicateA, predicateB], object) 88 | 89 | const result = addAll( 90 | dataset.match(subject, predicateA, object), 91 | dataset.match(subject, predicateB, object)) 92 | 93 | assert.strictEqual(result.size, 2) 94 | }) 95 | 96 | it('should support array values as object', () => { 97 | const dataset = rdf.dataset() 98 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 99 | const predicate = rdf.namedNode('http://schema.org/saw') 100 | const objectA = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 101 | const objectB = rdf.namedNode('http://localhost:8080/data/person/penny') 102 | const cf = clownface({ dataset, term: subject }) 103 | 104 | cf.addOut(predicate, [objectA, objectB]) 105 | 106 | const result = addAll( 107 | dataset.match(subject, predicate, objectA), 108 | dataset.match(subject, predicate, objectB)) 109 | 110 | assert.strictEqual(result.size, 2) 111 | }) 112 | 113 | it('should call the given function with a context for all added objects', () => { 114 | const dataset = rdf.dataset() 115 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 116 | const predicateA = rdf.namedNode('http://schema.org/knows') 117 | const predicateB = rdf.namedNode('http://schema.org/saw') 118 | const objectA = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 119 | const objectB = rdf.namedNode('http://localhost:8080/data/person/penny') 120 | const cf = clownface({ dataset, term: subject }) 121 | 122 | let result = null 123 | 124 | cf.addOut([predicateA, predicateB], [objectA, objectB], child => { 125 | result = child.values 126 | }) 127 | 128 | assert.deepStrictEqual(result, [objectA.value, objectB.value]) 129 | }) 130 | 131 | it('should call the given function with a Blank Node context for the created subject', () => { 132 | const dataset = rdf.dataset() 133 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 134 | const predicate = rdf.namedNode('http://schema.org/knows') 135 | const cf = clownface({ dataset, term: subject }) 136 | 137 | let result = null 138 | 139 | cf.addOut(predicate, child => { 140 | result = child 141 | }) 142 | 143 | assert.strictEqual(result._context.length, 1) 144 | assert.strictEqual(result._context[0].term.termType, 'BlankNode') 145 | }) 146 | 147 | it('should support clownface objects as predicate and object', () => { 148 | const dataset = rdf.dataset() 149 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 150 | const predicate = rdf.namedNode('http://schema.org/knows') 151 | const object = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 152 | const cf = clownface({ dataset, term: subject }) 153 | 154 | cf.addOut(cf.node(predicate), cf.node(object)) 155 | 156 | const result = dataset.match(subject, predicate, object) 157 | 158 | assert.strictEqual(result.size, 1) 159 | }) 160 | 161 | it('should return the called object', () => { 162 | const dataset = rdf.dataset() 163 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 164 | const predicate = rdf.namedNode('http://schema.org/knows') 165 | const object = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 166 | const cf = clownface({ dataset, term: subject }) 167 | 168 | assert.strictEqual(cf.addOut(predicate, object), cf) 169 | }) 170 | 171 | it('should use the provided factory', () => { 172 | const dataset = rdf.dataset() 173 | const predicate = rdf.namedNode('http://schema.org/knows') 174 | const term = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 175 | const factory = new Environment([NamespaceFactory, CustomDataFactory]) 176 | 177 | const cf = clownface({ dataset, factory, term }).addOut(predicate, 'test') 178 | 179 | assert.strictEqual(cf.out(predicate).term.testProperty, 'test') 180 | cf.dataset.match(null, predicate, null).forEach((quad) => { 181 | assert.strictEqual(quad.testProperty, 'test') 182 | }) 183 | }) 184 | 185 | context('multi pointer', () => { 186 | it('should reuse current factory', () => { 187 | const dataset = rdf.dataset() 188 | const predicate = rdf.namedNode('http://schema.org/knows') 189 | const term = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 190 | const factory = new Environment([NamespaceFactory, CustomDataFactory]) 191 | 192 | clownface({ dataset, factory, term }) 193 | .node([rdf.namedNode('foo'), rdf.namedNode('bar')]) 194 | .addOut(predicate, 'test') 195 | 196 | dataset.match(null, predicate, null).forEach((quad) => { 197 | assert.strictEqual(quad.testProperty, 'test') 198 | }) 199 | }) 200 | }) 201 | 202 | it('should not add quads if context is undefined', () => { 203 | const dataset = rdf.dataset() 204 | const cf = clownface({ dataset }) 205 | const object = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 206 | const predicate = rdf.namedNode('http://schema.org/knows') 207 | 208 | cf.addOut(predicate, object) 209 | 210 | assert.strictEqual(dataset.size, 0) 211 | }) 212 | 213 | it('should not call callback function if context is undefined', () => { 214 | const dataset = rdf.dataset() 215 | const cf = clownface({ dataset }) 216 | const object = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 217 | const predicate = rdf.namedNode('http://schema.org/knows') 218 | const callback = sinon.spy() 219 | 220 | cf.addOut(predicate, object, callback) 221 | 222 | assert.strictEqual(callback.called, false) 223 | }) 224 | }) 225 | -------------------------------------------------------------------------------- /test/Clownface/any.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { describe, it } from 'mocha' 3 | import clownface from '../../index.js' 4 | import rdf from '../support/factory.js' 5 | 6 | describe('.any', () => { 7 | it('should be a function', () => { 8 | const cf = clownface({ dataset: rdf.dataset() }) 9 | 10 | assert.strictEqual(typeof cf.any, 'function') 11 | }) 12 | 13 | it('should return any pointer object', () => { 14 | const pointer = clownface({ dataset: rdf.dataset() }) 15 | 16 | const any = pointer.any() 17 | 18 | assert.strictEqual(typeof any.term, 'undefined') 19 | assert.deepStrictEqual(any.terms, []) 20 | }) 21 | 22 | it('should remove current pointer', () => { 23 | const pointer = clownface({ dataset: rdf.dataset(), term: [rdf.blankNode(), rdf.blankNode()] }) 24 | 25 | const any = pointer.any() 26 | 27 | assert.strictEqual(typeof any.term, 'undefined') 28 | assert.deepStrictEqual(any.terms, []) 29 | }) 30 | 31 | it('should remove current pointers', () => { 32 | const pointer = clownface({ dataset: rdf.dataset(), term: [rdf.blankNode()] }) 33 | 34 | const any = pointer.any() 35 | 36 | assert.strictEqual(typeof any.term, 'undefined') 37 | assert.deepStrictEqual(any.terms, []) 38 | }) 39 | 40 | it('should keep same graph pointer in the result', () => { 41 | const cf = clownface({ dataset: rdf.dataset(), graph: rdf.namedNode('foo'), term: rdf.blankNode() }) 42 | 43 | const anyPointer = cf.any() 44 | 45 | assert.strictEqual(anyPointer._context[0].graph.value, 'foo') 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /test/Clownface/blankNode.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import rdf from '../support/factory.js' 6 | import Clownface from '../../lib/Clownface.js' 7 | 8 | describe('.blankNode', () => { 9 | it('should be a function', () => { 10 | const cf = clownface({ dataset: rdf.dataset() }) 11 | 12 | assert.strictEqual(typeof cf.blankNode, 'function') 13 | }) 14 | 15 | it('should return a new Clownface instance', () => { 16 | const cf = clownface({ dataset: rdf.dataset() }) 17 | 18 | const result = cf.blankNode() 19 | 20 | assert(result instanceof Clownface) 21 | assert.notStrictEqual(result, cf) 22 | }) 23 | 24 | it('should use the dataset from the context', () => { 25 | const dataset = rdf.dataset() 26 | const cf = clownface({ dataset }) 27 | 28 | const result = cf.blankNode() 29 | 30 | assert.strictEqual(result._context[0].dataset, dataset) 31 | }) 32 | 33 | it('should create a context with a Blank Node term', () => { 34 | const cf = clownface({ dataset: rdf.dataset() }) 35 | 36 | const result = cf.blankNode() 37 | 38 | assert.strictEqual(result._context.length, 1) 39 | assert.strictEqual(result._context[0].term.termType, 'BlankNode') 40 | }) 41 | 42 | it('should use the given label for the Blank Node', () => { 43 | const label = 'b321' 44 | const cf = clownface({ dataset: rdf.dataset() }) 45 | 46 | const result = cf.blankNode(label) 47 | 48 | assert.strictEqual(result._context.length, 1) 49 | assert.strictEqual(result._context[0].term.termType, 'BlankNode') 50 | assert.strictEqual(result._context[0].term.value, label) 51 | }) 52 | 53 | it('should support multiple values in an array', () => { 54 | const labelA = 'b321a' 55 | const labelB = 'b321b' 56 | const cf = clownface({ dataset: rdf.dataset() }) 57 | 58 | const result = cf.blankNode([labelA, labelB]) 59 | 60 | assert.strictEqual(result._context.length, 2) 61 | assert.strictEqual(result._context[0].term.termType, 'BlankNode') 62 | assert.strictEqual(result._context[0].term.value, labelA) 63 | assert.strictEqual(result._context[1].term.termType, 'BlankNode') 64 | assert.strictEqual(result._context[1].term.value, labelB) 65 | }) 66 | 67 | it('should support multiple values in an iterable', () => { 68 | const labelA = 'b321a' 69 | const labelB = 'b321b' 70 | const cf = clownface({ dataset: rdf.dataset() }) 71 | 72 | const result = cf.blankNode(new Set([labelA, labelB])) 73 | 74 | assert.strictEqual(result._context.length, 2) 75 | assert.strictEqual(result._context[0].term.termType, 'BlankNode') 76 | assert.strictEqual(result._context[0].term.value, labelA) 77 | assert.strictEqual(result._context[1].term.termType, 'BlankNode') 78 | assert.strictEqual(result._context[1].term.value, labelB) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /test/Clownface/constructor.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import rdf from '../support/factory.js' 5 | import Clownface from '../../lib/Clownface.js' 6 | 7 | describe('constructor', () => { 8 | it('should create a Clownface object', () => { 9 | const dataset = rdf.dataset() 10 | const cf = new Clownface({ dataset, factory: rdf }) 11 | 12 | assert(cf instanceof Clownface) 13 | }) 14 | 15 | it('should create an empty context using the given dataset', () => { 16 | const dataset = rdf.dataset() 17 | const cf = new Clownface({ dataset, factory: rdf }) 18 | 19 | assert.strictEqual(cf._context.length, 1) 20 | assert.strictEqual(cf._context[0].dataset, dataset) 21 | assert(!cf._context[0].graph) 22 | assert(!cf._context[0].term) 23 | }) 24 | 25 | it('should create an empty context using the given graph', () => { 26 | const dataset = rdf.dataset() 27 | const graph = rdf.namedNode('http://example.org/graph') 28 | const cf = new Clownface({ dataset, graph, factory: rdf }) 29 | 30 | assert.strictEqual(cf._context.length, 1) 31 | assert.strictEqual(cf._context[0].dataset, dataset) 32 | assert.strictEqual(cf._context[0].graph, graph) 33 | assert(!cf._context[0].term) 34 | }) 35 | 36 | it('should create a context using the given term', () => { 37 | const dataset = rdf.dataset() 38 | const term = rdf.namedNode('http://example.org/subject') 39 | const cf = new Clownface({ dataset, term, factory: rdf }) 40 | 41 | assert.strictEqual(cf._context.length, 1) 42 | assert.strictEqual(cf._context[0].term, term) 43 | }) 44 | 45 | it('should create a context using the given terms', () => { 46 | const dataset = rdf.dataset() 47 | const termA = rdf.namedNode('http://example.org/subjectA') 48 | const termB = rdf.namedNode('http://example.org/subjectB') 49 | const cf = new Clownface({ dataset, term: [termA, termB], factory: rdf }) 50 | 51 | assert.strictEqual(cf._context.length, 2) 52 | assert.strictEqual(cf._context[0].term, termA) 53 | assert.strictEqual(cf._context[1].term, termB) 54 | }) 55 | 56 | it('should create a context using the given value', () => { 57 | const dataset = rdf.dataset() 58 | const value = 'abc' 59 | const cf = new Clownface({ dataset, value, factory: rdf }) 60 | 61 | assert.strictEqual(cf._context.length, 1) 62 | assert.strictEqual(cf._context[0].term.termType, 'Literal') 63 | assert.strictEqual(cf._context[0].term.value, value) 64 | }) 65 | 66 | it('should create a context using the given values', () => { 67 | const dataset = rdf.dataset() 68 | const valueA = 'abc' 69 | const valueB = 'bcd' 70 | const cf = new Clownface({ dataset, value: [valueA, valueB], factory: rdf }) 71 | 72 | assert.strictEqual(cf._context.length, 2) 73 | assert.strictEqual(cf._context[0].term.termType, 'Literal') 74 | assert.strictEqual(cf._context[0].term.value, valueA) 75 | assert.strictEqual(cf._context[1].term.termType, 'Literal') 76 | assert.strictEqual(cf._context[1].term.value, valueB) 77 | }) 78 | 79 | it('should prioritize term over value', () => { 80 | const dataset = rdf.dataset() 81 | const termA = rdf.namedNode('http://example.org/subjectA') 82 | const termB = rdf.namedNode('http://example.org/subjectB') 83 | const valueA = 'abc' 84 | const valueB = 'bcd' 85 | const cf = new Clownface({ dataset, term: [termA, termB], value: [valueA, valueB], factory: rdf }) 86 | 87 | assert.strictEqual(cf._context.length, 2) 88 | assert.strictEqual(cf._context[0].term, termA) 89 | assert.strictEqual(cf._context[1].term, termB) 90 | }) 91 | 92 | it('should use the given _context', () => { 93 | const dataset = rdf.dataset() 94 | const term = rdf.namedNode('http://example.org/subject') 95 | const cf = new Clownface({ dataset, term, factory: rdf }) 96 | const clone = new Clownface(cf) 97 | 98 | assert.strictEqual(clone._context, cf._context) 99 | }) 100 | 101 | it('should inherit factory of the given _context', () => { 102 | const dataset = rdf.dataset() 103 | const term = rdf.namedNode('http://example.org/subject') 104 | const factory = rdf 105 | const cf = new Clownface({ dataset, term, factory }) 106 | const clone = new Clownface(cf) 107 | 108 | assert.strictEqual(clone.factory, factory) 109 | assert.strictEqual(clone._context[0].factory, factory) 110 | }) 111 | 112 | it('should prioritize _context over term', () => { 113 | const dataset = rdf.dataset() 114 | const term = rdf.namedNode('http://example.org/subject') 115 | const cf = new Clownface({ dataset, term, factory: rdf }) 116 | const clone = new Clownface({ term, _context: cf._context, factory: rdf }) 117 | 118 | assert.strictEqual(clone._context, cf._context) 119 | }) 120 | 121 | it('should throw an error if an unknown type is given as value', () => { 122 | const dataset = rdf.dataset() 123 | 124 | let error = null 125 | 126 | try { 127 | Boolean(new Clownface({ dataset, value: new RegExp(), factory: rdf })) 128 | } catch (err) { 129 | error = err 130 | } 131 | 132 | assert(error) 133 | assert(error.message.includes('cannot be converted') > 0) 134 | }) 135 | }) 136 | -------------------------------------------------------------------------------- /test/Clownface/dataset.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import rdf from '../support/factory.js' 6 | 7 | describe('.dataset', () => { 8 | it('should be undefined if there is no context with a dataset', () => { 9 | const cf = clownface({ dataset: rdf.dataset() }) 10 | 11 | cf._context = [] 12 | 13 | assert.strictEqual(typeof cf.dataset, 'undefined') 14 | }) 15 | 16 | it('should be the dataset of the context if there is only one dataset', () => { 17 | const dataset = rdf.dataset() 18 | const cf = clownface({ dataset }) 19 | 20 | assert.strictEqual(cf.dataset, dataset) 21 | }) 22 | 23 | it('should be undefined if there are multiple datasets in the context', () => { 24 | const termA = rdf.literal('1') 25 | const termB = rdf.namedNode('http://example.org/') 26 | const datasetA = rdf.dataset() 27 | const datasetB = rdf.dataset() 28 | const cf = clownface({ dataset: rdf.dataset(), value: [termA, termB] }) 29 | 30 | // TODO: should be possible with constructor or static method 31 | cf._context[0].dataset = datasetA 32 | cf._context[1].dataset = datasetB 33 | 34 | assert.strictEqual(typeof cf.dataset, 'undefined') 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /test/Clownface/datasets.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import equals from 'rdf-dataset-ext/equals.js' 5 | import clownface from '../../index.js' 6 | import rdf from '../support/factory.js' 7 | 8 | describe('.datasets', () => { 9 | it('should be an array property', () => { 10 | const cf = clownface({ dataset: rdf.dataset() }) 11 | 12 | assert(Array.isArray(cf.datasets)) 13 | }) 14 | 15 | it('should be empty if there is no context with a dataset', () => { 16 | const cf = clownface({ dataset: rdf.dataset() }) 17 | 18 | cf._context = [] 19 | 20 | assert.deepStrictEqual(cf.datasets, []) 21 | }) 22 | 23 | it('should contain all datasets of the context', () => { 24 | const termA = rdf.literal('1') 25 | const termB = rdf.namedNode('http://example.org/') 26 | const datasetA = rdf.dataset() 27 | const datasetB = rdf.dataset() 28 | const cf = clownface({ dataset: rdf.dataset(), value: [termA, termB] }) 29 | 30 | // TODO: should be possible with constructor or static method 31 | cf._context[0].dataset = datasetA 32 | cf._context[1].dataset = datasetB 33 | 34 | const result = cf.datasets 35 | 36 | assert.strictEqual(result.length, 2) 37 | assert(equals(datasetA, result[0])) 38 | assert(equals(datasetB, result[1])) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /test/Clownface/deleteIn.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import { addAll } from 'rdf-dataset-ext' 5 | import clownface from '../../index.js' 6 | import loadExample from '../support/example.js' 7 | import * as ns from '../support/namespace.js' 8 | import rdf from '../support/factory.js' 9 | 10 | describe('.deleteIn', () => { 11 | it('should be a function', () => { 12 | const cf = clownface({ dataset: rdf.dataset() }) 13 | 14 | assert.strictEqual(typeof cf.deleteIn, 'function') 15 | }) 16 | 17 | it('should return the called object', async () => { 18 | const dataset = addAll(rdf.dataset(), await loadExample()) 19 | const cf = clownface({ dataset }) 20 | 21 | assert.strictEqual(cf.deleteIn(), cf) 22 | }) 23 | 24 | it('should remove quads based on the object value', async () => { 25 | const dataset = addAll(rdf.dataset(), await loadExample()) 26 | const cf = clownface({ 27 | dataset, 28 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 29 | }) 30 | 31 | cf.deleteIn() 32 | 33 | assert.strictEqual(dataset.size, 118) 34 | }) 35 | 36 | it('should remove quads based on the object value and predicate', async () => { 37 | const dataset = addAll(rdf.dataset(), await loadExample()) 38 | const cf = clownface({ 39 | dataset, 40 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 41 | }) 42 | 43 | cf.deleteIn(rdf.namedNode('http://schema.org/knows')) 44 | 45 | assert.strictEqual(dataset.size, 119) 46 | }) 47 | 48 | it('should remove quads based on the object value and multiple predicates', async () => { 49 | const dataset = addAll(rdf.dataset(), await loadExample()) 50 | const cf = clownface({ 51 | dataset, 52 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 53 | }) 54 | 55 | cf.deleteIn([ 56 | rdf.namedNode('http://schema.org/knows'), 57 | rdf.namedNode('http://schema.org/spouse'), 58 | ]) 59 | 60 | assert.strictEqual(dataset.size, 118) 61 | }) 62 | 63 | it('should remove quads based on the object value, predicate and subject', async () => { 64 | const dataset = addAll(rdf.dataset(), await loadExample()) 65 | const cf = clownface({ 66 | dataset, 67 | term: ns.tbbtp('bernadette-rostenkowski'), 68 | }) 69 | 70 | cf.deleteIn(ns.schema.knows, ns.tbbtp('amy-farrah-fowler')) 71 | 72 | assert.strictEqual(dataset.size, 125) 73 | }) 74 | 75 | it('should remove quads based on the object value, multiple predicates and multiple subjects', async () => { 76 | const dataset = addAll(rdf.dataset(), await loadExample()) 77 | const cf = clownface({ 78 | dataset, 79 | term: ns.tbbtp('bernadette-rostenkowski'), 80 | }) 81 | 82 | cf.deleteIn([ns.schema.knows, ns.schema.spouse], [ns.tbbtp('amy-farrah-fowler'), ns.tbbtp('howard-wolowitz')]) 83 | 84 | assert.strictEqual(dataset.size, 123) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /test/Clownface/deleteList.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import addAll from 'rdf-dataset-ext/addAll.js' 5 | import clownface from '../../index.js' 6 | import rdf from '../support/factory.js' 7 | import * as ns from '../support/namespace.js' 8 | 9 | describe('.deleteList', () => { 10 | it('should be a function', () => { 11 | const cf = clownface({ dataset: rdf.dataset() }) 12 | 13 | assert.strictEqual(typeof cf.deleteList, 'function') 14 | }) 15 | 16 | it('should throw an error if predicate parameter is missing', () => { 17 | const dataset = rdf.dataset() 18 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 19 | const cf = clownface({ 20 | dataset, 21 | term: subject, 22 | }) 23 | 24 | let touched = false 25 | 26 | try { 27 | cf.deleteList(null) 28 | } catch (err) { 29 | touched = true 30 | } 31 | 32 | assert(touched) 33 | }) 34 | 35 | it('should remove list quads using the context term as subject and the given predicate', () => { 36 | const dataset = rdf.dataset() 37 | const subject = rdf.namedNode('http://localhost:8080/data/person/mary-cooper') 38 | const predicate = rdf.namedNode('http://schema.org/counts') 39 | const predicateOther = rdf.namedNode('http://schema.org/other') 40 | const item0 = rdf.literal('0') 41 | const item1 = rdf.literal('1') 42 | const first0 = rdf.blankNode() 43 | const first1 = rdf.blankNode() 44 | const other = rdf.quad(subject, predicateOther, item0) 45 | const cf = clownface({ 46 | dataset, 47 | term: subject, 48 | }) 49 | 50 | addAll(dataset, [ 51 | other, 52 | rdf.quad(subject, predicate, first0), 53 | rdf.quad(first0, ns.first, item0), 54 | rdf.quad(first0, ns.rest, first1), 55 | rdf.quad(first1, ns.first, item1), 56 | rdf.quad(first1, ns.rest, ns.nil), 57 | ]) 58 | 59 | cf.deleteList(predicate) 60 | 61 | assert.strictEqual(dataset.size, 1) 62 | assert([...dataset][0].equals(other)) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /test/Clownface/deleteOut.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import { addAll } from 'rdf-dataset-ext' 5 | import clownface from '../../index.js' 6 | import loadExample from '../support/example.js' 7 | import * as ns from '../support/namespace.js' 8 | import rdf from '../support/factory.js' 9 | 10 | describe('.deleteOut', () => { 11 | it('should be a function', () => { 12 | const cf = clownface({ dataset: rdf.dataset() }) 13 | 14 | assert.strictEqual(typeof cf.deleteOut, 'function') 15 | }) 16 | 17 | it('should return the called object', async () => { 18 | const dataset = addAll(rdf.dataset(), await loadExample()) 19 | const cf = clownface({ dataset }) 20 | 21 | assert.strictEqual(cf.deleteOut(), cf) 22 | }) 23 | 24 | it('should remove quads based on the object value', async () => { 25 | const dataset = addAll(rdf.dataset(), await loadExample()) 26 | const cf = clownface({ 27 | dataset, 28 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 29 | }) 30 | 31 | cf.deleteOut() 32 | 33 | assert.strictEqual(dataset.size, 113) 34 | }) 35 | 36 | it('should remove quads based on the object value and predicate', async () => { 37 | const dataset = addAll(rdf.dataset(), await loadExample()) 38 | const cf = clownface({ 39 | dataset, 40 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 41 | }) 42 | 43 | cf.deleteOut(rdf.namedNode('http://schema.org/knows')) 44 | 45 | assert.strictEqual(dataset.size, 119) 46 | }) 47 | 48 | it('should remove quads based on the object value and multiple predicates', async () => { 49 | const dataset = addAll(rdf.dataset(), await loadExample()) 50 | const cf = clownface({ 51 | dataset, 52 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 53 | }) 54 | 55 | cf.deleteOut([ 56 | rdf.namedNode('http://schema.org/knows'), 57 | rdf.namedNode('http://schema.org/spouse'), 58 | ]) 59 | 60 | assert.strictEqual(dataset.size, 118) 61 | }) 62 | 63 | it('should remove quads based on the object value and predicate', async () => { 64 | const dataset = addAll(rdf.dataset(), await loadExample()) 65 | const cf = clownface({ 66 | dataset, 67 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 68 | }) 69 | 70 | cf.deleteOut(rdf.namedNode('http://schema.org/knows')) 71 | 72 | assert.strictEqual(dataset.size, 119) 73 | }) 74 | 75 | it('should remove quads based on the object value, predicate and object', async () => { 76 | const dataset = addAll(rdf.dataset(), await loadExample()) 77 | const cf = clownface({ 78 | dataset, 79 | term: ns.tbbtp('bernadette-rostenkowski'), 80 | }) 81 | 82 | cf.deleteOut(ns.schema.knows, ns.tbbtp('amy-farrah-fowler')) 83 | 84 | assert.strictEqual(dataset.size, 125) 85 | }) 86 | 87 | it('should remove quads based on the object value, multiple predicates and multiple objects', async () => { 88 | const dataset = addAll(rdf.dataset(), await loadExample()) 89 | const cf = clownface({ 90 | dataset, 91 | term: ns.tbbtp('bernadette-rostenkowski'), 92 | }) 93 | 94 | cf.deleteOut([ns.schema.knows, ns.schema.spouse], [ns.tbbtp('amy-farrah-fowler'), ns.tbbtp('howard-wolowitz')]) 95 | 96 | assert.strictEqual(dataset.size, 123) 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /test/Clownface/factory.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import rdf from '../support/factory.js' 6 | import Clownface from '../../lib/Clownface.js' 7 | 8 | describe('factory', () => { 9 | it('should create a Clownface object', () => { 10 | const dataset = rdf.dataset() 11 | const cf = clownface({ dataset }) 12 | 13 | assert(cf instanceof Clownface) 14 | }) 15 | 16 | it('should forward the dataset argument', () => { 17 | const dataset = rdf.dataset() 18 | const cf = clownface({ dataset }) 19 | 20 | assert.strictEqual(cf._context.length, 1) 21 | assert.strictEqual(cf._context[0].dataset, dataset) 22 | }) 23 | 24 | it('should forward the term argument', () => { 25 | const dataset = rdf.dataset() 26 | const term = rdf.namedNode('http://example.org/subject') 27 | const cf = clownface({ dataset, term }) 28 | 29 | assert.strictEqual(cf._context.length, 1) 30 | assert.strictEqual(cf._context[0].term, term) 31 | }) 32 | 33 | it('should forward the value argument', () => { 34 | const dataset = rdf.dataset() 35 | const value = 'abc' 36 | const cf = clownface({ dataset, value }) 37 | 38 | assert.strictEqual(cf._context.length, 1) 39 | assert.strictEqual(cf._context[0].term.termType, 'Literal') 40 | assert.strictEqual(cf._context[0].term.value, value) 41 | }) 42 | 43 | it('should forward the factory argument', () => { 44 | const dataset = rdf.dataset() 45 | const factory = rdf 46 | const cf = clownface({ dataset, factory }) 47 | 48 | assert.strictEqual(cf.factory, factory) 49 | assert.strictEqual(cf._context[0].factory, factory) 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /test/Clownface/filter.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import loadExample from '../support/example.js' 6 | import rdf from '../support/factory.js' 7 | import Clownface from '../../lib/Clownface.js' 8 | import Context from '../../lib/Context.js' 9 | 10 | describe('.filter', () => { 11 | it('should be a function', () => { 12 | const cf = clownface({ dataset: rdf.dataset() }) 13 | 14 | assert.strictEqual(typeof cf.filter, 'function') 15 | }) 16 | 17 | it('should return a Dataset instance', () => { 18 | const cf = clownface({ dataset: rdf.dataset() }) 19 | 20 | assert(cf.filter(() => true) instanceof Clownface) 21 | }) 22 | 23 | it('should return instance with _context of correct type', () => { 24 | const cf = clownface({ dataset: rdf.dataset() }).namedNode() 25 | 26 | const [context] = cf.filter(() => true)._context 27 | 28 | assert(context instanceof Context) 29 | }) 30 | 31 | it('should call the function with Dataset parameter', async () => { 32 | const cf = clownface({ 33 | dataset: await loadExample(), 34 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 35 | }) 36 | 37 | cf.in(rdf.namedNode('http://schema.org/knows')).filter(item => { 38 | assert(item instanceof Clownface) 39 | 40 | return true 41 | }) 42 | }) 43 | 44 | it('should call the function for each context', async () => { 45 | const cf = clownface({ 46 | dataset: await loadExample(), 47 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 48 | }) 49 | 50 | let count = 0 51 | 52 | cf.in(rdf.namedNode('http://schema.org/knows')).filter(() => { 53 | count++ 54 | 55 | return true 56 | }) 57 | 58 | assert.strictEqual(count, 7) 59 | }) 60 | 61 | it('should filter the context based on the return value of the function', async () => { 62 | const cf = clownface({ 63 | dataset: await loadExample(), 64 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 65 | }) 66 | 67 | const result = cf.in(rdf.namedNode('http://schema.org/knows')).filter(item => { 68 | return !item.terms.every(term => { 69 | return term.equals(rdf.namedNode('http://localhost:8080/data/person/howard-wolowitz')) 70 | }) 71 | }) 72 | 73 | assert.strictEqual(result._context.length, 6) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /test/Clownface/forEach.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { describe, it } from 'mocha' 3 | import clownface from '../../index.js' 4 | import loadExample from '../support/example.js' 5 | import rdf from '../support/factory.js' 6 | import Clownface from '../../lib/Clownface.js' 7 | 8 | describe('.forEach', () => { 9 | it('should be a function', () => { 10 | const cf = clownface({ dataset: rdf.dataset() }) 11 | 12 | assert.strictEqual(typeof cf.forEach, 'function') 13 | }) 14 | 15 | it('should call the function with Dataset parameter', async () => { 16 | const cf = clownface({ 17 | dataset: await loadExample(), 18 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 19 | }) 20 | 21 | cf.in(rdf.namedNode('http://schema.org/knows')).forEach(item => { 22 | assert(item instanceof Clownface) 23 | 24 | return true 25 | }) 26 | }) 27 | 28 | it('should call the function for each context', async () => { 29 | const cf = clownface({ 30 | dataset: await loadExample(), 31 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 32 | }) 33 | 34 | let count = 0 35 | 36 | cf.in(rdf.namedNode('http://schema.org/knows')).forEach(() => { 37 | count++ 38 | 39 | return true 40 | }) 41 | 42 | assert.strictEqual(count, 7) 43 | }) 44 | 45 | it('should return self', () => { 46 | const cf = clownface({ 47 | dataset: rdf.dataset(), 48 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 49 | }) 50 | 51 | const forEachReturned = cf.forEach(() => {}) 52 | 53 | assert.strictEqual(forEachReturned, cf) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /test/Clownface/has.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import loadExample from '../support/example.js' 6 | import rdf from '../support/factory.js' 7 | import Clownface from '../../lib/Clownface.js' 8 | 9 | describe('.has', () => { 10 | it('should be a function', () => { 11 | const cf = clownface({ dataset: rdf.dataset() }) 12 | 13 | assert.strictEqual(typeof cf.has, 'function') 14 | }) 15 | 16 | it('should return a new Dataset instance', async () => { 17 | const predicate = rdf.namedNode('http://schema.org/givenName') 18 | const cf = clownface({ dataset: await loadExample() }) 19 | 20 | const result = cf.has(predicate, 'Stuart') 21 | 22 | assert(result instanceof Clownface) 23 | assert.notStrictEqual(result, cf) 24 | }) 25 | 26 | it('should use the dataset from the context', async () => { 27 | const dataset = await loadExample() 28 | const predicate = rdf.namedNode('http://schema.org/givenName') 29 | const cf = clownface({ dataset }) 30 | 31 | const result = cf.has(predicate, 'Stuart') 32 | 33 | assert.strictEqual(result._context[0].dataset, dataset) 34 | }) 35 | 36 | it('should use the found subject in the context', async () => { 37 | const subject = rdf.namedNode('http://localhost:8080/data/person/stuart-bloom') 38 | const predicate = rdf.namedNode('http://schema.org/givenName') 39 | const cf = clownface({ dataset: await loadExample() }) 40 | 41 | const result = cf.has(predicate, 'Stuart') 42 | 43 | assert.strictEqual(result._context.length, 1) 44 | assert(subject.equals(result._context[0].term)) 45 | }) 46 | 47 | it('should support multiple predicates in an array', async () => { 48 | const predicateA = rdf.namedNode('http://schema.org/knows') 49 | const predicateB = rdf.namedNode('http://schema.org/spouse') 50 | const object = rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski') 51 | const cf = clownface({ dataset: await loadExample() }) 52 | 53 | const result = cf.has([predicateA, predicateB], object) 54 | 55 | assert.strictEqual(result._context.length, 8) 56 | }) 57 | 58 | it('should support multiple predicates in an array', async () => { 59 | const predicate = rdf.namedNode('http://schema.org/givenName') 60 | const objectA = rdf.literal('Leonard') 61 | const objectB = rdf.literal('Sheldon') 62 | const cf = clownface({ dataset: await loadExample() }) 63 | 64 | const result = cf.has(predicate, [objectA, objectB]) 65 | 66 | assert.strictEqual(result._context.length, 2) 67 | }) 68 | 69 | it('should use context term as subject', async () => { 70 | const subjectA = rdf.namedNode('http://localhost:8080/data/person/sheldon-cooper') 71 | const subjectB = rdf.namedNode('http://localhost:8080/data/person/stuart-bloom') 72 | const predicate = rdf.namedNode('http://schema.org/givenName') 73 | const objects = [ 74 | rdf.literal('Leonard'), 75 | rdf.literal('Sheldon'), 76 | ] 77 | 78 | const cf = clownface({ dataset: await loadExample(), term: [subjectA, subjectB] }) 79 | 80 | const result = cf.has(predicate, objects) 81 | 82 | assert.strictEqual(result._context.length, 1) 83 | assert(result._context[0].term.equals(subjectA)) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /test/Clownface/in.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import loadExample from '../support/example.js' 6 | import rdf from '../support/factory.js' 7 | import * as ns from '../support/namespace.js' 8 | import Clownface from '../../lib/Clownface.js' 9 | 10 | describe('.in', () => { 11 | it('should be a function', () => { 12 | const cf = clownface({ dataset: rdf.dataset() }) 13 | 14 | assert.strictEqual(typeof cf.in, 'function') 15 | }) 16 | 17 | it('should return a new Clownface instance', async () => { 18 | const cf = clownface({ 19 | dataset: await loadExample(), 20 | value: '2311 North Los Robles Avenue, Aparment 4A', 21 | }) 22 | 23 | const result = cf.in(rdf.namedNode('http://schema.org/streetAddress')) 24 | 25 | assert(result instanceof Clownface) 26 | assert.notStrictEqual(result, cf) 27 | }) 28 | 29 | it('should search object -> subject without predicate', async () => { 30 | const cf = clownface({ 31 | dataset: await loadExample(), 32 | term: ns.tbbtp('bernadette-rostenkowski'), 33 | }) 34 | 35 | const result = cf.in() 36 | 37 | assert.strictEqual(result._context.length, 8) 38 | }) 39 | 40 | it('should search object -> subject with predicate', async () => { 41 | const cf = clownface({ 42 | dataset: await loadExample(), 43 | value: '2311 North Los Robles Avenue, Aparment 4A', 44 | }) 45 | 46 | const result = cf.in(rdf.namedNode('http://schema.org/streetAddress')) 47 | 48 | assert.strictEqual(result._context.length, 2) 49 | assert.strictEqual(result._context[0].term.termType, 'BlankNode') 50 | }) 51 | 52 | it('should support multiple predicate values in an array', async () => { 53 | const cf = clownface({ 54 | dataset: await loadExample(), 55 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 56 | }) 57 | 58 | const result = cf.in([ 59 | rdf.namedNode('http://schema.org/spouse'), 60 | rdf.namedNode('http://schema.org/knows'), 61 | ]) 62 | 63 | assert.strictEqual(result._context.length, 8) 64 | }) 65 | 66 | it('should support clownface objects as predicates', async () => { 67 | const cf = clownface({ 68 | dataset: await loadExample(), 69 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 70 | }) 71 | 72 | const result = cf.in(cf.node([ 73 | rdf.namedNode('http://schema.org/spouse'), 74 | rdf.namedNode('http://schema.org/knows'), 75 | ])) 76 | 77 | assert.strictEqual(result._context.length, 8) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/Clownface/isList.test.js: -------------------------------------------------------------------------------- 1 | import { strictEqual } from 'assert' 2 | import { describe, it } from 'mocha' 3 | import clownface from '../../index.js' 4 | import rdf from '../support/factory.js' 5 | import * as ns from '../support/namespace.js' 6 | 7 | describe('.isList', () => { 8 | it('should be a function', () => { 9 | const ptr = clownface({}) 10 | 11 | strictEqual(typeof ptr.isList, 'function') 12 | }) 13 | 14 | it('should return false if there is no term', () => { 15 | const ptr = clownface({}) 16 | 17 | strictEqual(ptr.isList(), false) 18 | }) 19 | 20 | it('should return false if the term is not a list', () => { 21 | const ptr = clownface({ term: rdf.blankNode(), dataset: rdf.dataset() }) 22 | 23 | strictEqual(ptr.isList(), false) 24 | }) 25 | 26 | it('should return true if the term points to a list', () => { 27 | const item = [rdf.blankNode(), rdf.blankNode()] 28 | const dataset = rdf.dataset([ 29 | rdf.quad(item[0], ns.first, rdf.literal('1')), 30 | rdf.quad(item[0], ns.rest, item[1]), 31 | rdf.quad(item[1], ns.first, rdf.literal('2')), 32 | rdf.quad(item[1], ns.rest, ns.nil), 33 | ]) 34 | const ptr = clownface({ term: item[0], dataset }) 35 | 36 | strictEqual(ptr.isList(), true) 37 | }) 38 | 39 | it('should return true if the term points to an empty list', () => { 40 | const ptr = clownface({ term: ns.nil }) 41 | 42 | strictEqual(ptr.isList(), true) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/Clownface/list.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import { addAll } from 'rdf-dataset-ext' 5 | import clownface from '../../index.js' 6 | import rdf from '../support/factory.js' 7 | import * as ns from '../support/namespace.js' 8 | 9 | function listDataset() { 10 | const start = rdf.blankNode() 11 | const item = [rdf.blankNode(), rdf.blankNode(), rdf.blankNode()] 12 | 13 | return rdf.dataset([ 14 | rdf.quad(start, ns.list, item[0]), 15 | rdf.quad(item[0], ns.first, rdf.literal('1')), 16 | rdf.quad(item[0], ns.rest, item[1]), 17 | rdf.quad(item[1], ns.first, rdf.literal('2')), 18 | rdf.quad(item[1], ns.rest, item[2]), 19 | rdf.quad(item[2], ns.first, rdf.literal('3')), 20 | rdf.quad(item[2], ns.rest, ns.nil), 21 | ]) 22 | } 23 | 24 | describe('.list', () => { 25 | it('should be a function', () => { 26 | const cf = clownface({ dataset: listDataset() }) 27 | 28 | assert.strictEqual(typeof cf.list, 'function') 29 | }) 30 | 31 | it('should return an iterator', () => { 32 | const cf = clownface({ dataset: listDataset() }) 33 | 34 | assert.strictEqual(typeof cf.out(ns.list).list()[Symbol.iterator], 'function') 35 | }) 36 | 37 | it('should iterate over a single term context', () => { 38 | const cf = clownface({ dataset: listDataset() }) 39 | 40 | const result = [] 41 | 42 | for (const item of cf.out(ns.list).list()) { 43 | result.push(item.value) 44 | } 45 | 46 | assert.deepStrictEqual(result, ['1', '2', '3']) 47 | }) 48 | 49 | it('should not iterate over a multiple term context', () => { 50 | const cf = clownface({ dataset: addAll(listDataset(), listDataset()) }) 51 | 52 | let list 53 | let touched = false 54 | 55 | try { 56 | list = cf.out(ns.list).list()[Symbol.iterator] 57 | } catch (e) { 58 | touched = true 59 | } 60 | 61 | assert(touched) 62 | assert(!list) 63 | }) 64 | 65 | it('should return empty iterator when no list exists', () => { 66 | const cf = clownface({ dataset: listDataset() }) 67 | 68 | const list = cf.out(rdf.namedNode('http://example.com/not-list')).list()[Symbol.iterator]() 69 | const first = list.next() 70 | 71 | assert.strictEqual(first.done, true) 72 | }) 73 | 74 | it('should return empty iterator when list is rdf:nil', () => { 75 | const start = rdf.blankNode() 76 | const dataset = rdf.dataset([ 77 | rdf.quad(start, ns.list, ns.nil), 78 | ]) 79 | const cf = clownface({ dataset }) 80 | 81 | const list = cf.out(ns.list).list()[Symbol.iterator]() 82 | const first = list.next() 83 | 84 | assert.strictEqual(first.done, true) 85 | }) 86 | 87 | it('should return null when node is literal', () => { 88 | const start = rdf.blankNode() 89 | const dataset = rdf.dataset([ 90 | rdf.quad(start, ns.list, rdf.literal('not list')), 91 | ]) 92 | const cf = clownface({ dataset }) 93 | 94 | const list = cf.out(ns.list).list() 95 | 96 | assert.strictEqual(list, null) 97 | }) 98 | 99 | it('should return null when node is not a list', () => { 100 | const start = rdf.blankNode() 101 | const dataset = rdf.dataset([ 102 | rdf.quad(start, ns.list, rdf.namedNode('not list')), 103 | ]) 104 | const cf = clownface({ dataset }) 105 | 106 | const list = cf.out(ns.list).list() 107 | 108 | assert.strictEqual(list, null) 109 | }) 110 | 111 | it('should throw when a list node has multiple rdf:first', () => { 112 | const start = rdf.blankNode() 113 | const listNode = rdf.blankNode() 114 | const dataset = rdf.dataset([ 115 | rdf.quad(start, ns.list, listNode), 116 | rdf.quad(listNode, ns.first, rdf.literal('1')), 117 | rdf.quad(listNode, ns.first, rdf.literal('3')), 118 | rdf.quad(listNode, ns.rest, ns.nil), 119 | ]) 120 | const cf = clownface({ dataset }) 121 | 122 | assert.throws(() => { 123 | [...cf.out(ns.list).list()] // eslint-disable-line no-unused-expressions 124 | }) 125 | }) 126 | 127 | it('should throw when a list node has multiple rdf:rest', () => { 128 | const start = rdf.blankNode() 129 | const listNode = rdf.blankNode() 130 | 131 | const dataset = rdf.dataset([ 132 | rdf.quad(start, ns.list, listNode), 133 | rdf.quad(listNode, ns.first, rdf.literal('1')), 134 | rdf.quad(listNode, ns.rest, rdf.blankNode()), 135 | rdf.quad(listNode, ns.rest, ns.nil), 136 | ]) 137 | const cf = clownface({ dataset }) 138 | 139 | assert.throws(() => { 140 | [...cf.out(ns.list).list()] // eslint-disable-line no-unused-expressions 141 | }) 142 | }) 143 | }) 144 | -------------------------------------------------------------------------------- /test/Clownface/literal.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import loadExample from '../support/example.js' 6 | import rdf from '../support/factory.js' 7 | import Clownface from '../../lib/Clownface.js' 8 | 9 | describe('.literal', () => { 10 | it('should be a function', () => { 11 | const cf = clownface({ dataset: rdf.dataset() }) 12 | 13 | assert.strictEqual(typeof cf.literal, 'function') 14 | }) 15 | 16 | it('should return a new Clownface instance', () => { 17 | const cf = clownface({ dataset: rdf.dataset() }) 18 | 19 | const result = cf.literal('example') 20 | 21 | assert(result instanceof Clownface) 22 | assert.notStrictEqual(result, cf) 23 | }) 24 | 25 | it('should use the dataset from the context', () => { 26 | const dataset = rdf.dataset() 27 | const cf = clownface({ dataset }) 28 | 29 | const result = cf.literal('example') 30 | 31 | assert.strictEqual(result._context[0].dataset, dataset) 32 | }) 33 | 34 | it('should use the given string as Literal', async () => { 35 | const value = '2311 North Los Robles Avenue, Aparment 4A' 36 | const cf = clownface({ dataset: await loadExample() }) 37 | 38 | const result = cf.literal(value) 39 | 40 | assert.strictEqual(result._context.length, 1) 41 | assert.strictEqual(result._context[0].term.termType, 'Literal') 42 | assert.strictEqual(result._context[0].term.value, value) 43 | }) 44 | 45 | it('should use the given double number as Literal', () => { 46 | const value = 1.23 47 | const cf = clownface({ dataset: rdf.dataset() }) 48 | 49 | const result = cf.literal(value) 50 | 51 | assert.strictEqual(result._context.length, 1) 52 | assert.strictEqual(result._context[0].term.termType, 'Literal') 53 | assert.strictEqual(result._context[0].term.value, value.toString(10)) 54 | assert.strictEqual(result._context[0].term.datatype.value, 'http://www.w3.org/2001/XMLSchema#double') 55 | }) 56 | 57 | it('should use the given integer number as Literal', () => { 58 | const value = 123 59 | const cf = clownface({ dataset: rdf.dataset() }) 60 | 61 | const result = cf.literal(value) 62 | 63 | assert.strictEqual(result._context.length, 1) 64 | assert.strictEqual(result._context[0].term.termType, 'Literal') 65 | assert.strictEqual(result._context[0].term.value, value.toString(10)) 66 | assert.strictEqual(result._context[0].term.datatype.value, 'http://www.w3.org/2001/XMLSchema#integer') 67 | }) 68 | 69 | it('should use the given boolean as Literal', () => { 70 | const value = true 71 | const cf = clownface({ dataset: rdf.dataset() }) 72 | 73 | const result = cf.literal(value) 74 | 75 | assert.strictEqual(result._context.length, 1) 76 | assert.strictEqual(result._context[0].term.termType, 'Literal') 77 | assert.strictEqual(result._context[0].term.value, value.toString()) 78 | assert.strictEqual(result._context[0].term.datatype.value, 'http://www.w3.org/2001/XMLSchema#boolean') 79 | }) 80 | 81 | it('should use the given false boolean as Literal', () => { 82 | const value = false 83 | const cf = clownface({ dataset: rdf.dataset() }) 84 | 85 | const result = cf.literal(value) 86 | 87 | assert.strictEqual(result._context.length, 1) 88 | assert.strictEqual(result._context[0].term.termType, 'Literal') 89 | assert.strictEqual(result._context[0].term.value, value.toString()) 90 | assert.strictEqual(result._context[0].term.datatype.value, 'http://www.w3.org/2001/XMLSchema#boolean') 91 | }) 92 | 93 | it('should use the numeric 0 as Literal', () => { 94 | const value = 0 95 | const cf = clownface({ dataset: rdf.dataset() }) 96 | 97 | const result = cf.literal(value) 98 | 99 | assert.strictEqual(result._context.length, 1) 100 | assert.strictEqual(result._context[0].term.termType, 'Literal') 101 | assert.strictEqual(result._context[0].term.value, value.toString()) 102 | assert.strictEqual(result._context[0].term.datatype.value, 'http://www.w3.org/2001/XMLSchema#integer') 103 | }) 104 | 105 | it('should use empty string as Literal', () => { 106 | const value = '' 107 | const cf = clownface({ dataset: rdf.dataset() }) 108 | 109 | const result = cf.literal(value) 110 | 111 | assert.strictEqual(result._context.length, 1) 112 | assert.strictEqual(result._context[0].term.termType, 'Literal') 113 | assert.strictEqual(result._context[0].term.value, value.toString()) 114 | assert.strictEqual(result._context[0].term.datatype.value, 'http://www.w3.org/2001/XMLSchema#string') 115 | }) 116 | 117 | it('should not create nodes for empty array', () => { 118 | const value = [] 119 | const cf = clownface({ dataset: rdf.dataset() }) 120 | 121 | const result = cf.literal(value) 122 | 123 | assert.strictEqual(result._context.length, 0) 124 | }) 125 | 126 | it('should support multiple values in an array', () => { 127 | const cf = clownface({ dataset: rdf.dataset() }) 128 | 129 | const result = cf.literal(['1', '2']) 130 | 131 | assert.strictEqual(result._context.length, 2) 132 | assert.strictEqual(result._context[0].term.termType, 'Literal') 133 | assert.strictEqual(result._context[0].term.value, '1') 134 | assert.strictEqual(result._context[1].term.termType, 'Literal') 135 | assert.strictEqual(result._context[1].term.value, '2') 136 | }) 137 | 138 | it('should support multiple values in an iterable', () => { 139 | const cf = clownface({ dataset: rdf.dataset() }) 140 | 141 | const result = cf.literal(new Set(['1', '2'])) 142 | 143 | assert.strictEqual(result._context.length, 2) 144 | assert.strictEqual(result._context[0].term.termType, 'Literal') 145 | assert.strictEqual(result._context[0].term.value, '1') 146 | assert.strictEqual(result._context[1].term.termType, 'Literal') 147 | assert.strictEqual(result._context[1].term.value, '2') 148 | }) 149 | 150 | it('should use the given datatype', () => { 151 | const datatypeIri = 'http://example.org/datatype' 152 | const cf = clownface({ dataset: rdf.dataset() }) 153 | 154 | const result = cf.literal('example', datatypeIri) 155 | 156 | assert.strictEqual(result._context.length, 1) 157 | assert.strictEqual(result._context[0].term.termType, 'Literal') 158 | assert.strictEqual(result._context[0].term.datatype.termType, 'NamedNode') 159 | assert.strictEqual(result._context[0].term.datatype.value, datatypeIri) 160 | }) 161 | 162 | it('should accept rdfjs NamedNodes as datatype', () => { 163 | const datatype = rdf.namedNode('http://example.org/datatype') 164 | const cf = clownface({ dataset: rdf.dataset() }) 165 | 166 | const result = cf.literal('example', datatype) 167 | 168 | assert.strictEqual(result._context.length, 1) 169 | assert.strictEqual(result._context[0].term.termType, 'Literal') 170 | assert.strictEqual(result._context[0].term.datatype.termType, 'NamedNode') 171 | assert.strictEqual(result._context[0].term.datatype.value, datatype.value) 172 | }) 173 | 174 | it('should use the given language', () => { 175 | const language = 'en' 176 | const cf = clownface({ dataset: rdf.dataset() }) 177 | 178 | const result = cf.literal('example', language) 179 | 180 | assert.strictEqual(result._context.length, 1) 181 | assert.strictEqual(result._context[0].term.termType, 'Literal') 182 | assert.strictEqual(result._context[0].term.datatype.termType, 'NamedNode') 183 | assert.strictEqual(result._context[0].term.language, language) 184 | }) 185 | }) 186 | -------------------------------------------------------------------------------- /test/Clownface/map.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import loadExample from '../support/example.js' 6 | import rdf from '../support/factory.js' 7 | import Clownface from '../../lib/Clownface.js' 8 | 9 | describe('.map', () => { 10 | it('should be a function', () => { 11 | const cf = clownface({ dataset: rdf.dataset() }) 12 | 13 | assert.strictEqual(typeof cf.map, 'function') 14 | }) 15 | 16 | it('should return an Array', () => { 17 | const cf = clownface({ dataset: rdf.dataset() }) 18 | 19 | assert(Array.isArray(cf.map(item => item))) 20 | }) 21 | 22 | it('should call the function with Dataset parameter', async () => { 23 | const cf = clownface({ 24 | dataset: await loadExample(), 25 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 26 | }) 27 | 28 | cf.in(rdf.namedNode('http://schema.org/knows')).map(item => { 29 | assert(item instanceof Clownface) 30 | 31 | return true 32 | }) 33 | }) 34 | 35 | it('should call the function for each context', async () => { 36 | const cf = clownface({ 37 | dataset: await loadExample(), 38 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 39 | }) 40 | 41 | let count = 0 42 | 43 | cf.in(rdf.namedNode('http://schema.org/knows')).map(() => { 44 | count++ 45 | 46 | return true 47 | }) 48 | 49 | assert.strictEqual(count, 7) 50 | }) 51 | 52 | it('should return an array of all return values', async () => { 53 | const cf = clownface({ 54 | dataset: await loadExample(), 55 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 56 | }) 57 | 58 | const result = cf.in(rdf.namedNode('http://schema.org/knows')).map((item, index) => index) 59 | 60 | assert.deepStrictEqual(result, [0, 1, 2, 3, 4, 5, 6]) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /test/Clownface/namedNode.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import rdf from '../support/factory.js' 6 | import Clownface from '../../lib/Clownface.js' 7 | 8 | describe('.namedNode', () => { 9 | it('should be a function', () => { 10 | const cf = clownface({ dataset: rdf.dataset() }) 11 | 12 | assert.strictEqual(typeof cf.namedNode, 'function') 13 | }) 14 | 15 | it('should return a new Clownface instance', () => { 16 | const cf = clownface({ dataset: rdf.dataset() }) 17 | 18 | const result = cf.namedNode('http://localhost:8080/data/person/stuart-bloom') 19 | 20 | assert(result instanceof Clownface) 21 | assert.notStrictEqual(result, cf) 22 | }) 23 | 24 | it('should use the dataset from the context', () => { 25 | const dataset = rdf.dataset() 26 | const cf = clownface({ dataset }) 27 | 28 | const result = cf.namedNode('http://localhost:8080/data/person/stuart-bloom') 29 | 30 | assert.strictEqual(result._context[0].dataset, dataset) 31 | }) 32 | 33 | it('should use the given string as IRI for the Named Node', () => { 34 | const iri = 'http://localhost:8080/data/person/stuart-bloom' 35 | const cf = clownface({ dataset: rdf.dataset() }) 36 | 37 | const result = cf.namedNode(iri) 38 | 39 | assert.strictEqual(result._context.length, 1) 40 | assert.strictEqual(result._context[0].term.termType, 'NamedNode') 41 | assert.strictEqual(result._context[0].term.value, iri) 42 | }) 43 | 44 | it('should support multiple values in an array', () => { 45 | const iriA = 'http://example.org/a' 46 | const iriB = 'http://example.org/b' 47 | const cf = clownface({ dataset: rdf.dataset() }) 48 | 49 | const result = cf.namedNode([iriA, iriB]) 50 | 51 | assert.strictEqual(result._context.length, 2) 52 | assert.strictEqual(result._context[0].term.termType, 'NamedNode') 53 | assert.strictEqual(result._context[0].term.value, iriA) 54 | assert.strictEqual(result._context[1].term.termType, 'NamedNode') 55 | assert.strictEqual(result._context[1].term.value, iriB) 56 | }) 57 | 58 | it('should support multiple values from NamedNode iterator', () => { 59 | const iriA = rdf.namedNode('http://example.org/a') 60 | const iriB = rdf.namedNode('http://example.org/b') 61 | const cf = clownface({ dataset: rdf.dataset() }) 62 | 63 | const result = cf.namedNode(rdf.termSet([iriA, iriB])) 64 | 65 | assert.strictEqual(result._context.length, 2) 66 | assert.deepStrictEqual(result._context[0].term, iriA) 67 | assert.deepStrictEqual(result._context[1].term, iriB) 68 | }) 69 | 70 | it('should support multiple values from string iterator', () => { 71 | const iriA = 'http://example.org/a' 72 | const iriB = 'http://example.org/b' 73 | const cf = clownface({ dataset: rdf.dataset() }) 74 | 75 | const result = cf.namedNode(new Set([iriA, iriB])) 76 | 77 | assert.strictEqual(result._context.length, 2) 78 | assert.strictEqual(result._context[0].term.termType, 'NamedNode') 79 | assert.strictEqual(result._context[0].term.value, iriA) 80 | assert.strictEqual(result._context[1].term.termType, 'NamedNode') 81 | assert.strictEqual(result._context[1].term.value, iriB) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /test/Clownface/node.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import { xsd } from '../support/namespace.js' 5 | import clownface from '../../index.js' 6 | import loadExample from '../support/example.js' 7 | import rdf from '../support/factory.js' 8 | import Clownface from '../../lib/Clownface.js' 9 | 10 | describe('.node', () => { 11 | it('should be a function', async () => { 12 | const cf = clownface({ dataset: rdf.dataset() }) 13 | 14 | assert.strictEqual(typeof cf.node, 'function') 15 | }) 16 | 17 | it('should return a new Dataset instance', async () => { 18 | const term = rdf.namedNode('http://localhost:8080/data/person/stuart-bloom') 19 | const cf = clownface({ dataset: await loadExample() }) 20 | 21 | const result = cf.node(term) 22 | 23 | assert(result instanceof Clownface) 24 | assert.notStrictEqual(result, cf) 25 | }) 26 | 27 | it('should use the dataset from the context', async () => { 28 | const dataset = await loadExample() 29 | const term = rdf.namedNode('http://localhost:8080/data/person/stuart-bloom') 30 | const cf = clownface({ dataset }) 31 | 32 | const result = cf.node(term) 33 | 34 | assert.strictEqual(result._context[0].dataset, dataset) 35 | }) 36 | 37 | it('should use the given Named Node', async () => { 38 | const term = rdf.namedNode('http://localhost:8080/data/person/stuart-bloom') 39 | const cf = clownface({ dataset: await loadExample() }) 40 | 41 | const result = cf.node(term) 42 | 43 | assert.strictEqual(result._context.length, 1) 44 | assert(term.equals(result._context[0].term)) 45 | }) 46 | 47 | it('should use the given string as Literal', async () => { 48 | const value = '2311 North Los Robles Avenue, Aparment 4A' 49 | const cf = clownface({ dataset: await loadExample() }) 50 | 51 | const result = cf.node(value) 52 | 53 | assert.strictEqual(result._context.length, 1) 54 | assert.strictEqual(result._context[0].term.termType, 'Literal') 55 | assert.strictEqual(result._context[0].term.value, value) 56 | }) 57 | 58 | it('should use the given number as Literal', async () => { 59 | const cf = clownface({ dataset: await loadExample() }) 60 | 61 | const result = cf.node(123) 62 | 63 | assert.strictEqual(result._context.length, 1) 64 | assert.strictEqual(result._context[0].term.termType, 'Literal') 65 | assert.strictEqual(result._context[0].term.value, '123') 66 | }) 67 | 68 | it('should throw an error if an unknown type is given', () => { 69 | const cf = clownface({ dataset: rdf.dataset() }) 70 | 71 | let result 72 | let catched = false 73 | 74 | try { 75 | result = cf.node(new RegExp()).nodes() 76 | } catch (e) { 77 | catched = true 78 | } 79 | 80 | assert.strictEqual(catched, true) 81 | assert.strictEqual(result, undefined) 82 | }) 83 | 84 | it('should support multiple values in an array', () => { 85 | const cf = clownface({ dataset: rdf.dataset() }) 86 | 87 | const result = cf.node(['1', '2']) 88 | 89 | assert.strictEqual(result._context.length, 2) 90 | assert.strictEqual(result._context[0].term.termType, 'Literal') 91 | assert.strictEqual(result._context[0].term.value, '1') 92 | assert.strictEqual(result._context[1].term.termType, 'Literal') 93 | assert.strictEqual(result._context[1].term.value, '2') 94 | }) 95 | 96 | it('should create Named Node context if type is NamedNode', () => { 97 | const cf = clownface({ dataset: rdf.dataset() }) 98 | 99 | const result = cf.node('http://example.org/', { type: 'NamedNode' }) 100 | 101 | assert.strictEqual(result._context.length, 1) 102 | assert.strictEqual(result._context[0].term.termType, 'NamedNode') 103 | }) 104 | 105 | it('should create Blank Node context if type is BlankNode', () => { 106 | const cf = clownface({ dataset: rdf.dataset() }) 107 | 108 | const result = cf.node(null, { type: 'BlankNode' }) 109 | 110 | assert.strictEqual(result._context.length, 1) 111 | assert.strictEqual(result._context[0].term.termType, 'BlankNode') 112 | }) 113 | 114 | it('should create nodes from term iterator', () => { 115 | const cf = clownface({ dataset: rdf.dataset() }) 116 | const nodes = rdf.termSet([ 117 | rdf.namedNode('http://example.com/'), 118 | rdf.literal('10', xsd.int), 119 | rdf.blankNode(), 120 | ]) 121 | 122 | const result = cf.node(nodes) 123 | 124 | assert.strictEqual(result._context.length, 3) 125 | assert.deepStrictEqual(result._context[0].term, rdf.namedNode('http://example.com/')) 126 | assert.deepStrictEqual(result._context[1].term, rdf.literal('10', xsd.int)) 127 | assert.strictEqual(result._context[2].term.termType, 'BlankNode') 128 | }) 129 | 130 | it('should use the given datatype', () => { 131 | const datatype = rdf.namedNode('http://example.org/datatype') 132 | const cf = clownface({ dataset: rdf.dataset() }) 133 | 134 | const result = cf.node('example', { datatype: datatype.value }) 135 | 136 | assert.strictEqual(result._context.length, 1) 137 | assert.strictEqual(result._context[0].term.termType, 'Literal') 138 | assert.strictEqual(result._context[0].term.datatype.termType, 'NamedNode') 139 | assert.strictEqual(result._context[0].term.datatype.value, datatype.value) 140 | }) 141 | 142 | it('should accept rdfjs NamedNodes as datatype', () => { 143 | const datatype = rdf.namedNode('http://example.org/datatype') 144 | const cf = clownface({ dataset: rdf.dataset() }) 145 | 146 | const result = cf.node('example', { datatype }) 147 | 148 | assert.strictEqual(result._context.length, 1) 149 | assert.strictEqual(result._context[0].term.termType, 'Literal') 150 | assert.strictEqual(result._context[0].term.datatype.termType, 'NamedNode') 151 | assert.strictEqual(result._context[0].term.datatype.value, datatype.value) 152 | }) 153 | }) 154 | -------------------------------------------------------------------------------- /test/Clownface/out.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { describe, it } from 'mocha' 3 | import clownface from '../../index.js' 4 | import loadExample from '../support/example.js' 5 | import rdf from '../support/factory.js' 6 | import Clownface from '../../lib/Clownface.js' 7 | import * as ns from '../support/namespace.js' 8 | import parse from '../support/parse.js' 9 | 10 | describe('.out', () => { 11 | it('should be a function', () => { 12 | const cf = clownface({ dataset: rdf.dataset() }) 13 | 14 | assert.strictEqual(typeof cf.out, 'function') 15 | }) 16 | 17 | it('should return a new Clownface instance', async () => { 18 | const cf = clownface({ 19 | dataset: await loadExample(), 20 | term: rdf.namedNode('http://localhost:8080/data/person/amy-farrah-fowler'), 21 | }) 22 | 23 | const result = cf.out(rdf.namedNode('http://schema.org/jobTitle')) 24 | 25 | assert(result instanceof Clownface) 26 | assert.notStrictEqual(result, cf) 27 | }) 28 | 29 | it('should search subject -> object without predicate', async () => { 30 | const cf = clownface({ 31 | dataset: await loadExample(), 32 | term: ns.tbbtp('amy-farrah-fowler'), 33 | }) 34 | 35 | const result = cf.out() 36 | 37 | assert.strictEqual(result._context.length, 12) 38 | }) 39 | 40 | it('should search subject -> object with predicate', async () => { 41 | const cf = clownface({ 42 | dataset: await loadExample(), 43 | term: rdf.namedNode('http://localhost:8080/data/person/amy-farrah-fowler'), 44 | }) 45 | 46 | const result = cf.out(rdf.namedNode('http://schema.org/jobTitle')) 47 | 48 | assert.strictEqual(result._context.length, 1) 49 | assert.strictEqual(result._context[0].term.termType, 'Literal') 50 | assert.strictEqual(result._context[0].term.value, 'neurobiologist') 51 | }) 52 | 53 | it('should support multiple predicate values in an array', async () => { 54 | const cf = clownface({ 55 | dataset: await loadExample(), 56 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 57 | }) 58 | 59 | const result = cf.out([ 60 | rdf.namedNode('http://schema.org/familyName'), 61 | rdf.namedNode('http://schema.org/givenName'), 62 | ]) 63 | 64 | assert.strictEqual(result._context.length, 2) 65 | }) 66 | 67 | it('should support clownface objects as predicates', async () => { 68 | const cf = clownface({ 69 | dataset: await loadExample(), 70 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 71 | }) 72 | 73 | const result = cf.out(cf.node([ 74 | rdf.namedNode('http://schema.org/familyName'), 75 | rdf.namedNode('http://schema.org/givenName'), 76 | ])) 77 | 78 | assert.strictEqual(result._context.length, 2) 79 | }) 80 | 81 | describe('with language option', () => { 82 | const testData = `<${ns.ex.ananas.value}> 83 | <${ns.rdfs.label.value}> "Pineapple" ; 84 | <${ns.rdfs.label.value}> "Ananas"@pl ; 85 | <${ns.rdfs.label.value}> "Ananas"@de ; 86 | <${ns.rdfs.label.value}> "Ananász"@hu ; 87 | <${ns.rdfs.label.value}> "Ananas"@sr-Latn ; 88 | <${ns.rdfs.label.value}> "Ананас"@sr-Cyrl ; 89 | . 90 | 91 | <${ns.ex.noLabels.value}> <${ns.rdfs.label.value}> _:foo , <${ns.ex.bar.value.value}>, 41 . 92 | 93 | <${ns.ex.apple.value}> 94 | <${ns.rdfs.label.value}> "Apple"@en ; 95 | <${ns.rdfs.label.value}> "Apfel"@de ; 96 | <${ns.rdfs.label.value}> "Јабука"@sr-Cyrl . 97 | 98 | <${ns.ex.carrot.value}> 99 | <${ns.rdfs.label.value}> "Karotte"@de ; 100 | <${ns.rdfs.label.value}> "Karotte"@de-AT ; 101 | <${ns.rdfs.label.value}> "Rüebli"@de-CH ; 102 | . 103 | 104 | <${ns.ex.eggplant.value}> 105 | <${ns.rdfs.label.value}> "Psianka podłużna"@pl, "Bakłażan"@pl, "Oberżyna"@pl . 106 | 107 | <${ns.ex.kongressstrasse.value}> 108 | <${ns.rdfs.label.value}> "Kongressstraße"@de ; 109 | <${ns.rdfs.label.value}> "Kongreßstraße"@de-DE-1901 ; 110 | .`.toString() 111 | 112 | describe('filtered by single language parameter', () => { 113 | it('should not return non-literals and non-string literals when language parameter is defined', async () => { 114 | const apple = (await parse(testData)).node(ns.ex.noLabels) 115 | 116 | const label = apple.out(ns.rdfs.label, { language: '' }) 117 | 118 | assert.strictEqual(label.terms.length, 0) 119 | }) 120 | 121 | it('should return exact match for given language', async () => { 122 | const apple = (await parse(testData)).node(ns.ex.apple) 123 | 124 | const label = apple.out(ns.rdfs.label, { language: 'de' }) 125 | 126 | assert(label.term.equals(rdf.literal('Apfel', 'de'))) 127 | }) 128 | 129 | it('should return plain string when language is empty string', async () => { 130 | const apple = (await parse(testData)).node(ns.ex.ananas) 131 | 132 | const label = apple.out(ns.rdfs.label, { language: '' }) 133 | 134 | assert(label.term.equals(rdf.literal('Pineapple'))) 135 | }) 136 | 137 | it('should skip pointers which do not have matching language', async () => { 138 | const apple = (await parse(testData)).node(ns.ex.ananas) 139 | 140 | const label = apple.out(ns.rdfs.label, { language: 'en' }) 141 | 142 | assert.strictEqual(label.values.length, 0) 143 | }) 144 | 145 | it('should return any result for wildcard language', async () => { 146 | const apple = (await parse(testData)).node(ns.ex.ananas) 147 | 148 | const label = apple.out(ns.rdfs.label, { language: '*' }) 149 | 150 | assert.ok(label.term) 151 | }) 152 | 153 | it('should return all matching literals for a language', async () => { 154 | const apple = (await parse(testData)).node(ns.ex.eggplant) 155 | 156 | const label = apple.out(ns.rdfs.label, { language: 'pl' }) 157 | 158 | assert.strictEqual(label.terms.length, 3) 159 | }) 160 | 161 | it('should return all matching literals for a wildcard language', async () => { 162 | const apple = (await parse(testData)).node(ns.ex.eggplant) 163 | 164 | const label = apple.out(ns.rdfs.label, { language: '*' }) 165 | 166 | assert.strictEqual(label.terms.length, 3) 167 | }) 168 | 169 | it('should be case-insensitive', async () => { 170 | const apple = (await parse(testData)).node(ns.ex.apple) 171 | 172 | const label = apple.out(ns.rdfs.label, { language: 'SR-cyrl' }) 173 | 174 | assert(label.term.equals(rdf.literal('Јабука', 'sr-Cyrl'))) 175 | }) 176 | 177 | it('should match secondary language tag by primary', async () => { 178 | const apple = (await parse(testData)).node(ns.ex.apple) 179 | 180 | const label = apple.out(ns.rdfs.label, { language: 'sr' }) 181 | 182 | assert(label.term.equals(rdf.literal('Јабука', 'sr-Cyrl'))) 183 | }) 184 | 185 | it('should match secondary language tag by primary regardless of case', async () => { 186 | const apple = (await parse(testData)).node(ns.ex.apple) 187 | 188 | const label = apple.out(ns.rdfs.label, { language: 'SR' }) 189 | 190 | assert(label.term.equals(rdf.literal('Јабука', 'sr-Cyrl'))) 191 | }) 192 | 193 | it('should match tertiary tag by secondary language', async () => { 194 | const apple = (await parse(testData)).node(ns.ex.kongressstrasse) 195 | 196 | const label = apple.out(ns.rdfs.label, { language: 'de-DE' }) 197 | 198 | assert(label.term.equals(rdf.literal('Kongreßstraße', 'de-DE-1901'))) 199 | }) 200 | }) 201 | 202 | describe('filtered by multiple languages', () => { 203 | it('should choose first match', async () => { 204 | const apple = (await parse(testData)).node(ns.ex.apple) 205 | 206 | const label = apple.out(ns.rdfs.label, { language: ['fr', 'no', 'be', 'en', 'de'] }) 207 | 208 | assert(label.term.equals(rdf.literal('Apple', 'en'))) 209 | }) 210 | 211 | it('should choose exact match over secondary language', async () => { 212 | const apple = (await parse(testData)).node(ns.ex.carrot) 213 | 214 | const label = apple.out(ns.rdfs.label, { language: ['de-1901', 'de'] }) 215 | 216 | assert(label.term.equals(rdf.literal('Karotte', 'de'))) 217 | }) 218 | }) 219 | }) 220 | }) 221 | -------------------------------------------------------------------------------- /test/Clownface/term.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import rdf from '../support/factory.js' 6 | 7 | describe('.term', () => { 8 | it('should be undefined if there is no context with a term', () => { 9 | const cf = clownface({ dataset: rdf.dataset() }) 10 | 11 | assert.strictEqual(typeof cf.term, 'undefined') 12 | }) 13 | 14 | it('should be the term of the context if there is only one term', () => { 15 | const term = rdf.literal('1') 16 | const cf = clownface({ dataset: rdf.dataset(), term }) 17 | 18 | assert(term.equals(cf.term)) 19 | }) 20 | 21 | it('should be undefined if there are multiple terms in the context', () => { 22 | const termA = rdf.literal('1') 23 | const termB = rdf.namedNode('http://example.org/') 24 | 25 | const cf = clownface({ dataset: rdf.dataset(), term: [termA, termB] }) 26 | 27 | assert.strictEqual(typeof cf.term, 'undefined') 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/Clownface/terms.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import rdf from '../support/factory.js' 6 | 7 | describe('.terms', () => { 8 | it('should be an array property', () => { 9 | const cf = clownface({ dataset: rdf.dataset() }) 10 | 11 | assert(Array.isArray(cf.terms)) 12 | }) 13 | 14 | it('should be empty if there is no context with a term', () => { 15 | const cf = clownface({ dataset: rdf.dataset() }) 16 | 17 | const result = cf.terms 18 | 19 | assert.deepStrictEqual(result, []) 20 | }) 21 | 22 | it('should contain all terms of the context', () => { 23 | const termA = rdf.literal('1') 24 | const termB = rdf.namedNode('http://example.org/') 25 | const cf = clownface({ dataset: rdf.dataset(), term: [termA, termB] }) 26 | 27 | const result = cf.terms 28 | 29 | assert.strictEqual(result.length, 2) 30 | assert(termA.equals(result[0])) 31 | assert(termB.equals(result[1])) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /test/Clownface/toArray.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import loadExample from '../support/example.js' 6 | import rdf from '../support/factory.js' 7 | import Clownface from '../../lib/Clownface.js' 8 | 9 | describe('.toArray', () => { 10 | it('should be a function', () => { 11 | const cf = clownface({ dataset: rdf.dataset() }) 12 | 13 | assert.strictEqual(typeof cf.toArray, 'function') 14 | }) 15 | 16 | it('should return an array', () => { 17 | const cf = clownface({ dataset: rdf.dataset() }) 18 | 19 | assert(Array.isArray(cf.toArray())) 20 | }) 21 | 22 | it('should return a Dataset instance for every context object', async () => { 23 | const cf = clownface({ 24 | dataset: await loadExample(), 25 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 26 | }) 27 | 28 | const result = cf.in(rdf.namedNode('http://schema.org/knows')).toArray() 29 | 30 | assert.strictEqual(result.length, 7) 31 | assert(result[0] instanceof Clownface) 32 | }) 33 | 34 | it('should not return an instance for undefined context', () => { 35 | const cf = clownface({ dataset: rdf.dataset() }) 36 | 37 | const array = cf.toArray() 38 | 39 | assert.strictEqual(array.length, 0) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /test/Clownface/toString.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import loadExample from '../support/example.js' 6 | import rdf from '../support/factory.js' 7 | 8 | describe('.toString', () => { 9 | it('should be a function', () => { 10 | const cf = clownface({ dataset: rdf.dataset() }) 11 | 12 | assert.strictEqual(typeof cf.toString, 'function') 13 | }) 14 | 15 | it('should return a string', () => { 16 | const cf = clownface({ dataset: rdf.dataset() }) 17 | 18 | assert.strictEqual(typeof cf.toString(), 'string') 19 | }) 20 | 21 | it('should return the value of a single term', async () => { 22 | const cf = clownface({ 23 | dataset: await loadExample(), 24 | term: rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 25 | }) 26 | 27 | const result = cf.out(rdf.namedNode('http://schema.org/givenName')).toString() 28 | 29 | assert.strictEqual(result, 'Bernadette') 30 | }) 31 | 32 | it('should return comma separated values if multiple terms', async () => { 33 | const cf = clownface({ 34 | dataset: await loadExample(), 35 | term: [ 36 | rdf.namedNode('http://localhost:8080/data/person/bernadette-rostenkowski'), 37 | rdf.namedNode('http://localhost:8080/data/person/howard-wolowitz'), 38 | ], 39 | }) 40 | 41 | const givenName = cf.out(rdf.namedNode('http://schema.org/givenName')).toString() 42 | 43 | assert.strictEqual(givenName, 'Bernadette,Howard') 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /test/Clownface/value.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import rdf from '../support/factory.js' 6 | 7 | describe('.value', () => { 8 | it('should be undefined if there is no context with a term', () => { 9 | const cf = clownface({ dataset: rdf.dataset() }) 10 | 11 | assert.strictEqual(typeof cf.value, 'undefined') 12 | }) 13 | 14 | it('should be the value of the context if there is only one term', () => { 15 | const term = rdf.literal('1') 16 | const cf = clownface({ dataset: rdf.dataset(), value: term.value }) 17 | 18 | assert.strictEqual(cf.value, term.value) 19 | }) 20 | 21 | it('should be undefined if there are multiple terms in the context', () => { 22 | const termA = rdf.literal('1') 23 | const termB = rdf.namedNode('http://example.org/') 24 | const cf = clownface({ dataset: rdf.dataset(), term: [termA, termB] }) 25 | 26 | assert.strictEqual(typeof cf.value, 'undefined') 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/Clownface/values.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../../index.js' 5 | import rdf from '../support/factory.js' 6 | 7 | describe('.values', () => { 8 | it('should be an array property', () => { 9 | const cf = clownface({ dataset: rdf.dataset() }) 10 | 11 | assert(Array.isArray(cf.values)) 12 | }) 13 | 14 | it('should be empty if there is no context with a term', () => { 15 | const cf = clownface({ dataset: rdf.dataset() }) 16 | 17 | assert.deepStrictEqual(cf.values, []) 18 | }) 19 | 20 | it('should contain the values of the terms', () => { 21 | const termA = rdf.literal('1') 22 | const termB = rdf.namedNode('http://example.org/') 23 | const cf = clownface({ dataset: rdf.dataset(), value: [termA, termB] }) 24 | 25 | const result = cf.values 26 | 27 | assert.strictEqual(result.length, 2) 28 | assert.strictEqual(result[0], termA.value) 29 | assert.strictEqual(result[1], termB.value) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /test/Context.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { describe, it } from 'mocha' 3 | import Context from '../lib/Context.js' 4 | import { schema } from './support/namespace.js' 5 | import rdf from './support/factory.js' 6 | 7 | describe('Context', () => { 8 | describe('out', () => { 9 | it('can be called without language', () => { 10 | const context = new Context({ dataset: rdf.dataset() }) 11 | 12 | assert.doesNotThrow(() => { 13 | context.out([schema.knows]) 14 | }) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /test/Factory.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import Environment from '@rdfjs/environment' 3 | import DatasetFactory from '@rdfjs/dataset/Factory.js' 4 | import $rdf from 'rdf-ext' 5 | import NamespaceFactory from '@rdfjs/namespace/Factory.js' 6 | import DataFactory from '@rdfjs/data-model/Factory.js' 7 | import ClownfaceFactory from '../Factory.js' 8 | 9 | describe('Factory', () => { 10 | const env = new Environment([ 11 | ClownfaceFactory, 12 | NamespaceFactory, 13 | DatasetFactory, 14 | DataFactory, 15 | ]) 16 | 17 | it('creates dataset using the available factory', () => { 18 | // when 19 | const ptr = env.clownface() 20 | 21 | // then 22 | expect(ptr.dataset).to.be.ok 23 | }) 24 | 25 | it('uses provided dataset', () => { 26 | // when 27 | const dataset = $rdf.dataset() 28 | const ptr = env.clownface({ dataset }) 29 | 30 | // then 31 | expect(ptr.dataset).to.eq(dataset) 32 | }) 33 | 34 | it('forwards arguments', () => { 35 | // when 36 | const term = $rdf.namedNode('http://example.com') 37 | const ptr = env.clownface({ term }) 38 | 39 | // then 40 | expect(ptr.term).to.deep.eq(term) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/filter.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import clownface from '../index.js' 5 | import { taggedLiteral } from '../filter.js' 6 | import rdf from './support/factory.js' 7 | 8 | describe('clownface/filter', () => { 9 | describe('.taggedLiteral', () => { 10 | it('should return literals for language', async () => { 11 | const schemaName = rdf.namedNode('http://schema.org/givenName') 12 | const cf = clownface({ dataset: rdf.dataset() }) 13 | .blankNode() 14 | .addOut(schemaName, [ 15 | rdf.literal('Zurich', 'en'), 16 | rdf.literal('Zürich', 'de'), 17 | rdf.literal('Zurych', 'pl')]) 18 | .out(schemaName) 19 | 20 | const result = cf.filter(taggedLiteral(['de'])) 21 | 22 | assert(result.term.equals(rdf.literal('Zürich', 'de'))) 23 | }) 24 | 25 | it('should work with multi-pointer', async () => { 26 | const containsPlace = rdf.namedNode('http://schema.org/containsPlace') 27 | const name = rdf.namedNode('http://schema.org/name') 28 | const cf = clownface({ dataset: rdf.dataset() }) 29 | cf.namedNode('Europe').addOut(containsPlace, [ 30 | cf.namedNode('CH') 31 | .addOut(name, cf.literal('Switzerland', 'en')) 32 | .addOut(name, cf.literal('Die Schweiz', 'de')), 33 | cf.namedNode('PL') 34 | .addOut(name, cf.literal('Poland', 'en')) 35 | .addOut(name, cf.literal('Polen', 'de')), 36 | ]) 37 | 38 | const multi = cf.node([rdf.namedNode('CH'), rdf.namedNode('PL')]).out(name) 39 | const result = multi.filter(taggedLiteral(['de'])) 40 | 41 | const terms = rdf.termSet(result.terms) 42 | assert.strictEqual(result.terms.length, 2) 43 | assert(terms.has(rdf.literal('Die Schweiz', 'de'))) 44 | assert(terms.has(rdf.literal('Polen', 'de'))) 45 | }) 46 | 47 | it('should ignore non-literals', async () => { 48 | const schemaKnows = rdf.namedNode('http://schema.org/knows') 49 | const cf = clownface({ dataset: rdf.dataset() }) 50 | .blankNode() 51 | .addOut(schemaKnows, [rdf.blankNode(), rdf.blankNode()]) 52 | 53 | const result = cf.out(schemaKnows).filter(taggedLiteral(['de'])) 54 | 55 | assert.strictEqual(result.terms.length, 0) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /test/fromPrimitive.test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import assert from 'assert' 4 | import namespace from '@rdfjs/namespace' 5 | import { booleanToLiteral, numberToLiteral, stringToLiteral, toLiteral } from '../lib/fromPrimitive.js' 6 | import rdf from './support/factory.js' 7 | 8 | const xsd = namespace('http://www.w3.org/2001/XMLSchema#') 9 | 10 | describe('fromPrimitive', () => { 11 | describe('booleanToLiteral', () => { 12 | it('is function', () => { 13 | assert.strictEqual(typeof booleanToLiteral, 'function') 14 | }) 15 | 16 | it('returns null if non boolean value is given', () => { 17 | assert.strictEqual(booleanToLiteral(3), null) 18 | }) 19 | 20 | it('returns Literal with xsd:boolean datatype for true value', () => { 21 | assert(rdf.literal('true', xsd.boolean), booleanToLiteral(true)) 22 | }) 23 | 24 | it('returns Literal with xsd:boolean datatype for false value', () => { 25 | assert(rdf.literal('false', xsd.boolean), booleanToLiteral(false)) 26 | }) 27 | }) 28 | 29 | describe('numberToLiteral', () => { 30 | it('is function', () => { 31 | assert.strictEqual(typeof numberToLiteral, 'function') 32 | }) 33 | 34 | it('returns null if non number value is given', () => { 35 | assert.strictEqual(numberToLiteral(true), null) 36 | }) 37 | 38 | it('returns Literal with xsd:double datatype for number value', () => { 39 | assert(rdf.literal('3.21', xsd.double).equals(numberToLiteral(3.21))) 40 | }) 41 | 42 | it('returns Literal with xsd:integer datatype for a number value that is an integer', () => { 43 | assert(rdf.literal('321', xsd.integer).equals(numberToLiteral(321))) 44 | }) 45 | }) 46 | 47 | describe('stringToLiteral', () => { 48 | it('is function', () => { 49 | assert.strictEqual(typeof stringToLiteral, 'function') 50 | }) 51 | 52 | it('returns null if non string value is given', () => { 53 | assert.strictEqual(stringToLiteral(true), null) 54 | }) 55 | 56 | it('returns Literal', () => { 57 | assert(rdf.literal('M42').equals(stringToLiteral('M42'))) 58 | }) 59 | }) 60 | 61 | describe('toLiteral', () => { 62 | it('is function', () => { 63 | assert.strictEqual(typeof toLiteral, 'function') 64 | }) 65 | 66 | it('returns null if a value with an unknown type is given', () => { 67 | assert.strictEqual(toLiteral({}), null) 68 | }) 69 | 70 | it('returns Literal with xsd:boolean datatype for a boolean value', () => { 71 | assert(rdf.literal('true', xsd.boolean), toLiteral(true)) 72 | }) 73 | 74 | it('returns Literal with xsd:double datatype for a number value', () => { 75 | assert(rdf.literal('3.21', xsd.double).equals(toLiteral(3.21))) 76 | }) 77 | 78 | it('returns Literal', () => { 79 | assert(rdf.literal('M42').equals(toLiteral('M42'))) 80 | }) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /test/support/CustomDataFactory.js: -------------------------------------------------------------------------------- 1 | import rdf from 'rdf-ext' 2 | 3 | export default class { 4 | quad(s, p, o, g) { 5 | const quad = rdf.quad(s, p, o, g) 6 | quad.testProperty = 'test' 7 | return quad 8 | } 9 | 10 | literal(value, languageOrDatatype) { 11 | const node = rdf.literal(value, languageOrDatatype) 12 | node.testProperty = 'test' 13 | return node 14 | } 15 | 16 | blankNode(value) { 17 | const node = rdf.blankNode(value) 18 | node.testProperty = 'test' 19 | return node 20 | } 21 | 22 | namedNode(value) { 23 | const node = rdf.namedNode(value) 24 | node.testProperty = 'test' 25 | return node 26 | } 27 | 28 | static get exports() { 29 | return ['quad', 'literal', 'namedNode', 'blankNode'] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/support/example.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import module from 'module' 4 | import N3Parser from '@rdfjs/parser-n3' 5 | import fromStream from 'rdf-dataset-ext/fromStream.js' 6 | import rdf from './factory.js' 7 | 8 | const require = module.createRequire(import.meta.url) 9 | 10 | export default function init() { 11 | const filename = path.join(path.dirname(require.resolve('tbbt-ld')), 'dist/tbbt.nq') 12 | const input = fs.createReadStream(filename) 13 | const parser = new N3Parser() 14 | 15 | return fromStream(rdf.dataset(), parser.import(input)) 16 | } 17 | -------------------------------------------------------------------------------- /test/support/factory.js: -------------------------------------------------------------------------------- 1 | // RDF-Ext 2 | import factory from 'rdf-ext' 3 | 4 | /* 5 | // Reference implementations of Data Model and Dataset 6 | import dataset from '@rdfjs/dataset' 7 | import model from '@rdfjs/data-model' 8 | 9 | const factory = { ...model, ...dataset } 10 | */ 11 | 12 | export default factory 13 | -------------------------------------------------------------------------------- /test/support/namespace.js: -------------------------------------------------------------------------------- 1 | import rdf from 'rdf-ext' 2 | 3 | export const first = rdf.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#first') 4 | export const list = rdf.namedNode('http://example.org/list') 5 | export const nil = rdf.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#nil') 6 | export const rest = rdf.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#rest') 7 | export const ex = rdf.namespace('http://example.org/') 8 | export const rdfs = rdf.namespace('http://www.w3.org/2000/01/rdf-schema#') 9 | export const schema = rdf.namespace('http://schema.org/') 10 | export const xsd = rdf.namespace('http://www.w3.org/2001/XMLSchema#') 11 | export const tbbtp = rdf.namespace('http://localhost:8080/data/person/') 12 | -------------------------------------------------------------------------------- /test/support/parse.js: -------------------------------------------------------------------------------- 1 | import $rdf from 'rdf-ext' 2 | import toStream from 'string-to-stream' 3 | import Parser from '@rdfjs/parser-n3' 4 | import cf from '../..//index.js' 5 | 6 | const parser = new Parser() 7 | 8 | export default async function parse(string) { 9 | const dataset = await $rdf.dataset().import(parser.import(toStream(string))) 10 | 11 | return cf({ dataset }) 12 | } 13 | -------------------------------------------------------------------------------- /test/term.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { describe, it } from 'mocha' 3 | import term from '../lib/term.js' 4 | import rdf from './support/factory.js' 5 | 6 | describe('term', () => { 7 | it('should be a function', () => { 8 | assert.strictEqual(typeof term, 'function') 9 | }) 10 | 11 | it('should return undefined if no argument is given', () => { 12 | const result = term() 13 | 14 | assert.strictEqual(typeof result, 'undefined') 15 | }) 16 | 17 | it('should return undefined if null is given', () => { 18 | const result = term(null) 19 | 20 | assert.strictEqual(typeof result, 'undefined') 21 | }) 22 | 23 | it('should create a NamedNode if only a URL object is given', () => { 24 | const url = new URL('http://localhost:8080/test') 25 | const result = term(url, undefined, undefined, rdf) 26 | 27 | assert.strictEqual(result.termType, 'NamedNode') 28 | assert.strictEqual(result.value, url.toString()) 29 | }) 30 | 31 | it('should create a Literal if only a string is given', () => { 32 | const result = term('test', undefined, undefined, rdf) 33 | 34 | assert.strictEqual(result.termType, 'Literal') 35 | assert.strictEqual(result.value, 'test') 36 | }) 37 | 38 | it('should create a BlankNode if the type is BlankNode', () => { 39 | const result = term(null, 'BlankNode', undefined, rdf) 40 | 41 | assert.strictEqual(result.termType, 'BlankNode') 42 | }) 43 | 44 | it('should use the value as blank node identifier', () => { 45 | const result = term('test', 'BlankNode', undefined, rdf) 46 | 47 | assert.strictEqual(result.termType, 'BlankNode') 48 | assert.strictEqual(result.value, 'test') 49 | }) 50 | 51 | it('should create a NamedNode if the type is NamedNode', () => { 52 | const result = term('http://example.org/', 'NamedNode', undefined, rdf) 53 | 54 | assert.strictEqual(result.termType, 'NamedNode') 55 | assert.strictEqual(result.value, 'http://example.org/') 56 | }) 57 | }) 58 | --------------------------------------------------------------------------------