Commit 136b6fd0 authored by ORD's avatar ORD Committed by GitHub

also export namespace of referenced nodes (#327)

* also export namespace of referenced nodes

* xmlexport: also export ns used in browsename

* __str__ for NodeData

* heavy cleanup of xmlimporter

* more xml import fixes

* fix importing lists of primitives

* export and import array dimensions

* fix by-ref error

* remove debug print

* fix xml test

* do not store ns uri when not necessary

* sdfsdfsd
parent 777e812f
......@@ -17,6 +17,7 @@ class XmlExporter(object):
self.logger = logging.getLogger(__name__)
self.server = server
self.aliases = {}
self._addr_idx_to_xml_idx = {}
node_set_attributes = OrderedDict()
node_set_attributes['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'
......@@ -48,36 +49,52 @@ class XmlExporter(object):
self._add_alias_els()
def _add_namespaces(self, nodes, uris):
# first get a list of all indexes used by nodes
idxs = []
for node in nodes:
if node.nodeid.NamespaceIndex != 0 and node.nodeid.NamespaceIndex not in idxs:
idxs.append(node.nodeid.NamespaceIndex)
idxs = self._get_ns_idxs_of_nodes(nodes)
ns_array = self.server.get_namespace_array()
print("IDXS 1", idxs)
# now add index of provided uris if necessary
if uris:
for uri in uris:
if uri in ns_array:
i = ns_array.index(uri)
if i not in idxs:
idxs.append(i)
print("IDXS 2", idxs)
self._add_idxs_from_uris(idxs, uris, ns_array)
# now create a dict of idx_in_address_space to idx_in_exported_file
self._addr_idx_to_xml_idx = self._make_idx_dict(idxs, ns_array)
ns_to_export = [ns_array[i] for i in sorted(list(self._addr_idx_to_xml_idx.keys())) if i != 0]
# write namespaces to xml
self._add_namespace_uri_els(ns_to_export)
def _make_idx_dict(self, idxs, ns_array):
idxs.sort()
self._addr_idx_to_xml_idx = {0: 0}
ns_to_export = []
addr_idx_to_xml_idx = {0: 0}
for xml_idx, addr_idx in enumerate(idxs):
if addr_idx >= len(ns_array):
break
self._addr_idx_to_xml_idx[addr_idx] = xml_idx + 1
ns_to_export.append(ns_array[addr_idx])
print(" ADDR_TO_ XML", self._addr_idx_to_xml_idx)
addr_idx_to_xml_idx[addr_idx] = xml_idx + 1
return addr_idx_to_xml_idx
def _get_ns_idxs_of_nodes(self, nodes):
"""
get a list of all indexes used or references by nodes
"""
idxs = []
for node in nodes:
node_idxs = [node.nodeid.NamespaceIndex]
node_idxs.append(node.get_browse_name().NamespaceIndex)
node_idxs.extend(ref.NodeId.NamespaceIndex for ref in node.get_references())
node_idxs = list(set(node_idxs)) # remove duplicates
for i in node_idxs:
if i != 0 and i not in idxs:
idxs.append(i)
return idxs
def _add_idxs_from_uris(self, idxs, uris, ns_array):
for uri in uris:
if uri in ns_array:
i = ns_array.index(uri)
if i not in idxs:
idxs.append(i)
# write namespaces to xml
self._add_namespace_uri_els(ns_to_export)
def write_xml(self, xmlpath, pretty=True):
"""
......@@ -96,12 +113,12 @@ class XmlExporter(object):
self.etree.write(xmlpath,
encoding='utf-8',
xml_declaration=True
)
)
else:
self.etree.write(xmlpath,
encoding='utf-8',
xml_declaration=True
)
)
def dump_etree(self):
"""
......@@ -120,7 +137,6 @@ class XmlExporter(object):
Returns:
"""
node_class = node.get_node_class()
print("Exporting: ", node)
if node_class is ua.NodeClass.Object:
self.add_etree_object(node)
......@@ -165,7 +181,6 @@ class XmlExporter(object):
parent = node.get_parent()
displayname = node.get_display_name().Text.decode('utf-8')
desc = node.get_description().Text
print("NODE COMMON", node)
node_el = Et.SubElement(self.etree.getroot(), nodetype)
node_el.attrib["NodeId"] = self._node_to_string(nodeid)
node_el.attrib["BrowseName"] = self._bname_to_string(browsename)
......@@ -209,8 +224,9 @@ class XmlExporter(object):
rank = node.get_value_rank()
if rank != -1:
el.attrib["ValueRank"] = str(rank)
#var = node.get_attribute(ua.AttributeIds.ArrayDimensions())
#self._addobj_el.attrib["ArrayDimensions"] = str(var.Value.Value)
dim = node.get_attribute(ua.AttributeIds.ArrayDimensions)
if dim.Value.Value:
el.attrib["ArrayDimensions"] = ",".join([str(i) for i in dim.Value.Value])
el.attrib["DataType"] = dtype_name
value_to_etree(el, dtype_name, dtype, node)
......@@ -332,7 +348,7 @@ def _val_to_etree(el, dtype, val):
id_el = Et.SubElement(el, "uax:String")
id_el.text = str(val)
elif not hasattr(val, "ua_types"):
if type(val) is bytes:
if isinstance(val, bytes):
el.text = val.decode("utf-8")
else:
el.text = str(val)
......
This diff is collapsed.
......@@ -15,6 +15,24 @@ def _to_bool(val):
return False
def ua_type_to_python(val, uatype):
if uatype.startswith("Int") or uatype.startswith("UInt"):
return int(val)
elif uatype.lower().startswith("bool"):
return _to_bool(val)
elif uatype in ("Double", "Float"):
return float(val)
elif uatype in ("String"):
return val
elif uatype in ("Bytes", "Bytes", "ByteString", "ByteArray"):
if sys.version_info.major > 2:
return bytes(val, 'utf8')
else:
return val
else:
raise Exception("uatype nopt handled", uatype, " for val ", val)
class NodeData(object):
def __init__(self):
......@@ -49,6 +67,10 @@ class NodeData(object):
# datatype
self.definition = []
def __str__(self):
return "NodeData(nodeid:{})".format(self.nodeid)
__repr__ = __str__
class RefStruct(object):
......@@ -69,8 +91,7 @@ class ExtObj(object):
class XMLParser(object):
def __init__(self, xmlpath, server):
self.server = server # POC
def __init__(self, xmlpath):
self.logger = logging.getLogger(__name__)
self._retag = re.compile(r"(\{.*\})(.*)")
self.path = xmlpath
......@@ -104,16 +125,15 @@ class XMLParser(object):
break
return aliases
def __iter__(self):
def get_node_datas(self):
nodes = []
for child in self.root:
name = self._retag.match(child.tag).groups()[1]
if name not in ["Aliases", "NamespaceUris"]:
node = self._parse_node(name, child)
nodes.append(node)
self.it = iter(nodes)
return self
return nodes
def __next__(self):
while True:
......@@ -219,6 +239,8 @@ class XMLParser(object):
obj.value = self._parse_list_of_extension_object(el)
elif ntag == "ListOfLocalizedText":
obj.value = self._parse_list_of_localized_text(el)
elif ntag.startswith("ListOf"):
obj.value = self._parse_list(el[0])
elif ntag == "ExtensionObject":
obj.value = self._parse_extension_object(el)
else:
......@@ -228,6 +250,17 @@ class XMLParser(object):
txtlist = [txt.strip() for txt in el.itertext()]
return "".join(txtlist)
def _parse_list(self, el):
value = []
for val_el in el:
ntag = self._retag.match(val_el.tag).groups()[1]
if ntag.startswith("ListOf"):
val = self._parse_list(val_el)
else:
val = ua_type_to_python(val_el.text, ntag)
value.append(val)
return value
def _parse_list_of_localized_text(self, el):
value = []
for localized_text_list in el:
......
......@@ -400,8 +400,8 @@ class Server(object):
"""
Import nodes defined in xml
"""
importer = xmlimporter.XmlImporter(self.iserver.node_mgt_service)
return importer.import_xml(path, self)
importer = xmlimporter.XmlImporter(self)
return importer.import_xml(path)
def export_xml(self, nodes, path):
"""
......
......@@ -443,49 +443,6 @@ class TestUnit(unittest.TestCase):
self.assertTrue(wce.eval(ev))
def test_xmlparser_get_node_id(self):
server = None
importer = XmlImporter(server)
res1 = importer._get_node_id('i=1001')
self.assertEqual(res1, 'i=1001')
res2 = importer._get_node_id('ns=1;i=1001')
self.assertEqual(res2, 'ns=1;i=1001')
importer.namespaces = {1: [3, 'http://someuri.com']}
res3 = importer._get_node_id('ns=1;i=1001')
self.assertEqual(res3, 'ns=3;i=1001')
importer.namespaces = {1: [3, 'http://someuri.com']}
res4 = importer._get_node_id('ns=2;i=1001')
self.assertEqual(res4, 'ns=2;i=1001')
def test_xmlparser_sort_nodes_by_parentid(self):
NodeMock = namedtuple('NodeMock', 'nodeid parent')
server = None
unordered_nodes = [
NodeMock('ns=1;i=1001', None),
NodeMock('ns=1;i=1002', 'ns=1;i=1003'),
NodeMock('ns=1;i=1003', 'ns=1;i=1001'),
NodeMock('ns=1;i=1004', 'ns=1;i=1002')
]
ordered_nodes = [
unordered_nodes[0],
unordered_nodes[2],
unordered_nodes[1],
unordered_nodes[3],
]
namespaces = {'1': (1, 'http://someuri.com')}
importer = XmlImporter(server)
importer.namespaces = namespaces
res = importer._sort_nodes_by_parentid(unordered_nodes)
self.assertEqual(res, ordered_nodes)
class TestMaskEnum(unittest.TestCase):
class MyEnum(_MaskEnum):
......
......@@ -10,6 +10,10 @@ def func(parent, value, string):
class XmlTests(object):
srv = None
opc = None # just to remove pylint warnings
assertEqual = dir
def test_xml_import(self):
self.srv.import_xml("tests/custom_nodes.xml")
o = self.opc.get_objects_node()
......@@ -58,12 +62,10 @@ class XmlTests(object):
self.assertEqual(r3.NodeId.NamespaceIndex, ns)
def test_xml_method(self):
o = self.opc.nodes.objects.add_object(2, "xmlexportobj")
self.opc.register_namespace("tititi")
self.opc.register_namespace("whatthefuck")
o = self.opc.nodes.objects.add_object(2, "xmlexportmethod")
m = o.add_method(2, "callme", func, [ua.VariantType.Double, ua.VariantType.String], [ua.VariantType.Float])
v = o.add_variable(3, "myxmlvar", 6.78, ua.VariantType.Float)
a = o.add_variable(3, "myxmlvar", [6, 1], ua.VariantType.UInt16)
a2 = o.add_variable(3, "myxmlvar", [[]], ua.VariantType.ByteString)
print(a.get_value_rank)
# set an arg dimension to a list to test list export
inputs = m.get_child("InputArguments")
val = inputs.get_value()
......@@ -78,7 +80,6 @@ class XmlTests(object):
self.opc.export_xml(nodes, "export.xml")
self.opc.delete_nodes(nodes)
self.opc.import_xml("export.xml")
# now see if our nodes are here
......@@ -87,19 +88,38 @@ class XmlTests(object):
self.assertEqual(val[0].ArrayDimensions, [2, 2])
self.assertEqual(val[0].Description.Text, desc)
def test_xml_vars(self):
self.opc.register_namespace("tititi")
self.opc.register_namespace("whatthefuck")
o = self.opc.nodes.objects.add_object(2, "xmlexportobj")
v = o.add_variable(3, "myxmlvar", 6.78, ua.VariantType.Float)
a = o.add_variable(3, "myxmlvar-array", [6, 1], ua.VariantType.UInt16)
a2 = o.add_variable(3, "myxmlvar-2dim", [[1, 2], [3,4]], ua.VariantType.UInt32)
a3 = o.add_variable(3, "myxmlvar-2dim", [[]], ua.VariantType.ByteString)
nodes = [o, v, a, a2, a3]
self.opc.export_xml(nodes, "export-vars.xml")
self.opc.delete_nodes(nodes)
self.opc.import_xml("export-vars.xml")
self.assertEqual(v.get_value(), 6.78)
self.assertEqual(v.get_data_type(), ua.NodeId(ua.ObjectIds.Float))
self.assertEqual(a.get_value(), [6, 1])
self.assertEqual(a.get_data_type(), ua.NodeId(ua.ObjectIds.UInt16))
self.assertIn(a.get_value_rank(), (0, 1))
self.assertEqual(a.get_value(), [6, 1])
self.assertEqual(a2.get_value(), [[]])
self.assertEqual(a2.get_data_type(), ua.NodeId(ua.ObjectIds.ByteString))
self.assertEqual(a2.get_value(), [[1, 2],[3, 4]])
self.assertEqual(a2.get_data_type(), ua.NodeId(ua.ObjectIds.UInt32))
self.assertIn(a2.get_value_rank(), (0, 2))
self.assertEqual(a2.get_attribute(ua.AttributeIds.ArrayDimensions).Value.Value, [1, 0])
self.assertEqual(a2.get_attribute(ua.AttributeIds.ArrayDimensions).Value.Value, [2, 2])
#self.assertEqual(a3.get_value(), [[]]) # would require special code ...
self.assertEqual(a3.get_data_type(), ua.NodeId(ua.ObjectIds.ByteString))
self.assertIn(a3.get_value_rank(), (0, 2))
self.assertEqual(a3.get_attribute(ua.AttributeIds.ArrayDimensions).Value.Value, [1, 0])
def test_export_import_ext_obj(self):
def test_xml_ext_obj(self):
arg = ua.Argument()
arg.DataType = ua.NodeId(ua.ObjectIds.Float)
arg.Description = ua.LocalizedText(b"This is a nice description")
......@@ -123,37 +143,57 @@ class XmlTests(object):
def test_xml_ns(self):
ns_array = self.opc.get_namespace_array()
if len(ns_array) <= 2:
if len(ns_array) < 3:
self.opc.register_namespace("dummy_ns")
print("ARRAY", self.opc.get_namespace_array())
ref_ns = self.opc.register_namespace("ref_namespace")
new_ns = self.opc.register_namespace("my_new_namespace")
bname_ns = self.opc.register_namespace("bname_namespace")
o = self.opc.nodes.objects.add_object(0, "xmlns0")
o2 = self.opc.nodes.objects.add_object(2, "xmlns2")
o20 = self.opc.nodes.objects.add_object(20, "xmlns20")
o50 = self.opc.nodes.objects.add_object(50, "xmlns20")
o200 = self.opc.nodes.objects.add_object(200, "xmlns200")
onew = self.opc.nodes.objects.add_object(new_ns, "xmlns_new")
vnew = onew.add_variable(new_ns, "xmlns_new_var", 9.99)
o_no_export = self.opc.nodes.objects.add_object(ref_ns, "xmlns_parent")
v_no_parent = o_no_export.add_variable(new_ns, "xmlns_new_var_no_parent", 9.99)
o_bname = onew.add_object("ns={};i=4000".format(new_ns), "{}:BNAME".format(bname_ns))
nodes = [o, o2, o20, o200, onew, vnew]
nodes = [o, o50, o200, onew, vnew, v_no_parent, o_bname]
print("CREATED", nodes, o_no_export)
self.opc.export_xml(nodes, "export-ns.xml")
# delete node and change index og new_ns before re-importing
self.opc.delete_nodes(nodes)
ns_node = self.opc.get_node(ua.NodeId(ua.ObjectIds.Server_NamespaceArray))
nss = ns_node.get_value()
nss.remove("my_new_namespace")
#nss.remove("ref_namespace")
nss.remove("bname_namespace")
ns_node.set_value(nss)
new_ns = self.opc.register_namespace("my_new_namespace_offsett")
new_ns = self.opc.register_namespace("my_new_namespace")
self.opc.import_xml("export-ns.xml")
new_nodes = self.opc.import_xml("export-ns.xml")
print("NEW NODES", new_nodes)
for i in nodes[:-2]:
for i in [o, o50, o200]:
print(i)
i.get_browse_name()
with self.assertRaises(uaerrors.BadNodeIdUnknown):
onew.get_browse_name()
onew.nodeid.NamespaceIndex += 1
# since my_new_namesspace2 is referenced byt a node it should have been reimported
nss = self.opc.get_namespace_array()
self.assertIn("bname_namespace", nss)
# get index of namespaces after import
new_ns = self.opc.register_namespace("my_new_namespace")
bname_ns = self.opc.register_namespace("bname_namespace")
print("ARRAY 2", self.opc.get_namespace_array())
print("NEW NS", new_ns, onew)
onew.nodeid.NamespaceIndex = new_ns
print("OENE", onew)
onew.get_browse_name()
vnew2 = onew.get_children()[0]
self.assertEqual(vnew.nodeid.NamespaceIndex + 1, vnew2.nodeid.NamespaceIndex)
vnew.nodeid.NamespaceIndex += 1
self.assertEqual(new_ns, vnew2.nodeid.NamespaceIndex)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment