UABaseNode.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import type { NamespaceTable } from "./NameSpaceTable";
  2. import { coerceNodeId, NodeId } from "./NodeId";
  3. import { UAReference } from "./UAReference";
  4. import { assert } from "@/util/assert";
  5. import { XMLElem, type IToXML } from "@/util/XmlElem";
  6. import { UARolePermission } from "./UARolePermission";
  7. import { type IAddressSpace } from "./IAddressSpace";
  8. import { ReferenceTypeIds } from "./opcua_node_ids";
  9. import { UALocalizedText } from "./UALocalizedText";
  10. export class UABaseNode implements IToXML{
  11. public static nullBaseNode=new UABaseNode({browseName: "", addressSpace: {} as IAddressSpace, nodeId: NodeId.nullNodeId});
  12. public nodeId!: NodeId;
  13. public nodeClass="UABaseNode";
  14. public browseName!: string;
  15. public addressSpace!: IAddressSpace
  16. public displayName: UALocalizedText[]=[];
  17. public description: UALocalizedText[]=[];
  18. public symbolicName?: string;
  19. public releaseStatus!: string;
  20. public hasNoPermissions!: boolean;
  21. public writeMask!: number;
  22. public userWriteMask!: number;
  23. public category: string[]=[];
  24. public documentation?: string;
  25. public accessRestriction?: number;
  26. public rolePermissions: UARolePermission[]=[];
  27. public references: UAReference[]=[];
  28. constructor(options: UABaseNodeOptions) {
  29. Object.assign(this, options);
  30. }
  31. reIndex(nst: NamespaceTable, onst: NamespaceTable) {
  32. const nsName=onst.getUri(this.nodeId.namespace);
  33. assert(nsName!=undefined)
  34. const newIndex=nst.getIndex(nsName);
  35. assert(newIndex!=undefined)
  36. this.nodeId.namespace=newIndex;
  37. for(const uaref of this.references) {
  38. uaref.reIndex(nst, onst);
  39. }
  40. }
  41. getParent(): UABaseNode|null {
  42. const ref=this.getParentRef();
  43. if(ref?.fromNode===this)
  44. return ref.toNode;
  45. if(ref?.toNode===this)
  46. return ref.fromNode;
  47. return null;
  48. }
  49. setParent(newParentNode: UABaseNode, newRefType: string) {
  50. const hierReferences=this.addressSpace.getSubTreeAsList("ns=0;i="+ReferenceTypeIds.HierarchicalReferences);
  51. let href;
  52. for(const r of this.references) { //find current href in references
  53. href=hierReferences.find((n) => {n.browseName===r.referenceType})
  54. }
  55. for(let i=this.references.length;i--;i>=0) { //remove current href from references
  56. const r=this.references[i];
  57. if(r.referenceType==href?.browseName)
  58. this.references.splice(i,1);
  59. }
  60. const newRefA=new UAReference(this.nodeId, newRefType, newParentNode.nodeId, true);
  61. newRefA.fromNode=this;
  62. newRefA.toNode=newParentNode;
  63. this.references.push(newRefA);
  64. const newRefB=new UAReference(newParentNode.nodeId, newRefType, this.nodeId, false);
  65. newRefB.fromNode=this;
  66. newRefB.toNode=newParentNode;
  67. this.references.push(newRefB);
  68. }
  69. getParentRef(): UAReference|null{
  70. for(const ref of this.references) {
  71. switch(ref.referenceType) {
  72. case 'HasComponent':
  73. case 'HasOrderedComponent':
  74. case 'Organizes':
  75. case 'HasProperty':
  76. case 'HasSubtype':
  77. case 'HasAddIn':
  78. if(ref.fromNode===this&&ref.isForward===false)
  79. return ref;
  80. if(ref.toNode===this&&ref.isForward===true)
  81. return ref;
  82. }
  83. }
  84. return null;
  85. }
  86. getChildren(): UABaseNode[] {
  87. const children: UABaseNode[]=[];
  88. for(const ref of this.references) {
  89. switch(ref.referenceType) {
  90. case 'HasComponent':
  91. case 'HasOrderedComponent':
  92. case 'Organizes':
  93. case 'HasProperty':
  94. case 'HasSubtype':
  95. case 'HasAddIn':
  96. if(ref.isForward&&ref.fromNode===this) {
  97. if(!children.includes(ref.toNode))
  98. children.push(ref.toNode);
  99. }
  100. if(!ref.isForward&&ref.toNode===this) {
  101. if(!children.includes(ref.fromNode))
  102. children.push(ref.fromNode);
  103. }
  104. break;
  105. }
  106. }
  107. return children;
  108. }
  109. getModellingRule(): string|undefined{
  110. for(const ref of this.references) {
  111. if(ref.referenceType == "HasModellingRule"){
  112. return ref.toNode.browseName;
  113. }
  114. }
  115. return undefined;
  116. }
  117. resolveReferences(nm: Map<string, UABaseNode>) {
  118. for(const ref of this.references) {
  119. const fromNode=nm.get(ref.fromRef.toString())
  120. if(fromNode)
  121. ref.fromNode=fromNode;
  122. const toNode=nm.get(ref.toRef.toString())
  123. if(!toNode)
  124. continue; //TODO: if we cant find the node; the parser is still incomplete or the nodeset is broken
  125. ref.toNode=toNode;
  126. if(ref.fromNode.nodeId.toString()===this.nodeId.toString()){ //add this reference to referenced node
  127. //Bug? when loading from filedrop; fromNode is a proxy; this is not.
  128. if(!ref.toNode.references.includes(ref))
  129. ref.toNode.references.push(ref);
  130. }
  131. }
  132. }
  133. static fromXML(xmlObject: any, addressSpace: IAddressSpace): UABaseNode{
  134. const references:UAReference[]=[];
  135. const nodeId=coerceNodeId(xmlObject['@_NodeId']);
  136. for(const xmlref of xmlObject['References']?.['Reference']||[]) {
  137. references.push(UAReference.fromXML(xmlref, nodeId));
  138. }
  139. const displayNames:UALocalizedText[]=[];
  140. for(const xmldn of xmlObject['DisplayName']||[]) {
  141. displayNames.push(UALocalizedText.fromXML("DisplayName", xmldn));
  142. }
  143. const descriptions:UALocalizedText[]=[];
  144. for(const desc of xmlObject['Description']||[]) {
  145. descriptions.push(UALocalizedText.fromXML("Description", desc));
  146. }
  147. const permissions:UARolePermission[]=[];
  148. for(const perm of xmlObject['RolePermissions']?.['RolePermission']||[]) {
  149. permissions.push(UARolePermission.fromXML(perm));
  150. }
  151. const ua=new UABaseNode({addressSpace: addressSpace,
  152. nodeId: nodeId,
  153. browseName: xmlObject['@_BrowseName'],
  154. writeMask: Number(xmlObject['@_WriteMask']||0),
  155. userWriteMask: Number(xmlObject['@_UserWriteMask']||0),
  156. accessRestriction: xmlObject['@_AccessRestrictions'],
  157. hasNoPermissions: xmlObject['@_HasNoPermissions']||false,
  158. symbolicName: xmlObject['@_SymbolicName'],
  159. releaseStatus: xmlObject['@_ReleaseStatus']||"Released",
  160. displayName: displayNames,
  161. description: descriptions,
  162. category: xmlObject['Category'],
  163. documentation: xmlObject['Documentation']?.['#text'],
  164. references: references,
  165. rolePermissions: permissions,
  166. });
  167. return ua;
  168. }
  169. toXML(lnst: NamespaceTable, gnst: NamespaceTable): XMLElem {
  170. const nid=UABaseNode.localNodeId(this.nodeId, lnst, gnst);
  171. const elem =new XMLElem('UABaseNode');
  172. elem.attr('NodeId', nid.toString())
  173. .attr('BrowseName', this.browseName)
  174. .attr('WriteMask', this.writeMask.toString())
  175. .attr('UserWriteMask', this.userWriteMask.toString())
  176. .attr('AccessRestriction', this.accessRestriction?.toString())
  177. .attr('HasNoPermissions', this.hasNoPermissions)
  178. .attr('SymbolicName', this.symbolicName)
  179. .attr('ReleaseStatus',this.releaseStatus)
  180. .attr('Documentation', this.documentation);
  181. for(const c in this.category) {
  182. elem.attr('Category', c);
  183. }
  184. const refs=new XMLElem('References');
  185. for(const ref of this.references) {
  186. if(ref.fromNode.nodeId.toString()==this.nodeId.toString()) //on load resolveReferences() duplicates references to both sides. skip them for export
  187. refs.add(ref.toXML(lnst, gnst));
  188. }
  189. if(refs.elements.length>0)
  190. elem.add(refs);
  191. for(const dn of this.displayName)
  192. elem.add(dn.toXML());
  193. for(const desc of this.description)
  194. elem.add(desc.toXML());
  195. const perms=new XMLElem('RolePermissions')
  196. for(const perm of this.rolePermissions)
  197. perms.add(perm.toXML());
  198. if(perms.elements.length>0)
  199. elem.add(perms);
  200. return elem;
  201. }
  202. static localNodeId(nodeId: NodeId, lnst: NamespaceTable, gnst: NamespaceTable) {
  203. const ns_uri=gnst.getUri(nodeId.namespace);
  204. assert(ns_uri);
  205. const nsIdx=lnst.getIndex(ns_uri);
  206. assert(nsIdx);
  207. const tmpNode= coerceNodeId(nodeId.toString());
  208. tmpNode.namespace=nsIdx;
  209. return tmpNode;
  210. }
  211. }
  212. export interface UABaseNodeOptions {
  213. addressSpace: IAddressSpace;
  214. nodeId: NodeId;
  215. browseName: string;
  216. writeMask?: number;
  217. userWriteMask?: number;
  218. accessRestriction?: number;
  219. hasNoPermissions?: boolean;
  220. symbolicName?: string;
  221. releaseStatus?: string;
  222. displayName?: UALocalizedText[];
  223. description?: UALocalizedText[];
  224. category?: string[];
  225. documentation?: string;
  226. references?: UAReference[];
  227. rolePermissions?: UARolePermission[];
  228. }