node-opcua-modeler 2.62.6 → 2.63.2

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.
@@ -0,0 +1,335 @@
1
+ import {
2
+ BaseNode,
3
+ UAReference,
4
+ UAObject,
5
+ UAVariable,
6
+ UAReferenceType,
7
+ UADataType,
8
+ UAObjectType,
9
+ UAVariableType,
10
+ UAMethod
11
+ } from "node-opcua-address-space";
12
+ import { ReferenceTypeIds } from "node-opcua-constants";
13
+ import { BrowseDirection, NodeClass } from "node-opcua-data-model";
14
+
15
+ function e(str: string): string {
16
+ return str.replace(/</g,"&lt;").replace(/>/g,"&gt;");
17
+ }
18
+ function innerText(node: UAObject | UAVariable) {
19
+ const browseName = node.browseName.name;
20
+ const typeDefinition = node.typeDefinitionObj?.browseName?.name;
21
+
22
+ if (typeDefinition) {
23
+ return `[ label =< <FONT point-size="8" ><I>${typeDefinition}</I></FONT><BR/>${e(browseName!)} >]`;
24
+ } else {
25
+ return `[label =< ${e(browseName!)} >]`;
26
+ }
27
+ }
28
+ function arrowHeadAttribute(reference: UAReference): string {
29
+ switch (reference.referenceType.value) {
30
+ case ReferenceTypeIds.HasTypeDefinition:
31
+ return "normalnormal";
32
+ case ReferenceTypeIds.HasComponent:
33
+ return "noneteetree";
34
+ case ReferenceTypeIds.HasProperty:
35
+ return "nonetee";
36
+ case ReferenceTypeIds.HasSubtype:
37
+ return "onormalonormal";
38
+ case ReferenceTypeIds.HasModellingRule:
39
+ return "none";
40
+ default:
41
+ return "normal";
42
+ }
43
+ }
44
+ function arrowHead(reference: UAReference): string {
45
+ return `[arrowhead = ${arrowHeadAttribute(reference)}]`;
46
+ }
47
+
48
+ const regularShapes: Record<string, string> = {
49
+ ObjectType: '[shape=rectangle, style="filled" fillcolor="#e8edf7;0.75:#d2def0" gradientangle=275]',
50
+ VariableType: '[shape=rectangle, style="rounded,filled" fillcolor="#e8edf7;0.75:#d2def0" gradientangle=275]',
51
+ Object: ' [shape=rectangle, style="rounded,filled" fillcolor="#e8edf7"]',
52
+ Variable: '[shape=rectangle, style="filled,rounded" fillcolor="#e8edf7"]',
53
+ Method: '[shape=circle, style="filled" fillcolor="#e8edf7"]'
54
+ };
55
+ const regularShapesOptionals: Record<string, string> = {
56
+ ObjectType: '[shape=rectangle, style="filled,dashed" fillcolor="#e8edf7;0.75:#d2def0" gradientangle=275]',
57
+ VariableType: '[shape=rectangle, style="rounded,filled,dashed" fillcolor="#e8edf7;0.75:#d2def0" gradientangle=275]',
58
+ Object: ' [shape=rectangle, style="rounded,filled,dashed" fillcolor="#e8edf7"]',
59
+ Variable: '[shape=rectangle, style="filled,rounded,dashed" fillcolor="#e8edf7"]',
60
+ Method: '[shape=circle, style="filled,dashed" fillcolor="#e8edf7"]'
61
+ };
62
+
63
+ interface Options {
64
+ naked: boolean;
65
+ }
66
+
67
+ class NodeRegistry {
68
+ m: Record<string, { name: string; node: BaseNode }[]> = {};
69
+ invisibleNodes: string[] = [];
70
+ duplicated: { [key: string]: string } = {};
71
+ add(name: string, node: BaseNode) {
72
+
73
+ if (this.duplicated[name]) {
74
+ return;//throw new Error("Already included");
75
+ }
76
+ this.duplicated[name]= name;
77
+ const nodeClass = NodeClass[node.nodeClass];
78
+ this.m[nodeClass] = this.m[nodeClass] || [];
79
+ this.m[nodeClass].push({ name, node });
80
+
81
+ }
82
+ addInvisibleNode(name: string) {
83
+ this.invisibleNodes.push(name);
84
+ }
85
+ }
86
+ function dumpNodeByNodeClass(str: string[], nodeRegistry: NodeRegistry) {
87
+ for (const [className, listNode] of Object.entries(nodeRegistry.m)) {
88
+ if (listNode.length === 0) {
89
+ continue;
90
+ }
91
+ str.push(` ## -> ${className}`);
92
+
93
+ const mandatoryNodes = listNode.filter(({ name, node }) => !node.modellingRule || node.modellingRule.match(/Mandatory/));
94
+ const optionalNodes = listNode.filter(({ name, node }) => node.modellingRule && node.modellingRule.match(/Optional/));
95
+ if (mandatoryNodes.length > 0) {
96
+ str.push(` node [];`);
97
+ const decoration = regularShapes[className] as string;
98
+ str.push(` node ${decoration};`);
99
+ for (const { name, node } of mandatoryNodes) {
100
+ str.push(` ${name} ${innerText(node as UAVariable | UAObject)}`);
101
+ }
102
+ }
103
+ if (optionalNodes.length > 0) {
104
+ const decoration2 = regularShapesOptionals[className] as string;
105
+ str.push(` node [];`);
106
+ str.push(` node ${decoration2};`);
107
+ for (const { name, node } of listNode) {
108
+ str.push(` ${name} ${innerText(node as UAVariable | UAObject)}`);
109
+ }
110
+ }
111
+ }
112
+ if (nodeRegistry.invisibleNodes.length) {
113
+ str.push(" # invisible nodes");
114
+ str.push(" node []");
115
+ str.push(" node [width=0,height=0,shape=point,style=invis];");
116
+ str.push(` ${nodeRegistry.invisibleNodes.join("\n ")};`);
117
+ }
118
+ }
119
+ export function opcuaToDot(node: UAObjectType | UAVariableType, options?: Options): string {
120
+ options = options || { naked: false };
121
+ const nodeRegistry = new NodeRegistry();
122
+
123
+ const str: string[] = [];
124
+ const str2: string[] = [];
125
+ str.push("digraph G {");
126
+ if (!options.naked) {
127
+ str.push(" rankdir=TB;");
128
+ str.push(" nodesep=0.5;");
129
+ str.push(" node [];");
130
+ }
131
+
132
+ function makeId(p: string, c: string) {
133
+ return `${p}_${c}`.replace(" ", "_").replace(/<|>/g, "_");
134
+ }
135
+ // eslint-disable-next-line max-params
136
+ // eslint-disable-next-line max-statements
137
+ function typeMemberAndSubTypeMember(
138
+ str: string[],
139
+ parentNode: string,
140
+ node: UAObjectType | UAVariableType | UAMethod | UAVariable | UAObject,
141
+ parent: UAObjectType | UAVariableType | UAMethod | UAVariable | UAObject | null,
142
+ offset: number,
143
+ prefix: string,
144
+ joinWithCaller: boolean,
145
+ visitorMap: Record<string, any>
146
+ ): [number, string[]] {
147
+ let innerDepth = 0;
148
+ const browseName = (parent || node).browseName.name!.toString();
149
+ const r: string[] = [];
150
+ const r2: string[] = [];
151
+ const references = node.findReferencesEx("Aggregates", BrowseDirection.Forward);
152
+ const folderElements = node.findReferencesEx("Organizes", BrowseDirection.Forward);
153
+ const childReferences = [...references, ...folderElements];
154
+ const id = makeId(parentNode,browseName);
155
+ nodeRegistry.add(id, node);
156
+
157
+ function addInvisibleNode(prefix: string, index: number) {
158
+ const breakNode = `${prefix}${index}`;
159
+ r2.push(breakNode);
160
+ nodeRegistry.addInvisibleNode(breakNode);
161
+ return breakNode;
162
+ }
163
+ for (let i = 0; i < childReferences.length; i++) {
164
+ const isLast = i === childReferences.length - 1;
165
+ const reference = childReferences[i];
166
+
167
+ const childNode = reference.node! as UAVariable | UAObject | UAMethod;
168
+ const childName = childNode.browseName.name!.toString();
169
+
170
+ const fullChildName = makeId(id,childName);
171
+ // avoid member duplication
172
+ if (visitorMap[fullChildName]) {
173
+ continue;
174
+ } else {
175
+ visitorMap[fullChildName] = 1;
176
+ }
177
+
178
+ innerDepth++;
179
+
180
+ nodeRegistry.add(fullChildName, childNode);
181
+ const edgeAttributes = arrowHead(reference);
182
+
183
+ const breakNode = addInvisibleNode(prefix, i + offset);
184
+ const horizontalPart = `{ rank=same ${breakNode} -> ${fullChildName} ${edgeAttributes} }`;
185
+ r.push(horizontalPart);
186
+
187
+ // push children on same level
188
+ const [depth] = typeMemberAndSubTypeMember(str, id, childNode, null, 0, `${prefix}${i + offset}_`, false, visitorMap);
189
+
190
+ // add invisible nodes
191
+ {
192
+ for (let d = 0; d < depth; d++) {
193
+ offset++;
194
+ if (!isLast) {
195
+ addInvisibleNode(prefix, i + offset);
196
+ }
197
+ }
198
+ innerDepth += depth;
199
+ }
200
+ }
201
+
202
+ if (node.nodeClass == NodeClass.ObjectType || node.nodeClass === NodeClass.VariableType) {
203
+ if (node.subtypeOfObj && node.subtypeOfObj.nodeId.namespace === node.nodeId.namespace) {
204
+ const [depth, rr2] = typeMemberAndSubTypeMember(
205
+ str,
206
+ parentNode,
207
+ node.subtypeOfObj,
208
+ node,
209
+ r.length,
210
+ prefix,
211
+ true,
212
+ visitorMap
213
+ );
214
+ innerDepth += depth;
215
+ r2.push(...rr2);
216
+ }
217
+ }
218
+ if (r.length) {
219
+ str.push(...r.map((x) => " " + x));
220
+ }
221
+
222
+ if (!joinWithCaller) {
223
+ if (r2.length) {
224
+ str.push(` ${id} -> ${r2.join(" -> ")} [arrowhead=none];`);
225
+ }
226
+ }
227
+
228
+ return [innerDepth, r2];
229
+ }
230
+
231
+ const visitorMap = {};
232
+ typeMemberAndSubTypeMember(str2, "", node, null, 0, "r", false, visitorMap);
233
+
234
+ if (!options.naked) {
235
+ dumpNodeByNodeClass(str, nodeRegistry);
236
+ }
237
+ str.push(...str2);
238
+ str.push("}");
239
+
240
+ //
241
+ const dot = str.join("\n");
242
+ return dot;
243
+ }
244
+
245
+ export function dumpClassHierachry(
246
+ typeNode: UAObjectType | UAVariableType | UADataType | UAReferenceType,
247
+ options?: { naked?: boolean; depth?: number; showBaseType?: boolean; showSubType?: boolean }
248
+ ) {
249
+ options = options || { naked: false, showBaseType: true, showSubType: true };
250
+ const level = options.depth || 50;
251
+
252
+ const str: string[] = [];
253
+ const nodeRegistry = new NodeRegistry();
254
+ nodeRegistry.add(typeNode.browseName.name!, typeNode);
255
+ str.push("digraph G {");
256
+ if (!options.naked) {
257
+ // str.push(" splines=ortho;");
258
+ str.push(" rankdir=BT;");
259
+ str.push(" nodesep=0.5;");
260
+ str.push(" node [];");
261
+ }
262
+ function dumpSubtypes(str: string[], typeNode: BaseNode, level: number) {
263
+ const parentName = typeNode.browseName.name!.toString();
264
+ const references = typeNode.findReferencesEx("HasSubtype", BrowseDirection.Forward);
265
+ for (let i = 0; i < references.length; i++) {
266
+ const reference = references[i];
267
+ const childNode = reference.node! as UAVariableType | UAObjectType;
268
+ const nodeClass = NodeClass[childNode.nodeClass];
269
+ const childName = childNode.browseName.name!.toString();
270
+ nodeRegistry.add(childName, childNode);
271
+ const edgeAttributes = arrowHead(reference);
272
+ str.push(` ${childName} -> ${parentName} ${edgeAttributes};`);
273
+
274
+ if (level > 0) {
275
+ dumpSubtypes(str, childNode, level - 1);
276
+ }
277
+ }
278
+ }
279
+ function dumpBaseTypes(str: string[], typeNode: BaseNode, level: number) {
280
+ const parentName = typeNode.browseName.name!.toString();
281
+ const references = typeNode.findReferencesEx("HasSubtype", BrowseDirection.Inverse);
282
+ for (let i = 0; i < references.length; i++) {
283
+ const reference = references[i];
284
+ const childNode = reference.node! as UAVariableType | UAObjectType;
285
+ const nodeClass = NodeClass[childNode.nodeClass];
286
+ const childName = childNode.browseName.name!.toString();
287
+ nodeRegistry.add(childName, childNode);
288
+ const edgeAttributes = arrowHead(reference);
289
+ str.push(` ${parentName} -> ${childName} ${edgeAttributes};`);
290
+ if (level > 0) {
291
+ dumpBaseTypes(str, childNode, level - 1);
292
+ }
293
+ }
294
+ }
295
+ const str2: string[] = [];
296
+ if (options.showBaseType) {
297
+ dumpBaseTypes(str2, typeNode, level);
298
+ }
299
+ /** */
300
+ if (options.showSubType) {
301
+ dumpSubtypes(str2, typeNode, level);
302
+ }
303
+ if (!options.naked) {
304
+ dumpNodeByNodeClass(str, nodeRegistry);
305
+ }
306
+ str.push(...str2);
307
+ str.push("}");
308
+ return str.join("\n");
309
+ }
310
+
311
+ export function graphVizToPlantUml(str: string): string {
312
+ const ttt = "```";
313
+ return `${ttt}plantuml\n@startuml\n${str}\n@enduml\n${ttt}`;
314
+ }
315
+ export function dumpTypeDiagram(namespace: any) {
316
+ const objectTypes = [...namespace._objectTypeIterator()];
317
+ const variableTypes = [...namespace._variableTypeIterator()];
318
+ const dataTypes = [...namespace._dataTypeIterator()];
319
+ const referenceTypes = [...namespace._referenceTypeIterator()];
320
+ const addressSpace = namespace.addressSpace;
321
+
322
+ const str: string[] = [];
323
+ for (const type of [...objectTypes, ...variableTypes]) {
324
+ const d = opcuaToDot(type);
325
+ str.push(graphVizToPlantUml(d));
326
+
327
+ const d2 = dumpClassHierachry(type);
328
+ str.push(graphVizToPlantUml(d2));
329
+ }
330
+ for (const dataType of dataTypes) {
331
+ const d = opcuaToDot(dataType);
332
+ str.push(graphVizToPlantUml(d));
333
+ }
334
+ return str.join("\n");
335
+ }
package/.mocharc.js DELETED
@@ -1,13 +0,0 @@
1
- module.exports = {
2
- recursive: true,
3
- diff: true,
4
- extension: [".ts", ".js"],
5
- spec: ["test/*.ts"],
6
- bail: true,
7
- timeout: 20000,
8
- require: [
9
- "should",
10
- "ts-node/register",
11
- "source-map-support/register"
12
- ]
13
- };