tricc-oo 1.0.1__py3-none-any.whl → 1.4.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. tests/build.py +213 -0
  2. tests/test_cql.py +197 -0
  3. tests/to_ocl.py +51 -0
  4. {tricc → tricc_oo}/__init__.py +3 -1
  5. tricc_oo/converters/codesystem_to_ocl.py +169 -0
  6. tricc_oo/converters/cql/cqlLexer.py +822 -0
  7. tricc_oo/converters/cql/cqlListener.py +1632 -0
  8. tricc_oo/converters/cql/cqlParser.py +11204 -0
  9. tricc_oo/converters/cql/cqlVisitor.py +913 -0
  10. tricc_oo/converters/cql_to_operation.py +402 -0
  11. tricc_oo/converters/datadictionnary.py +115 -0
  12. tricc_oo/converters/drawio_type_map.py +222 -0
  13. tricc_oo/converters/tricc_to_xls_form.py +61 -0
  14. tricc_oo/converters/utils.py +65 -0
  15. tricc_oo/converters/xml_to_tricc.py +1003 -0
  16. tricc_oo/models/__init__.py +4 -0
  17. tricc_oo/models/base.py +732 -0
  18. tricc_oo/models/calculate.py +216 -0
  19. tricc_oo/models/ocl.py +281 -0
  20. tricc_oo/models/ordered_set.py +125 -0
  21. tricc_oo/models/tricc.py +418 -0
  22. tricc_oo/parsers/xml.py +138 -0
  23. tricc_oo/serializers/__init__.py +0 -0
  24. tricc_oo/serializers/xls_form.py +745 -0
  25. tricc_oo/strategies/__init__.py +0 -0
  26. tricc_oo/strategies/input/__init__.py +0 -0
  27. tricc_oo/strategies/input/base_input_strategy.py +111 -0
  28. tricc_oo/strategies/input/drawio.py +317 -0
  29. tricc_oo/strategies/output/base_output_strategy.py +148 -0
  30. tricc_oo/strategies/output/spice.py +365 -0
  31. tricc_oo/strategies/output/xls_form.py +697 -0
  32. tricc_oo/strategies/output/xlsform_cdss.py +189 -0
  33. tricc_oo/strategies/output/xlsform_cht.py +200 -0
  34. tricc_oo/strategies/output/xlsform_cht_hf.py +334 -0
  35. tricc_oo/visitors/__init__.py +0 -0
  36. tricc_oo/visitors/tricc.py +2198 -0
  37. tricc_oo/visitors/utils.py +17 -0
  38. tricc_oo/visitors/xform_pd.py +264 -0
  39. tricc_oo-1.4.15.dist-info/METADATA +219 -0
  40. tricc_oo-1.4.15.dist-info/RECORD +46 -0
  41. {tricc_oo-1.0.1.dist-info → tricc_oo-1.4.15.dist-info}/WHEEL +1 -1
  42. tricc_oo-1.4.15.dist-info/top_level.txt +2 -0
  43. tricc/converters/mc_to_tricc.py +0 -542
  44. tricc/converters/tricc_to_xls_form.py +0 -553
  45. tricc/converters/utils.py +0 -44
  46. tricc/converters/xml_to_tricc.py +0 -740
  47. tricc/models/tricc.py +0 -1093
  48. tricc/parsers/xml.py +0 -81
  49. tricc/serializers/xls_form.py +0 -364
  50. tricc/strategies/input/base_input_strategy.py +0 -80
  51. tricc/strategies/input/drawio.py +0 -246
  52. tricc/strategies/input/medalcreator.py +0 -168
  53. tricc/strategies/output/base_output_strategy.py +0 -92
  54. tricc/strategies/output/xls_form.py +0 -194
  55. tricc/strategies/output/xlsform_cdss.py +0 -46
  56. tricc/strategies/output/xlsform_cht.py +0 -106
  57. tricc/visitors/tricc.py +0 -375
  58. tricc_oo-1.0.1.dist-info/LICENSE +0 -78
  59. tricc_oo-1.0.1.dist-info/METADATA +0 -229
  60. tricc_oo-1.0.1.dist-info/RECORD +0 -26
  61. tricc_oo-1.0.1.dist-info/top_level.txt +0 -2
  62. venv/bin/vba_extract.py +0 -78
  63. {tricc → tricc_oo}/converters/__init__.py +0 -0
  64. {tricc → tricc_oo}/models/lang.py +0 -0
  65. {tricc/serializers → tricc_oo/parsers}/__init__.py +0 -0
  66. {tricc → tricc_oo}/serializers/planuml.py +0 -0
@@ -0,0 +1,418 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import random
5
+ import string
6
+ from enum import Enum, auto
7
+ from typing import Dict, ForwardRef, List, Optional, Union
8
+ from fhir.resources.codesystem import CodeSystem
9
+ from fhir.resources.valueset import ValueSet
10
+ from pydantic import BaseModel, constr
11
+ from strenum import StrEnum
12
+ from .base import *
13
+ from tricc_oo.converters.utils import generate_id
14
+
15
+ class TriccNodeCalculateBase(TriccNodeBaseModel):
16
+ #input: Dict[TriccOperation, TriccNodeBaseModel] = {}
17
+ reference: Union[List[Union[TriccNodeBaseModel,TriccStatic]], Expression, TriccStatic] = None
18
+ expression_reference: Union[str, TriccOperation] = None
19
+ last: bool = None
20
+ datatype: str = 'boolean'
21
+ # to use the enum value of the TriccNodeType
22
+ class Config:
23
+ use_enum_values = True # <--
24
+
25
+ def make_instance(self, instance_nb, activity, **kwargs):
26
+ # shallow copy
27
+ instance = super().make_instance(instance_nb, activity=activity)
28
+ #input = {}
29
+ #instance.input = input
30
+ expression = self.expression.copy() if self.expression is not None else None
31
+ if self.reference:
32
+ instance.reference = [e.copy() if isinstance(e, (TriccReference, TriccOperation)) else (TriccReference(e.name) if hasattr(e, 'name') else e) for e in self.reference]
33
+ else:
34
+ instance.reference = None
35
+ version = self.version + 1
36
+ instance.version = version
37
+ return instance
38
+
39
+ def __init__(self, **data):
40
+ if 'name' not in data:
41
+ data['name'] = get_rand_name(data.get('id', None))
42
+ super().__init__(**data)
43
+
44
+
45
+ def append(self, elm):
46
+ self.reference.append(elm)
47
+
48
+ def get_references(self):
49
+ if isinstance(self.reference, set):
50
+ return self.reference
51
+ elif isinstance(self.reference, list):
52
+ return set(self.reference)
53
+ elif isinstance(self.expression_reference, TriccOperation):
54
+ return self.expression_reference.get_references()
55
+ elif isinstance(self.reference, TriccOperation):
56
+ return self.reference.get_references()
57
+
58
+ elif self.reference:
59
+ return self.reference
60
+ logger.critical("Cannot get reference from a sting")
61
+ def __str__(self):
62
+ return self.get_name()
63
+
64
+
65
+ class TriccNodeActivity(TriccNodeBaseModel):
66
+ tricc_type: TriccNodeType = TriccNodeType.activity
67
+ # starting point of the activity
68
+ root: TriccNodeBaseModel
69
+ # edge list
70
+ edges: List[TriccEdge] = []
71
+ # copy of the edge for later restauration
72
+ unused_edges: List[TriccEdge] = []
73
+ # nodes part of that actvity
74
+ nodes: Dict[str, TriccNodeBaseModel] = {}
75
+ # groups
76
+ groups: Dict[str, TriccGroup] = {}
77
+ # save the instance on the base activity
78
+ instances: Dict[int, TriccNodeBaseModel] = {}
79
+ relevance: Optional[Union[Expression, TriccOperation]] = None
80
+ #caclulate that are not part of the any skip logic:
81
+ # - inputs
82
+ # - dangling calculate
83
+ # - case definition
84
+ calculates: List[TriccNodeCalculateBase] = []
85
+ applicability: Optional[Union[Expression, TriccOperation]] = None
86
+
87
+ # redefine
88
+ def make_instance(self, instance_nb, **kwargs):
89
+ from tricc_oo.models.calculate import (
90
+ TriccNodeDisplayBridge,
91
+ TriccNodeBridge,
92
+
93
+ )
94
+ # shallow copy
95
+ if instance_nb in self.instances:
96
+ return self.instances[instance_nb]
97
+ else:
98
+ instance = super().make_instance(instance_nb, activity=None)
99
+ self.instances[instance_nb] = instance
100
+ # instance.base_instance = self
101
+ # we duplicate all the related nodes (not the calculate, duplication is manage in calculate version code)
102
+ nodes = {}
103
+ instance.nodes = nodes
104
+ edges = []
105
+ instance.edges = edges
106
+ unused_edges = []
107
+ instance.edges = unused_edges
108
+ calculates = []
109
+ instance.calculates = calculates
110
+ relevance = None
111
+ instance.relevance = relevance
112
+ groups = {}
113
+ instance.groups = groups
114
+ instance.group = instance
115
+ instance.activity = instance
116
+ for edge in self.edges:
117
+ instance.edges.append(edge.make_instance(instance_nb, activity=instance))
118
+ instance.update_nodes(self.root)
119
+ # we walk throught the nodes and replace them when ready
120
+ for node in list(filter(lambda p_node: isinstance(p_node, (TriccNodeDisplayBridge,TriccNodeBridge)),list(self.nodes.values()) )):
121
+ instance.update_nodes(node)
122
+ for node in list(filter(lambda p_node: p_node != self.root and not isinstance(p_node, (TriccNodeDisplayBridge,TriccNodeBridge)),list(self.nodes.values()) )):
123
+ instance_node = instance.update_nodes(node)
124
+ if node in self.calculates and instance_node:
125
+ instance.calculates.append(instance_node)
126
+ # update parents
127
+ for node in list(filter(lambda p_node: hasattr(p_node, 'parent'),list(instance.nodes.values()))):
128
+ new_parent = list(filter(lambda p_node: p_node.base_instance == node.parent,list(instance.nodes.values())))
129
+ if new_parent:
130
+ node.parent = new_parent[0]
131
+ else:
132
+ logger.error("Parent not found in the activity")
133
+ for group in self.groups.values():
134
+ instance.update_groups(group)
135
+ # update parent group
136
+ for group in self.groups.values():
137
+ instance.update_groups_group(group)
138
+
139
+ return instance
140
+
141
+ def update_groups_group(self, group):
142
+ for instance_group in self.groups.values():
143
+ if instance_group.group == group:
144
+ instance_group.group == instance_group
145
+ elif instance_group.group == self.base_instance:
146
+ instance_group.group == self
147
+
148
+ def update_groups(self, group):
149
+ # create new group
150
+ instance_group = group.make_instance(self.instance, activity=self)
151
+ # update the group in all activity
152
+ for node in list(self.nodes.values()):
153
+ if node.group == group:
154
+ node.group == instance_group
155
+ self.groups[instance_group.id] = instance_group
156
+
157
+ def update_nodes(self, node_origin):
158
+ from tricc_oo.models.calculate import (
159
+ TriccNodeEnd,
160
+ TriccNodeActivityStart,
161
+ TriccNodeMainStart,
162
+ TriccNodeActivityEnd,
163
+ TriccRhombusMixIn
164
+ )
165
+ updated_edges = 0
166
+ node_instance = None
167
+ if not isinstance(node_origin, TriccNodeSelectOption):
168
+ # do not perpetuate the instance number in the underlying activities
169
+ if isinstance(node_origin, TriccNodeActivity):
170
+ node_instance = node_origin.make_instance(node_origin.instance if node_origin.instance<100 else 0 , activity=self)
171
+ else:
172
+ node_instance = node_origin.make_instance(self.instance, activity=self)
173
+ self.nodes[node_instance.id] = node_instance
174
+ if isinstance(node_instance, (TriccNodeActivityEnd)):
175
+ node_instance.set_name()
176
+ # update root
177
+ if isinstance(node_origin, TriccNodeActivityStart) and node_origin == node_origin.activity.root:
178
+ self.root = node_instance
179
+ if issubclass(node_instance.__class__, TriccRhombusMixIn):
180
+ old_path = node_origin.path
181
+ if old_path is not None:
182
+ for n in node_instance.activity.nodes.values():
183
+ if n.base_instance.id == old_path.id:
184
+ node_instance.path = n
185
+ # test next_nodes to check that the instance has already prev/next
186
+ if node_instance.path is None and node_instance.next_nodes:
187
+ logger.critical("new path not found")
188
+ elif len(node_instance.prev_nodes) == 1:
189
+ node.path = list(node_instance.prev_nodes)[0]
190
+ elif not (len(node_instance.reference)== 1 and issubclass(node_instance.reference[0].__class__, TriccNodeInputModel)):
191
+ error.warning("Rhombus without a path")
192
+ exit(1)
193
+ # generate options
194
+ elif issubclass(node_instance.__class__, TriccNodeSelect):
195
+ for key, option_instance in node_instance.options.items():
196
+ updated_edges += self.update_edges(node_origin.options[key], option_instance)
197
+ updated_edges += self.update_edges(node_origin, node_instance)
198
+ if updated_edges == 0:
199
+ node_edge = list(filter(lambda x: (x.source == node_instance.id or x.source == node_instance) , node_instance.activity.edges))
200
+ node_edge_origin = list(filter(lambda x: (x.source == node_origin.id or x.source == node_origin) , node_origin.activity.edges))
201
+ if len(node_edge) == 0 and not issubclass(node_origin.__class__, TriccNodeCalculateBase):
202
+ logger.warning("no edge was updated for node {}::{}::{}::{}".format(node_instance.activity.get_name(),
203
+ node_instance.__class__,
204
+ node_instance.get_name(),
205
+ node_instance.instance))
206
+ return node_instance
207
+
208
+ def update_edges(self, node_origin, node_instance):
209
+ updates = 0
210
+
211
+ for edge in self.edges:
212
+ if edge.source == node_origin.id or edge.source == node_origin:
213
+ edge.source = node_instance.id
214
+ updates += 1
215
+ if edge.target == node_origin.id or edge.target == node_origin:
216
+ edge.target = node_instance.id
217
+ updates += 1
218
+ return updates
219
+
220
+ def get_end_nodes(self):
221
+ from tricc_oo.models.calculate import (
222
+ TriccNodeEnd,
223
+ TriccNodeActivityEnd,
224
+ )
225
+ return list(filter(lambda x: isinstance(x, (TriccNodeActivityEnd)) or (isinstance(x, (TriccNodeEnd)) and isinstance(self.root, TriccNodeMainStart )), self.nodes.values()))
226
+
227
+
228
+
229
+
230
+ class TriccNodeDisplayModel(TriccNodeBaseModel):
231
+ name: str
232
+ image: Optional[b64] = None
233
+ hint: Optional[Union[str, TriccNodeBaseModel]] = None
234
+ help: Optional[Union[str, TriccNodeBaseModel]] = None
235
+ group: Optional[Union[TriccGroup, TriccNodeActivity]] = None
236
+ relevance: Optional[Union[Expression, TriccOperation]] = None
237
+
238
+
239
+ # to use the enum value of the TriccNodeType
240
+
241
+
242
+ class TriccNodeNote(TriccNodeDisplayModel):
243
+ tricc_type: TriccNodeType = TriccNodeType.note
244
+ datatype: str = 'string'
245
+
246
+ class TriccNodeInputModel(TriccNodeDisplayModel):
247
+ required: Optional[Union[Expression, TriccOperation, TriccStatic]] = '1'
248
+ constraint_message: Optional[Union[str, Dict[str,str]]] = None
249
+ constraint: Optional[Expression] = None
250
+ save: Optional[str] = None # contribute to another calculate
251
+
252
+
253
+ class TriccNodeDate(TriccNodeInputModel):
254
+ tricc_type: TriccNodeType = TriccNodeType.date
255
+ datatype: str = 'date'
256
+
257
+
258
+ class TriccNodeMainStart(TriccNodeBaseModel):
259
+ tricc_type: TriccNodeType = TriccNodeType.start
260
+ form_id: Optional[str] = None
261
+ process: Optional[str] = None
262
+ relevance: Optional[Union[Expression, TriccOperation]] = None
263
+ datatype: str = 'boolean'
264
+
265
+
266
+ class TriccNodeLinkIn(TriccNodeBaseModel):
267
+ tricc_type: TriccNodeType = TriccNodeType.link_in
268
+ datatype: str = 'n/a'
269
+
270
+
271
+ class TriccNodeLinkOut(TriccNodeBaseModel):
272
+ tricc_type: TriccNodeType = TriccNodeType.link_out
273
+ reference: Optional[Union[TriccNodeLinkIn, triccId]] = None
274
+ # no need to copy
275
+ datatype: str = 'n/a'
276
+
277
+
278
+ class TriccNodeGoTo(TriccNodeBaseModel):
279
+ tricc_type: TriccNodeType = TriccNodeType.goto
280
+ link: Union[TriccNodeActivity, triccId]
281
+ datatype: str = 'n/a'
282
+
283
+ # no need ot copy
284
+ def make_instance(self, instance_nb, activity, **kwargs):
285
+ # shallow copy
286
+ instance = super().make_instance(instance_nb, activity=activity)
287
+ # do not use activity instance for goto
288
+ instance.instance = self.instance
289
+ return instance
290
+
291
+
292
+ class TriccNodeSelectOption(TriccNodeDisplayModel):
293
+ tricc_type: TriccNodeType = TriccNodeType.select_option
294
+ label: Union[str, Dict[str,str]]
295
+ save: Optional[str] = None
296
+ select: TriccNodeInputModel
297
+ list_name: str
298
+ def get_datatype(self):
299
+ if isnumeric(self.name):
300
+ return 'number'
301
+ else:
302
+ return 'string'
303
+
304
+
305
+ def make_instance(self, instance_nb, activity, select, **kwargs):
306
+ # shallow copy
307
+ instance = super().make_instance(instance_nb, activity=activity)
308
+ instance.select = select
309
+ return instance
310
+
311
+ def get_name(self):
312
+ name = super().get_name()
313
+ select_name = self.select.get_name()
314
+ return select_name + "::" + name
315
+
316
+
317
+ class TriccNodeSelect(TriccNodeInputModel):
318
+ filter: Optional[str] = None
319
+ options: Dict[int, TriccNodeSelectOption] = {}
320
+ list_name: str
321
+ def get_datatype(self):
322
+ rtype = set()
323
+ for k,o in options.items():
324
+ rtype.add(o.get_datatype())
325
+ if len(rtype)>1:
326
+ return 'mixed'
327
+ else:
328
+ return rtype.pop()
329
+
330
+ def make_instance(self, instance_nb, activity, **kwargs):
331
+ # shallow copy, no copy of filter and list_name
332
+ instance = super().make_instance(instance_nb, activity=activity)
333
+ instance.options = {}
334
+ for key, option in self.options.items():
335
+ instance.options[key] = option.make_instance(instance_nb, activity=activity, select=instance)
336
+ return instance
337
+
338
+
339
+ class TriccNodeSelectOne(TriccNodeSelect):
340
+ tricc_type: TriccNodeType = TriccNodeType.select_one
341
+
342
+
343
+ class TriccNodeSelectYesNo(TriccNodeSelectOne):
344
+ pass
345
+
346
+ class TriccNodeAcceptDiagnostic(TriccNodeSelectOne):
347
+ severity: Optional[str] = None
348
+
349
+
350
+ class TriccParentMixIn(BaseModel):
351
+ parent: Optional[TriccNodeBaseModel] = None
352
+
353
+ # options: List[TriccNodeSelectOption] = [TriccNodeSelectOption(label='Yes', name='yes'),
354
+ # TriccNodeSelectOption(label='No', name='no')]
355
+ class TriccNodeSelectNotAvailable(TriccNodeSelectOne, TriccParentMixIn):
356
+ ...
357
+
358
+
359
+ class TriccNodeSelectMultiple(TriccNodeSelect):
360
+ tricc_type: TriccNodeType = TriccNodeType.select_multiple
361
+
362
+
363
+ class TriccNodeNumber(TriccNodeInputModel):
364
+ min: Optional[float] = None
365
+ max: Optional[float] = None
366
+ datatype : str = 'number'
367
+ # no need to copy min max in make isntance
368
+
369
+
370
+ class TriccNodeDecimal(TriccNodeNumber):
371
+ tricc_type: TriccNodeType = TriccNodeType.decimal
372
+
373
+
374
+
375
+ class TriccNodeInteger(TriccNodeNumber):
376
+ tricc_type: TriccNodeType = TriccNodeType.integer
377
+
378
+
379
+ class TriccNodeText(TriccNodeInputModel):
380
+ tricc_type: TriccNodeType = TriccNodeType.text
381
+ datatype : str = 'string'
382
+
383
+
384
+ class TriccNodeMoreInfo(TriccNodeInputModel, TriccParentMixIn):
385
+ tricc_type: TriccNodeType = TriccNodeType.help
386
+ datatype : str = 'n/a'
387
+
388
+
389
+
390
+ class TriccProject(BaseModel):
391
+ title: str = "My project"
392
+ description: str = ""
393
+ lang_code: str = "en"
394
+ # abstract graph / Scheduling
395
+ #abs_graph: MultiDiGraph = MultiDiGraph()
396
+ #abs_graph_process_start: Dict = {}
397
+ # implementation graph
398
+ #impl_graph: MultiDiGraph = MultiDiGraph()
399
+ #impl_graph_process_start: Dict = {}
400
+ # authored graph
401
+ #graph: MultiDiGraph = MultiDiGraph()
402
+ #graph_process_start: Dict = {}
403
+ # list of context:
404
+ pages: Dict[str, TriccNodeActivity]= {}
405
+ start_pages: Dict[str, TriccNodeActivity] = {}
406
+ images: List[Dict[str,str]] = []
407
+ contexts : Set[triccName] = set()
408
+ # TODO manage trad properly
409
+ def get_keyword_trad(keyword):
410
+ return keyword
411
+ # dict of code_system_id: codesystem
412
+ code_systems: Dict[str, CodeSystem] = {}
413
+ # dict of valueset_id: valueset
414
+ value_sets: Dict[str, ValueSet] = {}
415
+
416
+ #class Config:
417
+ # Allow arbitrary types for validation
418
+ # arbitrary_types_allowed = True
@@ -0,0 +1,138 @@
1
+ from typing import List
2
+
3
+ import lxml.etree as etree
4
+
5
+
6
+ type_name = "odk_type"
7
+
8
+
9
+ def read_drawio(file_content):
10
+ if file_content.startswith('<mxfile host'):
11
+ file_content = "<?xml version='1.0' encoding='utf-8'?>\n" + file_content
12
+ file_content = bytes(bytearray(file_content, encoding="utf-8"))
13
+ root = etree.fromstring(file_content)
14
+ # import xml.etree.cElementTree as ET
15
+ # with open(filepath) as f:
16
+ # add a fake root so etree can work
17
+ # it = itertools.chain('<root>', f, '</root>')
18
+ # etree = ET.fromstringlist(it)
19
+ # get all the pages
20
+ diagram_list = root.findall(".//diagram")
21
+
22
+ return diagram_list
23
+
24
+
25
+ def get_container_media(diagram, container_id):
26
+ # get the edge
27
+ return diagram.find(
28
+ f".//object[@{type_name}='container_hint_media' and @id='{container_id}']"
29
+ )
30
+ # get the image node
31
+
32
+
33
+ def get_tricc_type(diagram, node_type, tricc_type):
34
+ return diagram.find(f'.//{node_type}[@{type_name}="{str(tricc_type)}"]')
35
+
36
+
37
+ def get_tricc_type_list(diagram, node_type, tricc_type=None, parent_id=None):
38
+ if tricc_type:
39
+ tricc_type = str(tricc_type)
40
+
41
+ parent_suffix = f"[@parent='{parent_id}']" if parent_id is not None else ""
42
+ if isinstance(tricc_type, list):
43
+ result = []
44
+ for type_ in tricc_type:
45
+ result += get_tricc_type_list(diagram, node_type, type_, parent_id)
46
+ return list(set(result))
47
+ if isinstance(node_type, list):
48
+ result = []
49
+ for type_ in node_type:
50
+ result += get_tricc_type_list(diagram, type_, tricc_type, parent_id)
51
+ return list(set(result))
52
+ elif tricc_type is None:
53
+ child = list(diagram.findall(f".//{node_type}[@{type_name}]{parent_suffix}"))
54
+ if child:
55
+ return child
56
+ else:
57
+ return get_child_through_mxcell(diagram, type_name, node_type, parent_suffix, tricc_type)
58
+
59
+ else:
60
+ child = list(
61
+ diagram.findall(
62
+ f'.//{node_type}[@{type_name}="{tricc_type}"]{parent_suffix}'
63
+ )
64
+ )
65
+ if child:
66
+ return child
67
+ else:
68
+ return get_child_through_mxcell(diagram, type_name, node_type, parent_suffix, tricc_type)
69
+
70
+ def get_child_through_mxcell(diagram, type_name, node_type, parent_suffix, tricc_type):
71
+ child = []
72
+ # try with mxCell
73
+ sub = list(diagram.findall(f".//mxCell{parent_suffix}"))
74
+ for s in sub:
75
+ obj = s.getparent()
76
+ if (
77
+ obj.tag == node_type
78
+ and type_name in obj.attrib
79
+ and (
80
+ not tricc_type
81
+ or tricc_type == obj.attrib.get(type_name, None)
82
+ )
83
+ ):
84
+ child.append(obj)
85
+ return child
86
+
87
+
88
+ # end def
89
+
90
+ def get_mxcell_parent_list(diagram, select_id, tricc_type=None, attrib=None):
91
+ # get the mxcels
92
+ if tricc_type is None:
93
+ if attrib is not None:
94
+ return diagram.findall(f".//mxCell[@parent='{select_id}']/..[@{attrib}]")
95
+ else:
96
+ return diagram.findall(f".//mxCell[@parent='{select_id}']")
97
+ elif isinstance(tricc_type, List):
98
+ result = []
99
+ for type in tricc_type:
100
+ result += get_mxcell_parent_list(diagram, select_id, type)
101
+ return result
102
+ else:
103
+ return diagram.findall(
104
+ f".//mxCell[@parent='{select_id}']/..[@{type_name}='{tricc_type}']"
105
+ )
106
+
107
+
108
+ def get_elm(diagram, id):
109
+ return diagram.find(f".//*[@id='{id}']")
110
+
111
+
112
+ def get_mxcell(diagram, id):
113
+ elm = diagram.find(f".//*[@id='{id}']")
114
+ if elm.tag == 'mxCell':
115
+ return elm
116
+ else:
117
+ return diagram.find(f".//*[@id='{id}']/mxCell")
118
+
119
+
120
+ def get_edges_list(diagram):
121
+ # return list(diagram.findall('.//mxCell[@edge][@source][@target]'))
122
+ # to ensure source and target one can use this xpath above but better trigger a pydantic error if source/target are missing
123
+ return list(
124
+ set(
125
+ diagram.findall(".//mxCell[@edge][@source]")
126
+ + diagram.findall(".//mxCell[@edge][@target]")
127
+ )
128
+ )
129
+
130
+
131
+ def get_select_option_image(diagram, select_option_id):
132
+ # get the edge
133
+ edge = diagram.find(f".//mxCell[@edge and @target='{select_option_id}']")
134
+ # get the image node
135
+ if edge is not None and edge.attrib.get("source") is not None:
136
+ return diagram.find(
137
+ f".//mxCell[@id='{edge.attrib.get('source')}' and not(@{type_name}) and not(@edge)]"
138
+ )
File without changes