Completed
Pull Request — master (#285)
by Olivier
04:17
created

XmlExporter._add_namespace_uri_els()   A

Complexity

Conditions 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 8
rs 9.4285
1
"""
2
from a list of nodes in the address space, build an XML file
3
format is the one from opc-ua specification
4
"""
5
import logging
6
from collections import OrderedDict
7
import xml.etree.ElementTree as Et
8
from xml.dom import minidom
9
10
from opcua import ua
11
from opcua.ua import object_ids as o_ids  # FIXME needed because the reverse look up isn't part of ObjectIds Class
12
13
14
class XmlExporter(object):
15
16
    def __init__(self, server):
17
        self.logger = logging.getLogger(__name__)
18
        self.server = server
19
        self.aliases = {}
20
21
        node_set_attributes = OrderedDict()
22
        node_set_attributes['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance'
23
        node_set_attributes['xmlns:uax'] = 'http://opcfoundation.org/UA/2008/02/Types.xsd'
24
        node_set_attributes['xmlns:xsd'] = 'http://www.w3.org/2001/XMLSchema'
25
        node_set_attributes['xmlns'] = 'http://opcfoundation.org/UA/2011/03/UANodeSet.xsd'
26
27
        self.etree = Et.ElementTree(Et.Element('UANodeSet', node_set_attributes))
28
29
    def build_etree(self, node_list, uris=None):
30
        """
31
        Create an XML etree object from a list of nodes; custom namespace uris are optional
32
        Args:
33
            node_list: list of Node objects for export
34
            uris: list of namespace uri strings
35
36
        Returns:
37
        """
38
        self.logger.info('Building XML etree')
39
40
        # add all nodes in the list to the XML etree
41
        for node in node_list:
42
            self.node_to_etree(node)
43
44
        # add all required aliases to the XML etree; must be done after nodes are added
45
        self._add_alias_els()
46
47
        if uris:
48
            # add all namespace uris to the XML etree; must be done after aliases are added
49
            self._add_namespace_uri_els(uris)
50
51
    def write_xml(self, xmlpath, pretty=True):
52
        """
53
        Write the XML etree in the exporter object to a file
54
        Args:
55
            xmlpath: string representing the path/file name
56
57
        Returns:
58
        """
59
        # try to write the XML etree to a file
60
        self.logger.info('Exporting XML file to %s', xmlpath)
61
        #from IPython import embed
62
        #embed()
63
        if pretty:
64
            rough_string = Et.tostring(self.etree.getroot(), 'utf-8')
65
            reparsed = minidom.parseString(rough_string)
66
            pretty_string = reparsed.toprettyxml(indent="    ")
67
            with open(xmlpath, "wt") as f:
68
                f.write(pretty_string)
69
        else:
70
            self.etree.write(xmlpath, short_empty_elements=False)
71
72
    def dump_etree(self):
73
        """
74
        Dump etree to console for debugging
75
        Returns:
76
        """
77
        self.logger.info('Dumping XML etree to console')
78
        Et.dump(self.etree)
79
80
    def node_to_etree(self, node):
81
        """
82
        Add the necessary XML sub elements to the etree for exporting the node
83
        Args:
84
            node: Node object which will be added to XML etree
85
86
        Returns:
87
        """
88
        node_class = node.get_node_class()
89
90
        if node_class is ua.NodeClass.Object:
91
            self.add_etree_object(node)
92
        elif node_class is ua.NodeClass.ObjectType:
93
            self.add_etree_object_type(node)
94
        elif node_class is ua.NodeClass.Variable:
95
            self.add_etree_variable(node)
96
        elif node_class is ua.NodeClass.VariableType:
97
            self.add_etree_variable_type(node)
98
        elif node_class is ua.NodeClass.ReferenceType:
99
            self.add_etree_reference(node)
100
        elif node_class is ua.NodeClass.DataType:
101
            self.add_etree_datatype(node)
102
        elif node_class is ua.NodeClass.Method:
103
            self.add_etree_method(node)
104
        else:
105
            self.logger.info("Exporting node class not implemented: %s ", node_class)
106
107
    def _add_node_common(self, nodetype, obj):
108
        browsename = obj.get_browse_name().to_string()
109
        nodeid = obj.nodeid.to_string()
110
        parent = obj.get_parent().nodeid.to_string()
111
        displayname = obj.get_display_name().Text.decode(encoding='UTF8')
112
113
        obj_el = Et.SubElement(self.etree.getroot(),
114
                               nodetype,
115
                               BrowseName=browsename,
116
                               NodeId=nodeid,
117
                               ParentNodeId=parent)
118
        disp_el = Et.SubElement(obj_el, 'DisplayName', )
119
        disp_el.text = displayname
120
        return obj_el
121
122
    def add_etree_object(self, obj):
123
        """
124
        Add a UA object element to the XML etree
125
        """
126
        obj_el = self._add_node_common("UAObject", obj)
127
        self._add_ref_els(obj_el, obj)
128
129
    def add_etree_object_type(self, obj):
130
        """
131
        Add a UA object type element to the XML etree
132
        """
133
        obj_el = self._add_node_common("UAObjectType", obj)
134
        self._add_ref_els(obj_el, obj)
135
136 View Code Duplication
    def add_etree_variable(self, obj):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
137
        """
138
        Add a UA variable element to the XML etree
139
        """
140
        var_el = self._add_node_common("UAVariable", obj)
141
142
        datatype = o_ids.ObjectIdNames[obj.get_data_type().Identifier]
143
        datatype_nodeid = obj.get_data_type().to_string()
144
        accesslevel = str(obj.get_attribute(ua.AttributeIds.AccessLevel).Value.Value)
145
        useraccesslevel = str(obj.get_attribute(ua.AttributeIds.UserAccessLevel).Value.Value)
146
        symbolicname = None  # TODO when to export this?
147
        value = str(obj.get_value())
148
149
        var_el.attrib["DataType"] = datatype
150
151
        defaults = ua.VariableAttributes()
152
        if accesslevel != defaults.AccessLevel:
153
            print("ACCESS", accesslevel, defaults.AccessLevel)
154
            var_el.attrib["AccessLevel"] = accesslevel
155
        if useraccesslevel != defaults.UserAccessLevel:
156
            var_el.attrib["UserAccessLevel"] = useraccesslevel
157
158
        val_el = Et.SubElement(var_el, 'Value')
159
        valx_el = Et.SubElement(val_el, 'uax:' + datatype)
160
        valx_el.text = value
161
162
        self._add_ref_els(var_el, obj)
163
        # add any references that get used to aliases dict; this gets handled later
164
        self.aliases[datatype] = datatype_nodeid
165
166 View Code Duplication
    def add_etree_variable_type(self, obj):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
167
        """
168
        Add a UA variable type element to the XML etree
169
        """
170
171
        var_el = self._add_node_common("UAVariableType", obj)
172
173
        datatype = o_ids.ObjectIdNames[obj.get_data_type().Identifier]
174
        datatype_nodeid = obj.get_data_type().to_string()
175
        accesslevel = str(obj.get_attribute(ua.AttributeIds.AccessLevel).Value.Value)
176
        useraccesslevel = str(obj.get_attribute(ua.AttributeIds.UserAccessLevel).Value.Value)
177
        symbolicname = None  # TODO when to export this?
178
        value = str(obj.get_value())
179
180
        var_el.attrib["DataType"] = datatype
181
182
        defaults = ua.VariableAttributes()
183
        if accesslevel != defaults.AccessLevel:
184
            print("ACCESS", accesslevel, defaults.AccessLevel)
185
            var_el.attrib["AccessLevel"] = accesslevel
186
        if useraccesslevel != defaults.UserAccessLevel:
187
            var_el.attrib["UserAccessLevel"] = useraccesslevel
188
189
        val_el = Et.SubElement(var_el, 'Value')
190
        valx_el = Et.SubElement(val_el, 'uax:' + datatype)
191
        valx_el.text = value
192
193
        self._add_ref_els(var_el, obj)
194
        # add any references that get used to aliases dict; this gets handled later
195
        self.aliases[datatype] = datatype_nodeid
196
197
    def add_etree_method(self, obj):
198
        obj_el = self._add_node_common("UAMethod", obj)
199
        self._add_ref_els(obj_el, obj)
200
        raise NotImplementedError
201
202
    def add_etree_reference(self, obj):
203
        obj_el = self._add_node_common("UAReference", obj)
204
        self._add_ref_els(obj_el, obj)
205
        raise NotImplementedError
206
207
    def add_etree_datatype(self, obj):
208
        """
209
        Add a UA data type element to the XML etree
210
        """
211
        obj_el = self._add_node_common("UADataType", obj)
212
        self._add_ref_els(obj_el, obj)
213
214
    def _add_namespace_uri_els(self, uris):
215
        nuris_el = Et.Element('NamespaceUris')
216
217
        for uri in uris:
218
            uri_el = Et.SubElement(nuris_el, 'Uri')
219
            uri_el.text = uri
220
221
        self.etree.getroot().insert(0, nuris_el)
222
223
    def _add_alias_els(self):
224
        aliases_el = Et.Element('Aliases')
225
226
        for k, v in self.aliases.items():
227
            ref_el = Et.SubElement(aliases_el, 'Alias', Alias=k)
228
            ref_el.text = v
229
230
        self.etree.getroot().insert(0, aliases_el)
231
232
    def _add_ref_els(self, parent_el, obj):
233
        refs = obj.get_references()
234
        refs_el = Et.SubElement(parent_el, 'References')
235
236
        for ref in refs:
237
            ref_name = o_ids.ObjectIdNames[ref.ReferenceTypeId.Identifier]
238
            ref_forward = str(ref.IsForward).lower()
239
            ref_nodeid = ref.NodeId.to_string()
240
            ref_el = Et.SubElement(refs_el, 'Reference', IsForward=ref_forward, ReferenceType=ref_name)
241
            ref_el.text = ref_nodeid
242
243
            # add any references that gets used to aliases dict; this gets handled later
244
            self.aliases[ref_name] = ref_nodeid
245