• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Wirecloud / wirecloud / 11129860147

01 Oct 2024 04:50PM UTC coverage: 88.219% (+0.009%) from 88.21%
11129860147

Pull #545

github

web-flow
Merge db6bb8c37 into 2a2576628
Pull Request #545: Add ability to define different widget layouts for different screen sizes

8211 of 9838 branches covered (83.46%)

Branch coverage included in aggregate %.

477 of 531 new or added lines in 19 files covered. (89.83%)

31 existing lines in 8 files now uncovered.

21705 of 24073 relevant lines covered (90.16%)

31.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.5
src/wirecloud/commons/utils/template/parsers/xml.py
1
# -*- coding: utf-8 -*-
2

3
# Copyright (c) 2012-2017 CoNWeT Lab., Universidad Politécnica de Madrid
4
# Copyright (c) 2019-2020 Future Internet Consulting and Development Solutions S.L.
5

6
# This file is part of Wirecloud.
7

8
# Wirecloud is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Affero General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
12

13
# Wirecloud is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU Affero General Public License for more details.
17

18
# You should have received a copy of the GNU Affero General Public License
19
# along with Wirecloud.  If not, see <http://www.gnu.org/licenses/>.
20

21
import codecs
12✔
22
from lxml import etree
12✔
23
import os
12✔
24

25
from django.utils.translation import ugettext as _
12✔
26

27
from wirecloud.commons.utils.template.base import ObsoleteFormatError, parse_contacts_info, TemplateParseException
12✔
28
from wirecloud.commons.utils.translation import get_trans_index
12✔
29
from wirecloud.platform.wiring.utils import get_behaviour_skeleton, get_wiring_skeleton, parse_wiring_old_version
12✔
30

31

32
XMLSCHEMA_FILE = codecs.open(os.path.join(os.path.dirname(__file__), '../schemas/xml_schema.xsd'), 'rb')
12✔
33
XMLSCHEMA_DOC = etree.parse(XMLSCHEMA_FILE)
12✔
34
XMLSCHEMA_FILE.close()
12✔
35
XMLSCHEMA = etree.XMLSchema(XMLSCHEMA_DOC)
12✔
36

37
WIRECLOUD_TEMPLATE_NS = 'http://wirecloud.conwet.fi.upm.es/ns/macdescription/1'
12✔
38
OLD_TEMPLATE_NAMESPACES = ('http://wirecloud.conwet.fi.upm.es/ns/template#', 'http://morfeo-project.org/2007/Template')
12✔
39

40
MAC_VERSION_XPATH = 't:macversion'
12✔
41
RESOURCE_DESCRIPTION_XPATH = 't:details'
12✔
42
DISPLAY_NAME_XPATH = 't:title'
12✔
43
DESCRIPTION_XPATH = 't:description'
12✔
44
LONG_DESCRIPTION_XPATH = 't:longdescription'
12✔
45
AUTHORS_XPATH = 't:authors'
12✔
46
CONTRIBUTORS_XPATH = 't:contributors'
12✔
47
IMAGE_URI_XPATH = 't:image'
12✔
48
IPHONE_IMAGE_URI_XPATH = 't:smartphoneimage'
12✔
49
MAIL_XPATH = 't:email'
12✔
50
HOMEPAGE_XPATH = 't:homepage'
12✔
51
DOC_URI_XPATH = 't:doc'
12✔
52
LICENCE_XPATH = 't:license'
12✔
53
LICENCE_URL_XPATH = 't:licenseurl'
12✔
54
CHANGELOG_XPATH = 't:changelog'
12✔
55
REQUIREMENTS_XPATH = 't:requirements'
12✔
56
ISSUETRACKER_XPATH = 't:issuetracker'
12✔
57

58
FEATURE_XPATH = 't:feature'
12✔
59
CODE_XPATH = 't:contents'
12✔
60
ALTCONTENT_XPATH = 't:altcontents'
12✔
61
PREFERENCE_XPATH = 't:preference'
12✔
62
PREFERENCE_VALUE_XPATH = 't:preferencevalue'
12✔
63
PREFERENCES_XPATH = 't:preferences/t:preference'
12✔
64
OPTION_XPATH = 't:option'
12✔
65
PROPERTY_XPATH = 't:persistentvariables/t:variable'
12✔
66
WIRING_XPATH = 't:wiring'
12✔
67
MASHUP_WIRING_XPATH = 't:structure/t:wiring'
12✔
68
INPUT_ENDPOINT_XPATH = 't:inputendpoint'
12✔
69
OUTPUT_ENDPOINT_XPATH = 't:outputendpoint'
12✔
70
SCRIPT_XPATH = 't:scripts/t:script'
12✔
71
PLATFORM_RENDERING_XPATH = 't:rendering'
12✔
72
ENTRYPOINT_XPATH = 't:entrypoint'
12✔
73

74
INCLUDED_RESOURCES_XPATH = 't:structure'
12✔
75
TAB_XPATH = 't:tab'
12✔
76
RESOURCE_XPATH = 't:resource'
12✔
77
POSITION_XPATH = 't:position'
12✔
78
SCREEN_SIZES_XPATH = 't:screensizes'
12✔
79
RENDERING_XPATH = 't:rendering'
12✔
80
PARAM_XPATH = 't:preferences/t:preference'
12✔
81
EMBEDDEDRESOURCE_XPATH = 't:embedded/t:resource'
12✔
82
PROPERTIES_XPATH = 't:variablevalue'
12✔
83
CONNECTION_XPATH = 't:connection'
12✔
84
IOPERATOR_XPATH = 't:operator'
12✔
85
SOURCE_XPATH = 't:source'
12✔
86
TARGET_XPATH = 't:target'
12✔
87

88
VISUALDESCRIPTION_XPATH = 't:visualdescription'
12✔
89
BEHAVIOUR_XPATH = 't:behaviour'
12✔
90

91
COMPONENT_XPATH = 't:component'
12✔
92
COMPONENTSOURCES_XPATH = 't:sources/t:endpoint'
12✔
93
COMPONENTTARGETS_XPATH = 't:targets/t:endpoint'
12✔
94
SOURCEHANDLE_XPATH = 't:sourcehandle'
12✔
95
TARGETHANDLE_XPATH = 't:targethandle'
12✔
96

97
TRANSLATIONS_XPATH = 't:translations'
12✔
98
TRANSLATION_XPATH = 't:translation'
12✔
99
MSG_XPATH = 't:msg'
12✔
100

101

102
class ApplicationMashupTemplateParser(object):
12✔
103

104
    _doc = None
12✔
105
    _component_description = None
12✔
106
    _parsed = False
12✔
107

108
    def __init__(self, template):
12✔
109
        self._info = {}
12✔
110
        self._translation_indexes = {}
12✔
111

112
        if isinstance(template, bytes):
12✔
113
            self._doc = etree.fromstring(template)
12✔
114
        elif isinstance(template, str):
12✔
115
            # Work around: ValueError: Unicode strings with encoding
116
            # declaration are not supported.
117
            self._doc = etree.fromstring(template.encode('utf-8'))
12✔
118
        else:
119
            self._doc = template
12✔
120

121
        root_element_qname = etree.QName(self._doc)
12✔
122
        xmlns = root_element_qname.namespace
12✔
123

124
        if xmlns is None:
12✔
125
            raise ValueError("Missing document namespace")
12✔
126
        elif xmlns in OLD_TEMPLATE_NAMESPACES:
12✔
127
            raise ObsoleteFormatError()
12✔
128
        elif xmlns != WIRECLOUD_TEMPLATE_NS:
12✔
129
            raise ValueError("Invalid namespace: " + xmlns)
12✔
130

131
        if root_element_qname.localname not in ('widget', 'operator', 'mashup'):
12✔
132
            raise TemplateParseException("Invalid root element (%s)" % root_element_qname.localname)
12✔
133

134
        self._info['type'] = root_element_qname.localname
12✔
135

136
    def _init(self):
12✔
137

138
        try:
12✔
139
            XMLSCHEMA.assertValid(self._doc)
12✔
140
        except Exception as e:
12✔
141
            raise TemplateParseException('%s' % e)
12✔
142

143
        self._component_description = self._xpath(RESOURCE_DESCRIPTION_XPATH, self._doc)[0]
12✔
144
        self._parse_basic_info()
12✔
145

146
    def _xpath(self, query, element):
12✔
147
        return element.xpath(query, namespaces={'t': WIRECLOUD_TEMPLATE_NS})
12✔
148

149
    def get_xpath(self, query, element, required=True):
12✔
150
        elements = self._xpath(query, element)
12✔
151

152
        if len(elements) == 0 and required:
12!
UNCOV
153
            raise TemplateParseException('Missing %s element' % query.replace('t:', ''))
×
154
        elif len(elements) > 0:
12✔
155
            return elements[0]
12✔
156
        else:
157
            return None
12✔
158

159
    def _add_translation_index(self, value, **kwargs):
12✔
160
        index = get_trans_index(str(value))
12✔
161
        if not index:
12✔
162
            return
12✔
163

164
        if index not in self._translation_indexes:
12✔
165
            self._translation_indexes[index] = []
12✔
166

167
        self._translation_indexes[index].append(kwargs)
12✔
168

169
    def _parse_extra_info(self):
12✔
170
        if self._info['type'] == 'widget':
12✔
171
            self._parse_widget_info()
12✔
172
        elif self._info['type'] == 'operator':
12✔
173
            self._parse_operator_info()
12✔
174
        elif self._info['type'] == 'mashup':
12!
175
            self._parse_workspace_info()
12✔
176

177
        self._parse_translation_catalogue()
12✔
178
        self._parsed = True
12✔
179
        self._doc = None
12✔
180
        self._component_description = None
12✔
181

182
    def _get_field(self, xpath, element, required=True):
12✔
183

184
        elements = self._xpath(xpath, element)
12✔
185
        if len(elements) == 1 and elements[0].text and len(elements[0].text.strip()) > 0:
12✔
186
            return str(elements[0].text)
12✔
187
        elif not required:
12!
188
            return ''
12✔
189
        else:
190
            msg = _('missing required field: %(field)s')
×
UNCOV
191
            raise TemplateParseException(msg % {'field': xpath})
×
192

193
    def _parse_basic_info(self):
12✔
194
        self._info['vendor'] = str(self._doc.get('vendor', '').strip())
12✔
195
        self._info['name'] = str(self._doc.get('name', '').strip())
12✔
196
        self._info['version'] = str(self._doc.get('version', '').strip())
12✔
197

198
        self._info['macversion'] = self._get_field(MAC_VERSION_XPATH, self._doc, required=False)
12✔
199
        if len(self._info['macversion']) == 0:
12✔
200
            self._info['macversion'] = 1
12✔
201
        else:
202
            self._info['macversion'] = int(self._info['macversion'])
12✔
203

204
        self._info['title'] = self._get_field(DISPLAY_NAME_XPATH, self._component_description, required=False)
12✔
205
        self._add_translation_index(self._info['title'], type='resource', field='title')
12✔
206

207
        self._info['description'] = self._get_field(DESCRIPTION_XPATH, self._component_description, required=False)
12✔
208
        self._add_translation_index(self._info['description'], type='resource', field='description')
12✔
209
        self._info['longdescription'] = self._get_field(LONG_DESCRIPTION_XPATH, self._component_description, required=False)
12✔
210

211
        self._info['authors'] = parse_contacts_info(self._get_field(AUTHORS_XPATH, self._component_description, required=False))
12✔
212
        self._info['contributors'] = parse_contacts_info(self._get_field(CONTRIBUTORS_XPATH, self._component_description, required=False))
12✔
213
        self._info['email'] = self._get_field(MAIL_XPATH, self._component_description, required=False)
12✔
214
        self._info['image'] = self._get_field(IMAGE_URI_XPATH, self._component_description, required=False)
12✔
215
        self._info['smartphoneimage'] = self._get_field(IPHONE_IMAGE_URI_XPATH, self._component_description, required=False)
12✔
216
        self._info['homepage'] = self._get_field(HOMEPAGE_XPATH, self._component_description, required=False)
12✔
217
        self._info['doc'] = self._get_field(DOC_URI_XPATH, self._component_description, required=False)
12✔
218
        self._info['license'] = self._get_field(LICENCE_XPATH, self._component_description, required=False)
12✔
219
        self._info['licenseurl'] = self._get_field(LICENCE_URL_XPATH, self._component_description, required=False)
12✔
220
        self._info['issuetracker'] = self._get_field(ISSUETRACKER_XPATH, self._component_description, required=False)
12✔
221
        self._info['changelog'] = self._get_field(CHANGELOG_XPATH, self._component_description, required=False)
12✔
222
        self._parse_requirements()
12✔
223

224
    def _parse_requirements(self):
12✔
225

226
        self._info['requirements'] = []
12✔
227
        requirements_elements = self._xpath(REQUIREMENTS_XPATH, self._doc)
12✔
228
        if len(requirements_elements) < 1:
12✔
229
            return
12✔
230

231
        for requirement in self._xpath(FEATURE_XPATH, requirements_elements[0]):
12✔
232
            self._info['requirements'].append({
12✔
233
                'type': u'feature',
234
                'name': str(requirement.get('name').strip())
235
            })
236

237
    def _parse_visualdescription_info(self, visualdescription_element):
12✔
238

239
        self._parse_wiring_component_view_info(self._info['wiring']['visualdescription'], visualdescription_element)
12✔
240
        self._parse_wiring_connection_view_info(self._info['wiring']['visualdescription'], visualdescription_element)
12✔
241
        self._parse_wiring_behaviour_view_info(self._info['wiring']['visualdescription'], visualdescription_element)
12✔
242

243
    def _parse_wiring_behaviour_view_info(self, target, behaviours_element):
12✔
244

245
        for behaviour in self._xpath(BEHAVIOUR_XPATH, behaviours_element):
12✔
246

247
            behaviour_info = get_behaviour_skeleton()
12✔
248
            behaviour_info['title'] = str(behaviour.get('title'))
12✔
249
            behaviour_info['description'] = str(behaviour.get('description'))
12✔
250

251
            self._parse_wiring_component_view_info(behaviour_info, behaviour)
12✔
252
            self._parse_wiring_connection_view_info(behaviour_info, behaviour)
12✔
253

254
            target['behaviours'].append(behaviour_info)
12✔
255

256
    def _parse_wiring_component_view_info(self, target, components_element):
12✔
257

258
        for component in self._xpath(COMPONENT_XPATH, components_element):
12✔
259
            component_info = {
12✔
260
                'collapsed': component.get('collapsed', 'false').strip().lower() == 'true',
261
                'endpoints': {
262
                    'source': [endpoint.text for endpoint in self._xpath(COMPONENTSOURCES_XPATH, component)],
263
                    'target': [endpoint.text for endpoint in self._xpath(COMPONENTTARGETS_XPATH, component)]
264
                }
265
            }
266

267
            position = self.get_xpath(POSITION_XPATH, component, required=False)
12✔
268
            if position is not None:
12✔
269
                component_info['position'] = {
12✔
270
                    'x': int(position.get('x')),
271
                    'y': int(position.get('y'))
272
                }
273

274
            target['components'][component.get('type')][component.get('id')] = component_info
12✔
275

276
    def _parse_wiring_connection_view_info(self, target, connections_element):
12✔
277

278
        for connection in self._xpath(CONNECTION_XPATH, connections_element):
12✔
279

280
            connection_info = {
12✔
281
                'sourcename': str(connection.get('sourcename')),
282
                'targetname': str(connection.get('targetname')),
283
            }
284

285
            sourcehandle_element = self.get_xpath(SOURCEHANDLE_XPATH, connection, required=False)
12✔
286
            targethandle_element = self.get_xpath(TARGETHANDLE_XPATH, connection, required=False)
12✔
287

288
            if sourcehandle_element is not None:
12✔
289
                connection_info['sourcehandle'] = {
12✔
290
                    'x': int(sourcehandle_element.get('x')),
291
                    'y': int(sourcehandle_element.get('y'))
292
                }
293
            else:
294
                connection_info['sourcehandle'] = u'auto'
12✔
295

296
            if targethandle_element is not None:
12✔
297
                connection_info['targethandle'] = {
12✔
298
                    'x': int(targethandle_element.get('x')),
299
                    'y': int(targethandle_element.get('y'))
300
                }
301
            else:
302
                connection_info['targethandle'] = u'auto'
12✔
303

304
            target['connections'].append(connection_info)
12✔
305

306
    def _parse_wiring_info(self):
12✔
307

308
        if self._info['type'] == 'mashup':
12✔
309
            self._info['wiring'] = get_wiring_skeleton()
12✔
310
        else:
311
            self._info['wiring'] = {}
12✔
312

313
        self._info['wiring']['inputs'] = []
12✔
314
        self._info['wiring']['outputs'] = []
12✔
315

316
        wiring_elements = self._xpath(WIRING_XPATH, self._doc)
12✔
317
        if len(wiring_elements) != 0:
12✔
318
            wiring_element = wiring_elements[0]
12✔
319

320
            for slot in self._xpath(INPUT_ENDPOINT_XPATH, wiring_element):
12✔
321
                self._add_translation_index(str(slot.get('label')), type='inputendpoint', variable=slot.get('name'))
12✔
322
                self._add_translation_index(str(slot.get('actionlabel', '')), type='inputendpoint', variable=slot.get('name'))
12✔
323
                self._add_translation_index(str(slot.get('description', '')), type='inputendpoint', variable=slot.get('name'))
12✔
324
                self._info['wiring']['inputs'].append({
12✔
325
                    'name': str(slot.get('name')),
326
                    'type': str(slot.get('type')),
327
                    'label': str(slot.get('label', '')),
328
                    'description': str(slot.get('description', '')),
329
                    'actionlabel': str(slot.get('actionlabel', '')),
330
                    'friendcode': str(slot.get('friendcode', '')),
331
                })
332

333
            for event in self._xpath(OUTPUT_ENDPOINT_XPATH, wiring_element):
12✔
334
                self._add_translation_index(str(event.get('label')), type='outputendpoint', variable=event.get('name'))
12✔
335
                self._add_translation_index(str(event.get('description', '')), type='outputendpoint', variable=event.get('name'))
12✔
336
                self._info['wiring']['outputs'].append({
12✔
337
                    'name': str(event.get('name')),
338
                    'type': str(event.get('type')),
339
                    'label': str(event.get('label', '')),
340
                    'description': str(event.get('description', '')),
341
                    'friendcode': str(event.get('friendcode', '')),
342
                })
343

344
        if self._info['type'] == "mashup":
12✔
345

346
            mashup_wiring_element = self.get_xpath(MASHUP_WIRING_XPATH, self._doc, required=False)
12✔
347
            if mashup_wiring_element is None:
12!
UNCOV
348
                return
×
349

350
            self._info['wiring']['version'] = str(mashup_wiring_element.get('version', "1.0"))
12✔
351

352
            self._parse_wiring_connection_info(mashup_wiring_element)
12✔
353
            self._parse_wiring_operator_info(mashup_wiring_element)
12✔
354

355
            if self._info['wiring']['version'] == '1.0':
12✔
356
                # TODO: update to the new wiring format
357
                inputs = self._info['wiring']['inputs']
12✔
358
                outputs = self._info['wiring']['outputs']
12✔
359
                self._info['wiring'] = parse_wiring_old_version(self._info['wiring'])
12✔
360
                self._info['wiring']['inputs'] = inputs
12✔
361
                self._info['wiring']['outputs'] = outputs
12✔
362
                # END TODO
363
            elif self._info['wiring']['version'] == '2.0':
12!
364
                visualdescription_element = self.get_xpath(VISUALDESCRIPTION_XPATH, mashup_wiring_element, required=False)
12✔
365
                if visualdescription_element is not None:
12✔
366
                    self._parse_visualdescription_info(visualdescription_element)
12✔
367
            else:
368
                # TODO raise unsupported version exception
369
                pass
9✔
370

371
    def _parse_wiring_connection_info(self, wiring_element):
12✔
372

373
        connections = []
12✔
374

375
        for connection in self._xpath(CONNECTION_XPATH, wiring_element):
12✔
376

377
            source_element = self._xpath(SOURCE_XPATH, connection)[0]
12✔
378
            target_element = self._xpath(TARGET_XPATH, connection)[0]
12✔
379

380
            connection_info = {
12✔
381
                'readonly': connection.get('readonly', 'false').lower() == 'true',
382
                'source': {
383
                    'type': str(source_element.get('type')),
384
                    'endpoint': str(source_element.get('endpoint')),
385
                    'id': str(source_element.get('id')),
386
                },
387
                'target': {
388
                    'type': str(target_element.get('type')),
389
                    'endpoint': str(target_element.get('endpoint')),
390
                    'id': str(target_element.get('id')),
391
                }
392
            }
393

394
            connections.append(connection_info)
12✔
395

396
        self._info['wiring']['connections'] = connections
12✔
397

398
    def _parse_wiring_operator_info(self, wiring_element):
12✔
399

400
        self._info['wiring']['operators'] = {}
12✔
401

402
        for operator in self._xpath(IOPERATOR_XPATH, wiring_element):
12✔
403
            operator_info = {
12✔
404
                'id': str(operator.get('id')),
405
                'name': str('/'.join((operator.get('vendor'), operator.get('name'), operator.get('version')))),
406
                'preferences': {},
407
            }
408

409
            for pref in self._xpath(PREFERENCE_VALUE_XPATH, operator):
12✔
410
                pref_value = pref.get('value')
12✔
411
                operator_info['preferences'][str(pref.get('name'))] = {
12✔
412
                    'readonly': pref.get('readonly', 'false').lower() == 'true',
413
                    'hidden': pref.get('hidden', 'false').lower() == 'true',
414
                    'value': str(pref_value) if pref_value is not None else None,
415
                }
416

417
            self._info['wiring']['operators'][operator_info['id']] = operator_info
12✔
418

419
    def _parse_widget_info(self):
12✔
420

421
        self._parse_component_preferences()
12✔
422
        self._parse_component_persistentvariables()
12✔
423
        self._parse_wiring_info()
12✔
424

425
        xhtml_element = self._xpath(CODE_XPATH, self._doc)[0]
12✔
426
        self._info['contents'] = {
12✔
427
            'src': str(xhtml_element.get('src')),
428
            'contenttype': str(xhtml_element.get('contenttype', 'text/html')),
429
            'charset': str(xhtml_element.get('charset', 'utf-8')),
430
            'useplatformstyle': xhtml_element.get('useplatformstyle', 'false').lower() == 'true',
431
            'cacheable': xhtml_element.get('cacheable', 'true').lower() == 'true'
432
        }
433

434
        self._info['altcontents'] = []
12✔
435
        for altcontents in self._xpath(ALTCONTENT_XPATH, xhtml_element):
12✔
436
            self._info['altcontents'].append({
12✔
437
                'scope': altcontents.get('scope'),
438
                'src': altcontents.get('src'),
439
                'contenttype': altcontents.get('contenttype', 'text/html'),
440
                'charset': altcontents.get('charset', 'utf-8')
441
            })
442

443
        rendering_element = self.get_xpath(PLATFORM_RENDERING_XPATH, self._doc)
12✔
444
        self._info['widget_width'] = rendering_element.get('width')
12✔
445
        self._info['widget_height'] = rendering_element.get('height')
12✔
446

447
        if self._info["macversion"] > 1:
12✔
448
            js_files = self._xpath(SCRIPT_XPATH, self._doc)
12✔
449

450
            self._info['js_files'] = []
12✔
451
            for script in js_files:
12✔
452
                self._info['js_files'].append(str(script.get('src')))
12✔
453

454
            entrypoint = self.get_xpath(ENTRYPOINT_XPATH, self._doc, required=False)
12✔
455
            if entrypoint is not None:
12!
456
                self._info["entrypoint"] = entrypoint.get('name')
12✔
457
        else:
458
            js_files = self._xpath(SCRIPT_XPATH, self._doc)
12✔
459
            if len(js_files) > 0:
12!
UNCOV
460
                raise TemplateParseException(_("The use of the script element is not allowed in version 1.0 widgets"))
×
461

462
    def _parse_operator_info(self):
12✔
463

464
        self._parse_component_preferences()
12✔
465
        self._parse_component_persistentvariables()
12✔
466
        self._parse_wiring_info()
12✔
467

468
        self._info['js_files'] = []
12✔
469
        for script in self._xpath(SCRIPT_XPATH, self._doc):
12✔
470
            self._info['js_files'].append(str(script.get('src')))
12✔
471

472
        if self._info["macversion"] > 1:
12✔
473
            entrypoint = self.get_xpath(ENTRYPOINT_XPATH, self._doc, required=False)
12✔
474
            if entrypoint is not None:
12!
475
                self._info["entrypoint"] = entrypoint.get('name')
12✔
476

477
    def _parse_component_preferences(self):
12✔
478

479
        self._info['preferences'] = []
12✔
480
        for preference in self._xpath(PREFERENCES_XPATH, self._doc):
12✔
481
            self._add_translation_index(preference.get('label'), type='vdef', variable=preference.get('name'), field='label')
12✔
482
            self._add_translation_index(preference.get('description', ''), type='vdef', variable=preference.get('name'), field='description')
12✔
483
            preference_info = {
12✔
484
                'name': str(preference.get('name')),
485
                'type': str(preference.get('type')),
486
                'label': str(preference.get('label', '')),
487
                'description': str(preference.get('description', '')),
488
                'readonly': preference.get('readonly', 'false').lower() == 'true',
489
                'default': str(preference.get('default', '')),
490
                'value': preference.get('value'),
491
                'secure': preference.get('secure', 'false').lower() == 'true',
492
                'multiuser': False,
493
                'required': preference.get('required', 'false').lower() == 'true',
494
                'language': str(preference.get('language', ''))
495
            }
496

497
            if preference_info['type'] == 'list':
12✔
498
                preference_info['options'] = []
12✔
499
                for option_index, option in enumerate(self._xpath(OPTION_XPATH, preference)):
12✔
500
                    option_label = option.get('label', option.get('name'))
12✔
501
                    self._add_translation_index(option_label, type='upo', variable=preference.get('name'), option=option_index)
12✔
502
                    preference_info['options'].append({
12✔
503
                        'label': str(option_label),
504
                        'value': option.get('value'),
505
                    })
506

507
            self._info['preferences'].append(preference_info)
12✔
508

509
    def _parse_component_persistentvariables(self):
12✔
510

511
        self._info['properties'] = []
12✔
512
        for prop in self._xpath(PROPERTY_XPATH, self._doc):
12✔
513
            self._add_translation_index(prop.get('label'), type='vdef', variable=prop.get('name'))
12✔
514
            self._add_translation_index(prop.get('description', ''), type='vdef', variable=prop.get('name'))
12✔
515
            self._info['properties'].append({
12✔
516
                'name': str(prop.get('name')),
517
                'type': str(prop.get('type')),
518
                'label': str(prop.get('label', '')),
519
                'description': str(prop.get('description', '')),
520
                'default': str(prop.get('default', '')),
521
                'secure': prop.get('secure', 'false').lower() == 'true',
522
                'multiuser': prop.get('multiuser', 'false').lower() == 'true'
523
            })
524

525
    def _parse_preference_values(self, element):
12✔
526
        values = {}
12✔
527

528
        for preference in self._xpath(PREFERENCE_VALUE_XPATH, element):
12✔
529
            pref_value = preference.get('value')
12✔
530
            values[str(preference.get('name'))] = str(pref_value) if pref_value is not None else None
12✔
531

532
        return values
12✔
533

534
    def _parse_workspace_info(self):
12✔
535

536
        workspace_structure = self._xpath(INCLUDED_RESOURCES_XPATH, self._doc)[0]
12✔
537

538
        self._info['preferences'] = self._parse_preference_values(workspace_structure)
12✔
539

540
        self._info['params'] = []
12✔
541
        for param in self._xpath(PARAM_XPATH, self._doc):
12✔
542
            self._info['params'].append({
12✔
543
                'name': str(param.get('name')),
544
                'type': str(param.get('type')),
545
                'label': str(param.get('label', '')),
546
                'description': str(param.get('description', '')),
547
                'readonly': param.get('readonly', 'false').lower() == 'true',
548
                'default': str(param.get('default', '')),
549
                'value': param.get('value'),
550
                'required': param.get('required', 'true').lower() == 'true',
551
            })
552

553
        self._info['embedded'] = []
12✔
554
        for component in self._xpath(EMBEDDEDRESOURCE_XPATH, self._doc):
12✔
555
            self._info['embedded'].append({
12✔
556
                'vendor': str(component.get('vendor')),
557
                'name': str(component.get('name')),
558
                'version': str(component.get('version')),
559
                'src': str(component.get('src'))
560
            })
561

562
        tabs = []
12✔
563
        for tab in self._xpath(TAB_XPATH, workspace_structure):
12✔
564
            tab_info = {
12✔
565
                'name': str(tab.get('name')),
566
                'title': str(tab.get('title', '')),
567
                'preferences': self._parse_preference_values(tab),
568
                'resources': [],
569
            }
570

571
            for widget in self._xpath(RESOURCE_XPATH, tab):
12✔
572
                position = self.get_xpath(POSITION_XPATH, widget, required=False)
12✔
573
                screenSizes = self.get_xpath(SCREEN_SIZES_XPATH, widget, required=False)
12✔
574
                rendering = self.get_xpath(RENDERING_XPATH, widget, required=False)
12✔
575

576
                if (position is None or rendering is None) and screenSizes is None:
12!
UNCOV
577
                    raise TemplateParseException(_("Missing position/rendering or screensizes element"))
×
578

579
                if (rendering is None and not widget.get('layout')):
12!
UNCOV
580
                    raise TemplateParseException(_("Missing layout in resource or rendering element"))
×
581

582
                if rendering is None:
12✔
583
                    layout = int(str(widget.get('layout')))
12✔
584
                else:
585
                    layout = int(str(rendering.get('layout')))
12✔
586

587
                widget_info = {
12✔
588
                    'id': str(widget.get('id')),
589
                    'name': str(widget.get('name')),
590
                    'vendor': str(widget.get('vendor')),
591
                    'version': str(widget.get('version')),
592
                    'title': str(widget.get('title')),
593
                    'readonly': widget.get('readonly', '').lower() == 'true',
594
                    'layout': layout,
595
                    'properties': {},
596
                    'preferences': {}
597
                }
598

599
                if screenSizes is not None:
12✔
600
                    widget_info['screenSizes'] = []
12✔
601
                    for screenSize in screenSizes:
12✔
602
                        position = self.get_xpath(POSITION_XPATH, screenSize)
12✔
603
                        rendering = self.get_xpath(RENDERING_XPATH, screenSize)
12✔
604
                        screen_size_info = {
12✔
605
                            'moreOrEqual': int(screenSize.get('moreOrEqual')),
606
                            'lessOrEqual': int(screenSize.get('lessOrEqual')),
607
                            'id': int(screenSize.get('id')),
608
                            'position': {
609
                                'anchor': str(position.get('anchor', 'top-left')),
610
                                'relx': position.get('relx', 'true').lower() == 'true',
611
                                'rely': position.get('rely', 'true' if layout != 1 else 'false').lower() == 'true',
612
                                'x': position.get('x'),
613
                                'y': position.get('y'),
614
                                'z': position.get('z'),
615
                            },
616
                            'rendering': {
617
                                'fulldragboard': rendering.get('fulldragboard', 'false').lower() == 'true',
618
                                'minimized': rendering.get('minimized', 'false').lower() == 'true',
619
                                'relwidth': rendering.get('relwidth', 'true').lower() == 'true',
620
                                'relheight': rendering.get('relheight', 'true' if layout != 1 else 'false').lower() == 'true',
621
                                'width': rendering.get('width'),
622
                                'height': rendering.get('height'),
623
                                'titlevisible': rendering.get('titlevisible', 'true').lower() == 'true',
624
                            }
625
                        }
626

627
                        widget_info['screenSizes'].append(screen_size_info)
12✔
628
                else:
629
                    widget_info['screenSizes'] = [
12✔
630
                        {
631
                            'moreOrEqual': 0,
632
                            'lessOrEqual': -1,
633
                            'id': 0,
634
                            'position': {
635
                                'anchor': str(position.get('anchor', 'top-left')),
636
                                'relx': position.get('relx', 'true').lower() == 'true',
637
                                'rely': position.get('rely', 'true' if layout != 1 else 'false').lower() == 'true',
638
                                'x': str(position.get('x')),
639
                                'y': str(position.get('y')),
640
                                'z': str(position.get('z')),
641
                            },
642
                            'rendering': {
643
                                'fulldragboard': rendering.get('fulldragboard', 'false').lower() == 'true',
644
                                'minimized': rendering.get('minimized', 'false').lower() == 'true',
645
                                'relwidth': rendering.get('relwidth', 'true').lower() == 'true',
646
                                'relheight': rendering.get('relheight', 'true' if layout != 1 else 'false').lower() == 'true',
647
                                'width': str(rendering.get('width')),
648
                                'height': str(rendering.get('height')),
649
                                'titlevisible': rendering.get('titlevisible', 'true').lower() == 'true',
650
                            }
651
                        }
652
                    ]
653

654
                for prop in self._xpath(PROPERTIES_XPATH, widget):
12✔
655
                    prop_value = prop.get('value')
12✔
656
                    widget_info['properties'][str(prop.get('name'))] = {
12✔
657
                        'readonly': prop.get('readonly', 'false').lower() == 'true',
658
                        'value': str(prop_value) if prop_value is not None else None,
659
                    }
660
                for pref in self._xpath(PREFERENCE_VALUE_XPATH, widget):
12✔
661
                    pref_value = pref.get('value')
12✔
662
                    widget_info['preferences'][str(pref.get('name'))] = {
12✔
663
                        'readonly': pref.get('readonly', 'false').lower() == 'true',
664
                        'hidden': pref.get('hidden', 'false').lower() == 'true',
665
                        'value': str(pref_value) if pref_value is not None else None,
666
                    }
667

668
                tab_info['resources'].append(widget_info)
12✔
669

670
            tabs.append(tab_info)
12✔
671

672
        self._info['tabs'] = tabs
12✔
673
        self._parse_wiring_info()
12✔
674

675
    def _parse_translation_catalogue(self):
12✔
676

677
        self._info['translations'] = {}
12✔
678
        self._info['default_lang'] = 'en'
12✔
679
        self._info['translation_index_usage'] = {}
12✔
680

681
        translations_elements = self._xpath(TRANSLATIONS_XPATH, self._doc)
12✔
682

683
        if len(translations_elements) == 0:
12✔
684
            return
12✔
685

686
        missing_translations = []
12✔
687
        extra_translations = set()
12✔
688

689
        translations = translations_elements[0]
12✔
690
        self._info['default_lang'] = str(translations.get('default'))
12✔
691

692
        for translation in self._xpath(TRANSLATION_XPATH, translations):
12✔
693
            current_catalogue = {}
12✔
694

695
            for msg in self._xpath(MSG_XPATH, translation):
12✔
696
                if msg.get('name') not in self._translation_indexes:
12✔
697
                    extra_translations.add(msg.get('name'))
12✔
698

699
                current_catalogue[msg.get('name')] = str(msg.text)
12✔
700

701
            self._info['translations'][translation.get('lang')] = current_catalogue
12✔
702

703
        if self._info['default_lang'] not in self._info['translations']:
12!
UNCOV
704
            raise TemplateParseException(_("There isn't a translation element for the default translation language: (%(default_lang)s)") % {'default_lang': self._info['default_lang']})
×
705

706
        for index in self._translation_indexes:
12✔
707
            if index not in self._info['translations'][self._info['default_lang']]:
12!
UNCOV
708
                missing_translations.append(index)
×
709

710
        if len(missing_translations) > 0:
12!
UNCOV
711
            msg = _("The following translation indexes need a default value: %(indexes)s.")
×
UNCOV
712
            raise TemplateParseException(msg % {'indexes': ', '.join(missing_translations)})
×
713

714
        if len(extra_translations) > 0:
12✔
715
            msg = _("The following translation indexes are not used: %(indexes)s.")
12✔
716
            raise TemplateParseException(msg % {'indexes': ', '.join(extra_translations)})
12✔
717

718
        self._info['translation_index_usage'] = self._translation_indexes
12✔
719

720
    def get_resource_type(self):
12✔
721
        return self._info['type']
12✔
722

723
    def get_resource_name(self):
12✔
724
        return self._info['name']
12✔
725

726
    def get_resource_vendor(self):
12✔
727
        return self._info['vendor']
12✔
728

729
    def get_resource_version(self):
12✔
730
        return self._info['version']
12✔
731

732
    def get_resource_info(self):
12✔
733
        if not self._parsed:
12✔
734
            self._parse_extra_info()
12✔
735

736
        return dict(self._info)
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc