@oml/language 0.19.1 → 0.19.3

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.
@@ -101,10 +101,26 @@ export class OmlScopeComputation extends DefaultScopeComputation {
101
101
  */
102
102
  export class OmlScopeProvider extends DefaultScopeProvider {
103
103
  protected services: LangiumCoreServices;
104
+ // Descriptions for an ontology's *own* exported symbols, keyed by ontology then referenceType.
105
+ // WeakMap so a re-parsed ontology (new AST root) naturally drops its old entry.
106
+ private ownDescCache = new WeakMap<Ontology, Map<string, AstNodeDescription[]>>();
107
+ // Descriptions contributed by an ontology's *imports* (all imports combined), keyed by the
108
+ // importing ontology then referenceType. The `prefix:name` variant uses the importer's prefix,
109
+ // so this is correctly indexed on the importer side.
110
+ private importedDescCache = new WeakMap<Ontology, Map<string, AstNodeDescription[]>>();
104
111
 
105
112
  constructor(services: LangiumCoreServices) {
106
113
  super(services);
107
114
  this.services = services;
115
+ // Wipe caches on any document update. Within a sustained linking pass (the hot phase)
116
+ // no documents are mutating, so the caches stay warm. After an update the importer-side
117
+ // cache could otherwise hold descriptions pointing at stale (re-parsed) imported nodes,
118
+ // so we reset rather than try to invalidate selectively.
119
+ const shared = (services as any).shared as LangiumSharedServices | undefined;
120
+ shared?.workspace.DocumentBuilder.onUpdate(() => {
121
+ this.ownDescCache = new WeakMap();
122
+ this.importedDescCache = new WeakMap();
123
+ });
108
124
  }
109
125
 
110
126
  override getScope(context: ReferenceInfo): Scope {
@@ -233,34 +249,52 @@ export class OmlScopeProvider extends DefaultScopeProvider {
233
249
  return resolved;
234
250
  }
235
251
 
236
- private addDescription(scope: Scope, node: any, name: string, document: LangiumDocument): Scope {
252
+ private pushDescription(out: AstNodeDescription[], node: any, name: string, document: LangiumDocument): void {
237
253
  try {
238
- const desc = this.descriptions.createDescription(node, name, document);
239
- return this.createScope(stream([desc]), scope);
254
+ out.push(this.descriptions.createDescription(node, name, document));
240
255
  } catch {
241
- return scope;
256
+ // ignore — a missing name or invalid node should not abort the rest of the scope
242
257
  }
243
258
  }
244
259
 
245
- private addCurrentOntologyEntries(scope: Scope, document: LangiumDocument, ontology: Ontology, referenceType: string): Scope {
246
- let result = scope;
260
+ private getOwnDescriptions(document: LangiumDocument, ontology: Ontology, referenceType: string): AstNodeDescription[] {
261
+ let perType = this.ownDescCache.get(ontology);
262
+ if (!perType) {
263
+ perType = new Map();
264
+ this.ownDescCache.set(ontology, perType);
265
+ }
266
+ const cached = perType.get(referenceType);
267
+ if (cached) {
268
+ return cached;
269
+ }
270
+ const descs: AstNodeDescription[] = [];
247
271
  const prefix = (ontology as any).prefix as string | undefined;
248
272
  for (const symbol of this.getExportedSymbols(ontology)) {
249
273
  if (!this.reflection.isSubtype(symbol.node.$type, referenceType)) {
250
274
  continue;
251
275
  }
252
276
  if (prefix) {
253
- result = this.addDescription(result, symbol.node, `${prefix}:${symbol.name}`, document);
277
+ this.pushDescription(descs, symbol.node, `${prefix}:${symbol.name}`, document);
254
278
  }
255
279
  if (symbol.relationAlias) {
256
- result = this.addDescription(result, symbol.node, symbol.name, document);
280
+ this.pushDescription(descs, symbol.node, symbol.name, document);
257
281
  }
258
282
  }
259
- return result;
283
+ perType.set(referenceType, descs);
284
+ return descs;
260
285
  }
261
286
 
262
- private addImportedOntologyEntries(scope: Scope, current: Ontology, referenceType: string): Scope {
263
- let result = scope;
287
+ private getImportedDescriptions(current: Ontology, referenceType: string): AstNodeDescription[] {
288
+ let perType = this.importedDescCache.get(current);
289
+ if (!perType) {
290
+ perType = new Map();
291
+ this.importedDescCache.set(current, perType);
292
+ }
293
+ const cached = perType.get(referenceType);
294
+ if (cached) {
295
+ return cached;
296
+ }
297
+ const descs: AstNodeDescription[] = [];
264
298
  for (const resolvedImport of this.resolveImports(current)) {
265
299
  const imported = resolvedImport.target;
266
300
  const importedSymbols = this.getExportedSymbols(imported.ontology);
@@ -269,18 +303,24 @@ export class OmlScopeProvider extends DefaultScopeProvider {
269
303
  continue;
270
304
  }
271
305
  if (resolvedImport.prefix) {
272
- result = this.addDescription(result, symbol.node, `${resolvedImport.prefix}:${symbol.name}`, imported.document);
306
+ this.pushDescription(descs, symbol.node, `${resolvedImport.prefix}:${symbol.name}`, imported.document);
273
307
  }
274
308
  const separator = imported.namespace.endsWith('#') || imported.namespace.endsWith('/') ? '' : '#';
275
- result = this.addDescription(
276
- result,
277
- symbol.node,
278
- `<${imported.namespace}${separator}${symbol.name}>`,
279
- imported.document
280
- );
309
+ this.pushDescription(descs, symbol.node, `<${imported.namespace}${separator}${symbol.name}>`, imported.document);
281
310
  }
282
311
  }
283
- return result;
312
+ perType.set(referenceType, descs);
313
+ return descs;
314
+ }
315
+
316
+ private addCurrentOntologyEntries(scope: Scope, document: LangiumDocument, ontology: Ontology, referenceType: string): Scope {
317
+ const descs = this.getOwnDescriptions(document, ontology, referenceType);
318
+ return descs.length === 0 ? scope : this.createScope(stream(descs), scope);
319
+ }
320
+
321
+ private addImportedOntologyEntries(scope: Scope, current: Ontology, referenceType: string): Scope {
322
+ const descs = this.getImportedDescriptions(current, referenceType);
323
+ return descs.length === 0 ? scope : this.createScope(stream(descs), scope);
284
324
  }
285
325
 
286
326
  }
@@ -86,6 +86,8 @@ import {
86
86
  isVocabulary,
87
87
  isVocabularyBundle,
88
88
  } from './generated/ast.js';
89
+ import { escapeIdentifier } from './oml-identifiers.js';
90
+ import { formatDoubleLiteralValue } from './oml-literals.js';
89
91
  import {
90
92
  findOwningOntologyNode,
91
93
  normalizeNamespace,
@@ -857,12 +859,9 @@ const serializeLiteral = (literal: Literal): string => {
857
859
  return appendUnitSuffix(base, literal);
858
860
  }
859
861
  if (isDoubleLiteral(literal)) {
860
- const v = literal.value as unknown;
861
862
  // Langium parses double literals to JS numbers, losing the required e-notation.
862
- // OML DOUBLE terminal requires e/E; always emit it.
863
- const base = (typeof v === 'number' && Number.isFinite(v))
864
- ? (Number.isInteger(v) ? `${v}.0e0` : `${v}e0`)
865
- : `${literal.value}`;
863
+ // OML DOUBLE terminal requires e/E, but JS may already stringify small numbers with one.
864
+ const base = formatDoubleLiteralValue(literal.value);
866
865
  return appendUnitSuffix(base, literal);
867
866
  }
868
867
  return '';
@@ -977,16 +976,6 @@ const formatIriRef = (iri: string): string => {
977
976
  return `<${iri}>`;
978
977
  };
979
978
 
980
- const escapeIdentifier = (value: string): string => {
981
- if (!value) {
982
- return '';
983
- }
984
- if (value.startsWith('^')) {
985
- return value;
986
- }
987
- return OML_KEYWORDS.has(value) ? `^${value}` : value;
988
- };
989
-
990
979
  const serializeName = (value?: string): string => escapeIdentifier(value ?? '');
991
980
 
992
981
  const createSerializeContext = (ontology: Ontology): SerializeContext => {
@@ -1041,58 +1030,3 @@ const pushAnnotations = (lines: string[], indent: string, annotations: Annotatio
1041
1030
  const stripAngles = (value: string): string => value.replace(/^<|>$/g, '');
1042
1031
 
1043
1032
  const wrapNamespace = (value: string): string => `<${stripAngles(value)}>`;
1044
-
1045
- const OML_KEYWORDS = new Set([
1046
- 'all',
1047
- 'annotation',
1048
- 'as',
1049
- 'aspect',
1050
- 'asymmetric',
1051
- 'builtIn',
1052
- 'builtin',
1053
- 'bundle',
1054
- 'concept',
1055
- 'description',
1056
- 'differentFrom',
1057
- 'domain',
1058
- 'entity',
1059
- 'exactly',
1060
- 'extends',
1061
- 'forward',
1062
- 'from',
1063
- 'functional',
1064
- 'includes',
1065
- 'instance',
1066
- 'inverse',
1067
- 'irreflexive',
1068
- 'key',
1069
- 'language',
1070
- 'length',
1071
- 'max',
1072
- 'maxExclusive',
1073
- 'maxInclusive',
1074
- 'maxLength',
1075
- 'min',
1076
- 'minExclusive',
1077
- 'minInclusive',
1078
- 'minLength',
1079
- 'oneOf',
1080
- 'pattern',
1081
- 'property',
1082
- 'range',
1083
- 'ref',
1084
- 'reflexive',
1085
- 'relation',
1086
- 'restricts',
1087
- 'reverse',
1088
- 'rule',
1089
- 'sameAs',
1090
- 'scalar',
1091
- 'self',
1092
- 'some',
1093
- 'symmetric',
1094
- 'to',
1095
- 'transitive',
1096
- 'uses',
1097
- 'vocabulary'
1098
- ]);
@@ -319,9 +319,9 @@ async function executeOperation(
319
319
  return new Set([context.ontologyIri]);
320
320
  case 'createInstanceRef':
321
321
  ensureDescriptionOntology(context.ontology, operation.kind);
322
- await ensureReferenceImport(shared, context.ontology, operation.instanceIri);
322
+ await ensureReferenceImport(shared, context.ontology, operation.instanceIri, contexts);
323
323
  if (operation.typeIri) {
324
- await ensureReferenceImport(shared, context.ontology, operation.typeIri);
324
+ await ensureReferenceImport(shared, context.ontology, operation.typeIri, contexts);
325
325
  }
326
326
  {
327
327
  const ownedTypes: any[] = [];
@@ -347,9 +347,9 @@ async function executeOperation(
347
347
  return new Set([context.ontologyIri]);
348
348
  case 'createRelationInstanceRef':
349
349
  ensureDescriptionOntology(context.ontology, operation.kind);
350
- await ensureReferenceImport(shared, context.ontology, operation.instanceIri);
350
+ await ensureReferenceImport(shared, context.ontology, operation.instanceIri, contexts);
351
351
  if (operation.typeIri) {
352
- await ensureReferenceImport(shared, context.ontology, operation.typeIri);
352
+ await ensureReferenceImport(shared, context.ontology, operation.typeIri, contexts);
353
353
  }
354
354
  {
355
355
  const ownedTypes: any[] = [];
@@ -377,12 +377,12 @@ async function executeOperation(
377
377
  return new Set([context.ontologyIri]);
378
378
  case 'addAssertion':
379
379
  ensureDescriptionOntology(context.ontology, operation.kind);
380
- return await addAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
380
+ return await addAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object, contexts)
381
381
  ? new Set([context.ontologyIri])
382
382
  : new Set();
383
383
  case 'updateAssertion':
384
384
  ensureDescriptionOntology(context.ontology, operation.kind);
385
- return await updateAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
385
+ return await updateAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object, contexts)
386
386
  ? new Set([context.ontologyIri])
387
387
  : new Set();
388
388
  case 'removeAssertion':
@@ -391,11 +391,11 @@ async function executeOperation(
391
391
  ? new Set([context.ontologyIri])
392
392
  : new Set();
393
393
  case 'addAnnotation':
394
- return await addAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
394
+ return await addAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object, contexts)
395
395
  ? new Set([context.ontologyIri])
396
396
  : new Set();
397
397
  case 'updateAnnotation':
398
- return await updateAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
398
+ return await updateAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object, contexts)
399
399
  ? new Set([context.ontologyIri])
400
400
  : new Set();
401
401
  case 'removeAnnotation':
@@ -457,7 +457,14 @@ function ensureUniqueLocalName(ontology: any, memberName: string): void {
457
457
  }
458
458
  }
459
459
 
460
- async function addAssertion(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
460
+ async function addAssertion(
461
+ shared: any,
462
+ ontology: any,
463
+ subjectIri: string,
464
+ predicateIri: string,
465
+ objectValue: unknown,
466
+ contexts?: ReadonlyMap<string, OntologyContext>
467
+ ): Promise<boolean> {
461
468
  const normalizedPredicate = normalizeIri(predicateIri);
462
469
  const subject = findNamedInstanceByIri(ontology, subjectIri);
463
470
  if (!subject) {
@@ -469,7 +476,7 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
469
476
  if ((subject.ownedTypes ?? []).some((entry: any) => matchesPredicateRef(ontology, entry?.type, normalizedTypeIri))) {
470
477
  return false;
471
478
  }
472
- await ensureReferenceImport(shared, ontology, normalizedTypeIri);
479
+ await ensureReferenceImport(shared, ontology, normalizedTypeIri, contexts);
473
480
  subject.ownedTypes ??= [];
474
481
  pushAstArrayChild(subject, 'ownedTypes', {
475
482
  $type: 'TypeAssertion',
@@ -483,7 +490,7 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
483
490
  throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
484
491
  }
485
492
  const sourceIri = normalizeIri(asRequiredIri(objectValue, 'source assertion object'));
486
- await ensureReferenceImport(shared, ontology, sourceIri);
493
+ await ensureReferenceImport(shared, ontology, sourceIri, contexts);
487
494
  subject.sources ??= [];
488
495
  subject.sources.push(asRef(ontology, sourceIri));
489
496
  return true;
@@ -494,7 +501,7 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
494
501
  throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
495
502
  }
496
503
  const targetIri = normalizeIri(asRequiredIri(objectValue, 'target assertion object'));
497
- await ensureReferenceImport(shared, ontology, targetIri);
504
+ await ensureReferenceImport(shared, ontology, targetIri, contexts);
498
505
  subject.targets ??= [];
499
506
  subject.targets.push(asRef(ontology, targetIri));
500
507
  return true;
@@ -505,7 +512,7 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
505
512
  .filter((assertion: any) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
506
513
  let propertyAssertion = matchingAssertions[0];
507
514
  if (!propertyAssertion) {
508
- await ensureReferenceImport(shared, ontology, normalizedPredicate);
515
+ await ensureReferenceImport(shared, ontology, normalizedPredicate, contexts);
509
516
  propertyAssertion = {
510
517
  $type: 'PropertyValueAssertion',
511
518
  property: asRef(ontology, normalizedPredicate),
@@ -518,9 +525,9 @@ async function addAssertion(shared: any, ontology: any, subjectIri: string, pred
518
525
  normalizePropertyAssertionGroup(subject, propertyAssertion, matchingAssertions.slice(1));
519
526
  }
520
527
  if (isIriLike(objectValue)) {
521
- await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
528
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')), contexts);
522
529
  } else {
523
- await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
530
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue, contexts);
524
531
  }
525
532
  appendAssertionValue(ontology, propertyAssertion, objectValue);
526
533
  normalizeAllPropertyAssertions(subject, ontology);
@@ -618,7 +625,14 @@ function removeAssertion(ontology: any, subjectIri: string, predicateIri: string
618
625
  return changed;
619
626
  }
620
627
 
621
- async function updateAssertion(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
628
+ async function updateAssertion(
629
+ shared: any,
630
+ ontology: any,
631
+ subjectIri: string,
632
+ predicateIri: string,
633
+ objectValue: unknown,
634
+ contexts?: ReadonlyMap<string, OntologyContext>
635
+ ): Promise<boolean> {
622
636
  const normalizedPredicate = normalizeIri(predicateIri);
623
637
  const subject = findNamedInstanceByIri(ontology, subjectIri);
624
638
  if (!subject) {
@@ -627,19 +641,19 @@ async function updateAssertion(shared: any, ontology: any, subjectIri: string, p
627
641
 
628
642
  if (normalizedPredicate === RDF_TYPE_IRI || isSourcePredicate(normalizedPredicate) || isTargetPredicate(normalizedPredicate)) {
629
643
  const removed = removeAssertion(ontology, subjectIri, predicateIri, undefined);
630
- const added = await addAssertion(shared, ontology, subjectIri, predicateIri, objectValue);
644
+ const added = await addAssertion(shared, ontology, subjectIri, predicateIri, objectValue, contexts);
631
645
  return removed || added;
632
646
  }
633
647
 
634
648
  subject.ownedPropertyValues ??= [];
635
649
  let propertyAssertion = subject.ownedPropertyValues.find((assertion: any) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
636
650
  if (!propertyAssertion) {
637
- return addAssertion(shared, ontology, subjectIri, predicateIri, objectValue);
651
+ return addAssertion(shared, ontology, subjectIri, predicateIri, objectValue, contexts);
638
652
  }
639
653
  if (isIriLike(objectValue)) {
640
- await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
654
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')), contexts);
641
655
  } else {
642
- await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
656
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue, contexts);
643
657
  }
644
658
  propertyAssertion.literalValues = [];
645
659
  propertyAssertion.referencedValues = [];
@@ -648,13 +662,20 @@ async function updateAssertion(shared: any, ontology: any, subjectIri: string, p
648
662
  return true;
649
663
  }
650
664
 
651
- async function addAnnotation(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
665
+ async function addAnnotation(
666
+ shared: any,
667
+ ontology: any,
668
+ subjectIri: string,
669
+ predicateIri: string,
670
+ objectValue: unknown,
671
+ contexts?: ReadonlyMap<string, OntologyContext>
672
+ ): Promise<boolean> {
652
673
  const subject = findAnnotationSubjectByIri(ontology, subjectIri);
653
674
  if (!subject) {
654
675
  throw new Error(`Annotation subject '${subjectIri}' was not found in the ontology.`);
655
676
  }
656
677
  const normalizedPredicate = normalizeIri(predicateIri);
657
- await ensureReferenceImport(shared, ontology, normalizedPredicate);
678
+ await ensureReferenceImport(shared, ontology, normalizedPredicate, contexts);
658
679
  subject.ownedAnnotations ??= [];
659
680
  const matchingAnnotations = subject.ownedAnnotations
660
681
  .filter((entry: any) => matchesPredicateRef(ontology, entry?.property, normalizedPredicate));
@@ -671,9 +692,9 @@ async function addAnnotation(shared: any, ontology: any, subjectIri: string, pre
671
692
  normalizeAnnotationGroup(subject, annotation, matchingAnnotations.slice(1));
672
693
  }
673
694
  if (isIriLike(objectValue)) {
674
- await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
695
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')), contexts);
675
696
  } else {
676
- await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
697
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue, contexts);
677
698
  }
678
699
  appendAnnotationValue(ontology, annotation, objectValue);
679
700
  normalizeAllAnnotations(subject, ontology);
@@ -718,7 +739,14 @@ function removeAnnotation(ontology: any, subjectIri: string, predicateIri: strin
718
739
  return changed;
719
740
  }
720
741
 
721
- async function updateAnnotation(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
742
+ async function updateAnnotation(
743
+ shared: any,
744
+ ontology: any,
745
+ subjectIri: string,
746
+ predicateIri: string,
747
+ objectValue: unknown,
748
+ contexts?: ReadonlyMap<string, OntologyContext>
749
+ ): Promise<boolean> {
722
750
  const subject = findAnnotationSubjectByIri(ontology, subjectIri);
723
751
  if (!subject) {
724
752
  throw new Error(`Annotation subject '${subjectIri}' was not found in the ontology.`);
@@ -727,12 +755,12 @@ async function updateAnnotation(shared: any, ontology: any, subjectIri: string,
727
755
  subject.ownedAnnotations ??= [];
728
756
  const annotation = subject.ownedAnnotations.find((entry: any) => matchesPredicateRef(ontology, entry?.property, normalizedPredicate));
729
757
  if (!annotation) {
730
- return addAnnotation(shared, ontology, subjectIri, predicateIri, objectValue);
758
+ return addAnnotation(shared, ontology, subjectIri, predicateIri, objectValue, contexts);
731
759
  }
732
760
  if (isIriLike(objectValue)) {
733
- await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
761
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')), contexts);
734
762
  } else {
735
- await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
763
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue, contexts);
736
764
  }
737
765
  annotation.literalValues = [];
738
766
  annotation.referencedValues = [];
@@ -1190,7 +1218,12 @@ function toRefText(ontology: any, iri: string): string {
1190
1218
  return `<${normalizedIri}>`;
1191
1219
  }
1192
1220
 
1193
- async function ensureReferenceImport(shared: any, ontology: any, iri: string): Promise<void> {
1221
+ async function ensureReferenceImport(
1222
+ shared: any,
1223
+ ontology: any,
1224
+ iri: string,
1225
+ contexts?: ReadonlyMap<string, OntologyContext>
1226
+ ): Promise<void> {
1194
1227
  const target = parseIriParts(iri);
1195
1228
  if (!target) {
1196
1229
  return;
@@ -1215,7 +1248,8 @@ async function ensureReferenceImport(shared: any, ontology: any, iri: string): P
1215
1248
  const importedNamespace = normalizeNamespace(String(resolveImportNamespaceRaw(imp) ?? ''));
1216
1249
  return importedNamespace === target.namespace;
1217
1250
  });
1218
- const importedOntology = await findOntologyByNamespace(shared, target.namespace);
1251
+ const importedOntology = findOntologyInContexts(contexts, target.namespace)
1252
+ ?? await findOntologyByNamespace(shared, target.namespace);
1219
1253
  const declaredPrefix = typeof importedOntology?.prefix === 'string' ? importedOntology.prefix.trim() : '';
1220
1254
  const preferredPrefix = declaredPrefix && !usedPrefixes.has(declaredPrefix)
1221
1255
  ? declaredPrefix
@@ -1363,6 +1397,13 @@ async function findOntologyByNamespace(shared: any, namespace: string): Promise<
1363
1397
  return ontology && isOntology(ontology) ? ontology : undefined;
1364
1398
  }
1365
1399
 
1400
+ function findOntologyInContexts(contexts: ReadonlyMap<string, OntologyContext> | undefined, namespace: string): any | undefined {
1401
+ if (!contexts) {
1402
+ return undefined;
1403
+ }
1404
+ return contexts.get(normalizeOntologyIriKey(namespace))?.ontology;
1405
+ }
1406
+
1366
1407
  function matchesRefTextTarget(
1367
1408
  ontology: any,
1368
1409
  statement: any,
@@ -1423,6 +1464,9 @@ function matchesRefTextFragment(statement: any, fragment: string): boolean {
1423
1464
  }
1424
1465
 
1425
1466
  function asLiteral(ontology: any, value: unknown): any {
1467
+ if (isExplicitLiteralTransport(value)) {
1468
+ return explicitLiteralFromTransport(ontology, value);
1469
+ }
1426
1470
  if (isQuantityLiteralTransport(value)) {
1427
1471
  const raw = value.value;
1428
1472
  const num = typeof raw === 'number' ? raw : Number(raw);
@@ -1454,12 +1498,6 @@ function asLiteral(ontology: any, value: unknown): any {
1454
1498
  }
1455
1499
  return literal;
1456
1500
  }
1457
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1458
- const record = value as Record<string, unknown>;
1459
- if ((record.$type === 'DoubleLiteral' || record.$type === 'DecimalLiteral') && 'value' in record) {
1460
- return { $type: record.$type, value: record.value };
1461
- }
1462
- }
1463
1501
  if (typeof value === 'boolean') {
1464
1502
  return { $type: 'BooleanLiteral', value };
1465
1503
  }
@@ -1472,17 +1510,125 @@ function asLiteral(ontology: any, value: unknown): any {
1472
1510
  return { $type: 'QuotedLiteral', value: String(value ?? '') };
1473
1511
  }
1474
1512
 
1475
- async function ensureTypedLiteralDatatypeImport(shared: any, ontology: any, value: unknown): Promise<void> {
1513
+ function isExplicitLiteralTransport(value: unknown): value is {
1514
+ $type: 'BooleanLiteral' | 'DecimalLiteral' | 'DoubleLiteral' | 'IntegerLiteral' | 'QuotedLiteral';
1515
+ value?: unknown;
1516
+ langTag?: unknown;
1517
+ language?: unknown;
1518
+ datatypeIri?: unknown;
1519
+ datatypeRefText?: unknown;
1520
+ typeIri?: unknown;
1521
+ typeRefText?: unknown;
1522
+ type?: unknown;
1523
+ unitIri?: unknown;
1524
+ unitRefText?: unknown;
1525
+ unit?: unknown;
1526
+ } {
1527
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1528
+ return false;
1529
+ }
1530
+ const type = (value as Record<string, unknown>).$type;
1531
+ return type === 'BooleanLiteral'
1532
+ || type === 'DecimalLiteral'
1533
+ || type === 'DoubleLiteral'
1534
+ || type === 'IntegerLiteral'
1535
+ || type === 'QuotedLiteral';
1536
+ }
1537
+
1538
+ function explicitLiteralFromTransport(ontology: any, value: {
1539
+ $type: 'BooleanLiteral' | 'DecimalLiteral' | 'DoubleLiteral' | 'IntegerLiteral' | 'QuotedLiteral';
1540
+ value?: unknown;
1541
+ langTag?: unknown;
1542
+ language?: unknown;
1543
+ datatypeIri?: unknown;
1544
+ datatypeRefText?: unknown;
1545
+ typeIri?: unknown;
1546
+ typeRefText?: unknown;
1547
+ type?: unknown;
1548
+ unitIri?: unknown;
1549
+ unitRefText?: unknown;
1550
+ unit?: unknown;
1551
+ }): Record<string, unknown> {
1552
+ if (value.$type === 'QuotedLiteral') {
1553
+ const literalValue = String(value.value ?? '');
1554
+ const langTag = literalLanguageTagFromTransport(value);
1555
+ const datatypeIri = literalDatatypeIriFromTransport(ontology, value);
1556
+ const literal: Record<string, unknown> = {
1557
+ $type: 'QuotedLiteral',
1558
+ value: literalValue,
1559
+ };
1560
+ if (langTag) {
1561
+ literal.langTag = langTag;
1562
+ }
1563
+ if (datatypeIri && !isXsdStringDatatypeIri(ontology, datatypeIri)) {
1564
+ literal.type = { $refText: toRefText(ontology, datatypeIri) };
1565
+ }
1566
+ return literal;
1567
+ }
1568
+ if (value.$type === 'BooleanLiteral') {
1569
+ return {
1570
+ $type: 'BooleanLiteral',
1571
+ value: booleanLiteralValue(value.value),
1572
+ };
1573
+ }
1574
+ const literal: Record<string, unknown> = {
1575
+ $type: value.$type,
1576
+ value: numericLiteralValue(value.$type, value.value),
1577
+ };
1578
+ const unitIri = literalUnitIriFromTransport(ontology, value);
1579
+ if (unitIri) {
1580
+ literal.unit = { $refText: toRefText(ontology, unitIri) };
1581
+ }
1582
+ return literal;
1583
+ }
1584
+
1585
+ function numericLiteralValue(type: 'DecimalLiteral' | 'DoubleLiteral' | 'IntegerLiteral', value: unknown): number {
1586
+ const numberValue = typeof value === 'number'
1587
+ ? value
1588
+ : typeof value === 'string' && value.trim().length > 0
1589
+ ? Number(value.trim())
1590
+ : Number.NaN;
1591
+ if (!Number.isFinite(numberValue)) {
1592
+ throw new Error(`${type} value must be a finite number.`);
1593
+ }
1594
+ if (type === 'IntegerLiteral' && !Number.isInteger(numberValue)) {
1595
+ throw new Error('IntegerLiteral value must be an integer.');
1596
+ }
1597
+ return numberValue;
1598
+ }
1599
+
1600
+ function booleanLiteralValue(value: unknown): boolean {
1601
+ if (typeof value === 'boolean') {
1602
+ return value;
1603
+ }
1604
+ if (typeof value === 'string') {
1605
+ const normalized = value.trim().toLowerCase();
1606
+ if (normalized === 'true') {
1607
+ return true;
1608
+ }
1609
+ if (normalized === 'false') {
1610
+ return false;
1611
+ }
1612
+ }
1613
+ throw new Error('BooleanLiteral value must be true or false.');
1614
+ }
1615
+
1616
+ async function ensureTypedLiteralDatatypeImport(
1617
+ shared: any,
1618
+ ontology: any,
1619
+ value: unknown,
1620
+ contexts?: ReadonlyMap<string, OntologyContext>
1621
+ ): Promise<void> {
1476
1622
  if (isTypedQuotedLiteralTransport(value)) {
1477
1623
  const datatypeIri = literalDatatypeIriFromTransport(ontology, value);
1478
1624
  if (datatypeIri && !isXsdStringDatatypeIri(ontology, datatypeIri)) {
1479
- await ensureReferenceImport(shared, ontology, datatypeIri);
1625
+ await ensureReferenceImport(shared, ontology, datatypeIri, contexts);
1480
1626
  }
1481
1627
  }
1482
1628
  if (isQuantityLiteralTransport(value)) {
1483
1629
  const unitIri = literalUnitIriFromTransport(ontology, value);
1484
1630
  if (unitIri) {
1485
- await ensureReferenceImport(shared, ontology, unitIri);
1631
+ await ensureReferenceImport(shared, ontology, unitIri, contexts);
1486
1632
  }
1487
1633
  }
1488
1634
  }