generate_datatypes.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. from __future__ import print_function
  2. import sys
  3. import time
  4. import platform
  5. import getpass
  6. from collections import OrderedDict
  7. import re
  8. from lxml import etree
  9. import argparse
  10. fixed_size = {"UA_Boolean": 1, "UA_SByte": 1, "UA_Byte": 1, "UA_Int16": 2, "UA_UInt16": 2,
  11. "UA_Int32": 4, "UA_UInt32": 4, "UA_Int64": 8, "UA_UInt64": 8, "UA_Float": 4,
  12. "UA_Double": 8, "UA_DateTime": 8, "UA_Guid": 16, "UA_StatusCode": 4}
  13. builtin_types = ["UA_Boolean", "UA_Byte", "UA_Int16", "UA_UInt16", "UA_Int32", "UA_UInt32",
  14. "UA_Int64", "UA_UInt64", "UA_Float", "UA_Double", "UA_String", "UA_DateTime",
  15. "UA_Guid", "UA_ByteString", "UA_XmlElement", "UA_NodeId", "UA_ExpandedNodeId",
  16. "UA_StatusCode", "UA_QualifiedName", "UA_LocalizedText", "UA_ExtensionObject",
  17. "UA_Variant", "UA_DataValue", "UA_DiagnosticInfo"]
  18. excluded_types = ["UA_InstanceNode", "UA_TypeNode", "UA_ServerDiagnosticsSummaryDataType",
  19. "UA_SamplingIntervalDiagnosticsDataType", "UA_SessionSecurityDiagnosticsDataType",
  20. "UA_SubscriptionDiagnosticsDataType", "UA_SessionDiagnosticsDataType"]
  21. class Type(object):
  22. def __init__(self, name, description = ""):
  23. self.name = name
  24. self.description = description
  25. def string_c(self):
  26. pass
  27. class EnumerationType(Type):
  28. def __init__(self, name, description = "", elements = OrderedDict()):
  29. self.name = name
  30. self.description = description
  31. self.elements = elements # maps a name to an integer value
  32. def append_enum(name, value):
  33. self.elements[name] = value
  34. def string_c(self):
  35. return "typedef enum { \n " + \
  36. ",\n ".join(map(lambda (key, value) : key.upper() + " = " + value, self.elements.iteritems())) + \
  37. "\n} " + self.name + ";"
  38. class OpaqueType(Type):
  39. def string_c(self):
  40. return "typedef UA_ByteString " + self.name + ";"
  41. class StructMember(object):
  42. def __init__(self, name, memberType, isArray):
  43. self.name = name
  44. self.memberType = memberType
  45. self.isArray = isArray
  46. class StructType(Type):
  47. def __init__(self, name, description, members = OrderedDict()):
  48. self.name = name
  49. self.description = description
  50. self.members = members # maps a name to a member definition
  51. def fixed_size(self):
  52. if self.name in fixed_size:
  53. return True
  54. for m in self.members:
  55. if m.isArray or not m.memberType.fixed_size():
  56. return False
  57. return True
  58. def string_c(self):
  59. if len(self.members) == 0:
  60. return "typedef void * " + self.name + ";"
  61. returnstr = "typedef struct {\n"
  62. for name, member in self.members.iteritems():
  63. if member.isArray:
  64. returnstr += " UA_Int32 noOf" + name[0].upper() + name[1:] + ";\n"
  65. returnstr += " " + member.memberType + " *" +name + ";\n"
  66. else:
  67. returnstr += " " + member.memberType + " " +name + ";\n"
  68. return returnstr + "} " + self.name + ";"
  69. def parseTypeDefinitions(xmlDescription, type_selection = None):
  70. '''Returns an ordered dict that maps names to types. The order is such that
  71. every type depends only on known types. '''
  72. ns = {"opc": "http://opcfoundation.org/BinarySchema/"}
  73. tree = etree.parse(xmlDescription)
  74. typeSnippets = tree.xpath("/opc:TypeDictionary/*[not(self::opc:Import)]", namespaces=ns)
  75. types = OrderedDict()
  76. # types we do not want to autogenerate
  77. def skipType(name):
  78. if name in builtin_types:
  79. return True
  80. if name in excluded_types:
  81. return True
  82. if "Test" in name: # skip all test types
  83. return True
  84. if re.search("NodeId$", name) != None:
  85. return True
  86. if type_selection and not(name in type_selection):
  87. return True
  88. return False
  89. def stripTypename(tn):
  90. return tn[tn.find(":")+1:]
  91. def camlCase2CCase(item):
  92. "Member names begin with a lower case character"
  93. return item[:1].lower() + item[1:] if item else ''
  94. def typeReady(element):
  95. "Do we have the member types yet?"
  96. for child in element:
  97. if child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  98. if stripTypename(child.get("TypeName")) not in types:
  99. return False
  100. return True
  101. def parseEnumeration(typeXml):
  102. name = "UA_" + typeXml.get("Name")
  103. description = ""
  104. elements = OrderedDict()
  105. for child in typeXml:
  106. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  107. description = child.text
  108. if child.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedValue":
  109. elements[name + "_" + child.get("Name")] = child.get("Value")
  110. return EnumerationType(name, description, elements)
  111. def parseOpaque(typeXml):
  112. name = "UA_" + typeXml.get("Name")
  113. description = ""
  114. for child in typeXml:
  115. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  116. description = child.text
  117. return OpaqueType(name, description)
  118. def parseStructured(typeXml):
  119. "Returns None if we miss member descriptions"
  120. name = "UA_" + typeXml.get("Name")
  121. description = ""
  122. for child in typeXml:
  123. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  124. description = child.text
  125. # ignore lengthfields, just tag the array-members as an array
  126. lengthfields = []
  127. for child in typeXml:
  128. if child.get("LengthField"):
  129. lengthfields.append(child.get("LengthField"))
  130. members = OrderedDict()
  131. for child in typeXml:
  132. if not child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  133. continue
  134. if child.get("Name") in lengthfields:
  135. continue
  136. memberType = "UA_" + stripTypename(child.get("TypeName"))
  137. if not memberType in types and not memberType in builtin_types:
  138. return None
  139. memberName = camlCase2CCase(child.get("Name"))
  140. isArray = True if child.get("LengthField") else False
  141. members[memberName] = StructMember(memberName, memberType, isArray)
  142. return StructType(name, description, members)
  143. finished = False
  144. while(not finished):
  145. finished = True
  146. for typeXml in typeSnippets:
  147. name = "UA_" + typeXml.get("Name")
  148. if name in types or skipType(name):
  149. continue
  150. if typeXml.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedType":
  151. t = parseEnumeration(typeXml)
  152. types[t.name] = t
  153. elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}OpaqueType":
  154. t = parseOpaque(typeXml)
  155. types[t.name] = t
  156. elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}StructuredType":
  157. t = parseStructured(typeXml)
  158. if t == None:
  159. finished = False
  160. else:
  161. types[t.name] = t
  162. return types
  163. def main():
  164. parser = argparse.ArgumentParser()
  165. parser.add_argument('--only-needed', action='store_true', help='generate only types needed for compile')
  166. parser.add_argument('xml', help='path/to/Opc.Ua.Types.bsd')
  167. parser.add_argument('outfile', help='outfile w/o extension')
  168. args = parser.parse_args()
  169. outname = args.outfile.split("/")[-1]
  170. inname = args.xml.split("/")[-1]
  171. fh = open(args.outfile + "_generated.h",'w')
  172. def printh(string):
  173. print(string, end='\n', file=fh)
  174. # # whitelist for "only needed" profile
  175. # from type_lists import only_needed_types
  176. # # some types are omitted (pretend they exist already)
  177. # existing_types.add("NodeIdType")
  178. types = parseTypeDefinitions(args.xml)
  179. printh('''/**
  180. * @file ''' + outname + '''_generated.h
  181. *
  182. * @brief Autogenerated data types
  183. *
  184. * Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
  185. * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + '''
  186. */
  187. #ifndef ''' + outname.upper() + '''_GENERATED_H_
  188. #define ''' + outname.upper() + '''_GENERATED_H_
  189. #ifdef __cplusplus
  190. extern "C" {
  191. #endif
  192. #include "ua_types.h"
  193. /**
  194. * @ingroup types
  195. *
  196. * @defgroup ''' + outname + '''_generated Autogenerated ''' + outname + ''' Types
  197. *
  198. * @brief Data structures that are autogenerated from an XML-Schema.
  199. * @{
  200. */''')
  201. for t in types.itervalues():
  202. printh("")
  203. if t.description != "":
  204. printh("/** @brief " + t.description + "*/")
  205. printh(t.string_c())
  206. printh('''
  207. /// @} /* end of group */
  208. #ifdef __cplusplus
  209. } // extern "C"
  210. #endif
  211. #endif''')
  212. fh.close()
  213. if __name__ == "__main__":
  214. main()