tricc-oo 1.5.13__py3-none-any.whl → 1.6.8__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.
- tests/build.py +20 -28
- tests/test_build.py +260 -0
- tests/test_cql.py +48 -109
- tests/to_ocl.py +15 -17
- tricc_oo/__init__.py +0 -6
- tricc_oo/converters/codesystem_to_ocl.py +51 -40
- tricc_oo/converters/cql/cqlLexer.py +1 -0
- tricc_oo/converters/cql/cqlListener.py +1 -0
- tricc_oo/converters/cql/cqlParser.py +1 -0
- tricc_oo/converters/cql/cqlVisitor.py +1 -0
- tricc_oo/converters/cql_to_operation.py +129 -123
- tricc_oo/converters/datadictionnary.py +45 -54
- tricc_oo/converters/drawio_type_map.py +146 -65
- tricc_oo/converters/tricc_to_xls_form.py +58 -28
- tricc_oo/converters/utils.py +4 -4
- tricc_oo/converters/xml_to_tricc.py +296 -235
- tricc_oo/models/__init__.py +2 -1
- tricc_oo/models/base.py +333 -305
- tricc_oo/models/calculate.py +66 -51
- tricc_oo/models/lang.py +26 -27
- tricc_oo/models/ocl.py +146 -161
- tricc_oo/models/ordered_set.py +15 -19
- tricc_oo/models/tricc.py +149 -89
- tricc_oo/parsers/xml.py +15 -30
- tricc_oo/serializers/planuml.py +4 -6
- tricc_oo/serializers/xls_form.py +110 -153
- tricc_oo/strategies/input/base_input_strategy.py +28 -32
- tricc_oo/strategies/input/drawio.py +59 -71
- tricc_oo/strategies/output/base_output_strategy.py +151 -65
- tricc_oo/strategies/output/dhis2_form.py +908 -0
- tricc_oo/strategies/output/fhir_form.py +377 -0
- tricc_oo/strategies/output/html_form.py +224 -0
- tricc_oo/strategies/output/openmrs_form.py +694 -0
- tricc_oo/strategies/output/spice.py +106 -127
- tricc_oo/strategies/output/xls_form.py +322 -244
- tricc_oo/strategies/output/xlsform_cdss.py +627 -142
- tricc_oo/strategies/output/xlsform_cht.py +252 -125
- tricc_oo/strategies/output/xlsform_cht_hf.py +13 -24
- tricc_oo/visitors/tricc.py +1424 -1033
- tricc_oo/visitors/utils.py +16 -16
- tricc_oo/visitors/xform_pd.py +91 -89
- {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/METADATA +128 -84
- tricc_oo-1.6.8.dist-info/RECORD +52 -0
- tricc_oo-1.6.8.dist-info/licenses/LICENSE +373 -0
- {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/top_level.txt +0 -0
- tricc_oo-1.5.13.dist-info/RECORD +0 -46
- {tricc_oo-1.5.13.dist-info → tricc_oo-1.6.8.dist-info}/WHEEL +0 -0
|
@@ -6,17 +6,32 @@ Strategy to build the skyp logic following the XLSForm way
|
|
|
6
6
|
import datetime
|
|
7
7
|
import logging
|
|
8
8
|
import os
|
|
9
|
-
|
|
9
|
+
import re
|
|
10
10
|
import pandas as pd
|
|
11
|
+
from pyxform import create_survey_from_xls
|
|
11
12
|
|
|
12
|
-
from tricc_oo.converters.
|
|
13
|
+
from tricc_oo.converters.utils import clean_name
|
|
14
|
+
from tricc_oo.models.base import (
|
|
15
|
+
TriccOperator,
|
|
16
|
+
TriccOperation, TriccStatic, TriccReference
|
|
17
|
+
)
|
|
18
|
+
from tricc_oo.models.ordered_set import OrderedSet
|
|
19
|
+
from tricc_oo.models.calculate import (
|
|
20
|
+
TriccNodeEnd,
|
|
21
|
+
TriccNodeDisplayCalculateBase,
|
|
13
22
|
|
|
14
|
-
from tricc_oo.models import (
|
|
15
|
-
TriccNodeActivity,
|
|
16
|
-
TriccGroup,
|
|
17
|
-
TriccOperation,
|
|
18
|
-
TriccOperator
|
|
19
23
|
)
|
|
24
|
+
from tricc_oo.models.tricc import (
|
|
25
|
+
TriccNodeCalculateBase,
|
|
26
|
+
TriccNodeBaseModel,
|
|
27
|
+
TriccNodeSelectOption,
|
|
28
|
+
TriccNodeInputModel,
|
|
29
|
+
TriccNodeDisplayModel,
|
|
30
|
+
TRICC_FALSE_VALUE,
|
|
31
|
+
TRICC_TRUE_VALUE,
|
|
32
|
+
)
|
|
33
|
+
from tricc_oo.converters.tricc_to_xls_form import get_export_name, BOOLEAN_MAP
|
|
34
|
+
|
|
20
35
|
from tricc_oo.models.lang import SingletonLangClass
|
|
21
36
|
|
|
22
37
|
from tricc_oo.visitors.tricc import (
|
|
@@ -25,9 +40,9 @@ from tricc_oo.visitors.tricc import (
|
|
|
25
40
|
is_ready_to_process,
|
|
26
41
|
process_reference,
|
|
27
42
|
get_node_expressions,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
set_last_version_false,
|
|
44
|
+
generate_calculate,
|
|
45
|
+
generate_base,
|
|
31
46
|
)
|
|
32
47
|
from tricc_oo.serializers.xls_form import (
|
|
33
48
|
CHOICE_MAP,
|
|
@@ -35,14 +50,12 @@ from tricc_oo.serializers.xls_form import (
|
|
|
35
50
|
end_group,
|
|
36
51
|
generate_xls_form_export,
|
|
37
52
|
start_group,
|
|
38
|
-
BOOLEAN_MAP
|
|
39
53
|
)
|
|
40
54
|
from tricc_oo.strategies.output.base_output_strategy import BaseOutPutStrategy
|
|
41
55
|
|
|
42
56
|
logger = logging.getLogger("default")
|
|
43
57
|
|
|
44
58
|
|
|
45
|
-
|
|
46
59
|
"""
|
|
47
60
|
The XLSForm strategy is a strategy that will generate the XLSForm logic
|
|
48
61
|
The XLSForm logic is a logic that is based on the XLSForm format
|
|
@@ -61,7 +74,7 @@ logger = logging.getLogger("default")
|
|
|
61
74
|
check_stashed_loop
|
|
62
75
|
generate_xls_form_export
|
|
63
76
|
generate_xls_form_export
|
|
64
|
-
|
|
77
|
+
|
|
65
78
|
"""
|
|
66
79
|
langs = SingletonLangClass()
|
|
67
80
|
|
|
@@ -73,31 +86,31 @@ OPERATOR_COALESCE_FALLBACK = {
|
|
|
73
86
|
TriccOperator.MORE: -2147483648,
|
|
74
87
|
TriccOperator.MORE_OR_EQUAL: -2147483648,
|
|
75
88
|
TriccOperator.LESS: 2147483647,
|
|
76
|
-
TriccOperator.LESS_OR_EQUAL: 2147483647
|
|
89
|
+
TriccOperator.LESS_OR_EQUAL: 2147483647,
|
|
77
90
|
}
|
|
78
91
|
|
|
92
|
+
|
|
79
93
|
class XLSFormStrategy(BaseOutPutStrategy):
|
|
80
|
-
pd.set_option(
|
|
94
|
+
pd.set_option("display.max_colwidth", None)
|
|
81
95
|
df_survey = pd.DataFrame(columns=SURVEY_MAP.keys())
|
|
82
96
|
df_calculate = pd.DataFrame(columns=SURVEY_MAP.keys())
|
|
83
97
|
df_choice = pd.DataFrame(columns=CHOICE_MAP.keys())
|
|
84
98
|
calculates = {}
|
|
85
99
|
# add save nodes and merge nodes
|
|
86
100
|
|
|
87
|
-
|
|
88
|
-
def clean_coalesce(self, expression):
|
|
101
|
+
def clean_coalesce(self, expression):
|
|
89
102
|
if re.match(r"^coalesce\(\${[^}]+},''\)$", str(expression)):
|
|
90
103
|
return str(expression[9:-4])
|
|
91
104
|
return str(expression)
|
|
92
105
|
|
|
93
106
|
def generate_base(self, node, **kwargs):
|
|
94
|
-
return
|
|
107
|
+
return generate_base(node, **kwargs)
|
|
95
108
|
|
|
96
109
|
def generate_relevance(self, node, **kwargs):
|
|
97
110
|
return self.generate_xls_form_relevance(node, **kwargs)
|
|
98
111
|
|
|
99
112
|
def generate_calculate(self, node, **kwargs):
|
|
100
|
-
return
|
|
113
|
+
return generate_calculate(node, **kwargs)
|
|
101
114
|
|
|
102
115
|
def __init__(self, project, output_path):
|
|
103
116
|
super().__init__(project, output_path)
|
|
@@ -112,12 +125,36 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
112
125
|
"df_survey": self.df_survey,
|
|
113
126
|
"df_choice": self.df_choice,
|
|
114
127
|
"df_calculate": self.df_calculate,
|
|
115
|
-
"calculates": self.calculates
|
|
128
|
+
"calculates": self.calculates,
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
def generate_export(self, node, **kwargs):
|
|
119
132
|
return generate_xls_form_export(self, node, **kwargs)
|
|
120
133
|
|
|
134
|
+
def inject_version(self):
|
|
135
|
+
# Add hidden version field using ODK's version()
|
|
136
|
+
empty = langs.get_trads("", force_dict=True)
|
|
137
|
+
self.df_survey.loc[len(self.df_survey)] = [
|
|
138
|
+
"hidden",
|
|
139
|
+
"version",
|
|
140
|
+
*list(empty.values()), # label
|
|
141
|
+
*list(empty.values()), # hint
|
|
142
|
+
*list(empty.values()), # help
|
|
143
|
+
"", # default
|
|
144
|
+
"hidden", # appearance
|
|
145
|
+
"", # constraint
|
|
146
|
+
*list(empty.values()), # constraint_message
|
|
147
|
+
"", # relevance
|
|
148
|
+
"", # disabled
|
|
149
|
+
"", # required
|
|
150
|
+
*list(empty.values()), # required_message
|
|
151
|
+
"", # read only
|
|
152
|
+
"version()", # calculation
|
|
153
|
+
"", # trigger
|
|
154
|
+
"", # repeat_count
|
|
155
|
+
"", # image
|
|
156
|
+
"", # choice_filter
|
|
157
|
+
]
|
|
121
158
|
|
|
122
159
|
def export(self, start_pages, version):
|
|
123
160
|
if start_pages["main"].root.form_id is not None:
|
|
@@ -128,7 +165,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
128
165
|
title = start_pages["main"].root.label
|
|
129
166
|
file_name = form_id + ".xlsx"
|
|
130
167
|
# make a 'settings' tab
|
|
131
|
-
|
|
168
|
+
datetime.datetime.now()
|
|
132
169
|
indx = [[1]]
|
|
133
170
|
|
|
134
171
|
settings = {
|
|
@@ -146,10 +183,10 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
146
183
|
if not os.path.exists(self.output_path):
|
|
147
184
|
os.makedirs(self.output_path)
|
|
148
185
|
|
|
186
|
+
self.inject_version()
|
|
187
|
+
|
|
149
188
|
# create a Pandas Excel writer using XlsxWriter as the engine
|
|
150
189
|
writer = pd.ExcelWriter(newpath, engine="xlsxwriter")
|
|
151
|
-
if len(self.df_survey[self.df_survey['name'] == 'version'] ):
|
|
152
|
-
self.df_survey.loc[ self.df_survey['name'] == 'version', 'label'] = f"v{version}"
|
|
153
190
|
self.df_survey.to_excel(writer, sheet_name="survey", index=False)
|
|
154
191
|
self.df_choice.to_excel(writer, sheet_name="choices", index=False)
|
|
155
192
|
df_settings.to_excel(writer, sheet_name="settings", index=False)
|
|
@@ -175,9 +212,16 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
175
212
|
cur_group = activity
|
|
176
213
|
groups[activity.id] = 0
|
|
177
214
|
path_len = 0
|
|
178
|
-
process = [
|
|
215
|
+
process = ["main"]
|
|
179
216
|
# keep the vesrions on the group id, max version
|
|
180
|
-
start_group(
|
|
217
|
+
start_group(
|
|
218
|
+
self,
|
|
219
|
+
cur_group=cur_group,
|
|
220
|
+
groups=groups,
|
|
221
|
+
processed_nodes=processed_nodes,
|
|
222
|
+
process=process,
|
|
223
|
+
**self.get_kwargs(),
|
|
224
|
+
)
|
|
181
225
|
walktrhough_tricc_node_processed_stached(
|
|
182
226
|
activity.root,
|
|
183
227
|
self.generate_export,
|
|
@@ -187,14 +231,14 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
187
231
|
cur_group=activity.root.group,
|
|
188
232
|
process=process,
|
|
189
233
|
recursive=False,
|
|
190
|
-
**self.get_kwargs()
|
|
234
|
+
**self.get_kwargs(),
|
|
191
235
|
)
|
|
192
236
|
end_group(self, cur_group=activity, groups=groups, **self.get_kwargs())
|
|
193
237
|
# we save the survey data frame
|
|
194
238
|
df_survey_final = pd.DataFrame(columns=SURVEY_MAP.keys())
|
|
195
239
|
if len(self.df_survey) > (2 + skip_header):
|
|
196
240
|
df_survey_final = self.df_survey
|
|
197
|
-
|
|
241
|
+
# MANAGE STASHED NODES
|
|
198
242
|
prev_stashed_nodes = stashed_nodes.copy()
|
|
199
243
|
loop_count = 0
|
|
200
244
|
len_prev_processed_nodes = 0
|
|
@@ -214,9 +258,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
214
258
|
# while len(stashed_nodes)>0 and isinstance(s_node,TriccGroup):
|
|
215
259
|
# s_node = stashed_nodes.pop()
|
|
216
260
|
if s_node.group is None:
|
|
217
|
-
logger.critical(
|
|
218
|
-
"ERROR group is none for node {}".format(s_node.get_name())
|
|
219
|
-
)
|
|
261
|
+
logger.critical("ERROR group is none for node {}".format(s_node.get_name()))
|
|
220
262
|
start_group(
|
|
221
263
|
self,
|
|
222
264
|
cur_group=s_node.group,
|
|
@@ -224,7 +266,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
224
266
|
processed_nodes=processed_nodes,
|
|
225
267
|
process=process,
|
|
226
268
|
relevance=True,
|
|
227
|
-
**self.get_kwargs()
|
|
269
|
+
**self.get_kwargs(),
|
|
228
270
|
)
|
|
229
271
|
# arrange empty group
|
|
230
272
|
walktrhough_tricc_node_processed_stached(
|
|
@@ -236,8 +278,8 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
236
278
|
groups=groups,
|
|
237
279
|
cur_group=s_node.group,
|
|
238
280
|
recursive=False,
|
|
239
|
-
process
|
|
240
|
-
**self.get_kwargs()
|
|
281
|
+
process=process,
|
|
282
|
+
**self.get_kwargs(),
|
|
241
283
|
)
|
|
242
284
|
# add end group if new node where added OR if the previous end group was removed
|
|
243
285
|
end_group(self, cur_group=s_node.group, groups=groups, **self.get_kwargs())
|
|
@@ -254,13 +296,9 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
254
296
|
)
|
|
255
297
|
)
|
|
256
298
|
if len(df_survey_final):
|
|
257
|
-
df_survey_final.drop(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
self.df_survey = self.df_survey[(1 + skip_header) :]
|
|
261
|
-
df_survey_final = pd.concat(
|
|
262
|
-
[df_survey_final, self.df_survey], ignore_index=True
|
|
263
|
-
)
|
|
299
|
+
df_survey_final.drop(index=df_survey_final.index[-1], axis=0, inplace=True)
|
|
300
|
+
self.df_survey = self.df_survey[(1 + skip_header):]
|
|
301
|
+
df_survey_final = pd.concat([df_survey_final, self.df_survey], ignore_index=True)
|
|
264
302
|
|
|
265
303
|
else:
|
|
266
304
|
logger.debug(
|
|
@@ -271,9 +309,7 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
271
309
|
s_node.group.instance,
|
|
272
310
|
)
|
|
273
311
|
)
|
|
274
|
-
df_survey_final = pd.concat(
|
|
275
|
-
[df_survey_final, self.df_survey], ignore_index=True
|
|
276
|
-
)
|
|
312
|
+
df_survey_final = pd.concat([df_survey_final, self.df_survey], ignore_index=True)
|
|
277
313
|
cur_group = s_node.group
|
|
278
314
|
|
|
279
315
|
# add the calulate
|
|
@@ -282,20 +318,15 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
282
318
|
self.df_survey.reset_index(drop=True, inplace=True)
|
|
283
319
|
self.df_calculate.reset_index(drop=True, inplace=True)
|
|
284
320
|
self.df_calculate = self.df_calculate.drop(df_empty_calc.index)
|
|
285
|
-
self.df_survey = pd.concat(
|
|
286
|
-
|
|
287
|
-
)
|
|
288
|
-
df_duplicate = self.df_calculate[
|
|
289
|
-
self.df_calculate.duplicated(subset=["calculation"], keep="first")
|
|
290
|
-
]
|
|
321
|
+
self.df_survey = pd.concat([df_survey_final, self.df_calculate], ignore_index=True)
|
|
322
|
+
df_duplicate = self.df_calculate[self.df_calculate.duplicated(subset=["calculation"], keep="first")]
|
|
291
323
|
# self.df_survey=self.df_survey.drop_duplicates(subset=['name'])
|
|
292
324
|
for index, drop_calc in df_duplicate.iterrows():
|
|
293
325
|
# remove the duplicate
|
|
294
326
|
replace_name = False
|
|
295
327
|
# find the actual calcualte
|
|
296
328
|
similar_calc = self.df_survey[
|
|
297
|
-
(drop_calc["calculation"] == self.df_survey["calculation"])
|
|
298
|
-
& (self.df_survey["type"] == "calculate")
|
|
329
|
+
(drop_calc["calculation"] == self.df_survey["calculation"]) & (self.df_survey["type"] == "calculate")
|
|
299
330
|
]
|
|
300
331
|
same_calc = self.df_survey[self.df_survey["name"] == drop_calc["name"]]
|
|
301
332
|
if len(same_calc) > 1:
|
|
@@ -324,373 +355,390 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
324
355
|
)
|
|
325
356
|
else:
|
|
326
357
|
logger.critical(
|
|
327
|
-
"duplicate reference not found for calculation: {}".format(
|
|
328
|
-
drop_calc["calculation"]
|
|
329
|
-
)
|
|
358
|
+
"duplicate reference not found for calculation: {}".format(drop_calc["calculation"])
|
|
330
359
|
)
|
|
331
360
|
for index, empty_calc in df_empty_calc.iterrows():
|
|
332
361
|
self.df_survey.replace("${" + empty_calc["name"] + "}", "1", regex=True)
|
|
333
362
|
|
|
334
363
|
# TODO try to reinject calc to reduce complexity
|
|
335
|
-
for i, c in self.df_calculate[
|
|
336
|
-
~self.df_calculate["name"].isin(self.df_survey["name"])
|
|
337
|
-
].iterrows():
|
|
364
|
+
for i, c in self.df_calculate[~self.df_calculate["name"].isin(self.df_survey["name"])].iterrows():
|
|
338
365
|
real_calc = re.find(r"^number\((.+)\)$", c["calculation"])
|
|
339
366
|
if real_calc is not None and real_calc != "":
|
|
340
|
-
self.df_survey[~self.df_survey["name"] == c["name"]].replace(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
df_duplicate = self.df_survey[
|
|
345
|
-
self.df_survey.duplicated(subset=["name"], keep="first")
|
|
346
|
-
]
|
|
347
|
-
for index, duplicate in df_duplicate.iterrows():
|
|
367
|
+
self.df_survey[~self.df_survey["name"] == c["name"]].replace(real_calc, "${" + c["name"] + "}")
|
|
368
|
+
|
|
369
|
+
df_duplicate = self.df_survey[self.df_survey.duplicated(subset=["name"], keep="first")]
|
|
370
|
+
for index, duplicate in df_duplicate.iterrows():
|
|
348
371
|
logger.critical(f"duplicate survey name: {duplicate['name']}")
|
|
349
372
|
self.df_survey.reset_index(drop=True, inplace=True)
|
|
350
373
|
return processed_nodes
|
|
351
374
|
|
|
352
375
|
def get_tricc_operation_expression(self, operation):
|
|
353
376
|
ref_expressions = []
|
|
354
|
-
if not hasattr(operation,
|
|
355
|
-
return self.get_tricc_operation_operand(operation)
|
|
356
|
-
|
|
357
|
-
operator = getattr(operation,
|
|
358
|
-
coalesce_fallback = OPERATOR_COALESCE_FALLBACK[operator] if operator in OPERATOR_COALESCE_FALLBACK else
|
|
377
|
+
if not hasattr(operation, "reference"):
|
|
378
|
+
return self.get_tricc_operation_operand(operation)
|
|
379
|
+
|
|
380
|
+
operator = getattr(operation, "operator", "")
|
|
381
|
+
coalesce_fallback = OPERATOR_COALESCE_FALLBACK[operator] if operator in OPERATOR_COALESCE_FALLBACK else "''"
|
|
359
382
|
for r in operation.reference:
|
|
360
383
|
if isinstance(r, list):
|
|
361
384
|
r_expr = [
|
|
362
|
-
|
|
363
|
-
|
|
385
|
+
(
|
|
386
|
+
self.get_tricc_operation_expression(sr)
|
|
387
|
+
if isinstance(sr, TriccOperation)
|
|
388
|
+
else self.get_tricc_operation_operand(sr, coalesce_fallback)
|
|
389
|
+
)
|
|
364
390
|
for sr in r
|
|
365
391
|
]
|
|
366
392
|
elif isinstance(r, TriccOperation):
|
|
367
393
|
r_expr = self.get_tricc_operation_expression(r)
|
|
368
394
|
else:
|
|
369
|
-
r_expr = self.get_tricc_operation_operand(r,coalesce_fallback)
|
|
395
|
+
r_expr = self.get_tricc_operation_operand(r, coalesce_fallback)
|
|
370
396
|
if isinstance(r_expr, TriccReference):
|
|
371
|
-
r_expr = self.get_tricc_operation_operand(r_expr,coalesce_fallback)
|
|
397
|
+
r_expr = self.get_tricc_operation_operand(r_expr, coalesce_fallback)
|
|
372
398
|
ref_expressions.append(r_expr)
|
|
373
|
-
|
|
399
|
+
|
|
374
400
|
# build lower level
|
|
375
|
-
if hasattr(self,f"tricc_operation_{operation.operator}"):
|
|
376
|
-
callable = getattr(self,f"tricc_operation_{operation.operator}")
|
|
377
|
-
return callable(ref_expressions)
|
|
401
|
+
if hasattr(self, f"tricc_operation_{operation.operator}"):
|
|
402
|
+
callable = getattr(self, f"tricc_operation_{operation.operator}")
|
|
403
|
+
return callable(ref_expressions)
|
|
378
404
|
else:
|
|
379
|
-
raise NotImplementedError(
|
|
380
|
-
|
|
405
|
+
raise NotImplementedError(
|
|
406
|
+
f"This type of opreation '{operation.operator}' is not supported in this strategy"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def tricc_operation_count(self, ref_expressions):
|
|
410
|
+
return f"count-selected({self.clean_coalesce(ref_expressions[0])})"
|
|
411
|
+
|
|
381
412
|
def tricc_operation_multiplied(self, ref_expressions):
|
|
382
|
-
return
|
|
413
|
+
return "*".join(map(str, ref_expressions))
|
|
414
|
+
|
|
383
415
|
def tricc_operation_divided(self, ref_expressions):
|
|
384
416
|
return f"{ref_expressions[0]} div {ref_expressions[1]}"
|
|
417
|
+
|
|
385
418
|
def tricc_operation_modulo(self, ref_expressions):
|
|
386
419
|
return f"{ref_expressions[0]} mod {ref_expressions[1]}"
|
|
420
|
+
|
|
387
421
|
def tricc_operation_coalesce(self, ref_expressions):
|
|
388
422
|
return f"coalesce({','.join(map(self.clean_coalesce, ref_expressions))})"
|
|
423
|
+
|
|
389
424
|
def tricc_operation_module(self, ref_expressions):
|
|
390
425
|
return f"{ref_expressions[0]} mod {ref_expressions[1]}"
|
|
426
|
+
|
|
391
427
|
def tricc_operation_minus(self, ref_expressions):
|
|
392
|
-
if len(ref_expressions)>1:
|
|
393
|
-
return
|
|
394
|
-
elif len(ref_expressions)==1:
|
|
395
|
-
return f
|
|
428
|
+
if len(ref_expressions) > 1:
|
|
429
|
+
return " - ".join(map(str, ref_expressions))
|
|
430
|
+
elif len(ref_expressions) == 1:
|
|
431
|
+
return f"-{ref_expressions[0]}"
|
|
432
|
+
|
|
396
433
|
def tricc_operation_plus(self, ref_expressions):
|
|
397
|
-
return
|
|
434
|
+
return " + ".join(map(str, ref_expressions))
|
|
435
|
+
|
|
398
436
|
def tricc_operation_not(self, ref_expressions):
|
|
399
437
|
return f"not({ref_expressions[0]})"
|
|
438
|
+
|
|
400
439
|
def tricc_operation_and(self, ref_expressions):
|
|
401
440
|
if len(ref_expressions) == 1:
|
|
402
441
|
return ref_expressions[0]
|
|
403
|
-
if len(ref_expressions)>1:
|
|
404
|
-
ref_expressions = [
|
|
405
|
-
|
|
442
|
+
if len(ref_expressions) > 1:
|
|
443
|
+
ref_expressions = [
|
|
444
|
+
(f"({r})" if isinstance(r, str) and any(op in r for op in [" or ", " + ", " - "]) else r)
|
|
445
|
+
for r in ref_expressions
|
|
446
|
+
]
|
|
447
|
+
return " and ".join(map(str, ref_expressions))
|
|
406
448
|
else:
|
|
407
|
-
return
|
|
449
|
+
return "1"
|
|
408
450
|
|
|
409
451
|
def tricc_operation_or(self, ref_expressions):
|
|
410
452
|
if len(ref_expressions) == 1:
|
|
411
453
|
return ref_expressions[0]
|
|
412
|
-
if len(ref_expressions)>1:
|
|
413
|
-
ref_expressions = [
|
|
414
|
-
|
|
454
|
+
if len(ref_expressions) > 1:
|
|
455
|
+
ref_expressions = [
|
|
456
|
+
(f"({r})" if isinstance(r, str) and any(op in r for op in [" and ", " + ", " - "]) else r)
|
|
457
|
+
for r in ref_expressions
|
|
458
|
+
]
|
|
459
|
+
return " or ".join(map(str, ref_expressions))
|
|
415
460
|
else:
|
|
416
|
-
return
|
|
461
|
+
return "1"
|
|
417
462
|
|
|
463
|
+
def tricc_operation_native(self, ref_expressions):
|
|
418
464
|
|
|
465
|
+
if len(ref_expressions) > 0:
|
|
466
|
+
if ref_expressions[0].startswith(("'", "`",)):
|
|
467
|
+
ref_expressions[0] = ref_expressions[0][1:-1]
|
|
468
|
+
if ref_expressions[0] == "GetChoiceName":
|
|
469
|
+
return f"""jr:choice-name({
|
|
470
|
+
self.clean_coalesce(ref_expressions[1])
|
|
471
|
+
}, '{
|
|
472
|
+
self.clean_coalesce(ref_expressions[2])
|
|
473
|
+
}')"""
|
|
474
|
+
elif ref_expressions[0] == "GetFacilityParam":
|
|
475
|
+
return "0"
|
|
476
|
+
# return f"jr:choice-name({','.join(ref_expressions[1:])})"
|
|
477
|
+
else:
|
|
478
|
+
return f"{ref_expressions[0]}({','.join(map(str, ref_expressions[1:]))})"
|
|
419
479
|
|
|
420
|
-
def tricc_operation_native(self, ref_expressions):
|
|
421
|
-
if len(ref_expressions)>0:
|
|
422
|
-
if ref_expressions[0] =='GetChoiceName':
|
|
423
|
-
return f"jr:choice-name({self.clean_coalesce(ref_expressions[1])}, '{self.clean_coalesce(ref_expressions[2])}')"
|
|
424
|
-
elif ref_expressions[0] =='GetFacilityParam':
|
|
425
|
-
return '0'
|
|
426
|
-
#return f"jr:choice-name({','.join(ref_expressions[1:])})"
|
|
427
|
-
else:
|
|
428
|
-
return f"{ref_expressions[0]}({','.join(ref_expressions[1:])})"
|
|
429
|
-
|
|
430
480
|
def tricc_operation_istrue(self, ref_expressions):
|
|
431
481
|
if str(BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]).isnumeric():
|
|
432
482
|
return f"{ref_expressions[0]}>={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
433
483
|
else:
|
|
434
484
|
return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
485
|
+
|
|
435
486
|
def tricc_operation_isfalse(self, ref_expressions):
|
|
436
487
|
if str(BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]).isnumeric():
|
|
437
488
|
return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
438
489
|
else:
|
|
439
490
|
return f"{ref_expressions[0]}={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
491
|
+
|
|
440
492
|
def tricc_operation_parenthesis(self, ref_expressions):
|
|
441
493
|
return f"({ref_expressions[0]})"
|
|
494
|
+
|
|
442
495
|
def tricc_operation_selected(self, ref_expressions):
|
|
443
496
|
parts = []
|
|
444
497
|
for s in ref_expressions[1:]:
|
|
445
498
|
# for option with numeric value
|
|
446
|
-
|
|
499
|
+
if isinstance(s, str):
|
|
500
|
+
cleaned_s = s
|
|
501
|
+
elif isinstance(s, TriccNodeSelectOption):
|
|
502
|
+
cleaned_s = s.name
|
|
503
|
+
else:
|
|
504
|
+
cleaned_s = "'" + str(s) + "'"
|
|
447
505
|
parts.append(f"selected({self.clean_coalesce(ref_expressions[0])}, {cleaned_s})")
|
|
448
506
|
if len(parts) == 1:
|
|
449
507
|
return parts[0]
|
|
450
508
|
else:
|
|
451
509
|
return self.tricc_operation_or(parts)
|
|
510
|
+
|
|
452
511
|
def tricc_operation_more_or_equal(self, ref_expressions):
|
|
453
512
|
return f"{ref_expressions[0]}>={ref_expressions[1]}"
|
|
513
|
+
|
|
454
514
|
def tricc_operation_less_or_equal(self, ref_expressions):
|
|
455
515
|
return f"{ref_expressions[0]}<={ref_expressions[1]}"
|
|
516
|
+
|
|
456
517
|
def tricc_operation_more(self, ref_expressions):
|
|
457
518
|
return f"{ref_expressions[0]}>{ref_expressions[1]}"
|
|
519
|
+
|
|
458
520
|
def tricc_operation_less(self, ref_expressions):
|
|
459
521
|
return f"{ref_expressions[0]}<{ref_expressions[1]}"
|
|
522
|
+
|
|
460
523
|
def tricc_operation_between(self, ref_expressions):
|
|
461
|
-
return
|
|
524
|
+
return f"{ref_expressions[0]}>={ref_expressions[1]} and {ref_expressions[0]} < {ref_expressions[2]}"
|
|
525
|
+
|
|
462
526
|
def tricc_operation_equal(self, ref_expressions):
|
|
463
527
|
return f"{ref_expressions[0]}={ref_expressions[1]}"
|
|
528
|
+
|
|
464
529
|
def tricc_operation_not_equal(self, ref_expressions):
|
|
465
530
|
return f"{ref_expressions[0]}!={ref_expressions[1]}"
|
|
531
|
+
|
|
466
532
|
def tricc_operation_isnull(self, ref_expressions):
|
|
467
533
|
return f"{ref_expressions[0]}=''"
|
|
534
|
+
|
|
468
535
|
def tricc_operation_isnotnull(self, ref_expressions):
|
|
469
536
|
return f"{ref_expressions[0]}!=''"
|
|
470
|
-
|
|
537
|
+
|
|
471
538
|
def tricc_operation_isnottrue(self, ref_expressions):
|
|
472
539
|
if str(BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]).isnumeric():
|
|
473
540
|
return f"{ref_expressions[0]}<{BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
474
541
|
else:
|
|
475
542
|
return f"{ref_expressions[0]}!={BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]}"
|
|
543
|
+
|
|
476
544
|
def tricc_operation_isnotfalse(self, ref_expressions):
|
|
477
545
|
if str(BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]).isnumeric():
|
|
478
546
|
return f"{ref_expressions[0]}>{BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
479
547
|
else:
|
|
480
548
|
return f"{ref_expressions[0]}!={BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]}"
|
|
549
|
+
|
|
481
550
|
def tricc_operation_notexist(self, ref_expressions):
|
|
482
551
|
return f"{ref_expressions[0]}=''"
|
|
483
552
|
|
|
484
|
-
|
|
485
|
-
|
|
486
553
|
def tricc_operation_case(self, ref_expressions):
|
|
487
554
|
ifs = 0
|
|
488
555
|
parts = []
|
|
489
556
|
else_found = False
|
|
490
|
-
if
|
|
557
|
+
if isinstance(ref_expressions[0], list):
|
|
491
558
|
return self.tricc_operation_ifs(ref_expressions)
|
|
492
|
-
for i in range(int(len(ref_expressions))):
|
|
559
|
+
for i in range(int(len(ref_expressions[1:]))):
|
|
493
560
|
if isinstance(ref_expressions[i], list):
|
|
494
|
-
parts.append(f"if({ref_expressions[i][0]},{ref_expressions[i][1]}")
|
|
561
|
+
parts.append(f"if({ref_expressions[0]}={ref_expressions[i+1][0]},{ref_expressions[i+1][1]}")
|
|
495
562
|
ifs += 1
|
|
496
563
|
else:
|
|
497
564
|
else_found = True
|
|
498
|
-
parts.append(ref_expressions[i])
|
|
499
|
-
#join the if
|
|
500
|
-
exp =
|
|
565
|
+
parts.append(ref_expressions[i+1])
|
|
566
|
+
# join the if
|
|
567
|
+
exp = ",".join(map(str, parts))
|
|
501
568
|
# in case there is no default put ''
|
|
502
569
|
if not else_found:
|
|
503
570
|
exp += ",''"
|
|
504
|
-
#add the closing )
|
|
571
|
+
# add the closing )
|
|
505
572
|
for i in range(ifs):
|
|
506
573
|
exp += ")"
|
|
507
574
|
return exp
|
|
508
|
-
|
|
575
|
+
|
|
509
576
|
def tricc_operation_ifs(self, ref_expressions):
|
|
510
577
|
ifs = 0
|
|
511
578
|
parts = []
|
|
512
579
|
else_found = False
|
|
513
|
-
for i in range(int(len(ref_expressions
|
|
514
|
-
if isinstance(ref_expressions[i
|
|
515
|
-
parts.append(f"if({ref_expressions[
|
|
580
|
+
for i in range(int(len(ref_expressions))):
|
|
581
|
+
if isinstance(ref_expressions[i], list):
|
|
582
|
+
parts.append(f"if({ref_expressions[i][0]},{ref_expressions[i][1]}")
|
|
516
583
|
ifs += 1
|
|
517
584
|
else:
|
|
518
585
|
else_found = True
|
|
519
|
-
parts.append(ref_expressions[i
|
|
520
|
-
|
|
521
|
-
|
|
586
|
+
parts.append(ref_expressions[i])
|
|
587
|
+
break
|
|
588
|
+
# join the if
|
|
589
|
+
exp = ",".join(map(str, parts))
|
|
522
590
|
# in case there is no default put ''
|
|
523
591
|
if not else_found:
|
|
524
592
|
exp += ",''"
|
|
525
|
-
#add the closing )
|
|
593
|
+
# add the closing )
|
|
526
594
|
for i in range(ifs):
|
|
527
595
|
exp += ")"
|
|
528
596
|
return exp
|
|
529
|
-
|
|
597
|
+
|
|
598
|
+
def get_empty_label(self):
|
|
599
|
+
return "."
|
|
600
|
+
|
|
530
601
|
def tricc_operation_if(self, ref_expressions):
|
|
531
602
|
return f"if({ref_expressions[0]},{ref_expressions[1]},{ref_expressions[2]})"
|
|
532
|
-
|
|
603
|
+
|
|
533
604
|
def tricc_operation_contains(self, ref_expressions):
|
|
534
|
-
return f"contains(
|
|
535
|
-
|
|
605
|
+
return f"contains({self.clean_coalesce(ref_expressions[0])}, {self.clean_coalesce(ref_expressions[1])})"
|
|
606
|
+
|
|
536
607
|
def tricc_operation_exists(self, ref_expressions):
|
|
537
608
|
parts = []
|
|
538
609
|
for ref in ref_expressions:
|
|
539
610
|
parts.append(self.tricc_operation_not_equal([self.tricc_operation_coalesce([ref, "''"]), "''"]))
|
|
540
611
|
return self.tricc_operation_and(parts)
|
|
541
|
-
|
|
612
|
+
|
|
542
613
|
def tricc_operation_cast_number(self, ref_expressions):
|
|
543
|
-
if isinstance(
|
|
614
|
+
if isinstance(
|
|
615
|
+
ref_expressions[0],
|
|
616
|
+
(
|
|
617
|
+
int,
|
|
618
|
+
float,
|
|
619
|
+
),
|
|
620
|
+
):
|
|
544
621
|
return f"{ref_expressions[0]}"
|
|
545
|
-
elif not ref_expressions or ref_expressions[0] ==
|
|
622
|
+
elif not ref_expressions or ref_expressions[0] == "":
|
|
546
623
|
logger.warning("empty cast number")
|
|
547
|
-
return
|
|
548
|
-
elif
|
|
624
|
+
return "0"
|
|
625
|
+
elif (
|
|
626
|
+
ref_expressions[0] == "true"
|
|
627
|
+
or ref_expressions[0] is True
|
|
628
|
+
or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]
|
|
629
|
+
):
|
|
549
630
|
return 1
|
|
550
|
-
elif
|
|
631
|
+
elif (
|
|
632
|
+
ref_expressions[0] == "false"
|
|
633
|
+
or ref_expressions[0] is False
|
|
634
|
+
or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
|
|
635
|
+
):
|
|
551
636
|
return 0
|
|
552
637
|
else:
|
|
553
638
|
return f"number({ref_expressions[0]})"
|
|
554
|
-
|
|
639
|
+
|
|
555
640
|
def tricc_operation_cast_integer(self, ref_expressions):
|
|
556
|
-
if isinstance(
|
|
641
|
+
if isinstance(
|
|
642
|
+
ref_expressions[0],
|
|
643
|
+
(
|
|
644
|
+
int,
|
|
645
|
+
float,
|
|
646
|
+
),
|
|
647
|
+
):
|
|
557
648
|
return f"{ref_expressions[0]}"
|
|
558
|
-
elif not ref_expressions or ref_expressions[0] ==
|
|
649
|
+
elif not ref_expressions or ref_expressions[0] == "":
|
|
559
650
|
logger.warning("empty cast number")
|
|
560
|
-
return
|
|
561
|
-
elif
|
|
651
|
+
return "0"
|
|
652
|
+
elif (
|
|
653
|
+
ref_expressions[0] == "true"
|
|
654
|
+
or ref_expressions[0] is True
|
|
655
|
+
or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]
|
|
656
|
+
):
|
|
562
657
|
return 1
|
|
563
|
-
elif
|
|
658
|
+
elif (
|
|
659
|
+
ref_expressions[0] == "false"
|
|
660
|
+
or ref_expressions[0] is False
|
|
661
|
+
or ref_expressions[0] == BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
|
|
662
|
+
):
|
|
564
663
|
return 0
|
|
565
664
|
else:
|
|
566
|
-
return f"int({ref_expressions[0]})"
|
|
665
|
+
return f"int({ref_expressions[0]})"
|
|
666
|
+
|
|
567
667
|
def tricc_operation_zscore(self, ref_expressions):
|
|
568
668
|
y, ll, m, s = self.get_zscore_params(ref_expressions)
|
|
569
669
|
# return ((Math.pow((y / m), l) - 1) / (s * l));
|
|
570
670
|
return f"(pow({y} div ({m}), {ll}) -1) div (({s}) div ({ll}))"
|
|
571
|
-
|
|
572
|
-
|
|
671
|
+
|
|
672
|
+
def tricc_operation_datetime_to_decimal(self, ref_expressions):
|
|
673
|
+
return f"decimal-date-time({ref_expressions[0]})"
|
|
674
|
+
|
|
675
|
+
def tricc_operation_round(self, ref_expressions):
|
|
676
|
+
return f"round({ref_expressions[0]})"
|
|
677
|
+
|
|
573
678
|
def tricc_operation_izscore(self, ref_expressions):
|
|
574
679
|
z, ll, m, s = self.get_zscore_params(ref_expressions)
|
|
575
680
|
# return (m * (z*s*l-1)^(1/l));
|
|
576
681
|
return f"pow({m} * ({z} * {s} * {ll} -1), 1 div {ll})"
|
|
577
|
-
|
|
682
|
+
|
|
578
683
|
def get_zscore_params(self, ref_expressions):
|
|
579
684
|
table = ref_expressions[0]
|
|
580
685
|
sex = clean_name(ref_expressions[1])
|
|
581
686
|
x = clean_name(ref_expressions[2])
|
|
582
687
|
yz = clean_name(ref_expressions[3])
|
|
583
|
-
ll = (
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
+ "]/l)"
|
|
589
|
-
)
|
|
590
|
-
m = (
|
|
591
|
-
f"number(instance({table})/root/item[sex={sex} and x_max>"
|
|
592
|
-
+ x
|
|
593
|
-
+ " and x_min<="
|
|
594
|
-
+ x
|
|
595
|
-
+ "]/m)"
|
|
596
|
-
)
|
|
597
|
-
s = (
|
|
598
|
-
f"number(instance({table})/root/item[sex={sex} and x_max>"
|
|
599
|
-
+ x
|
|
600
|
-
+ " and x_min<="
|
|
601
|
-
+ x
|
|
602
|
-
+ "]/s)"
|
|
603
|
-
)
|
|
604
|
-
return yz, ll, m, s
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
688
|
+
ll = f"number(instance({table})/root/item[sex={sex} and x_max>" + x + " and x_min<=" + x + "]/l)"
|
|
689
|
+
m = f"number(instance({table})/root/item[sex={sex} and x_max>" + x + " and x_min<=" + x + "]/m)"
|
|
690
|
+
s = f"number(instance({table})/root/item[sex={sex} and x_max>" + x + " and x_min<=" + x + "]/s)"
|
|
691
|
+
return yz, ll, m, s
|
|
692
|
+
|
|
608
693
|
# function update the calcualte in the XLSFORM format
|
|
609
694
|
# @param left part
|
|
610
|
-
# @param right part
|
|
695
|
+
# @param right part
|
|
611
696
|
def generate_xls_form_calculate(self, node, processed_nodes, stashed_nodes, calculates, **kwargs):
|
|
612
|
-
if is_ready_to_process(node, processed_nodes, strict=False) and process_reference(
|
|
697
|
+
if is_ready_to_process(node, processed_nodes, strict=False) and process_reference(
|
|
698
|
+
node,
|
|
699
|
+
processed_nodes,
|
|
700
|
+
calculates,
|
|
701
|
+
replace_reference=False,
|
|
702
|
+
codesystems=kwargs.get("codesystems", None),
|
|
703
|
+
):
|
|
613
704
|
if node not in processed_nodes:
|
|
614
|
-
if kwargs.get(
|
|
705
|
+
if kwargs.get("warn", True):
|
|
615
706
|
logger.debug("generation of calculate for node {}".format(node.get_name()))
|
|
616
|
-
if
|
|
617
|
-
|
|
707
|
+
if (
|
|
708
|
+
hasattr(node, "expression")
|
|
709
|
+
and (node.expression is None)
|
|
710
|
+
and issubclass(node.__class__, TriccNodeCalculateBase)
|
|
711
|
+
):
|
|
712
|
+
node.expression = get_node_expressions(
|
|
713
|
+
node, processed_nodes, process=kwargs.get("process", "main ")
|
|
714
|
+
)
|
|
618
715
|
# continue walk
|
|
619
|
-
if issubclass(
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if is_ready_to_process(node, processed_nodes, strict=False) and process_reference(node, processed_nodes, calculates, replace_reference=False, codesystems= kwargs.get('codesystems', None)):
|
|
629
|
-
if node not in processed_nodes:
|
|
630
|
-
if issubclass(node.__class__, TriccRhombusMixIn) and isinstance(node.reference, str):
|
|
631
|
-
logger.warning("node {} still using the reference string".format(node.get_name()))
|
|
632
|
-
if issubclass(node.__class__, TriccNodeInputModel):
|
|
633
|
-
# we don't overright if define in the diagram
|
|
634
|
-
if node.constraint is None:
|
|
635
|
-
if isinstance(node, TriccNodeSelectMultiple):
|
|
636
|
-
node.constraint = or_join(
|
|
637
|
-
[
|
|
638
|
-
TriccOperation(TriccOperator.EQUAL, ['$this', TriccStatic('opt_none')]),
|
|
639
|
-
TriccOperation(TriccOperator.NOT, [
|
|
640
|
-
TriccOperation(TriccOperator.SELECTED,
|
|
641
|
-
[
|
|
642
|
-
'$this', TriccStatic('opt_none')
|
|
643
|
-
])
|
|
644
|
-
])
|
|
645
|
-
]
|
|
646
|
-
)#'.=\'opt_none\' or not(selected(.,\'opt_none\'))'
|
|
647
|
-
node.constraint_message = '**None** cannot be selected together with choice.'
|
|
648
|
-
elif node.tricc_type in (TriccNodeType.integer, TriccNodeType.decimal):
|
|
649
|
-
constraints = []
|
|
650
|
-
constraints_min = ''
|
|
651
|
-
constraints_max = ''
|
|
652
|
-
if node.min is not None and node.min != '':
|
|
653
|
-
constraints.append(TriccOperation(TriccOperator.MORE_OR_EQUAL, ['$this', node.min]))
|
|
654
|
-
constraints_min= "The minimun value is {0}.".format(node.min)
|
|
655
|
-
if node.max is not None and node.max != '':
|
|
656
|
-
constraints.append(TriccOperation(TriccOperator.LESS_OR_EQUAL, ['$this', node.max]))
|
|
657
|
-
constraints_max="The maximum value is {0}.".format(node.max)
|
|
658
|
-
if len(constraints) > 1:
|
|
659
|
-
node.constraint = TriccOperation(TriccOperator.AND, constraints)
|
|
660
|
-
node.constraint_message = (constraints_min + " " + constraints_max).strip()
|
|
661
|
-
elif len(constraints) == 1:
|
|
662
|
-
node.constraint = constraints[0]
|
|
663
|
-
node.constraint_message = (constraints_min + " " + constraints_max).strip()
|
|
664
|
-
# continue walk
|
|
716
|
+
if issubclass(
|
|
717
|
+
node.__class__,
|
|
718
|
+
(
|
|
719
|
+
TriccNodeDisplayModel,
|
|
720
|
+
TriccNodeDisplayCalculateBase,
|
|
721
|
+
TriccNodeEnd,
|
|
722
|
+
),
|
|
723
|
+
):
|
|
724
|
+
set_last_version_false(node, processed_nodes)
|
|
665
725
|
return True
|
|
666
726
|
return False
|
|
667
727
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
728
|
+
def get_tricc_operation_operand(self, r, coalesce_fallback="''"):
|
|
729
|
+
# function transform an object to XLSFORM value
|
|
730
|
+
# @param r reference to be translated
|
|
671
731
|
if isinstance(r, TriccOperation):
|
|
672
|
-
return self.get_tricc_operation_expression(r)
|
|
732
|
+
return self.get_tricc_operation_expression(r)
|
|
733
|
+
elif isinstance(r, (TriccStatic, str, int, float)):
|
|
734
|
+
return get_export_name(r)
|
|
673
735
|
elif isinstance(r, TriccReference):
|
|
674
|
-
logger.warning(f"reference still used in
|
|
675
|
-
return f"${{{get_export_name(r.value)}}}"
|
|
676
|
-
|
|
677
|
-
if isinstance(r.value, bool) :#or r.value in ('true', 'false')
|
|
678
|
-
return BOOLEAN_MAP[str(TRICC_TRUE_VALUE)] if r.value else BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
|
|
679
|
-
if r.value == TRICC_TRUE_VALUE:
|
|
680
|
-
return BOOLEAN_MAP[str(TRICC_TRUE_VALUE)]
|
|
681
|
-
if r.value == TRICC_FALSE_VALUE:
|
|
682
|
-
return BOOLEAN_MAP[str(TRICC_FALSE_VALUE)]
|
|
683
|
-
if isinstance(r.value, str):
|
|
684
|
-
return f"'{r.value}'"
|
|
685
|
-
else:
|
|
686
|
-
return str(r.value)
|
|
687
|
-
elif isinstance(r, str):
|
|
688
|
-
return f"{r}"
|
|
689
|
-
elif isinstance(r, (int, float)):
|
|
690
|
-
return str(r)
|
|
736
|
+
logger.warning(f"reference `{r.value}` still used in a calculate")
|
|
737
|
+
return f"${{{get_export_name(r.value)}}}"
|
|
738
|
+
|
|
691
739
|
elif isinstance(r, TriccNodeSelectOption):
|
|
692
|
-
logger.
|
|
693
|
-
return
|
|
740
|
+
logger.debug(f"select option {r.get_name()} from {r.select.get_name()} was used as a reference")
|
|
741
|
+
return get_export_name(r)
|
|
694
742
|
elif issubclass(r.__class__, TriccNodeInputModel):
|
|
695
743
|
return f"coalesce(${{{get_export_name(r)}}},{coalesce_fallback})"
|
|
696
744
|
elif issubclass(r.__class__, TriccNodeBaseModel):
|
|
@@ -699,4 +747,34 @@ class XLSFormStrategy(BaseOutPutStrategy):
|
|
|
699
747
|
raise NotImplementedError(f"This type of node {r.__class__} is not supported within an operation")
|
|
700
748
|
|
|
701
749
|
def tricc_operation_concatenate(self, ref_expressions):
|
|
702
|
-
return f"concat({','.join(ref_expressions)})"
|
|
750
|
+
return f"concat({','.join(map(str, ref_expressions))})"
|
|
751
|
+
|
|
752
|
+
def validate(self):
|
|
753
|
+
"""Validate the generated XLS form using pyxform."""
|
|
754
|
+
try:
|
|
755
|
+
# Determine the XLS file path
|
|
756
|
+
if self.project.start_pages["main"].root.form_id is not None:
|
|
757
|
+
form_id = str(self.project.start_pages["main"].root.form_id)
|
|
758
|
+
xls_path = os.path.join(self.output_path, form_id + ".xlsx")
|
|
759
|
+
|
|
760
|
+
if not os.path.exists(xls_path):
|
|
761
|
+
logger.error(f"XLS file not found: {xls_path}")
|
|
762
|
+
return False
|
|
763
|
+
|
|
764
|
+
# Validate using pyxform
|
|
765
|
+
survey = create_survey_from_xls(xls_path)
|
|
766
|
+
xml_output = survey.to_xml()
|
|
767
|
+
|
|
768
|
+
# Check if XML was generated successfully
|
|
769
|
+
if xml_output and len(xml_output.strip()) > 0:
|
|
770
|
+
logger.info("XLSForm validation successful")
|
|
771
|
+
return True
|
|
772
|
+
else:
|
|
773
|
+
logger.error("XLSForm validation failed: Empty XML output")
|
|
774
|
+
return False
|
|
775
|
+
else:
|
|
776
|
+
logger.error("Form ID not found for validation")
|
|
777
|
+
return False
|
|
778
|
+
except Exception as e:
|
|
779
|
+
logger.error(f"XLSForm validation failed: {str(e)}")
|
|
780
|
+
return False
|