@oml/language 0.14.17 → 0.15.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/out/oml/generated/ast.d.ts +1 -1
- package/out/oml/generated/ast.js +1 -1
- package/out/oml/generated/grammar.d.ts +1 -1
- package/out/oml/generated/grammar.js +1 -1
- package/out/oml/generated/module.d.ts +1 -1
- package/out/oml/generated/module.js +1 -1
- package/out/oml/oml-candidates.js +1 -42
- package/out/oml/oml-candidates.js.map +1 -1
- package/out/oml/oml-document.js +1 -5
- package/out/oml/oml-document.js.map +1 -1
- package/out/oml/oml-index.d.ts +1 -0
- package/out/oml/oml-index.js +8 -1
- package/out/oml/oml-index.js.map +1 -1
- package/out/oml/oml-scope.js +10 -4
- package/out/oml/oml-scope.js.map +1 -1
- package/out/oml/oml-update.d.ts +32 -15
- package/out/oml/oml-update.js +393 -199
- package/out/oml/oml-update.js.map +1 -1
- package/out/oml/oml-utils.d.ts +18 -0
- package/out/oml/oml-utils.js +183 -26
- package/out/oml/oml-utils.js.map +1 -1
- package/out/oml/oml-workspace.js +1 -4
- package/out/oml/oml-workspace.js.map +1 -1
- package/package.json +1 -1
- package/src/oml/generated/ast.ts +1 -1
- package/src/oml/generated/grammar.ts +1 -1
- package/src/oml/generated/module.ts +1 -1
- package/src/oml/oml-candidates.ts +1 -46
- package/src/oml/oml-document.ts +1 -5
- package/src/oml/oml-index.ts +9 -1
- package/src/oml/oml-scope.ts +10 -4
- package/src/oml/oml-update.ts +459 -223
- package/src/oml/oml-utils.ts +199 -26
- package/src/oml/oml-workspace.ts +1 -4
package/src/oml/oml-update.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
isAnnotation,
|
|
7
7
|
isConceptInstance,
|
|
8
8
|
isDescription,
|
|
9
|
+
isDescriptionBundle,
|
|
9
10
|
isDescriptionBox,
|
|
10
11
|
isImport,
|
|
11
12
|
isOntology,
|
|
@@ -20,9 +21,20 @@ import { getOntologyModelIndex } from './oml-index.js';
|
|
|
20
21
|
import { serializeOntology } from './oml-serializer.js';
|
|
21
22
|
import {
|
|
22
23
|
collectOntologyMembers,
|
|
24
|
+
expandRefTextToIri,
|
|
23
25
|
findOntologyMemberByName,
|
|
26
|
+
getLocalNameForNamespace,
|
|
27
|
+
getRefText,
|
|
24
28
|
getIriForNode,
|
|
29
|
+
isSameIriTarget,
|
|
30
|
+
localNameFromRefText,
|
|
31
|
+
normalizeIri,
|
|
25
32
|
normalizeNamespace,
|
|
33
|
+
parseIriParts,
|
|
34
|
+
pickImportPrefix,
|
|
35
|
+
resolveImportNamespaceRaw,
|
|
36
|
+
resolveImportPrefix,
|
|
37
|
+
withNamespaceSeparator,
|
|
26
38
|
} from './oml-utils.js';
|
|
27
39
|
|
|
28
40
|
type ConnectionLike = {
|
|
@@ -32,56 +44,122 @@ type ConnectionLike = {
|
|
|
32
44
|
};
|
|
33
45
|
};
|
|
34
46
|
|
|
35
|
-
const
|
|
47
|
+
const OmlUpdateRequestType = new RequestType<OmlUpdateRequest, OmlUpdateResponse, void>('oml/update');
|
|
36
48
|
const RDF_TYPE_IRI = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
37
49
|
const OML_HAS_SOURCE_IRI = 'http://opencaesar.io/oml#hasSource';
|
|
38
50
|
const OML_HAS_TARGET_IRI = 'http://opencaesar.io/oml#hasTarget';
|
|
39
51
|
const XSD_STRING_IRI = 'http://www.w3.org/2001/XMLSchema#string';
|
|
40
52
|
|
|
41
|
-
type
|
|
42
|
-
| { kind: 'createInstance';
|
|
43
|
-
| { kind: 'createRelationInstance';
|
|
44
|
-
| { kind: 'createInstanceRef';
|
|
45
|
-
| { kind: 'createRelationInstanceRef';
|
|
46
|
-
| { kind: 'addAssertion';
|
|
47
|
-
| { kind: 'updateAssertion';
|
|
48
|
-
| { kind: 'removeAssertion';
|
|
53
|
+
type OmlUpdateOperation =
|
|
54
|
+
| { kind: 'createInstance'; descriptionIri: string; instanceName: string }
|
|
55
|
+
| { kind: 'createRelationInstance'; descriptionIri: string; instanceName: string }
|
|
56
|
+
| { kind: 'createInstanceRef'; descriptionIri: string; instanceIri: string; typeIri?: string }
|
|
57
|
+
| { kind: 'createRelationInstanceRef'; descriptionIri: string; instanceIri: string; typeIri?: string }
|
|
58
|
+
| { kind: 'addAssertion'; descriptionIri: string; subjectIri: string; predicateIri: string; object: unknown }
|
|
59
|
+
| { kind: 'updateAssertion'; descriptionIri: string; subjectIri: string; predicateIri: string; object: unknown }
|
|
60
|
+
| { kind: 'removeAssertion'; descriptionIri: string; subjectIri: string; predicateIri: string; object?: unknown }
|
|
49
61
|
| { kind: 'addAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
|
|
50
62
|
| { kind: 'updateAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
|
|
51
63
|
| { kind: 'removeAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object?: unknown }
|
|
52
64
|
| { kind: 'deleteMemberCascade'; ontologyIri: string; memberIri: string }
|
|
53
|
-
| { kind: 'deleteMemberRef'; ontologyIri: string; memberIri: string; typeIri?: string }
|
|
65
|
+
| { kind: 'deleteMemberRef'; ontologyIri: string; memberIri: string; typeIri?: string }
|
|
66
|
+
| { kind: 'deleteOntology'; ontologyIri: string }
|
|
67
|
+
| { kind: 'addImport'; importingIri: string; importedIri: string }
|
|
68
|
+
| { kind: 'removeImport'; importingIri: string; importedIri: string }
|
|
69
|
+
| {
|
|
70
|
+
kind: 'createOntology';
|
|
71
|
+
ontologyKind: 'vocabulary' | 'description' | 'vocabulary bundle' | 'description bundle';
|
|
72
|
+
ontologyNamespace: string;
|
|
73
|
+
ontologyPrefix: string;
|
|
74
|
+
targetFolder?: string;
|
|
75
|
+
};
|
|
54
76
|
|
|
55
|
-
export type
|
|
56
|
-
operations:
|
|
77
|
+
export type OmlUpdateRequest = {
|
|
78
|
+
operations: OmlUpdateOperation[];
|
|
57
79
|
referencingUri?: string;
|
|
58
80
|
};
|
|
59
81
|
|
|
60
|
-
export type
|
|
82
|
+
export type OmlUpdateError = {
|
|
61
83
|
operationIndex: number;
|
|
62
84
|
message: string;
|
|
63
85
|
};
|
|
64
86
|
|
|
65
|
-
export type
|
|
87
|
+
export type OmlUpdateResponse = {
|
|
66
88
|
edit?: WorkspaceEdit;
|
|
67
|
-
errors?:
|
|
89
|
+
errors?: OmlUpdateError[];
|
|
68
90
|
};
|
|
69
91
|
|
|
70
92
|
export async function applyOmlUpdate(
|
|
71
93
|
shared: any,
|
|
72
|
-
params:
|
|
94
|
+
params: OmlUpdateRequest,
|
|
73
95
|
logError?: (message: string) => void,
|
|
74
|
-
|
|
96
|
+
workspaceUri?: string,
|
|
97
|
+
preview?: boolean,
|
|
98
|
+
): Promise<OmlUpdateResponse> {
|
|
75
99
|
const operations = Array.isArray(params?.operations) ? params.operations : [];
|
|
76
|
-
const referencingUri = typeof params?.referencingUri === 'string'
|
|
77
|
-
? params.referencingUri.trim()
|
|
78
|
-
: undefined;
|
|
79
100
|
const contexts = new Map<string, OntologyContext>();
|
|
80
101
|
const changedOntologyIris = new Set<string>();
|
|
102
|
+
const createdOntologyByIri = new Map<string, CreatedOntology>();
|
|
103
|
+
const deletedOntologyModelUriByIri = new Map<string, string>();
|
|
81
104
|
for (let i = 0; i < operations.length; i += 1) {
|
|
82
105
|
const operation = operations[i];
|
|
83
106
|
try {
|
|
84
|
-
|
|
107
|
+
if (operation.kind === 'createOntology') {
|
|
108
|
+
const created = await createOntologyOperation(shared, operation, workspaceUri);
|
|
109
|
+
contexts.set(created.ontologyIri, {
|
|
110
|
+
ontologyIri: created.ontologyIri,
|
|
111
|
+
modelUri: created.modelUri,
|
|
112
|
+
ontology: created.ontology,
|
|
113
|
+
document: undefined
|
|
114
|
+
});
|
|
115
|
+
createdOntologyByIri.set(created.ontologyIri, created);
|
|
116
|
+
changedOntologyIris.add(created.ontologyIri);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (operation.kind === 'deleteOntology') {
|
|
120
|
+
const normalizedOntologyIri = normalizeOntologyIriKey(operation.ontologyIri);
|
|
121
|
+
if (createdOntologyByIri.has(normalizedOntologyIri)) {
|
|
122
|
+
createdOntologyByIri.delete(normalizedOntologyIri);
|
|
123
|
+
contexts.delete(normalizedOntologyIri);
|
|
124
|
+
changedOntologyIris.delete(normalizedOntologyIri);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const existingContext = contexts.get(normalizedOntologyIri);
|
|
128
|
+
if (existingContext) {
|
|
129
|
+
deletedOntologyModelUriByIri.set(existingContext.ontologyIri, existingContext.modelUri);
|
|
130
|
+
contexts.delete(existingContext.ontologyIri);
|
|
131
|
+
changedOntologyIris.delete(existingContext.ontologyIri);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const modelUri = resolveModelUriFromLoadedDocuments(shared, normalizedOntologyIri);
|
|
135
|
+
if (!modelUri) {
|
|
136
|
+
// Idempotent delete: if ontology is already absent, treat as successful no-op.
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
deletedOntologyModelUriByIri.set(normalizedOntologyIri, modelUri);
|
|
140
|
+
contexts.delete(normalizedOntologyIri);
|
|
141
|
+
changedOntologyIris.delete(normalizedOntologyIri);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (operation.kind === 'addImport') {
|
|
145
|
+
const importing = await resolveOntologyContext(shared, operation.importingIri, contexts, workspaceUri);
|
|
146
|
+
const imported = await resolveOntologyContext(shared, operation.importedIri, contexts, workspaceUri);
|
|
147
|
+
const changed = await addImport(shared, importing.ontology, imported.ontology);
|
|
148
|
+
if (changed) {
|
|
149
|
+
changedOntologyIris.add(importing.ontologyIri);
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (operation.kind === 'removeImport') {
|
|
154
|
+
const importing = await resolveOntologyContext(shared, operation.importingIri, contexts, workspaceUri);
|
|
155
|
+
const imported = await resolveOntologyContext(shared, operation.importedIri, contexts, workspaceUri);
|
|
156
|
+
const changed = removeImport(importing.ontology, imported.ontology);
|
|
157
|
+
if (changed) {
|
|
158
|
+
changedOntologyIris.add(importing.ontologyIri);
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const context = await resolveOntologyContext(shared, getOperationTargetOntologyIri(operation), contexts, workspaceUri);
|
|
85
163
|
const changed = await executeOperation(shared, context, operation, contexts);
|
|
86
164
|
for (const ontologyIri of changed) {
|
|
87
165
|
changedOntologyIris.add(ontologyIri);
|
|
@@ -92,7 +170,7 @@ export async function applyOmlUpdate(
|
|
|
92
170
|
const details = error instanceof Error && error.stack
|
|
93
171
|
? `${message}\n${error.stack}`
|
|
94
172
|
: message;
|
|
95
|
-
logError?.(`[oml]
|
|
173
|
+
logError?.(`[oml] OmlUpdateRequest failed at operation ${i} (${operation.kind}): ${details}`);
|
|
96
174
|
return {
|
|
97
175
|
errors: [{
|
|
98
176
|
operationIndex: i,
|
|
@@ -101,13 +179,40 @@ export async function applyOmlUpdate(
|
|
|
101
179
|
};
|
|
102
180
|
}
|
|
103
181
|
}
|
|
104
|
-
const edit = buildWorkspaceEdit(contexts, changedOntologyIris);
|
|
182
|
+
const edit = buildWorkspaceEdit(contexts, changedOntologyIris, createdOntologyByIri, deletedOntologyModelUriByIri);
|
|
183
|
+
if (preview) {
|
|
184
|
+
const existingContexts = new Map([...contexts.entries()].filter(([iri]) => !createdOntologyByIri.has(iri)));
|
|
185
|
+
await restoreContextsFromDocuments(shared, existingContexts);
|
|
186
|
+
}
|
|
105
187
|
return edit ? { edit } : {};
|
|
106
188
|
}
|
|
107
189
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
190
|
+
function resolveModelUriFromLoadedDocuments(shared: any, ontologyIri: string): string | undefined {
|
|
191
|
+
const normalized = normalizeOntologyIriKey(ontologyIri);
|
|
192
|
+
const documentsService: any = shared.workspace.LangiumDocuments;
|
|
193
|
+
const all = documentsService?.all ?? [];
|
|
194
|
+
const iterable: any[] = Array.isArray(all)
|
|
195
|
+
? all
|
|
196
|
+
: (typeof all?.toArray === 'function' ? all.toArray() : Array.from(all as Iterable<any>));
|
|
197
|
+
for (const document of iterable) {
|
|
198
|
+
const root = document?.parseResult?.value;
|
|
199
|
+
if (!root || !isOntology(root)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const namespace = normalizeNamespace(String((root as any).namespace ?? ''));
|
|
203
|
+
if (!namespace) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (normalizeOntologyIriKey(namespace) === normalized) {
|
|
207
|
+
return String(document.uri);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function registerOmlUpdateRequests(connection: ConnectionLike, shared: any): void {
|
|
214
|
+
connection.onRequest(OmlUpdateRequestType, async (params: OmlUpdateRequest): Promise<OmlUpdateResponse> => {
|
|
215
|
+
return await applyOmlUpdate(shared, params, (message) => connection.console?.error(message), params.referencingUri);
|
|
111
216
|
});
|
|
112
217
|
}
|
|
113
218
|
|
|
@@ -118,13 +223,34 @@ type OntologyContext = {
|
|
|
118
223
|
document: any;
|
|
119
224
|
};
|
|
120
225
|
|
|
226
|
+
function getOperationTargetOntologyIri(operation: OmlUpdateOperation): string {
|
|
227
|
+
switch (operation.kind) {
|
|
228
|
+
case 'createInstance':
|
|
229
|
+
case 'createRelationInstance':
|
|
230
|
+
case 'createInstanceRef':
|
|
231
|
+
case 'createRelationInstanceRef':
|
|
232
|
+
case 'addAssertion':
|
|
233
|
+
case 'updateAssertion':
|
|
234
|
+
case 'removeAssertion':
|
|
235
|
+
return operation.descriptionIri;
|
|
236
|
+
case 'addAnnotation':
|
|
237
|
+
case 'updateAnnotation':
|
|
238
|
+
case 'removeAnnotation':
|
|
239
|
+
case 'deleteMemberCascade':
|
|
240
|
+
case 'deleteMemberRef':
|
|
241
|
+
return operation.ontologyIri;
|
|
242
|
+
default:
|
|
243
|
+
throw new Error(`Unsupported operation target resolution for '${(operation as any).kind}'.`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
121
247
|
async function resolveOntologyContext(
|
|
122
248
|
shared: any,
|
|
123
249
|
ontologyIri: string,
|
|
124
250
|
cache: Map<string, OntologyContext>,
|
|
125
251
|
referencingUri?: string
|
|
126
252
|
): Promise<OntologyContext> {
|
|
127
|
-
const normalizedOntologyIri =
|
|
253
|
+
const normalizedOntologyIri = normalizeOntologyIriKey(ontologyIri);
|
|
128
254
|
const cached = cache.get(normalizedOntologyIri);
|
|
129
255
|
if (cached) {
|
|
130
256
|
return cached;
|
|
@@ -157,7 +283,7 @@ async function resolveOntologyContext(
|
|
|
157
283
|
async function executeOperation(
|
|
158
284
|
shared: any,
|
|
159
285
|
context: OntologyContext,
|
|
160
|
-
operation:
|
|
286
|
+
operation: OmlUpdateOperation,
|
|
161
287
|
contexts: Map<string, OntologyContext>
|
|
162
288
|
): Promise<ReadonlySet<string>> {
|
|
163
289
|
switch (operation.kind) {
|
|
@@ -1064,93 +1190,6 @@ function toRefText(ontology: any, iri: string): string {
|
|
|
1064
1190
|
return `<${normalizedIri}>`;
|
|
1065
1191
|
}
|
|
1066
1192
|
|
|
1067
|
-
function getLocalNameForNamespace(iri: string, rawNamespace: string | undefined): string | undefined {
|
|
1068
|
-
const namespace = normalizeNamespace(String(rawNamespace ?? ''));
|
|
1069
|
-
if (!namespace) {
|
|
1070
|
-
return undefined;
|
|
1071
|
-
}
|
|
1072
|
-
const hashPrefix = `${namespace}#`;
|
|
1073
|
-
if (iri.startsWith(hashPrefix) && iri.length > hashPrefix.length) {
|
|
1074
|
-
return iri.slice(hashPrefix.length);
|
|
1075
|
-
}
|
|
1076
|
-
const slashPrefix = `${namespace}/`;
|
|
1077
|
-
if (iri.startsWith(slashPrefix) && iri.length > slashPrefix.length) {
|
|
1078
|
-
return iri.slice(slashPrefix.length);
|
|
1079
|
-
}
|
|
1080
|
-
return undefined;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
function resolveImportPrefix(imp: any): string | undefined {
|
|
1084
|
-
const explicitPrefix = typeof imp?.prefix === 'string' ? imp.prefix.trim() : '';
|
|
1085
|
-
if (explicitPrefix) {
|
|
1086
|
-
return explicitPrefix;
|
|
1087
|
-
}
|
|
1088
|
-
const resolvedPrefix = typeof imp?.imported?.ref?.prefix === 'string' ? imp.imported.ref.prefix.trim() : '';
|
|
1089
|
-
return resolvedPrefix || undefined;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
function resolveImportNamespaceRaw(imp: any): string | undefined {
|
|
1093
|
-
const resolvedNamespace = typeof imp?.imported?.ref?.namespace === 'string' ? imp.imported.ref.namespace : '';
|
|
1094
|
-
if (resolvedNamespace.trim().length > 0) {
|
|
1095
|
-
return resolvedNamespace.trim();
|
|
1096
|
-
}
|
|
1097
|
-
const refText = typeof imp?.imported?.$refText === 'string' ? imp.imported.$refText.trim() : '';
|
|
1098
|
-
return refText || undefined;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
function expandRefTextToIri(ontology: any, refText: string): string | undefined {
|
|
1102
|
-
const trimmed = String(refText ?? '').trim();
|
|
1103
|
-
if (!trimmed) {
|
|
1104
|
-
return undefined;
|
|
1105
|
-
}
|
|
1106
|
-
if (trimmed === 'a') {
|
|
1107
|
-
return RDF_TYPE_IRI;
|
|
1108
|
-
}
|
|
1109
|
-
if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
|
|
1110
|
-
return normalizeIri(trimmed);
|
|
1111
|
-
}
|
|
1112
|
-
if (trimmed.includes('://')) {
|
|
1113
|
-
return normalizeIri(trimmed);
|
|
1114
|
-
}
|
|
1115
|
-
const localInCurrentOntology = expandLocalRefText(ontology?.namespace, trimmed);
|
|
1116
|
-
if (localInCurrentOntology) {
|
|
1117
|
-
return localInCurrentOntology;
|
|
1118
|
-
}
|
|
1119
|
-
const separatorIndex = trimmed.indexOf(':');
|
|
1120
|
-
if (separatorIndex <= 0) {
|
|
1121
|
-
return undefined;
|
|
1122
|
-
}
|
|
1123
|
-
const prefix = trimmed.slice(0, separatorIndex);
|
|
1124
|
-
const local = trimmed.slice(separatorIndex + 1);
|
|
1125
|
-
if (!local) {
|
|
1126
|
-
return undefined;
|
|
1127
|
-
}
|
|
1128
|
-
if (typeof ontology?.prefix === 'string' && ontology.prefix.trim() === prefix) {
|
|
1129
|
-
return expandLocalRefText(ontology.namespace, local);
|
|
1130
|
-
}
|
|
1131
|
-
const imports = Array.isArray(ontology?.ownedImports) ? ontology.ownedImports : [];
|
|
1132
|
-
for (const imp of imports) {
|
|
1133
|
-
if (resolveImportPrefix(imp) !== prefix) {
|
|
1134
|
-
continue;
|
|
1135
|
-
}
|
|
1136
|
-
return expandLocalRefText(resolveImportNamespaceRaw(imp), local);
|
|
1137
|
-
}
|
|
1138
|
-
return undefined;
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
function expandLocalRefText(rawNamespace: string | undefined, localName: string): string | undefined {
|
|
1142
|
-
const namespaceText = String(rawNamespace ?? '').trim();
|
|
1143
|
-
const local = String(localName ?? '').trim();
|
|
1144
|
-
if (!namespaceText || !local) {
|
|
1145
|
-
return undefined;
|
|
1146
|
-
}
|
|
1147
|
-
const namespace = normalizeIri(namespaceText);
|
|
1148
|
-
if (namespace.endsWith('#') || namespace.endsWith('/')) {
|
|
1149
|
-
return `${namespace}${local}`;
|
|
1150
|
-
}
|
|
1151
|
-
return `${namespace}#${local}`;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
1193
|
async function ensureReferenceImport(shared: any, ontology: any, iri: string): Promise<void> {
|
|
1155
1194
|
const target = parseIriParts(iri);
|
|
1156
1195
|
if (!target) {
|
|
@@ -1161,23 +1200,28 @@ async function ensureReferenceImport(shared: any, ontology: any, iri: string): P
|
|
|
1161
1200
|
return;
|
|
1162
1201
|
}
|
|
1163
1202
|
const imports = Array.isArray(ontology?.ownedImports) ? ontology.ownedImports : [];
|
|
1164
|
-
const hasImport = imports.some((imp: any) => {
|
|
1165
|
-
const importedNamespace = normalizeNamespace(String(resolveImportNamespaceRaw(imp) ?? ''));
|
|
1166
|
-
return importedNamespace === target.namespace;
|
|
1167
|
-
});
|
|
1168
|
-
if (hasImport) {
|
|
1169
|
-
return;
|
|
1170
|
-
}
|
|
1171
1203
|
const usedPrefixes = new Set<string>();
|
|
1172
1204
|
const rootPrefix = typeof ontology?.prefix === 'string' ? ontology.prefix.trim() : '';
|
|
1173
1205
|
if (rootPrefix) {
|
|
1174
1206
|
usedPrefixes.add(rootPrefix);
|
|
1175
1207
|
}
|
|
1176
1208
|
for (const imp of imports) {
|
|
1177
|
-
const
|
|
1178
|
-
if (
|
|
1179
|
-
usedPrefixes.add(
|
|
1209
|
+
const p = resolveImportPrefix(imp);
|
|
1210
|
+
if (p) {
|
|
1211
|
+
usedPrefixes.add(p);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
const existingImport = imports.find((imp: any) => {
|
|
1215
|
+
const importedNamespace = normalizeNamespace(String(resolveImportNamespaceRaw(imp) ?? ''));
|
|
1216
|
+
return importedNamespace === target.namespace;
|
|
1217
|
+
});
|
|
1218
|
+
if (existingImport) {
|
|
1219
|
+
// If the existing import has no prefix (e.g., a bundle `includes` added without one),
|
|
1220
|
+
// patch it now so that the reference can use the abbreviated form.
|
|
1221
|
+
if (!resolveImportPrefix(existingImport)) {
|
|
1222
|
+
existingImport.prefix = pickImportPrefix(target.namespace, usedPrefixes);
|
|
1180
1223
|
}
|
|
1224
|
+
return;
|
|
1181
1225
|
}
|
|
1182
1226
|
const prefix = pickImportPrefix(target.namespace, usedPrefixes);
|
|
1183
1227
|
const importedRef = `<${target.namespace}${target.separator}>`;
|
|
@@ -1191,15 +1235,97 @@ async function ensureReferenceImport(shared: any, ontology: any, iri: string): P
|
|
|
1191
1235
|
pushAstArrayChild(ontology, 'ownedImports', importStatement);
|
|
1192
1236
|
}
|
|
1193
1237
|
|
|
1194
|
-
async function
|
|
1195
|
-
const
|
|
1238
|
+
async function addImport(shared: any, importingOntology: any, importedOntology: any): Promise<boolean> {
|
|
1239
|
+
const importingNamespace = normalizeNamespace(String(importingOntology?.namespace ?? ''));
|
|
1240
|
+
const importedNamespaceRaw = String(importedOntology?.namespace ?? '').trim();
|
|
1241
|
+
const importedNamespace = normalizeNamespace(importedNamespaceRaw);
|
|
1242
|
+
if (!importingNamespace || !importedNamespace) {
|
|
1243
|
+
throw new Error('addImport requires both importing and imported ontologies to have valid namespaces.');
|
|
1244
|
+
}
|
|
1245
|
+
if (importingNamespace === importedNamespace) {
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
const imports = Array.isArray(importingOntology?.ownedImports) ? importingOntology.ownedImports : [];
|
|
1250
|
+
const hasImport = imports.some((imp: any) => {
|
|
1251
|
+
const imported = normalizeNamespace(String(resolveImportNamespaceRaw(imp) ?? ''));
|
|
1252
|
+
return imported === importedNamespace;
|
|
1253
|
+
});
|
|
1254
|
+
if (hasImport) {
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
const separator: '#' | '/' = importedNamespaceRaw.endsWith('/') ? '/' : '#';
|
|
1259
|
+
const importedRef = `<${importedNamespace}${separator}>`;
|
|
1260
|
+
const importKind = await resolveImportKind(shared, importingOntology, importedNamespace, importedOntology);
|
|
1261
|
+
const usedPrefixes = new Set<string>();
|
|
1262
|
+
const rootPrefix = typeof importingOntology?.prefix === 'string' ? importingOntology.prefix.trim() : '';
|
|
1263
|
+
if (rootPrefix) {
|
|
1264
|
+
usedPrefixes.add(rootPrefix);
|
|
1265
|
+
}
|
|
1266
|
+
for (const imp of imports) {
|
|
1267
|
+
const p = resolveImportPrefix(imp);
|
|
1268
|
+
if (p) {
|
|
1269
|
+
usedPrefixes.add(p);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
// `includes` never gets a prefix — if an annotation later references a term from this
|
|
1273
|
+
// vocabulary, ensureReferenceImport will patch the prefix onto this import at that point.
|
|
1274
|
+
let prefix: string | undefined;
|
|
1275
|
+
if (importKind !== 'includes') {
|
|
1276
|
+
const candidatePrefix = typeof importedOntology?.prefix === 'string' ? importedOntology.prefix.trim() : '';
|
|
1277
|
+
prefix = candidatePrefix && !usedPrefixes.has(candidatePrefix)
|
|
1278
|
+
? candidatePrefix
|
|
1279
|
+
: pickImportPrefix(importedNamespace, usedPrefixes);
|
|
1280
|
+
}
|
|
1281
|
+
const importStatement: any = {
|
|
1282
|
+
$type: 'Import',
|
|
1283
|
+
kind: importKind,
|
|
1284
|
+
...(prefix ? { prefix } : {}),
|
|
1285
|
+
imported: { $refText: importedRef }
|
|
1286
|
+
};
|
|
1287
|
+
pushAstArrayChild(importingOntology, 'ownedImports', importStatement);
|
|
1288
|
+
return true;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function removeImport(importingOntology: any, importedOntology: any): boolean {
|
|
1292
|
+
const importedNamespace = normalizeNamespace(String(importedOntology?.namespace ?? ''));
|
|
1293
|
+
if (!importedNamespace) {
|
|
1294
|
+
throw new Error('removeImport requires imported ontology to have a valid namespace.');
|
|
1295
|
+
}
|
|
1296
|
+
const imports = Array.isArray(importingOntology?.ownedImports) ? importingOntology.ownedImports : [];
|
|
1297
|
+
if (imports.length === 0) {
|
|
1298
|
+
return false;
|
|
1299
|
+
}
|
|
1300
|
+
const kept = imports.filter((imp: any) => {
|
|
1301
|
+
const namespace = normalizeNamespace(String(resolveImportNamespaceRaw(imp) ?? ''));
|
|
1302
|
+
return namespace !== importedNamespace;
|
|
1303
|
+
});
|
|
1304
|
+
if (kept.length === imports.length) {
|
|
1305
|
+
return false;
|
|
1306
|
+
}
|
|
1307
|
+
importingOntology.ownedImports = kept;
|
|
1308
|
+
return true;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
async function resolveImportKind(shared: any, importing: any, importedNamespace: string, importedOntology?: any): Promise<'extends' | 'includes' | 'uses'> {
|
|
1312
|
+
const imported = importedOntology ?? await findOntologyByNamespace(shared, importedNamespace);
|
|
1196
1313
|
if (imported) {
|
|
1197
1314
|
if (importing.$type === imported.$type) {
|
|
1198
1315
|
return 'extends';
|
|
1199
1316
|
}
|
|
1317
|
+
if (isDescriptionBundle(importing) && isDescription(imported)) {
|
|
1318
|
+
return 'includes';
|
|
1319
|
+
}
|
|
1200
1320
|
if (isVocabulary(importing) && isDescription(imported)) {
|
|
1201
1321
|
return 'uses';
|
|
1202
1322
|
}
|
|
1323
|
+
if (isDescriptionBundle(importing) && isVocabularyBundle(imported)) {
|
|
1324
|
+
return 'uses';
|
|
1325
|
+
}
|
|
1326
|
+
if (isDescriptionBundle(importing) && isVocabulary(imported)) {
|
|
1327
|
+
return 'uses';
|
|
1328
|
+
}
|
|
1203
1329
|
if (isDescriptionBox(importing) && isVocabulary(imported)) {
|
|
1204
1330
|
return 'uses';
|
|
1205
1331
|
}
|
|
@@ -1232,43 +1358,6 @@ async function findOntologyByNamespace(shared: any, namespace: string): Promise<
|
|
|
1232
1358
|
return ontology && isOntology(ontology) ? ontology : undefined;
|
|
1233
1359
|
}
|
|
1234
1360
|
|
|
1235
|
-
function parseIriParts(iri: string): { namespace: string; fragment: string; separator: '#' | '/' } | undefined {
|
|
1236
|
-
const normalized = normalizeIri(iri);
|
|
1237
|
-
if (!normalized) {
|
|
1238
|
-
return undefined;
|
|
1239
|
-
}
|
|
1240
|
-
const hashIndex = normalized.lastIndexOf('#');
|
|
1241
|
-
if (hashIndex > 0 && hashIndex < normalized.length - 1) {
|
|
1242
|
-
return {
|
|
1243
|
-
namespace: normalizeNamespace(normalized.slice(0, hashIndex)),
|
|
1244
|
-
fragment: normalized.slice(hashIndex + 1),
|
|
1245
|
-
separator: '#',
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
const slashIndex = normalized.lastIndexOf('/');
|
|
1249
|
-
if (slashIndex > 0 && slashIndex < normalized.length - 1) {
|
|
1250
|
-
return {
|
|
1251
|
-
namespace: normalizeNamespace(normalized.slice(0, slashIndex)),
|
|
1252
|
-
fragment: normalized.slice(slashIndex + 1),
|
|
1253
|
-
separator: '/',
|
|
1254
|
-
};
|
|
1255
|
-
}
|
|
1256
|
-
return undefined;
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
function getRefText(ref: any): string | undefined {
|
|
1260
|
-
if (typeof ref === 'string') {
|
|
1261
|
-
return ref;
|
|
1262
|
-
}
|
|
1263
|
-
if (typeof ref?.$refText === 'string') {
|
|
1264
|
-
return ref.$refText;
|
|
1265
|
-
}
|
|
1266
|
-
if (typeof ref?.$refNode?.text === 'string') {
|
|
1267
|
-
return ref.$refNode.text;
|
|
1268
|
-
}
|
|
1269
|
-
return undefined;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
1361
|
function matchesRefTextTarget(
|
|
1273
1362
|
ontology: any,
|
|
1274
1363
|
statement: any,
|
|
@@ -1328,59 +1417,6 @@ function matchesRefTextFragment(statement: any, fragment: string): boolean {
|
|
|
1328
1417
|
return !!local && local === fragment;
|
|
1329
1418
|
}
|
|
1330
1419
|
|
|
1331
|
-
function localNameFromRefText(refText: string): string | undefined {
|
|
1332
|
-
const trimmed = refText.trim().replace(/^<|>$/g, '');
|
|
1333
|
-
if (!trimmed) {
|
|
1334
|
-
return undefined;
|
|
1335
|
-
}
|
|
1336
|
-
const colonIndex = trimmed.indexOf(':');
|
|
1337
|
-
if (colonIndex > 0 && colonIndex < trimmed.length - 1) {
|
|
1338
|
-
return trimmed.slice(colonIndex + 1);
|
|
1339
|
-
}
|
|
1340
|
-
const hashIndex = trimmed.lastIndexOf('#');
|
|
1341
|
-
if (hashIndex >= 0 && hashIndex < trimmed.length - 1) {
|
|
1342
|
-
return trimmed.slice(hashIndex + 1);
|
|
1343
|
-
}
|
|
1344
|
-
const slashIndex = trimmed.lastIndexOf('/');
|
|
1345
|
-
if (slashIndex >= 0 && slashIndex < trimmed.length - 1) {
|
|
1346
|
-
return trimmed.slice(slashIndex + 1);
|
|
1347
|
-
}
|
|
1348
|
-
return trimmed;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
function isSameIriTarget(left: string, right: string): boolean {
|
|
1352
|
-
const leftNorm = normalizeIri(left);
|
|
1353
|
-
const rightNorm = normalizeIri(right);
|
|
1354
|
-
if (leftNorm === rightNorm) {
|
|
1355
|
-
return true;
|
|
1356
|
-
}
|
|
1357
|
-
const leftParts = parseIriParts(leftNorm);
|
|
1358
|
-
const rightParts = parseIriParts(rightNorm);
|
|
1359
|
-
if (!leftParts || !rightParts) {
|
|
1360
|
-
return false;
|
|
1361
|
-
}
|
|
1362
|
-
return leftParts.namespace === rightParts.namespace
|
|
1363
|
-
&& leftParts.fragment === rightParts.fragment;
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
function pickImportPrefix(namespace: string, usedPrefixes: ReadonlySet<string>): string {
|
|
1367
|
-
const normalizedNamespace = normalizeNamespace(namespace);
|
|
1368
|
-
const pieces = normalizedNamespace.split('/').filter((piece) => piece.length > 0);
|
|
1369
|
-
const tail = (pieces[pieces.length - 1] ?? normalizedNamespace)
|
|
1370
|
-
.replace(/[^A-Za-z0-9_]/g, '')
|
|
1371
|
-
.toLowerCase();
|
|
1372
|
-
const base = /^[A-Za-z_]/.test(tail) ? tail : `ns${tail}`;
|
|
1373
|
-
const candidateBase = (base || 'ns').slice(0, 32);
|
|
1374
|
-
if (!usedPrefixes.has(candidateBase)) {
|
|
1375
|
-
return candidateBase;
|
|
1376
|
-
}
|
|
1377
|
-
let suffix = 1;
|
|
1378
|
-
while (usedPrefixes.has(`${candidateBase}${suffix}`)) {
|
|
1379
|
-
suffix += 1;
|
|
1380
|
-
}
|
|
1381
|
-
return `${candidateBase}${suffix}`;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
1420
|
function asLiteral(ontology: any, value: unknown): any {
|
|
1385
1421
|
if (isTypedQuotedLiteralTransport(value)) {
|
|
1386
1422
|
const literalValue = String(value.value ?? '');
|
|
@@ -1541,8 +1577,8 @@ function isIriLike(value: unknown): boolean {
|
|
|
1541
1577
|
return trimmed.includes('://');
|
|
1542
1578
|
}
|
|
1543
1579
|
|
|
1544
|
-
function
|
|
1545
|
-
return
|
|
1580
|
+
function normalizeOntologyIriKey(value: string): string {
|
|
1581
|
+
return normalizeNamespace(normalizeIri(value));
|
|
1546
1582
|
}
|
|
1547
1583
|
|
|
1548
1584
|
function normalizePropertyAssertionGroup(subject: any, target: any, duplicates: any[]): void {
|
|
@@ -1662,17 +1698,26 @@ function isTargetPredicate(predicateIri: string): boolean {
|
|
|
1662
1698
|
|
|
1663
1699
|
function buildWorkspaceEdit(
|
|
1664
1700
|
contexts: Map<string, OntologyContext>,
|
|
1665
|
-
changedOntologyIris: ReadonlySet<string
|
|
1701
|
+
changedOntologyIris: ReadonlySet<string>,
|
|
1702
|
+
createdOntologyByIri: ReadonlyMap<string, CreatedOntology>,
|
|
1703
|
+
deletedOntologyModelUriByIri: ReadonlyMap<string, string>
|
|
1666
1704
|
): WorkspaceEdit | undefined {
|
|
1667
|
-
if (changedOntologyIris.size === 0) {
|
|
1705
|
+
if (changedOntologyIris.size === 0 && createdOntologyByIri.size === 0 && deletedOntologyModelUriByIri.size === 0) {
|
|
1668
1706
|
return undefined;
|
|
1669
1707
|
}
|
|
1670
1708
|
const changes: NonNullable<WorkspaceEdit['changes']> = {};
|
|
1709
|
+
const deletedModelUris = new Set<string>([...deletedOntologyModelUriByIri.values()]);
|
|
1671
1710
|
for (const ontologyIri of [...changedOntologyIris].sort()) {
|
|
1711
|
+
if (createdOntologyByIri.has(ontologyIri)) {
|
|
1712
|
+
continue;
|
|
1713
|
+
}
|
|
1672
1714
|
const context = contexts.get(ontologyIri);
|
|
1673
1715
|
if (!context) {
|
|
1674
1716
|
continue;
|
|
1675
1717
|
}
|
|
1718
|
+
if (deletedModelUris.has(context.modelUri)) {
|
|
1719
|
+
continue;
|
|
1720
|
+
}
|
|
1676
1721
|
const serialized = serializeOntology(context.ontology);
|
|
1677
1722
|
const textDocument = context.document?.textDocument;
|
|
1678
1723
|
if (!textDocument) {
|
|
@@ -1687,15 +1732,206 @@ function buildWorkspaceEdit(
|
|
|
1687
1732
|
newText: serialized
|
|
1688
1733
|
}];
|
|
1689
1734
|
}
|
|
1690
|
-
|
|
1735
|
+
for (const [createdIri, created] of [...createdOntologyByIri.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
1736
|
+
const createdContext = contexts.get(createdIri);
|
|
1737
|
+
const content = createdContext ? serializeOntology(createdContext.ontology) : created.content;
|
|
1738
|
+
changes[created.modelUri] = [{
|
|
1739
|
+
range: {
|
|
1740
|
+
start: { line: 0, character: 0 },
|
|
1741
|
+
end: { line: 0, character: 0 }
|
|
1742
|
+
},
|
|
1743
|
+
newText: content
|
|
1744
|
+
}];
|
|
1745
|
+
}
|
|
1746
|
+
const documentChanges = [...deletedModelUris].sort().map((uri) => ({ kind: 'delete' as const, uri }));
|
|
1747
|
+
if (Object.keys(changes).length === 0 && documentChanges.length === 0) {
|
|
1748
|
+
return undefined;
|
|
1749
|
+
}
|
|
1750
|
+
if (documentChanges.length === 0) {
|
|
1751
|
+
return { changes };
|
|
1752
|
+
}
|
|
1753
|
+
if (Object.keys(changes).length === 0) {
|
|
1754
|
+
return { documentChanges };
|
|
1755
|
+
}
|
|
1756
|
+
return { changes, documentChanges };
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
type CreatedOntology = {
|
|
1760
|
+
ontologyIri: string;
|
|
1761
|
+
modelUri: string;
|
|
1762
|
+
content: string;
|
|
1763
|
+
ontology: any;
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
function detectOmlSrcDocument(shared: any): string | undefined {
|
|
1767
|
+
const documentsService: any = shared.workspace.LangiumDocuments;
|
|
1768
|
+
const all = documentsService?.all ?? [];
|
|
1769
|
+
const iterable: any[] = Array.isArray(all)
|
|
1770
|
+
? all
|
|
1771
|
+
: (typeof all?.toArray === 'function' ? all.toArray() : Array.from(all as Iterable<any>));
|
|
1772
|
+
for (const document of iterable) {
|
|
1773
|
+
const uri = String(document?.uri ?? '');
|
|
1774
|
+
if (uri.includes('/oml/')) {
|
|
1775
|
+
return uri;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
return undefined;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
async function createOntologyOperation(
|
|
1782
|
+
shared: any,
|
|
1783
|
+
operation: Extract<OmlUpdateOperation, { kind: 'createOntology' }>,
|
|
1784
|
+
workspaceUri?: string
|
|
1785
|
+
): Promise<CreatedOntology> {
|
|
1786
|
+
const namespaceCore = normalizeNamespace(operation.ontologyNamespace);
|
|
1787
|
+
if (!namespaceCore) {
|
|
1788
|
+
throw new Error('createOntology requires a non-empty ontologyNamespace.');
|
|
1789
|
+
}
|
|
1790
|
+
const ontologyPrefix = operation.ontologyPrefix.trim();
|
|
1791
|
+
if (!ontologyPrefix) {
|
|
1792
|
+
throw new Error('createOntology requires a non-empty ontologyPrefix.');
|
|
1793
|
+
}
|
|
1794
|
+
const namespaceWithSeparator = withNamespaceSeparator(operation.ontologyNamespace);
|
|
1795
|
+
const index = getOntologyModelIndex(shared);
|
|
1796
|
+
const referencingUri = operation.targetFolder ? undefined : detectOmlSrcDocument(shared) ?? workspaceUri;
|
|
1797
|
+
const existingModelUri = index.resolveModelUri(namespaceCore, referencingUri);
|
|
1798
|
+
if (existingModelUri) {
|
|
1799
|
+
throw new Error(`Ontology '${namespaceWithSeparator}' already exists at '${existingModelUri}'.`);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
const modelUri = deriveNewOntologyModelUri(namespaceCore, referencingUri, operation.targetFolder, workspaceUri);
|
|
1803
|
+
const ontologyIri = normalizeOntologyIriKey(namespaceWithSeparator);
|
|
1804
|
+
const ontology = createOntologyAst(operation.ontologyKind, namespaceWithSeparator, ontologyPrefix);
|
|
1805
|
+
const content = serializeOntology(ontology as any);
|
|
1806
|
+
return {
|
|
1807
|
+
ontologyIri,
|
|
1808
|
+
modelUri,
|
|
1809
|
+
content,
|
|
1810
|
+
ontology
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
function deriveNewOntologyModelUri(namespaceCore: string, referencingUri?: string, targetFolder?: string, workspaceUri?: string): string {
|
|
1815
|
+
let namespaceUrl: URL;
|
|
1816
|
+
try {
|
|
1817
|
+
namespaceUrl = new URL(namespaceCore.includes('://') ? namespaceCore : `http://${namespaceCore}`);
|
|
1818
|
+
} catch {
|
|
1819
|
+
throw new Error(`Unable to derive ontology file path from namespace '${namespaceCore}'.`);
|
|
1820
|
+
}
|
|
1821
|
+
const nsHost = namespaceUrl.host;
|
|
1822
|
+
const nsPath = (namespaceUrl.pathname ?? '').replace(/^\/+/, '').replace(/\/+$/, '');
|
|
1823
|
+
const relative = [nsHost, ...nsPath.split('/').filter(Boolean)].join('/');
|
|
1824
|
+
if (!relative) {
|
|
1825
|
+
throw new Error(`Unable to derive ontology file path from namespace '${namespaceCore}'.`);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
if (targetFolder) {
|
|
1829
|
+
const normalizedFolder = targetFolder.trim().replace(/\/+$/, '');
|
|
1830
|
+
if (!normalizedFolder) {
|
|
1831
|
+
throw new Error('createOntology targetFolder must not be empty.');
|
|
1832
|
+
}
|
|
1833
|
+
let folderPath = normalizedFolder;
|
|
1834
|
+
if (!normalizedFolder.startsWith('/') && workspaceUri) {
|
|
1835
|
+
const workspacePath = URI.parse(workspaceUri).path ?? '';
|
|
1836
|
+
folderPath = `${workspacePath.replace(/\/+$/, '')}/${normalizedFolder}`;
|
|
1837
|
+
}
|
|
1838
|
+
return URI.from({
|
|
1839
|
+
scheme: 'file',
|
|
1840
|
+
authority: '',
|
|
1841
|
+
path: `${folderPath}/${relative}.oml`,
|
|
1842
|
+
query: undefined,
|
|
1843
|
+
fragment: undefined
|
|
1844
|
+
}).toString();
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
if (!referencingUri) {
|
|
1848
|
+
throw new Error('createOntology requires either targetFolder or referencingUri to derive output path.');
|
|
1849
|
+
}
|
|
1850
|
+
let reference: URI;
|
|
1851
|
+
try {
|
|
1852
|
+
reference = URI.parse(referencingUri);
|
|
1853
|
+
} catch {
|
|
1854
|
+
throw new Error(`Invalid referencingUri '${referencingUri}'.`);
|
|
1855
|
+
}
|
|
1856
|
+
const refPath = reference.path ?? '';
|
|
1857
|
+
const marker = '/oml/';
|
|
1858
|
+
const markerIndex = refPath.lastIndexOf(marker);
|
|
1859
|
+
if (markerIndex < 0) {
|
|
1860
|
+
throw new Error(`Unable to derive ontology path from referencingUri '${referencingUri}': missing '/oml/' segment.`);
|
|
1861
|
+
}
|
|
1862
|
+
const basePath = refPath.slice(0, markerIndex + marker.length);
|
|
1863
|
+
const targetPath = `${basePath}${relative}.oml`;
|
|
1864
|
+
return URI.from({
|
|
1865
|
+
scheme: reference.scheme,
|
|
1866
|
+
authority: reference.authority,
|
|
1867
|
+
path: targetPath,
|
|
1868
|
+
query: reference.query,
|
|
1869
|
+
fragment: undefined
|
|
1870
|
+
}).toString();
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
function createOntologyAst(
|
|
1874
|
+
ontologyKind: 'vocabulary' | 'description' | 'vocabulary bundle' | 'description bundle',
|
|
1875
|
+
namespace: string,
|
|
1876
|
+
prefix: string
|
|
1877
|
+
): any {
|
|
1878
|
+
switch (ontologyKind) {
|
|
1879
|
+
case 'vocabulary':
|
|
1880
|
+
return {
|
|
1881
|
+
$type: 'Vocabulary',
|
|
1882
|
+
namespace,
|
|
1883
|
+
prefix,
|
|
1884
|
+
ownedAnnotations: [],
|
|
1885
|
+
ownedImports: [],
|
|
1886
|
+
ownedStatements: []
|
|
1887
|
+
};
|
|
1888
|
+
case 'description':
|
|
1889
|
+
return {
|
|
1890
|
+
$type: 'Description',
|
|
1891
|
+
namespace,
|
|
1892
|
+
prefix,
|
|
1893
|
+
ownedAnnotations: [],
|
|
1894
|
+
ownedImports: [],
|
|
1895
|
+
ownedStatements: []
|
|
1896
|
+
};
|
|
1897
|
+
case 'vocabulary bundle':
|
|
1898
|
+
return {
|
|
1899
|
+
$type: 'VocabularyBundle',
|
|
1900
|
+
namespace,
|
|
1901
|
+
prefix,
|
|
1902
|
+
ownedAnnotations: [],
|
|
1903
|
+
ownedImports: []
|
|
1904
|
+
};
|
|
1905
|
+
case 'description bundle':
|
|
1906
|
+
return {
|
|
1907
|
+
$type: 'DescriptionBundle',
|
|
1908
|
+
namespace,
|
|
1909
|
+
prefix,
|
|
1910
|
+
ownedAnnotations: [],
|
|
1911
|
+
ownedImports: []
|
|
1912
|
+
};
|
|
1913
|
+
default:
|
|
1914
|
+
throw new Error(`Unsupported ontology kind '${(ontologyKind as any)}'.`);
|
|
1915
|
+
}
|
|
1691
1916
|
}
|
|
1692
1917
|
|
|
1693
1918
|
async function restoreContextsFromDocuments(shared: any, contexts: Map<string, OntologyContext>): Promise<void> {
|
|
1694
1919
|
const uris = [...new Set([...contexts.values()].map((context) => URI.parse(context.modelUri)))];
|
|
1920
|
+
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
1921
|
+
// Langium skips re-parsing when the on-disk text matches the CST text stored in the
|
|
1922
|
+
// current parseResult. Preview modifies the AST in-place without changing the file, so
|
|
1923
|
+
// the texts are still equal and the optimisation would keep the modified AST. Clearing
|
|
1924
|
+
// $cstNode forces oldText to be undefined, guaranteeing a fresh parse from disk.
|
|
1925
|
+
for (const context of contexts.values()) {
|
|
1926
|
+
const doc = langiumDocuments.getDocument(URI.parse(context.modelUri));
|
|
1927
|
+
const root = doc?.parseResult?.value as any;
|
|
1928
|
+
if (root) {
|
|
1929
|
+
root.$cstNode = undefined;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1695
1932
|
if (uris.length > 0) {
|
|
1696
1933
|
await shared.workspace.DocumentBuilder.update(uris, []);
|
|
1697
1934
|
}
|
|
1698
|
-
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
1699
1935
|
for (const context of contexts.values()) {
|
|
1700
1936
|
const refreshed = langiumDocuments.getDocument(URI.parse(context.modelUri))
|
|
1701
1937
|
?? await langiumDocuments.getOrCreateDocument(URI.parse(context.modelUri));
|