@oml/language 0.7.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/README.md +44 -0
- package/out/oml/generated/ast.d.ts +2109 -0
- package/out/oml/generated/ast.js +1807 -0
- package/out/oml/generated/ast.js.map +1 -0
- package/out/oml/generated/grammar.d.ts +6 -0
- package/out/oml/generated/grammar.js +6885 -0
- package/out/oml/generated/grammar.js.map +1 -0
- package/out/oml/generated/module.d.ts +13 -0
- package/out/oml/generated/module.js +21 -0
- package/out/oml/generated/module.js.map +1 -0
- package/out/oml/index.d.ts +17 -0
- package/out/oml/index.js +19 -0
- package/out/oml/index.js.map +1 -0
- package/out/oml/oml-candidates.d.ts +27 -0
- package/out/oml/oml-candidates.js +146 -0
- package/out/oml/oml-candidates.js.map +1 -0
- package/out/oml/oml-code-actions.d.ts +6 -0
- package/out/oml/oml-code-actions.js +79 -0
- package/out/oml/oml-code-actions.js.map +1 -0
- package/out/oml/oml-completion.d.ts +50 -0
- package/out/oml/oml-completion.js +188 -0
- package/out/oml/oml-completion.js.map +1 -0
- package/out/oml/oml-converter.d.ts +10 -0
- package/out/oml/oml-converter.js +62 -0
- package/out/oml/oml-converter.js.map +1 -0
- package/out/oml/oml-document-validator.d.ts +10 -0
- package/out/oml/oml-document-validator.js +31 -0
- package/out/oml/oml-document-validator.js.map +1 -0
- package/out/oml/oml-document.d.ts +9 -0
- package/out/oml/oml-document.js +18 -0
- package/out/oml/oml-document.js.map +1 -0
- package/out/oml/oml-edit.d.ts +72 -0
- package/out/oml/oml-edit.js +1155 -0
- package/out/oml/oml-edit.js.map +1 -0
- package/out/oml/oml-formatter.d.ts +22 -0
- package/out/oml/oml-formatter.js +357 -0
- package/out/oml/oml-formatter.js.map +1 -0
- package/out/oml/oml-hover.d.ts +13 -0
- package/out/oml/oml-hover.js +71 -0
- package/out/oml/oml-hover.js.map +1 -0
- package/out/oml/oml-index-manager.d.ts +10 -0
- package/out/oml/oml-index-manager.js +48 -0
- package/out/oml/oml-index-manager.js.map +1 -0
- package/out/oml/oml-index.d.ts +20 -0
- package/out/oml/oml-index.js +133 -0
- package/out/oml/oml-index.js.map +1 -0
- package/out/oml/oml-module.d.ts +42 -0
- package/out/oml/oml-module.js +76 -0
- package/out/oml/oml-module.js.map +1 -0
- package/out/oml/oml-rename.d.ts +14 -0
- package/out/oml/oml-rename.js +114 -0
- package/out/oml/oml-rename.js.map +1 -0
- package/out/oml/oml-scope.d.ts +30 -0
- package/out/oml/oml-scope.js +225 -0
- package/out/oml/oml-scope.js.map +1 -0
- package/out/oml/oml-serializer.d.ts +2 -0
- package/out/oml/oml-serializer.js +883 -0
- package/out/oml/oml-serializer.js.map +1 -0
- package/out/oml/oml-utils.d.ts +53 -0
- package/out/oml/oml-utils.js +241 -0
- package/out/oml/oml-utils.js.map +1 -0
- package/out/oml/oml-validator.d.ts +49 -0
- package/out/oml/oml-validator.js +668 -0
- package/out/oml/oml-validator.js.map +1 -0
- package/out/oml/oml-workspace.d.ts +23 -0
- package/out/oml/oml-workspace.js +68 -0
- package/out/oml/oml-workspace.js.map +1 -0
- package/package.json +50 -0
- package/src/oml/generated/ast.ts +2641 -0
- package/src/oml/generated/grammar.ts +6887 -0
- package/src/oml/generated/module.ts +25 -0
- package/src/oml/index.ts +19 -0
- package/src/oml/oml-candidates.ts +176 -0
- package/src/oml/oml-code-actions.ts +120 -0
- package/src/oml/oml-completion.ts +222 -0
- package/src/oml/oml-converter.ts +66 -0
- package/src/oml/oml-document-validator.ts +39 -0
- package/src/oml/oml-document.ts +24 -0
- package/src/oml/oml-edit.ts +1292 -0
- package/src/oml/oml-formatter.ts +390 -0
- package/src/oml/oml-hover.ts +93 -0
- package/src/oml/oml-index-manager.ts +56 -0
- package/src/oml/oml-index.ts +145 -0
- package/src/oml/oml-module.ts +105 -0
- package/src/oml/oml-rename.ts +140 -0
- package/src/oml/oml-scope.ts +279 -0
- package/src/oml/oml-serializer.ts +1080 -0
- package/src/oml/oml-utils.ts +294 -0
- package/src/oml/oml-validator.ts +725 -0
- package/src/oml/oml-workspace.ts +81 -0
- package/src/oml/oml.langium +594 -0
|
@@ -0,0 +1,1292 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { URI } from 'langium';
|
|
4
|
+
import { RequestType, type WorkspaceEdit } from 'vscode-languageserver-protocol';
|
|
5
|
+
import {
|
|
6
|
+
isAnnotation,
|
|
7
|
+
isConceptInstance,
|
|
8
|
+
isDescription,
|
|
9
|
+
isImport,
|
|
10
|
+
isOntology,
|
|
11
|
+
isPropertyValueAssertion,
|
|
12
|
+
isRelationInstance,
|
|
13
|
+
isRule,
|
|
14
|
+
isTypeAssertion,
|
|
15
|
+
} from './generated/ast.js';
|
|
16
|
+
import { getOntologyModelIndex } from './oml-index.js';
|
|
17
|
+
import { serializeOntology } from './oml-serializer.js';
|
|
18
|
+
import {
|
|
19
|
+
collectOntologyMembers,
|
|
20
|
+
findOntologyMemberByName,
|
|
21
|
+
getIriForNode,
|
|
22
|
+
normalizeNamespace,
|
|
23
|
+
} from './oml-utils.js';
|
|
24
|
+
|
|
25
|
+
type ConnectionLike = {
|
|
26
|
+
onRequest: (type: RequestType<any, any, any>, handler: (params: any) => any | Promise<any>) => void;
|
|
27
|
+
console?: {
|
|
28
|
+
error: (message: string) => void;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const OmlEditRequestType = new RequestType<OmlEditRequest, OmlEditResponse, void>('oml/edit');
|
|
33
|
+
const RDF_TYPE_IRI = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
34
|
+
const OML_HAS_SOURCE_IRI = 'http://opencaesar.io/oml#hasSource';
|
|
35
|
+
const OML_HAS_TARGET_IRI = 'http://opencaesar.io/oml#hasTarget';
|
|
36
|
+
|
|
37
|
+
type OmlEditOperation =
|
|
38
|
+
| { kind: 'createInstance'; ontologyIri: string; instanceName: string }
|
|
39
|
+
| { kind: 'createRelationInstance'; ontologyIri: string; instanceName: string }
|
|
40
|
+
| { kind: 'createInstanceRef'; ontologyIri: string; instanceIri: string; typeIri?: string }
|
|
41
|
+
| { kind: 'createRelationInstanceRef'; ontologyIri: string; instanceIri: string; typeIri?: string }
|
|
42
|
+
| { kind: 'addAssertion'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
|
|
43
|
+
| { kind: 'removeAssertion'; ontologyIri: string; subjectIri: string; predicateIri: string; object?: unknown }
|
|
44
|
+
| { kind: 'addAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
|
|
45
|
+
| { kind: 'removeAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object?: unknown }
|
|
46
|
+
| { kind: 'deleteMemberCascade'; ontologyIri: string; memberIri: string }
|
|
47
|
+
| { kind: 'deleteMemberRef'; ontologyIri: string; memberIri: string; typeIri?: string };
|
|
48
|
+
|
|
49
|
+
export type OmlEditRequest = {
|
|
50
|
+
operations: OmlEditOperation[];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type OmlEditError = {
|
|
54
|
+
operationIndex: number;
|
|
55
|
+
message: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type OmlEditResponse = {
|
|
59
|
+
edit?: WorkspaceEdit;
|
|
60
|
+
errors?: OmlEditError[];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export function registerOmlEditRequests(connection: ConnectionLike, shared: any): void {
|
|
64
|
+
connection.onRequest(OmlEditRequestType, async (params: OmlEditRequest): Promise<OmlEditResponse> => {
|
|
65
|
+
const operations = Array.isArray(params?.operations) ? params.operations : [];
|
|
66
|
+
const contexts = new Map<string, OntologyContext>();
|
|
67
|
+
const changedOntologyIris = new Set<string>();
|
|
68
|
+
for (let i = 0; i < operations.length; i += 1) {
|
|
69
|
+
const operation = operations[i];
|
|
70
|
+
try {
|
|
71
|
+
const context = await resolveOntologyContext(shared, operation.ontologyIri, contexts);
|
|
72
|
+
const changed = await executeOperation(shared, context, operation, contexts);
|
|
73
|
+
for (const ontologyIri of changed) {
|
|
74
|
+
changedOntologyIris.add(ontologyIri);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
await restoreContextsFromDocuments(shared, contexts);
|
|
78
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
79
|
+
const details = error instanceof Error && error.stack
|
|
80
|
+
? `${message}\n${error.stack}`
|
|
81
|
+
: message;
|
|
82
|
+
connection.console?.error(`[oml] OmlEditRequest failed at operation ${i} (${operation.kind}): ${details}`);
|
|
83
|
+
return {
|
|
84
|
+
errors: [{
|
|
85
|
+
operationIndex: i,
|
|
86
|
+
message
|
|
87
|
+
}]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const edit = buildWorkspaceEdit(contexts, changedOntologyIris);
|
|
92
|
+
return edit ? { edit } : {};
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
type OntologyContext = {
|
|
97
|
+
ontologyIri: string;
|
|
98
|
+
modelUri: string;
|
|
99
|
+
ontology: any;
|
|
100
|
+
document: any;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
async function resolveOntologyContext(shared: any, ontologyIri: string, cache: Map<string, OntologyContext>): Promise<OntologyContext> {
|
|
104
|
+
const normalizedOntologyIri = normalizeIri(ontologyIri);
|
|
105
|
+
const cached = cache.get(normalizedOntologyIri);
|
|
106
|
+
if (cached) {
|
|
107
|
+
return cached;
|
|
108
|
+
}
|
|
109
|
+
const index = getOntologyModelIndex(shared);
|
|
110
|
+
const modelUri = index.resolveModelUri(normalizedOntologyIri);
|
|
111
|
+
if (!modelUri) {
|
|
112
|
+
throw new Error(`Unable to resolve ontology '${ontologyIri}'.`);
|
|
113
|
+
}
|
|
114
|
+
const uri = URI.parse(modelUri);
|
|
115
|
+
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
116
|
+
const builder: any = shared.workspace.DocumentBuilder;
|
|
117
|
+
const document = langiumDocuments.getDocument(uri)
|
|
118
|
+
?? await langiumDocuments.getOrCreateDocument(uri);
|
|
119
|
+
await builder.build([document], { validation: true });
|
|
120
|
+
const ontology = document?.parseResult?.value;
|
|
121
|
+
if (!ontology || !isOntology(ontology)) {
|
|
122
|
+
throw new Error(`Resolved document '${modelUri}' is not an ontology.`);
|
|
123
|
+
}
|
|
124
|
+
const context: OntologyContext = {
|
|
125
|
+
ontologyIri: normalizedOntologyIri,
|
|
126
|
+
modelUri,
|
|
127
|
+
ontology,
|
|
128
|
+
document
|
|
129
|
+
};
|
|
130
|
+
cache.set(normalizedOntologyIri, context);
|
|
131
|
+
return context;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function executeOperation(
|
|
135
|
+
shared: any,
|
|
136
|
+
context: OntologyContext,
|
|
137
|
+
operation: OmlEditOperation,
|
|
138
|
+
contexts: Map<string, OntologyContext>
|
|
139
|
+
): Promise<ReadonlySet<string>> {
|
|
140
|
+
switch (operation.kind) {
|
|
141
|
+
case 'createInstance':
|
|
142
|
+
ensureDescriptionOntology(context.ontology, operation.kind);
|
|
143
|
+
ensureUniqueLocalName(context.ontology, operation.instanceName);
|
|
144
|
+
{
|
|
145
|
+
const statement: any = {
|
|
146
|
+
$type: 'ConceptInstance',
|
|
147
|
+
name: operation.instanceName,
|
|
148
|
+
ownedAnnotations: [],
|
|
149
|
+
ownedPropertyValues: [],
|
|
150
|
+
ownedTypes: []
|
|
151
|
+
};
|
|
152
|
+
statement.$container = context.ontology;
|
|
153
|
+
context.ontology.ownedStatements.push(statement);
|
|
154
|
+
}
|
|
155
|
+
return new Set([context.ontologyIri]);
|
|
156
|
+
case 'createRelationInstance':
|
|
157
|
+
ensureDescriptionOntology(context.ontology, operation.kind);
|
|
158
|
+
ensureUniqueLocalName(context.ontology, operation.instanceName);
|
|
159
|
+
{
|
|
160
|
+
const statement: any = {
|
|
161
|
+
$type: 'RelationInstance',
|
|
162
|
+
name: operation.instanceName,
|
|
163
|
+
ownedAnnotations: [],
|
|
164
|
+
ownedPropertyValues: [],
|
|
165
|
+
ownedTypes: [],
|
|
166
|
+
sources: [],
|
|
167
|
+
targets: []
|
|
168
|
+
};
|
|
169
|
+
statement.$container = context.ontology;
|
|
170
|
+
context.ontology.ownedStatements.push(statement);
|
|
171
|
+
}
|
|
172
|
+
return new Set([context.ontologyIri]);
|
|
173
|
+
case 'createInstanceRef':
|
|
174
|
+
ensureDescriptionOntology(context.ontology, operation.kind);
|
|
175
|
+
ensureReferenceImport(context.ontology, operation.instanceIri);
|
|
176
|
+
if (operation.typeIri) {
|
|
177
|
+
ensureReferenceImport(context.ontology, operation.typeIri);
|
|
178
|
+
}
|
|
179
|
+
{
|
|
180
|
+
const ownedTypes: any[] = [];
|
|
181
|
+
if (operation.typeIri) {
|
|
182
|
+
ownedTypes.push({
|
|
183
|
+
$type: 'TypeAssertion',
|
|
184
|
+
type: asRef(context.ontology, operation.typeIri)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const statement: any = {
|
|
188
|
+
$type: 'ConceptInstance',
|
|
189
|
+
ref: asRef(context.ontology, operation.instanceIri),
|
|
190
|
+
ownedAnnotations: [],
|
|
191
|
+
ownedPropertyValues: [],
|
|
192
|
+
ownedTypes
|
|
193
|
+
};
|
|
194
|
+
statement.__targetIri = normalizeIri(operation.instanceIri);
|
|
195
|
+
statement.$container = context.ontology;
|
|
196
|
+
context.ontology.ownedStatements.push(statement);
|
|
197
|
+
}
|
|
198
|
+
return new Set([context.ontologyIri]);
|
|
199
|
+
case 'createRelationInstanceRef':
|
|
200
|
+
ensureDescriptionOntology(context.ontology, operation.kind);
|
|
201
|
+
ensureReferenceImport(context.ontology, operation.instanceIri);
|
|
202
|
+
if (operation.typeIri) {
|
|
203
|
+
ensureReferenceImport(context.ontology, operation.typeIri);
|
|
204
|
+
}
|
|
205
|
+
{
|
|
206
|
+
const ownedTypes: any[] = [];
|
|
207
|
+
if (operation.typeIri) {
|
|
208
|
+
ownedTypes.push({
|
|
209
|
+
$type: 'TypeAssertion',
|
|
210
|
+
type: asRef(context.ontology, operation.typeIri)
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
const statement: any = {
|
|
214
|
+
$type: 'RelationInstance',
|
|
215
|
+
ref: asRef(context.ontology, operation.instanceIri),
|
|
216
|
+
ownedAnnotations: [],
|
|
217
|
+
ownedPropertyValues: [],
|
|
218
|
+
ownedTypes,
|
|
219
|
+
sources: [],
|
|
220
|
+
targets: []
|
|
221
|
+
};
|
|
222
|
+
statement.__targetIri = normalizeIri(operation.instanceIri);
|
|
223
|
+
statement.$container = context.ontology;
|
|
224
|
+
context.ontology.ownedStatements.push(statement);
|
|
225
|
+
}
|
|
226
|
+
return new Set([context.ontologyIri]);
|
|
227
|
+
case 'addAssertion':
|
|
228
|
+
ensureDescriptionOntology(context.ontology, operation.kind);
|
|
229
|
+
return addAssertion(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
|
|
230
|
+
? new Set([context.ontologyIri])
|
|
231
|
+
: new Set();
|
|
232
|
+
case 'removeAssertion':
|
|
233
|
+
ensureDescriptionOntology(context.ontology, operation.kind);
|
|
234
|
+
return removeAssertion(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
|
|
235
|
+
? new Set([context.ontologyIri])
|
|
236
|
+
: new Set();
|
|
237
|
+
case 'addAnnotation':
|
|
238
|
+
return addAnnotation(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
|
|
239
|
+
? new Set([context.ontologyIri])
|
|
240
|
+
: new Set();
|
|
241
|
+
case 'removeAnnotation':
|
|
242
|
+
return removeAnnotation(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
|
|
243
|
+
? new Set([context.ontologyIri])
|
|
244
|
+
: new Set();
|
|
245
|
+
case 'deleteMemberCascade':
|
|
246
|
+
return deleteMemberCascade(shared, context, operation.memberIri, contexts);
|
|
247
|
+
case 'deleteMemberRef':
|
|
248
|
+
ensureDescriptionOntology(context.ontology, operation.kind);
|
|
249
|
+
return deleteMemberRef(context.ontology, operation.memberIri, operation.typeIri)
|
|
250
|
+
? new Set([context.ontologyIri])
|
|
251
|
+
: new Set();
|
|
252
|
+
default:
|
|
253
|
+
throw new Error(`Unsupported operation '${(operation as { kind?: string })?.kind ?? 'unknown'}'.`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function resolveOntologyContextByModelUri(
|
|
258
|
+
shared: any,
|
|
259
|
+
modelUri: string,
|
|
260
|
+
cache: Map<string, OntologyContext>
|
|
261
|
+
): Promise<OntologyContext> {
|
|
262
|
+
for (const context of cache.values()) {
|
|
263
|
+
if (context.modelUri === modelUri) {
|
|
264
|
+
return context;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const uri = URI.parse(modelUri);
|
|
268
|
+
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
269
|
+
const builder: any = shared.workspace.DocumentBuilder;
|
|
270
|
+
const document = langiumDocuments.getDocument(uri)
|
|
271
|
+
?? await langiumDocuments.getOrCreateDocument(uri);
|
|
272
|
+
await builder.build([document], { validation: true });
|
|
273
|
+
const ontology = document?.parseResult?.value;
|
|
274
|
+
if (!ontology || !isOntology(ontology)) {
|
|
275
|
+
throw new Error(`Resolved document '${modelUri}' is not an ontology.`);
|
|
276
|
+
}
|
|
277
|
+
const ontologyIri = normalizeNamespace(String(ontology.namespace ?? ''));
|
|
278
|
+
const context: OntologyContext = {
|
|
279
|
+
ontologyIri,
|
|
280
|
+
modelUri,
|
|
281
|
+
ontology,
|
|
282
|
+
document
|
|
283
|
+
};
|
|
284
|
+
cache.set(ontologyIri, context);
|
|
285
|
+
return context;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function ensureDescriptionOntology(ontology: any, operationKind: string): void {
|
|
289
|
+
if (!isDescription(ontology)) {
|
|
290
|
+
throw new Error(`${operationKind} requires a description ontology.`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function ensureUniqueLocalName(ontology: any, memberName: string): void {
|
|
295
|
+
if (findOntologyMemberByName(ontology, memberName)) {
|
|
296
|
+
throw new Error(`A member named '${memberName}' already exists in the ontology.`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function addAssertion(ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): boolean {
|
|
301
|
+
const normalizedPredicate = normalizeIri(predicateIri);
|
|
302
|
+
const subject = findNamedInstanceByIri(ontology, subjectIri);
|
|
303
|
+
if (!subject) {
|
|
304
|
+
throw new Error(`Subject '${subjectIri}' was not found in the ontology.`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (normalizedPredicate === RDF_TYPE_IRI) {
|
|
308
|
+
const normalizedTypeIri = normalizeIri(asRequiredIri(objectValue, 'type assertion object'));
|
|
309
|
+
if ((subject.ownedTypes ?? []).some((entry: any) => matchesPredicateRef(ontology, entry?.type, normalizedTypeIri))) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
ensureReferenceImport(ontology, normalizedTypeIri);
|
|
313
|
+
subject.ownedTypes ??= [];
|
|
314
|
+
subject.ownedTypes.push({
|
|
315
|
+
$type: 'TypeAssertion',
|
|
316
|
+
type: asRef(ontology, normalizedTypeIri)
|
|
317
|
+
});
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (isSourcePredicate(normalizedPredicate)) {
|
|
322
|
+
if (!isRelationInstance(subject)) {
|
|
323
|
+
throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
|
|
324
|
+
}
|
|
325
|
+
const sourceIri = normalizeIri(asRequiredIri(objectValue, 'source assertion object'));
|
|
326
|
+
ensureReferenceImport(ontology, sourceIri);
|
|
327
|
+
subject.sources ??= [];
|
|
328
|
+
subject.sources.push(asRef(ontology, sourceIri));
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (isTargetPredicate(normalizedPredicate)) {
|
|
333
|
+
if (!isRelationInstance(subject)) {
|
|
334
|
+
throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
|
|
335
|
+
}
|
|
336
|
+
const targetIri = normalizeIri(asRequiredIri(objectValue, 'target assertion object'));
|
|
337
|
+
ensureReferenceImport(ontology, targetIri);
|
|
338
|
+
subject.targets ??= [];
|
|
339
|
+
subject.targets.push(asRef(ontology, targetIri));
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
subject.ownedPropertyValues ??= [];
|
|
344
|
+
let propertyAssertion = subject.ownedPropertyValues.find((assertion: any) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
|
|
345
|
+
if (!propertyAssertion) {
|
|
346
|
+
propertyAssertion = {
|
|
347
|
+
$type: 'PropertyValueAssertion',
|
|
348
|
+
property: asRef(ontology, normalizedPredicate),
|
|
349
|
+
literalValues: [],
|
|
350
|
+
referencedValues: [],
|
|
351
|
+
containedValues: []
|
|
352
|
+
};
|
|
353
|
+
subject.ownedPropertyValues.push(propertyAssertion);
|
|
354
|
+
}
|
|
355
|
+
appendAssertionValue(ontology, propertyAssertion, objectValue);
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function removeAssertion(ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): boolean {
|
|
360
|
+
const subject = findNamedInstanceByIri(ontology, subjectIri);
|
|
361
|
+
if (!subject) {
|
|
362
|
+
throw new Error(`Subject '${subjectIri}' was not found in the ontology.`);
|
|
363
|
+
}
|
|
364
|
+
const normalizedPredicate = normalizeIri(predicateIri);
|
|
365
|
+
let changed = false;
|
|
366
|
+
|
|
367
|
+
if (normalizedPredicate === RDF_TYPE_IRI) {
|
|
368
|
+
if (!Array.isArray(subject.ownedTypes) || subject.ownedTypes.length === 0) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const before = subject.ownedTypes.length;
|
|
372
|
+
if (objectValue === undefined) {
|
|
373
|
+
subject.ownedTypes = [];
|
|
374
|
+
} else {
|
|
375
|
+
const iri = normalizeIri(asRequiredIri(objectValue, 'type assertion object'));
|
|
376
|
+
subject.ownedTypes = subject.ownedTypes.filter((entry: any) => !matchesPredicateRef(ontology, entry?.type, iri));
|
|
377
|
+
}
|
|
378
|
+
changed = before !== subject.ownedTypes.length;
|
|
379
|
+
return changed;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (isSourcePredicate(normalizedPredicate)) {
|
|
383
|
+
if (!isRelationInstance(subject) || !Array.isArray(subject.sources)) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
const before = subject.sources.length;
|
|
387
|
+
if (objectValue === undefined) {
|
|
388
|
+
subject.sources = [];
|
|
389
|
+
} else {
|
|
390
|
+
const iri = normalizeIri(asRequiredIri(objectValue, 'source assertion object'));
|
|
391
|
+
subject.sources = subject.sources.filter((ref: any) => !matchesPredicateRef(ontology, ref, iri));
|
|
392
|
+
}
|
|
393
|
+
return before !== subject.sources.length;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (isTargetPredicate(normalizedPredicate)) {
|
|
397
|
+
if (!isRelationInstance(subject) || !Array.isArray(subject.targets)) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
const before = subject.targets.length;
|
|
401
|
+
if (objectValue === undefined) {
|
|
402
|
+
subject.targets = [];
|
|
403
|
+
} else {
|
|
404
|
+
const iri = normalizeIri(asRequiredIri(objectValue, 'target assertion object'));
|
|
405
|
+
subject.targets = subject.targets.filter((ref: any) => !matchesPredicateRef(ontology, ref, iri));
|
|
406
|
+
}
|
|
407
|
+
return before !== subject.targets.length;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!Array.isArray(subject.ownedPropertyValues) || subject.ownedPropertyValues.length === 0) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const remaining: any[] = [];
|
|
415
|
+
for (const assertion of subject.ownedPropertyValues) {
|
|
416
|
+
if (!matchesPredicateRef(ontology, assertion?.property, normalizedPredicate)) {
|
|
417
|
+
remaining.push(assertion);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (objectValue === undefined) {
|
|
421
|
+
changed = true;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const beforeCounts = {
|
|
425
|
+
literal: assertion.literalValues?.length ?? 0,
|
|
426
|
+
referenced: assertion.referencedValues?.length ?? 0,
|
|
427
|
+
contained: assertion.containedValues?.length ?? 0
|
|
428
|
+
};
|
|
429
|
+
removeAssertionValue(ontology, assertion, objectValue);
|
|
430
|
+
const afterCounts = {
|
|
431
|
+
literal: assertion.literalValues?.length ?? 0,
|
|
432
|
+
referenced: assertion.referencedValues?.length ?? 0,
|
|
433
|
+
contained: assertion.containedValues?.length ?? 0
|
|
434
|
+
};
|
|
435
|
+
const hasValues = afterCounts.literal > 0
|
|
436
|
+
|| afterCounts.referenced > 0
|
|
437
|
+
|| afterCounts.contained > 0;
|
|
438
|
+
if (hasValues) {
|
|
439
|
+
remaining.push(assertion);
|
|
440
|
+
}
|
|
441
|
+
changed ||= beforeCounts.literal !== afterCounts.literal
|
|
442
|
+
|| beforeCounts.referenced !== afterCounts.referenced
|
|
443
|
+
|| beforeCounts.contained !== afterCounts.contained
|
|
444
|
+
|| !hasValues;
|
|
445
|
+
}
|
|
446
|
+
subject.ownedPropertyValues = remaining;
|
|
447
|
+
return changed;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function addAnnotation(ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): boolean {
|
|
451
|
+
const subject = findAnnotationSubjectByIri(ontology, subjectIri);
|
|
452
|
+
if (!subject) {
|
|
453
|
+
throw new Error(`Annotation subject '${subjectIri}' was not found in the ontology.`);
|
|
454
|
+
}
|
|
455
|
+
subject.ownedAnnotations ??= [];
|
|
456
|
+
const annotation: any = {
|
|
457
|
+
$type: 'Annotation',
|
|
458
|
+
property: asRef(ontology, predicateIri),
|
|
459
|
+
literalValues: [],
|
|
460
|
+
referencedValues: []
|
|
461
|
+
};
|
|
462
|
+
appendAnnotationValue(ontology, annotation, objectValue);
|
|
463
|
+
subject.ownedAnnotations.push(annotation);
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function removeAnnotation(ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): boolean {
|
|
468
|
+
const subject = findAnnotationSubjectByIri(ontology, subjectIri);
|
|
469
|
+
if (!subject || !Array.isArray(subject.ownedAnnotations) || subject.ownedAnnotations.length === 0) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
const normalizedPredicate = normalizeIri(predicateIri);
|
|
473
|
+
let changed = false;
|
|
474
|
+
const kept: any[] = [];
|
|
475
|
+
for (const annotation of subject.ownedAnnotations) {
|
|
476
|
+
if (!matchesPredicateRef(ontology, annotation?.property, normalizedPredicate)) {
|
|
477
|
+
kept.push(annotation);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (objectValue === undefined) {
|
|
481
|
+
changed = true;
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const beforeCounts = {
|
|
485
|
+
literal: annotation.literalValues?.length ?? 0,
|
|
486
|
+
referenced: annotation.referencedValues?.length ?? 0
|
|
487
|
+
};
|
|
488
|
+
removeAnnotationValue(ontology, annotation, objectValue);
|
|
489
|
+
const afterCounts = {
|
|
490
|
+
literal: annotation.literalValues?.length ?? 0,
|
|
491
|
+
referenced: annotation.referencedValues?.length ?? 0
|
|
492
|
+
};
|
|
493
|
+
const hasValues = afterCounts.literal > 0 || afterCounts.referenced > 0;
|
|
494
|
+
if (hasValues) {
|
|
495
|
+
kept.push(annotation);
|
|
496
|
+
}
|
|
497
|
+
changed ||= beforeCounts.literal !== afterCounts.literal
|
|
498
|
+
|| beforeCounts.referenced !== afterCounts.referenced
|
|
499
|
+
|| !hasValues;
|
|
500
|
+
}
|
|
501
|
+
subject.ownedAnnotations = kept;
|
|
502
|
+
return changed;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function deleteMemberRef(ontology: any, memberIri: string, typeIri: string | undefined): boolean {
|
|
506
|
+
if (!Array.isArray(ontology.ownedStatements)) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
const normalizedMemberIri = normalizeIri(memberIri);
|
|
510
|
+
const normalizedTypeIri = typeIri ? normalizeIri(typeIri) : undefined;
|
|
511
|
+
const before = ontology.ownedStatements.length;
|
|
512
|
+
ontology.ownedStatements = ontology.ownedStatements.filter((statement: any) => {
|
|
513
|
+
const ref = statement?.ref;
|
|
514
|
+
if (!ref) {
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
if (!matchesPredicateRef(ontology, ref, normalizedMemberIri)) {
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
if (!normalizedTypeIri) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
const typeMatches = (statement?.ownedTypes ?? []).some((typeAssertion: any) => matchesPredicateRef(ontology, typeAssertion?.type, normalizedTypeIri));
|
|
524
|
+
return !typeMatches;
|
|
525
|
+
});
|
|
526
|
+
return before !== ontology.ownedStatements.length;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async function deleteMemberCascade(
|
|
530
|
+
shared: any,
|
|
531
|
+
context: OntologyContext,
|
|
532
|
+
memberIri: string,
|
|
533
|
+
contexts: Map<string, OntologyContext>
|
|
534
|
+
): Promise<ReadonlySet<string>> {
|
|
535
|
+
const targetMember = findOntologyMemberByIri(context.ontology, memberIri);
|
|
536
|
+
if (!targetMember) {
|
|
537
|
+
throw new Error(`Member '${memberIri}' was not found in the ontology.`);
|
|
538
|
+
}
|
|
539
|
+
const languageServices = getLanguageServicesForModelUri(shared, context.modelUri);
|
|
540
|
+
const rawReferences = languageServices.references.References
|
|
541
|
+
.findReferences(targetMember, { includeDeclaration: false })
|
|
542
|
+
.toArray();
|
|
543
|
+
const references = rawReferences;
|
|
544
|
+
const astNodeLocator = languageServices.workspace.AstNodeLocator;
|
|
545
|
+
const resolvedReferences: Array<{ context: OntologyContext; sourceNode: any }> = [];
|
|
546
|
+
for (const reference of references) {
|
|
547
|
+
const sourceContext = await resolveOntologyContextByModelUri(shared, reference.sourceUri.toString(), contexts);
|
|
548
|
+
const sourceNode = astNodeLocator.getAstNode(sourceContext.ontology, reference.sourcePath);
|
|
549
|
+
if (sourceNode) {
|
|
550
|
+
resolvedReferences.push({ context: sourceContext, sourceNode });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const changedOntologyIris = new Set<string>();
|
|
555
|
+
if (deleteOwningStatement(context.ontology, targetMember)) {
|
|
556
|
+
changedOntologyIris.add(context.ontologyIri);
|
|
557
|
+
}
|
|
558
|
+
for (const reference of resolvedReferences) {
|
|
559
|
+
const changed = deleteCascadeReference(reference.context.ontology, reference.sourceNode, memberIri);
|
|
560
|
+
if (changed) {
|
|
561
|
+
changedOntologyIris.add(reference.context.ontologyIri);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return changedOntologyIris;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function getLanguageServicesForModelUri(shared: any, modelUri: string): any {
|
|
568
|
+
const registry = shared?.ServiceRegistry;
|
|
569
|
+
if (registry?.getServices) {
|
|
570
|
+
return registry.getServices(URI.parse(modelUri));
|
|
571
|
+
}
|
|
572
|
+
const all = registry?.all;
|
|
573
|
+
const services = Array.isArray(all)
|
|
574
|
+
? all[0]
|
|
575
|
+
: (typeof all?.toArray === 'function' ? all.toArray()[0] : Array.from((all ?? []) as Iterable<any>)[0]);
|
|
576
|
+
if (!services) {
|
|
577
|
+
throw new Error(`Unable to resolve language services for '${modelUri}'.`);
|
|
578
|
+
}
|
|
579
|
+
return services;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function findOntologyMemberByIri(ontology: any, iri: string): any | undefined {
|
|
583
|
+
const normalizedIri = normalizeIri(iri);
|
|
584
|
+
return collectOntologyMembers(ontology).find((member: any) => isSameIriTarget(getIriForNode(member) ?? '', normalizedIri));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function deleteCascadeReference(ontology: any, sourceNode: any, targetIri: string): boolean {
|
|
588
|
+
if (!sourceNode || !sourceNode.$container) {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
if (isConceptInstance(sourceNode) || isRelationInstance(sourceNode)) {
|
|
592
|
+
return deleteCascadeFromMember(ontology, sourceNode, targetIri);
|
|
593
|
+
}
|
|
594
|
+
if (isTypeAssertion(sourceNode)) {
|
|
595
|
+
const owner = sourceNode.$container;
|
|
596
|
+
if ((owner?.ownedTypes?.length ?? 0) <= 1) {
|
|
597
|
+
return deleteOwningStatement(ontology, owner);
|
|
598
|
+
}
|
|
599
|
+
const before = owner.ownedTypes.length;
|
|
600
|
+
owner.ownedTypes = owner.ownedTypes.filter((entry: any) => entry !== sourceNode);
|
|
601
|
+
return before !== owner.ownedTypes.length;
|
|
602
|
+
}
|
|
603
|
+
if (isPropertyValueAssertion(sourceNode)) {
|
|
604
|
+
const before = sourceNode.referencedValues?.length ?? 0;
|
|
605
|
+
sourceNode.referencedValues = (sourceNode.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, targetIri));
|
|
606
|
+
if (before === (sourceNode.referencedValues?.length ?? 0)) {
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
if ((sourceNode.referencedValues?.length ?? 0) === 0
|
|
610
|
+
&& (sourceNode.literalValues?.length ?? 0) === 0
|
|
611
|
+
&& (sourceNode.containedValues?.length ?? 0) === 0) {
|
|
612
|
+
return removeNodeFromParent(sourceNode);
|
|
613
|
+
}
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
if (isAnnotation(sourceNode)) {
|
|
617
|
+
const before = sourceNode.referencedValues?.length ?? 0;
|
|
618
|
+
sourceNode.referencedValues = (sourceNode.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, targetIri));
|
|
619
|
+
if (before === (sourceNode.referencedValues?.length ?? 0)) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
if ((sourceNode.referencedValues?.length ?? 0) === 0 && (sourceNode.literalValues?.length ?? 0) === 0) {
|
|
623
|
+
return removeNodeFromParent(sourceNode);
|
|
624
|
+
}
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
if (isImport(sourceNode)) {
|
|
628
|
+
return removeNodeFromParent(sourceNode);
|
|
629
|
+
}
|
|
630
|
+
const changed = removeNodeFromParent(sourceNode);
|
|
631
|
+
if (!changed) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
const parent = sourceNode.$container;
|
|
635
|
+
if (isRule(parent) && ((parent.antecedent?.length ?? 0) === 0 || (parent.consequent?.length ?? 0) === 0)) {
|
|
636
|
+
return deleteOwningStatement(ontology, parent) || changed;
|
|
637
|
+
}
|
|
638
|
+
return changed;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function deleteCascadeFromMember(ontology: any, member: any, targetIri: string): boolean {
|
|
642
|
+
let changed = false;
|
|
643
|
+
if (matchesPredicateRef(ontology, member.ref, targetIri)) {
|
|
644
|
+
return deleteOwningStatement(ontology, member);
|
|
645
|
+
}
|
|
646
|
+
if (Array.isArray(member.ownedTypes) && member.ownedTypes.some((entry: any) => matchesPredicateRef(ontology, entry?.type, targetIri))) {
|
|
647
|
+
if (member.ownedTypes.length <= 1) {
|
|
648
|
+
return deleteOwningStatement(ontology, member);
|
|
649
|
+
}
|
|
650
|
+
const before = member.ownedTypes.length;
|
|
651
|
+
member.ownedTypes = member.ownedTypes.filter((entry: any) => !matchesPredicateRef(ontology, entry?.type, targetIri));
|
|
652
|
+
changed ||= before !== member.ownedTypes.length;
|
|
653
|
+
}
|
|
654
|
+
if (isRelationInstance(member)) {
|
|
655
|
+
if (Array.isArray(member.sources) && member.sources.some((ref: any) => matchesPredicateRef(ontology, ref, targetIri))) {
|
|
656
|
+
if (member.sources.length <= 1) {
|
|
657
|
+
return deleteOwningStatement(ontology, member);
|
|
658
|
+
}
|
|
659
|
+
const before = member.sources.length;
|
|
660
|
+
member.sources = member.sources.filter((ref: any) => !matchesPredicateRef(ontology, ref, targetIri));
|
|
661
|
+
changed ||= before !== member.sources.length;
|
|
662
|
+
}
|
|
663
|
+
if (Array.isArray(member.targets) && member.targets.some((ref: any) => matchesPredicateRef(ontology, ref, targetIri))) {
|
|
664
|
+
if (member.targets.length <= 1) {
|
|
665
|
+
return deleteOwningStatement(ontology, member);
|
|
666
|
+
}
|
|
667
|
+
const before = member.targets.length;
|
|
668
|
+
member.targets = member.targets.filter((ref: any) => !matchesPredicateRef(ontology, ref, targetIri));
|
|
669
|
+
changed ||= before !== member.targets.length;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (Array.isArray(member.ownedPropertyValues)) {
|
|
673
|
+
const remainingAssertions: any[] = [];
|
|
674
|
+
for (const assertion of member.ownedPropertyValues) {
|
|
675
|
+
if (!deleteCascadeReference(ontology, assertion, targetIri)) {
|
|
676
|
+
remainingAssertions.push(assertion);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
changed = true;
|
|
680
|
+
const stillOwned = Array.isArray(member.ownedPropertyValues) && member.ownedPropertyValues.includes(assertion);
|
|
681
|
+
if (stillOwned) {
|
|
682
|
+
remainingAssertions.push(assertion);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
member.ownedPropertyValues = remainingAssertions;
|
|
686
|
+
}
|
|
687
|
+
if (Array.isArray(member.ownedAnnotations)) {
|
|
688
|
+
const remainingAnnotations: any[] = [];
|
|
689
|
+
for (const annotation of member.ownedAnnotations) {
|
|
690
|
+
if (!deleteCascadeReference(ontology, annotation, targetIri)) {
|
|
691
|
+
remainingAnnotations.push(annotation);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
changed = true;
|
|
695
|
+
const stillOwned = Array.isArray(member.ownedAnnotations) && member.ownedAnnotations.includes(annotation);
|
|
696
|
+
if (stillOwned) {
|
|
697
|
+
remainingAnnotations.push(annotation);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
member.ownedAnnotations = remainingAnnotations;
|
|
701
|
+
}
|
|
702
|
+
return changed;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function deleteOwningStatement(ontology: any, node: any): boolean {
|
|
706
|
+
const statement = findOwningStatement(ontology, node);
|
|
707
|
+
if (!statement) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
if (!Array.isArray(ontology.ownedStatements)) {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
const before = ontology.ownedStatements.length;
|
|
714
|
+
ontology.ownedStatements = ontology.ownedStatements.filter((entry: any) => entry !== statement);
|
|
715
|
+
return before !== ontology.ownedStatements.length;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function findOwningStatement(ontology: any, node: any): any | undefined {
|
|
719
|
+
let current = node;
|
|
720
|
+
while (current) {
|
|
721
|
+
if (current?.$container === ontology) {
|
|
722
|
+
return current;
|
|
723
|
+
}
|
|
724
|
+
current = current.$container;
|
|
725
|
+
}
|
|
726
|
+
return undefined;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function removeNodeFromParent(node: any): boolean {
|
|
730
|
+
const parent = node?.$container;
|
|
731
|
+
if (!parent || typeof parent !== 'object') {
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
for (const [key, value] of Object.entries(parent)) {
|
|
735
|
+
if (key.startsWith('$')) {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (Array.isArray(value)) {
|
|
739
|
+
const index = value.indexOf(node);
|
|
740
|
+
if (index >= 0) {
|
|
741
|
+
value.splice(index, 1);
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
if (value === node) {
|
|
747
|
+
(parent as Record<string, unknown>)[key] = undefined;
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function findNamedInstanceByIri(ontology: any, iri: string): any | undefined {
|
|
755
|
+
const normalized = normalizeIri(iri);
|
|
756
|
+
const targetParts = parseIriParts(normalized);
|
|
757
|
+
const statements = Array.isArray(ontology.ownedStatements) ? ontology.ownedStatements : [];
|
|
758
|
+
for (const statement of statements) {
|
|
759
|
+
if (!isConceptInstance(statement) && !isRelationInstance(statement)) {
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const marker = statement as any;
|
|
763
|
+
const directTarget = typeof marker.__targetIri === 'string' ? normalizeIri(marker.__targetIri) : '';
|
|
764
|
+
if (directTarget && isSameIriTarget(directTarget, normalized)) {
|
|
765
|
+
return statement;
|
|
766
|
+
}
|
|
767
|
+
const effectiveIri = resolveInstanceIri(ontology, statement);
|
|
768
|
+
if (effectiveIri && isSameIriTarget(effectiveIri, normalized)) {
|
|
769
|
+
return statement;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (!targetParts) {
|
|
773
|
+
return undefined;
|
|
774
|
+
}
|
|
775
|
+
const fragmentMatches: any[] = [];
|
|
776
|
+
for (const statement of statements) {
|
|
777
|
+
if (!isConceptInstance(statement) && !isRelationInstance(statement)) {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (matchesRefTextTarget(ontology, statement, targetParts.namespace, targetParts.fragment)) {
|
|
781
|
+
return statement;
|
|
782
|
+
}
|
|
783
|
+
if (matchesRefTextFragment(statement, targetParts.fragment)) {
|
|
784
|
+
fragmentMatches.push(statement);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (fragmentMatches.length === 1) {
|
|
788
|
+
return fragmentMatches[0];
|
|
789
|
+
}
|
|
790
|
+
return undefined;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function resolveInstanceIri(ontology: any, statement: any): string | undefined {
|
|
794
|
+
const namedIri = getIriForNode(statement);
|
|
795
|
+
if (namedIri) {
|
|
796
|
+
return normalizeIri(namedIri);
|
|
797
|
+
}
|
|
798
|
+
const refText = getRefText(statement?.ref);
|
|
799
|
+
if (refText) {
|
|
800
|
+
return normalizeIri(expandRefTextToIri(ontology, refText) ?? refText);
|
|
801
|
+
}
|
|
802
|
+
const resolved = statement?.ref?.ref ?? statement?.ref?._ref;
|
|
803
|
+
const resolvedIri = resolved ? getIriForNode(resolved) : undefined;
|
|
804
|
+
return resolvedIri ? normalizeIri(resolvedIri) : undefined;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function findAnnotationSubjectByIri(ontology: any, iri: string): any | undefined {
|
|
808
|
+
const normalized = normalizeIri(iri);
|
|
809
|
+
const ontologyIri = normalizeNamespace((ontology as any).namespace ?? '');
|
|
810
|
+
if (normalizeIri(ontologyIri) === normalized) {
|
|
811
|
+
return ontology;
|
|
812
|
+
}
|
|
813
|
+
return collectOntologyMembers(ontology).find((member: any) => normalizeIri(getIriForNode(member) ?? '') === normalized);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function matchesPredicateRef(ontology: any, ref: any, iri: string): boolean {
|
|
817
|
+
if (!ref) {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
const normalizedWanted = normalizeIri(iri);
|
|
821
|
+
const refText = typeof ref === 'string'
|
|
822
|
+
? ref
|
|
823
|
+
: (typeof ref.$refText === 'string' ? ref.$refText : undefined);
|
|
824
|
+
if (refText) {
|
|
825
|
+
const normalizedRefText = normalizeIri(expandRefTextToIri(ontology, refText) ?? refText);
|
|
826
|
+
if (isSameIriTarget(normalizedRefText, normalizedWanted)) {
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const resolved = ref.ref ?? ref._ref;
|
|
831
|
+
const resolvedIri = resolved ? getIriForNode(resolved) : undefined;
|
|
832
|
+
return isSameIriTarget(normalizeIri(resolvedIri ?? ''), normalizedWanted);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function appendAssertionValue(ontology: any, assertion: any, objectValue: unknown): void {
|
|
836
|
+
if (isIriLike(objectValue)) {
|
|
837
|
+
const normalizedObjectIri = normalizeIri(asRequiredIri(objectValue, 'assertion object'));
|
|
838
|
+
ensureReferenceImport(ontology, normalizedObjectIri);
|
|
839
|
+
assertion.referencedValues ??= [];
|
|
840
|
+
assertion.referencedValues.push(asRef(ontology, normalizedObjectIri));
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
assertion.literalValues ??= [];
|
|
844
|
+
assertion.literalValues.push(asLiteral(objectValue));
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function removeAssertionValue(ontology: any, assertion: any, objectValue: unknown): void {
|
|
848
|
+
if (isIriLike(objectValue)) {
|
|
849
|
+
const normalizedIri = normalizeIri(asRequiredIri(objectValue, 'assertion object'));
|
|
850
|
+
assertion.referencedValues = (assertion.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
const normalizedLiteral = normalizeLiteralValue(asLiteral(objectValue));
|
|
854
|
+
assertion.literalValues = (assertion.literalValues ?? []).filter((literal: any) => normalizeLiteralValue(literal) !== normalizedLiteral);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function appendAnnotationValue(ontology: any, annotation: any, objectValue: unknown): void {
|
|
858
|
+
if (isIriLike(objectValue)) {
|
|
859
|
+
const normalizedObjectIri = normalizeIri(asRequiredIri(objectValue, 'annotation object'));
|
|
860
|
+
ensureReferenceImport(ontology, normalizedObjectIri);
|
|
861
|
+
annotation.referencedValues ??= [];
|
|
862
|
+
annotation.referencedValues.push(asRef(ontology, normalizedObjectIri));
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
annotation.literalValues ??= [];
|
|
866
|
+
annotation.literalValues.push(asLiteral(objectValue));
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function removeAnnotationValue(ontology: any, annotation: any, objectValue: unknown): void {
|
|
870
|
+
if (isIriLike(objectValue)) {
|
|
871
|
+
const normalizedIri = normalizeIri(asRequiredIri(objectValue, 'annotation object'));
|
|
872
|
+
annotation.referencedValues = (annotation.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const normalizedLiteral = normalizeLiteralValue(asLiteral(objectValue));
|
|
876
|
+
annotation.literalValues = (annotation.literalValues ?? []).filter((literal: any) => normalizeLiteralValue(literal) !== normalizedLiteral);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function normalizeLiteralValue(literal: any): string {
|
|
880
|
+
if (!literal || typeof literal !== 'object') {
|
|
881
|
+
return String(literal);
|
|
882
|
+
}
|
|
883
|
+
if (literal.$type === 'BooleanLiteral') {
|
|
884
|
+
return `bool:${literal.value ? 'true' : 'false'}`;
|
|
885
|
+
}
|
|
886
|
+
if (literal.$type === 'IntegerLiteral') {
|
|
887
|
+
return `int:${String(literal.value)}`;
|
|
888
|
+
}
|
|
889
|
+
if (literal.$type === 'DecimalLiteral') {
|
|
890
|
+
return `decimal:${String(literal.value)}`;
|
|
891
|
+
}
|
|
892
|
+
if (literal.$type === 'DoubleLiteral') {
|
|
893
|
+
return `double:${String(literal.value)}`;
|
|
894
|
+
}
|
|
895
|
+
return `quoted:${String(literal.value ?? '')}:${String(literal.langTag ?? '')}:${normalizeIri(literal.type?.$refText ?? '')}`;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function asRef(ontology: any, iri: string): any {
|
|
899
|
+
return { $refText: toRefText(ontology, iri) };
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function toRefText(ontology: any, iri: string): string {
|
|
903
|
+
const normalizedIri = normalizeIri(iri);
|
|
904
|
+
const localInCurrentOntology = getLocalNameForNamespace(normalizedIri, ontology?.namespace);
|
|
905
|
+
if (localInCurrentOntology) {
|
|
906
|
+
return localInCurrentOntology;
|
|
907
|
+
}
|
|
908
|
+
const imports = Array.isArray(ontology?.ownedImports) ? ontology.ownedImports : [];
|
|
909
|
+
for (const imp of imports) {
|
|
910
|
+
const prefix = resolveImportPrefix(imp);
|
|
911
|
+
if (!prefix) {
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
const localInImport = getLocalNameForNamespace(normalizedIri, resolveImportNamespaceRaw(imp));
|
|
915
|
+
if (localInImport) {
|
|
916
|
+
return `${prefix}:${localInImport}`;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return `<${normalizedIri}>`;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function getLocalNameForNamespace(iri: string, rawNamespace: string | undefined): string | undefined {
|
|
923
|
+
const namespace = normalizeNamespace(String(rawNamespace ?? ''));
|
|
924
|
+
if (!namespace) {
|
|
925
|
+
return undefined;
|
|
926
|
+
}
|
|
927
|
+
const hashPrefix = `${namespace}#`;
|
|
928
|
+
if (iri.startsWith(hashPrefix) && iri.length > hashPrefix.length) {
|
|
929
|
+
return iri.slice(hashPrefix.length);
|
|
930
|
+
}
|
|
931
|
+
const slashPrefix = `${namespace}/`;
|
|
932
|
+
if (iri.startsWith(slashPrefix) && iri.length > slashPrefix.length) {
|
|
933
|
+
return iri.slice(slashPrefix.length);
|
|
934
|
+
}
|
|
935
|
+
return undefined;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function resolveImportPrefix(imp: any): string | undefined {
|
|
939
|
+
const explicitPrefix = typeof imp?.prefix === 'string' ? imp.prefix.trim() : '';
|
|
940
|
+
if (explicitPrefix) {
|
|
941
|
+
return explicitPrefix;
|
|
942
|
+
}
|
|
943
|
+
const resolvedPrefix = typeof imp?.imported?.ref?.prefix === 'string' ? imp.imported.ref.prefix.trim() : '';
|
|
944
|
+
return resolvedPrefix || undefined;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function resolveImportNamespaceRaw(imp: any): string | undefined {
|
|
948
|
+
const resolvedNamespace = typeof imp?.imported?.ref?.namespace === 'string' ? imp.imported.ref.namespace : '';
|
|
949
|
+
if (resolvedNamespace.trim().length > 0) {
|
|
950
|
+
return resolvedNamespace.trim();
|
|
951
|
+
}
|
|
952
|
+
const refText = typeof imp?.imported?.$refText === 'string' ? imp.imported.$refText.trim() : '';
|
|
953
|
+
return refText || undefined;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function expandRefTextToIri(ontology: any, refText: string): string | undefined {
|
|
957
|
+
const trimmed = String(refText ?? '').trim();
|
|
958
|
+
if (!trimmed) {
|
|
959
|
+
return undefined;
|
|
960
|
+
}
|
|
961
|
+
if (trimmed === 'a') {
|
|
962
|
+
return RDF_TYPE_IRI;
|
|
963
|
+
}
|
|
964
|
+
if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
|
|
965
|
+
return normalizeIri(trimmed);
|
|
966
|
+
}
|
|
967
|
+
if (trimmed.includes('://')) {
|
|
968
|
+
return normalizeIri(trimmed);
|
|
969
|
+
}
|
|
970
|
+
const localInCurrentOntology = expandLocalRefText(ontology?.namespace, trimmed);
|
|
971
|
+
if (localInCurrentOntology) {
|
|
972
|
+
return localInCurrentOntology;
|
|
973
|
+
}
|
|
974
|
+
const separatorIndex = trimmed.indexOf(':');
|
|
975
|
+
if (separatorIndex <= 0) {
|
|
976
|
+
return undefined;
|
|
977
|
+
}
|
|
978
|
+
const prefix = trimmed.slice(0, separatorIndex);
|
|
979
|
+
const local = trimmed.slice(separatorIndex + 1);
|
|
980
|
+
if (!local) {
|
|
981
|
+
return undefined;
|
|
982
|
+
}
|
|
983
|
+
if (typeof ontology?.prefix === 'string' && ontology.prefix.trim() === prefix) {
|
|
984
|
+
return expandLocalRefText(ontology.namespace, local);
|
|
985
|
+
}
|
|
986
|
+
const imports = Array.isArray(ontology?.ownedImports) ? ontology.ownedImports : [];
|
|
987
|
+
for (const imp of imports) {
|
|
988
|
+
if (resolveImportPrefix(imp) !== prefix) {
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
return expandLocalRefText(resolveImportNamespaceRaw(imp), local);
|
|
992
|
+
}
|
|
993
|
+
return undefined;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function expandLocalRefText(rawNamespace: string | undefined, localName: string): string | undefined {
|
|
997
|
+
const namespaceText = String(rawNamespace ?? '').trim();
|
|
998
|
+
const local = String(localName ?? '').trim();
|
|
999
|
+
if (!namespaceText || !local) {
|
|
1000
|
+
return undefined;
|
|
1001
|
+
}
|
|
1002
|
+
const namespace = normalizeIri(namespaceText);
|
|
1003
|
+
if (namespace.endsWith('#') || namespace.endsWith('/')) {
|
|
1004
|
+
return `${namespace}${local}`;
|
|
1005
|
+
}
|
|
1006
|
+
return `${namespace}#${local}`;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function ensureReferenceImport(ontology: any, iri: string): void {
|
|
1010
|
+
const target = parseIriParts(iri);
|
|
1011
|
+
if (!target) {
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
const localNamespace = normalizeNamespace(String(ontology?.namespace ?? ''));
|
|
1015
|
+
if (!localNamespace || target.namespace === localNamespace) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
const imports = Array.isArray(ontology?.ownedImports) ? ontology.ownedImports : [];
|
|
1019
|
+
const hasImport = imports.some((imp: any) => {
|
|
1020
|
+
const importedNamespace = normalizeNamespace(String(resolveImportNamespaceRaw(imp) ?? ''));
|
|
1021
|
+
return importedNamespace === target.namespace;
|
|
1022
|
+
});
|
|
1023
|
+
if (hasImport) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const usedPrefixes = new Set<string>();
|
|
1027
|
+
const rootPrefix = typeof ontology?.prefix === 'string' ? ontology.prefix.trim() : '';
|
|
1028
|
+
if (rootPrefix) {
|
|
1029
|
+
usedPrefixes.add(rootPrefix);
|
|
1030
|
+
}
|
|
1031
|
+
for (const imp of imports) {
|
|
1032
|
+
const prefix = resolveImportPrefix(imp);
|
|
1033
|
+
if (prefix) {
|
|
1034
|
+
usedPrefixes.add(prefix);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const prefix = pickImportPrefix(target.namespace, usedPrefixes);
|
|
1038
|
+
const importedRef = `<${target.namespace}${target.separator}>`;
|
|
1039
|
+
const importStatement: any = {
|
|
1040
|
+
$type: 'Import',
|
|
1041
|
+
kind: 'extends',
|
|
1042
|
+
prefix,
|
|
1043
|
+
imported: { $refText: importedRef }
|
|
1044
|
+
};
|
|
1045
|
+
importStatement.$container = ontology;
|
|
1046
|
+
if (!Array.isArray(ontology.ownedImports)) {
|
|
1047
|
+
ontology.ownedImports = [];
|
|
1048
|
+
}
|
|
1049
|
+
ontology.ownedImports.push(importStatement);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function parseIriParts(iri: string): { namespace: string; fragment: string; separator: '#' | '/' } | undefined {
|
|
1053
|
+
const normalized = normalizeIri(iri);
|
|
1054
|
+
if (!normalized) {
|
|
1055
|
+
return undefined;
|
|
1056
|
+
}
|
|
1057
|
+
const hashIndex = normalized.lastIndexOf('#');
|
|
1058
|
+
if (hashIndex > 0 && hashIndex < normalized.length - 1) {
|
|
1059
|
+
return {
|
|
1060
|
+
namespace: normalizeNamespace(normalized.slice(0, hashIndex)),
|
|
1061
|
+
fragment: normalized.slice(hashIndex + 1),
|
|
1062
|
+
separator: '#',
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
const slashIndex = normalized.lastIndexOf('/');
|
|
1066
|
+
if (slashIndex > 0 && slashIndex < normalized.length - 1) {
|
|
1067
|
+
return {
|
|
1068
|
+
namespace: normalizeNamespace(normalized.slice(0, slashIndex)),
|
|
1069
|
+
fragment: normalized.slice(slashIndex + 1),
|
|
1070
|
+
separator: '/',
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
return undefined;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function getRefText(ref: any): string | undefined {
|
|
1077
|
+
if (typeof ref === 'string') {
|
|
1078
|
+
return ref;
|
|
1079
|
+
}
|
|
1080
|
+
if (typeof ref?.$refText === 'string') {
|
|
1081
|
+
return ref.$refText;
|
|
1082
|
+
}
|
|
1083
|
+
if (typeof ref?.$refNode?.text === 'string') {
|
|
1084
|
+
return ref.$refNode.text;
|
|
1085
|
+
}
|
|
1086
|
+
return undefined;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function matchesRefTextTarget(
|
|
1090
|
+
ontology: any,
|
|
1091
|
+
statement: any,
|
|
1092
|
+
namespace: string,
|
|
1093
|
+
fragment: string
|
|
1094
|
+
): boolean {
|
|
1095
|
+
const refText = getRefText(statement?.ref);
|
|
1096
|
+
if (!refText) {
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
const expanded = expandRefTextToIri(ontology, refText);
|
|
1100
|
+
if (expanded) {
|
|
1101
|
+
return isSameIriTarget(expanded, `${namespace}#${fragment}`);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const trimmed = refText.trim();
|
|
1105
|
+
if (!trimmed) {
|
|
1106
|
+
return false;
|
|
1107
|
+
}
|
|
1108
|
+
const separatorIndex = trimmed.indexOf(':');
|
|
1109
|
+
if (separatorIndex > 0) {
|
|
1110
|
+
const prefix = trimmed.slice(0, separatorIndex);
|
|
1111
|
+
const local = trimmed.slice(separatorIndex + 1);
|
|
1112
|
+
if (local !== fragment) {
|
|
1113
|
+
return false;
|
|
1114
|
+
}
|
|
1115
|
+
if (typeof ontology?.prefix === 'string' && ontology.prefix.trim() === prefix) {
|
|
1116
|
+
return normalizeNamespace(String(ontology?.namespace ?? '')) === namespace;
|
|
1117
|
+
}
|
|
1118
|
+
const imports = Array.isArray(ontology?.ownedImports) ? ontology.ownedImports : [];
|
|
1119
|
+
for (const imp of imports) {
|
|
1120
|
+
if (resolveImportPrefix(imp) !== prefix) {
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
const importedNamespace = normalizeNamespace(String(resolveImportNamespaceRaw(imp) ?? ''));
|
|
1124
|
+
return importedNamespace === namespace;
|
|
1125
|
+
}
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
const localInCurrent = trimmed.replace(/^<|>$/g, '');
|
|
1130
|
+
if (localInCurrent !== fragment) {
|
|
1131
|
+
return false;
|
|
1132
|
+
}
|
|
1133
|
+
return normalizeNamespace(String(ontology?.namespace ?? '')) === namespace;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function matchesRefTextFragment(statement: any, fragment: string): boolean {
|
|
1137
|
+
if (!fragment) {
|
|
1138
|
+
return false;
|
|
1139
|
+
}
|
|
1140
|
+
const refText = getRefText(statement?.ref);
|
|
1141
|
+
if (!refText) {
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
const local = localNameFromRefText(refText);
|
|
1145
|
+
return !!local && local === fragment;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function localNameFromRefText(refText: string): string | undefined {
|
|
1149
|
+
const trimmed = refText.trim().replace(/^<|>$/g, '');
|
|
1150
|
+
if (!trimmed) {
|
|
1151
|
+
return undefined;
|
|
1152
|
+
}
|
|
1153
|
+
const colonIndex = trimmed.indexOf(':');
|
|
1154
|
+
if (colonIndex > 0 && colonIndex < trimmed.length - 1) {
|
|
1155
|
+
return trimmed.slice(colonIndex + 1);
|
|
1156
|
+
}
|
|
1157
|
+
const hashIndex = trimmed.lastIndexOf('#');
|
|
1158
|
+
if (hashIndex >= 0 && hashIndex < trimmed.length - 1) {
|
|
1159
|
+
return trimmed.slice(hashIndex + 1);
|
|
1160
|
+
}
|
|
1161
|
+
const slashIndex = trimmed.lastIndexOf('/');
|
|
1162
|
+
if (slashIndex >= 0 && slashIndex < trimmed.length - 1) {
|
|
1163
|
+
return trimmed.slice(slashIndex + 1);
|
|
1164
|
+
}
|
|
1165
|
+
return trimmed;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function isSameIriTarget(left: string, right: string): boolean {
|
|
1169
|
+
const leftNorm = normalizeIri(left);
|
|
1170
|
+
const rightNorm = normalizeIri(right);
|
|
1171
|
+
if (leftNorm === rightNorm) {
|
|
1172
|
+
return true;
|
|
1173
|
+
}
|
|
1174
|
+
const leftParts = parseIriParts(leftNorm);
|
|
1175
|
+
const rightParts = parseIriParts(rightNorm);
|
|
1176
|
+
if (!leftParts || !rightParts) {
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
return leftParts.namespace === rightParts.namespace
|
|
1180
|
+
&& leftParts.fragment === rightParts.fragment;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
function pickImportPrefix(namespace: string, usedPrefixes: ReadonlySet<string>): string {
|
|
1184
|
+
const normalizedNamespace = normalizeNamespace(namespace);
|
|
1185
|
+
const pieces = normalizedNamespace.split('/').filter((piece) => piece.length > 0);
|
|
1186
|
+
const tail = (pieces[pieces.length - 1] ?? normalizedNamespace)
|
|
1187
|
+
.replace(/[^A-Za-z0-9_]/g, '')
|
|
1188
|
+
.toLowerCase();
|
|
1189
|
+
const base = /^[A-Za-z_]/.test(tail) ? tail : `ns${tail}`;
|
|
1190
|
+
const candidateBase = (base || 'ns').slice(0, 32);
|
|
1191
|
+
if (!usedPrefixes.has(candidateBase)) {
|
|
1192
|
+
return candidateBase;
|
|
1193
|
+
}
|
|
1194
|
+
let suffix = 1;
|
|
1195
|
+
while (usedPrefixes.has(`${candidateBase}${suffix}`)) {
|
|
1196
|
+
suffix += 1;
|
|
1197
|
+
}
|
|
1198
|
+
return `${candidateBase}${suffix}`;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function asLiteral(value: unknown): any {
|
|
1202
|
+
if (typeof value === 'boolean') {
|
|
1203
|
+
return { $type: 'BooleanLiteral', value };
|
|
1204
|
+
}
|
|
1205
|
+
if (typeof value === 'number' && Number.isInteger(value)) {
|
|
1206
|
+
return { $type: 'IntegerLiteral', value };
|
|
1207
|
+
}
|
|
1208
|
+
if (typeof value === 'number') {
|
|
1209
|
+
return { $type: 'DecimalLiteral', value };
|
|
1210
|
+
}
|
|
1211
|
+
return { $type: 'QuotedLiteral', value: String(value ?? '') };
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function asRequiredIri(value: unknown, context: string): string {
|
|
1215
|
+
if (!isIriLike(value)) {
|
|
1216
|
+
throw new Error(`Expected IRI for ${context}.`);
|
|
1217
|
+
}
|
|
1218
|
+
return String(value);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function isIriLike(value: unknown): boolean {
|
|
1222
|
+
if (typeof value !== 'string') {
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
const trimmed = value.trim();
|
|
1226
|
+
if (!trimmed) {
|
|
1227
|
+
return false;
|
|
1228
|
+
}
|
|
1229
|
+
if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
|
|
1230
|
+
return true;
|
|
1231
|
+
}
|
|
1232
|
+
return trimmed.includes('://');
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function normalizeIri(value: string): string {
|
|
1236
|
+
return String(value ?? '').trim().replace(/^<|>$/g, '');
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function isSourcePredicate(predicateIri: string): boolean {
|
|
1240
|
+
return normalizeIri(predicateIri) === OML_HAS_SOURCE_IRI;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function isTargetPredicate(predicateIri: string): boolean {
|
|
1244
|
+
return normalizeIri(predicateIri) === OML_HAS_TARGET_IRI;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function buildWorkspaceEdit(
|
|
1248
|
+
contexts: Map<string, OntologyContext>,
|
|
1249
|
+
changedOntologyIris: ReadonlySet<string>
|
|
1250
|
+
): WorkspaceEdit | undefined {
|
|
1251
|
+
if (changedOntologyIris.size === 0) {
|
|
1252
|
+
return undefined;
|
|
1253
|
+
}
|
|
1254
|
+
const changes: NonNullable<WorkspaceEdit['changes']> = {};
|
|
1255
|
+
for (const ontologyIri of [...changedOntologyIris].sort()) {
|
|
1256
|
+
const context = contexts.get(ontologyIri);
|
|
1257
|
+
if (!context) {
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
const serialized = serializeOntology(context.ontology);
|
|
1261
|
+
const textDocument = context.document?.textDocument;
|
|
1262
|
+
if (!textDocument) {
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
const end = textDocument.positionAt(textDocument.getText().length);
|
|
1266
|
+
changes[context.modelUri] = [{
|
|
1267
|
+
range: {
|
|
1268
|
+
start: { line: 0, character: 0 },
|
|
1269
|
+
end: { line: end.line, character: end.character }
|
|
1270
|
+
},
|
|
1271
|
+
newText: serialized
|
|
1272
|
+
}];
|
|
1273
|
+
}
|
|
1274
|
+
return Object.keys(changes).length > 0 ? { changes } : undefined;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
async function restoreContextsFromDocuments(shared: any, contexts: Map<string, OntologyContext>): Promise<void> {
|
|
1278
|
+
const uris = [...new Set([...contexts.values()].map((context) => URI.parse(context.modelUri)))];
|
|
1279
|
+
if (uris.length > 0) {
|
|
1280
|
+
await shared.workspace.DocumentBuilder.update(uris, []);
|
|
1281
|
+
}
|
|
1282
|
+
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
1283
|
+
for (const context of contexts.values()) {
|
|
1284
|
+
const refreshed = langiumDocuments.getDocument(URI.parse(context.modelUri))
|
|
1285
|
+
?? await langiumDocuments.getOrCreateDocument(URI.parse(context.modelUri));
|
|
1286
|
+
const refreshedOntology = refreshed?.parseResult?.value;
|
|
1287
|
+
if (refreshedOntology && isOntology(refreshedOntology)) {
|
|
1288
|
+
context.document = refreshed;
|
|
1289
|
+
context.ontology = refreshedOntology;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|