@muze-nl/oldm-core 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/package.json +30 -0
- package/src/oldm.mjs +961 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Muze.nl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# @muze-nl/oldm-core
|
|
2
|
+
|
|
3
|
+
Core Object Linked Data Mapper package.
|
|
4
|
+
|
|
5
|
+
This package contains the object/graph mapping layer only. It has explicit ESM exports, no bundled parser/writer, no N3 dependency, and no global side effects.
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import oldm, { one, many, first, Collection } from '@muze-nl/oldm-core'
|
|
9
|
+
import { n3Parser, n3Writer } from '@muze-nl/oldm-n3'
|
|
10
|
+
|
|
11
|
+
const context = oldm({
|
|
12
|
+
parser: n3Parser,
|
|
13
|
+
writer: n3Writer
|
|
14
|
+
})
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Prefix preference
|
|
19
|
+
|
|
20
|
+
OLDM shortens predicate and type IRIs with the prefixes configured on the context. Prefix declarations found in Turtle input are parser conveniences; they do not decide the JavaScript property names exposed by OLDM.
|
|
21
|
+
|
|
22
|
+
When multiple prefixes point at the same namespace, client-provided prefixes are preferred over OLDM defaults, and defaults are preferred over prefixes found in a parsed source document. For example, both `pim:` and `space:` are common aliases for `http://www.w3.org/ns/pim/space#`. Since OLDM prefers `space` for that namespace, profile data using either `pim:storage` or `space:storage` is exposed as `space$storage` in JavaScript.
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const context = oldm({
|
|
26
|
+
parser: n3Parser,
|
|
27
|
+
prefixes: {
|
|
28
|
+
space: 'http://www.w3.org/ns/pim/space#'
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const profile = context.parse(turtle, profileUrl, 'text/turtle')
|
|
33
|
+
const me = profile.subjects[`${profileUrl}#me`]
|
|
34
|
+
|
|
35
|
+
console.log(me.space$storage.id)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Multiple graphs in one context
|
|
40
|
+
|
|
41
|
+
`Context` keeps a registry of parsed graphs and exposes a combined view over all graphs loaded into the same context.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
const profile = context.parse(profileTurtle, profileUrl, 'text/turtle')
|
|
45
|
+
const settings = context.parse(settingsTurtle, settingsUrl, 'text/turtle')
|
|
46
|
+
|
|
47
|
+
profile.get(`${profileUrl}#me`) // graph-specific view
|
|
48
|
+
context.get(`${profileUrl}#me`) // combined context view
|
|
49
|
+
context.graphs // parsed graphs in load order
|
|
50
|
+
context.graph(profileUrl) // graph by source URL
|
|
51
|
+
context.data // combined subjects
|
|
52
|
+
context.subjects // combined subject map
|
|
53
|
+
context.sources(context.get(`${profileUrl}#me`))
|
|
54
|
+
// graphs containing that subject
|
|
55
|
+
context.sources(context.get(`${profileUrl}#me`), 'vcard$fn')
|
|
56
|
+
// graphs containing that property
|
|
57
|
+
context.sources(context.get(`${profileUrl}#me`), 'vcard$fn', 'Auke')
|
|
58
|
+
// graphs containing that specific value
|
|
59
|
+
profile.context.data // same combined view, starting from a graph
|
|
60
|
+
profile.context.subjects // same combined subject map, starting from a graph
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The combined context view merges named subjects by IRI. Graph-specific views remain unchanged, so code can still separate data by original resource. Blank nodes remain graph-scoped.
|
|
64
|
+
|
|
65
|
+
If a loader or middleware gives you a `Graph`, use `graph.context` to access the combined view for all graphs loaded into the same context:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
const graph = context.parse(profileTurtle, profileUrl, 'text/turtle')
|
|
69
|
+
|
|
70
|
+
graph.data // subjects from this one resource
|
|
71
|
+
graph.context.data // combined subjects from the whole context
|
|
72
|
+
graph.context.get(id) // merged subject from the whole context
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For source-aware writes, use the graph-specific helpers when you know the resource you want to edit:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
profile.set(`${profileUrl}#me`, 'vcard$fn', 'Auke')
|
|
79
|
+
profile.add(`${profileUrl}#me`, 'schema$knowsAbout', 'Linked Data')
|
|
80
|
+
profile.delete(`${profileUrl}#me`, 'schema$knowsAbout', 'Old value')
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Context-level helpers can choose a graph explicitly:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
context.set(`${profileUrl}#me`, 'vcard$fn', 'Auke', { graph: profile })
|
|
87
|
+
context.add(`${profileUrl}#me`, 'schema$knowsAbout', 'Solid', { graph: profileUrl })
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
When no graph is passed, `context.set/add/delete()` uses a conservative default: the subject's exact graph URL, the subject document URL without a fragment, the only graph that currently contains the subject, the configured `defaultGraph`, or the only graph in the context. Direct property assignment on a named subject from `context.get(...)` uses the same resolver, so simple edits can stay object-like:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
const me = context.get(`${profileUrl}#me`)
|
|
94
|
+
me.vcard$fn = 'Auke'
|
|
95
|
+
delete me.vcard$nickname
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If there is no obvious source graph, OLDM throws and asks you to choose one explicitly with `context.set/add/delete(..., { graph })` or `graph.set/add/delete(...)`.
|
|
99
|
+
|
|
100
|
+
## Public exports
|
|
101
|
+
|
|
102
|
+
- default `oldm(options)` context factory
|
|
103
|
+
- `Context`
|
|
104
|
+
- `Graph`
|
|
105
|
+
- `NamedNode`
|
|
106
|
+
- `BlankNode`
|
|
107
|
+
- `Collection`
|
|
108
|
+
- `one(values, whichOne)`
|
|
109
|
+
- `many(values)`
|
|
110
|
+
- `first(...values)`
|
|
111
|
+
- `prefixes`
|
|
112
|
+
- `rdfType`
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT.
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@muze-nl/oldm-core",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Core Object - Linked Data Mapper, without parser or writer dependencies",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/oldm.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/oldm.mjs"
|
|
9
|
+
},
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "tap --disable-coverage test/*.mjs",
|
|
13
|
+
"coverage": "tap --allow-incomplete-coverage --show-full-coverage test/*.mjs"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/muze-nl/oldm.git",
|
|
18
|
+
"directory": "packages/oldm-core"
|
|
19
|
+
},
|
|
20
|
+
"author": "auke@muze.nl",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"files": [
|
|
23
|
+
"src/",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/oldm.mjs
ADDED
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
export default function oldm(options)
|
|
2
|
+
{
|
|
3
|
+
return new Context(options)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const rdfType = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'
|
|
7
|
+
|
|
8
|
+
export const prefixes = {
|
|
9
|
+
acl: 'http://www.w3.org/ns/auth/acl#',
|
|
10
|
+
acp: 'http://www.w3.org/ns/solid/acp#',
|
|
11
|
+
dcterms:'http://purl.org/dc/terms/',
|
|
12
|
+
foaf: 'http://xmlns.com/foaf/0.1/',
|
|
13
|
+
ldn: 'https://www.w3.org/ns/ldn#',
|
|
14
|
+
ldp: 'http://www.w3.org/ns/ldp#',
|
|
15
|
+
notify: 'http://www.w3.org/ns/solid/notifications#',
|
|
16
|
+
oidc: 'http://www.w3.org/ns/solid/oidc#',
|
|
17
|
+
owl: 'http://www.w3.org/2002/07/owl#',
|
|
18
|
+
pim: 'http://www.w3.org/ns/pim/space#',
|
|
19
|
+
rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
|
|
20
|
+
rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
|
|
21
|
+
schema: 'http://schema.org/',
|
|
22
|
+
solid: 'http://www.w3.org/ns/solid/terms#',
|
|
23
|
+
stat: 'http://www.w3.org/ns/posix/stat#',
|
|
24
|
+
turtle: 'http://www.w3.org/ns/iana/media-types/text/turtle#',
|
|
25
|
+
vcard: 'http://www.w3.org/2006/vcard/ns#',
|
|
26
|
+
xsd: 'http://www.w3.org/2001/XMLSchema#'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function one(values, whichOne='last')
|
|
30
|
+
{
|
|
31
|
+
let result = values
|
|
32
|
+
if (Array.isArray(values)) {
|
|
33
|
+
if (whichOne=='last') {
|
|
34
|
+
result = values[values.length-1]
|
|
35
|
+
} else if (whichOne=='first') {
|
|
36
|
+
result = values[0]
|
|
37
|
+
} else if (typeof whichOne=='function') {
|
|
38
|
+
result = whichOne(values)
|
|
39
|
+
} else {
|
|
40
|
+
throw new Error('Unknown value for whichOne parameter')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function many(values)
|
|
47
|
+
{
|
|
48
|
+
if (Array.isArray(values)) {
|
|
49
|
+
return values
|
|
50
|
+
}
|
|
51
|
+
if (values == null) {
|
|
52
|
+
return []
|
|
53
|
+
}
|
|
54
|
+
return [values]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function first(...values)
|
|
58
|
+
{
|
|
59
|
+
for (const value of values) {
|
|
60
|
+
if (value!==null && value!==undefined) {
|
|
61
|
+
return value
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function values(value)
|
|
68
|
+
{
|
|
69
|
+
if (Array.isArray(value) && !(value instanceof Collection)) {
|
|
70
|
+
return value
|
|
71
|
+
}
|
|
72
|
+
if (value === undefined) {
|
|
73
|
+
return []
|
|
74
|
+
}
|
|
75
|
+
return [value]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function mergeValue(existing, value)
|
|
79
|
+
{
|
|
80
|
+
const result = values(existing)
|
|
81
|
+
for (const item of values(value)) {
|
|
82
|
+
if (!result.some(existingItem => sameValue(existingItem, item))) {
|
|
83
|
+
result.push(item)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (result.length == 0) {
|
|
87
|
+
return undefined
|
|
88
|
+
}
|
|
89
|
+
if (result.length == 1) {
|
|
90
|
+
return result[0]
|
|
91
|
+
}
|
|
92
|
+
return result
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sameValue(left, right)
|
|
96
|
+
{
|
|
97
|
+
if (left === right) {
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
if (left instanceof NamedNode && right instanceof NamedNode) {
|
|
101
|
+
return left.id == right.id
|
|
102
|
+
}
|
|
103
|
+
if (left instanceof NamedNode && typeof right == 'string') {
|
|
104
|
+
return left.id == right
|
|
105
|
+
}
|
|
106
|
+
if (typeof left == 'string' && right instanceof NamedNode) {
|
|
107
|
+
return left == right.id
|
|
108
|
+
}
|
|
109
|
+
if (left instanceof Collection && right instanceof Collection) {
|
|
110
|
+
return left.length == right.length
|
|
111
|
+
&& left.every((item, index) => sameValue(item, right[index]))
|
|
112
|
+
}
|
|
113
|
+
if (isLiteral(left) && isLiteral(right)) {
|
|
114
|
+
return String(left) == String(right)
|
|
115
|
+
&& left?.type == right?.type
|
|
116
|
+
&& left?.language == right?.language
|
|
117
|
+
}
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
function sameSourceValue(left, right)
|
|
123
|
+
{
|
|
124
|
+
if (left === right) {
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
if (left instanceof NamedNode && right instanceof NamedNode) {
|
|
128
|
+
return left.id == right.id
|
|
129
|
+
}
|
|
130
|
+
if (left instanceof NamedNode && typeof right == 'string') {
|
|
131
|
+
return left.id == right
|
|
132
|
+
}
|
|
133
|
+
if (typeof left == 'string' && right instanceof NamedNode) {
|
|
134
|
+
return left == right.id
|
|
135
|
+
}
|
|
136
|
+
if (left instanceof Collection && right instanceof Collection) {
|
|
137
|
+
return left.length == right.length
|
|
138
|
+
&& left.every((item, index) => sameSourceValue(item, right[index]))
|
|
139
|
+
}
|
|
140
|
+
if (isLiteral(left) && isLiteral(right)) {
|
|
141
|
+
const leftType = left?.type
|
|
142
|
+
const rightType = right?.type
|
|
143
|
+
const leftLanguage = left?.language
|
|
144
|
+
const rightLanguage = right?.language
|
|
145
|
+
return String(left) == String(right)
|
|
146
|
+
&& (!leftType || !rightType || leftType == rightType)
|
|
147
|
+
&& (!leftLanguage || !rightLanguage || leftLanguage == rightLanguage)
|
|
148
|
+
}
|
|
149
|
+
return false
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function resolveValue(value, subjects, context)
|
|
153
|
+
{
|
|
154
|
+
if (value instanceof Collection) {
|
|
155
|
+
const collection = new Collection(context)
|
|
156
|
+
for (const item of value) {
|
|
157
|
+
collection.push(resolveValue(item, subjects, context))
|
|
158
|
+
}
|
|
159
|
+
return collection
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(value)) {
|
|
162
|
+
return value.map(item => resolveValue(item, subjects, context))
|
|
163
|
+
}
|
|
164
|
+
if (value instanceof NamedNode && subjects[value.id]) {
|
|
165
|
+
return subjects[value.id]
|
|
166
|
+
}
|
|
167
|
+
return value
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isLiteral(value)
|
|
171
|
+
{
|
|
172
|
+
return (
|
|
173
|
+
value instanceof String
|
|
174
|
+
|| value instanceof Number
|
|
175
|
+
|| typeof value == 'boolean'
|
|
176
|
+
|| typeof value == 'string'
|
|
177
|
+
|| typeof value == 'number'
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export class Context {
|
|
182
|
+
#buildingSubjects = false
|
|
183
|
+
|
|
184
|
+
constructor(options)
|
|
185
|
+
{
|
|
186
|
+
const clientPrefixes = options?.prefixes ?? {}
|
|
187
|
+
this.prefixes = {...prefixes, ...clientPrefixes}
|
|
188
|
+
this.prefixOrder = [
|
|
189
|
+
...Object.keys(clientPrefixes),
|
|
190
|
+
...Object.keys(prefixes).filter(prefix => !(prefix in clientPrefixes))
|
|
191
|
+
]
|
|
192
|
+
if (!this.prefixes['xsd']) {
|
|
193
|
+
this.prefixes['xsd'] = 'http://www.w3.org/2001/XMLSchema#'
|
|
194
|
+
this.prefixOrder.push('xsd')
|
|
195
|
+
}
|
|
196
|
+
this.parser = options?.parser
|
|
197
|
+
this.writer = options?.writer
|
|
198
|
+
this.graphs = []
|
|
199
|
+
this.graphsByUrl = Object.create(null)
|
|
200
|
+
this.defaultGraph = options?.defaultGraph ?? null
|
|
201
|
+
this.separator = options?.separator ?? '$'
|
|
202
|
+
|
|
203
|
+
Object.defineProperty(this, 'subjects', {
|
|
204
|
+
get() {
|
|
205
|
+
return this.getSubjects()
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
Object.defineProperty(this, 'data', {
|
|
210
|
+
get() {
|
|
211
|
+
return Object.values(this.subjects)
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
parse(input, url, type)
|
|
217
|
+
{
|
|
218
|
+
const {quads, prefixes} = this.parser(input, url, type)
|
|
219
|
+
if (prefixes) {
|
|
220
|
+
for (let prefix in prefixes) {
|
|
221
|
+
let prefixURL = prefixes[prefix]
|
|
222
|
+
if (prefixURL.match(/^http(s?):\/\/$/i)) {
|
|
223
|
+
prefixURL += url.substring(prefixURL.length)
|
|
224
|
+
} else try {
|
|
225
|
+
prefixURL = new URL(prefixes[prefix], url).href
|
|
226
|
+
} catch(err) {
|
|
227
|
+
console.error('Could not parse prefix', prefixes[prefix], err.message)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!this.prefixes[prefix]) {
|
|
231
|
+
this.prefixes[prefix] = prefixURL
|
|
232
|
+
this.prefixOrder.push(prefix)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return this.addGraph(new Graph(quads, url, type, prefixes, this))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
addGraph(graph)
|
|
240
|
+
{
|
|
241
|
+
if (!graph?.url) {
|
|
242
|
+
throw new Error('Cannot add graph without a url')
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const existing = this.graphsByUrl[graph.url]
|
|
246
|
+
if (existing) {
|
|
247
|
+
const index = this.graphs.indexOf(existing)
|
|
248
|
+
if (index >= 0) {
|
|
249
|
+
this.graphs[index] = graph
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
this.graphs.push(graph)
|
|
253
|
+
}
|
|
254
|
+
this.graphsByUrl[graph.url] = graph
|
|
255
|
+
return graph
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
graph(url)
|
|
259
|
+
{
|
|
260
|
+
return this.graphsByUrl[this.fullURI(url)]
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
set(subject, predicate, value, options={})
|
|
264
|
+
{
|
|
265
|
+
return this.resolveGraph(subject, options).set(subject, predicate, value)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
add(subject, predicate, value, options={})
|
|
269
|
+
{
|
|
270
|
+
return this.resolveGraph(subject, options).add(subject, predicate, value)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
delete(subject, predicate=null, value=undefined, options={})
|
|
274
|
+
{
|
|
275
|
+
const graph = this.resolveGraph(subject, options)
|
|
276
|
+
if (arguments.length < 3) {
|
|
277
|
+
return graph.delete(subject, predicate)
|
|
278
|
+
}
|
|
279
|
+
return graph.delete(subject, predicate, value)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
resolveGraph(subject, options={})
|
|
283
|
+
{
|
|
284
|
+
if (options.graph) {
|
|
285
|
+
return this.getGraphOption(options.graph)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (subject instanceof BlankNode && subject.graph instanceof Graph) {
|
|
289
|
+
return subject.graph
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const id = this.subjectID(subject)
|
|
293
|
+
if (id) {
|
|
294
|
+
const exactGraph = this.graphsByUrl[id]
|
|
295
|
+
if (exactGraph) {
|
|
296
|
+
return exactGraph
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const documentGraph = this.graphsByUrl[this.documentURL(id)]
|
|
300
|
+
if (documentGraph) {
|
|
301
|
+
return documentGraph
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const subjectSources = this.graphs.filter(graph => graph.subjects[id])
|
|
305
|
+
if (subjectSources.length == 1) {
|
|
306
|
+
return subjectSources[0]
|
|
307
|
+
}
|
|
308
|
+
if (subjectSources.length > 1) {
|
|
309
|
+
throw new Error(`Cannot choose a source graph for ${id}. Use context.set/add/delete(..., { graph }) or graph.set/add/delete(...) to choose one explicitly.`)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (this.defaultGraph) {
|
|
314
|
+
return this.getGraphOption(this.defaultGraph)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (this.graphs.length == 1) {
|
|
318
|
+
return this.graphs[0]
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
throw new Error('Cannot choose a source graph. Use context.set/add/delete(..., { graph }) or graph.set/add/delete(...) to choose one explicitly.')
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
getGraphOption(graph)
|
|
325
|
+
{
|
|
326
|
+
if (graph instanceof Graph) {
|
|
327
|
+
if (!this.graphs.includes(graph)) {
|
|
328
|
+
throw new Error('The selected graph is not part of this context')
|
|
329
|
+
}
|
|
330
|
+
return graph
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const resolved = this.graph(graph)
|
|
334
|
+
if (!resolved) {
|
|
335
|
+
throw new Error(`Unknown graph: ${graph}`)
|
|
336
|
+
}
|
|
337
|
+
return resolved
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
documentURL(id)
|
|
341
|
+
{
|
|
342
|
+
try {
|
|
343
|
+
const url = new URL(id)
|
|
344
|
+
url.hash = ''
|
|
345
|
+
return url.href
|
|
346
|
+
} catch(err) {
|
|
347
|
+
return id
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
sources(subject, predicate=null, value=undefined)
|
|
352
|
+
{
|
|
353
|
+
if (!subject) {
|
|
354
|
+
return [...this.graphs]
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (subject instanceof BlankNode && !(subject instanceof NamedNode)) {
|
|
358
|
+
return this.sourcesForBlankNode(subject, predicate, value, arguments.length >= 3)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const id = this.subjectID(subject)
|
|
362
|
+
if (!id) {
|
|
363
|
+
return []
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return this.graphs.filter(graph => {
|
|
367
|
+
const graphSubject = graph.subjects[id]
|
|
368
|
+
return graphSubject
|
|
369
|
+
&& this.subjectHasSource(graphSubject, predicate, value, arguments.length >= 3)
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
sourcesForBlankNode(subject, predicate, value, hasValue)
|
|
374
|
+
{
|
|
375
|
+
const graph = subject.graph
|
|
376
|
+
if (!(graph instanceof Graph)) {
|
|
377
|
+
return []
|
|
378
|
+
}
|
|
379
|
+
if (this.subjectHasSource(subject, predicate, value, hasValue)) {
|
|
380
|
+
return [graph]
|
|
381
|
+
}
|
|
382
|
+
return []
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
subjectHasSource(subject, predicate, value, hasValue)
|
|
386
|
+
{
|
|
387
|
+
if (!predicate) {
|
|
388
|
+
return true
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const property = this.propertyName(predicate)
|
|
392
|
+
if (!(property in subject)) {
|
|
393
|
+
return false
|
|
394
|
+
}
|
|
395
|
+
if (!hasValue) {
|
|
396
|
+
return true
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return values(subject[property]).some(item => sameSourceValue(item, value))
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
subjectID(subject)
|
|
403
|
+
{
|
|
404
|
+
if (subject?.id) {
|
|
405
|
+
return this.fullURI(subject.id)
|
|
406
|
+
}
|
|
407
|
+
if (typeof subject == 'string') {
|
|
408
|
+
return this.fullURI(subject)
|
|
409
|
+
}
|
|
410
|
+
return null
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
propertyName(predicate)
|
|
414
|
+
{
|
|
415
|
+
if (predicate?.id) {
|
|
416
|
+
predicate = predicate.id
|
|
417
|
+
}
|
|
418
|
+
if (predicate == 'a' || predicate == rdfType || this.fullURI(predicate) == rdfType) {
|
|
419
|
+
return 'a'
|
|
420
|
+
}
|
|
421
|
+
return this.shortURI(this.fullURI(predicate))
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
get(shortID)
|
|
425
|
+
{
|
|
426
|
+
return this.subjects[this.fullURI(shortID)]
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
getSubjects()
|
|
430
|
+
{
|
|
431
|
+
const subjects = Object.create(null)
|
|
432
|
+
|
|
433
|
+
this.#buildingSubjects = true
|
|
434
|
+
try {
|
|
435
|
+
for (const graph of this.graphs) {
|
|
436
|
+
for (const id of Object.keys(graph.subjects)) {
|
|
437
|
+
if (!subjects[id]) {
|
|
438
|
+
subjects[id] = this.contextSubject(new NamedNode(id, this))
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
for (const graph of this.graphs) {
|
|
444
|
+
for (const [id, subject] of Object.entries(graph.subjects)) {
|
|
445
|
+
this.mergeSubject(subjects[id], subject, subjects)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
} finally {
|
|
449
|
+
this.#buildingSubjects = false
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return subjects
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
mergeSubject(target, source, subjects)
|
|
456
|
+
{
|
|
457
|
+
for (const [predicate, value] of Object.entries(source)) {
|
|
458
|
+
if (predicate == 'id') {
|
|
459
|
+
continue
|
|
460
|
+
}
|
|
461
|
+
target[predicate] = mergeValue(
|
|
462
|
+
target[predicate],
|
|
463
|
+
resolveValue(value, subjects, this)
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
contextSubject(subject)
|
|
469
|
+
{
|
|
470
|
+
const context = this
|
|
471
|
+
return new Proxy(subject, {
|
|
472
|
+
set(target, property, value, receiver) {
|
|
473
|
+
if (context.#buildingSubjects || typeof property == 'symbol' || property == 'id' || property == 'graph') {
|
|
474
|
+
return Reflect.set(target, property, value, receiver)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
context.set(target.id, property, value)
|
|
478
|
+
context.updateContextProperty(target, property)
|
|
479
|
+
return true
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
deleteProperty(target, property) {
|
|
483
|
+
if (context.#buildingSubjects || typeof property == 'symbol' || property == 'id' || property == 'graph') {
|
|
484
|
+
return Reflect.deleteProperty(target, property)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
context.delete(target.id, property)
|
|
488
|
+
context.updateContextProperty(target, property)
|
|
489
|
+
return true
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
updateContextProperty(target, property)
|
|
495
|
+
{
|
|
496
|
+
const updated = this.get(target.id)
|
|
497
|
+
if (updated && property in updated) {
|
|
498
|
+
target[property] = updated[property]
|
|
499
|
+
} else {
|
|
500
|
+
delete target[property]
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
fullURI(shortURI, separator=null)
|
|
505
|
+
{
|
|
506
|
+
if (!separator) {
|
|
507
|
+
separator = this.separator
|
|
508
|
+
}
|
|
509
|
+
const [prefix, path] = shortURI.split(separator)
|
|
510
|
+
if (path && this.prefixes[prefix]) {
|
|
511
|
+
return this.prefixes[prefix]+path
|
|
512
|
+
}
|
|
513
|
+
return shortURI
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
shortURI(fullURI, separator=null)
|
|
517
|
+
{
|
|
518
|
+
if (!separator) {
|
|
519
|
+
separator = this.separator
|
|
520
|
+
}
|
|
521
|
+
for (const prefix of this.prefixOrder) {
|
|
522
|
+
if (fullURI.startsWith(this.prefixes[prefix])) {
|
|
523
|
+
return prefix + separator + fullURI.substring(this.prefixes[prefix].length)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return fullURI
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
setType(literal, shortType)
|
|
530
|
+
{
|
|
531
|
+
if (!shortType) {
|
|
532
|
+
return literal
|
|
533
|
+
}
|
|
534
|
+
if (typeof literal == 'string') {
|
|
535
|
+
literal = new String(literal)
|
|
536
|
+
} else if (typeof literal == 'number') {
|
|
537
|
+
literal = new Number(literal)
|
|
538
|
+
}
|
|
539
|
+
if (typeof literal !== 'object') {
|
|
540
|
+
throw new Error('cannot set type on ',literal,shortType)
|
|
541
|
+
}
|
|
542
|
+
literal.type = shortType
|
|
543
|
+
return literal
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
getType(literal)
|
|
547
|
+
{
|
|
548
|
+
if (literal && typeof literal == 'object') {
|
|
549
|
+
return literal.type
|
|
550
|
+
}
|
|
551
|
+
return null
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export class Graph
|
|
556
|
+
{
|
|
557
|
+
#blankNodes = Object.create(null)
|
|
558
|
+
|
|
559
|
+
constructor(quads, url, mimetype, prefixes, context)
|
|
560
|
+
{
|
|
561
|
+
this.mimetype = mimetype
|
|
562
|
+
this.url = url
|
|
563
|
+
this.prefixes = prefixes
|
|
564
|
+
this.context = context
|
|
565
|
+
this.subjects = Object.create(null)
|
|
566
|
+
for (let quad of quads) {
|
|
567
|
+
let subject
|
|
568
|
+
if (quad.subject.termType=='BlankNode') {
|
|
569
|
+
let shortPred = this.shortURI(quad.predicate.id,':')
|
|
570
|
+
let shortObj
|
|
571
|
+
switch(shortPred) {
|
|
572
|
+
case 'rdf:first':
|
|
573
|
+
subject = this.addCollection(quad.subject.id)
|
|
574
|
+
shortObj = quad.object.id ? this.shortURI(quad.object.id, ':') : null
|
|
575
|
+
if (shortObj!='rdf:nil') {
|
|
576
|
+
const value = this.getValue(quad.object)
|
|
577
|
+
if (value) {
|
|
578
|
+
subject.push(value)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
continue
|
|
582
|
+
case 'rdf:rest':
|
|
583
|
+
this.#blankNodes[quad.object.id] = this.#blankNodes[quad.subject.id]
|
|
584
|
+
continue
|
|
585
|
+
default:
|
|
586
|
+
subject = this.addBlankNode(quad.subject.id)
|
|
587
|
+
break
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
subject = this.addNamedNode(quad.subject.id)
|
|
591
|
+
}
|
|
592
|
+
subject.addPredicate(quad.predicate.id, quad.object)
|
|
593
|
+
}
|
|
594
|
+
if (this.subjects[url]) {
|
|
595
|
+
this.primary = this.subjects[url]
|
|
596
|
+
} else {
|
|
597
|
+
this.primary = null
|
|
598
|
+
}
|
|
599
|
+
Object.defineProperty(this, 'data', {
|
|
600
|
+
get() {
|
|
601
|
+
return Object.values(this.subjects)
|
|
602
|
+
}
|
|
603
|
+
})
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
addNamedNode(uri)
|
|
607
|
+
{
|
|
608
|
+
// make sure any relative uri subject ids are fully qualified
|
|
609
|
+
let absURI = new URL(uri, this.url).href
|
|
610
|
+
if (!this.subjects[absURI]) {
|
|
611
|
+
this.subjects[absURI] = new NamedNode(absURI, this)
|
|
612
|
+
}
|
|
613
|
+
return this.subjects[absURI]
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
addBlankNode(id)
|
|
617
|
+
{
|
|
618
|
+
if (!this.#blankNodes[id]) {
|
|
619
|
+
this.#blankNodes[id] = new BlankNode(this)
|
|
620
|
+
}
|
|
621
|
+
return this.#blankNodes[id]
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
addCollection(id)
|
|
625
|
+
{
|
|
626
|
+
if (!this.#blankNodes[id]) {
|
|
627
|
+
this.#blankNodes[id] = new Collection(this)
|
|
628
|
+
}
|
|
629
|
+
return this.#blankNodes[id]
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
write()
|
|
633
|
+
{
|
|
634
|
+
return this.context.writer(this)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
get(shortID)
|
|
638
|
+
{
|
|
639
|
+
return this.subjects[this.fullURI(shortID)]
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
set(subject, predicate, value)
|
|
643
|
+
{
|
|
644
|
+
const node = this.ensureSubject(subject)
|
|
645
|
+
const property = this.context.propertyName(predicate)
|
|
646
|
+
|
|
647
|
+
if (property == 'a') {
|
|
648
|
+
node.a = this.normalizeTypeValues(value)
|
|
649
|
+
} else {
|
|
650
|
+
node[property] = this.normalizeValues(value)
|
|
651
|
+
}
|
|
652
|
+
return node
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
add(subject, predicate, value)
|
|
656
|
+
{
|
|
657
|
+
const node = this.ensureSubject(subject)
|
|
658
|
+
const property = this.context.propertyName(predicate)
|
|
659
|
+
const newValue = property == 'a'
|
|
660
|
+
? this.normalizeTypeValues(value)
|
|
661
|
+
: this.normalizeValues(value)
|
|
662
|
+
|
|
663
|
+
node[property] = mergeValue(node[property], newValue)
|
|
664
|
+
return node
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
delete(subject, predicate=null, value=undefined)
|
|
668
|
+
{
|
|
669
|
+
const node = this.findSubject(subject)
|
|
670
|
+
if (!node) {
|
|
671
|
+
return false
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (!predicate) {
|
|
675
|
+
if (node.id) {
|
|
676
|
+
delete this.subjects[node.id]
|
|
677
|
+
if (this.primary === node) {
|
|
678
|
+
this.primary = null
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return true
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const property = this.context.propertyName(predicate)
|
|
685
|
+
if (!(property in node)) {
|
|
686
|
+
return false
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (arguments.length < 3) {
|
|
690
|
+
delete node[property]
|
|
691
|
+
return true
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const deleteValues = property == 'a'
|
|
695
|
+
? values(this.normalizeTypeValues(value))
|
|
696
|
+
: values(this.normalizeValues(value))
|
|
697
|
+
const remaining = values(node[property])
|
|
698
|
+
.filter(item => !deleteValues.some(deleteValue => sameValue(item, deleteValue)))
|
|
699
|
+
|
|
700
|
+
if (remaining.length == values(node[property]).length) {
|
|
701
|
+
return false
|
|
702
|
+
}
|
|
703
|
+
if (remaining.length == 0) {
|
|
704
|
+
delete node[property]
|
|
705
|
+
} else if (remaining.length == 1) {
|
|
706
|
+
node[property] = remaining[0]
|
|
707
|
+
} else {
|
|
708
|
+
node[property] = remaining
|
|
709
|
+
}
|
|
710
|
+
return true
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
ensureSubject(subject)
|
|
714
|
+
{
|
|
715
|
+
if (subject instanceof BlankNode && !(subject instanceof NamedNode)) {
|
|
716
|
+
if (subject.graph !== this) {
|
|
717
|
+
throw new Error('Cannot write a blank node into a different graph')
|
|
718
|
+
}
|
|
719
|
+
return subject
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (subject instanceof NamedNode) {
|
|
723
|
+
return this.addNamedNode(subject.id)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return this.addNamedNode(this.fullURI(subject))
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
findSubject(subject)
|
|
730
|
+
{
|
|
731
|
+
if (subject instanceof BlankNode && !(subject instanceof NamedNode)) {
|
|
732
|
+
return subject.graph === this ? subject : null
|
|
733
|
+
}
|
|
734
|
+
const id = subject?.id ? subject.id : this.fullURI(subject)
|
|
735
|
+
return this.subjects[id]
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
normalizeValues(value)
|
|
739
|
+
{
|
|
740
|
+
if (Array.isArray(value) && !(value instanceof Collection)) {
|
|
741
|
+
return value.map(item => this.normalizeValue(item))
|
|
742
|
+
}
|
|
743
|
+
return this.normalizeValue(value)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
normalizeValue(value)
|
|
747
|
+
{
|
|
748
|
+
if (value instanceof Collection) {
|
|
749
|
+
const collection = new Collection(this)
|
|
750
|
+
for (const item of value) {
|
|
751
|
+
collection.push(this.normalizeValue(item))
|
|
752
|
+
}
|
|
753
|
+
return collection
|
|
754
|
+
}
|
|
755
|
+
if (value instanceof NamedNode) {
|
|
756
|
+
return this.addNamedNode(value.id)
|
|
757
|
+
}
|
|
758
|
+
if (value instanceof BlankNode) {
|
|
759
|
+
if (value.graph !== this) {
|
|
760
|
+
throw new Error('Cannot write a blank node into a different graph')
|
|
761
|
+
}
|
|
762
|
+
return value
|
|
763
|
+
}
|
|
764
|
+
if (this.looksLikeURI(value)) {
|
|
765
|
+
return this.addNamedNode(this.fullURI(value))
|
|
766
|
+
}
|
|
767
|
+
return value
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
normalizeTypeValues(value)
|
|
771
|
+
{
|
|
772
|
+
if (Array.isArray(value) && !(value instanceof Collection)) {
|
|
773
|
+
return value.map(item => this.normalizeTypeValue(item))
|
|
774
|
+
}
|
|
775
|
+
return this.normalizeTypeValue(value)
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
normalizeTypeValue(value)
|
|
779
|
+
{
|
|
780
|
+
if (value instanceof NamedNode) {
|
|
781
|
+
return this.shortURI(value.id)
|
|
782
|
+
}
|
|
783
|
+
return this.shortURI(this.fullURI(value))
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
looksLikeURI(value)
|
|
787
|
+
{
|
|
788
|
+
if (typeof value != 'string') {
|
|
789
|
+
return false
|
|
790
|
+
}
|
|
791
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(value)) {
|
|
792
|
+
return true
|
|
793
|
+
}
|
|
794
|
+
const [prefix, path] = value.split(this.context.separator)
|
|
795
|
+
return Boolean(path && this.context.prefixes[prefix])
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
fullURI(shortURI, separator=null)
|
|
799
|
+
{
|
|
800
|
+
if (!separator) {
|
|
801
|
+
separator = this.context.separator
|
|
802
|
+
}
|
|
803
|
+
const [prefix, path] = shortURI.split(separator)
|
|
804
|
+
if (path) {
|
|
805
|
+
if (this.context.prefixes[prefix]) {
|
|
806
|
+
return this.context.prefixes[prefix]+path
|
|
807
|
+
}
|
|
808
|
+
if (this.prefixes[prefix]) {
|
|
809
|
+
return this.prefixes[prefix]+path
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return shortURI
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
shortURI(fullURI, separator=null)
|
|
816
|
+
{
|
|
817
|
+
if (!separator) {
|
|
818
|
+
separator = this.context.separator
|
|
819
|
+
}
|
|
820
|
+
for (const prefix of this.context.prefixOrder) {
|
|
821
|
+
if (fullURI.startsWith(this.context.prefixes[prefix])) {
|
|
822
|
+
return prefix + separator + fullURI.substring(this.context.prefixes[prefix].length)
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (this.url && fullURI.startsWith(this.url)) {
|
|
826
|
+
return fullURI.substring(this.url.length)
|
|
827
|
+
}
|
|
828
|
+
return fullURI
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* This sets the type of a literal, usually one of the xsd types
|
|
833
|
+
*/
|
|
834
|
+
setType(literal, type)
|
|
835
|
+
{
|
|
836
|
+
const shortType = this.shortURI(type)
|
|
837
|
+
return this.context.setType(literal, shortType)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* This returns the type of a literal, or null
|
|
842
|
+
*/
|
|
843
|
+
getType(literal)
|
|
844
|
+
{
|
|
845
|
+
return this.context.getType(literal)
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
setLanguage(literal, language)
|
|
849
|
+
{
|
|
850
|
+
if (typeof literal == 'string') {
|
|
851
|
+
literal = new String(literal)
|
|
852
|
+
} else if (typeof literal == 'number') {
|
|
853
|
+
literal = new Number(literal)
|
|
854
|
+
}
|
|
855
|
+
if (typeof literal !== 'object') {
|
|
856
|
+
throw new Error('cannot set language on ',literal)
|
|
857
|
+
}
|
|
858
|
+
literal.language = language
|
|
859
|
+
return literal
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
getValue(object)
|
|
863
|
+
{
|
|
864
|
+
let result
|
|
865
|
+
if (object.termType=='Literal') {
|
|
866
|
+
result = object.value
|
|
867
|
+
let datatype = object.datatype?.id
|
|
868
|
+
if (datatype) {
|
|
869
|
+
result = this.setType(result, datatype)
|
|
870
|
+
}
|
|
871
|
+
let language = object.language
|
|
872
|
+
if (language) {
|
|
873
|
+
result = this.setLanguage(result, language)
|
|
874
|
+
}
|
|
875
|
+
} else if (object.termType=='BlankNode') {
|
|
876
|
+
result = this.addBlankNode(object.id)
|
|
877
|
+
} else {
|
|
878
|
+
result = this.addNamedNode(object.id)
|
|
879
|
+
}
|
|
880
|
+
return result
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
export class BlankNode
|
|
887
|
+
{
|
|
888
|
+
|
|
889
|
+
constructor(graph)
|
|
890
|
+
{
|
|
891
|
+
Object.defineProperty(this, 'graph', {
|
|
892
|
+
value: graph,
|
|
893
|
+
writable: false,
|
|
894
|
+
enumerable: false
|
|
895
|
+
})
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
addPredicate(predicate, object)
|
|
899
|
+
{
|
|
900
|
+
if (predicate.id) {
|
|
901
|
+
predicate = predicate.id
|
|
902
|
+
}
|
|
903
|
+
if (predicate==rdfType) {
|
|
904
|
+
let type = this.graph.shortURI(object.id)
|
|
905
|
+
this.addType(type)
|
|
906
|
+
} else {
|
|
907
|
+
const value = this.graph.getValue(object)
|
|
908
|
+
predicate = this.graph.shortURI(predicate)
|
|
909
|
+
if (!this[predicate]) {
|
|
910
|
+
this[predicate] = value
|
|
911
|
+
} else if (Array.isArray(this[predicate])) {
|
|
912
|
+
this[predicate].push(value)
|
|
913
|
+
} else {
|
|
914
|
+
this[predicate] = [ this[predicate], value]
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Adds a rdfType value, stored in this.a
|
|
921
|
+
* Subjects can have more than one type (or class), unlike literals
|
|
922
|
+
* The type value can be any URI, xsdTypes are unexpected here
|
|
923
|
+
*/
|
|
924
|
+
addType(type)
|
|
925
|
+
{
|
|
926
|
+
if (!this.a) {
|
|
927
|
+
this.a = type
|
|
928
|
+
} else {
|
|
929
|
+
if (!Array.isArray(this.a)) {
|
|
930
|
+
this.a = [ this.a ]
|
|
931
|
+
}
|
|
932
|
+
this.a.push(type)
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
export class NamedNode extends BlankNode
|
|
938
|
+
{
|
|
939
|
+
constructor(id, graph)
|
|
940
|
+
{
|
|
941
|
+
super(graph)
|
|
942
|
+
Object.defineProperty(this, 'id', {
|
|
943
|
+
value: id,
|
|
944
|
+
writable: false,
|
|
945
|
+
enumerable: true
|
|
946
|
+
})
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
export class Collection extends Array
|
|
951
|
+
{
|
|
952
|
+
constructor(graph)
|
|
953
|
+
{
|
|
954
|
+
super()
|
|
955
|
+
Object.defineProperty(this, 'graph', {
|
|
956
|
+
value: graph,
|
|
957
|
+
writable: false,
|
|
958
|
+
enumerable: false
|
|
959
|
+
})
|
|
960
|
+
}
|
|
961
|
+
}
|