nodeset.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/env/python
  2. # -*- coding: utf-8 -*-
  3. ###
  4. ### Author: Chris Iatrou (ichrispa@core-vector.net)
  5. ### Version: rev 13
  6. ###
  7. ### This program was created for educational purposes and has been
  8. ### contributed to the open62541 project by the author. All licensing
  9. ### terms for this source is inherited by the terms and conditions
  10. ### specified for by the open62541 project (see the projects readme
  11. ### file for more information on the LGPL terms and restrictions).
  12. ###
  13. ### This program is not meant to be used in a production environment. The
  14. ### author is not liable for any complications arising due to the use of
  15. ### this program.
  16. ###
  17. from __future__ import print_function
  18. import sys
  19. import xml.dom.minidom as dom
  20. from struct import pack as structpack
  21. from time import struct_time, strftime, strptime, mktime
  22. import logging; logger = logging.getLogger(__name__)
  23. from datatypes import *
  24. from nodes import *
  25. from constants import *
  26. ####################
  27. # Helper Functions #
  28. ####################
  29. hassubtype = NodeId("ns=0;i=45")
  30. def getSubTypesOf(nodeset, node):
  31. re = [node]
  32. for ref in node.references:
  33. if ref.referenceType == hassubtype and ref.isForward:
  34. re = re + getSubTypesOf(nodeset, nodeset.nodes[ref.target])
  35. return re
  36. def extractNamespaces(xmlfile):
  37. # Extract a list of namespaces used. The first namespace is always
  38. # "http://opcfoundation.org/UA/". minidom gobbles up
  39. # <NamespaceUris></NamespaceUris> elements, without a decent way to reliably
  40. # access this dom2 <uri></uri> elements (only attribute xmlns= are accessible
  41. # using minidom). We need them for dereferencing though... This function
  42. # attempts to do just that.
  43. namespaces = ["http://opcfoundation.org/UA/"]
  44. infile = open(xmlfile.name)
  45. foundURIs = False
  46. nsline = ""
  47. line = infile.readline()
  48. for line in infile:
  49. if "<namespaceuris>" in line.lower():
  50. foundURIs = True
  51. elif "</namespaceuris>" in line.lower():
  52. foundURIs = False
  53. nsline = nsline + line
  54. break
  55. if foundURIs:
  56. nsline = nsline + line
  57. if len(nsline) > 0:
  58. ns = dom.parseString(nsline).getElementsByTagName("NamespaceUris")
  59. for uri in ns[0].childNodes:
  60. if uri.nodeType != uri.ELEMENT_NODE:
  61. continue
  62. if uri.firstChild.data in namespaces:
  63. continue
  64. namespaces.append(uri.firstChild.data)
  65. infile.close()
  66. return namespaces
  67. def buildAliasList(xmlelement):
  68. """Parses the <Alias> XML Element present in must XML NodeSet definitions.
  69. Contents the Alias element are stored in a dictionary for further
  70. dereferencing during pointer linkage (see linkOpenPointer())."""
  71. aliases = {}
  72. for al in xmlelement.childNodes:
  73. if al.nodeType == al.ELEMENT_NODE:
  74. if al.hasAttribute("Alias"):
  75. aliasst = al.getAttribute("Alias")
  76. aliasnd = unicode(al.firstChild.data)
  77. aliases[aliasst] = aliasnd
  78. return aliases
  79. class NodeSet(object):
  80. """ This class handles parsing XML description of namespaces, instantiating
  81. nodes, linking references, graphing the namespace and compiling a binary
  82. representation.
  83. Note that nodes assigned to this class are not restricted to having a
  84. single namespace ID. This class represents the entire physical address
  85. space of the binary representation and all nodes that are to be included
  86. in that segment of memory.
  87. """
  88. def __init__(self):
  89. self.nodes = {}
  90. self.namespaces = ["http://opcfoundation.org/UA/"]
  91. def sanitize(self):
  92. for n in self.nodes.values():
  93. if n.sanitize() == False:
  94. raise Exception("Failed to sanitize node " + str(n))
  95. # Sanitize reference consistency
  96. for n in self.nodes.values():
  97. for ref in n.references:
  98. if not ref.source == n.id:
  99. raise Exception("Reference " + str(ref) + " has an invalid source")
  100. if not ref.referenceType in self.nodes:
  101. raise Exception("Reference " + str(ref) + " has an unknown reference type")
  102. if not ref.target in self.nodes:
  103. raise Exception("Reference " + str(ref) + " has an unknown target")
  104. def addNamespace(self, nsURL):
  105. if not nsURL in self.namespaces:
  106. self.namespaces.append(nsURL)
  107. def createNamespaceMapping(self, orig_namespaces):
  108. """Creates a dict that maps from the nsindex in the original nodeset to the
  109. nsindex in the combined nodeset"""
  110. m = {}
  111. for index,name in enumerate(orig_namespaces):
  112. m[index] = self.namespaces.index(name)
  113. return m
  114. def getNodeByBrowseName(self, idstring):
  115. return next((n for n in self.nodes.values() if idstring==n.browseName.name), None)
  116. def getRoot(self):
  117. return self.getNodeByBrowseName("Root")
  118. def createNode(self, xmlelement, nsMapping, hidden=False):
  119. ndtype = xmlelement.tagName.lower()
  120. if ndtype[:2] == "ua":
  121. ndtype = ndtype[2:]
  122. node = None
  123. if ndtype == 'variable':
  124. node = VariableNode(xmlelement)
  125. if ndtype == 'object':
  126. node = ObjectNode(xmlelement)
  127. if ndtype == 'method':
  128. node = MethodNode(xmlelement)
  129. if ndtype == 'objecttype':
  130. node = ObjectTypeNode(xmlelement)
  131. if ndtype == 'variabletype':
  132. node = VariableTypeNode(xmlelement)
  133. if ndtype == 'methodtype':
  134. node = MethodNode(xmlelement)
  135. if ndtype == 'datatype':
  136. node = DataTypeNode(xmlelement)
  137. if ndtype == 'referencetype':
  138. node = ReferenceTypeNode(xmlelement)
  139. if node and hidden:
  140. node.hidden = True
  141. # References from an existing nodeset are all suppressed
  142. for ref in node.references:
  143. ref.hidden = True
  144. for ref in node.inverseReferences:
  145. ref.hidden = True
  146. return node
  147. def addNodeSet(self, xmlfile, hidden = False):
  148. # Extract NodeSet DOM
  149. nodesets = dom.parse(xmlfile).getElementsByTagName("UANodeSet")
  150. if len(nodesets) == 0 or len(nodesets) > 1:
  151. raise Exception(self, self.originXML + " contains no or more then 1 nodeset")
  152. nodeset = nodesets[0]
  153. # Create the namespace mapping
  154. orig_namespaces = extractNamespaces(xmlfile) # List of namespaces used in the xml file
  155. for ns in orig_namespaces:
  156. self.addNamespace(ns)
  157. nsMapping = self.createNamespaceMapping(orig_namespaces)
  158. # Extract the aliases
  159. aliases = None
  160. for nd in nodeset.childNodes:
  161. if nd.nodeType != nd.ELEMENT_NODE:
  162. continue
  163. ndtype = nd.tagName.lower()
  164. if 'aliases' in ndtype:
  165. aliases = buildAliasList(nd)
  166. # Instantiate nodes
  167. newnodes = []
  168. for nd in nodeset.childNodes:
  169. if nd.nodeType != nd.ELEMENT_NODE:
  170. continue
  171. node = self.createNode(nd, nsMapping, hidden)
  172. if not node:
  173. continue
  174. node.replaceAliases(aliases)
  175. node.replaceNamespaces(nsMapping)
  176. # Add the node the the global dict
  177. if node.id in self.nodes:
  178. raise Exception("XMLElement with duplicate ID " + str(node.id))
  179. self.nodes[node.id] = node
  180. newnodes.append(node)
  181. # add inverse references
  182. for node in newnodes:
  183. for ref in node.references:
  184. newsource = self.nodes[ref.target]
  185. hide = ref.hidden or (node.hidden and newsource.hidden)
  186. newref = Reference(newsource.id, ref.referenceType, ref.source, False, hide)
  187. newsource.inverseReferences.add(newref)
  188. for ref in node.inverseReferences:
  189. newsource = self.nodes[ref.target]
  190. hide = ref.hidden or (node.hidden and newsource.hidden)
  191. newref = Reference(newsource.id, ref.referenceType, ref.source, True, hide)
  192. newsource.references.add(newref)