@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
|
@@ -0,0 +1,1708 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import * as ElkModule from 'elkjs/lib/elk.bundled.js';
|
|
4
|
+
import { URI } from 'langium';
|
|
5
|
+
import type { AstNode, LangiumDocument } from 'langium';
|
|
6
|
+
import type { SModelElement, SModelRoot } from 'sprotty-protocol';
|
|
7
|
+
import {
|
|
8
|
+
isAspect,
|
|
9
|
+
isConcept,
|
|
10
|
+
isConceptInstance,
|
|
11
|
+
isDescription,
|
|
12
|
+
isDescriptionBundle,
|
|
13
|
+
isEquivalenceAxiom,
|
|
14
|
+
isImport,
|
|
15
|
+
isInstanceEnumerationAxiom,
|
|
16
|
+
isLiteralEnumerationAxiom,
|
|
17
|
+
isOntology,
|
|
18
|
+
isPropertyValueAssertion,
|
|
19
|
+
isRelation,
|
|
20
|
+
isRelationEntity,
|
|
21
|
+
isRelationInstance,
|
|
22
|
+
isScalar,
|
|
23
|
+
isScalarProperty,
|
|
24
|
+
isSpecializationAxiom,
|
|
25
|
+
isUnreifiedRelation,
|
|
26
|
+
isVocabulary,
|
|
27
|
+
isVocabularyBundle,
|
|
28
|
+
type Import,
|
|
29
|
+
type Ontology
|
|
30
|
+
} from './generated/ast.js';
|
|
31
|
+
import { getOntologyModelIndex } from './oml-index.js';
|
|
32
|
+
import {
|
|
33
|
+
collectOntologyMembers,
|
|
34
|
+
findOntologyMemberByName,
|
|
35
|
+
getIriForNode,
|
|
36
|
+
getModelIdForNode,
|
|
37
|
+
getNamedElementName,
|
|
38
|
+
humanizeTypeName,
|
|
39
|
+
normalizeNamespace,
|
|
40
|
+
splitIri
|
|
41
|
+
} from './oml-utils.js';
|
|
42
|
+
|
|
43
|
+
export type RangeResponse = {
|
|
44
|
+
uri: string;
|
|
45
|
+
startLine: number;
|
|
46
|
+
startColumn: number;
|
|
47
|
+
endLine: number;
|
|
48
|
+
endColumn: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type DiagramEntry = {
|
|
52
|
+
id: string;
|
|
53
|
+
modelId: string;
|
|
54
|
+
text: string;
|
|
55
|
+
qualifiedText?: string;
|
|
56
|
+
tooltip?: string;
|
|
57
|
+
statement?: AstNode;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type DiagramNode = {
|
|
61
|
+
id: string;
|
|
62
|
+
viewId: string;
|
|
63
|
+
modelId: string;
|
|
64
|
+
iri?: string;
|
|
65
|
+
label: string;
|
|
66
|
+
instanceTypeLabels?: string[];
|
|
67
|
+
qualifiedLabel?: string;
|
|
68
|
+
tooltip?: string;
|
|
69
|
+
propertyEntries?: DiagramEntry[];
|
|
70
|
+
compartmentEntries?: DiagramEntry[];
|
|
71
|
+
kind: 'entity' | 'instance' | 'ontology' | 'relation' | 'equivalence';
|
|
72
|
+
statement?: AstNode;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
type DiagramEdge = {
|
|
76
|
+
id: string;
|
|
77
|
+
viewId: string;
|
|
78
|
+
modelId: string;
|
|
79
|
+
iri?: string;
|
|
80
|
+
sourceId: string;
|
|
81
|
+
targetId: string;
|
|
82
|
+
label: string;
|
|
83
|
+
qualifiedLabel?: string;
|
|
84
|
+
tooltip?: string;
|
|
85
|
+
kind: 'relation' | 'relation-source' | 'relation-target' | 'specialization' | 'equivalence' | 'equivalence-source' | 'import';
|
|
86
|
+
statement?: AstNode;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
type DiagramGraph = {
|
|
90
|
+
rootUri: string;
|
|
91
|
+
root: Ontology;
|
|
92
|
+
nodes: DiagramNode[];
|
|
93
|
+
edges: DiagramEdge[];
|
|
94
|
+
statementsById: Map<string, AstNode>;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type AstRef = {
|
|
98
|
+
uri: string;
|
|
99
|
+
path: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
type DiagramBuildContext = {
|
|
103
|
+
root: Ontology;
|
|
104
|
+
nsToPrefix: Map<string, string>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const ElkConstructor = (ElkModule as any).default || ElkModule;
|
|
108
|
+
let elkPromise: Promise<any> | undefined;
|
|
109
|
+
const navigationIndex = new Map<string, Map<string, AstRef>>();
|
|
110
|
+
|
|
111
|
+
async function getElk(): Promise<any> {
|
|
112
|
+
if (!elkPromise) {
|
|
113
|
+
elkPromise = (async () => {
|
|
114
|
+
try {
|
|
115
|
+
const workerModule = await import('elkjs/lib/elk-worker.js');
|
|
116
|
+
const ElkWorkerCtor = (workerModule as any).Worker
|
|
117
|
+
|| (workerModule as any).default?.Worker
|
|
118
|
+
|| (workerModule as any).default
|
|
119
|
+
|| workerModule;
|
|
120
|
+
return new ElkConstructor({
|
|
121
|
+
algorithms: ['layered'],
|
|
122
|
+
workerFactory: (url?: string) => new ElkWorkerCtor(url) as unknown
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('[OML Diagram] Failed to initialize ELK:', error);
|
|
126
|
+
return {
|
|
127
|
+
layout: async (graph: any) => graph
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
131
|
+
}
|
|
132
|
+
return await elkPromise;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getAstNodeLocator(shared: any): { getAstNodePath(node: any): string; getAstNode(root: any, path: string): any } | undefined {
|
|
136
|
+
const isLocator = (candidate: any): boolean =>
|
|
137
|
+
!!candidate
|
|
138
|
+
&& typeof candidate.getAstNodePath === 'function'
|
|
139
|
+
&& typeof candidate.getAstNode === 'function';
|
|
140
|
+
|
|
141
|
+
const direct = shared?.workspace?.AstNodeLocator ?? shared?.AstNodeLocator;
|
|
142
|
+
if (isLocator(direct)) {
|
|
143
|
+
return direct;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const registry: any = shared?.ServiceRegistry;
|
|
147
|
+
if (!registry) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const all = registry.all;
|
|
151
|
+
const services = Array.isArray(all)
|
|
152
|
+
? all
|
|
153
|
+
: (typeof all?.toArray === 'function' ? all.toArray() : Array.from((all ?? []) as Iterable<any>));
|
|
154
|
+
for (const languageServices of services) {
|
|
155
|
+
const locator = languageServices?.workspace?.AstNodeLocator ?? languageServices?.AstNodeLocator;
|
|
156
|
+
if (isLocator(locator)) {
|
|
157
|
+
return locator;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildDiagramContext(root: Ontology): DiagramBuildContext {
|
|
164
|
+
const nsToPrefix = new Map<string, string>();
|
|
165
|
+
for (const ownedImport of (((root as any).ownedImports ?? []) as Import[])) {
|
|
166
|
+
if (!isImport(ownedImport)) continue;
|
|
167
|
+
const importedOntology = (ownedImport as any).imported?.ref;
|
|
168
|
+
const prefix = (ownedImport as any).prefix;
|
|
169
|
+
if (!importedOntology || !prefix) continue;
|
|
170
|
+
const ns = normalizeNamespace(String((importedOntology as any).namespace ?? '').replace(/^<|>$/g, ''));
|
|
171
|
+
if (!ns) continue;
|
|
172
|
+
nsToPrefix.set(ns, prefix);
|
|
173
|
+
}
|
|
174
|
+
return { root, nsToPrefix };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getContainingOntology(astNode: any): any {
|
|
178
|
+
let current = astNode;
|
|
179
|
+
while (current && !isOntology(current)) {
|
|
180
|
+
current = current.$container;
|
|
181
|
+
}
|
|
182
|
+
return current;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function unwrapReferenceValue(candidate: any): any {
|
|
186
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
187
|
+
return candidate;
|
|
188
|
+
}
|
|
189
|
+
if ('$refText' in candidate && 'ref' in candidate) {
|
|
190
|
+
return (candidate as any).ref;
|
|
191
|
+
}
|
|
192
|
+
return candidate;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getQualifiedName(ctx: DiagramBuildContext, astNode: any): string | undefined {
|
|
196
|
+
const value = unwrapReferenceValue(astNode);
|
|
197
|
+
if (!value) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const iri = getIriForNode(value);
|
|
202
|
+
if (iri) {
|
|
203
|
+
const parts = splitIri(iri);
|
|
204
|
+
if (parts?.fragment) {
|
|
205
|
+
const prefix = ctx.nsToPrefix.get(normalizeNamespace(parts.base));
|
|
206
|
+
return prefix ? `${prefix}:${parts.fragment}` : parts.fragment;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (isOntology(value)) {
|
|
211
|
+
const namespace = normalizeNamespace(String((value as any).namespace ?? '').replace(/^<|>$/g, ''));
|
|
212
|
+
if (!namespace) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
const prefix = (value as any).prefix;
|
|
216
|
+
if (typeof prefix === 'string' && prefix.length > 0) {
|
|
217
|
+
return prefix;
|
|
218
|
+
}
|
|
219
|
+
return namespace;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const name = getNamedElementName(value);
|
|
223
|
+
if (!name) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const owningOntology = getContainingOntology(value);
|
|
228
|
+
const namespace = normalizeNamespace(String((owningOntology as any)?.namespace ?? '').replace(/^<|>$/g, ''));
|
|
229
|
+
const prefix = namespace ? ctx.nsToPrefix.get(namespace) : undefined;
|
|
230
|
+
return prefix ? `${prefix}:${name}` : name;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function getRefDisplayName(ctx: DiagramBuildContext, refLike: any): string | undefined {
|
|
234
|
+
const direct = getQualifiedName(ctx, refLike?.ref ?? refLike);
|
|
235
|
+
if (direct) {
|
|
236
|
+
return direct;
|
|
237
|
+
}
|
|
238
|
+
const raw = typeof refLike?.$refText === 'string' ? refLike.$refText.trim() : '';
|
|
239
|
+
if (!raw) {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
const normalized = raw.replace(/^<|>$/g, '');
|
|
243
|
+
const parts = splitIri(normalized);
|
|
244
|
+
if (!parts?.fragment) {
|
|
245
|
+
return normalized;
|
|
246
|
+
}
|
|
247
|
+
const prefix = ctx.nsToPrefix.get(normalizeNamespace(parts.base));
|
|
248
|
+
return prefix ? `${prefix}:${parts.fragment}` : parts.fragment;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function toSimpleName(label: string | undefined): string {
|
|
252
|
+
if (!label) return '';
|
|
253
|
+
const trimmed = label.trim();
|
|
254
|
+
if (!trimmed) return '';
|
|
255
|
+
const colonIndex = trimmed.indexOf(':');
|
|
256
|
+
if (colonIndex >= 0 && colonIndex < trimmed.length - 1) {
|
|
257
|
+
return trimmed.slice(colonIndex + 1);
|
|
258
|
+
}
|
|
259
|
+
const parts = splitIri(trimmed);
|
|
260
|
+
return parts?.fragment || trimmed;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function truncateWithEllipsis(text: string, maxChars: number): string {
|
|
264
|
+
if (maxChars <= 0) return '';
|
|
265
|
+
if (text.length <= maxChars) return text;
|
|
266
|
+
if (maxChars === 1) return '…';
|
|
267
|
+
return `${text.slice(0, maxChars - 1)}…`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function iriNamespace(iri: string | undefined): string | undefined {
|
|
271
|
+
if (!iri) return undefined;
|
|
272
|
+
const parts = splitIri(iri);
|
|
273
|
+
return parts?.base;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function formatHoverLikeTooltip(kind: string, qualified: string | undefined, iri?: string): string | undefined {
|
|
277
|
+
const parts = [kind];
|
|
278
|
+
if (qualified && qualified.length > 0) {
|
|
279
|
+
parts.push(qualified);
|
|
280
|
+
}
|
|
281
|
+
if (iri && iri.length > 0) {
|
|
282
|
+
const ns = iriNamespace(iri);
|
|
283
|
+
parts.push(ns ? `${iri}\nnamespace: ${ns}` : iri);
|
|
284
|
+
}
|
|
285
|
+
return parts.filter(Boolean).join('\n');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function toLiteralLabel(literal: any): string {
|
|
289
|
+
if (!literal) return '[literal]';
|
|
290
|
+
if (typeof literal.lexicalValue === 'string' && literal.lexicalValue.length > 0) {
|
|
291
|
+
return literal.lexicalValue;
|
|
292
|
+
}
|
|
293
|
+
if (typeof literal.value === 'string' && literal.value.length > 0) {
|
|
294
|
+
return literal.value;
|
|
295
|
+
}
|
|
296
|
+
if (typeof literal.stringValue === 'string' && literal.stringValue.length > 0) {
|
|
297
|
+
return literal.stringValue;
|
|
298
|
+
}
|
|
299
|
+
return '[literal]';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getOneOfEntries(
|
|
303
|
+
statement: any,
|
|
304
|
+
ctx: DiagramBuildContext,
|
|
305
|
+
viewId: string,
|
|
306
|
+
astNodeLocator: { getAstNodePath(node: any): string } | undefined,
|
|
307
|
+
rootUri: string,
|
|
308
|
+
): DiagramEntry[] {
|
|
309
|
+
const entries: DiagramEntry[] = [];
|
|
310
|
+
if (isLiteralEnumerationAxiom(statement)) {
|
|
311
|
+
let index = 0;
|
|
312
|
+
for (const literal of ((statement as any).literals ?? [])) {
|
|
313
|
+
const entryId = `${viewId}:oneOf:${index++}`;
|
|
314
|
+
entries.push({
|
|
315
|
+
id: entryId,
|
|
316
|
+
modelId: getModelIdForNode(statement, astNodeLocator, rootUri) ?? entryId,
|
|
317
|
+
text: toLiteralLabel(literal),
|
|
318
|
+
statement
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return entries;
|
|
322
|
+
}
|
|
323
|
+
if (isInstanceEnumerationAxiom(statement)) {
|
|
324
|
+
let index = 0;
|
|
325
|
+
for (const instanceRef of ((statement as any).instances ?? [])) {
|
|
326
|
+
const qName = getRefDisplayName(ctx, instanceRef) ?? (instanceRef?.$refText ?? '[instance]');
|
|
327
|
+
const entryId = `${viewId}:oneOf:${index++}`;
|
|
328
|
+
entries.push({
|
|
329
|
+
id: entryId,
|
|
330
|
+
modelId: getModelIdForNode(statement, astNodeLocator, rootUri) ?? entryId,
|
|
331
|
+
text: toSimpleName(qName),
|
|
332
|
+
qualifiedText: qName,
|
|
333
|
+
statement
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return entries;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function getScalarPropertyLabel(property: any, ctx: DiagramBuildContext): string | undefined {
|
|
341
|
+
const qName = getQualifiedName(ctx, property);
|
|
342
|
+
if (!qName) {
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
const ranges = ((property as any).ranges ?? [])
|
|
346
|
+
.map((rangeRef: any) => getRefDisplayName(ctx, rangeRef))
|
|
347
|
+
.filter((value: any): value is string => typeof value === 'string' && value.length > 0)
|
|
348
|
+
.map((value: string) => toSimpleName(value));
|
|
349
|
+
return ranges.length > 0 ? `${toSimpleName(qName)}: ${ranges.join(' | ')}` : toSimpleName(qName);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function getAssertionValueLabel(ctx: DiagramBuildContext, assertion: any): string {
|
|
353
|
+
const parts: string[] = [];
|
|
354
|
+
for (const literal of ((assertion as any).literalValues ?? [])) {
|
|
355
|
+
parts.push(toLiteralLabel(literal));
|
|
356
|
+
}
|
|
357
|
+
for (const referenced of ((assertion as any).referencedValues ?? [])) {
|
|
358
|
+
const qName = getRefDisplayName(ctx, referenced);
|
|
359
|
+
parts.push(qName ? toSimpleName(qName) : '[instance]');
|
|
360
|
+
}
|
|
361
|
+
for (const contained of ((assertion as any).containedValues ?? [])) {
|
|
362
|
+
const qName = getQualifiedName(ctx, contained);
|
|
363
|
+
parts.push(qName ? toSimpleName(qName) : '[instance]');
|
|
364
|
+
}
|
|
365
|
+
return parts.join(', ');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export async function computeLaidOutSModelForUri(shared: any, modelUri: string): Promise<SModelRoot> {
|
|
369
|
+
const documentUri = URI.parse(modelUri);
|
|
370
|
+
let document = shared.workspace.LangiumDocuments.getDocument(documentUri);
|
|
371
|
+
if (!document) {
|
|
372
|
+
return { id: 'root', type: 'graph', children: [] } as unknown as SModelRoot;
|
|
373
|
+
}
|
|
374
|
+
const root = document.parseResult?.value;
|
|
375
|
+
if (!root || !isOntology(root)) {
|
|
376
|
+
return { id: 'root', type: 'graph', children: [] } as unknown as SModelRoot;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const graph = await computeDiagramGraph(shared, document, root);
|
|
380
|
+
indexDiagramMappings(shared, graph);
|
|
381
|
+
return await layoutAndConvertToSModel(graph);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function computeDiagramGraph(shared: any, document: LangiumDocument, root: Ontology): Promise<DiagramGraph> {
|
|
385
|
+
const graph: DiagramGraph = {
|
|
386
|
+
rootUri: document.uri.toString(),
|
|
387
|
+
root,
|
|
388
|
+
nodes: [],
|
|
389
|
+
edges: [],
|
|
390
|
+
statementsById: new Map<string, AstNode>()
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
if (isVocabulary(root)) {
|
|
394
|
+
computeVocabularyGraph(shared, root, graph);
|
|
395
|
+
return graph;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (isDescription(root)) {
|
|
399
|
+
computeDescriptionGraph(shared, root, graph);
|
|
400
|
+
return graph;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (isVocabularyBundle(root) || isDescriptionBundle(root)) {
|
|
404
|
+
await computeBundleGraph(shared, root, graph);
|
|
405
|
+
return graph;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return graph;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function computeVocabularyGraph(shared: any, root: any, graph: DiagramGraph): void {
|
|
412
|
+
const astNodeLocator = getAstNodeLocator(shared);
|
|
413
|
+
const ctx = buildDiagramContext(root);
|
|
414
|
+
const nodeByQName = new Map<string, DiagramNode>();
|
|
415
|
+
const statements = root.ownedStatements ?? [];
|
|
416
|
+
|
|
417
|
+
const ensureEntityNode = (candidate: any): DiagramNode | undefined => {
|
|
418
|
+
const isReferenceWrapper =
|
|
419
|
+
!!candidate
|
|
420
|
+
&& typeof candidate === 'object'
|
|
421
|
+
&& ('$refText' in candidate)
|
|
422
|
+
&& ('ref' in candidate)
|
|
423
|
+
&& !('$type' in candidate);
|
|
424
|
+
const statement = isReferenceWrapper ? candidate.ref : candidate;
|
|
425
|
+
const isConcreteEntity =
|
|
426
|
+
!!statement
|
|
427
|
+
&& (isConcept(statement) || isAspect(statement) || isRelationEntity(statement) || isScalar(statement));
|
|
428
|
+
const qName = isConcreteEntity
|
|
429
|
+
? getQualifiedName(ctx, statement)
|
|
430
|
+
: (isReferenceWrapper ? getRefDisplayName(ctx, candidate) : undefined);
|
|
431
|
+
if (!qName) return undefined;
|
|
432
|
+
const existing = nodeByQName.get(qName);
|
|
433
|
+
if (existing) return existing;
|
|
434
|
+
|
|
435
|
+
const viewId = `ent:${qName}`;
|
|
436
|
+
const modelId = isConcreteEntity
|
|
437
|
+
? (getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? viewId)
|
|
438
|
+
: viewId;
|
|
439
|
+
const nodeKind: DiagramNode['kind'] = isConcreteEntity && isUnreifiedRelation(statement) ? 'relation' : 'entity';
|
|
440
|
+
const node: DiagramNode = {
|
|
441
|
+
id: viewId,
|
|
442
|
+
viewId,
|
|
443
|
+
modelId,
|
|
444
|
+
iri: isConcreteEntity ? getIriForNode(statement) : undefined,
|
|
445
|
+
label: toSimpleName(qName),
|
|
446
|
+
qualifiedLabel: qName,
|
|
447
|
+
tooltip: formatHoverLikeTooltip(
|
|
448
|
+
isConcreteEntity ? humanizeTypeName((statement as any).$type ?? 'entity') : 'entity',
|
|
449
|
+
qName,
|
|
450
|
+
isConcreteEntity ? getIriForNode(statement) : undefined
|
|
451
|
+
),
|
|
452
|
+
propertyEntries: [],
|
|
453
|
+
compartmentEntries: isConcreteEntity
|
|
454
|
+
? getOneOfEntries(statement, ctx, viewId, astNodeLocator, graph.rootUri)
|
|
455
|
+
: [],
|
|
456
|
+
kind: nodeKind,
|
|
457
|
+
statement: isConcreteEntity ? statement : undefined
|
|
458
|
+
};
|
|
459
|
+
graph.nodes.push(node);
|
|
460
|
+
if (isConcreteEntity) {
|
|
461
|
+
graph.statementsById.set(viewId, statement);
|
|
462
|
+
}
|
|
463
|
+
for (const entry of (node.compartmentEntries ?? [])) {
|
|
464
|
+
if (entry.statement) {
|
|
465
|
+
graph.statementsById.set(entry.id, entry.statement);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
nodeByQName.set(qName, node);
|
|
469
|
+
return node;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
for (const statement of statements) {
|
|
473
|
+
ensureEntityNode(statement);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
for (const statement of statements) {
|
|
477
|
+
if (isScalarProperty(statement)) {
|
|
478
|
+
const propertyLabel = getScalarPropertyLabel(statement, ctx);
|
|
479
|
+
if (!propertyLabel) continue;
|
|
480
|
+
for (const domainRef of ((statement as any).domains ?? [])) {
|
|
481
|
+
const domainNode = ensureEntityNode(domainRef);
|
|
482
|
+
if (!domainNode) continue;
|
|
483
|
+
if (!domainNode.propertyEntries) domainNode.propertyEntries = [];
|
|
484
|
+
const entryId = `${domainNode.viewId}:properties:${domainNode.propertyEntries.length}`;
|
|
485
|
+
domainNode.propertyEntries.push({
|
|
486
|
+
id: entryId,
|
|
487
|
+
modelId: getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? entryId,
|
|
488
|
+
text: propertyLabel,
|
|
489
|
+
qualifiedText: propertyLabel,
|
|
490
|
+
tooltip: formatHoverLikeTooltip('scalar property', getQualifiedName(ctx, statement), getIriForNode(statement)),
|
|
491
|
+
statement
|
|
492
|
+
});
|
|
493
|
+
graph.statementsById.set(entryId, statement);
|
|
494
|
+
}
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (isRelationEntity(statement)) {
|
|
499
|
+
const relNode = ensureEntityNode(statement);
|
|
500
|
+
if (!relNode) continue;
|
|
501
|
+
const sources = (statement.sources ?? []).map((r: any) => ensureEntityNode(r)).filter(Boolean) as DiagramNode[];
|
|
502
|
+
const targets = (statement.targets ?? []).map((r: any) => ensureEntityNode(r)).filter(Boolean) as DiagramNode[];
|
|
503
|
+
let index = 0;
|
|
504
|
+
for (const src of sources) {
|
|
505
|
+
const edgeId = `relent:${relNode.id}:src:${index++}`;
|
|
506
|
+
const modelId = getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
507
|
+
graph.edges.push({
|
|
508
|
+
id: edgeId,
|
|
509
|
+
viewId: edgeId,
|
|
510
|
+
modelId,
|
|
511
|
+
iri: getIriForNode(statement),
|
|
512
|
+
sourceId: src.id,
|
|
513
|
+
targetId: relNode.id,
|
|
514
|
+
label: '',
|
|
515
|
+
kind: 'relation-source',
|
|
516
|
+
statement
|
|
517
|
+
});
|
|
518
|
+
graph.statementsById.set(edgeId, statement);
|
|
519
|
+
}
|
|
520
|
+
index = 0;
|
|
521
|
+
for (const dst of targets) {
|
|
522
|
+
const edgeId = `relent:${relNode.id}:dst:${index++}`;
|
|
523
|
+
const modelId = getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
524
|
+
graph.edges.push({
|
|
525
|
+
id: edgeId,
|
|
526
|
+
viewId: edgeId,
|
|
527
|
+
modelId,
|
|
528
|
+
iri: getIriForNode(statement),
|
|
529
|
+
sourceId: relNode.id,
|
|
530
|
+
targetId: dst.id,
|
|
531
|
+
label: '',
|
|
532
|
+
kind: 'relation-target',
|
|
533
|
+
statement
|
|
534
|
+
});
|
|
535
|
+
graph.statementsById.set(edgeId, statement);
|
|
536
|
+
}
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (!isUnreifiedRelation(statement)) continue;
|
|
541
|
+
const relationName = getQualifiedName(ctx, statement);
|
|
542
|
+
if (!relationName) continue;
|
|
543
|
+
const sources = (statement.sources ?? []).map((r: any) => ensureEntityNode(r)).filter(Boolean) as DiagramNode[];
|
|
544
|
+
const targets = (statement.targets ?? []).map((r: any) => ensureEntityNode(r)).filter(Boolean) as DiagramNode[];
|
|
545
|
+
|
|
546
|
+
if (sources.length === 1 && targets.length === 1) {
|
|
547
|
+
const edgeId = `urel:${relationName}`;
|
|
548
|
+
const modelId = getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
549
|
+
graph.edges.push({
|
|
550
|
+
id: edgeId,
|
|
551
|
+
viewId: edgeId,
|
|
552
|
+
modelId,
|
|
553
|
+
iri: getIriForNode(statement),
|
|
554
|
+
sourceId: sources[0].id,
|
|
555
|
+
targetId: targets[0].id,
|
|
556
|
+
label: toSimpleName(relationName),
|
|
557
|
+
qualifiedLabel: relationName,
|
|
558
|
+
tooltip: formatHoverLikeTooltip('relation', relationName, getIriForNode(statement)),
|
|
559
|
+
kind: 'relation',
|
|
560
|
+
statement
|
|
561
|
+
});
|
|
562
|
+
graph.statementsById.set(edgeId, statement);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const relationNodeViewId = `rel:${relationName}`;
|
|
567
|
+
let relationNode = nodeByQName.get(relationName);
|
|
568
|
+
if (!relationNode) {
|
|
569
|
+
const modelId = getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? relationNodeViewId;
|
|
570
|
+
relationNode = {
|
|
571
|
+
id: relationNodeViewId,
|
|
572
|
+
viewId: relationNodeViewId,
|
|
573
|
+
modelId,
|
|
574
|
+
iri: getIriForNode(statement),
|
|
575
|
+
label: toSimpleName(relationName),
|
|
576
|
+
qualifiedLabel: relationName,
|
|
577
|
+
tooltip: formatHoverLikeTooltip('relation', relationName, getIriForNode(statement)),
|
|
578
|
+
kind: 'relation',
|
|
579
|
+
statement
|
|
580
|
+
};
|
|
581
|
+
graph.nodes.push(relationNode);
|
|
582
|
+
graph.statementsById.set(relationNodeViewId, statement);
|
|
583
|
+
nodeByQName.set(relationName, relationNode);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
let index = 0;
|
|
587
|
+
for (const src of sources) {
|
|
588
|
+
const edgeId = `urel:${relationName}:src:${index++}`;
|
|
589
|
+
const modelId = getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
590
|
+
graph.edges.push({
|
|
591
|
+
id: edgeId,
|
|
592
|
+
viewId: edgeId,
|
|
593
|
+
modelId,
|
|
594
|
+
iri: getIriForNode(statement),
|
|
595
|
+
sourceId: src.id,
|
|
596
|
+
targetId: relationNode.id,
|
|
597
|
+
label: '',
|
|
598
|
+
tooltip: formatHoverLikeTooltip('relation source', relationName, getIriForNode(statement)),
|
|
599
|
+
kind: 'relation-source',
|
|
600
|
+
statement
|
|
601
|
+
});
|
|
602
|
+
graph.statementsById.set(edgeId, statement);
|
|
603
|
+
}
|
|
604
|
+
index = 0;
|
|
605
|
+
for (const dst of targets) {
|
|
606
|
+
const edgeId = `urel:${relationName}:dst:${index++}`;
|
|
607
|
+
const modelId = getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
608
|
+
graph.edges.push({
|
|
609
|
+
id: edgeId,
|
|
610
|
+
viewId: edgeId,
|
|
611
|
+
modelId,
|
|
612
|
+
iri: getIriForNode(statement),
|
|
613
|
+
sourceId: relationNode.id,
|
|
614
|
+
targetId: dst.id,
|
|
615
|
+
label: '',
|
|
616
|
+
tooltip: formatHoverLikeTooltip('relation target', relationName, getIriForNode(statement)),
|
|
617
|
+
kind: 'relation-target',
|
|
618
|
+
statement
|
|
619
|
+
});
|
|
620
|
+
graph.statementsById.set(edgeId, statement);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const addSpecializationEdge = (owner: any, axiom: any, index: number): void => {
|
|
625
|
+
if (!isSpecializationAxiom(axiom)) return;
|
|
626
|
+
const superTerm = (axiom as any).superTerm?.ref;
|
|
627
|
+
const subNode = ensureEntityNode(owner);
|
|
628
|
+
const superNode = ensureEntityNode(superTerm);
|
|
629
|
+
if (!subNode || !superNode) return;
|
|
630
|
+
const edgeId = `spec:${subNode.id}:${superNode.id}:${index}`;
|
|
631
|
+
const modelId = getModelIdForNode(axiom, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
632
|
+
graph.edges.push({
|
|
633
|
+
id: edgeId,
|
|
634
|
+
viewId: edgeId,
|
|
635
|
+
modelId,
|
|
636
|
+
iri: getIriForNode(axiom),
|
|
637
|
+
sourceId: subNode.id,
|
|
638
|
+
targetId: superNode.id,
|
|
639
|
+
label: '',
|
|
640
|
+
tooltip: formatHoverLikeTooltip('specialization', getQualifiedName(ctx, owner), getIriForNode(axiom)),
|
|
641
|
+
kind: 'specialization',
|
|
642
|
+
statement: axiom
|
|
643
|
+
});
|
|
644
|
+
graph.statementsById.set(edgeId, axiom);
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
const addEquivalenceEdges = (owner: any, axiom: any, index: number): void => {
|
|
648
|
+
if (!isEquivalenceAxiom(axiom)) return;
|
|
649
|
+
const subNode = ensureEntityNode(owner);
|
|
650
|
+
if (!subNode) return;
|
|
651
|
+
const superTerms = ((axiom as any).superTerms ?? []).map((ref: any) => ref?.ref).filter(Boolean);
|
|
652
|
+
if (superTerms.length === 1) {
|
|
653
|
+
const superNode = ensureEntityNode(superTerms[0]);
|
|
654
|
+
if (!superNode) return;
|
|
655
|
+
const edgeId = `eq:${subNode.id}:${superNode.id}:${index}:0`;
|
|
656
|
+
const modelId = getModelIdForNode(axiom, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
657
|
+
graph.edges.push({
|
|
658
|
+
id: edgeId,
|
|
659
|
+
viewId: edgeId,
|
|
660
|
+
modelId,
|
|
661
|
+
iri: getIriForNode(axiom),
|
|
662
|
+
sourceId: subNode.id,
|
|
663
|
+
targetId: superNode.id,
|
|
664
|
+
label: '',
|
|
665
|
+
tooltip: formatHoverLikeTooltip('equivalence', getQualifiedName(ctx, owner), getIriForNode(axiom)),
|
|
666
|
+
kind: 'equivalence',
|
|
667
|
+
statement: axiom
|
|
668
|
+
});
|
|
669
|
+
graph.statementsById.set(edgeId, axiom);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (superTerms.length > 1) {
|
|
674
|
+
const eqNodeId = `eqnode:${subNode.id}:${index}`;
|
|
675
|
+
const eqNode: DiagramNode = {
|
|
676
|
+
id: eqNodeId,
|
|
677
|
+
viewId: eqNodeId,
|
|
678
|
+
modelId: getModelIdForNode(axiom, astNodeLocator, graph.rootUri) ?? eqNodeId,
|
|
679
|
+
iri: getIriForNode(axiom),
|
|
680
|
+
label: '&',
|
|
681
|
+
qualifiedLabel: getQualifiedName(ctx, owner),
|
|
682
|
+
tooltip: formatHoverLikeTooltip('equivalence', getQualifiedName(ctx, owner), getIriForNode(axiom)),
|
|
683
|
+
propertyEntries: [],
|
|
684
|
+
compartmentEntries: [],
|
|
685
|
+
kind: 'equivalence',
|
|
686
|
+
statement: axiom
|
|
687
|
+
};
|
|
688
|
+
graph.nodes.push(eqNode);
|
|
689
|
+
graph.statementsById.set(eqNodeId, axiom);
|
|
690
|
+
|
|
691
|
+
const inEdgeId = `eq:${subNode.id}:${eqNodeId}:${index}:in`;
|
|
692
|
+
graph.edges.push({
|
|
693
|
+
id: inEdgeId,
|
|
694
|
+
viewId: inEdgeId,
|
|
695
|
+
modelId: getModelIdForNode(axiom, astNodeLocator, graph.rootUri) ?? inEdgeId,
|
|
696
|
+
iri: getIriForNode(axiom),
|
|
697
|
+
sourceId: subNode.id,
|
|
698
|
+
targetId: eqNodeId,
|
|
699
|
+
label: '',
|
|
700
|
+
tooltip: formatHoverLikeTooltip('equivalence', getQualifiedName(ctx, owner), getIriForNode(axiom)),
|
|
701
|
+
kind: 'equivalence-source',
|
|
702
|
+
statement: axiom
|
|
703
|
+
});
|
|
704
|
+
graph.statementsById.set(inEdgeId, axiom);
|
|
705
|
+
|
|
706
|
+
let superIndex = 0;
|
|
707
|
+
for (const superTerm of superTerms) {
|
|
708
|
+
const superNode = ensureEntityNode(superTerm);
|
|
709
|
+
if (!superNode) continue;
|
|
710
|
+
const outEdgeId = `eq:${eqNodeId}:${superNode.id}:${index}:${superIndex++}`;
|
|
711
|
+
graph.edges.push({
|
|
712
|
+
id: outEdgeId,
|
|
713
|
+
viewId: outEdgeId,
|
|
714
|
+
modelId: getModelIdForNode(axiom, astNodeLocator, graph.rootUri) ?? outEdgeId,
|
|
715
|
+
iri: getIriForNode(axiom),
|
|
716
|
+
sourceId: eqNodeId,
|
|
717
|
+
targetId: superNode.id,
|
|
718
|
+
label: '',
|
|
719
|
+
tooltip: formatHoverLikeTooltip('equivalence', getQualifiedName(ctx, owner), getIriForNode(axiom)),
|
|
720
|
+
kind: 'equivalence',
|
|
721
|
+
statement: axiom
|
|
722
|
+
});
|
|
723
|
+
graph.statementsById.set(outEdgeId, axiom);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
for (const statement of statements) {
|
|
729
|
+
const ownedSpecializations = (statement as any).ownedSpecializations ?? [];
|
|
730
|
+
for (let i = 0; i < ownedSpecializations.length; i += 1) {
|
|
731
|
+
addSpecializationEdge(statement, ownedSpecializations[i], i);
|
|
732
|
+
}
|
|
733
|
+
const ownedEquivalences = (statement as any).ownedEquivalences ?? [];
|
|
734
|
+
for (let i = 0; i < ownedEquivalences.length; i += 1) {
|
|
735
|
+
addEquivalenceEdges(statement, ownedEquivalences[i], i);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function computeDescriptionGraph(shared: any, root: any, graph: DiagramGraph): void {
|
|
741
|
+
const astNodeLocator = getAstNodeLocator(shared);
|
|
742
|
+
const ctx = buildDiagramContext(root);
|
|
743
|
+
const nodeByQName = new Map<string, DiagramNode>();
|
|
744
|
+
|
|
745
|
+
const ensureInstanceNode = (candidate: any): DiagramNode | undefined => {
|
|
746
|
+
const statement = unwrapReferenceValue(candidate);
|
|
747
|
+
if (!statement || (!isConceptInstance(statement) && !isRelationInstance(statement))) return undefined;
|
|
748
|
+
const qName = getQualifiedName(ctx, statement);
|
|
749
|
+
if (!qName) return undefined;
|
|
750
|
+
const existing = nodeByQName.get(qName);
|
|
751
|
+
if (existing) return existing;
|
|
752
|
+
const viewId = `ci:${qName}`;
|
|
753
|
+
const modelId = getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? viewId;
|
|
754
|
+
const typeLabels = Array.from(new Set(
|
|
755
|
+
((statement as any).ownedTypes ?? [])
|
|
756
|
+
.map((typeAssertion: any) => getQualifiedName(ctx, typeAssertion?.type?.ref ?? typeAssertion?.type))
|
|
757
|
+
.filter((label: any) => typeof label === 'string' && label.length > 0)
|
|
758
|
+
)).map((label) => toSimpleName(label as string | undefined)) as string[];
|
|
759
|
+
const label = toSimpleName(qName);
|
|
760
|
+
const propertyEntries: DiagramEntry[] = [];
|
|
761
|
+
let propertyIndex = 0;
|
|
762
|
+
for (const assertion of (((statement as any).ownedPropertyValues ?? []) as any[])) {
|
|
763
|
+
if (getContainingOntology(assertion) !== root) continue;
|
|
764
|
+
if (!isPropertyValueAssertion(assertion)) continue;
|
|
765
|
+
const propertyRef = (assertion as any).property?.ref;
|
|
766
|
+
if (!propertyRef || !isScalarProperty(propertyRef)) continue;
|
|
767
|
+
const propertyName = getQualifiedName(ctx, propertyRef) ?? (assertion as any).property?.$refText ?? 'property';
|
|
768
|
+
const valueLabel = getAssertionValueLabel(ctx, assertion);
|
|
769
|
+
const text = valueLabel.length > 0
|
|
770
|
+
? `${toSimpleName(propertyName)} = ${valueLabel}`
|
|
771
|
+
: toSimpleName(propertyName);
|
|
772
|
+
const entryId = `${viewId}:properties:${propertyIndex++}`;
|
|
773
|
+
propertyEntries.push({
|
|
774
|
+
id: entryId,
|
|
775
|
+
modelId: getModelIdForNode(assertion, astNodeLocator, graph.rootUri) ?? entryId,
|
|
776
|
+
text,
|
|
777
|
+
qualifiedText: propertyName,
|
|
778
|
+
statement: assertion
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
const node: DiagramNode = {
|
|
782
|
+
id: viewId,
|
|
783
|
+
viewId,
|
|
784
|
+
modelId,
|
|
785
|
+
iri: getIriForNode(statement),
|
|
786
|
+
label,
|
|
787
|
+
instanceTypeLabels: typeLabels,
|
|
788
|
+
qualifiedLabel: qName,
|
|
789
|
+
tooltip: formatHoverLikeTooltip(humanizeTypeName((statement as any).$type ?? 'instance'), qName, getIriForNode(statement)),
|
|
790
|
+
propertyEntries,
|
|
791
|
+
kind: 'instance',
|
|
792
|
+
statement
|
|
793
|
+
};
|
|
794
|
+
graph.nodes.push(node);
|
|
795
|
+
graph.statementsById.set(viewId, statement);
|
|
796
|
+
for (const entry of propertyEntries) {
|
|
797
|
+
if (entry.statement) {
|
|
798
|
+
graph.statementsById.set(entry.id, entry.statement);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
nodeByQName.set(qName, node);
|
|
802
|
+
return node;
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
const statements = root.ownedStatements ?? [];
|
|
806
|
+
for (const statement of statements) {
|
|
807
|
+
ensureInstanceNode(statement);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
let assertionEdgeIndex = 0;
|
|
811
|
+
const addRelationPropertyAssertionEdges = (ownerStatement: any, ownerNode: DiagramNode | undefined): void => {
|
|
812
|
+
if (!ownerNode) return;
|
|
813
|
+
const ownedPropertyValues = (ownerStatement as any).ownedPropertyValues ?? [];
|
|
814
|
+
for (const assertion of ownedPropertyValues) {
|
|
815
|
+
if (!isPropertyValueAssertion(assertion)) continue;
|
|
816
|
+
const propertyRef = (assertion as any).property?.ref;
|
|
817
|
+
if (!propertyRef || !isRelation(propertyRef)) continue;
|
|
818
|
+
const relationName = getQualifiedName(ctx, propertyRef) ?? (assertion as any).property?.$refText ?? 'relation';
|
|
819
|
+
const referencedValues = (assertion as any).referencedValues ?? [];
|
|
820
|
+
for (const valueRef of referencedValues) {
|
|
821
|
+
const targetNode = ensureInstanceNode(valueRef);
|
|
822
|
+
if (!targetNode) continue;
|
|
823
|
+
const edgeId = `pva:${ownerNode.id}:${targetNode.id}:${assertionEdgeIndex++}`;
|
|
824
|
+
const modelId = getModelIdForNode(assertion, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
825
|
+
graph.edges.push({
|
|
826
|
+
id: edgeId,
|
|
827
|
+
viewId: edgeId,
|
|
828
|
+
modelId,
|
|
829
|
+
iri: getIriForNode(propertyRef),
|
|
830
|
+
sourceId: ownerNode.id,
|
|
831
|
+
targetId: targetNode.id,
|
|
832
|
+
label: toSimpleName(relationName),
|
|
833
|
+
qualifiedLabel: relationName,
|
|
834
|
+
tooltip: formatHoverLikeTooltip('relation', relationName, getIriForNode(propertyRef)),
|
|
835
|
+
kind: 'relation',
|
|
836
|
+
statement: assertion
|
|
837
|
+
});
|
|
838
|
+
graph.statementsById.set(edgeId, assertion);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
for (const statement of statements) {
|
|
844
|
+
const ownerNode = ensureInstanceNode(statement);
|
|
845
|
+
addRelationPropertyAssertionEdges(statement, ownerNode);
|
|
846
|
+
|
|
847
|
+
if (!isRelationInstance(statement)) continue;
|
|
848
|
+
if (!ownerNode) continue;
|
|
849
|
+
const relName = getQualifiedName(ctx, statement) ?? statement.name ?? 'relation';
|
|
850
|
+
const sources = (statement.sources ?? []).map((r: any) => ensureInstanceNode(r)).filter(Boolean) as DiagramNode[];
|
|
851
|
+
const targets = (statement.targets ?? []).map((r: any) => ensureInstanceNode(r)).filter(Boolean) as DiagramNode[];
|
|
852
|
+
let index = 0;
|
|
853
|
+
for (const src of sources) {
|
|
854
|
+
const edgeId = `ri:${relName}:src:${index++}`;
|
|
855
|
+
const modelId = getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
856
|
+
graph.edges.push({
|
|
857
|
+
id: edgeId,
|
|
858
|
+
viewId: edgeId,
|
|
859
|
+
modelId,
|
|
860
|
+
iri: getIriForNode(statement),
|
|
861
|
+
sourceId: src.id,
|
|
862
|
+
targetId: ownerNode.id,
|
|
863
|
+
label: '',
|
|
864
|
+
tooltip: formatHoverLikeTooltip('relation source', relName, getIriForNode(statement)),
|
|
865
|
+
kind: 'relation-source',
|
|
866
|
+
statement
|
|
867
|
+
});
|
|
868
|
+
graph.statementsById.set(edgeId, statement);
|
|
869
|
+
}
|
|
870
|
+
index = 0;
|
|
871
|
+
for (const dst of targets) {
|
|
872
|
+
const edgeId = `ri:${relName}:dst:${index++}`;
|
|
873
|
+
const modelId = getModelIdForNode(statement, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
874
|
+
graph.edges.push({
|
|
875
|
+
id: edgeId,
|
|
876
|
+
viewId: edgeId,
|
|
877
|
+
modelId,
|
|
878
|
+
iri: getIriForNode(statement),
|
|
879
|
+
sourceId: ownerNode.id,
|
|
880
|
+
targetId: dst.id,
|
|
881
|
+
label: '',
|
|
882
|
+
tooltip: formatHoverLikeTooltip('relation target', relName, getIriForNode(statement)),
|
|
883
|
+
kind: 'relation-target',
|
|
884
|
+
statement
|
|
885
|
+
});
|
|
886
|
+
graph.statementsById.set(edgeId, statement);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
async function computeBundleGraph(shared: any, root: Ontology, graph: DiagramGraph): Promise<void> {
|
|
892
|
+
const astNodeLocator = getAstNodeLocator(shared);
|
|
893
|
+
const queue: Ontology[] = [root];
|
|
894
|
+
const seen = new Set<string>();
|
|
895
|
+
const nodeByNamespace = new Map<string, DiagramNode>();
|
|
896
|
+
const directImports = new Map<string, Array<{ targetNamespace: string; ownedImport: Import }>>();
|
|
897
|
+
|
|
898
|
+
const seedImports = ((root as any).ownedImports ?? []) as Import[];
|
|
899
|
+
for (const ownedImport of seedImports) {
|
|
900
|
+
const importedOntology = await resolveImportedOntology(shared, ownedImport);
|
|
901
|
+
if (!importedOntology) continue;
|
|
902
|
+
queue.push(importedOntology);
|
|
903
|
+
}
|
|
904
|
+
const ensureOntologyNode = (ontology: Ontology, namespace: string): DiagramNode => {
|
|
905
|
+
const existing = nodeByNamespace.get(namespace);
|
|
906
|
+
if (existing) return existing;
|
|
907
|
+
const nodeId = `ont:${namespace}`;
|
|
908
|
+
const modelId = namespace || getModelIdForNode(ontology, astNodeLocator, graph.rootUri) || nodeId;
|
|
909
|
+
const label = (ontology as any).prefix || namespace.split('/').pop() || namespace;
|
|
910
|
+
const node: DiagramNode = {
|
|
911
|
+
id: nodeId,
|
|
912
|
+
viewId: nodeId,
|
|
913
|
+
modelId,
|
|
914
|
+
iri: getIriForNode(ontology),
|
|
915
|
+
label,
|
|
916
|
+
kind: 'ontology',
|
|
917
|
+
statement: ontology
|
|
918
|
+
};
|
|
919
|
+
node.tooltip = getIriForNode(ontology);
|
|
920
|
+
graph.nodes.push(node);
|
|
921
|
+
graph.statementsById.set(nodeId, ontology);
|
|
922
|
+
nodeByNamespace.set(namespace, node);
|
|
923
|
+
return node;
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
while (queue.length > 0) {
|
|
927
|
+
const current = queue.shift()!;
|
|
928
|
+
const namespace = normalizeNamespace((current as any).namespace ?? '');
|
|
929
|
+
if (!namespace || seen.has(namespace)) continue;
|
|
930
|
+
seen.add(namespace);
|
|
931
|
+
|
|
932
|
+
ensureOntologyNode(current, namespace);
|
|
933
|
+
|
|
934
|
+
for (const ownedImport of ((current as any).ownedImports ?? []) as Import[]) {
|
|
935
|
+
const importedOntology = await resolveImportedOntology(shared, ownedImport);
|
|
936
|
+
if (!importedOntology) continue;
|
|
937
|
+
const importedNamespace = normalizeNamespace((importedOntology as any).namespace ?? '');
|
|
938
|
+
if (!importedNamespace) continue;
|
|
939
|
+
|
|
940
|
+
if (!seen.has(importedNamespace)) {
|
|
941
|
+
queue.push(importedOntology);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
ensureOntologyNode(importedOntology, importedNamespace);
|
|
945
|
+
const existing = directImports.get(namespace) ?? [];
|
|
946
|
+
existing.push({ targetNamespace: importedNamespace, ownedImport });
|
|
947
|
+
directImports.set(namespace, existing);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const reachabilityWithoutDirectEdge = (source: string, target: string): boolean => {
|
|
952
|
+
const visited = new Set<string>([source]);
|
|
953
|
+
const work: string[] = [];
|
|
954
|
+
const firstHops = directImports.get(source) ?? [];
|
|
955
|
+
for (const hop of firstHops) {
|
|
956
|
+
if (hop.targetNamespace === target) continue;
|
|
957
|
+
work.push(hop.targetNamespace);
|
|
958
|
+
}
|
|
959
|
+
while (work.length > 0) {
|
|
960
|
+
const current = work.shift()!;
|
|
961
|
+
if (current === target) return true;
|
|
962
|
+
if (visited.has(current)) continue;
|
|
963
|
+
visited.add(current);
|
|
964
|
+
for (const next of (directImports.get(current) ?? [])) {
|
|
965
|
+
if (!visited.has(next.targetNamespace)) {
|
|
966
|
+
work.push(next.targetNamespace);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return false;
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
for (const [sourceNamespace, imports] of directImports.entries()) {
|
|
974
|
+
const sourceNode = nodeByNamespace.get(sourceNamespace);
|
|
975
|
+
if (!sourceNode) continue;
|
|
976
|
+
for (const imported of imports) {
|
|
977
|
+
const targetNode = nodeByNamespace.get(imported.targetNamespace);
|
|
978
|
+
if (!targetNode) continue;
|
|
979
|
+
if (reachabilityWithoutDirectEdge(sourceNamespace, imported.targetNamespace)) {
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
const edgeId = `imp:${sourceNamespace}->${imported.targetNamespace}`;
|
|
983
|
+
const edgeModelId = getModelIdForNode(imported.ownedImport, astNodeLocator, graph.rootUri) ?? edgeId;
|
|
984
|
+
graph.edges.push({
|
|
985
|
+
id: edgeId,
|
|
986
|
+
viewId: edgeId,
|
|
987
|
+
modelId: edgeModelId,
|
|
988
|
+
iri: getIriForNode(imported.ownedImport),
|
|
989
|
+
sourceId: sourceNode.id,
|
|
990
|
+
targetId: targetNode.id,
|
|
991
|
+
label: '',
|
|
992
|
+
tooltip: getIriForNode(imported.ownedImport),
|
|
993
|
+
kind: 'import',
|
|
994
|
+
statement: imported.ownedImport
|
|
995
|
+
});
|
|
996
|
+
graph.statementsById.set(edgeId, imported.ownedImport);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
async function resolveImportedOntology(shared: any, ownedImport: Import): Promise<Ontology | undefined> {
|
|
1002
|
+
const refText = (ownedImport as any)?.imported?.$refText ?? '';
|
|
1003
|
+
const namespace = normalizeNamespace(String(refText).replace(/^<|>$/g, ''));
|
|
1004
|
+
if (namespace) {
|
|
1005
|
+
const resolved = await findOntologyByNamespace(shared, namespace);
|
|
1006
|
+
if (resolved) {
|
|
1007
|
+
return resolved;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
const direct = (ownedImport as any)?.imported?.ref;
|
|
1011
|
+
if (direct && isOntology(direct)) {
|
|
1012
|
+
return direct;
|
|
1013
|
+
}
|
|
1014
|
+
return undefined;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function getNodeSize(node: DiagramNode): { width: number; height: number } {
|
|
1018
|
+
if (node.kind === 'equivalence') {
|
|
1019
|
+
return { width: 28, height: 28 };
|
|
1020
|
+
}
|
|
1021
|
+
const properties = node.propertyEntries ?? [];
|
|
1022
|
+
const entries = node.compartmentEntries ?? [];
|
|
1023
|
+
const maxHeaderChars = 34;
|
|
1024
|
+
const maxBodyChars = 38;
|
|
1025
|
+
const headerName = Math.min(node.label.length, maxHeaderChars);
|
|
1026
|
+
const headerType = Math.min(getNodeTypeLabel(node).length, maxHeaderChars);
|
|
1027
|
+
const widestHeaderPx = Math.max(
|
|
1028
|
+
Math.round(headerName * 8.2),
|
|
1029
|
+
Math.round(headerType * 6.9)
|
|
1030
|
+
);
|
|
1031
|
+
const widestBodyPx = Math.max(
|
|
1032
|
+
...[...properties, ...entries].map((entry) => Math.round(Math.min(entry.text.length, maxBodyChars) * 6.0)),
|
|
1033
|
+
0
|
|
1034
|
+
);
|
|
1035
|
+
const contentWidth = Math.max(widestHeaderPx, widestBodyPx);
|
|
1036
|
+
const width = Math.max(120, Math.min(340, contentWidth + 28));
|
|
1037
|
+
const headerHeight = 34;
|
|
1038
|
+
const compartmentPadding = 10;
|
|
1039
|
+
const entryHeight = 16;
|
|
1040
|
+
const propertyHeight = properties.length > 0
|
|
1041
|
+
? (compartmentPadding * 2) + (properties.length * entryHeight)
|
|
1042
|
+
: 0;
|
|
1043
|
+
const oneOfHeight = entries.length > 0
|
|
1044
|
+
? (compartmentPadding * 2) + (entries.length * entryHeight)
|
|
1045
|
+
: 0;
|
|
1046
|
+
const gap = properties.length > 0 && entries.length > 0 ? 4 : 0;
|
|
1047
|
+
const bodyHeight = propertyHeight + gap + oneOfHeight + (propertyHeight + oneOfHeight > 0 ? 0 : 22);
|
|
1048
|
+
return { width, height: headerHeight + bodyHeight };
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function getNodeTypeLabel(node: DiagramNode): string {
|
|
1052
|
+
if (node.kind === 'equivalence') return '«equivalence»';
|
|
1053
|
+
if (node.kind === 'instance') {
|
|
1054
|
+
const types = (node.instanceTypeLabels ?? []).filter((t) => t.length > 0);
|
|
1055
|
+
if (types.length > 0) {
|
|
1056
|
+
return `«${types.join(', ')}»`;
|
|
1057
|
+
}
|
|
1058
|
+
return '«concept instance»';
|
|
1059
|
+
}
|
|
1060
|
+
const explicitType = (node.statement as any)?.$type;
|
|
1061
|
+
if (typeof explicitType === 'string' && explicitType.length > 0) {
|
|
1062
|
+
return `«${humanizeTypeName(explicitType)}»`;
|
|
1063
|
+
}
|
|
1064
|
+
if (node.kind === 'ontology') return '«ontology»';
|
|
1065
|
+
return '«concept»';
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
async function layoutAndConvertToSModel(graph: DiagramGraph): Promise<SModelRoot> {
|
|
1069
|
+
if (graph.nodes.length === 0) {
|
|
1070
|
+
return { id: 'root', type: 'graph', children: [] } as unknown as SModelRoot;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const selfLoopCountByNode = new Map<string, number>();
|
|
1074
|
+
for (const edge of graph.edges) {
|
|
1075
|
+
if (edge.sourceId === edge.targetId) {
|
|
1076
|
+
const current = selfLoopCountByNode.get(edge.sourceId) ?? 0;
|
|
1077
|
+
selfLoopCountByNode.set(edge.sourceId, current + 1);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const layoutNodeSizeById = new Map<string, { width: number; height: number }>();
|
|
1081
|
+
for (const node of graph.nodes) {
|
|
1082
|
+
const baseSize = getNodeSize(node);
|
|
1083
|
+
const selfLoopCount = selfLoopCountByNode.get(node.id) ?? 0;
|
|
1084
|
+
if (selfLoopCount === 0) {
|
|
1085
|
+
layoutNodeSizeById.set(node.id, baseSize);
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
const labelHeight = 16;
|
|
1089
|
+
const loopSpacing = labelHeight + 8;
|
|
1090
|
+
const baseLoopHeight = 40;
|
|
1091
|
+
const outermostLoopIndex = selfLoopCount - 1;
|
|
1092
|
+
const outermostLoopHeight = baseLoopHeight + (outermostLoopIndex * loopSpacing);
|
|
1093
|
+
const minLoopHostHeight = outermostLoopHeight + 20;
|
|
1094
|
+
layoutNodeSizeById.set(node.id, {
|
|
1095
|
+
width: baseSize.width,
|
|
1096
|
+
height: Math.max(baseSize.height, minLoopHostHeight)
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const elk = await getElk();
|
|
1101
|
+
|
|
1102
|
+
const elkGraph: any = {
|
|
1103
|
+
id: 'root',
|
|
1104
|
+
layoutOptions: {
|
|
1105
|
+
'elk.algorithm': 'org.eclipse.elk.layered',
|
|
1106
|
+
'elk.direction': 'DOWN',
|
|
1107
|
+
'elk.layered.nodePlacement.strategy': 'BRANDES_KOEPF',
|
|
1108
|
+
'elk.spacing.nodeNode': '50',
|
|
1109
|
+
'elk.layered.spacing.nodeNodeBetweenLayers': '80',
|
|
1110
|
+
'elk.selfLoopDistribution': 'EQUALLY_SPACED',
|
|
1111
|
+
'elk.selfLoopOrdering': 'STACKED',
|
|
1112
|
+
'elk.spacing.edgeSelfLoop': '72',
|
|
1113
|
+
'elk.spacing.edgeEdge': '20',
|
|
1114
|
+
'elk.spacing.edgeNode': '20',
|
|
1115
|
+
'elk.edgeRouting': 'ORTHOGONAL',
|
|
1116
|
+
'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',
|
|
1117
|
+
'elk.layered.unnecessaryBendpoints': 'true',
|
|
1118
|
+
'elk.layered.nodePlacement.bk.fixedAlignment': 'BALANCED',
|
|
1119
|
+
'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES',
|
|
1120
|
+
'elk.hierarchyHandling': 'INCLUDE_CHILDREN'
|
|
1121
|
+
},
|
|
1122
|
+
children: graph.nodes.map((n) => {
|
|
1123
|
+
const size = layoutNodeSizeById.get(n.id) ?? getNodeSize(n);
|
|
1124
|
+
return { id: n.id, width: size.width, height: size.height };
|
|
1125
|
+
}),
|
|
1126
|
+
edges: graph.edges.map((e) => ({
|
|
1127
|
+
id: e.id,
|
|
1128
|
+
sources: [e.sourceId],
|
|
1129
|
+
targets: [e.targetId],
|
|
1130
|
+
labels: e.label
|
|
1131
|
+
? [{
|
|
1132
|
+
id: `${e.id}:elk-label`,
|
|
1133
|
+
text: e.label,
|
|
1134
|
+
width: e.label.length * 7 + 8,
|
|
1135
|
+
height: 14,
|
|
1136
|
+
layoutOptions: {
|
|
1137
|
+
'elk.edgeLabels.inline': 'false',
|
|
1138
|
+
'elk.edgeLabels.placement': e.sourceId === e.targetId ? 'TAIL' : 'CENTER'
|
|
1139
|
+
}
|
|
1140
|
+
}]
|
|
1141
|
+
: []
|
|
1142
|
+
}))
|
|
1143
|
+
};
|
|
1144
|
+
|
|
1145
|
+
const laidOut = await elk.layout(elkGraph);
|
|
1146
|
+
const nodeMap = new Map<string, any>((laidOut.children ?? []).map((n: any) => [n.id, n]));
|
|
1147
|
+
const edgeMap = new Map<string, any>((laidOut.edges ?? []).map((e: any) => [e.id, e]));
|
|
1148
|
+
const diagramNodeById = new Map<string, DiagramNode>(graph.nodes.map((node) => [node.id, node]));
|
|
1149
|
+
|
|
1150
|
+
const extractRoutingPoints = (elkEdge: any): Array<{ x: number; y: number }> => {
|
|
1151
|
+
if (!elkEdge || !Array.isArray(elkEdge.sections) || elkEdge.sections.length === 0) return [];
|
|
1152
|
+
const section = elkEdge.sections[0];
|
|
1153
|
+
const points: Array<{ x: number; y: number }> = [];
|
|
1154
|
+
const pushPoint = (p: any): void => {
|
|
1155
|
+
if (!p || typeof p.x !== 'number' || typeof p.y !== 'number') return;
|
|
1156
|
+
const x = Number(p.x);
|
|
1157
|
+
const y = Number(p.y);
|
|
1158
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return;
|
|
1159
|
+
const last = points[points.length - 1];
|
|
1160
|
+
if (!last || Math.abs(last.x - x) > 0.001 || Math.abs(last.y - y) > 0.001) {
|
|
1161
|
+
points.push({ x, y });
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
pushPoint(section.startPoint);
|
|
1165
|
+
for (const bend of Array.isArray(section.bendPoints) ? section.bendPoints : []) {
|
|
1166
|
+
pushPoint(bend);
|
|
1167
|
+
}
|
|
1168
|
+
pushPoint(section.endPoint);
|
|
1169
|
+
return simplifyRoutingPoints(points);
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
const simplifyRoutingPoints = (points: Array<{ x: number; y: number }>): Array<{ x: number; y: number }> => {
|
|
1173
|
+
if (points.length <= 2) return points;
|
|
1174
|
+
const simplified: Array<{ x: number; y: number }> = [points[0]];
|
|
1175
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
1176
|
+
const prev = points[i - 1];
|
|
1177
|
+
const curr = points[i];
|
|
1178
|
+
const next = points[i + 1];
|
|
1179
|
+
const isHorizontalLine = Math.abs(prev.y - curr.y) < 0.001 && Math.abs(curr.y - next.y) < 0.001;
|
|
1180
|
+
const isVerticalLine = Math.abs(prev.x - curr.x) < 0.001 && Math.abs(curr.x - next.x) < 0.001;
|
|
1181
|
+
if (!isHorizontalLine && !isVerticalLine) {
|
|
1182
|
+
simplified.push(curr);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
simplified.push(points[points.length - 1]);
|
|
1186
|
+
return simplified;
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
const rectangularSelfLoopRoutingPoints = (
|
|
1190
|
+
nodeLayout: { x: number; y: number; width: number; height: number },
|
|
1191
|
+
loopIndex: number
|
|
1192
|
+
): Array<{ x: number; y: number }> => {
|
|
1193
|
+
const leftX = nodeLayout.x;
|
|
1194
|
+
const centerY = nodeLayout.y + (nodeLayout.height / 2);
|
|
1195
|
+
const labelHeight = 16;
|
|
1196
|
+
const loopSpacing = labelHeight + 8;
|
|
1197
|
+
const baseLoopWidth = 60;
|
|
1198
|
+
const baseLoopHeight = 40;
|
|
1199
|
+
const loopWidth = baseLoopWidth + (loopIndex * loopSpacing);
|
|
1200
|
+
const loopHeight = baseLoopHeight + (loopIndex * loopSpacing);
|
|
1201
|
+
const top = centerY - (loopHeight / 2);
|
|
1202
|
+
const bottom = centerY + (loopHeight / 2);
|
|
1203
|
+
const outerX = leftX - loopWidth;
|
|
1204
|
+
return [
|
|
1205
|
+
{ x: leftX, y: top },
|
|
1206
|
+
{ x: outerX, y: top },
|
|
1207
|
+
{ x: outerX, y: bottom },
|
|
1208
|
+
{ x: leftX, y: bottom }
|
|
1209
|
+
];
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
const selfLoopOrdinalByNode = new Map<string, number>();
|
|
1213
|
+
|
|
1214
|
+
const children: SModelElement[] = [];
|
|
1215
|
+
const headerHeight = 34;
|
|
1216
|
+
const entryHeight = 16;
|
|
1217
|
+
const compartmentPadding = 10;
|
|
1218
|
+
for (const node of graph.nodes) {
|
|
1219
|
+
const laid = nodeMap.get(node.viewId);
|
|
1220
|
+
const size = getNodeSize(node);
|
|
1221
|
+
const width = laid?.width ?? size.width;
|
|
1222
|
+
const height = laid?.height ?? size.height;
|
|
1223
|
+
if (node.kind === 'equivalence') {
|
|
1224
|
+
children.push({
|
|
1225
|
+
id: node.viewId,
|
|
1226
|
+
viewId: node.viewId,
|
|
1227
|
+
modelId: node.modelId,
|
|
1228
|
+
tooltip: node.iri,
|
|
1229
|
+
type: 'node:rect',
|
|
1230
|
+
position: { x: laid?.x ?? 0, y: laid?.y ?? 0 },
|
|
1231
|
+
size: { width, height },
|
|
1232
|
+
kind: node.kind,
|
|
1233
|
+
children: [
|
|
1234
|
+
{
|
|
1235
|
+
id: `${node.viewId}:name`,
|
|
1236
|
+
viewId: `${node.viewId}:name`,
|
|
1237
|
+
modelId: node.modelId,
|
|
1238
|
+
tooltip: node.iri,
|
|
1239
|
+
type: 'label',
|
|
1240
|
+
text: '&',
|
|
1241
|
+
position: { x: width / 2, y: height / 2 }
|
|
1242
|
+
} as unknown as SModelElement
|
|
1243
|
+
]
|
|
1244
|
+
} as unknown as SModelElement);
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
const propertyEntries = node.propertyEntries ?? [];
|
|
1248
|
+
const entries = node.compartmentEntries ?? [];
|
|
1249
|
+
const maxBodyChars = Math.max(8, Math.floor((width - 24) / 6.0));
|
|
1250
|
+
const propertyHeight = propertyEntries.length > 0
|
|
1251
|
+
? (compartmentPadding * 2) + (propertyEntries.length * entryHeight)
|
|
1252
|
+
: 0;
|
|
1253
|
+
const oneOfHeight = entries.length > 0
|
|
1254
|
+
? (compartmentPadding * 2) + (entries.length * entryHeight)
|
|
1255
|
+
: 0;
|
|
1256
|
+
const gap = propertyEntries.length > 0 && entries.length > 0 ? 4 : 0;
|
|
1257
|
+
const nodeChildren: SModelElement[] = [
|
|
1258
|
+
{
|
|
1259
|
+
id: `${node.viewId}:header`,
|
|
1260
|
+
viewId: `${node.viewId}:header`,
|
|
1261
|
+
modelId: node.modelId,
|
|
1262
|
+
tooltip: node.iri,
|
|
1263
|
+
type: 'node:rect',
|
|
1264
|
+
position: { x: 0, y: 0 },
|
|
1265
|
+
size: { width, height: headerHeight },
|
|
1266
|
+
kind: 'label-box',
|
|
1267
|
+
children: [
|
|
1268
|
+
{
|
|
1269
|
+
id: `${node.viewId}:type`,
|
|
1270
|
+
viewId: `${node.viewId}:type`,
|
|
1271
|
+
modelId: node.modelId,
|
|
1272
|
+
tooltip: node.iri,
|
|
1273
|
+
type: 'label',
|
|
1274
|
+
text: getNodeTypeLabel(node),
|
|
1275
|
+
position: { x: width / 2, y: 10 }
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
id: `${node.viewId}:name`,
|
|
1279
|
+
viewId: `${node.viewId}:name`,
|
|
1280
|
+
modelId: node.modelId,
|
|
1281
|
+
tooltip: node.iri,
|
|
1282
|
+
type: 'label',
|
|
1283
|
+
text: node.label,
|
|
1284
|
+
position: { x: width / 2, y: 24 }
|
|
1285
|
+
}
|
|
1286
|
+
]
|
|
1287
|
+
} as unknown as SModelElement
|
|
1288
|
+
];
|
|
1289
|
+
|
|
1290
|
+
let compartmentY = headerHeight;
|
|
1291
|
+
if (propertyEntries.length > 0) {
|
|
1292
|
+
nodeChildren.push({
|
|
1293
|
+
id: `${node.viewId}:properties`,
|
|
1294
|
+
viewId: `${node.viewId}:properties`,
|
|
1295
|
+
modelId: node.modelId,
|
|
1296
|
+
tooltip: node.iri,
|
|
1297
|
+
type: 'node:rect',
|
|
1298
|
+
position: { x: 0, y: compartmentY },
|
|
1299
|
+
size: { width, height: propertyHeight },
|
|
1300
|
+
kind: 'property-compartment',
|
|
1301
|
+
children: propertyEntries.map((entry, index) => ({
|
|
1302
|
+
id: entry.id,
|
|
1303
|
+
viewId: entry.id,
|
|
1304
|
+
modelId: entry.modelId,
|
|
1305
|
+
tooltip: getIriForNode(entry.statement) ?? node.iri,
|
|
1306
|
+
type: 'label',
|
|
1307
|
+
text: truncateWithEllipsis(entry.text, maxBodyChars),
|
|
1308
|
+
position: {
|
|
1309
|
+
x: 12,
|
|
1310
|
+
y: compartmentPadding + 8 + (index * entryHeight)
|
|
1311
|
+
}
|
|
1312
|
+
})) as unknown as SModelElement[]
|
|
1313
|
+
} as unknown as SModelElement);
|
|
1314
|
+
compartmentY += propertyHeight + gap;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (entries.length > 0) {
|
|
1318
|
+
nodeChildren.push({
|
|
1319
|
+
id: `${node.viewId}:compartment`,
|
|
1320
|
+
viewId: `${node.viewId}:compartment`,
|
|
1321
|
+
modelId: node.modelId,
|
|
1322
|
+
tooltip: node.iri,
|
|
1323
|
+
type: 'node:rect',
|
|
1324
|
+
position: { x: 0, y: compartmentY },
|
|
1325
|
+
size: { width, height: oneOfHeight },
|
|
1326
|
+
kind: 'compartment',
|
|
1327
|
+
children: entries.map((entry, index) => ({
|
|
1328
|
+
id: entry.id,
|
|
1329
|
+
viewId: entry.id,
|
|
1330
|
+
modelId: entry.modelId,
|
|
1331
|
+
tooltip: getIriForNode(entry.statement) ?? node.iri,
|
|
1332
|
+
type: 'label',
|
|
1333
|
+
text: truncateWithEllipsis(entry.text, maxBodyChars),
|
|
1334
|
+
position: {
|
|
1335
|
+
x: 12,
|
|
1336
|
+
y: compartmentPadding + 8 + (index * entryHeight)
|
|
1337
|
+
}
|
|
1338
|
+
})) as unknown as SModelElement[]
|
|
1339
|
+
} as unknown as SModelElement);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
children.push({
|
|
1343
|
+
id: node.viewId,
|
|
1344
|
+
viewId: node.viewId,
|
|
1345
|
+
modelId: node.modelId,
|
|
1346
|
+
tooltip: node.iri,
|
|
1347
|
+
type: 'node:rect',
|
|
1348
|
+
position: { x: laid?.x ?? 0, y: laid?.y ?? 0 },
|
|
1349
|
+
size: { width, height },
|
|
1350
|
+
kind: node.kind,
|
|
1351
|
+
children: nodeChildren
|
|
1352
|
+
} as unknown as SModelElement);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
for (const edge of graph.edges) {
|
|
1356
|
+
const laidEdge = edgeMap.get(edge.viewId);
|
|
1357
|
+
const isSelfLoop = edge.sourceId === edge.targetId;
|
|
1358
|
+
const selfLoopIndex = isSelfLoop
|
|
1359
|
+
? (selfLoopOrdinalByNode.get(edge.sourceId) ?? 0)
|
|
1360
|
+
: -1;
|
|
1361
|
+
if (isSelfLoop) {
|
|
1362
|
+
selfLoopOrdinalByNode.set(edge.sourceId, selfLoopIndex + 1);
|
|
1363
|
+
}
|
|
1364
|
+
const routingPoints = (() => {
|
|
1365
|
+
if (!isSelfLoop) return extractRoutingPoints(laidEdge);
|
|
1366
|
+
const laidNode = nodeMap.get(edge.sourceId);
|
|
1367
|
+
const fallbackNode = diagramNodeById.get(edge.sourceId);
|
|
1368
|
+
const fallbackSize = fallbackNode ? getNodeSize(fallbackNode) : { width: 120, height: 56 };
|
|
1369
|
+
const nodeLayout = {
|
|
1370
|
+
x: laidNode?.x ?? 0,
|
|
1371
|
+
y: laidNode?.y ?? 0,
|
|
1372
|
+
width: laidNode?.width ?? fallbackSize.width,
|
|
1373
|
+
height: laidNode?.height ?? fallbackSize.height
|
|
1374
|
+
};
|
|
1375
|
+
return rectangularSelfLoopRoutingPoints(nodeLayout, selfLoopIndex);
|
|
1376
|
+
})();
|
|
1377
|
+
const edgeChild = edge.label
|
|
1378
|
+
? [(() => {
|
|
1379
|
+
if (isSelfLoop) {
|
|
1380
|
+
return {
|
|
1381
|
+
id: `${edge.viewId}:label`,
|
|
1382
|
+
viewId: `${edge.viewId}:label`,
|
|
1383
|
+
modelId: edge.modelId,
|
|
1384
|
+
tooltip: edge.iri,
|
|
1385
|
+
type: 'label:edge',
|
|
1386
|
+
text: edge.label,
|
|
1387
|
+
edgePlacement: {
|
|
1388
|
+
rotate: false,
|
|
1389
|
+
side: 'top',
|
|
1390
|
+
position: 0.75,
|
|
1391
|
+
offset: 8,
|
|
1392
|
+
moveMode: 'edge'
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
return {
|
|
1397
|
+
id: `${edge.viewId}:label`,
|
|
1398
|
+
viewId: `${edge.viewId}:label`,
|
|
1399
|
+
modelId: edge.modelId,
|
|
1400
|
+
tooltip: edge.iri,
|
|
1401
|
+
type: 'label:edge',
|
|
1402
|
+
text: edge.label,
|
|
1403
|
+
edgePlacement: {
|
|
1404
|
+
rotate: false,
|
|
1405
|
+
side: 'top',
|
|
1406
|
+
position: 0.5,
|
|
1407
|
+
offset: 8,
|
|
1408
|
+
moveMode: 'edge'
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
})()]
|
|
1412
|
+
: [];
|
|
1413
|
+
children.push({
|
|
1414
|
+
id: edge.viewId,
|
|
1415
|
+
viewId: edge.viewId,
|
|
1416
|
+
modelId: edge.modelId,
|
|
1417
|
+
tooltip: edge.iri,
|
|
1418
|
+
type: `edge:${edge.kind}`,
|
|
1419
|
+
sourceId: edge.sourceId,
|
|
1420
|
+
targetId: edge.targetId,
|
|
1421
|
+
routerKind: 'polyline',
|
|
1422
|
+
routingPoints,
|
|
1423
|
+
kind: edge.kind,
|
|
1424
|
+
children: edgeChild
|
|
1425
|
+
} as unknown as SModelElement);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
return {
|
|
1429
|
+
id: 'root',
|
|
1430
|
+
type: 'graph',
|
|
1431
|
+
children
|
|
1432
|
+
} as unknown as SModelRoot;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function normalizeDiagramElementId(elementId: string): string {
|
|
1436
|
+
return elementId.endsWith(':label') ? elementId.slice(0, -':label'.length) : elementId;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function indexDiagramMappings(shared: any, graph: DiagramGraph): void {
|
|
1440
|
+
const astNodeLocator = getAstNodeLocator(shared);
|
|
1441
|
+
if (!astNodeLocator) {
|
|
1442
|
+
navigationIndex.set(graph.rootUri, new Map());
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
const map = new Map<string, AstRef>();
|
|
1446
|
+
for (const node of graph.nodes) {
|
|
1447
|
+
const statement = node.statement;
|
|
1448
|
+
if (!statement) continue;
|
|
1449
|
+
const root = statement.$document?.parseResult?.value;
|
|
1450
|
+
if (!root) continue;
|
|
1451
|
+
const path = astNodeLocator.getAstNodePath(statement);
|
|
1452
|
+
if (!path) continue;
|
|
1453
|
+
const uri = statement.$document?.uri?.toString() ?? graph.rootUri;
|
|
1454
|
+
const astRef = { uri, path };
|
|
1455
|
+
map.set(normalizeDiagramElementId(node.viewId), astRef);
|
|
1456
|
+
map.set(normalizeDiagramElementId(node.modelId), astRef);
|
|
1457
|
+
}
|
|
1458
|
+
for (const edge of graph.edges) {
|
|
1459
|
+
const statement = edge.statement;
|
|
1460
|
+
if (!statement) continue;
|
|
1461
|
+
const root = statement.$document?.parseResult?.value;
|
|
1462
|
+
if (!root) continue;
|
|
1463
|
+
const path = astNodeLocator.getAstNodePath(statement);
|
|
1464
|
+
if (!path) continue;
|
|
1465
|
+
const uri = statement.$document?.uri?.toString() ?? graph.rootUri;
|
|
1466
|
+
const astRef = { uri, path };
|
|
1467
|
+
map.set(normalizeDiagramElementId(edge.viewId), astRef);
|
|
1468
|
+
map.set(normalizeDiagramElementId(edge.modelId), astRef);
|
|
1469
|
+
}
|
|
1470
|
+
for (const [id, statement] of graph.statementsById.entries()) {
|
|
1471
|
+
const root = statement.$document?.parseResult?.value;
|
|
1472
|
+
if (!root) continue;
|
|
1473
|
+
const path = astNodeLocator.getAstNodePath(statement);
|
|
1474
|
+
if (!path) continue;
|
|
1475
|
+
const uri = statement.$document?.uri?.toString() ?? graph.rootUri;
|
|
1476
|
+
map.set(normalizeDiagramElementId(id), { uri, path });
|
|
1477
|
+
}
|
|
1478
|
+
navigationIndex.set(graph.rootUri, map);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
export async function navigateToElement(shared: any, contextUri: string, elementId: string): Promise<RangeResponse | null> {
|
|
1482
|
+
const astNodeLocator = getAstNodeLocator(shared);
|
|
1483
|
+
if (!astNodeLocator) {
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
const normalizedId = normalizeDiagramElementId(elementId);
|
|
1487
|
+
const directSeparator = normalizedId.indexOf('#');
|
|
1488
|
+
const directUri = directSeparator > 0 ? normalizedId.slice(0, directSeparator) : undefined;
|
|
1489
|
+
const directPath = directSeparator > 0 ? normalizedId.slice(directSeparator + 1) : undefined;
|
|
1490
|
+
const indexed = !directUri || !directPath
|
|
1491
|
+
? navigationIndex.get(contextUri)?.get(normalizedId)
|
|
1492
|
+
: undefined;
|
|
1493
|
+
const resolvedUri = directUri ?? indexed?.uri;
|
|
1494
|
+
const resolvedPath = directPath ?? indexed?.path;
|
|
1495
|
+
if (!resolvedUri || !resolvedPath) {
|
|
1496
|
+
const ontologyIri = normalizedId.startsWith('ont:')
|
|
1497
|
+
? normalizeNamespace(normalizedId.slice(4))
|
|
1498
|
+
: normalizeNamespace(normalizedId);
|
|
1499
|
+
if (ontologyIri && ontologyIri.includes('://')) {
|
|
1500
|
+
const ontology = await findOntologyByNamespace(shared, ontologyIri);
|
|
1501
|
+
if (ontology?.$document) {
|
|
1502
|
+
return toRange(ontology.$document, ontology);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
if (!resolvedUri || !resolvedPath) {
|
|
1507
|
+
return null;
|
|
1508
|
+
}
|
|
1509
|
+
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
1510
|
+
const resolvedDocUri = URI.parse(resolvedUri);
|
|
1511
|
+
const doc = langiumDocuments.getDocument(resolvedDocUri)
|
|
1512
|
+
?? (typeof langiumDocuments.getOrCreateDocument === 'function'
|
|
1513
|
+
? await langiumDocuments.getOrCreateDocument(resolvedDocUri)
|
|
1514
|
+
: undefined);
|
|
1515
|
+
if (!doc) {
|
|
1516
|
+
return null;
|
|
1517
|
+
}
|
|
1518
|
+
const root = doc.parseResult?.value;
|
|
1519
|
+
if (!root) {
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
const node = astNodeLocator.getAstNode(root, resolvedPath);
|
|
1523
|
+
if (!node) {
|
|
1524
|
+
return null;
|
|
1525
|
+
}
|
|
1526
|
+
return toRange(node.$document ?? doc, node);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
export async function resolveDefinition(shared: any, elementId: string, referencingUri?: string): Promise<RangeResponse | null> {
|
|
1530
|
+
const normalizedElementId = String(elementId ?? '').trim();
|
|
1531
|
+
if (!normalizedElementId) {
|
|
1532
|
+
return null;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
const directSeparator = normalizedElementId.indexOf('#');
|
|
1536
|
+
const directUri = directSeparator > 0 ? normalizedElementId.slice(0, directSeparator) : undefined;
|
|
1537
|
+
const directPath = directSeparator > 0 ? normalizedElementId.slice(directSeparator + 1) : undefined;
|
|
1538
|
+
if (directUri && directPath && directPath.startsWith('/')) {
|
|
1539
|
+
try {
|
|
1540
|
+
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
1541
|
+
const resolvedDocUri = URI.parse(directUri);
|
|
1542
|
+
const doc = langiumDocuments.getDocument(resolvedDocUri)
|
|
1543
|
+
?? (typeof langiumDocuments.getOrCreateDocument === 'function'
|
|
1544
|
+
? await langiumDocuments.getOrCreateDocument(resolvedDocUri)
|
|
1545
|
+
: undefined);
|
|
1546
|
+
const root = doc?.parseResult?.value;
|
|
1547
|
+
const astNodeLocator = getAstNodeLocator(shared);
|
|
1548
|
+
if (doc && root && astNodeLocator) {
|
|
1549
|
+
const node = astNodeLocator.getAstNode(root, directPath);
|
|
1550
|
+
if (node) {
|
|
1551
|
+
return toRange(node.$document ?? doc, node);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
} catch {
|
|
1555
|
+
// Fall through to IRI-based resolution when this is not a resolvable document URI.
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
const normalizedIri = normalizedElementId.replace(/^<|>$/g, '').trim();
|
|
1560
|
+
if (!normalizedIri) {
|
|
1561
|
+
return null;
|
|
1562
|
+
}
|
|
1563
|
+
if (referencingUri) {
|
|
1564
|
+
const preferredRange = await resolveDefinitionByIriInModel(shared, normalizedIri, referencingUri);
|
|
1565
|
+
if (preferredRange) {
|
|
1566
|
+
return preferredRange;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
const parts = splitIri(normalizedIri);
|
|
1570
|
+
if (!parts || !parts.base || !parts.fragment) {
|
|
1571
|
+
return null;
|
|
1572
|
+
}
|
|
1573
|
+
const ontology = await findOntologyByNamespace(shared, parts.base);
|
|
1574
|
+
if (!ontology?.$document) {
|
|
1575
|
+
return null;
|
|
1576
|
+
}
|
|
1577
|
+
const member = findOntologyMemberByName(ontology, parts.fragment);
|
|
1578
|
+
if (!member) {
|
|
1579
|
+
return null;
|
|
1580
|
+
}
|
|
1581
|
+
return toRange(member.$document ?? ontology.$document, member);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
async function resolveDefinitionByIriInModel(shared: any, iri: string, modelUri: string): Promise<RangeResponse | null> {
|
|
1585
|
+
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
1586
|
+
const builder: any = shared.workspace.DocumentBuilder;
|
|
1587
|
+
const uri = URI.parse(modelUri);
|
|
1588
|
+
const document = langiumDocuments.getDocument(uri)
|
|
1589
|
+
?? (typeof langiumDocuments.getOrCreateDocument === 'function'
|
|
1590
|
+
? await langiumDocuments.getOrCreateDocument(uri)
|
|
1591
|
+
: undefined);
|
|
1592
|
+
if (!document) {
|
|
1593
|
+
return null;
|
|
1594
|
+
}
|
|
1595
|
+
if (builder?.build) {
|
|
1596
|
+
await builder.build([document], { validation: false });
|
|
1597
|
+
}
|
|
1598
|
+
const root = document.parseResult?.value;
|
|
1599
|
+
if (!root || !isOntology(root) || (!isVocabulary(root) && !isDescription(root))) {
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
const targetIri = normalizeIriText(iri);
|
|
1603
|
+
const member = collectOntologyMembers(root).find((candidate) => {
|
|
1604
|
+
const candidateIri = normalizeIriText(getIriForNode(candidate));
|
|
1605
|
+
if (candidateIri === targetIri) {
|
|
1606
|
+
return true;
|
|
1607
|
+
}
|
|
1608
|
+
const resolvedRefIri = normalizeIriText(getIriForNode((candidate as any)?.ref?.ref));
|
|
1609
|
+
if (resolvedRefIri === targetIri) {
|
|
1610
|
+
return true;
|
|
1611
|
+
}
|
|
1612
|
+
const rawRefText = normalizeIriText((candidate as any)?.ref?.$refText);
|
|
1613
|
+
return rawRefText === targetIri;
|
|
1614
|
+
});
|
|
1615
|
+
if (!member) {
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
return toRange(member.$document ?? document, member);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
function normalizeIriText(value: unknown): string {
|
|
1622
|
+
return typeof value === 'string' ? value.replace(/^<|>$/g, '').trim() : '';
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
async function findOntologyByNamespace(shared: any, namespace: string): Promise<Ontology | undefined> {
|
|
1626
|
+
const index = getOntologyModelIndex(shared);
|
|
1627
|
+
const modelUri = index?.resolveModelUri(normalizeNamespace(namespace));
|
|
1628
|
+
if (!modelUri) {
|
|
1629
|
+
return undefined;
|
|
1630
|
+
}
|
|
1631
|
+
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
1632
|
+
const uri = URI.parse(modelUri);
|
|
1633
|
+
const document = langiumDocuments.getDocument(uri)
|
|
1634
|
+
?? (typeof langiumDocuments.getOrCreateDocument === 'function'
|
|
1635
|
+
? await langiumDocuments.getOrCreateDocument(uri)
|
|
1636
|
+
: undefined);
|
|
1637
|
+
const root = document?.parseResult?.value;
|
|
1638
|
+
return root && isOntology(root) ? root : undefined;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
export type OntologyMemberLabelEntry = {
|
|
1642
|
+
iri: string;
|
|
1643
|
+
name: string;
|
|
1644
|
+
label?: string;
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
export async function resolveOntologyMemberLabels(
|
|
1648
|
+
shared: any,
|
|
1649
|
+
oml: any,
|
|
1650
|
+
modelUri: string,
|
|
1651
|
+
): Promise<OntologyMemberLabelEntry[]> {
|
|
1652
|
+
const langiumDocuments: any = shared.workspace.LangiumDocuments;
|
|
1653
|
+
const builder: any = shared.workspace.DocumentBuilder;
|
|
1654
|
+
const uri = URI.parse(modelUri);
|
|
1655
|
+
const document = langiumDocuments.getDocument(uri)
|
|
1656
|
+
?? (typeof langiumDocuments.getOrCreateDocument === 'function'
|
|
1657
|
+
? await langiumDocuments.getOrCreateDocument(uri)
|
|
1658
|
+
: undefined);
|
|
1659
|
+
if (!document) {
|
|
1660
|
+
return [];
|
|
1661
|
+
}
|
|
1662
|
+
if (builder?.build) {
|
|
1663
|
+
await builder.build([document], { validation: false });
|
|
1664
|
+
}
|
|
1665
|
+
const root = document.parseResult?.value;
|
|
1666
|
+
if (!root || !isOntology(root) || (!isVocabulary(root) && !isDescription(root))) {
|
|
1667
|
+
return [];
|
|
1668
|
+
}
|
|
1669
|
+
const reasoningService = oml?.reasoning?.ReasoningService;
|
|
1670
|
+
const labelSnapshot: Record<string, string> = reasoningService && typeof reasoningService.getContextMemberLabelSnapshot === 'function'
|
|
1671
|
+
? await reasoningService.getContextMemberLabelSnapshot(modelUri)
|
|
1672
|
+
: {};
|
|
1673
|
+
|
|
1674
|
+
const entries: OntologyMemberLabelEntry[] = [];
|
|
1675
|
+
for (const member of collectOntologyMembers(root)) {
|
|
1676
|
+
const iri = getIriForNode(member)?.trim();
|
|
1677
|
+
const name = getNamedElementName(member)?.trim();
|
|
1678
|
+
if (!iri || !name) {
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
const label = labelSnapshot[iri]?.trim();
|
|
1682
|
+
entries.push({
|
|
1683
|
+
iri,
|
|
1684
|
+
name,
|
|
1685
|
+
label: label && label.length > 0 ? label : undefined,
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
entries.sort((left, right) =>
|
|
1689
|
+
left.name.localeCompare(right.name)
|
|
1690
|
+
|| left.iri.localeCompare(right.iri)
|
|
1691
|
+
);
|
|
1692
|
+
return entries;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
function toRange(document: LangiumDocument | undefined, node: AstNode): RangeResponse | null {
|
|
1696
|
+
if (!document?.textDocument) return null;
|
|
1697
|
+
const cstNode = node.$cstNode;
|
|
1698
|
+
if (!cstNode) return null;
|
|
1699
|
+
const start = document.textDocument.positionAt(cstNode.offset);
|
|
1700
|
+
const end = document.textDocument.positionAt(cstNode.offset + cstNode.length);
|
|
1701
|
+
return {
|
|
1702
|
+
uri: document.uri.toString(),
|
|
1703
|
+
startLine: start.line + 1,
|
|
1704
|
+
startColumn: start.character,
|
|
1705
|
+
endLine: end.line + 1,
|
|
1706
|
+
endColumn: end.character
|
|
1707
|
+
};
|
|
1708
|
+
}
|