@oml/language 0.13.0 → 0.14.1
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} +256 -56
- 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 +17 -2
- 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} +302 -55
- package/src/oml/oml-utils.ts +3 -4
- package/src/oml/oml-validator.ts +17 -2
- 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
|
}
|
|
@@ -357,7 +375,9 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
|
|
|
357
375
|
}
|
|
358
376
|
|
|
359
377
|
subject.ownedPropertyValues ??= [];
|
|
360
|
-
|
|
378
|
+
const matchingAssertions = subject.ownedPropertyValues
|
|
379
|
+
.filter((assertion: any) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
|
|
380
|
+
let propertyAssertion = matchingAssertions[0];
|
|
361
381
|
if (!propertyAssertion) {
|
|
362
382
|
await ensureReferenceImport(shared, ontology, normalizedPredicate);
|
|
363
383
|
propertyAssertion = {
|
|
@@ -368,6 +388,8 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
|
|
|
368
388
|
containedValues: []
|
|
369
389
|
};
|
|
370
390
|
pushAstArrayChild(subject, 'ownedPropertyValues', propertyAssertion);
|
|
391
|
+
} else if (matchingAssertions.length > 1) {
|
|
392
|
+
normalizePropertyAssertionGroup(subject, propertyAssertion, matchingAssertions.slice(1));
|
|
371
393
|
}
|
|
372
394
|
if (isIriLike(objectValue)) {
|
|
373
395
|
await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
|
|
@@ -375,6 +397,7 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
|
|
|
375
397
|
await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
|
|
376
398
|
}
|
|
377
399
|
appendAssertionValue(ontology, propertyAssertion, objectValue);
|
|
400
|
+
normalizeAllPropertyAssertions(subject, ontology);
|
|
378
401
|
return true;
|
|
379
402
|
}
|
|
380
403
|
|
|
@@ -507,19 +530,27 @@ async function addAnnotation(shared: any, ontology: any, subjectIri: string, pre
|
|
|
507
530
|
const normalizedPredicate = normalizeIri(predicateIri);
|
|
508
531
|
await ensureReferenceImport(shared, ontology, normalizedPredicate);
|
|
509
532
|
subject.ownedAnnotations ??= [];
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
+
}
|
|
516
547
|
if (isIriLike(objectValue)) {
|
|
517
548
|
await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
|
|
518
549
|
} else {
|
|
519
550
|
await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
|
|
520
551
|
}
|
|
521
552
|
appendAnnotationValue(ontology, annotation, objectValue);
|
|
522
|
-
|
|
553
|
+
normalizeAllAnnotations(subject, ontology);
|
|
523
554
|
return true;
|
|
524
555
|
}
|
|
525
556
|
|
|
@@ -928,7 +959,7 @@ function removeAssertionValue(ontology: any, assertion: any, objectValue: unknow
|
|
|
928
959
|
assertion.referencedValues = (assertion.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
|
|
929
960
|
return;
|
|
930
961
|
}
|
|
931
|
-
const expectedLiteral =
|
|
962
|
+
const expectedLiteral = toLiteralRemovalMatchSpec(ontology, objectValue);
|
|
932
963
|
assertion.literalValues = (assertion.literalValues ?? []).filter((literal: any) => !matchesLiteralForRemoval(ontology, literal, expectedLiteral));
|
|
933
964
|
}
|
|
934
965
|
|
|
@@ -949,7 +980,7 @@ function removeAnnotationValue(ontology: any, annotation: any, objectValue: unkn
|
|
|
949
980
|
annotation.referencedValues = (annotation.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
|
|
950
981
|
return;
|
|
951
982
|
}
|
|
952
|
-
const expectedLiteral =
|
|
983
|
+
const expectedLiteral = toLiteralRemovalMatchSpec(ontology, objectValue);
|
|
953
984
|
annotation.literalValues = (annotation.literalValues ?? []).filter((literal: any) => !matchesLiteralForRemoval(ontology, literal, expectedLiteral));
|
|
954
985
|
}
|
|
955
986
|
|
|
@@ -968,15 +999,44 @@ function literalTypeIri(ontology: any, literal: any): string {
|
|
|
968
999
|
return normalizeIri(expandRefTextToIri(ontology, refText) ?? refText);
|
|
969
1000
|
}
|
|
970
1001
|
|
|
971
|
-
|
|
972
|
-
|
|
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)) {
|
|
973
1030
|
return false;
|
|
974
1031
|
}
|
|
975
|
-
const expectedType =
|
|
1032
|
+
const expectedType = expected.typeIri ?? '';
|
|
976
1033
|
if (!expectedType) {
|
|
977
1034
|
return true;
|
|
978
1035
|
}
|
|
979
1036
|
const existingType = literalTypeIri(ontology, existingLiteral);
|
|
1037
|
+
if (!existingType) {
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
980
1040
|
return isSameIriTarget(existingType, expectedType);
|
|
981
1041
|
}
|
|
982
1042
|
|
|
@@ -1324,13 +1384,19 @@ function pickImportPrefix(namespace: string, usedPrefixes: ReadonlySet<string>):
|
|
|
1324
1384
|
function asLiteral(ontology: any, value: unknown): any {
|
|
1325
1385
|
if (isTypedQuotedLiteralTransport(value)) {
|
|
1326
1386
|
const literalValue = String(value.value ?? '');
|
|
1327
|
-
const
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
:
|
|
1332
|
-
|
|
1333
|
-
|
|
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;
|
|
1334
1400
|
}
|
|
1335
1401
|
if (typeof value === 'boolean') {
|
|
1336
1402
|
return { $type: 'BooleanLiteral', value };
|
|
@@ -1348,14 +1414,24 @@ async function ensureTypedLiteralDatatypeImport(shared: any, ontology: any, valu
|
|
|
1348
1414
|
if (!isTypedQuotedLiteralTransport(value)) {
|
|
1349
1415
|
return;
|
|
1350
1416
|
}
|
|
1351
|
-
const datatypeIri =
|
|
1352
|
-
if (!datatypeIri) {
|
|
1417
|
+
const datatypeIri = literalDatatypeIriFromTransport(ontology, value);
|
|
1418
|
+
if (!datatypeIri || isXsdStringDatatypeIri(ontology, datatypeIri)) {
|
|
1353
1419
|
return;
|
|
1354
1420
|
}
|
|
1355
1421
|
await ensureReferenceImport(shared, ontology, datatypeIri);
|
|
1356
1422
|
}
|
|
1357
1423
|
|
|
1358
|
-
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
|
+
} {
|
|
1359
1435
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1360
1436
|
return false;
|
|
1361
1437
|
}
|
|
@@ -1363,7 +1439,71 @@ function isTypedQuotedLiteralTransport(value: unknown): value is { $type?: unkno
|
|
|
1363
1439
|
if (record.$type === 'QuotedLiteral') {
|
|
1364
1440
|
return true;
|
|
1365
1441
|
}
|
|
1366
|
-
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;
|
|
1367
1507
|
}
|
|
1368
1508
|
|
|
1369
1509
|
function attachAstNode<T extends Record<string, any>>(node: T, parent: any, containerProperty: string): T {
|
|
@@ -1405,6 +1545,113 @@ function normalizeIri(value: string): string {
|
|
|
1405
1545
|
return String(value ?? '').trim().replace(/^<|>$/g, '');
|
|
1406
1546
|
}
|
|
1407
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
|
+
|
|
1408
1655
|
function isSourcePredicate(predicateIri: string): boolean {
|
|
1409
1656
|
return normalizeIri(predicateIri) === OML_HAS_SOURCE_IRI;
|
|
1410
1657
|
}
|
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) {
|