@oml/language 0.12.0 → 0.14.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/index.d.ts +3 -1
- package/out/oml/index.js +19 -1
- package/out/oml/index.js.map +1 -1
- package/out/oml/oml-candidates.d.ts +3 -3
- package/out/oml/oml-candidates.js +1 -1
- package/out/oml/oml-candidates.js.map +1 -1
- package/out/oml/oml-diagram.d.ts +17 -0
- package/out/oml/oml-diagram.js +1549 -0
- package/out/oml/oml-diagram.js.map +1 -0
- package/out/oml/oml-document.d.ts +5 -0
- package/out/oml/oml-document.js +12 -1
- package/out/oml/oml-document.js.map +1 -1
- package/out/oml/oml-index.d.ts +2 -1
- package/out/oml/oml-index.js +90 -9
- package/out/oml/oml-index.js.map +1 -1
- package/out/oml/oml-scope.js +4 -3
- package/out/oml/oml-scope.js.map +1 -1
- package/out/oml/oml-search.d.ts +24 -0
- package/out/oml/oml-search.js +95 -0
- package/out/oml/oml-search.js.map +1 -0
- package/out/oml/{oml-edit.d.ts → oml-update.d.ts} +2 -0
- package/out/oml/{oml-edit.js → oml-update.js} +270 -67
- package/out/oml/oml-update.js.map +1 -0
- package/out/oml/oml-utils.d.ts +1 -1
- package/out/oml/oml-utils.js +3 -4
- package/out/oml/oml-utils.js.map +1 -1
- package/out/oml/oml-validator.d.ts +1 -0
- package/out/oml/oml-validator.js +18 -3
- package/out/oml/oml-validator.js.map +1 -1
- package/package.json +4 -2
- package/src/oml/index.ts +32 -1
- package/src/oml/oml-candidates.ts +4 -4
- package/src/oml/oml-diagram.ts +1708 -0
- package/src/oml/oml-document.ts +13 -1
- package/src/oml/oml-index.ts +87 -9
- package/src/oml/oml-scope.ts +4 -3
- package/src/oml/oml-search.ts +132 -0
- package/src/oml/{oml-edit.ts → oml-update.ts} +317 -66
- package/src/oml/oml-utils.ts +3 -4
- package/src/oml/oml-validator.ts +18 -3
- package/out/oml/oml-edit.js.map +0 -1
|
@@ -32,10 +32,11 @@ type ConnectionLike = {
|
|
|
32
32
|
};
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
const OmlEditRequestType = new RequestType<OmlEditRequest, OmlEditResponse, void>('oml/
|
|
35
|
+
const OmlEditRequestType = new RequestType<OmlEditRequest, OmlEditResponse, void>('oml/update');
|
|
36
36
|
const RDF_TYPE_IRI = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
37
37
|
const OML_HAS_SOURCE_IRI = 'http://opencaesar.io/oml#hasSource';
|
|
38
38
|
const OML_HAS_TARGET_IRI = 'http://opencaesar.io/oml#hasTarget';
|
|
39
|
+
const XSD_STRING_IRI = 'http://www.w3.org/2001/XMLSchema#string';
|
|
39
40
|
|
|
40
41
|
type OmlEditOperation =
|
|
41
42
|
| { kind: 'createInstance'; ontologyIri: string; instanceName: string }
|
|
@@ -53,6 +54,7 @@ type OmlEditOperation =
|
|
|
53
54
|
|
|
54
55
|
export type OmlEditRequest = {
|
|
55
56
|
operations: OmlEditOperation[];
|
|
57
|
+
referencingUri?: string;
|
|
56
58
|
};
|
|
57
59
|
|
|
58
60
|
export type OmlEditError = {
|
|
@@ -65,36 +67,47 @@ export type OmlEditResponse = {
|
|
|
65
67
|
errors?: OmlEditError[];
|
|
66
68
|
};
|
|
67
69
|
|
|
68
|
-
export function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
: message;
|
|
87
|
-
connection.console?.error(`[oml] OmlEditRequest failed at operation ${i} (${operation.kind}): ${details}`);
|
|
88
|
-
return {
|
|
89
|
-
errors: [{
|
|
90
|
-
operationIndex: i,
|
|
91
|
-
message
|
|
92
|
-
}]
|
|
93
|
-
};
|
|
70
|
+
export async function applyOmlUpdate(
|
|
71
|
+
shared: any,
|
|
72
|
+
params: OmlEditRequest,
|
|
73
|
+
logError?: (message: string) => void,
|
|
74
|
+
): Promise<OmlEditResponse> {
|
|
75
|
+
const operations = Array.isArray(params?.operations) ? params.operations : [];
|
|
76
|
+
const referencingUri = typeof params?.referencingUri === 'string'
|
|
77
|
+
? params.referencingUri.trim()
|
|
78
|
+
: undefined;
|
|
79
|
+
const contexts = new Map<string, OntologyContext>();
|
|
80
|
+
const changedOntologyIris = new Set<string>();
|
|
81
|
+
for (let i = 0; i < operations.length; i += 1) {
|
|
82
|
+
const operation = operations[i];
|
|
83
|
+
try {
|
|
84
|
+
const context = await resolveOntologyContext(shared, operation.ontologyIri, contexts, referencingUri);
|
|
85
|
+
const changed = await executeOperation(shared, context, operation, contexts);
|
|
86
|
+
for (const ontologyIri of changed) {
|
|
87
|
+
changedOntologyIris.add(ontologyIri);
|
|
94
88
|
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
await restoreContextsFromDocuments(shared, contexts);
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
92
|
+
const details = error instanceof Error && error.stack
|
|
93
|
+
? `${message}\n${error.stack}`
|
|
94
|
+
: message;
|
|
95
|
+
logError?.(`[oml] OmlEditRequest failed at operation ${i} (${operation.kind}): ${details}`);
|
|
96
|
+
return {
|
|
97
|
+
errors: [{
|
|
98
|
+
operationIndex: i,
|
|
99
|
+
message
|
|
100
|
+
}]
|
|
101
|
+
};
|
|
95
102
|
}
|
|
96
|
-
|
|
97
|
-
|
|
103
|
+
}
|
|
104
|
+
const edit = buildWorkspaceEdit(contexts, changedOntologyIris);
|
|
105
|
+
return edit ? { edit } : {};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function registerOmlEditRequests(connection: ConnectionLike, shared: any): void {
|
|
109
|
+
connection.onRequest(OmlEditRequestType, async (params: OmlEditRequest): Promise<OmlEditResponse> => {
|
|
110
|
+
return await applyOmlUpdate(shared, params, (message) => connection.console?.error(message));
|
|
98
111
|
});
|
|
99
112
|
}
|
|
100
113
|
|
|
@@ -105,14 +118,19 @@ type OntologyContext = {
|
|
|
105
118
|
document: any;
|
|
106
119
|
};
|
|
107
120
|
|
|
108
|
-
async function resolveOntologyContext(
|
|
121
|
+
async function resolveOntologyContext(
|
|
122
|
+
shared: any,
|
|
123
|
+
ontologyIri: string,
|
|
124
|
+
cache: Map<string, OntologyContext>,
|
|
125
|
+
referencingUri?: string
|
|
126
|
+
): Promise<OntologyContext> {
|
|
109
127
|
const normalizedOntologyIri = normalizeIri(ontologyIri);
|
|
110
128
|
const cached = cache.get(normalizedOntologyIri);
|
|
111
129
|
if (cached) {
|
|
112
130
|
return cached;
|
|
113
131
|
}
|
|
114
132
|
const index = getOntologyModelIndex(shared);
|
|
115
|
-
const modelUri = index.resolveModelUri(normalizedOntologyIri);
|
|
133
|
+
const modelUri = index.resolveModelUri(normalizedOntologyIri, referencingUri);
|
|
116
134
|
if (!modelUri) {
|
|
117
135
|
throw new Error(`Unable to resolve ontology '${ontologyIri}'.`);
|
|
118
136
|
}
|
|
@@ -121,12 +139,7 @@ async function resolveOntologyContext(shared: any, ontologyIri: string, cache: M
|
|
|
121
139
|
const builder: any = shared.workspace.DocumentBuilder;
|
|
122
140
|
const document = langiumDocuments.getDocument(uri)
|
|
123
141
|
?? await langiumDocuments.getOrCreateDocument(uri);
|
|
124
|
-
|
|
125
|
-
// is already Validated causes a document-state conflict in Langium on Windows.
|
|
126
|
-
const docState: number | undefined = document?.state;
|
|
127
|
-
if (docState === undefined || docState < DocumentState.Linked) {
|
|
128
|
-
await builder.build([document], { validation: false });
|
|
129
|
-
}
|
|
142
|
+
await ensureDocumentBuiltForEdit(builder, document);
|
|
130
143
|
const ontology = document?.parseResult?.value;
|
|
131
144
|
if (!ontology || !isOntology(ontology)) {
|
|
132
145
|
throw new Error(`Resolved document '${modelUri}' is not an ontology.`);
|
|
@@ -290,10 +303,7 @@ async function resolveOntologyContextByModelUri(
|
|
|
290
303
|
const builder: any = shared.workspace.DocumentBuilder;
|
|
291
304
|
const document = langiumDocuments.getDocument(uri)
|
|
292
305
|
?? await langiumDocuments.getOrCreateDocument(uri);
|
|
293
|
-
|
|
294
|
-
if (docState2 === undefined || docState2 < DocumentState.Linked) {
|
|
295
|
-
await builder.build([document], { validation: false });
|
|
296
|
-
}
|
|
306
|
+
await ensureDocumentBuiltForEdit(builder, document);
|
|
297
307
|
const ontology = document?.parseResult?.value;
|
|
298
308
|
if (!ontology || !isOntology(ontology)) {
|
|
299
309
|
throw new Error(`Resolved document '${modelUri}' is not an ontology.`);
|
|
@@ -365,7 +375,9 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
|
|
|
365
375
|
}
|
|
366
376
|
|
|
367
377
|
subject.ownedPropertyValues ??= [];
|
|
368
|
-
|
|
378
|
+
const matchingAssertions = subject.ownedPropertyValues
|
|
379
|
+
.filter((assertion: any) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
|
|
380
|
+
let propertyAssertion = matchingAssertions[0];
|
|
369
381
|
if (!propertyAssertion) {
|
|
370
382
|
await ensureReferenceImport(shared, ontology, normalizedPredicate);
|
|
371
383
|
propertyAssertion = {
|
|
@@ -376,6 +388,8 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
|
|
|
376
388
|
containedValues: []
|
|
377
389
|
};
|
|
378
390
|
pushAstArrayChild(subject, 'ownedPropertyValues', propertyAssertion);
|
|
391
|
+
} else if (matchingAssertions.length > 1) {
|
|
392
|
+
normalizePropertyAssertionGroup(subject, propertyAssertion, matchingAssertions.slice(1));
|
|
379
393
|
}
|
|
380
394
|
if (isIriLike(objectValue)) {
|
|
381
395
|
await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
|
|
@@ -383,6 +397,7 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
|
|
|
383
397
|
await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
|
|
384
398
|
}
|
|
385
399
|
appendAssertionValue(ontology, propertyAssertion, objectValue);
|
|
400
|
+
normalizeAllPropertyAssertions(subject, ontology);
|
|
386
401
|
return true;
|
|
387
402
|
}
|
|
388
403
|
|
|
@@ -515,19 +530,27 @@ async function addAnnotation(shared: any, ontology: any, subjectIri: string, pre
|
|
|
515
530
|
const normalizedPredicate = normalizeIri(predicateIri);
|
|
516
531
|
await ensureReferenceImport(shared, ontology, normalizedPredicate);
|
|
517
532
|
subject.ownedAnnotations ??= [];
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
533
|
+
const matchingAnnotations = subject.ownedAnnotations
|
|
534
|
+
.filter((entry: any) => matchesPredicateRef(ontology, entry?.property, normalizedPredicate));
|
|
535
|
+
let annotation = matchingAnnotations[0];
|
|
536
|
+
if (!annotation) {
|
|
537
|
+
annotation = {
|
|
538
|
+
$type: 'Annotation',
|
|
539
|
+
property: asRef(ontology, normalizedPredicate),
|
|
540
|
+
literalValues: [],
|
|
541
|
+
referencedValues: []
|
|
542
|
+
};
|
|
543
|
+
pushAstArrayChild(subject, 'ownedAnnotations', annotation);
|
|
544
|
+
} else if (matchingAnnotations.length > 1) {
|
|
545
|
+
normalizeAnnotationGroup(subject, annotation, matchingAnnotations.slice(1));
|
|
546
|
+
}
|
|
524
547
|
if (isIriLike(objectValue)) {
|
|
525
548
|
await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
|
|
526
549
|
} else {
|
|
527
550
|
await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
|
|
528
551
|
}
|
|
529
552
|
appendAnnotationValue(ontology, annotation, objectValue);
|
|
530
|
-
|
|
553
|
+
normalizeAllAnnotations(subject, ontology);
|
|
531
554
|
return true;
|
|
532
555
|
}
|
|
533
556
|
|
|
@@ -936,7 +959,7 @@ function removeAssertionValue(ontology: any, assertion: any, objectValue: unknow
|
|
|
936
959
|
assertion.referencedValues = (assertion.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
|
|
937
960
|
return;
|
|
938
961
|
}
|
|
939
|
-
const expectedLiteral =
|
|
962
|
+
const expectedLiteral = toLiteralRemovalMatchSpec(ontology, objectValue);
|
|
940
963
|
assertion.literalValues = (assertion.literalValues ?? []).filter((literal: any) => !matchesLiteralForRemoval(ontology, literal, expectedLiteral));
|
|
941
964
|
}
|
|
942
965
|
|
|
@@ -957,7 +980,7 @@ function removeAnnotationValue(ontology: any, annotation: any, objectValue: unkn
|
|
|
957
980
|
annotation.referencedValues = (annotation.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
|
|
958
981
|
return;
|
|
959
982
|
}
|
|
960
|
-
const expectedLiteral =
|
|
983
|
+
const expectedLiteral = toLiteralRemovalMatchSpec(ontology, objectValue);
|
|
961
984
|
annotation.literalValues = (annotation.literalValues ?? []).filter((literal: any) => !matchesLiteralForRemoval(ontology, literal, expectedLiteral));
|
|
962
985
|
}
|
|
963
986
|
|
|
@@ -976,15 +999,44 @@ function literalTypeIri(ontology: any, literal: any): string {
|
|
|
976
999
|
return normalizeIri(expandRefTextToIri(ontology, refText) ?? refText);
|
|
977
1000
|
}
|
|
978
1001
|
|
|
979
|
-
|
|
980
|
-
|
|
1002
|
+
type LiteralRemovalMatchSpec = {
|
|
1003
|
+
lexical: string;
|
|
1004
|
+
languageTag?: string;
|
|
1005
|
+
typeIri?: string;
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
function toLiteralRemovalMatchSpec(ontology: any, objectValue: unknown): LiteralRemovalMatchSpec {
|
|
1009
|
+
if (isTypedQuotedLiteralTransport(objectValue)) {
|
|
1010
|
+
return {
|
|
1011
|
+
lexical: String(objectValue.value ?? ''),
|
|
1012
|
+
languageTag: literalLanguageTagFromTransport(objectValue),
|
|
1013
|
+
typeIri: literalDatatypeIriFromTransport(ontology, objectValue),
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
const expectedLiteral = asLiteral(ontology, objectValue);
|
|
1017
|
+
return {
|
|
1018
|
+
lexical: lexicalLiteralValue(expectedLiteral),
|
|
1019
|
+
languageTag: literalLanguageTag(expectedLiteral),
|
|
1020
|
+
typeIri: literalTypeIri(ontology, expectedLiteral),
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
function matchesLiteralForRemoval(ontology: any, existingLiteral: any, expected: LiteralRemovalMatchSpec): boolean {
|
|
1025
|
+
if (lexicalLiteralValue(existingLiteral) !== expected.lexical) {
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
1028
|
+
const existingLanguage = literalLanguageTag(existingLiteral);
|
|
1029
|
+
if (existingLanguage && expected.languageTag && normalizeLanguageTag(existingLanguage) !== normalizeLanguageTag(expected.languageTag)) {
|
|
981
1030
|
return false;
|
|
982
1031
|
}
|
|
983
|
-
const expectedType =
|
|
1032
|
+
const expectedType = expected.typeIri ?? '';
|
|
984
1033
|
if (!expectedType) {
|
|
985
1034
|
return true;
|
|
986
1035
|
}
|
|
987
1036
|
const existingType = literalTypeIri(ontology, existingLiteral);
|
|
1037
|
+
if (!existingType) {
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
988
1040
|
return isSameIriTarget(existingType, expectedType);
|
|
989
1041
|
}
|
|
990
1042
|
|
|
@@ -1175,7 +1227,7 @@ async function findOntologyByNamespace(shared: any, namespace: string): Promise<
|
|
|
1175
1227
|
const builder: any = shared.workspace.DocumentBuilder;
|
|
1176
1228
|
const document = langiumDocuments.getDocument(uri)
|
|
1177
1229
|
?? await langiumDocuments.getOrCreateDocument(uri);
|
|
1178
|
-
await builder
|
|
1230
|
+
await ensureDocumentBuiltForEdit(builder, document);
|
|
1179
1231
|
const ontology = document?.parseResult?.value;
|
|
1180
1232
|
return ontology && isOntology(ontology) ? ontology : undefined;
|
|
1181
1233
|
}
|
|
@@ -1332,13 +1384,19 @@ function pickImportPrefix(namespace: string, usedPrefixes: ReadonlySet<string>):
|
|
|
1332
1384
|
function asLiteral(ontology: any, value: unknown): any {
|
|
1333
1385
|
if (isTypedQuotedLiteralTransport(value)) {
|
|
1334
1386
|
const literalValue = String(value.value ?? '');
|
|
1335
|
-
const
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
:
|
|
1340
|
-
|
|
1341
|
-
|
|
1387
|
+
const langTag = literalLanguageTagFromTransport(value);
|
|
1388
|
+
const datatypeIri = literalDatatypeIriFromTransport(ontology, value);
|
|
1389
|
+
const literal: Record<string, unknown> = {
|
|
1390
|
+
$type: 'QuotedLiteral',
|
|
1391
|
+
value: literalValue,
|
|
1392
|
+
};
|
|
1393
|
+
if (langTag) {
|
|
1394
|
+
literal.langTag = langTag;
|
|
1395
|
+
}
|
|
1396
|
+
if (datatypeIri && !isXsdStringDatatypeIri(ontology, datatypeIri)) {
|
|
1397
|
+
literal.type = { $refText: toRefText(ontology, datatypeIri) };
|
|
1398
|
+
}
|
|
1399
|
+
return literal;
|
|
1342
1400
|
}
|
|
1343
1401
|
if (typeof value === 'boolean') {
|
|
1344
1402
|
return { $type: 'BooleanLiteral', value };
|
|
@@ -1356,14 +1414,24 @@ async function ensureTypedLiteralDatatypeImport(shared: any, ontology: any, valu
|
|
|
1356
1414
|
if (!isTypedQuotedLiteralTransport(value)) {
|
|
1357
1415
|
return;
|
|
1358
1416
|
}
|
|
1359
|
-
const datatypeIri =
|
|
1360
|
-
if (!datatypeIri) {
|
|
1417
|
+
const datatypeIri = literalDatatypeIriFromTransport(ontology, value);
|
|
1418
|
+
if (!datatypeIri || isXsdStringDatatypeIri(ontology, datatypeIri)) {
|
|
1361
1419
|
return;
|
|
1362
1420
|
}
|
|
1363
1421
|
await ensureReferenceImport(shared, ontology, datatypeIri);
|
|
1364
1422
|
}
|
|
1365
1423
|
|
|
1366
|
-
function isTypedQuotedLiteralTransport(value: unknown): value is {
|
|
1424
|
+
function isTypedQuotedLiteralTransport(value: unknown): value is {
|
|
1425
|
+
$type?: unknown;
|
|
1426
|
+
value?: unknown;
|
|
1427
|
+
langTag?: unknown;
|
|
1428
|
+
language?: unknown;
|
|
1429
|
+
datatypeIri?: unknown;
|
|
1430
|
+
datatypeRefText?: unknown;
|
|
1431
|
+
typeIri?: unknown;
|
|
1432
|
+
typeRefText?: unknown;
|
|
1433
|
+
type?: unknown;
|
|
1434
|
+
} {
|
|
1367
1435
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1368
1436
|
return false;
|
|
1369
1437
|
}
|
|
@@ -1371,7 +1439,71 @@ function isTypedQuotedLiteralTransport(value: unknown): value is { $type?: unkno
|
|
|
1371
1439
|
if (record.$type === 'QuotedLiteral') {
|
|
1372
1440
|
return true;
|
|
1373
1441
|
}
|
|
1374
|
-
return (
|
|
1442
|
+
return (
|
|
1443
|
+
typeof record.datatypeIri === 'string'
|
|
1444
|
+
|| typeof record.datatypeRefText === 'string'
|
|
1445
|
+
|| typeof record.typeIri === 'string'
|
|
1446
|
+
|| typeof record.typeRefText === 'string'
|
|
1447
|
+
|| typeof record.type === 'string'
|
|
1448
|
+
) && 'value' in record;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function literalLanguageTag(literal: any): string {
|
|
1452
|
+
if (!literal || typeof literal !== 'object') {
|
|
1453
|
+
return '';
|
|
1454
|
+
}
|
|
1455
|
+
return typeof literal.langTag === 'string' ? literal.langTag.trim() : '';
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function literalLanguageTagFromTransport(value: {
|
|
1459
|
+
langTag?: unknown;
|
|
1460
|
+
language?: unknown;
|
|
1461
|
+
}): string {
|
|
1462
|
+
if (typeof value.langTag === 'string') {
|
|
1463
|
+
return value.langTag.trim();
|
|
1464
|
+
}
|
|
1465
|
+
if (typeof value.language === 'string') {
|
|
1466
|
+
return value.language.trim();
|
|
1467
|
+
}
|
|
1468
|
+
return '';
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
function normalizeLanguageTag(value: string): string {
|
|
1472
|
+
return value.trim().toLowerCase();
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function literalDatatypeIriFromTransport(ontology: any, value: {
|
|
1476
|
+
datatypeIri?: unknown;
|
|
1477
|
+
datatypeRefText?: unknown;
|
|
1478
|
+
typeIri?: unknown;
|
|
1479
|
+
typeRefText?: unknown;
|
|
1480
|
+
type?: unknown;
|
|
1481
|
+
}): string {
|
|
1482
|
+
const directIri = typeof value.datatypeIri === 'string'
|
|
1483
|
+
? value.datatypeIri.trim()
|
|
1484
|
+
: typeof value.typeIri === 'string'
|
|
1485
|
+
? value.typeIri.trim()
|
|
1486
|
+
: '';
|
|
1487
|
+
if (directIri) {
|
|
1488
|
+
const expanded = expandRefTextToIri(ontology, directIri);
|
|
1489
|
+
return normalizeIri(expanded ?? directIri);
|
|
1490
|
+
}
|
|
1491
|
+
const refText = typeof value.datatypeRefText === 'string'
|
|
1492
|
+
? value.datatypeRefText.trim()
|
|
1493
|
+
: typeof value.typeRefText === 'string'
|
|
1494
|
+
? value.typeRefText.trim()
|
|
1495
|
+
: typeof value.type === 'string'
|
|
1496
|
+
? value.type.trim()
|
|
1497
|
+
: '';
|
|
1498
|
+
if (!refText) {
|
|
1499
|
+
return '';
|
|
1500
|
+
}
|
|
1501
|
+
return normalizeIri(expandRefTextToIri(ontology, refText) ?? refText);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
function isXsdStringDatatypeIri(ontology: any, value: string): boolean {
|
|
1505
|
+
const normalized = normalizeIri(expandRefTextToIri(ontology, value) ?? value);
|
|
1506
|
+
return normalized === XSD_STRING_IRI;
|
|
1375
1507
|
}
|
|
1376
1508
|
|
|
1377
1509
|
function attachAstNode<T extends Record<string, any>>(node: T, parent: any, containerProperty: string): T {
|
|
@@ -1413,6 +1545,113 @@ function normalizeIri(value: string): string {
|
|
|
1413
1545
|
return String(value ?? '').trim().replace(/^<|>$/g, '');
|
|
1414
1546
|
}
|
|
1415
1547
|
|
|
1548
|
+
function normalizePropertyAssertionGroup(subject: any, target: any, duplicates: any[]): void {
|
|
1549
|
+
if (!Array.isArray(subject?.ownedPropertyValues) || !target || duplicates.length === 0) {
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
target.literalValues ??= [];
|
|
1553
|
+
target.referencedValues ??= [];
|
|
1554
|
+
target.containedValues ??= [];
|
|
1555
|
+
for (const duplicate of duplicates) {
|
|
1556
|
+
for (const literal of duplicate?.literalValues ?? []) {
|
|
1557
|
+
attachAstNode(literal, target, 'literalValues');
|
|
1558
|
+
target.literalValues.push(literal);
|
|
1559
|
+
}
|
|
1560
|
+
for (const ref of duplicate?.referencedValues ?? []) {
|
|
1561
|
+
target.referencedValues.push(ref);
|
|
1562
|
+
}
|
|
1563
|
+
for (const contained of duplicate?.containedValues ?? []) {
|
|
1564
|
+
attachAstNode(contained, target, 'containedValues');
|
|
1565
|
+
target.containedValues.push(contained);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
const duplicateSet = new Set(duplicates);
|
|
1569
|
+
subject.ownedPropertyValues = subject.ownedPropertyValues.filter((entry: any) => !duplicateSet.has(entry));
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
function normalizeAnnotationGroup(subject: any, target: any, duplicates: any[]): void {
|
|
1573
|
+
if (!Array.isArray(subject?.ownedAnnotations) || !target || duplicates.length === 0) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
target.literalValues ??= [];
|
|
1577
|
+
target.referencedValues ??= [];
|
|
1578
|
+
for (const duplicate of duplicates) {
|
|
1579
|
+
for (const literal of duplicate?.literalValues ?? []) {
|
|
1580
|
+
attachAstNode(literal, target, 'literalValues');
|
|
1581
|
+
target.literalValues.push(literal);
|
|
1582
|
+
}
|
|
1583
|
+
for (const ref of duplicate?.referencedValues ?? []) {
|
|
1584
|
+
target.referencedValues.push(ref);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const duplicateSet = new Set(duplicates);
|
|
1588
|
+
subject.ownedAnnotations = subject.ownedAnnotations.filter((entry: any) => !duplicateSet.has(entry));
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
function normalizeAllPropertyAssertions(subject: any, ontology: any): void {
|
|
1592
|
+
if (!Array.isArray(subject?.ownedPropertyValues) || subject.ownedPropertyValues.length < 2) {
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1595
|
+
const groups = new Map<string, { target: any; duplicates: any[] }>();
|
|
1596
|
+
for (const assertion of subject.ownedPropertyValues) {
|
|
1597
|
+
const key = propertyRefMergeKey(ontology, assertion?.property);
|
|
1598
|
+
if (!key) {
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
const group = groups.get(key);
|
|
1602
|
+
if (!group) {
|
|
1603
|
+
groups.set(key, { target: assertion, duplicates: [] });
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
group.duplicates.push(assertion);
|
|
1607
|
+
}
|
|
1608
|
+
for (const { target, duplicates } of groups.values()) {
|
|
1609
|
+
if (duplicates.length > 0) {
|
|
1610
|
+
normalizePropertyAssertionGroup(subject, target, duplicates);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
function normalizeAllAnnotations(subject: any, ontology: any): void {
|
|
1616
|
+
if (!Array.isArray(subject?.ownedAnnotations) || subject.ownedAnnotations.length < 2) {
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
const groups = new Map<string, { target: any; duplicates: any[] }>();
|
|
1620
|
+
for (const annotation of subject.ownedAnnotations) {
|
|
1621
|
+
const key = propertyRefMergeKey(ontology, annotation?.property);
|
|
1622
|
+
if (!key) {
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
const group = groups.get(key);
|
|
1626
|
+
if (!group) {
|
|
1627
|
+
groups.set(key, { target: annotation, duplicates: [] });
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
group.duplicates.push(annotation);
|
|
1631
|
+
}
|
|
1632
|
+
for (const { target, duplicates } of groups.values()) {
|
|
1633
|
+
if (duplicates.length > 0) {
|
|
1634
|
+
normalizeAnnotationGroup(subject, target, duplicates);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function propertyRefMergeKey(ontology: any, ref: any): string | undefined {
|
|
1640
|
+
const refText = getRefText(ref);
|
|
1641
|
+
if (refText) {
|
|
1642
|
+
const expanded = expandRefTextToIri(ontology, refText);
|
|
1643
|
+
return expanded
|
|
1644
|
+
? `iri:${normalizeIri(expanded)}`
|
|
1645
|
+
: `ref:${normalizeIri(refText)}`;
|
|
1646
|
+
}
|
|
1647
|
+
const resolved = ref?.ref ?? ref?._ref;
|
|
1648
|
+
const resolvedIri = resolved ? getIriForNode(resolved) : undefined;
|
|
1649
|
+
if (resolvedIri) {
|
|
1650
|
+
return `iri:${normalizeIri(resolvedIri)}`;
|
|
1651
|
+
}
|
|
1652
|
+
return undefined;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1416
1655
|
function isSourcePredicate(predicateIri: string): boolean {
|
|
1417
1656
|
return normalizeIri(predicateIri) === OML_HAS_SOURCE_IRI;
|
|
1418
1657
|
}
|
|
@@ -1467,3 +1706,15 @@ async function restoreContextsFromDocuments(shared: any, contexts: Map<string, O
|
|
|
1467
1706
|
}
|
|
1468
1707
|
}
|
|
1469
1708
|
}
|
|
1709
|
+
|
|
1710
|
+
async function ensureDocumentBuiltForEdit(builder: any, document: any): Promise<void> {
|
|
1711
|
+
const docState: number | undefined = document?.state;
|
|
1712
|
+
if (docState !== undefined && docState >= DocumentState.Linked) {
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
const workspaceState: number | undefined = builder?.currentState;
|
|
1716
|
+
const validation = workspaceState !== undefined && workspaceState >= DocumentState.Validated
|
|
1717
|
+
? { categories: ['built-in', 'fast'] as const }
|
|
1718
|
+
: false;
|
|
1719
|
+
await builder.build([document], { validation });
|
|
1720
|
+
}
|
package/src/oml/oml-utils.ts
CHANGED
|
@@ -160,16 +160,15 @@ export const getWorkspaceSnapshot = (documents: LangiumDocuments): string =>
|
|
|
160
160
|
.sort()
|
|
161
161
|
.join('|');
|
|
162
162
|
|
|
163
|
-
const
|
|
163
|
+
const OML_LS_IGNORED_DOCUMENT_SCHEMES = new Set([
|
|
164
164
|
'chat-editing-text-model',
|
|
165
165
|
'chat-editing-snapshot-text-model',
|
|
166
166
|
'vscode-chat-code-block',
|
|
167
|
-
'git',
|
|
168
167
|
]);
|
|
169
168
|
|
|
170
|
-
export function
|
|
169
|
+
export function isIgnoredByOmlLsDocumentUri(uri: string): boolean {
|
|
171
170
|
try {
|
|
172
|
-
return
|
|
171
|
+
return OML_LS_IGNORED_DOCUMENT_SCHEMES.has(URI.parse(uri).scheme);
|
|
173
172
|
} catch {
|
|
174
173
|
return false;
|
|
175
174
|
}
|
package/src/oml/oml-validator.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
2
|
|
|
3
|
-
import { AstUtils } from 'langium';
|
|
3
|
+
import { AstUtils, URI } from 'langium';
|
|
4
4
|
import type { ValidationAcceptor, ValidationChecks } from 'langium';
|
|
5
5
|
import type { OmlServices } from './oml-module.js';
|
|
6
6
|
import { getOntologyModelIndex } from './oml-index.js';
|
|
@@ -270,8 +270,15 @@ export class OmlValidator {
|
|
|
270
270
|
const namespace = rawNamespace.replace(/^<|>$/g, '');
|
|
271
271
|
const iri = namespace.replace(/[#/]?$/, '');
|
|
272
272
|
const docUri = ontology?.$document?.uri?.toString?.() ?? '';
|
|
273
|
+
const currentScheme = this.getUriScheme(docUri);
|
|
273
274
|
const duplicateModelUris = getOntologyModelIndex(this.services.shared).getDuplicateWorkspaceModelUris(iri);
|
|
274
|
-
const conflictingModelUris = duplicateModelUris.filter((modelUri) =>
|
|
275
|
+
const conflictingModelUris = duplicateModelUris.filter((modelUri) => {
|
|
276
|
+
if (modelUri === docUri) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
const otherScheme = this.getUriScheme(modelUri);
|
|
280
|
+
return !currentScheme || !otherScheme || currentScheme === otherScheme;
|
|
281
|
+
});
|
|
275
282
|
if (conflictingModelUris.length > 0) {
|
|
276
283
|
accept('error', `Ontology IRI '${iri}' is also declared by '${conflictingModelUris[0]}'`, {
|
|
277
284
|
node: ontology,
|
|
@@ -309,6 +316,14 @@ export class OmlValidator {
|
|
|
309
316
|
}
|
|
310
317
|
}
|
|
311
318
|
|
|
319
|
+
private getUriScheme(value: string): string | undefined {
|
|
320
|
+
try {
|
|
321
|
+
return URI.parse(value).scheme;
|
|
322
|
+
} catch {
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
312
327
|
checkQuotedLiteralNonStandardType(literal: QuotedLiteral, accept: ValidationAcceptor): void {
|
|
313
328
|
const scalar = literal.type?.ref as any;
|
|
314
329
|
if (!scalar) {
|
|
@@ -700,7 +715,7 @@ export class OmlValidator {
|
|
|
700
715
|
// Mask quoted literals and comments so prefix scanning only sees code tokens.
|
|
701
716
|
const stripped = text.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\/\*[\s\S]*?\*\/|\/\/[^\n\r]*/g, ' ');
|
|
702
717
|
const usedPrefixes = new Set<string>();
|
|
703
|
-
const pattern =
|
|
718
|
+
const pattern = /(?<![A-Za-z0-9$._~%-])([A-Za-z0-9._~%-][A-Za-z0-9$._~%-]*):\S/g;
|
|
704
719
|
let match: RegExpExecArray | null;
|
|
705
720
|
while ((match = pattern.exec(stripped)) !== null) {
|
|
706
721
|
usedPrefixes.add(match[1]);
|