UABaseNode.ts 9.7 KB

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