@oml/language 0.8.0 → 0.10.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.
@@ -6,12 +6,15 @@ import {
6
6
  isAnnotation,
7
7
  isConceptInstance,
8
8
  isDescription,
9
+ isDescriptionBox,
9
10
  isImport,
10
11
  isOntology,
11
12
  isPropertyValueAssertion,
12
13
  isRelationInstance,
13
14
  isRule,
14
15
  isTypeAssertion,
16
+ isVocabulary,
17
+ isVocabularyBundle,
15
18
  } from './generated/ast.js';
16
19
  import { getOntologyModelIndex } from './oml-index.js';
17
20
  import { serializeOntology } from './oml-serializer.js';
@@ -40,8 +43,10 @@ type OmlEditOperation =
40
43
  | { kind: 'createInstanceRef'; ontologyIri: string; instanceIri: string; typeIri?: string }
41
44
  | { kind: 'createRelationInstanceRef'; ontologyIri: string; instanceIri: string; typeIri?: string }
42
45
  | { kind: 'addAssertion'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
46
+ | { kind: 'updateAssertion'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
43
47
  | { kind: 'removeAssertion'; ontologyIri: string; subjectIri: string; predicateIri: string; object?: unknown }
44
48
  | { kind: 'addAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
49
+ | { kind: 'updateAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object: unknown }
45
50
  | { kind: 'removeAnnotation'; ontologyIri: string; subjectIri: string; predicateIri: string; object?: unknown }
46
51
  | { kind: 'deleteMemberCascade'; ontologyIri: string; memberIri: string }
47
52
  | { kind: 'deleteMemberRef'; ontologyIri: string; memberIri: string; typeIri?: string };
@@ -149,8 +154,7 @@ async function executeOperation(
149
154
  ownedPropertyValues: [],
150
155
  ownedTypes: []
151
156
  };
152
- statement.$container = context.ontology;
153
- context.ontology.ownedStatements.push(statement);
157
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
154
158
  }
155
159
  return new Set([context.ontologyIri]);
156
160
  case 'createRelationInstance':
@@ -166,15 +170,14 @@ async function executeOperation(
166
170
  sources: [],
167
171
  targets: []
168
172
  };
169
- statement.$container = context.ontology;
170
- context.ontology.ownedStatements.push(statement);
173
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
171
174
  }
172
175
  return new Set([context.ontologyIri]);
173
176
  case 'createInstanceRef':
174
177
  ensureDescriptionOntology(context.ontology, operation.kind);
175
- ensureReferenceImport(context.ontology, operation.instanceIri);
178
+ await ensureReferenceImport(shared, context.ontology, operation.instanceIri);
176
179
  if (operation.typeIri) {
177
- ensureReferenceImport(context.ontology, operation.typeIri);
180
+ await ensureReferenceImport(shared, context.ontology, operation.typeIri);
178
181
  }
179
182
  {
180
183
  const ownedTypes: any[] = [];
@@ -192,15 +195,17 @@ async function executeOperation(
192
195
  ownedTypes
193
196
  };
194
197
  statement.__targetIri = normalizeIri(operation.instanceIri);
195
- statement.$container = context.ontology;
196
- context.ontology.ownedStatements.push(statement);
198
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
199
+ for (const typeAssertion of ownedTypes) {
200
+ attachAstNode(typeAssertion, statement, 'ownedTypes');
201
+ }
197
202
  }
198
203
  return new Set([context.ontologyIri]);
199
204
  case 'createRelationInstanceRef':
200
205
  ensureDescriptionOntology(context.ontology, operation.kind);
201
- ensureReferenceImport(context.ontology, operation.instanceIri);
206
+ await ensureReferenceImport(shared, context.ontology, operation.instanceIri);
202
207
  if (operation.typeIri) {
203
- ensureReferenceImport(context.ontology, operation.typeIri);
208
+ await ensureReferenceImport(shared, context.ontology, operation.typeIri);
204
209
  }
205
210
  {
206
211
  const ownedTypes: any[] = [];
@@ -220,13 +225,20 @@ async function executeOperation(
220
225
  targets: []
221
226
  };
222
227
  statement.__targetIri = normalizeIri(operation.instanceIri);
223
- statement.$container = context.ontology;
224
- context.ontology.ownedStatements.push(statement);
228
+ pushAstArrayChild(context.ontology, 'ownedStatements', statement);
229
+ for (const typeAssertion of ownedTypes) {
230
+ attachAstNode(typeAssertion, statement, 'ownedTypes');
231
+ }
225
232
  }
226
233
  return new Set([context.ontologyIri]);
227
234
  case 'addAssertion':
228
235
  ensureDescriptionOntology(context.ontology, operation.kind);
229
- return addAssertion(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
236
+ return await addAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
237
+ ? new Set([context.ontologyIri])
238
+ : new Set();
239
+ case 'updateAssertion':
240
+ ensureDescriptionOntology(context.ontology, operation.kind);
241
+ return await updateAssertion(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
230
242
  ? new Set([context.ontologyIri])
231
243
  : new Set();
232
244
  case 'removeAssertion':
@@ -235,7 +247,11 @@ async function executeOperation(
235
247
  ? new Set([context.ontologyIri])
236
248
  : new Set();
237
249
  case 'addAnnotation':
238
- return addAnnotation(context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
250
+ return await addAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
251
+ ? new Set([context.ontologyIri])
252
+ : new Set();
253
+ case 'updateAnnotation':
254
+ return await updateAnnotation(shared, context.ontology, operation.subjectIri, operation.predicateIri, operation.object)
239
255
  ? new Set([context.ontologyIri])
240
256
  : new Set();
241
257
  case 'removeAnnotation':
@@ -297,7 +313,7 @@ function ensureUniqueLocalName(ontology: any, memberName: string): void {
297
313
  }
298
314
  }
299
315
 
300
- function addAssertion(ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): boolean {
316
+ async function addAssertion(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
301
317
  const normalizedPredicate = normalizeIri(predicateIri);
302
318
  const subject = findNamedInstanceByIri(ontology, subjectIri);
303
319
  if (!subject) {
@@ -309,9 +325,9 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
309
325
  if ((subject.ownedTypes ?? []).some((entry: any) => matchesPredicateRef(ontology, entry?.type, normalizedTypeIri))) {
310
326
  return false;
311
327
  }
312
- ensureReferenceImport(ontology, normalizedTypeIri);
328
+ await ensureReferenceImport(shared, ontology, normalizedTypeIri);
313
329
  subject.ownedTypes ??= [];
314
- subject.ownedTypes.push({
330
+ pushAstArrayChild(subject, 'ownedTypes', {
315
331
  $type: 'TypeAssertion',
316
332
  type: asRef(ontology, normalizedTypeIri)
317
333
  });
@@ -323,7 +339,7 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
323
339
  throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
324
340
  }
325
341
  const sourceIri = normalizeIri(asRequiredIri(objectValue, 'source assertion object'));
326
- ensureReferenceImport(ontology, sourceIri);
342
+ await ensureReferenceImport(shared, ontology, sourceIri);
327
343
  subject.sources ??= [];
328
344
  subject.sources.push(asRef(ontology, sourceIri));
329
345
  return true;
@@ -334,7 +350,7 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
334
350
  throw new Error(`Predicate '${predicateIri}' applies only to relation instances.`);
335
351
  }
336
352
  const targetIri = normalizeIri(asRequiredIri(objectValue, 'target assertion object'));
337
- ensureReferenceImport(ontology, targetIri);
353
+ await ensureReferenceImport(shared, ontology, targetIri);
338
354
  subject.targets ??= [];
339
355
  subject.targets.push(asRef(ontology, targetIri));
340
356
  return true;
@@ -343,6 +359,7 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
343
359
  subject.ownedPropertyValues ??= [];
344
360
  let propertyAssertion = subject.ownedPropertyValues.find((assertion: any) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
345
361
  if (!propertyAssertion) {
362
+ await ensureReferenceImport(shared, ontology, normalizedPredicate);
346
363
  propertyAssertion = {
347
364
  $type: 'PropertyValueAssertion',
348
365
  property: asRef(ontology, normalizedPredicate),
@@ -350,7 +367,12 @@ function addAssertion(ontology: any, subjectIri: string, predicateIri: string, o
350
367
  referencedValues: [],
351
368
  containedValues: []
352
369
  };
353
- subject.ownedPropertyValues.push(propertyAssertion);
370
+ pushAstArrayChild(subject, 'ownedPropertyValues', propertyAssertion);
371
+ }
372
+ if (isIriLike(objectValue)) {
373
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
374
+ } else {
375
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
354
376
  }
355
377
  appendAssertionValue(ontology, propertyAssertion, objectValue);
356
378
  return true;
@@ -447,20 +469,57 @@ function removeAssertion(ontology: any, subjectIri: string, predicateIri: string
447
469
  return changed;
448
470
  }
449
471
 
450
- function addAnnotation(ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): boolean {
472
+ async function updateAssertion(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
473
+ const normalizedPredicate = normalizeIri(predicateIri);
474
+ const subject = findNamedInstanceByIri(ontology, subjectIri);
475
+ if (!subject) {
476
+ throw new Error(`Subject '${subjectIri}' was not found in the ontology.`);
477
+ }
478
+
479
+ if (normalizedPredicate === RDF_TYPE_IRI || isSourcePredicate(normalizedPredicate) || isTargetPredicate(normalizedPredicate)) {
480
+ const removed = removeAssertion(ontology, subjectIri, predicateIri, undefined);
481
+ const added = await addAssertion(shared, ontology, subjectIri, predicateIri, objectValue);
482
+ return removed || added;
483
+ }
484
+
485
+ subject.ownedPropertyValues ??= [];
486
+ let propertyAssertion = subject.ownedPropertyValues.find((assertion: any) => matchesPredicateRef(ontology, assertion?.property, normalizedPredicate));
487
+ if (!propertyAssertion) {
488
+ return addAssertion(shared, ontology, subjectIri, predicateIri, objectValue);
489
+ }
490
+ if (isIriLike(objectValue)) {
491
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'assertion object')));
492
+ } else {
493
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
494
+ }
495
+ propertyAssertion.literalValues = [];
496
+ propertyAssertion.referencedValues = [];
497
+ propertyAssertion.containedValues = [];
498
+ appendAssertionValue(ontology, propertyAssertion, objectValue);
499
+ return true;
500
+ }
501
+
502
+ async function addAnnotation(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
451
503
  const subject = findAnnotationSubjectByIri(ontology, subjectIri);
452
504
  if (!subject) {
453
505
  throw new Error(`Annotation subject '${subjectIri}' was not found in the ontology.`);
454
506
  }
507
+ const normalizedPredicate = normalizeIri(predicateIri);
508
+ await ensureReferenceImport(shared, ontology, normalizedPredicate);
455
509
  subject.ownedAnnotations ??= [];
456
510
  const annotation: any = {
457
511
  $type: 'Annotation',
458
- property: asRef(ontology, predicateIri),
512
+ property: asRef(ontology, normalizedPredicate),
459
513
  literalValues: [],
460
514
  referencedValues: []
461
515
  };
516
+ if (isIriLike(objectValue)) {
517
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
518
+ } else {
519
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
520
+ }
462
521
  appendAnnotationValue(ontology, annotation, objectValue);
463
- subject.ownedAnnotations.push(annotation);
522
+ pushAstArrayChild(subject, 'ownedAnnotations', annotation);
464
523
  return true;
465
524
  }
466
525
 
@@ -502,6 +561,28 @@ function removeAnnotation(ontology: any, subjectIri: string, predicateIri: strin
502
561
  return changed;
503
562
  }
504
563
 
564
+ async function updateAnnotation(shared: any, ontology: any, subjectIri: string, predicateIri: string, objectValue: unknown): Promise<boolean> {
565
+ const subject = findAnnotationSubjectByIri(ontology, subjectIri);
566
+ if (!subject) {
567
+ throw new Error(`Annotation subject '${subjectIri}' was not found in the ontology.`);
568
+ }
569
+ const normalizedPredicate = normalizeIri(predicateIri);
570
+ subject.ownedAnnotations ??= [];
571
+ const annotation = subject.ownedAnnotations.find((entry: any) => matchesPredicateRef(ontology, entry?.property, normalizedPredicate));
572
+ if (!annotation) {
573
+ return addAnnotation(shared, ontology, subjectIri, predicateIri, objectValue);
574
+ }
575
+ if (isIriLike(objectValue)) {
576
+ await ensureReferenceImport(shared, ontology, normalizeIri(asRequiredIri(objectValue, 'annotation object')));
577
+ } else {
578
+ await ensureTypedLiteralDatatypeImport(shared, ontology, objectValue);
579
+ }
580
+ annotation.literalValues = [];
581
+ annotation.referencedValues = [];
582
+ appendAnnotationValue(ontology, annotation, objectValue);
583
+ return true;
584
+ }
585
+
505
586
  function deleteMemberRef(ontology: any, memberIri: string, typeIri: string | undefined): boolean {
506
587
  if (!Array.isArray(ontology.ownedStatements)) {
507
588
  return false;
@@ -818,9 +899,7 @@ function matchesPredicateRef(ontology: any, ref: any, iri: string): boolean {
818
899
  return false;
819
900
  }
820
901
  const normalizedWanted = normalizeIri(iri);
821
- const refText = typeof ref === 'string'
822
- ? ref
823
- : (typeof ref.$refText === 'string' ? ref.$refText : undefined);
902
+ const refText = getRefText(ref);
824
903
  if (refText) {
825
904
  const normalizedRefText = normalizeIri(expandRefTextToIri(ontology, refText) ?? refText);
826
905
  if (isSameIriTarget(normalizedRefText, normalizedWanted)) {
@@ -835,13 +914,12 @@ function matchesPredicateRef(ontology: any, ref: any, iri: string): boolean {
835
914
  function appendAssertionValue(ontology: any, assertion: any, objectValue: unknown): void {
836
915
  if (isIriLike(objectValue)) {
837
916
  const normalizedObjectIri = normalizeIri(asRequiredIri(objectValue, 'assertion object'));
838
- ensureReferenceImport(ontology, normalizedObjectIri);
839
917
  assertion.referencedValues ??= [];
840
918
  assertion.referencedValues.push(asRef(ontology, normalizedObjectIri));
841
919
  return;
842
920
  }
843
921
  assertion.literalValues ??= [];
844
- assertion.literalValues.push(asLiteral(objectValue));
922
+ pushAstArrayChild(assertion, 'literalValues', asLiteral(ontology, objectValue));
845
923
  }
846
924
 
847
925
  function removeAssertionValue(ontology: any, assertion: any, objectValue: unknown): void {
@@ -850,20 +928,19 @@ function removeAssertionValue(ontology: any, assertion: any, objectValue: unknow
850
928
  assertion.referencedValues = (assertion.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
851
929
  return;
852
930
  }
853
- const normalizedLiteral = normalizeLiteralValue(asLiteral(objectValue));
931
+ const normalizedLiteral = normalizeLiteralValue(asLiteral(ontology, objectValue));
854
932
  assertion.literalValues = (assertion.literalValues ?? []).filter((literal: any) => normalizeLiteralValue(literal) !== normalizedLiteral);
855
933
  }
856
934
 
857
935
  function appendAnnotationValue(ontology: any, annotation: any, objectValue: unknown): void {
858
936
  if (isIriLike(objectValue)) {
859
937
  const normalizedObjectIri = normalizeIri(asRequiredIri(objectValue, 'annotation object'));
860
- ensureReferenceImport(ontology, normalizedObjectIri);
861
938
  annotation.referencedValues ??= [];
862
939
  annotation.referencedValues.push(asRef(ontology, normalizedObjectIri));
863
940
  return;
864
941
  }
865
942
  annotation.literalValues ??= [];
866
- annotation.literalValues.push(asLiteral(objectValue));
943
+ pushAstArrayChild(annotation, 'literalValues', asLiteral(ontology, objectValue));
867
944
  }
868
945
 
869
946
  function removeAnnotationValue(ontology: any, annotation: any, objectValue: unknown): void {
@@ -872,7 +949,7 @@ function removeAnnotationValue(ontology: any, annotation: any, objectValue: unkn
872
949
  annotation.referencedValues = (annotation.referencedValues ?? []).filter((ref: any) => !matchesPredicateRef(ontology, ref, normalizedIri));
873
950
  return;
874
951
  }
875
- const normalizedLiteral = normalizeLiteralValue(asLiteral(objectValue));
952
+ const normalizedLiteral = normalizeLiteralValue(asLiteral(ontology, objectValue));
876
953
  annotation.literalValues = (annotation.literalValues ?? []).filter((literal: any) => normalizeLiteralValue(literal) !== normalizedLiteral);
877
954
  }
878
955
 
@@ -1006,7 +1083,7 @@ function expandLocalRefText(rawNamespace: string | undefined, localName: string)
1006
1083
  return `${namespace}#${local}`;
1007
1084
  }
1008
1085
 
1009
- function ensureReferenceImport(ontology: any, iri: string): void {
1086
+ async function ensureReferenceImport(shared: any, ontology: any, iri: string): Promise<void> {
1010
1087
  const target = parseIriParts(iri);
1011
1088
  if (!target) {
1012
1089
  return;
@@ -1036,17 +1113,55 @@ function ensureReferenceImport(ontology: any, iri: string): void {
1036
1113
  }
1037
1114
  const prefix = pickImportPrefix(target.namespace, usedPrefixes);
1038
1115
  const importedRef = `<${target.namespace}${target.separator}>`;
1116
+ const importKind = await resolveImportKind(shared, ontology, target.namespace);
1039
1117
  const importStatement: any = {
1040
1118
  $type: 'Import',
1041
- kind: 'extends',
1119
+ kind: importKind,
1042
1120
  prefix,
1043
1121
  imported: { $refText: importedRef }
1044
1122
  };
1045
- importStatement.$container = ontology;
1046
- if (!Array.isArray(ontology.ownedImports)) {
1047
- ontology.ownedImports = [];
1123
+ pushAstArrayChild(ontology, 'ownedImports', importStatement);
1124
+ }
1125
+
1126
+ async function resolveImportKind(shared: any, importing: any, importedNamespace: string): Promise<'extends' | 'includes' | 'uses'> {
1127
+ const imported = await findOntologyByNamespace(shared, importedNamespace);
1128
+ if (imported) {
1129
+ if (importing.$type === imported.$type) {
1130
+ return 'extends';
1131
+ }
1132
+ if (isVocabulary(importing) && isDescription(imported)) {
1133
+ return 'uses';
1134
+ }
1135
+ if (isDescriptionBox(importing) && isVocabulary(imported)) {
1136
+ return 'uses';
1137
+ }
1138
+ if (isVocabularyBundle(importing) && isVocabulary(imported)) {
1139
+ return 'includes';
1140
+ }
1141
+ }
1142
+ if (isVocabularyBundle(importing)) {
1143
+ return 'includes';
1144
+ }
1145
+ if (isDescriptionBox(importing)) {
1146
+ return 'uses';
1048
1147
  }
1049
- ontology.ownedImports.push(importStatement);
1148
+ return 'extends';
1149
+ }
1150
+
1151
+ async function findOntologyByNamespace(shared: any, namespace: string): Promise<any | undefined> {
1152
+ const index = getOntologyModelIndex(shared);
1153
+ const modelUri = index.resolveModelUri(normalizeNamespace(namespace));
1154
+ if (!modelUri) {
1155
+ return undefined;
1156
+ }
1157
+ const uri = URI.parse(modelUri);
1158
+ const langiumDocuments: any = shared.workspace.LangiumDocuments;
1159
+ const builder: any = shared.workspace.DocumentBuilder;
1160
+ const document = langiumDocuments.getDocument(uri)
1161
+ ?? await langiumDocuments.getOrCreateDocument(uri);
1162
+ await builder.build([document], { validation: false });
1163
+ const ontology = document?.parseResult?.value;
1164
+ return ontology && isOntology(ontology) ? ontology : undefined;
1050
1165
  }
1051
1166
 
1052
1167
  function parseIriParts(iri: string): { namespace: string; fragment: string; separator: '#' | '/' } | undefined {
@@ -1198,7 +1313,17 @@ function pickImportPrefix(namespace: string, usedPrefixes: ReadonlySet<string>):
1198
1313
  return `${candidateBase}${suffix}`;
1199
1314
  }
1200
1315
 
1201
- function asLiteral(value: unknown): any {
1316
+ function asLiteral(ontology: any, value: unknown): any {
1317
+ if (isTypedQuotedLiteralTransport(value)) {
1318
+ const literalValue = String(value.value ?? '');
1319
+ const datatypeIri = typeof value.datatypeIri === 'string' ? value.datatypeIri.trim() : '';
1320
+ const datatypeRefText = typeof value.datatypeRefText === 'string' ? value.datatypeRefText.trim() : '';
1321
+ return datatypeIri
1322
+ ? { $type: 'QuotedLiteral', value: literalValue, type: { $refText: toRefText(ontology, normalizeIri(datatypeIri)) } }
1323
+ : datatypeRefText
1324
+ ? { $type: 'QuotedLiteral', value: literalValue, type: { $refText: datatypeRefText } }
1325
+ : { $type: 'QuotedLiteral', value: literalValue };
1326
+ }
1202
1327
  if (typeof value === 'boolean') {
1203
1328
  return { $type: 'BooleanLiteral', value };
1204
1329
  }
@@ -1211,6 +1336,42 @@ function asLiteral(value: unknown): any {
1211
1336
  return { $type: 'QuotedLiteral', value: String(value ?? '') };
1212
1337
  }
1213
1338
 
1339
+ async function ensureTypedLiteralDatatypeImport(shared: any, ontology: any, value: unknown): Promise<void> {
1340
+ if (!isTypedQuotedLiteralTransport(value)) {
1341
+ return;
1342
+ }
1343
+ const datatypeIri = typeof value.datatypeIri === 'string' ? normalizeIri(value.datatypeIri) : '';
1344
+ if (!datatypeIri) {
1345
+ return;
1346
+ }
1347
+ await ensureReferenceImport(shared, ontology, datatypeIri);
1348
+ }
1349
+
1350
+ function isTypedQuotedLiteralTransport(value: unknown): value is { $type?: unknown; value?: unknown; datatypeIri?: unknown; datatypeRefText?: unknown } {
1351
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1352
+ return false;
1353
+ }
1354
+ const record = value as Record<string, unknown>;
1355
+ if (record.$type === 'QuotedLiteral') {
1356
+ return true;
1357
+ }
1358
+ return (typeof record.datatypeIri === 'string' || typeof record.datatypeRefText === 'string') && 'value' in record;
1359
+ }
1360
+
1361
+ function attachAstNode<T extends Record<string, any>>(node: T, parent: any, containerProperty: string): T {
1362
+ (node as any).$container = parent;
1363
+ (node as any).$containerProperty = containerProperty;
1364
+ return node;
1365
+ }
1366
+
1367
+ function pushAstArrayChild(parent: any, property: string, node: any): void {
1368
+ if (!Array.isArray(parent[property])) {
1369
+ parent[property] = [];
1370
+ }
1371
+ attachAstNode(node, parent, property);
1372
+ parent[property].push(node);
1373
+ }
1374
+
1214
1375
  function asRequiredIri(value: unknown, context: string): string {
1215
1376
  if (!isIriLike(value)) {
1216
1377
  throw new Error(`Expected IRI for ${context}.`);