node-opcua-address-space 2.186.1 → 2.186.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.
- package/api/loader/nodeset_record.ts +2 -0
- package/api/loader/nodeset_record_applier.ts +1 -0
- package/api/loader/nodeset_records_to_xml.ts +3 -0
- package/api/loader/nodeset_xml_producer.ts +1 -0
- package/dist/api/loader/nodeset_record.d.ts +2 -0
- package/dist/api/loader/nodeset_record.d.ts.map +1 -1
- package/dist/api/loader/nodeset_record.js.map +1 -1
- package/dist/api/loader/nodeset_record_applier.d.ts.map +1 -1
- package/dist/api/loader/nodeset_record_applier.js +1 -0
- package/dist/api/loader/nodeset_record_applier.js.map +1 -1
- package/dist/api/loader/nodeset_records_to_xml.js +3 -0
- package/dist/api/loader/nodeset_records_to_xml.js.map +1 -1
- package/dist/api/loader/nodeset_xml_producer.d.ts.map +1 -1
- package/dist/api/loader/nodeset_xml_producer.js +2 -0
- package/dist/api/loader/nodeset_xml_producer.js.map +1 -1
- package/dist/impl/_instantiate_helpers.d.ts.map +1 -1
- package/dist/impl/_instantiate_helpers.js +6 -1
- package/dist/impl/_instantiate_helpers.js.map +1 -1
- package/dist/impl/_mandatory_child_or_requested_optional_filter.d.ts +7 -1
- package/dist/impl/_mandatory_child_or_requested_optional_filter.d.ts.map +1 -1
- package/dist/impl/_mandatory_child_or_requested_optional_filter.js +14 -3
- package/dist/impl/_mandatory_child_or_requested_optional_filter.js.map +1 -1
- package/dist/impl/base_node_impl.d.ts +4 -0
- package/dist/impl/base_node_impl.d.ts.map +1 -1
- package/dist/impl/base_node_impl.js +5 -0
- package/dist/impl/base_node_impl.js.map +1 -1
- package/dist/impl/base_node_private.d.ts.map +1 -1
- package/dist/impl/base_node_private.js +52 -0
- package/dist/impl/base_node_private.js.map +1 -1
- package/dist/impl/nodeid_manager.d.ts +19 -0
- package/dist/impl/nodeid_manager.d.ts.map +1 -1
- package/dist/impl/nodeid_manager.js +92 -16
- package/dist/impl/nodeid_manager.js.map +1 -1
- package/dist/impl/nodeset_tools/dump_to_bsd.d.ts.map +1 -1
- package/dist/impl/nodeset_tools/dump_to_bsd.js +15 -0
- package/dist/impl/nodeset_tools/dump_to_bsd.js.map +1 -1
- package/dist/impl/nodeset_tools/nodeset_to_records.d.ts.map +1 -1
- package/dist/impl/nodeset_tools/nodeset_to_records.js +3 -0
- package/dist/impl/nodeset_tools/nodeset_to_records.js.map +1 -1
- package/dist/impl/nodeset_tools/nodeset_to_xml.d.ts.map +1 -1
- package/dist/impl/nodeset_tools/nodeset_to_xml.js +3 -0
- package/dist/impl/nodeset_tools/nodeset_to_xml.js.map +1 -1
- package/dist/tsconfig_base.tsbuildinfo +1 -1
- package/impl/_instantiate_helpers.ts +12 -1
- package/impl/_mandatory_child_or_requested_optional_filter.ts +20 -3
- package/impl/base_node_impl.ts +7 -0
- package/impl/base_node_private.ts +57 -0
- package/impl/nodeid_manager.ts +97 -16
- package/impl/nodeset_tools/dump_to_bsd.ts +16 -0
- package/impl/nodeset_tools/nodeset_to_records.ts +3 -0
- package/impl/nodeset_tools/nodeset_to_xml.ts +3 -0
- package/package.json +5 -5
|
@@ -61,7 +61,13 @@ function _initialize_properties_and_components<B extends UAObject | UAVariable |
|
|
|
61
61
|
return; // nothing to do
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
// an instance declaration of a type (copyAlsoModellingRules) takes its type's placeholders too
|
|
65
|
+
const filter = new MandatoryChildOrRequestedOptionalFilter(
|
|
66
|
+
instance,
|
|
67
|
+
copyAlsoAllOptionals,
|
|
68
|
+
optionalsMap,
|
|
69
|
+
copyAlsoModellingRules
|
|
70
|
+
);
|
|
65
71
|
|
|
66
72
|
doTrace &&
|
|
67
73
|
traceLog(
|
|
@@ -115,6 +121,8 @@ export function initialize_properties_and_components<
|
|
|
115
121
|
optionals?: string[]
|
|
116
122
|
): void {
|
|
117
123
|
const extraInfo = new CloneHelper();
|
|
124
|
+
// flushed below, once the whole tree is cloned
|
|
125
|
+
extraInfo.canDefer = true;
|
|
118
126
|
|
|
119
127
|
extraInfo.pushContext({
|
|
120
128
|
clonedParent: instance,
|
|
@@ -151,6 +159,9 @@ export function initialize_properties_and_components<
|
|
|
151
159
|
browseNameMap
|
|
152
160
|
);
|
|
153
161
|
|
|
162
|
+
// the members a node only organizes, now that their owners were cloned
|
|
163
|
+
extraInfo.flushDeferred();
|
|
164
|
+
|
|
154
165
|
reconstructFunctionalGroupType(extraInfo);
|
|
155
166
|
|
|
156
167
|
reconstructNonHierarchicalReferences(extraInfo);
|
|
@@ -25,9 +25,16 @@ export class MandatoryChildOrRequestedOptionalFilter implements CloneFilter {
|
|
|
25
25
|
private readonly optionalsMap: OptionalMap;
|
|
26
26
|
private readonly references: UAReference[];
|
|
27
27
|
private readonly copyAlsoAllOptionals: boolean = false;
|
|
28
|
+
/**
|
|
29
|
+
* the clone is an instance declaration of a type (instantiate() inside a type): the
|
|
30
|
+
* placeholders its type declares are part of it, as the ModelCompiler emits them
|
|
31
|
+
* (OPC 10000-100 DI DeviceType/DeviceTypeImage/<ImageIdentifier>). An instance never takes one.
|
|
32
|
+
*/
|
|
33
|
+
private readonly keepPlaceholders: boolean = false;
|
|
28
34
|
|
|
29
|
-
constructor(instance: BaseNode, copyAlsoAllOptionals: boolean, optionalsMap: OptionalMap) {
|
|
35
|
+
constructor(instance: BaseNode, copyAlsoAllOptionals: boolean, optionalsMap: OptionalMap, keepPlaceholders = false) {
|
|
30
36
|
this.copyAlsoAllOptionals = copyAlsoAllOptionals;
|
|
37
|
+
this.keepPlaceholders = keepPlaceholders;
|
|
31
38
|
// should we clone the node to be a component or propertyOf of a instance
|
|
32
39
|
assert(null !== instance);
|
|
33
40
|
this.optionalsMap = optionalsMap;
|
|
@@ -78,7 +85,8 @@ export class MandatoryChildOrRequestedOptionalFilter implements CloneFilter {
|
|
|
78
85
|
// only if in requested optionals
|
|
79
86
|
return this.copyAlsoAllOptionals || (node.browseName?.name || "") in this.optionalsMap;
|
|
80
87
|
case "OptionalPlaceholder":
|
|
81
|
-
|
|
88
|
+
case "MandatoryPlaceholder":
|
|
89
|
+
return this.keepPlaceholders;
|
|
82
90
|
default:
|
|
83
91
|
return false; // ignored
|
|
84
92
|
}
|
|
@@ -94,7 +102,16 @@ export class MandatoryChildOrRequestedOptionalFilter implements CloneFilter {
|
|
|
94
102
|
}
|
|
95
103
|
/* c8 ignore next */
|
|
96
104
|
doTrace && traceLog("filterFor ", browseName, map);
|
|
97
|
-
|
|
105
|
+
// a placeholder's copy takes its own members, not the placeholders of its type: the walk
|
|
106
|
+
// would never end on a type whose placeholder is typed by a type holding one in turn
|
|
107
|
+
const isPlaceholder =
|
|
108
|
+
childInstance.modellingRule === "OptionalPlaceholder" || childInstance.modellingRule === "MandatoryPlaceholder";
|
|
109
|
+
const newFilter = new MandatoryChildOrRequestedOptionalFilter(
|
|
110
|
+
childInstance,
|
|
111
|
+
false,
|
|
112
|
+
map,
|
|
113
|
+
this.keepPlaceholders && !isPlaceholder
|
|
114
|
+
);
|
|
98
115
|
return newFilter;
|
|
99
116
|
}
|
|
100
117
|
}
|
package/impl/base_node_impl.ts
CHANGED
|
@@ -184,6 +184,8 @@ export interface InternalBaseNodeOptions {
|
|
|
184
184
|
nodesetDocumentation?: string;
|
|
185
185
|
/** the `<Category>` elements as the nodeset declared them; see BaseNode.nodesetCategory */
|
|
186
186
|
nodesetCategory?: string[];
|
|
187
|
+
/** `DesignToolOnly="true"` as the nodeset declared it; see BaseNode.designToolOnly */
|
|
188
|
+
designToolOnly?: boolean;
|
|
187
189
|
/** the AccessRestrictions as declared, whatever this loader applies; see BaseNode */
|
|
188
190
|
declaredAccessRestrictions?: string;
|
|
189
191
|
}
|
|
@@ -233,6 +235,8 @@ export abstract class BaseNodeImpl<T extends BaseNodeEvents & ListenerSignature<
|
|
|
233
235
|
public nodesetDocumentation?: string;
|
|
234
236
|
/** see BaseNode.nodesetCategory; only set when the nodeset declared at least one */
|
|
235
237
|
public nodesetCategory?: string[];
|
|
238
|
+
/** see BaseNode.designToolOnly; only set when the nodeset declared it */
|
|
239
|
+
public designToolOnly?: boolean;
|
|
236
240
|
/** see BaseNode.declaredAccessRestrictions */
|
|
237
241
|
public declaredAccessRestrictions?: string;
|
|
238
242
|
private _accessRestrictions?: AccessRestrictionsFlag;
|
|
@@ -459,6 +463,9 @@ export abstract class BaseNodeImpl<T extends BaseNodeEvents & ListenerSignature<
|
|
|
459
463
|
if (options.nodesetCategory && options.nodesetCategory.length > 0) {
|
|
460
464
|
this.nodesetCategory = [...options.nodesetCategory];
|
|
461
465
|
}
|
|
466
|
+
if (options.designToolOnly) {
|
|
467
|
+
this.designToolOnly = true;
|
|
468
|
+
}
|
|
462
469
|
if (options.parentNodeId) {
|
|
463
470
|
_private._declaredParentNodeId =
|
|
464
471
|
options.parentNodeId instanceof NodeId
|
|
@@ -595,6 +595,39 @@ function _merge_base_declaration(
|
|
|
595
595
|
);
|
|
596
596
|
}
|
|
597
597
|
|
|
598
|
+
/**
|
|
599
|
+
* whether `declaringNode` is the declared parent (ParentNodeId) of `node` while `reference` links the
|
|
600
|
+
* two without aggregating it: the parent then names the node, the reference does not
|
|
601
|
+
*/
|
|
602
|
+
function _isDeclaredNonAggregatedMemberOf(node: BaseNode, declaringNode: BaseNode, reference: UAReference): boolean {
|
|
603
|
+
const declaredParent = node.parentNodeId;
|
|
604
|
+
if (!declaredParent || !sameNodeId(declaredParent, declaringNode.nodeId)) {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
const addressSpace = node.addressSpace;
|
|
608
|
+
const referenceType = addressSpace.findReferenceType(reference.referenceType);
|
|
609
|
+
const aggregates = addressSpace.findReferenceType("Aggregates");
|
|
610
|
+
return !!referenceType && !!aggregates && !referenceType.isSubtypeOf(aggregates);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* whether `organizer` reaches `node` through a reference that does not aggregate it, while the
|
|
615
|
+
* node's declared parent (ParentNodeId) is another node that aggregates it: its owner
|
|
616
|
+
*/
|
|
617
|
+
function _isOrganizedWhileOwnedByAnother(node: BaseNode, organizer: BaseNode, reference: UAReference): boolean {
|
|
618
|
+
const owner = node.parentNodeId;
|
|
619
|
+
if (!owner || sameNodeId(owner, organizer.nodeId)) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
const addressSpace = node.addressSpace;
|
|
623
|
+
const referenceType = addressSpace.findReferenceType(reference.referenceType);
|
|
624
|
+
const aggregates = addressSpace.findReferenceType("Aggregates");
|
|
625
|
+
if (!referenceType || !aggregates || referenceType.isSubtypeOf(aggregates)) {
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
return node.findReferencesEx("Aggregates", BrowseDirection.Inverse).some((r) => sameNodeId(r.nodeId, owner));
|
|
629
|
+
}
|
|
630
|
+
|
|
598
631
|
/*
|
|
599
632
|
* clone properties and methods
|
|
600
633
|
* @private
|
|
@@ -662,6 +695,23 @@ function _clone_collection_new(
|
|
|
662
695
|
}
|
|
663
696
|
browseNameMap?.add(key);
|
|
664
697
|
|
|
698
|
+
// a node this one only organizes, while another node of the template owns it (aggregates
|
|
699
|
+
// it, as its ParentNodeId), is left to its owner: cloned through this reference first, it
|
|
700
|
+
// was parentless and named after its browse name alone (OPC 30100 IO-Link:
|
|
701
|
+
// IOLinkDeviceType/General organizes MethodSet/ApplicationReset). Once the whole tree is
|
|
702
|
+
// cloned, the owner's clone gets this reference too; a node no owner cloned is cloned here.
|
|
703
|
+
if (
|
|
704
|
+
extraInfo instanceof CloneHelper &&
|
|
705
|
+
extraInfo.canDefer &&
|
|
706
|
+
_isOrganizedWhileOwnedByAnother(node, nodeToClone, reference)
|
|
707
|
+
) {
|
|
708
|
+
extraInfo.deferInCurrentContext(() => attach(reference, node));
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
attach(reference, node);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function attach(reference: UAReference, node: UAVariable | UAObject | UAMethod): void {
|
|
665
715
|
// assert(reference.isForward);
|
|
666
716
|
// assert(reference.referenceType instanceof NodeId, "" + reference.referenceType.toString());
|
|
667
717
|
const options: CloneOptions = {
|
|
@@ -670,6 +720,13 @@ function _clone_collection_new(
|
|
|
670
720
|
copyAlsoModellingRules,
|
|
671
721
|
baseDeclarations: _base_declarations_of(nodeToClone, node.browseName)
|
|
672
722
|
};
|
|
723
|
+
// a member that the template declares under the node being cloned (its ParentNodeId), but links
|
|
724
|
+
// through a reference that does not aggregate it (Organizes), is named after that parent on the
|
|
725
|
+
// type (`DirectoryType_CertificateGroups`): the clone declares its own parent likewise, or it is
|
|
726
|
+
// named after its browse name alone and misses its id (`Directory_CertificateGroups`)
|
|
727
|
+
if (_isDeclaredNonAggregatedMemberOf(node, nodeToClone, reference)) {
|
|
728
|
+
options.parentNodeId = newParent.nodeId;
|
|
729
|
+
}
|
|
673
730
|
|
|
674
731
|
const alreadyCloned = extraInfo.getCloned({ originalParent: nodeToClone, clonedParent: newParent, originalNode: node });
|
|
675
732
|
if (alreadyCloned) {
|
package/impl/nodeid_manager.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BaseNode, UAReference, UAReferenceType } from "node-opcua-address-space-base";
|
|
2
2
|
import { assert } from "node-opcua-assert";
|
|
3
|
-
import { NodeClass, type QualifiedName, type QualifiedNameOptions } from "node-opcua-data-model";
|
|
3
|
+
import { BrowseDirection, NodeClass, type QualifiedName, type QualifiedNameOptions } from "node-opcua-data-model";
|
|
4
4
|
import { make_debugLog, make_warningLog } from "node-opcua-debug";
|
|
5
5
|
import { makeNodeId, NodeId, type NodeIdLike, NodeIdType, resolveNodeId, sameNodeId } from "node-opcua-nodeid";
|
|
6
6
|
import { setOwnProperty } from "node-opcua-utils";
|
|
@@ -86,6 +86,16 @@ function _findParentNodeId(addressSpace: AddressSpacePartial, options: Construct
|
|
|
86
86
|
return references.length ? _filterAggregates(addressSpace, references) : null;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** whether the node being built hangs, through an inverse reference, from a node `occupant` hangs from */
|
|
90
|
+
function _shareAnOrganizer(occupant: BaseNode, options: ConstructNodeIdOptions): boolean {
|
|
91
|
+
const references = (options.references ?? []).filter((r) => !r.isForward);
|
|
92
|
+
if (references.length === 0) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
const parents = occupant.findReferencesEx("HierarchicalReferences", BrowseDirection.Inverse).map((r) => r.nodeId);
|
|
96
|
+
return references.some((r) => parents.some((p) => sameNodeId(p, r.nodeId)));
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
function prepareName(browseName?: QualifiedName | QualifiedNameOptions): string {
|
|
90
100
|
const m = browseName?.name?.toString().replace(/[ ]/g, "").replace(/(<|>)/g, "");
|
|
91
101
|
return m || "";
|
|
@@ -110,9 +120,14 @@ export interface ConstructNodeIdOptions {
|
|
|
110
120
|
export type NodeEntry = [string, number, NodeClass];
|
|
111
121
|
export type NodeEntry1 = [string, number, string /*"Object" | "Variable" etc...*/];
|
|
112
122
|
|
|
123
|
+
/** the separator of a disambiguated symbolic name (`X__2`): prepareName never produces it from a browse name */
|
|
124
|
+
export const SYMBOLIC_NAME_OCCURRENCE_SEPARATOR = "__";
|
|
125
|
+
|
|
113
126
|
export class NodeIdManager {
|
|
114
127
|
private _cacheSymbolicName: { [key: string]: [number, NodeClass] } = {};
|
|
115
128
|
private _cacheSymbolicNameRev: Set<number> = new Set<number>();
|
|
129
|
+
/** the symbolic name each id was registered under, so that a node's members are named after it */
|
|
130
|
+
private _nameOfId: Map<number, string> = new Map<number, string>();
|
|
116
131
|
|
|
117
132
|
private _internal_id_counter: number;
|
|
118
133
|
private namespaceIndex: number;
|
|
@@ -149,9 +164,51 @@ export class NodeIdManager {
|
|
|
149
164
|
for (const [name, value, nodeClass] of symbols2) {
|
|
150
165
|
setOwnProperty(this._cacheSymbolicName, name, [value, nodeClass]);
|
|
151
166
|
this._cacheSymbolicNameRev.add(value);
|
|
167
|
+
if (!this._nameOfId.has(value)) {
|
|
168
|
+
this._nameOfId.set(value, name);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private _registerName(name: string, value: number, nodeClass: NodeClass | undefined): void {
|
|
174
|
+
setOwnProperty(this._cacheSymbolicName, name, [value, nodeClass || NodeClass.Unspecified]);
|
|
175
|
+
this._cacheSymbolicNameRev.add(value);
|
|
176
|
+
this._nameOfId.set(value, name);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The first `name__n` (n >= 2) a node may take: one the table does not list, or, for a node
|
|
181
|
+
* whose id is not fixed yet, one listed with an id that no node holds (a preset). A node whose
|
|
182
|
+
* id is fixed (`nodeId`) takes the name listed with that id, or a new one.
|
|
183
|
+
*/
|
|
184
|
+
private _nextFreeName(fullName: string, nodeId?: number): { name: string; value?: number } {
|
|
185
|
+
for (let n = 2; ; n++) {
|
|
186
|
+
const name = `${fullName}${SYMBOLIC_NAME_OCCURRENCE_SEPARATOR}${n}`;
|
|
187
|
+
const cached = this._cacheSymbolicName[name];
|
|
188
|
+
if (!cached) {
|
|
189
|
+
return { name };
|
|
190
|
+
}
|
|
191
|
+
if (
|
|
192
|
+
nodeId === undefined
|
|
193
|
+
? !this.addressSpace.findNode(makeNodeId(cached[0], this.namespaceIndex))
|
|
194
|
+
: cached[0] === nodeId
|
|
195
|
+
) {
|
|
196
|
+
return { name, value: cached[0] };
|
|
197
|
+
}
|
|
152
198
|
}
|
|
153
199
|
}
|
|
154
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Whether a node whose symbolic name another node holds takes the next free `name__n`: a
|
|
203
|
+
* parentless node (its name is its browse name alone, which distinct nodes share), and a
|
|
204
|
+
* child whose browse name differs from its sibling's by the namespace only (`1:Speed` next to
|
|
205
|
+
* `0:Speed`), which the name does not spell. A child of a string NodeId is not named at all:
|
|
206
|
+
* its id is built from its parent's.
|
|
207
|
+
*/
|
|
208
|
+
private _mayTakeAnotherName(parentInfo: [NodeId, Suffix] | null): boolean {
|
|
209
|
+
return parentInfo === null || parentInfo[0].identifierType === NodeIdType.NUMERIC;
|
|
210
|
+
}
|
|
211
|
+
|
|
155
212
|
public getSymbols(): NodeEntry1[] {
|
|
156
213
|
const line: NodeEntry1[] = [];
|
|
157
214
|
for (const [key, [value, nodeClass1]] of Object.entries(this._cacheSymbolicName)) {
|
|
@@ -185,6 +242,13 @@ export class NodeIdManager {
|
|
|
185
242
|
};
|
|
186
243
|
|
|
187
244
|
const buildUpName2 = (nodeId: NodeId, suffix: string) => {
|
|
245
|
+
// a parent registered under a name of its own (`X__2`) passes that name on to its members
|
|
246
|
+
if (nodeId.namespace === this.namespaceIndex && nodeId.identifierType === NodeIdType.NUMERIC) {
|
|
247
|
+
const registered = this._nameOfId.get(nodeId.value as number);
|
|
248
|
+
if (registered !== undefined) {
|
|
249
|
+
return registered + suffix;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
188
252
|
const namespaceIndex = nodeId.namespace;
|
|
189
253
|
let name = "";
|
|
190
254
|
let n: BaseNode | null = this.addressSpace.findNode(nodeId);
|
|
@@ -204,17 +268,27 @@ export class NodeIdManager {
|
|
|
204
268
|
fullParentName = buildUpName2(parentNodeId, suffix);
|
|
205
269
|
}
|
|
206
270
|
const fullName = compose(fullParentName, prepareName(options.browseName));
|
|
271
|
+
let name = fullName;
|
|
207
272
|
const cached = this._cacheSymbolicName[fullName];
|
|
208
|
-
if (cached
|
|
209
|
-
|
|
273
|
+
if (cached) {
|
|
274
|
+
if (this._isCacheHitReusable(cached[0], options, parentInfo)) {
|
|
275
|
+
return makeNodeId(cached[0], this.namespaceIndex);
|
|
276
|
+
}
|
|
277
|
+
if (!this._mayTakeAnotherName(parentInfo)) {
|
|
278
|
+
// a fresh id, with no name
|
|
279
|
+
return this._constructNodeId(options);
|
|
280
|
+
}
|
|
281
|
+
// another node holds the name: this one takes the next free `name__n`
|
|
282
|
+
const free = this._nextFreeName(fullName);
|
|
283
|
+
if (free.value !== undefined) {
|
|
284
|
+
this._nameOfId.set(free.value, free.name);
|
|
285
|
+
return makeNodeId(free.value, this.namespaceIndex);
|
|
286
|
+
}
|
|
287
|
+
name = free.name;
|
|
210
288
|
}
|
|
211
289
|
const nodeId = this._constructNodeId(options);
|
|
212
|
-
if (nodeId.identifierType === NodeIdType.NUMERIC
|
|
213
|
-
|
|
214
|
-
nodeId.value as number,
|
|
215
|
-
options.nodeClass || NodeClass.Unspecified
|
|
216
|
-
]);
|
|
217
|
-
this._cacheSymbolicNameRev.add(nodeId.value as number);
|
|
290
|
+
if (nodeId.identifierType === NodeIdType.NUMERIC) {
|
|
291
|
+
this._registerName(name, nodeId.value as number, options.nodeClass);
|
|
218
292
|
}
|
|
219
293
|
return nodeId;
|
|
220
294
|
}
|
|
@@ -231,12 +305,17 @@ export class NodeIdManager {
|
|
|
231
305
|
fullParentName = buildUpName2(parentNodeId, suffix);
|
|
232
306
|
}
|
|
233
307
|
const fullName = compose(fullParentName, prepareName(options.browseName));
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.
|
|
308
|
+
const value = nodeId.value as number;
|
|
309
|
+
const cached = this._cacheSymbolicName[fullName];
|
|
310
|
+
if (!cached) {
|
|
311
|
+
this._registerName(fullName, value, options.nodeClass);
|
|
312
|
+
} else if (cached[0] === value) {
|
|
313
|
+
this._nameOfId.set(value, fullName);
|
|
314
|
+
} else if (this._mayTakeAnotherName(parentInfo)) {
|
|
315
|
+
// another node holds the name (OPC 40502 CNC's three parentless X axes): this
|
|
316
|
+
// one is listed under the next free `name__n`, as it would be named when built
|
|
317
|
+
const free = this._nextFreeName(fullName, value);
|
|
318
|
+
this._registerName(free.name, value, options.nodeClass);
|
|
240
319
|
}
|
|
241
320
|
}
|
|
242
321
|
return nodeId;
|
|
@@ -275,9 +354,11 @@ export class NodeIdManager {
|
|
|
275
354
|
(occupant.browseName.namespaceIndex ?? 0) === (options.browseName?.namespaceIndex ?? 0);
|
|
276
355
|
|
|
277
356
|
const expectedParentNodeId = parentInfo ? parentInfo[0] : null;
|
|
357
|
+
// two parentless nodes are the same node only when one node organizes both (the same
|
|
358
|
+
// node declared twice in a folder); otherwise they merely share a browse name
|
|
278
359
|
const sameParent = expectedParentNodeId
|
|
279
360
|
? !!occupant.parentNodeId && sameNodeId(occupant.parentNodeId, expectedParentNodeId)
|
|
280
|
-
: !occupant.parentNodeId;
|
|
361
|
+
: !occupant.parentNodeId && _shareAnOrganizer(occupant, options);
|
|
281
362
|
|
|
282
363
|
return sameBrowseName && sameParent;
|
|
283
364
|
}
|
|
@@ -132,7 +132,23 @@ function dumpDataTypeStructure(
|
|
|
132
132
|
xw.endElement();
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* whether the 1.03 dictionary describes `dataType`: it lists Structures and Enumerations only. A
|
|
137
|
+
* basic type's subtype (OPC 30010 AutoID's CodeTypeDataType, a String) has no definition at all,
|
|
138
|
+
* and a bit-mask OptionSet's lists bits, not values: asking for them threw.
|
|
139
|
+
*/
|
|
140
|
+
function isDescribedInBSD(dataType: UADataType): boolean {
|
|
141
|
+
if (dataType.isStructure()) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
const isOptionSet = !!(dataType as UADataType & { isOptionSetDataType?: boolean }).isOptionSetDataType;
|
|
145
|
+
return !isOptionSet && dataType.isEnumeration();
|
|
146
|
+
}
|
|
147
|
+
|
|
135
148
|
function dumpDataTypeToBSD(xw: XmlWriter, dataType: UADataType, map: Map<number, string>) {
|
|
149
|
+
if (!isDescribedInBSD(dataType)) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
136
152
|
const addressSpace = dataType.addressSpace;
|
|
137
153
|
|
|
138
154
|
const name: string = dataType.browseName.name || "";
|
|
@@ -366,6 +366,9 @@ class RecordExporter {
|
|
|
366
366
|
if (node.releaseStatus) {
|
|
367
367
|
record.releaseStatus = node.releaseStatus;
|
|
368
368
|
}
|
|
369
|
+
if (node.designToolOnly) {
|
|
370
|
+
record.designToolOnly = true;
|
|
371
|
+
}
|
|
369
372
|
if (node.nodesetCategory && node.nodesetCategory.length > 0) {
|
|
370
373
|
record.category = [...node.nodesetCategory];
|
|
371
374
|
}
|
|
@@ -685,6 +685,9 @@ function dumpCommonAttributes(xw: XmlWriter, node: BaseNode) {
|
|
|
685
685
|
if (node.releaseStatus) {
|
|
686
686
|
xw.writeAttribute("ReleaseStatus", node.releaseStatus);
|
|
687
687
|
}
|
|
688
|
+
if (node.designToolOnly) {
|
|
689
|
+
xw.writeAttribute("DesignToolOnly", "true");
|
|
690
|
+
}
|
|
688
691
|
if (Object.hasOwn(node, "isAbstract")) {
|
|
689
692
|
const isAbstract = (node as unknown as { isAbstract: boolean }).isAbstract;
|
|
690
693
|
if (isAbstract) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-opcua-address-space",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.186.
|
|
4
|
+
"version": "2.186.2",
|
|
5
5
|
"description": "pure nodejs OPCUA SDK - module address-space",
|
|
6
6
|
"main": "./dist/api/index.js",
|
|
7
7
|
"types": "./dist/api/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@types/semver": "7.8.0",
|
|
24
24
|
"chalk": "4.1.2",
|
|
25
25
|
"dequeue": "^1.0.5",
|
|
26
|
-
"node-opcua-address-space-base": "2.186.
|
|
26
|
+
"node-opcua-address-space-base": "2.186.2",
|
|
27
27
|
"node-opcua-assert": "2.184.0",
|
|
28
28
|
"node-opcua-basic-types": "2.186.1",
|
|
29
29
|
"node-opcua-binary-stream": "2.186.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node-opcua-extension-object": "2.186.1",
|
|
40
40
|
"node-opcua-factory": "2.186.1",
|
|
41
41
|
"node-opcua-nodeid": "2.186.1",
|
|
42
|
-
"node-opcua-nodeset-ua": "2.186.
|
|
42
|
+
"node-opcua-nodeset-ua": "2.186.2",
|
|
43
43
|
"node-opcua-numeric-range": "2.186.1",
|
|
44
44
|
"node-opcua-object-registry": "2.184.0",
|
|
45
45
|
"node-opcua-pseudo-session": "2.186.1",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"node-opcua-nodesets": "2.185.0",
|
|
65
65
|
"node-opcua-packet-analyzer": "2.186.1",
|
|
66
66
|
"node-opcua-schemas": "2.186.1",
|
|
67
|
-
"node-opcua-service-filter": "2.186.
|
|
67
|
+
"node-opcua-service-filter": "2.186.2",
|
|
68
68
|
"node-opcua-test-fixtures": "2.184.0",
|
|
69
69
|
"node-opcua-test-helpers": "2.184.0"
|
|
70
70
|
},
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"internet of things"
|
|
84
84
|
],
|
|
85
85
|
"homepage": "http://node-opcua.github.io/",
|
|
86
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "86af5eac683e2b11900c1e93c232d4adc06b5b77",
|
|
87
87
|
"files": [
|
|
88
88
|
"dist",
|
|
89
89
|
"distHelpers",
|