jsonata-python 0.1.0__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.
- jsonata/__init__.py +10 -0
- jsonata/cli/__init__.py +0 -0
- jsonata/cli/__main__.py +242 -0
- jsonata/constants.py +68 -0
- jsonata/datetimeutils.py +1144 -0
- jsonata/functions.py +2179 -0
- jsonata/jexception.py +232 -0
- jsonata/jsonata.py +2045 -0
- jsonata/parser.py +1397 -0
- jsonata/signature.py +441 -0
- jsonata/timebox.py +89 -0
- jsonata/tokenizer.py +306 -0
- jsonata/utils.py +150 -0
- jsonata_python-0.1.0.dist-info/METADATA +338 -0
- jsonata_python-0.1.0.dist-info/RECORD +17 -0
- jsonata_python-0.1.0.dist-info/WHEEL +4 -0
- jsonata_python-0.1.0.dist-info/licenses/LICENSE +202 -0
jsonata/functions.py
ADDED
|
@@ -0,0 +1,2179 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright Robert Yokota
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Derived from the following code:
|
|
17
|
+
#
|
|
18
|
+
# Project name: jsonata-java
|
|
19
|
+
# Copyright Dashjoin GmbH. https://dashjoin.com
|
|
20
|
+
# Licensed under the Apache License, Version 2.0 (the "License")
|
|
21
|
+
#
|
|
22
|
+
# Project name: elementpath
|
|
23
|
+
# Copyright (c), 2018-2021, SISSA (Scuola Internazionale Superiore di Studi Avanzati)
|
|
24
|
+
# This project is licensed under the MIT License, see LICENSE
|
|
25
|
+
|
|
26
|
+
import base64
|
|
27
|
+
import datetime
|
|
28
|
+
import decimal
|
|
29
|
+
import functools
|
|
30
|
+
import inspect
|
|
31
|
+
import json
|
|
32
|
+
import math
|
|
33
|
+
import random
|
|
34
|
+
import re
|
|
35
|
+
import sys
|
|
36
|
+
import unicodedata
|
|
37
|
+
import urllib.parse
|
|
38
|
+
from dataclasses import dataclass
|
|
39
|
+
from typing import Any, AnyStr, Mapping, NoReturn, Optional, Sequence, Callable, Type, Union
|
|
40
|
+
|
|
41
|
+
from jsonata import datetimeutils, jexception, parser, utils
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Functions:
|
|
45
|
+
|
|
46
|
+
#
|
|
47
|
+
# Sum function
|
|
48
|
+
# @param {Object} args - Arguments
|
|
49
|
+
# @returns {number} Total value of arguments
|
|
50
|
+
#
|
|
51
|
+
@staticmethod
|
|
52
|
+
def sum(args: Optional[Sequence[float]]) -> Optional[float]:
|
|
53
|
+
# undefined inputs always return undefined
|
|
54
|
+
if args is None:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
return sum(args)
|
|
58
|
+
|
|
59
|
+
#
|
|
60
|
+
# Count function
|
|
61
|
+
# @param {Object} args - Arguments
|
|
62
|
+
# @returns {number} Number of elements in the array
|
|
63
|
+
#
|
|
64
|
+
@staticmethod
|
|
65
|
+
def count(args: Optional[Sequence[Any]]) -> float:
|
|
66
|
+
# undefined inputs always return undefined
|
|
67
|
+
if args is None:
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
return len(args)
|
|
71
|
+
|
|
72
|
+
#
|
|
73
|
+
# Max function
|
|
74
|
+
# @param {Object} args - Arguments
|
|
75
|
+
# @returns {number} Max element in the array
|
|
76
|
+
#
|
|
77
|
+
@staticmethod
|
|
78
|
+
def max(args: Optional[Sequence[float]]) -> Optional[float]:
|
|
79
|
+
# undefined inputs always return undefined
|
|
80
|
+
if args is None or len(args) == 0:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
return max(args)
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# Min function
|
|
87
|
+
# @param {Object} args - Arguments
|
|
88
|
+
# @returns {number} Min element in the array
|
|
89
|
+
#
|
|
90
|
+
@staticmethod
|
|
91
|
+
def min(args: Optional[Sequence[float]]) -> Optional[float]:
|
|
92
|
+
# undefined inputs always return undefined
|
|
93
|
+
if args is None or len(args) == 0:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
return min(args)
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# Average function
|
|
100
|
+
# @param {Object} args - Arguments
|
|
101
|
+
# @returns {number} Average element in the array
|
|
102
|
+
#
|
|
103
|
+
@staticmethod
|
|
104
|
+
def average(args: Optional[Sequence[float]]) -> Optional[float]:
|
|
105
|
+
# undefined inputs always return undefined
|
|
106
|
+
if args is None or len(args) == 0:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
return sum(args) / len(args)
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# Stringify arguments
|
|
113
|
+
# @param {Object} arg - Arguments
|
|
114
|
+
# @param {boolean} [prettify] - Pretty print the result
|
|
115
|
+
# @returns {String} String from arguments
|
|
116
|
+
#
|
|
117
|
+
@staticmethod
|
|
118
|
+
def string(arg: Optional[Any], prettify: Optional[bool]) -> Optional[str]:
|
|
119
|
+
|
|
120
|
+
if isinstance(arg, utils.Utils.JList):
|
|
121
|
+
if arg.outer_wrapper:
|
|
122
|
+
arg = arg[0]
|
|
123
|
+
|
|
124
|
+
if arg is None:
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
# see https://docs.jsonata.org/string-functions#string: Strings are unchanged
|
|
128
|
+
if isinstance(arg, str):
|
|
129
|
+
return str(arg)
|
|
130
|
+
|
|
131
|
+
return Functions._string(arg, prettify is not None and prettify)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def _string(arg: Any, prettify: bool) -> str:
|
|
135
|
+
from jsonata import jsonata
|
|
136
|
+
|
|
137
|
+
if isinstance(arg, (jsonata.Jsonata.JFunction, parser.Parser.Symbol)):
|
|
138
|
+
return ""
|
|
139
|
+
|
|
140
|
+
if prettify:
|
|
141
|
+
return json.dumps(arg, cls=Functions.Encoder, indent=" ")
|
|
142
|
+
else:
|
|
143
|
+
return json.dumps(arg, cls=Functions.Encoder, separators=(',', ':'))
|
|
144
|
+
|
|
145
|
+
class Encoder(json.JSONEncoder):
|
|
146
|
+
def encode(self, arg):
|
|
147
|
+
if not isinstance(arg, bool) and isinstance(arg, (int, float)):
|
|
148
|
+
d = decimal.Decimal(arg)
|
|
149
|
+
res = Functions.remove_exponent(d, decimal.Context(prec=15))
|
|
150
|
+
return str(res).lower()
|
|
151
|
+
|
|
152
|
+
return super().encode(arg)
|
|
153
|
+
|
|
154
|
+
def default(self, arg):
|
|
155
|
+
from jsonata import jsonata
|
|
156
|
+
|
|
157
|
+
if arg is utils.Utils.NULL_VALUE:
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
if isinstance(arg, (jsonata.Jsonata.JFunction, parser.Parser.Symbol)):
|
|
161
|
+
return ""
|
|
162
|
+
|
|
163
|
+
return super().default(arg)
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def remove_exponent(d: decimal.Decimal, ctx: decimal.Context) -> decimal.Decimal:
|
|
167
|
+
# Adapted from https://docs.python.org/3/library/decimal.html#decimal-faq
|
|
168
|
+
if d == d.to_integral():
|
|
169
|
+
try:
|
|
170
|
+
return d.quantize(decimal.Decimal(1), context=ctx)
|
|
171
|
+
except decimal.InvalidOperation:
|
|
172
|
+
pass
|
|
173
|
+
return d.normalize(ctx)
|
|
174
|
+
|
|
175
|
+
#
|
|
176
|
+
# Validate input data types.
|
|
177
|
+
# This will make sure that all input data can be processed.
|
|
178
|
+
#
|
|
179
|
+
# @param arg
|
|
180
|
+
# @return
|
|
181
|
+
#
|
|
182
|
+
@staticmethod
|
|
183
|
+
def validate_input(arg: Optional[Any]) -> None:
|
|
184
|
+
from jsonata import jsonata
|
|
185
|
+
|
|
186
|
+
if arg is None or arg is utils.Utils.NULL_VALUE:
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
if isinstance(arg, (jsonata.Jsonata.JFunction, parser.Parser.Symbol)):
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
if isinstance(arg, bool):
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
if isinstance(arg, (int, float)):
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
if isinstance(arg, str):
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
if isinstance(arg, dict):
|
|
202
|
+
for k, v in arg.items():
|
|
203
|
+
Functions.validate_input(k)
|
|
204
|
+
Functions.validate_input(v)
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
if isinstance(arg, list):
|
|
208
|
+
for v in arg:
|
|
209
|
+
Functions.validate_input(v)
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
# Throw error for unknown types
|
|
213
|
+
raise ValueError(
|
|
214
|
+
"Only JSON types (values, Map, List) are allowed as input. Unsupported type: " + str(type(arg)))
|
|
215
|
+
|
|
216
|
+
#
|
|
217
|
+
# Create substring based on character number and length
|
|
218
|
+
# @param {String} str - String to evaluate
|
|
219
|
+
# @param {Integer} start - Character number to start substring
|
|
220
|
+
# @param {Integer} [length] - Number of characters in substring
|
|
221
|
+
# @returns {string|*} Substring
|
|
222
|
+
#
|
|
223
|
+
@staticmethod
|
|
224
|
+
def substring(string: Optional[str], _start: Optional[float], _length: Optional[float]) -> Optional[str]:
|
|
225
|
+
# undefined inputs always return undefined
|
|
226
|
+
if string is None:
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
start = int(_start) if _start is not None else None
|
|
230
|
+
length = int(_length) if _length is not None else None
|
|
231
|
+
|
|
232
|
+
# not used: var strArray = stringToArray(string)
|
|
233
|
+
str_length = len(string)
|
|
234
|
+
|
|
235
|
+
if str_length + start < 0:
|
|
236
|
+
start = 0
|
|
237
|
+
|
|
238
|
+
if length is not None:
|
|
239
|
+
if length <= 0:
|
|
240
|
+
return ""
|
|
241
|
+
return Functions.substr(string, start, length)
|
|
242
|
+
|
|
243
|
+
return Functions.substr(string, start, str_length)
|
|
244
|
+
|
|
245
|
+
#
|
|
246
|
+
# Source = Jsonata4Java JSONataUtils.substr
|
|
247
|
+
# @param str
|
|
248
|
+
# @param start Location at which to begin extracting characters. If a negative
|
|
249
|
+
# number is given, it is treated as strLength - start where
|
|
250
|
+
# strLength is the length of the string. For example,
|
|
251
|
+
# str.substr(-3) is treated as str.substr(str.length - 3)
|
|
252
|
+
# @param length The number of characters to extract. If this argument is null,
|
|
253
|
+
# all the characters from start to the end of the string are
|
|
254
|
+
# extracted.
|
|
255
|
+
# @return A new string containing the extracted section of the given string. If
|
|
256
|
+
# length is 0 or a negative number, an empty string is returned.
|
|
257
|
+
#
|
|
258
|
+
@staticmethod
|
|
259
|
+
def substr(string: Optional[str], start: Optional[int], length: Optional[int]) -> str:
|
|
260
|
+
|
|
261
|
+
# below has to convert start and length for emojis and unicode
|
|
262
|
+
orig_len = len(string)
|
|
263
|
+
|
|
264
|
+
str_data = string
|
|
265
|
+
str_len = len(str_data)
|
|
266
|
+
if start >= str_len:
|
|
267
|
+
return ""
|
|
268
|
+
# If start is negative, substr() uses it as a character index from the
|
|
269
|
+
# end of the string; the index of the last character is -1.
|
|
270
|
+
start = start if start >= 0 else (0 if (str_len + start) < 0 else str_len + start)
|
|
271
|
+
if start < 0:
|
|
272
|
+
start = 0 # If start is negative and abs(start) is larger than the length of the
|
|
273
|
+
# string, substr() uses 0 as the start index.
|
|
274
|
+
# If length is omitted, substr() extracts characters to the end of the
|
|
275
|
+
# string.
|
|
276
|
+
if length is None:
|
|
277
|
+
length = len(str_data)
|
|
278
|
+
elif length < 0:
|
|
279
|
+
# If length is 0 or negative, substr() returns an empty string.
|
|
280
|
+
return ""
|
|
281
|
+
elif length > len(str_data):
|
|
282
|
+
length = len(str_data)
|
|
283
|
+
|
|
284
|
+
if start >= 0:
|
|
285
|
+
# If start is positive and is greater than or equal to the length of
|
|
286
|
+
# the string, substr() returns an empty string.
|
|
287
|
+
if start >= orig_len:
|
|
288
|
+
return ""
|
|
289
|
+
|
|
290
|
+
# collect length characters (unless it reaches the end of the string
|
|
291
|
+
# first, in which case it will return fewer)
|
|
292
|
+
end = start + length
|
|
293
|
+
if end > orig_len:
|
|
294
|
+
end = orig_len
|
|
295
|
+
|
|
296
|
+
return str_data[start:end]
|
|
297
|
+
|
|
298
|
+
#
|
|
299
|
+
# Create substring up until a character
|
|
300
|
+
# @param {String} str - String to evaluate
|
|
301
|
+
# @param {String} chars - Character to define substring boundary
|
|
302
|
+
# @returns {*} Substring
|
|
303
|
+
#
|
|
304
|
+
@staticmethod
|
|
305
|
+
def substring_before(string: Optional[str], chars: Optional[str]) -> Optional[str]:
|
|
306
|
+
# undefined inputs always return undefined
|
|
307
|
+
if string is None:
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
if chars is None:
|
|
311
|
+
return string
|
|
312
|
+
|
|
313
|
+
pos = string.find(chars)
|
|
314
|
+
if pos > -1:
|
|
315
|
+
return string[0:pos]
|
|
316
|
+
else:
|
|
317
|
+
return string
|
|
318
|
+
|
|
319
|
+
#
|
|
320
|
+
# Create substring after a character
|
|
321
|
+
# @param {String} str - String to evaluate
|
|
322
|
+
# @param {String} chars - Character to define substring boundary
|
|
323
|
+
# @returns {*} Substring
|
|
324
|
+
#
|
|
325
|
+
@staticmethod
|
|
326
|
+
def substring_after(string: Optional[str], chars: Optional[str]) -> Optional[str]:
|
|
327
|
+
# undefined inputs always return undefined
|
|
328
|
+
if string is None:
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
pos = string.find(chars)
|
|
332
|
+
if pos > -1:
|
|
333
|
+
return string[pos + len(chars):]
|
|
334
|
+
else:
|
|
335
|
+
return string
|
|
336
|
+
|
|
337
|
+
#
|
|
338
|
+
# Lowercase a string
|
|
339
|
+
# @param {String} str - String to evaluate
|
|
340
|
+
# @returns {string} Lowercase string
|
|
341
|
+
#
|
|
342
|
+
@staticmethod
|
|
343
|
+
def lowercase(string: Optional[str]) -> Optional[str]:
|
|
344
|
+
# undefined inputs always return undefined
|
|
345
|
+
if string is None:
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
return string.casefold()
|
|
349
|
+
|
|
350
|
+
#
|
|
351
|
+
# Uppercase a string
|
|
352
|
+
# @param {String} str - String to evaluate
|
|
353
|
+
# @returns {string} Uppercase string
|
|
354
|
+
#
|
|
355
|
+
@staticmethod
|
|
356
|
+
def uppercase(string: Optional[str]) -> Optional[str]:
|
|
357
|
+
# undefined inputs always return undefined
|
|
358
|
+
if string is None:
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
return string.upper()
|
|
362
|
+
|
|
363
|
+
#
|
|
364
|
+
# length of a string
|
|
365
|
+
# @param {String} str - string
|
|
366
|
+
# @returns {Number} The number of characters in the string
|
|
367
|
+
#
|
|
368
|
+
@staticmethod
|
|
369
|
+
def length(string: Optional[str]) -> Optional[int]:
|
|
370
|
+
# undefined inputs always return undefined
|
|
371
|
+
if string is None:
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
return len(string)
|
|
375
|
+
|
|
376
|
+
#
|
|
377
|
+
# Normalize and trim whitespace within a string
|
|
378
|
+
# @param {string} str - string to be trimmed
|
|
379
|
+
# @returns {string} - trimmed string
|
|
380
|
+
#
|
|
381
|
+
@staticmethod
|
|
382
|
+
def trim(string: Optional[str]) -> Optional[str]:
|
|
383
|
+
# undefined inputs always return undefined
|
|
384
|
+
if string is None:
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
if len(string) == 0:
|
|
388
|
+
return ""
|
|
389
|
+
|
|
390
|
+
# normalize whitespace
|
|
391
|
+
result = re.sub("[ \t\n\r]+", " ", string)
|
|
392
|
+
if result[0] == ' ':
|
|
393
|
+
# strip leading space
|
|
394
|
+
result = result[1:]
|
|
395
|
+
|
|
396
|
+
if result == "":
|
|
397
|
+
return ""
|
|
398
|
+
|
|
399
|
+
if result[len(result) - 1] == ' ':
|
|
400
|
+
# strip trailing space
|
|
401
|
+
result = result[0:len(result) - 1]
|
|
402
|
+
return result
|
|
403
|
+
|
|
404
|
+
#
|
|
405
|
+
# Pad a string to a minimum width by adding characters to the start or end
|
|
406
|
+
# @param {string} str - string to be padded
|
|
407
|
+
# @param {number} width - the minimum width; +ve pads to the right, -ve pads to the left
|
|
408
|
+
# @param {string} [char] - the pad character(s); defaults to ' '
|
|
409
|
+
# @returns {string} - padded string
|
|
410
|
+
#
|
|
411
|
+
@staticmethod
|
|
412
|
+
def pad(string: Optional[str], width: Optional[int], _char: Optional[str]) -> Optional[str]:
|
|
413
|
+
# undefined inputs always return undefined
|
|
414
|
+
if string is None:
|
|
415
|
+
return None
|
|
416
|
+
|
|
417
|
+
if _char is None or len(_char) == 0:
|
|
418
|
+
_char = " "
|
|
419
|
+
|
|
420
|
+
result = None
|
|
421
|
+
|
|
422
|
+
if width < 0:
|
|
423
|
+
result = Functions.left_pad(string, -width, _char)
|
|
424
|
+
else:
|
|
425
|
+
result = Functions.right_pad(string, width, _char)
|
|
426
|
+
return result
|
|
427
|
+
|
|
428
|
+
# Source: Jsonata4Java PadFunction
|
|
429
|
+
@staticmethod
|
|
430
|
+
def left_pad(string: Optional[str], size: Optional[int], pad_str: Optional[str]) -> Optional[str]:
|
|
431
|
+
if string is None:
|
|
432
|
+
return None
|
|
433
|
+
if pad_str is None:
|
|
434
|
+
pad_str = " "
|
|
435
|
+
|
|
436
|
+
str_data = string
|
|
437
|
+
str_len = len(str_data)
|
|
438
|
+
|
|
439
|
+
pad_data = pad_str
|
|
440
|
+
pad_len = len(pad_data)
|
|
441
|
+
|
|
442
|
+
if pad_len == 0:
|
|
443
|
+
pad_str = " "
|
|
444
|
+
pads = size - str_len
|
|
445
|
+
if pads <= 0:
|
|
446
|
+
return string
|
|
447
|
+
padding = ""
|
|
448
|
+
i = 0
|
|
449
|
+
while i < pads + 1:
|
|
450
|
+
padding += pad_str
|
|
451
|
+
i += 1
|
|
452
|
+
return Functions.substr(padding, 0, pads) + string
|
|
453
|
+
|
|
454
|
+
# Source: Jsonata4Java PadFunction
|
|
455
|
+
@staticmethod
|
|
456
|
+
def right_pad(string: Optional[str], size: Optional[int], pad_str: Optional[str]) -> Optional[str]:
|
|
457
|
+
if string is None:
|
|
458
|
+
return None
|
|
459
|
+
if pad_str is None:
|
|
460
|
+
pad_str = " "
|
|
461
|
+
|
|
462
|
+
str_data = string
|
|
463
|
+
str_len = len(str_data)
|
|
464
|
+
|
|
465
|
+
pad_data = pad_str
|
|
466
|
+
pad_len = len(pad_data)
|
|
467
|
+
|
|
468
|
+
if pad_len == 0:
|
|
469
|
+
pad_str = " "
|
|
470
|
+
pads = size - str_len
|
|
471
|
+
if pads <= 0:
|
|
472
|
+
return string
|
|
473
|
+
padding = ""
|
|
474
|
+
i = 0
|
|
475
|
+
while i < pads + 1:
|
|
476
|
+
padding += pad_str
|
|
477
|
+
i += 1
|
|
478
|
+
return string + Functions.substr(padding, 0, pads)
|
|
479
|
+
|
|
480
|
+
@dataclass
|
|
481
|
+
class RegexpMatch:
|
|
482
|
+
match: str
|
|
483
|
+
index: int
|
|
484
|
+
groups: Sequence[AnyStr]
|
|
485
|
+
|
|
486
|
+
#
|
|
487
|
+
# Evaluate the matcher function against the str arg
|
|
488
|
+
#
|
|
489
|
+
# @param {*} matcher - matching function (native or lambda)
|
|
490
|
+
# @param {string} str - the string to match against
|
|
491
|
+
# @returns {object} - structure that represents the match(es)
|
|
492
|
+
#
|
|
493
|
+
@staticmethod
|
|
494
|
+
def evaluate_matcher(matcher: Optional[re.Pattern], string: Optional[str]) -> list[RegexpMatch]:
|
|
495
|
+
res = []
|
|
496
|
+
matches = matcher.finditer(string)
|
|
497
|
+
for m in matches:
|
|
498
|
+
groups = []
|
|
499
|
+
# Collect the groups
|
|
500
|
+
g = 1
|
|
501
|
+
while g <= len(m.groups()):
|
|
502
|
+
groups.append(m.group(g))
|
|
503
|
+
g += 1
|
|
504
|
+
|
|
505
|
+
rm = Functions.RegexpMatch(m.group(), m.start(), groups)
|
|
506
|
+
rm.groups = groups
|
|
507
|
+
res.append(rm)
|
|
508
|
+
return res
|
|
509
|
+
|
|
510
|
+
#
|
|
511
|
+
# Tests if the str contains the token
|
|
512
|
+
# @param {String} str - string to test
|
|
513
|
+
# @param {String} token - substring or regex to find
|
|
514
|
+
# @returns {Boolean} - true if str contains token
|
|
515
|
+
#
|
|
516
|
+
@staticmethod
|
|
517
|
+
def contains(string: Optional[str], token: Union[None, str, re.Pattern]) -> Optional[bool]:
|
|
518
|
+
# undefined inputs always return undefined
|
|
519
|
+
if string is None:
|
|
520
|
+
return None
|
|
521
|
+
|
|
522
|
+
result = False
|
|
523
|
+
|
|
524
|
+
if isinstance(token, str):
|
|
525
|
+
result = (string.find(str(token)) != - 1)
|
|
526
|
+
elif isinstance(token, re.Pattern):
|
|
527
|
+
matches = Functions.evaluate_matcher(token, string)
|
|
528
|
+
# if (dbg) System.out.println("match = "+matches)
|
|
529
|
+
# result = (typeof matches !== 'undefined')
|
|
530
|
+
# throw new Error("regexp not impl"); //result = false
|
|
531
|
+
result = len(matches) > 0
|
|
532
|
+
else:
|
|
533
|
+
raise RuntimeError("unknown type to match: " + str(token))
|
|
534
|
+
|
|
535
|
+
return result
|
|
536
|
+
|
|
537
|
+
#
|
|
538
|
+
# Match a string with a regex returning an array of object containing details of each match
|
|
539
|
+
# @param {String} str - string
|
|
540
|
+
# @param {String} regex - the regex applied to the string
|
|
541
|
+
# @param {Integer} [limit] - max number of matches to return
|
|
542
|
+
# @returns {Array} The array of match objects
|
|
543
|
+
#
|
|
544
|
+
@staticmethod
|
|
545
|
+
def match_(string: Optional[str], regex: Optional[re.Pattern], limit: Optional[int]) -> Optional[list[dict]]:
|
|
546
|
+
# undefined inputs always return undefined
|
|
547
|
+
if string is None:
|
|
548
|
+
return None
|
|
549
|
+
|
|
550
|
+
# limit, if specified, must be a non-negative number
|
|
551
|
+
if limit is not None and limit < 0:
|
|
552
|
+
raise jexception.JException("D3040", -1, limit)
|
|
553
|
+
|
|
554
|
+
result = utils.Utils.create_sequence()
|
|
555
|
+
matches = Functions.evaluate_matcher(regex, string)
|
|
556
|
+
max = sys.maxsize
|
|
557
|
+
if limit is not None:
|
|
558
|
+
max = limit
|
|
559
|
+
|
|
560
|
+
for i, rm in enumerate(matches):
|
|
561
|
+
m = {"match": rm.match, "index": rm.index, "groups": rm.groups}
|
|
562
|
+
# Convert to JSON map:
|
|
563
|
+
result.append(m)
|
|
564
|
+
if i >= max:
|
|
565
|
+
break
|
|
566
|
+
return result
|
|
567
|
+
|
|
568
|
+
#
|
|
569
|
+
# Join an array of strings
|
|
570
|
+
# @param {Array} strs - array of string
|
|
571
|
+
# @param {String} [separator] - the token that splits the string
|
|
572
|
+
# @returns {String} The concatenated string
|
|
573
|
+
#
|
|
574
|
+
@staticmethod
|
|
575
|
+
def join(strs: Optional[Sequence[str]], separator: Optional[str]) -> Optional[str]:
|
|
576
|
+
# undefined inputs always return undefined
|
|
577
|
+
if strs is None:
|
|
578
|
+
return None
|
|
579
|
+
|
|
580
|
+
# if separator is not specified, default to empty string
|
|
581
|
+
if separator is None:
|
|
582
|
+
separator = ""
|
|
583
|
+
|
|
584
|
+
return separator.join(strs)
|
|
585
|
+
|
|
586
|
+
@staticmethod
|
|
587
|
+
def safe_replacement(in_: str) -> str:
|
|
588
|
+
result = in_
|
|
589
|
+
|
|
590
|
+
# Replace "$<num>" with "\<num>" for Python regex
|
|
591
|
+
result = re.sub(r"\$(\d+)", r"\\g<\g<1>>", result)
|
|
592
|
+
|
|
593
|
+
# Replace "$$" with "$"
|
|
594
|
+
result = re.sub("\\$\\$", "$", result)
|
|
595
|
+
|
|
596
|
+
return result
|
|
597
|
+
|
|
598
|
+
#
|
|
599
|
+
# Safe replaceAll
|
|
600
|
+
#
|
|
601
|
+
# In Java, non-existing groups cause an exception.
|
|
602
|
+
# Ignore these non-existing groups (replace with "")
|
|
603
|
+
#
|
|
604
|
+
# @param s
|
|
605
|
+
# @param pattern
|
|
606
|
+
# @param replacement
|
|
607
|
+
# @return
|
|
608
|
+
#
|
|
609
|
+
@staticmethod
|
|
610
|
+
def safe_replace_all(s: Optional[str], pattern: re.Pattern, _replacement: Optional[Any]) -> Optional[str]:
|
|
611
|
+
|
|
612
|
+
if not (isinstance(_replacement, str)):
|
|
613
|
+
return Functions.safe_replace_all_fn(s, pattern, _replacement)
|
|
614
|
+
|
|
615
|
+
replacement = str(_replacement)
|
|
616
|
+
|
|
617
|
+
replacement = Functions.safe_replacement(replacement)
|
|
618
|
+
r = None
|
|
619
|
+
for i in range(0, 10):
|
|
620
|
+
try:
|
|
621
|
+
r = re.sub(pattern, replacement, s)
|
|
622
|
+
break
|
|
623
|
+
except Exception as e:
|
|
624
|
+
msg = str(e)
|
|
625
|
+
|
|
626
|
+
# Message we understand needs to be:
|
|
627
|
+
# invalid group reference <g> at position <p>
|
|
628
|
+
m = re.match(r"invalid group reference (\d+) at position (\d+)", msg)
|
|
629
|
+
|
|
630
|
+
if m is None:
|
|
631
|
+
raise e
|
|
632
|
+
|
|
633
|
+
g = m.group(1)
|
|
634
|
+
suffix = g[-1]
|
|
635
|
+
prefix = g[:-1]
|
|
636
|
+
# Try capturing a smaller numbered group, e.g. "\g<1>2" instead of "\g<12>"
|
|
637
|
+
replace = "" if len(prefix) == 0 else r"\g<" + prefix + ">" + suffix
|
|
638
|
+
|
|
639
|
+
# Adjust replacement to remove the non-existing group
|
|
640
|
+
replacement = replacement.replace(r"\g<" + g + ">", replace)
|
|
641
|
+
return r
|
|
642
|
+
|
|
643
|
+
#
|
|
644
|
+
# Converts Java MatchResult to the Jsonata object format
|
|
645
|
+
# @param mr
|
|
646
|
+
# @return
|
|
647
|
+
#
|
|
648
|
+
@staticmethod
|
|
649
|
+
def to_jsonata_match(mr: re.Match[str]) -> dict[str, str]:
|
|
650
|
+
obj = {"match": mr.group()}
|
|
651
|
+
|
|
652
|
+
groups = []
|
|
653
|
+
i = 0
|
|
654
|
+
while i <= len(mr.groups()):
|
|
655
|
+
groups.append(mr.group(i))
|
|
656
|
+
i += 1
|
|
657
|
+
|
|
658
|
+
obj["groups"] = groups
|
|
659
|
+
|
|
660
|
+
return obj
|
|
661
|
+
|
|
662
|
+
#
|
|
663
|
+
# Regexp Replace with replacer function
|
|
664
|
+
# @param s
|
|
665
|
+
# @param pattern
|
|
666
|
+
# @param fn
|
|
667
|
+
# @return
|
|
668
|
+
#
|
|
669
|
+
@staticmethod
|
|
670
|
+
def safe_replace_all_fn(s: Optional[str], pattern: re.Pattern, fn: Optional[Any]) -> str:
|
|
671
|
+
def replace_fn(t):
|
|
672
|
+
res = Functions.func_apply(fn, [Functions.to_jsonata_match(t)])
|
|
673
|
+
if isinstance(res, str):
|
|
674
|
+
return res
|
|
675
|
+
else:
|
|
676
|
+
raise jexception.JException("D3012", -1)
|
|
677
|
+
|
|
678
|
+
r = re.sub(pattern, replace_fn, s)
|
|
679
|
+
return r
|
|
680
|
+
|
|
681
|
+
#
|
|
682
|
+
# Safe replaceFirst
|
|
683
|
+
#
|
|
684
|
+
# @param s
|
|
685
|
+
# @param pattern
|
|
686
|
+
# @param replacement
|
|
687
|
+
# @return
|
|
688
|
+
#
|
|
689
|
+
@staticmethod
|
|
690
|
+
def safe_replace_first(s: Optional[str], pattern: re.Pattern, replacement: str) -> Optional[str]:
|
|
691
|
+
replacement = Functions.safe_replacement(replacement)
|
|
692
|
+
r = None
|
|
693
|
+
for i in range(0, 10):
|
|
694
|
+
try:
|
|
695
|
+
r = re.sub(pattern, replacement, s, 1)
|
|
696
|
+
break
|
|
697
|
+
except Exception as e:
|
|
698
|
+
msg = str(e)
|
|
699
|
+
|
|
700
|
+
# Message we understand needs to be:
|
|
701
|
+
# invalid group reference <g> at position <p>
|
|
702
|
+
m = re.match(r"invalid group reference (\d+) at position (\d+)", msg)
|
|
703
|
+
|
|
704
|
+
if m is None:
|
|
705
|
+
raise e
|
|
706
|
+
|
|
707
|
+
g = m.group(1)
|
|
708
|
+
suffix = g[-1]
|
|
709
|
+
prefix = g[:-1]
|
|
710
|
+
# Try capturing a smaller numbered group, e.g. "\g<1>2" instead of "\g<12>"
|
|
711
|
+
replace = "" if len(prefix) == 0 else r"\g<" + prefix + ">" + suffix
|
|
712
|
+
|
|
713
|
+
# Adjust replacement to remove the non-existing group
|
|
714
|
+
replacement = replacement.replace(r"\g<" + g + ">", replace)
|
|
715
|
+
return r
|
|
716
|
+
|
|
717
|
+
@staticmethod
|
|
718
|
+
def replace(string: Optional[str], pattern: Union[str, re.Pattern], replacement: Optional[Any], limit: Optional[int]) -> Optional[str]:
|
|
719
|
+
if string is None:
|
|
720
|
+
return None
|
|
721
|
+
if isinstance(pattern, str):
|
|
722
|
+
if len((str(pattern))) == 0:
|
|
723
|
+
raise jexception.JException("Second argument of replace function cannot be an empty string", 0)
|
|
724
|
+
if limit is None:
|
|
725
|
+
if isinstance(pattern, str):
|
|
726
|
+
return re.sub(pattern, str(replacement), string)
|
|
727
|
+
else:
|
|
728
|
+
return Functions.safe_replace_all(string, pattern, replacement)
|
|
729
|
+
else:
|
|
730
|
+
|
|
731
|
+
if limit < 0:
|
|
732
|
+
raise jexception.JException("Fourth argument of replace function must evaluate to a positive number", 0)
|
|
733
|
+
|
|
734
|
+
for i in range(0, limit):
|
|
735
|
+
if isinstance(pattern, str):
|
|
736
|
+
string = re.sub(pattern, str(replacement), string, 1)
|
|
737
|
+
else:
|
|
738
|
+
string = Functions.safe_replace_first(string, pattern, str(replacement))
|
|
739
|
+
return string
|
|
740
|
+
|
|
741
|
+
#
|
|
742
|
+
# Base64 encode a string
|
|
743
|
+
# @param {String} str - string
|
|
744
|
+
# @returns {String} Base 64 encoding of the binary data
|
|
745
|
+
#
|
|
746
|
+
@staticmethod
|
|
747
|
+
def base64encode(string: Optional[str]) -> Optional[str]:
|
|
748
|
+
# undefined inputs always return undefined
|
|
749
|
+
if string is None:
|
|
750
|
+
return None
|
|
751
|
+
try:
|
|
752
|
+
return base64.b64encode(string.encode("utf-8")).decode("utf-8")
|
|
753
|
+
except Exception as e:
|
|
754
|
+
return None
|
|
755
|
+
|
|
756
|
+
#
|
|
757
|
+
# Base64 decode a string
|
|
758
|
+
# @param {String} str - string
|
|
759
|
+
# @returns {String} Base 64 encoding of the binary data
|
|
760
|
+
#
|
|
761
|
+
@staticmethod
|
|
762
|
+
def base64decode(string: Optional[str]) -> Optional[str]:
|
|
763
|
+
# undefined inputs always return undefined
|
|
764
|
+
if string is None:
|
|
765
|
+
return None
|
|
766
|
+
try:
|
|
767
|
+
return base64.b64decode(string.encode("utf-8")).decode("utf-8")
|
|
768
|
+
except Exception as e:
|
|
769
|
+
return None
|
|
770
|
+
|
|
771
|
+
#
|
|
772
|
+
# Encode a string into a component for a url
|
|
773
|
+
# @param {String} str - String to encode
|
|
774
|
+
# @returns {string} Encoded string
|
|
775
|
+
#
|
|
776
|
+
@staticmethod
|
|
777
|
+
def encode_url_component(string: Optional[str]) -> Optional[str]:
|
|
778
|
+
# undefined inputs always return undefined
|
|
779
|
+
if string is None:
|
|
780
|
+
return None
|
|
781
|
+
|
|
782
|
+
# See https://stackoverflow.com/questions/946170/equivalent-javascript-functions-for-pythons-urllib-parse-quote-and-urllib-par
|
|
783
|
+
return urllib.parse.quote(string, safe="~()*!.'")
|
|
784
|
+
|
|
785
|
+
#
|
|
786
|
+
# Encode a string into a url
|
|
787
|
+
# @param {String} str - String to encode
|
|
788
|
+
# @returns {string} Encoded string
|
|
789
|
+
#
|
|
790
|
+
@staticmethod
|
|
791
|
+
def encode_url(string: Optional[str]) -> Optional[str]:
|
|
792
|
+
# undefined inputs always return undefined
|
|
793
|
+
if string is None:
|
|
794
|
+
return None
|
|
795
|
+
|
|
796
|
+
# See https://stackoverflow.com/questions/946170/equivalent-javascript-functions-for-pythons-urllib-parse-quote-and-urllib-par
|
|
797
|
+
return urllib.parse.quote(string, safe="~@#$&()*!+=:;,.?/'")
|
|
798
|
+
|
|
799
|
+
#
|
|
800
|
+
# Decode a string from a component for a url
|
|
801
|
+
# @param {String} str - String to decode
|
|
802
|
+
# @returns {string} Decoded string
|
|
803
|
+
#
|
|
804
|
+
@staticmethod
|
|
805
|
+
def decode_url_component(string: Optional[str]) -> Optional[str]:
|
|
806
|
+
# undefined inputs always return undefined
|
|
807
|
+
if string is None:
|
|
808
|
+
return None
|
|
809
|
+
|
|
810
|
+
# See https://stackoverflow.com/questions/946170/equivalent-javascript-functions-for-pythons-urllib-parse-quote-and-urllib-par
|
|
811
|
+
return urllib.parse.unquote(string, errors="strict")
|
|
812
|
+
|
|
813
|
+
#
|
|
814
|
+
# Decode a string from a url
|
|
815
|
+
# @param {String} str - String to decode
|
|
816
|
+
# @returns {string} Decoded string
|
|
817
|
+
#
|
|
818
|
+
@staticmethod
|
|
819
|
+
def decode_url(string: Optional[str]) -> Optional[str]:
|
|
820
|
+
# undefined inputs always return undefined
|
|
821
|
+
if string is None:
|
|
822
|
+
return None
|
|
823
|
+
|
|
824
|
+
# See https://stackoverflow.com/questions/946170/equivalent-javascript-functions-for-pythons-urllib-parse-quote-and-urllib-par
|
|
825
|
+
return urllib.parse.unquote(string, errors="strict")
|
|
826
|
+
|
|
827
|
+
@staticmethod
|
|
828
|
+
def split(string: Optional[str], pattern: Union[str, Optional[re.Pattern]], limit: Optional[float]) -> Optional[list[str]]:
|
|
829
|
+
if string is None:
|
|
830
|
+
return None
|
|
831
|
+
|
|
832
|
+
if limit is not None and int(limit) < 0:
|
|
833
|
+
raise jexception.JException("D3020", -1, string)
|
|
834
|
+
|
|
835
|
+
result = []
|
|
836
|
+
if limit is not None and int(limit) == 0:
|
|
837
|
+
return result
|
|
838
|
+
|
|
839
|
+
if isinstance(pattern, str):
|
|
840
|
+
sep = str(pattern)
|
|
841
|
+
if len(sep) == 0:
|
|
842
|
+
# $split("str", ""): Split string into characters
|
|
843
|
+
lim = int(limit) if limit is not None else sys.maxsize
|
|
844
|
+
i = 0
|
|
845
|
+
while i < len(string) and i < lim:
|
|
846
|
+
result.append(string[i])
|
|
847
|
+
i += 1
|
|
848
|
+
else:
|
|
849
|
+
# Quote separator string + preserve trailing empty strings (-1)
|
|
850
|
+
result = string.split(sep, -1)
|
|
851
|
+
else:
|
|
852
|
+
result = pattern.split(string)
|
|
853
|
+
if limit is not None and int(limit) < len(result):
|
|
854
|
+
result = result[0:int(limit)]
|
|
855
|
+
return result
|
|
856
|
+
|
|
857
|
+
EXPONENT_PIC = re.compile(r'\d[eE]\d')
|
|
858
|
+
|
|
859
|
+
#
|
|
860
|
+
# Formats a number into a decimal string representation using XPath 3.1 F&O fn:format-number spec
|
|
861
|
+
# @param {number} value - number to format
|
|
862
|
+
# @param {String} picture - picture string definition
|
|
863
|
+
# @param {Object} [options] - override locale defaults
|
|
864
|
+
# @returns {String} The formatted string
|
|
865
|
+
#
|
|
866
|
+
# Adapted from https://github.com/sissaschool/elementpath
|
|
867
|
+
@staticmethod
|
|
868
|
+
def format_number(value: Optional[float], picture: Optional[str], decimal_format: Optional[Mapping[str, str]]) -> Optional[str]:
|
|
869
|
+
if decimal_format is None:
|
|
870
|
+
decimal_format = {}
|
|
871
|
+
pattern_separator = decimal_format.get('pattern-separator', ';')
|
|
872
|
+
sub_pictures = picture.split(pattern_separator)
|
|
873
|
+
if len(sub_pictures) > 2:
|
|
874
|
+
raise jexception.JException('D3080', -1)
|
|
875
|
+
|
|
876
|
+
decimal_separator = decimal_format.get('decimal-separator', '.')
|
|
877
|
+
if any(p.count(decimal_separator) > 1 for p in sub_pictures):
|
|
878
|
+
raise jexception.JException('D3081', -1)
|
|
879
|
+
|
|
880
|
+
percent_sign = decimal_format.get('percent', '%')
|
|
881
|
+
if any(p.count(percent_sign) > 1 for p in sub_pictures):
|
|
882
|
+
raise jexception.JException('D3082', -1)
|
|
883
|
+
|
|
884
|
+
per_mille_sign = decimal_format.get('per-mille', '‰')
|
|
885
|
+
if any(p.count(per_mille_sign) > 1 for p in sub_pictures):
|
|
886
|
+
raise jexception.JException('D3083', -1)
|
|
887
|
+
if any(p.count(percent_sign) + p.count(per_mille_sign) > 1 for p in sub_pictures):
|
|
888
|
+
raise jexception.JException('D3084')
|
|
889
|
+
|
|
890
|
+
zero_digit = decimal_format.get('zero-digit', '0')
|
|
891
|
+
optional_digit = decimal_format.get('digit', '#')
|
|
892
|
+
digits_family = ''.join(chr(cp + ord(zero_digit)) for cp in range(10))
|
|
893
|
+
if any(optional_digit not in p and all(x not in p for x in digits_family)
|
|
894
|
+
for p in sub_pictures):
|
|
895
|
+
raise jexception.JException('D3085', -1)
|
|
896
|
+
|
|
897
|
+
grouping_separator = decimal_format.get('grouping-separator', ',')
|
|
898
|
+
adjacent_pattern = re.compile(r'[\\%s\\%s]{2}' % (grouping_separator, decimal_separator))
|
|
899
|
+
if any(adjacent_pattern.search(p) for p in sub_pictures):
|
|
900
|
+
raise jexception.JException('D3087', -1)
|
|
901
|
+
|
|
902
|
+
if any(x.endswith(grouping_separator)
|
|
903
|
+
for s in sub_pictures for x in s.split(decimal_separator)):
|
|
904
|
+
raise jexception.JException('D3088', -1)
|
|
905
|
+
|
|
906
|
+
active_characters = digits_family + ''.join([
|
|
907
|
+
decimal_separator, grouping_separator, pattern_separator, optional_digit
|
|
908
|
+
])
|
|
909
|
+
|
|
910
|
+
exponent_pattern = None
|
|
911
|
+
|
|
912
|
+
# Check optional exponent spec correctness in each sub-picture
|
|
913
|
+
exponent_separator = decimal_format.get('exponent-separator', 'e')
|
|
914
|
+
_pattern = re.compile(r'(?<=[{0}]){1}[{0}]'.format(
|
|
915
|
+
re.escape(active_characters), exponent_separator
|
|
916
|
+
))
|
|
917
|
+
for p in sub_pictures:
|
|
918
|
+
for match in _pattern.finditer(p):
|
|
919
|
+
if percent_sign in p or per_mille_sign in p:
|
|
920
|
+
raise jexception.JException('D3092', -1)
|
|
921
|
+
elif any(c not in digits_family for c in p[match.span()[1] - 1:]):
|
|
922
|
+
# detailed check to consider suffix
|
|
923
|
+
has_suffix = False
|
|
924
|
+
for ch in p[match.span()[1] - 1:]:
|
|
925
|
+
if ch in digits_family:
|
|
926
|
+
if has_suffix:
|
|
927
|
+
raise jexception.JException('D3093', -1)
|
|
928
|
+
elif ch in active_characters:
|
|
929
|
+
raise jexception.JException('D3086', -1)
|
|
930
|
+
else:
|
|
931
|
+
has_suffix = True
|
|
932
|
+
|
|
933
|
+
exponent_pattern = _pattern
|
|
934
|
+
|
|
935
|
+
if value is None:
|
|
936
|
+
return None
|
|
937
|
+
elif math.isnan(value):
|
|
938
|
+
return decimal_format.get('NaN', 'NaN')
|
|
939
|
+
elif isinstance(value, float):
|
|
940
|
+
value = decimal.Decimal.from_float(value)
|
|
941
|
+
elif not isinstance(value, decimal.Decimal):
|
|
942
|
+
value = decimal.Decimal(value)
|
|
943
|
+
|
|
944
|
+
minus_sign = decimal_format.get('minus-sign', '-')
|
|
945
|
+
|
|
946
|
+
prefix = ''
|
|
947
|
+
if value >= 0:
|
|
948
|
+
subpic = sub_pictures[0]
|
|
949
|
+
else:
|
|
950
|
+
subpic = sub_pictures[-1]
|
|
951
|
+
if len(sub_pictures) == 1:
|
|
952
|
+
prefix = minus_sign
|
|
953
|
+
|
|
954
|
+
for k, ch in enumerate(subpic):
|
|
955
|
+
if ch in active_characters:
|
|
956
|
+
prefix += subpic[:k]
|
|
957
|
+
subpic = subpic[k:]
|
|
958
|
+
break
|
|
959
|
+
else:
|
|
960
|
+
prefix += subpic
|
|
961
|
+
subpic = ''
|
|
962
|
+
|
|
963
|
+
if not subpic:
|
|
964
|
+
suffix = ''
|
|
965
|
+
elif subpic.endswith(percent_sign):
|
|
966
|
+
suffix = percent_sign
|
|
967
|
+
subpic = subpic[:-len(percent_sign)]
|
|
968
|
+
|
|
969
|
+
if value.as_tuple().exponent < 0:
|
|
970
|
+
value *= 100
|
|
971
|
+
else:
|
|
972
|
+
value = decimal.Decimal(int(value) * 100)
|
|
973
|
+
|
|
974
|
+
elif subpic.endswith(per_mille_sign):
|
|
975
|
+
suffix = per_mille_sign
|
|
976
|
+
subpic = subpic[:-len(per_mille_sign)]
|
|
977
|
+
|
|
978
|
+
if value.as_tuple().exponent < 0:
|
|
979
|
+
value *= 1000
|
|
980
|
+
else:
|
|
981
|
+
value = decimal.Decimal(int(value) * 1000)
|
|
982
|
+
|
|
983
|
+
else:
|
|
984
|
+
for k, ch in enumerate(reversed(subpic)):
|
|
985
|
+
if ch in active_characters:
|
|
986
|
+
idx = len(subpic) - k
|
|
987
|
+
suffix = subpic[idx:]
|
|
988
|
+
subpic = subpic[:idx]
|
|
989
|
+
break
|
|
990
|
+
else:
|
|
991
|
+
suffix = subpic
|
|
992
|
+
subpic = ''
|
|
993
|
+
|
|
994
|
+
exp_fmt = None
|
|
995
|
+
if exponent_pattern is not None:
|
|
996
|
+
exp_match = exponent_pattern.search(subpic)
|
|
997
|
+
if exp_match is not None:
|
|
998
|
+
exp_fmt = subpic[exp_match.span()[0] + 1:]
|
|
999
|
+
subpic = subpic[:exp_match.span()[0]]
|
|
1000
|
+
|
|
1001
|
+
fmt_tokens = subpic.split(decimal_separator)
|
|
1002
|
+
if all(not fmt for fmt in fmt_tokens):
|
|
1003
|
+
raise jexception.JException('both integer and fractional parts are empty', -1)
|
|
1004
|
+
|
|
1005
|
+
if math.isinf(value):
|
|
1006
|
+
return prefix + decimal_format.get('infinity', '∞') + suffix
|
|
1007
|
+
|
|
1008
|
+
# Calculate the exponent value if it's in the sub-picture
|
|
1009
|
+
exp_value = 0
|
|
1010
|
+
if exp_fmt and value:
|
|
1011
|
+
num_digits = 0
|
|
1012
|
+
for ch in fmt_tokens[0]:
|
|
1013
|
+
if ch in digits_family:
|
|
1014
|
+
num_digits += 1
|
|
1015
|
+
|
|
1016
|
+
if abs(value) > 1:
|
|
1017
|
+
v = abs(value)
|
|
1018
|
+
while v > 10 ** num_digits:
|
|
1019
|
+
exp_value += 1
|
|
1020
|
+
v /= 10
|
|
1021
|
+
|
|
1022
|
+
# modify empty fractional part to store a digit
|
|
1023
|
+
if not num_digits:
|
|
1024
|
+
if len(fmt_tokens) == 1:
|
|
1025
|
+
fmt_tokens.append(zero_digit)
|
|
1026
|
+
elif not fmt_tokens[-1]:
|
|
1027
|
+
fmt_tokens[-1] = zero_digit
|
|
1028
|
+
|
|
1029
|
+
elif len(fmt_tokens) > 1 and fmt_tokens[-1] and value >= 0:
|
|
1030
|
+
v = abs(value) * 10
|
|
1031
|
+
while v < 10 ** num_digits:
|
|
1032
|
+
exp_value -= 1
|
|
1033
|
+
v *= 10
|
|
1034
|
+
else:
|
|
1035
|
+
v = abs(value) * 10
|
|
1036
|
+
while v < 10:
|
|
1037
|
+
exp_value -= 1
|
|
1038
|
+
v *= 10
|
|
1039
|
+
|
|
1040
|
+
if exp_value:
|
|
1041
|
+
value = value * decimal.Decimal(10) ** -exp_value
|
|
1042
|
+
|
|
1043
|
+
# round the value by fractional part
|
|
1044
|
+
if len(fmt_tokens) == 1 or not fmt_tokens[-1]:
|
|
1045
|
+
exp = decimal.Decimal('1')
|
|
1046
|
+
else:
|
|
1047
|
+
k = -1
|
|
1048
|
+
for ch in fmt_tokens[-1]:
|
|
1049
|
+
if ch in digits_family or ch == optional_digit:
|
|
1050
|
+
k += 1
|
|
1051
|
+
exp = decimal.Decimal('.' + '0' * k + '1')
|
|
1052
|
+
|
|
1053
|
+
try:
|
|
1054
|
+
if value > 0:
|
|
1055
|
+
value = value.quantize(exp, rounding='ROUND_HALF_UP')
|
|
1056
|
+
else:
|
|
1057
|
+
value = value.quantize(exp, rounding='ROUND_HALF_DOWN')
|
|
1058
|
+
except decimal.InvalidOperation:
|
|
1059
|
+
pass # number too large, don't round ...
|
|
1060
|
+
|
|
1061
|
+
chunks = Functions.decimal_to_string(value).lstrip('-').split('.')
|
|
1062
|
+
kwargs = {
|
|
1063
|
+
'digits_family': digits_family,
|
|
1064
|
+
'optional_digit': optional_digit,
|
|
1065
|
+
'grouping_separator': grouping_separator,
|
|
1066
|
+
}
|
|
1067
|
+
result = Functions.format_digits(chunks[0], fmt_tokens[0], **kwargs)
|
|
1068
|
+
|
|
1069
|
+
if len(fmt_tokens) > 1 and fmt_tokens[0]:
|
|
1070
|
+
has_decimal_digit = False
|
|
1071
|
+
for ch in fmt_tokens[0]:
|
|
1072
|
+
if ch in digits_family:
|
|
1073
|
+
has_decimal_digit = True
|
|
1074
|
+
elif ch == optional_digit and has_decimal_digit:
|
|
1075
|
+
raise jexception.JException('D3090', -1)
|
|
1076
|
+
|
|
1077
|
+
if len(fmt_tokens) > 1 and fmt_tokens[-1]:
|
|
1078
|
+
has_optional_digit = False
|
|
1079
|
+
for ch in fmt_tokens[-1]:
|
|
1080
|
+
if ch == optional_digit:
|
|
1081
|
+
has_optional_digit = True
|
|
1082
|
+
elif ch in digits_family and has_optional_digit:
|
|
1083
|
+
raise jexception.JException('D3091', -1)
|
|
1084
|
+
|
|
1085
|
+
if len(chunks) == 1:
|
|
1086
|
+
chunks.append(zero_digit)
|
|
1087
|
+
|
|
1088
|
+
decimal_part = Functions.format_digits(chunks[1], fmt_tokens[-1], **kwargs)
|
|
1089
|
+
|
|
1090
|
+
for ch in reversed(fmt_tokens[-1]):
|
|
1091
|
+
if ch == optional_digit:
|
|
1092
|
+
if decimal_part and decimal_part[-1] == zero_digit:
|
|
1093
|
+
decimal_part = decimal_part[:-1]
|
|
1094
|
+
else:
|
|
1095
|
+
if not decimal_part:
|
|
1096
|
+
decimal_part = zero_digit
|
|
1097
|
+
break
|
|
1098
|
+
|
|
1099
|
+
if decimal_part:
|
|
1100
|
+
result += decimal_separator + decimal_part
|
|
1101
|
+
|
|
1102
|
+
if not fmt_tokens[0] and result.startswith(zero_digit):
|
|
1103
|
+
result = result.lstrip(zero_digit)
|
|
1104
|
+
|
|
1105
|
+
if exp_fmt:
|
|
1106
|
+
exp_digits = Functions.format_digits(str(abs(exp_value)), exp_fmt, **kwargs)
|
|
1107
|
+
if exp_value >= 0:
|
|
1108
|
+
result += f'{exponent_separator}{exp_digits}'
|
|
1109
|
+
else:
|
|
1110
|
+
result += f'{exponent_separator}-{exp_digits}'
|
|
1111
|
+
|
|
1112
|
+
return prefix + result + suffix
|
|
1113
|
+
|
|
1114
|
+
@staticmethod
|
|
1115
|
+
def decimal_to_string(value: decimal.Decimal) -> str:
|
|
1116
|
+
"""
|
|
1117
|
+
Convert a Decimal value to a string representation
|
|
1118
|
+
that not includes exponent and with its decimals.
|
|
1119
|
+
"""
|
|
1120
|
+
sign, digits, exponent = value.as_tuple()
|
|
1121
|
+
|
|
1122
|
+
if not exponent:
|
|
1123
|
+
result = ''.join(str(x) for x in digits)
|
|
1124
|
+
elif exponent > 0:
|
|
1125
|
+
result = ''.join(str(x) for x in digits) + '0' * exponent
|
|
1126
|
+
else:
|
|
1127
|
+
result = ''.join(str(x) for x in digits[:exponent])
|
|
1128
|
+
if not result:
|
|
1129
|
+
result = '0'
|
|
1130
|
+
result += '.'
|
|
1131
|
+
if len(digits) >= -exponent:
|
|
1132
|
+
result += ''.join(str(x) for x in digits[exponent:])
|
|
1133
|
+
else:
|
|
1134
|
+
result += '0' * (-exponent - len(digits))
|
|
1135
|
+
result += ''.join(str(x) for x in digits)
|
|
1136
|
+
|
|
1137
|
+
return '-' + result if sign else result
|
|
1138
|
+
|
|
1139
|
+
@staticmethod
|
|
1140
|
+
def format_digits(digits: str,
|
|
1141
|
+
fmt: str,
|
|
1142
|
+
digits_family: str = '0123456789',
|
|
1143
|
+
optional_digit: str = '#',
|
|
1144
|
+
grouping_separator: Optional[str] = None) -> str:
|
|
1145
|
+
result = []
|
|
1146
|
+
iter_num_digits = reversed(digits)
|
|
1147
|
+
num_digit = next(iter_num_digits)
|
|
1148
|
+
|
|
1149
|
+
for fmt_char in reversed(fmt):
|
|
1150
|
+
if fmt_char in digits_family or fmt_char == optional_digit:
|
|
1151
|
+
if num_digit:
|
|
1152
|
+
result.append(digits_family[ord(num_digit) - 48])
|
|
1153
|
+
num_digit = next(iter_num_digits, '')
|
|
1154
|
+
elif fmt_char != optional_digit:
|
|
1155
|
+
result.append(digits_family[0])
|
|
1156
|
+
elif not result or not result[-1] in digits_family and grouping_separator \
|
|
1157
|
+
and result[-1] != grouping_separator:
|
|
1158
|
+
raise jexception.JException("invalid grouping in picture argument", -1)
|
|
1159
|
+
else:
|
|
1160
|
+
result.append(fmt_char)
|
|
1161
|
+
|
|
1162
|
+
if num_digit:
|
|
1163
|
+
separator = ''
|
|
1164
|
+
_separator = {x for x in fmt if x not in digits_family and x != optional_digit}
|
|
1165
|
+
if len(_separator) != 1:
|
|
1166
|
+
repeat = None
|
|
1167
|
+
else:
|
|
1168
|
+
separator = _separator.pop()
|
|
1169
|
+
chunks = fmt.split(separator)
|
|
1170
|
+
|
|
1171
|
+
if len(chunks[0]) > len(chunks[-1]):
|
|
1172
|
+
repeat = None
|
|
1173
|
+
elif all(len(item) == len(chunks[-1]) for item in chunks[1:-1]):
|
|
1174
|
+
repeat = len(chunks[-1]) + 1
|
|
1175
|
+
else:
|
|
1176
|
+
repeat = None
|
|
1177
|
+
|
|
1178
|
+
if repeat is None:
|
|
1179
|
+
while num_digit:
|
|
1180
|
+
result.append(digits_family[ord(num_digit) - 48])
|
|
1181
|
+
num_digit = next(iter_num_digits, '')
|
|
1182
|
+
else:
|
|
1183
|
+
while num_digit:
|
|
1184
|
+
if ((len(result) + 1) % repeat) == 0:
|
|
1185
|
+
result.append(separator)
|
|
1186
|
+
result.append(digits_family[ord(num_digit) - 48])
|
|
1187
|
+
num_digit = next(iter_num_digits, '')
|
|
1188
|
+
|
|
1189
|
+
if grouping_separator:
|
|
1190
|
+
return ''.join(reversed(result)).lstrip(grouping_separator)
|
|
1191
|
+
while result and \
|
|
1192
|
+
unicodedata.category(result[-1]) not in ('Nd', 'Nl', 'No', 'Lu', 'Ll', 'Lt', 'Lm', 'Lo'):
|
|
1193
|
+
result.pop()
|
|
1194
|
+
return ''.join(reversed(result))
|
|
1195
|
+
|
|
1196
|
+
#
|
|
1197
|
+
# Converts a number to a string using a specified number base
|
|
1198
|
+
# @param {number} value - the number to convert
|
|
1199
|
+
# @param {number} [radix] - the number base; must be between 2 and 36. Defaults to 10
|
|
1200
|
+
# @returns {string} - the converted string
|
|
1201
|
+
#
|
|
1202
|
+
@staticmethod
|
|
1203
|
+
def format_base(value: Optional[float], _radix: Optional[float]) -> Optional[str]:
|
|
1204
|
+
# undefined inputs always return undefined
|
|
1205
|
+
if value is None:
|
|
1206
|
+
return None
|
|
1207
|
+
|
|
1208
|
+
value = Functions.round(value, 0)
|
|
1209
|
+
|
|
1210
|
+
radix = 0
|
|
1211
|
+
if _radix is None:
|
|
1212
|
+
radix = 10
|
|
1213
|
+
else:
|
|
1214
|
+
radix = int(_radix)
|
|
1215
|
+
|
|
1216
|
+
if radix < 2 or radix > 36:
|
|
1217
|
+
raise jexception.JException("D3100", radix)
|
|
1218
|
+
|
|
1219
|
+
result = Functions.base_repr(int(value), radix).lower()
|
|
1220
|
+
|
|
1221
|
+
return result
|
|
1222
|
+
|
|
1223
|
+
@staticmethod
|
|
1224
|
+
def base_repr(number: int, base: int = 2, padding: int = 0) -> str:
|
|
1225
|
+
"""
|
|
1226
|
+
Return a string representation of a number in the given base system.
|
|
1227
|
+
|
|
1228
|
+
Parameters
|
|
1229
|
+
----------
|
|
1230
|
+
number : int
|
|
1231
|
+
The value to convert. Positive and negative values are handled.
|
|
1232
|
+
base : int, optional
|
|
1233
|
+
Convert `number` to the `base` number system. The valid range is 2-36,
|
|
1234
|
+
the default value is 2.
|
|
1235
|
+
padding : int, optional
|
|
1236
|
+
Number of zeros padded on the left. Default is 0 (no padding).
|
|
1237
|
+
|
|
1238
|
+
Returns
|
|
1239
|
+
-------
|
|
1240
|
+
out : str
|
|
1241
|
+
String representation of `number` in `base` system.
|
|
1242
|
+
|
|
1243
|
+
"""
|
|
1244
|
+
digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
1245
|
+
if base > len(digits):
|
|
1246
|
+
raise ValueError("Bases greater than 36 not handled in base_repr.")
|
|
1247
|
+
elif base < 2:
|
|
1248
|
+
raise ValueError("Bases less than 2 not handled in base_repr.")
|
|
1249
|
+
|
|
1250
|
+
num = abs(number)
|
|
1251
|
+
res = []
|
|
1252
|
+
while num:
|
|
1253
|
+
res.append(digits[num % base])
|
|
1254
|
+
num //= base
|
|
1255
|
+
if padding:
|
|
1256
|
+
res.append('0' * padding)
|
|
1257
|
+
if number < 0:
|
|
1258
|
+
res.append('-')
|
|
1259
|
+
return ''.join(reversed(res or '0'))
|
|
1260
|
+
|
|
1261
|
+
#
|
|
1262
|
+
# Cast argument to number
|
|
1263
|
+
# @param {Object} arg - Argument
|
|
1264
|
+
# @throws NumberFormatException
|
|
1265
|
+
# @returns {Number} numeric value of argument
|
|
1266
|
+
#
|
|
1267
|
+
@staticmethod
|
|
1268
|
+
def number(arg: Optional[Any]) -> Optional[float]:
|
|
1269
|
+
result = None
|
|
1270
|
+
|
|
1271
|
+
# undefined inputs always return undefined
|
|
1272
|
+
if arg is None:
|
|
1273
|
+
return None
|
|
1274
|
+
|
|
1275
|
+
if arg is utils.Utils.NULL_VALUE:
|
|
1276
|
+
raise jexception.JException("T0410", -1)
|
|
1277
|
+
|
|
1278
|
+
if isinstance(arg, bool):
|
|
1279
|
+
result = 1 if (bool(arg)) else 0
|
|
1280
|
+
elif isinstance(arg, (int, float)):
|
|
1281
|
+
result = arg
|
|
1282
|
+
elif isinstance(arg, str):
|
|
1283
|
+
s = str(arg)
|
|
1284
|
+
if s.startswith("0x"):
|
|
1285
|
+
result = int(s[2:], 16)
|
|
1286
|
+
elif s.startswith("0B"):
|
|
1287
|
+
result = int(s[2:], 2)
|
|
1288
|
+
elif s.startswith("0O"):
|
|
1289
|
+
result = int(s[2:], 8)
|
|
1290
|
+
else:
|
|
1291
|
+
result = float(str(arg))
|
|
1292
|
+
return result
|
|
1293
|
+
|
|
1294
|
+
#
|
|
1295
|
+
# Absolute value of a number
|
|
1296
|
+
# @param {Number} arg - Argument
|
|
1297
|
+
# @returns {Number} absolute value of argument
|
|
1298
|
+
#
|
|
1299
|
+
@staticmethod
|
|
1300
|
+
def abs(arg: Optional[float]) -> Optional[float]:
|
|
1301
|
+
|
|
1302
|
+
# undefined inputs always return undefined
|
|
1303
|
+
if arg is None:
|
|
1304
|
+
return None
|
|
1305
|
+
|
|
1306
|
+
return abs(float(arg)) if isinstance(arg, float) else abs(int(arg))
|
|
1307
|
+
|
|
1308
|
+
#
|
|
1309
|
+
# Rounds a number down to integer
|
|
1310
|
+
# @param {Number} arg - Argument
|
|
1311
|
+
# @returns {Number} rounded integer
|
|
1312
|
+
#
|
|
1313
|
+
@staticmethod
|
|
1314
|
+
def floor(arg: Optional[float]) -> Optional[float]:
|
|
1315
|
+
|
|
1316
|
+
# undefined inputs always return undefined
|
|
1317
|
+
if arg is None:
|
|
1318
|
+
return None
|
|
1319
|
+
|
|
1320
|
+
return math.floor(float(arg))
|
|
1321
|
+
|
|
1322
|
+
#
|
|
1323
|
+
# Rounds a number up to integer
|
|
1324
|
+
# @param {Number} arg - Argument
|
|
1325
|
+
# @returns {Number} rounded integer
|
|
1326
|
+
#
|
|
1327
|
+
@staticmethod
|
|
1328
|
+
def ceil(arg: Optional[float]) -> Optional[float]:
|
|
1329
|
+
|
|
1330
|
+
# undefined inputs always return undefined
|
|
1331
|
+
if arg is None:
|
|
1332
|
+
return None
|
|
1333
|
+
|
|
1334
|
+
return math.ceil(float(arg))
|
|
1335
|
+
|
|
1336
|
+
#
|
|
1337
|
+
# Round to half even
|
|
1338
|
+
# @param {Number} arg - Argument
|
|
1339
|
+
# @param {Number} [precision] - number of decimal places
|
|
1340
|
+
# @returns {Number} rounded integer
|
|
1341
|
+
#
|
|
1342
|
+
@staticmethod
|
|
1343
|
+
def round(arg: Optional[float], precision: Optional[float]) -> Optional[float]:
|
|
1344
|
+
|
|
1345
|
+
# undefined inputs always return undefined
|
|
1346
|
+
if arg is None:
|
|
1347
|
+
return None
|
|
1348
|
+
|
|
1349
|
+
d = decimal.Decimal(str(arg))
|
|
1350
|
+
return float(round(d, precision))
|
|
1351
|
+
|
|
1352
|
+
#
|
|
1353
|
+
# Square root of number
|
|
1354
|
+
# @param {Number} arg - Argument
|
|
1355
|
+
# @returns {Number} square root
|
|
1356
|
+
#
|
|
1357
|
+
@staticmethod
|
|
1358
|
+
def sqrt(arg: Optional[float]) -> Optional[float]:
|
|
1359
|
+
|
|
1360
|
+
# undefined inputs always return undefined
|
|
1361
|
+
if arg is None:
|
|
1362
|
+
return None
|
|
1363
|
+
|
|
1364
|
+
if float(arg) < 0:
|
|
1365
|
+
raise jexception.JException("D3060", 1, arg)
|
|
1366
|
+
|
|
1367
|
+
return math.sqrt(float(arg))
|
|
1368
|
+
|
|
1369
|
+
#
|
|
1370
|
+
# Raises number to the power of the second number
|
|
1371
|
+
# @param {Number} arg - the base
|
|
1372
|
+
# @param {Number} exp - the exponent
|
|
1373
|
+
# @returns {Number} rounded integer
|
|
1374
|
+
#
|
|
1375
|
+
@staticmethod
|
|
1376
|
+
def power(arg: Optional[float], exp: Optional[float]) -> Optional[float]:
|
|
1377
|
+
|
|
1378
|
+
# undefined inputs always return undefined
|
|
1379
|
+
if arg is None:
|
|
1380
|
+
return None
|
|
1381
|
+
|
|
1382
|
+
result = float(arg) ** float(exp)
|
|
1383
|
+
|
|
1384
|
+
if not math.isfinite(result):
|
|
1385
|
+
raise jexception.JException("D3061", 1, arg, exp)
|
|
1386
|
+
|
|
1387
|
+
return result
|
|
1388
|
+
|
|
1389
|
+
#
|
|
1390
|
+
# Returns a random number 0 <= n < 1
|
|
1391
|
+
# @returns {number} random number
|
|
1392
|
+
#
|
|
1393
|
+
@staticmethod
|
|
1394
|
+
def random() -> float:
|
|
1395
|
+
return random.random()
|
|
1396
|
+
|
|
1397
|
+
#
|
|
1398
|
+
# Evaluate an input and return a boolean
|
|
1399
|
+
# @param {*} arg - Arguments
|
|
1400
|
+
# @returns {boolean} Boolean
|
|
1401
|
+
#
|
|
1402
|
+
@staticmethod
|
|
1403
|
+
def to_boolean(arg: Optional[Any]) -> Optional[bool]:
|
|
1404
|
+
from jsonata import jsonata
|
|
1405
|
+
# cast arg to its effective boolean value
|
|
1406
|
+
# boolean: unchanged
|
|
1407
|
+
# string: zero-length -> false; otherwise -> true
|
|
1408
|
+
# number: 0 -> false; otherwise -> true
|
|
1409
|
+
# null -> false
|
|
1410
|
+
# array: empty -> false; length > 1 -> true
|
|
1411
|
+
# object: empty -> false; non-empty -> true
|
|
1412
|
+
# function -> false
|
|
1413
|
+
|
|
1414
|
+
# undefined inputs always return undefined
|
|
1415
|
+
if arg is None:
|
|
1416
|
+
return None # Uli: Null would need to be handled as false anyway
|
|
1417
|
+
|
|
1418
|
+
result = False
|
|
1419
|
+
if isinstance(arg, list):
|
|
1420
|
+
el = arg
|
|
1421
|
+
if len(el) == 1:
|
|
1422
|
+
result = Functions.to_boolean(el[0])
|
|
1423
|
+
elif len(el) > 1:
|
|
1424
|
+
trues_length = len(list(filter(lambda e: jsonata.Jsonata.boolize(e), el)))
|
|
1425
|
+
result = trues_length > 0
|
|
1426
|
+
elif isinstance(arg, str):
|
|
1427
|
+
s = str(arg)
|
|
1428
|
+
if len(s) > 0:
|
|
1429
|
+
result = True
|
|
1430
|
+
elif isinstance(arg, bool):
|
|
1431
|
+
result = bool(arg)
|
|
1432
|
+
elif isinstance(arg, (int, float)):
|
|
1433
|
+
if float(arg) != 0:
|
|
1434
|
+
result = True
|
|
1435
|
+
elif isinstance(arg, dict):
|
|
1436
|
+
if len(arg) > 0:
|
|
1437
|
+
result = True
|
|
1438
|
+
return result
|
|
1439
|
+
|
|
1440
|
+
#
|
|
1441
|
+
# returns the Boolean NOT of the arg
|
|
1442
|
+
# @param {*} arg - argument
|
|
1443
|
+
# @returns {boolean} - NOT arg
|
|
1444
|
+
#
|
|
1445
|
+
@staticmethod
|
|
1446
|
+
def not_(arg: Optional[Any]) -> Optional[bool]:
|
|
1447
|
+
# undefined inputs always return undefined
|
|
1448
|
+
if arg is None:
|
|
1449
|
+
return None
|
|
1450
|
+
|
|
1451
|
+
return not Functions.to_boolean(arg)
|
|
1452
|
+
|
|
1453
|
+
@staticmethod
|
|
1454
|
+
def get_function_arity(func: Optional[Any]) -> int:
|
|
1455
|
+
from jsonata import jsonata
|
|
1456
|
+
if isinstance(func, jsonata.Jsonata.JFunction):
|
|
1457
|
+
return func.signature.get_min_number_of_args()
|
|
1458
|
+
else:
|
|
1459
|
+
# Lambda
|
|
1460
|
+
return len(func.arguments)
|
|
1461
|
+
|
|
1462
|
+
#
|
|
1463
|
+
# Helper function to build the arguments to be supplied to the function arg of the
|
|
1464
|
+
# HOFs map, filter, each, sift and single
|
|
1465
|
+
# @param {function} func - the function to be invoked
|
|
1466
|
+
# @param {*} arg1 - the first (required) arg - the value
|
|
1467
|
+
# @param {*} arg2 - the second (optional) arg - the position (index or key)
|
|
1468
|
+
# @param {*} arg3 - the third (optional) arg - the whole structure (array or object)
|
|
1469
|
+
# @returns {*[]} the argument list
|
|
1470
|
+
#
|
|
1471
|
+
@staticmethod
|
|
1472
|
+
def hof_func_args(func: Optional[Any], arg1: Optional[Any], arg2: Optional[Any], arg3: Optional[Any]) -> list:
|
|
1473
|
+
func_args = [arg1]
|
|
1474
|
+
# the other two are optional - only supply it if the function can take it
|
|
1475
|
+
length = Functions.get_function_arity(func)
|
|
1476
|
+
if length >= 2:
|
|
1477
|
+
func_args.append(arg2)
|
|
1478
|
+
if length >= 3:
|
|
1479
|
+
func_args.append(arg3)
|
|
1480
|
+
return func_args
|
|
1481
|
+
|
|
1482
|
+
#
|
|
1483
|
+
# Call helper for Java
|
|
1484
|
+
#
|
|
1485
|
+
# @param func
|
|
1486
|
+
# @param funcArgs
|
|
1487
|
+
# @return
|
|
1488
|
+
# @throws Throwable
|
|
1489
|
+
#
|
|
1490
|
+
@staticmethod
|
|
1491
|
+
def func_apply(func: Optional[Any], func_args: Optional[Sequence]) -> Optional[Any]:
|
|
1492
|
+
from jsonata import jsonata
|
|
1493
|
+
res = None
|
|
1494
|
+
if Functions.is_lambda(func):
|
|
1495
|
+
res = jsonata.Jsonata.CURRENT.jsonata.apply(func, func_args, None,
|
|
1496
|
+
jsonata.Jsonata.CURRENT.jsonata.environment)
|
|
1497
|
+
else:
|
|
1498
|
+
res = func.call(None, func_args)
|
|
1499
|
+
return res
|
|
1500
|
+
|
|
1501
|
+
#
|
|
1502
|
+
# Create a map from an array of arguments
|
|
1503
|
+
# @param {Array} [arr] - array to map over
|
|
1504
|
+
# @param {Function} func - function to apply
|
|
1505
|
+
# @returns {Array} Map array
|
|
1506
|
+
#
|
|
1507
|
+
@staticmethod
|
|
1508
|
+
def map(arr: Optional[Sequence], func: Optional[Any]) -> Optional[list]:
|
|
1509
|
+
|
|
1510
|
+
# undefined inputs always return undefined
|
|
1511
|
+
if arr is None:
|
|
1512
|
+
return None
|
|
1513
|
+
|
|
1514
|
+
result = utils.Utils.create_sequence()
|
|
1515
|
+
# do the map - iterate over the arrays, and invoke func
|
|
1516
|
+
for i, arg in enumerate(arr):
|
|
1517
|
+
func_args = Functions.hof_func_args(func, arg, i, arr)
|
|
1518
|
+
|
|
1519
|
+
res = Functions.func_apply(func, func_args)
|
|
1520
|
+
if res is not None:
|
|
1521
|
+
result.append(res)
|
|
1522
|
+
return result
|
|
1523
|
+
|
|
1524
|
+
#
|
|
1525
|
+
# Create a map from an array of arguments
|
|
1526
|
+
# @param {Array} [arr] - array to filter
|
|
1527
|
+
# @param {Function} func - predicate function
|
|
1528
|
+
# @returns {Array} Map array
|
|
1529
|
+
#
|
|
1530
|
+
@staticmethod
|
|
1531
|
+
def filter(arr: Optional[Sequence], func: Optional[Any]) -> Optional[list]:
|
|
1532
|
+
# undefined inputs always return undefined
|
|
1533
|
+
if arr is None:
|
|
1534
|
+
return None
|
|
1535
|
+
|
|
1536
|
+
result = utils.Utils.create_sequence()
|
|
1537
|
+
|
|
1538
|
+
for i, entry in enumerate(arr):
|
|
1539
|
+
func_args = Functions.hof_func_args(func, entry, i, arr)
|
|
1540
|
+
# invoke func
|
|
1541
|
+
res = Functions.func_apply(func, func_args)
|
|
1542
|
+
if Functions.to_boolean(res):
|
|
1543
|
+
result.append(entry)
|
|
1544
|
+
|
|
1545
|
+
return result
|
|
1546
|
+
|
|
1547
|
+
#
|
|
1548
|
+
# Given an array, find the single element matching a specified condition
|
|
1549
|
+
# Throws an exception if the number of matching elements is not exactly one
|
|
1550
|
+
# @param {Array} [arr] - array to filter
|
|
1551
|
+
# @param {Function} [func] - predicate function
|
|
1552
|
+
# @returns {*} Matching element
|
|
1553
|
+
#
|
|
1554
|
+
@staticmethod
|
|
1555
|
+
def single(arr: Optional[Sequence], func: Optional[Any]) -> Optional[Any]:
|
|
1556
|
+
# undefined inputs always return undefined
|
|
1557
|
+
if arr is None:
|
|
1558
|
+
return None
|
|
1559
|
+
|
|
1560
|
+
has_found_match = False
|
|
1561
|
+
result = None
|
|
1562
|
+
|
|
1563
|
+
for i, entry in enumerate(arr):
|
|
1564
|
+
positive_result = True
|
|
1565
|
+
if func is not None:
|
|
1566
|
+
func_args = Functions.hof_func_args(func, entry, i, arr)
|
|
1567
|
+
# invoke func
|
|
1568
|
+
res = Functions.func_apply(func, func_args)
|
|
1569
|
+
positive_result = Functions.to_boolean(res)
|
|
1570
|
+
if positive_result:
|
|
1571
|
+
if not has_found_match:
|
|
1572
|
+
result = entry
|
|
1573
|
+
has_found_match = True
|
|
1574
|
+
else:
|
|
1575
|
+
raise jexception.JException("D3138", i)
|
|
1576
|
+
|
|
1577
|
+
if not has_found_match:
|
|
1578
|
+
raise jexception.JException("D3139", -1)
|
|
1579
|
+
|
|
1580
|
+
return result
|
|
1581
|
+
|
|
1582
|
+
#
|
|
1583
|
+
# Convolves (zips) each value from a set of arrays
|
|
1584
|
+
# @param {Array} [args] - arrays to zip
|
|
1585
|
+
# @returns {Array} Zipped array
|
|
1586
|
+
#
|
|
1587
|
+
@staticmethod
|
|
1588
|
+
def zip(*args: Sequence) -> list:
|
|
1589
|
+
result = []
|
|
1590
|
+
# length of the shortest array
|
|
1591
|
+
length = sys.maxsize
|
|
1592
|
+
nargs = 0
|
|
1593
|
+
# nargs : the real size of args!=null
|
|
1594
|
+
while nargs < len(args):
|
|
1595
|
+
if args[nargs] is None:
|
|
1596
|
+
length = 0
|
|
1597
|
+
break
|
|
1598
|
+
|
|
1599
|
+
length = min(length, len(args[nargs]))
|
|
1600
|
+
nargs += 1
|
|
1601
|
+
|
|
1602
|
+
for i in range(0, length):
|
|
1603
|
+
tuple = []
|
|
1604
|
+
for k in range(0, nargs):
|
|
1605
|
+
tuple.append(args[k][i])
|
|
1606
|
+
result.append(tuple)
|
|
1607
|
+
return result
|
|
1608
|
+
|
|
1609
|
+
#
|
|
1610
|
+
# Fold left function
|
|
1611
|
+
# @param {Array} sequence - Sequence
|
|
1612
|
+
# @param {Function} func - Function
|
|
1613
|
+
# @param {Object} init - Initial value
|
|
1614
|
+
# @returns {*} Result
|
|
1615
|
+
#
|
|
1616
|
+
@staticmethod
|
|
1617
|
+
def fold_left(sequence: Optional[Sequence], func: Optional[Any], init: Optional[Any]) -> Optional[Any]:
|
|
1618
|
+
# undefined inputs always return undefined
|
|
1619
|
+
if sequence is None:
|
|
1620
|
+
return None
|
|
1621
|
+
result = None
|
|
1622
|
+
|
|
1623
|
+
arity = Functions.get_function_arity(func)
|
|
1624
|
+
if arity < 2:
|
|
1625
|
+
raise jexception.JException("D3050", 1)
|
|
1626
|
+
|
|
1627
|
+
index = 0
|
|
1628
|
+
if init is None and len(sequence) > 0:
|
|
1629
|
+
result = sequence[0]
|
|
1630
|
+
index = 1
|
|
1631
|
+
else:
|
|
1632
|
+
result = init
|
|
1633
|
+
index = 0
|
|
1634
|
+
|
|
1635
|
+
while index < len(sequence):
|
|
1636
|
+
args = [result, sequence[index]]
|
|
1637
|
+
if arity >= 3:
|
|
1638
|
+
args.append(index)
|
|
1639
|
+
if arity >= 4:
|
|
1640
|
+
args.append(sequence)
|
|
1641
|
+
result = Functions.func_apply(func, args)
|
|
1642
|
+
index += 1
|
|
1643
|
+
|
|
1644
|
+
return result
|
|
1645
|
+
|
|
1646
|
+
#
|
|
1647
|
+
# Return keys for an object
|
|
1648
|
+
# @param {Object} arg - Object
|
|
1649
|
+
# @returns {Array} Array of keys
|
|
1650
|
+
#
|
|
1651
|
+
@staticmethod
|
|
1652
|
+
def keys(arg: Union[Sequence, Mapping, None]) -> list:
|
|
1653
|
+
result = utils.Utils.create_sequence()
|
|
1654
|
+
|
|
1655
|
+
if isinstance(arg, list):
|
|
1656
|
+
# merge the keys of all of the items in the array
|
|
1657
|
+
keys = {}
|
|
1658
|
+
for el in arg:
|
|
1659
|
+
keys.update({k: '' for k in Functions.keys(el)})
|
|
1660
|
+
result.extend(keys.keys())
|
|
1661
|
+
elif isinstance(arg, dict):
|
|
1662
|
+
result.extend(arg.keys())
|
|
1663
|
+
return result
|
|
1664
|
+
|
|
1665
|
+
# here: append, lookup
|
|
1666
|
+
|
|
1667
|
+
#
|
|
1668
|
+
# Determines if the argument is undefined
|
|
1669
|
+
# @param {*} arg - argument
|
|
1670
|
+
# @returns {boolean} False if argument undefined, otherwise true
|
|
1671
|
+
#
|
|
1672
|
+
@staticmethod
|
|
1673
|
+
def exists(arg: Optional[Any]) -> bool:
|
|
1674
|
+
if arg is None:
|
|
1675
|
+
return False
|
|
1676
|
+
else:
|
|
1677
|
+
return True
|
|
1678
|
+
|
|
1679
|
+
#
|
|
1680
|
+
# Splits an object into an array of object with one property each
|
|
1681
|
+
# @param {*} arg - the object to split
|
|
1682
|
+
# @returns {*} - the array
|
|
1683
|
+
#
|
|
1684
|
+
@staticmethod
|
|
1685
|
+
def spread(arg: Optional[Any]) -> Optional[Any]:
|
|
1686
|
+
result = utils.Utils.create_sequence()
|
|
1687
|
+
|
|
1688
|
+
if isinstance(arg, list):
|
|
1689
|
+
# spread all of the items in the array
|
|
1690
|
+
for item in arg:
|
|
1691
|
+
result = Functions.append(result, Functions.spread(item))
|
|
1692
|
+
elif isinstance(arg, dict):
|
|
1693
|
+
for k, v in arg.items():
|
|
1694
|
+
obj = {k: v}
|
|
1695
|
+
result.append(obj)
|
|
1696
|
+
else:
|
|
1697
|
+
return arg # result = arg;
|
|
1698
|
+
return result
|
|
1699
|
+
|
|
1700
|
+
#
|
|
1701
|
+
# Merges an array of objects into a single object. Duplicate properties are
|
|
1702
|
+
# overridden by entries later in the array
|
|
1703
|
+
# @param {*} arg - the objects to merge
|
|
1704
|
+
# @returns {*} - the object
|
|
1705
|
+
#
|
|
1706
|
+
@staticmethod
|
|
1707
|
+
def merge(arg: Optional[Sequence]) -> Optional[dict]:
|
|
1708
|
+
# undefined inputs always return undefined
|
|
1709
|
+
if arg is None:
|
|
1710
|
+
return None
|
|
1711
|
+
|
|
1712
|
+
result = {}
|
|
1713
|
+
|
|
1714
|
+
for obj in arg:
|
|
1715
|
+
for k, v in obj.items():
|
|
1716
|
+
result[k] = v
|
|
1717
|
+
return result
|
|
1718
|
+
|
|
1719
|
+
#
|
|
1720
|
+
# Reverses the order of items in an array
|
|
1721
|
+
# @param {Array} arr - the array to reverse
|
|
1722
|
+
# @returns {Array} - the reversed array
|
|
1723
|
+
#
|
|
1724
|
+
@staticmethod
|
|
1725
|
+
def reverse(arr: Optional[Sequence]) -> Optional[Sequence]:
|
|
1726
|
+
# undefined inputs always return undefined
|
|
1727
|
+
if arr is None:
|
|
1728
|
+
return None
|
|
1729
|
+
|
|
1730
|
+
if len(arr) <= 1:
|
|
1731
|
+
return arr
|
|
1732
|
+
|
|
1733
|
+
result = list(arr)
|
|
1734
|
+
result.reverse()
|
|
1735
|
+
return result
|
|
1736
|
+
|
|
1737
|
+
#
|
|
1738
|
+
#
|
|
1739
|
+
# @param {*} obj - the input object to iterate over
|
|
1740
|
+
# @param {*} func - the function to apply to each key/value pair
|
|
1741
|
+
# @throws Throwable
|
|
1742
|
+
# @returns {Array} - the resultant array
|
|
1743
|
+
#
|
|
1744
|
+
@staticmethod
|
|
1745
|
+
def each(obj: Optional[Mapping], func: Optional[Any]) -> Optional[list]:
|
|
1746
|
+
if obj is None:
|
|
1747
|
+
return None
|
|
1748
|
+
|
|
1749
|
+
result = utils.Utils.create_sequence()
|
|
1750
|
+
|
|
1751
|
+
for key in obj:
|
|
1752
|
+
func_args = Functions.hof_func_args(func, obj[key], key, obj)
|
|
1753
|
+
# invoke func
|
|
1754
|
+
val = Functions.func_apply(func, func_args)
|
|
1755
|
+
if val is not None:
|
|
1756
|
+
result.append(val)
|
|
1757
|
+
|
|
1758
|
+
return result
|
|
1759
|
+
|
|
1760
|
+
#
|
|
1761
|
+
#
|
|
1762
|
+
# @param {string} [message] - the message to attach to the error
|
|
1763
|
+
# @throws custom error with code 'D3137'
|
|
1764
|
+
#
|
|
1765
|
+
@staticmethod
|
|
1766
|
+
def error(message: Optional[str]) -> NoReturn:
|
|
1767
|
+
raise jexception.JException("D3137", -1, message if message is not None else "$error() function evaluated")
|
|
1768
|
+
|
|
1769
|
+
#
|
|
1770
|
+
#
|
|
1771
|
+
# @param {boolean} condition - the condition to evaluate
|
|
1772
|
+
# @param {string} [message] - the message to attach to the error
|
|
1773
|
+
# @throws custom error with code 'D3137'
|
|
1774
|
+
# @returns {undefined}
|
|
1775
|
+
#
|
|
1776
|
+
@staticmethod
|
|
1777
|
+
def assert_fn(condition: Optional[bool], message: Optional[str]) -> None:
|
|
1778
|
+
if condition is utils.Utils.NULL_VALUE:
|
|
1779
|
+
raise jexception.JException("T0410", -1)
|
|
1780
|
+
|
|
1781
|
+
if not condition:
|
|
1782
|
+
raise jexception.JException("D3141", -1, "$assert() statement failed")
|
|
1783
|
+
# message: message || "$assert() statement failed"
|
|
1784
|
+
|
|
1785
|
+
#
|
|
1786
|
+
#
|
|
1787
|
+
# @param {*} [value] - the input to which the type will be checked
|
|
1788
|
+
# @returns {string} - the type of the input
|
|
1789
|
+
#
|
|
1790
|
+
@staticmethod
|
|
1791
|
+
def type(value: Optional[Any]) -> Optional[str]:
|
|
1792
|
+
if value is None:
|
|
1793
|
+
return None
|
|
1794
|
+
|
|
1795
|
+
if value is utils.Utils.NULL_VALUE:
|
|
1796
|
+
return "null"
|
|
1797
|
+
|
|
1798
|
+
if isinstance(value, bool):
|
|
1799
|
+
return "boolean"
|
|
1800
|
+
|
|
1801
|
+
if isinstance(value, (int, float)):
|
|
1802
|
+
return "number"
|
|
1803
|
+
|
|
1804
|
+
if isinstance(value, str):
|
|
1805
|
+
return "string"
|
|
1806
|
+
|
|
1807
|
+
if isinstance(value, list):
|
|
1808
|
+
return "array"
|
|
1809
|
+
|
|
1810
|
+
if utils.Utils.is_function(value) or Functions.is_lambda(value):
|
|
1811
|
+
return "function"
|
|
1812
|
+
|
|
1813
|
+
return "object"
|
|
1814
|
+
|
|
1815
|
+
#
|
|
1816
|
+
# Implements the merge sort (stable) with optional comparator function
|
|
1817
|
+
#
|
|
1818
|
+
# @param {Array} arr - the array to sort
|
|
1819
|
+
# @param {*} comparator - comparator function
|
|
1820
|
+
# @returns {Array} - sorted array
|
|
1821
|
+
#
|
|
1822
|
+
@staticmethod
|
|
1823
|
+
def sort(arr: Optional[Sequence], comparator: Optional[Any]) -> Optional[Sequence]:
|
|
1824
|
+
# undefined inputs always return undefined
|
|
1825
|
+
if arr is None:
|
|
1826
|
+
return None
|
|
1827
|
+
|
|
1828
|
+
if len(arr) <= 1:
|
|
1829
|
+
return arr
|
|
1830
|
+
|
|
1831
|
+
result = list(arr)
|
|
1832
|
+
|
|
1833
|
+
if comparator is not None:
|
|
1834
|
+
comp = Functions.Comparator(comparator).compare
|
|
1835
|
+
result = sorted(result, key=functools.cmp_to_key(comp))
|
|
1836
|
+
else:
|
|
1837
|
+
result = sorted(result)
|
|
1838
|
+
|
|
1839
|
+
return result
|
|
1840
|
+
|
|
1841
|
+
class Comparator:
|
|
1842
|
+
_comparator: Optional[Any]
|
|
1843
|
+
|
|
1844
|
+
def __init__(self, comparator):
|
|
1845
|
+
from jsonata import jsonata
|
|
1846
|
+
if isinstance(comparator, Callable):
|
|
1847
|
+
self._comparator = jsonata.Jsonata.JLambda(comparator)
|
|
1848
|
+
else:
|
|
1849
|
+
self._comparator = comparator
|
|
1850
|
+
|
|
1851
|
+
def compare(self, o1, o2):
|
|
1852
|
+
res = Functions.func_apply(self._comparator, [o1, o2])
|
|
1853
|
+
if isinstance(res, bool):
|
|
1854
|
+
return 1 if res else -1
|
|
1855
|
+
return int(res)
|
|
1856
|
+
|
|
1857
|
+
#
|
|
1858
|
+
# Randomly shuffles the contents of an array
|
|
1859
|
+
# @param {Array} arr - the input array
|
|
1860
|
+
# @returns {Array} the shuffled array
|
|
1861
|
+
#
|
|
1862
|
+
@staticmethod
|
|
1863
|
+
def shuffle(arr: Optional[Sequence]) -> Optional[Sequence]:
|
|
1864
|
+
# undefined inputs always return undefined
|
|
1865
|
+
if arr is None:
|
|
1866
|
+
return None
|
|
1867
|
+
|
|
1868
|
+
if len(arr) <= 1:
|
|
1869
|
+
return arr
|
|
1870
|
+
|
|
1871
|
+
result = list(arr)
|
|
1872
|
+
random.shuffle(result)
|
|
1873
|
+
return result
|
|
1874
|
+
|
|
1875
|
+
#
|
|
1876
|
+
# Returns the values that appear in a sequence, with duplicates eliminated.
|
|
1877
|
+
# @param {Array} arr - An array or sequence of values
|
|
1878
|
+
# @returns {Array} - sequence of distinct values
|
|
1879
|
+
#
|
|
1880
|
+
@staticmethod
|
|
1881
|
+
def distinct(_arr: Optional[Any]) -> Optional[Any]:
|
|
1882
|
+
# undefined inputs always return undefined
|
|
1883
|
+
if _arr is None:
|
|
1884
|
+
return None
|
|
1885
|
+
|
|
1886
|
+
if not (isinstance(_arr, list)) or len(_arr) <= 1:
|
|
1887
|
+
return _arr
|
|
1888
|
+
arr = _arr
|
|
1889
|
+
|
|
1890
|
+
results = utils.Utils.create_sequence() if (isinstance(arr, utils.Utils.JList)) else []
|
|
1891
|
+
|
|
1892
|
+
for el in arr:
|
|
1893
|
+
if el not in results:
|
|
1894
|
+
results.append(el)
|
|
1895
|
+
|
|
1896
|
+
return results
|
|
1897
|
+
|
|
1898
|
+
#
|
|
1899
|
+
# Applies a predicate function to each key/value pair in an object, and returns an object containing
|
|
1900
|
+
# only the key/value pairs that passed the predicate
|
|
1901
|
+
#
|
|
1902
|
+
# @param {object} arg - the object to be sifted
|
|
1903
|
+
# @param {object} func - the predicate function (lambda or native)
|
|
1904
|
+
# @throws Throwable
|
|
1905
|
+
# @returns {object} - sifted object
|
|
1906
|
+
#
|
|
1907
|
+
@staticmethod
|
|
1908
|
+
def sift(arg: Optional[Mapping], func: Optional[Any]) -> Optional[dict]:
|
|
1909
|
+
from jsonata import jsonata
|
|
1910
|
+
if arg is None:
|
|
1911
|
+
return None
|
|
1912
|
+
|
|
1913
|
+
result = {}
|
|
1914
|
+
|
|
1915
|
+
for item, entry in arg.items():
|
|
1916
|
+
func_args = Functions.hof_func_args(func, entry, item, arg)
|
|
1917
|
+
# invoke func
|
|
1918
|
+
res = Functions.func_apply(func, func_args)
|
|
1919
|
+
if jsonata.Jsonata.boolize(res):
|
|
1920
|
+
result[item] = entry
|
|
1921
|
+
|
|
1922
|
+
# empty objects should be changed to undefined
|
|
1923
|
+
if len(result) == 0:
|
|
1924
|
+
result = None
|
|
1925
|
+
|
|
1926
|
+
return result
|
|
1927
|
+
|
|
1928
|
+
# /////
|
|
1929
|
+
# /////
|
|
1930
|
+
# /////
|
|
1931
|
+
# /////
|
|
1932
|
+
|
|
1933
|
+
#
|
|
1934
|
+
# Append second argument to first
|
|
1935
|
+
# @param {Array|Object} arg1 - First argument
|
|
1936
|
+
# @param {Array|Object} arg2 - Second argument
|
|
1937
|
+
# @returns {*} Appended arguments
|
|
1938
|
+
#
|
|
1939
|
+
@staticmethod
|
|
1940
|
+
def append(arg1: Optional[Any], arg2: Optional[Any]) -> Optional[Any]:
|
|
1941
|
+
# disregard undefined args
|
|
1942
|
+
if arg1 is None:
|
|
1943
|
+
return arg2
|
|
1944
|
+
if arg2 is None:
|
|
1945
|
+
return arg1
|
|
1946
|
+
|
|
1947
|
+
# if either argument is not an array, make it so
|
|
1948
|
+
if not (isinstance(arg1, list)):
|
|
1949
|
+
arg1 = utils.Utils.create_sequence(arg1)
|
|
1950
|
+
if not (isinstance(arg2, list)):
|
|
1951
|
+
arg2 = utils.Utils.JList([arg2])
|
|
1952
|
+
# else
|
|
1953
|
+
# // Arg2 was a list: add it as a list element (don't flatten)
|
|
1954
|
+
# ((List)arg1).add((List)arg2)
|
|
1955
|
+
|
|
1956
|
+
arg1 = utils.Utils.JList(arg1) # create a new copy!
|
|
1957
|
+
if isinstance(arg2, utils.Utils.JList) and arg2.cons:
|
|
1958
|
+
arg1.append(arg2)
|
|
1959
|
+
else:
|
|
1960
|
+
arg1.extend(arg2)
|
|
1961
|
+
return arg1
|
|
1962
|
+
|
|
1963
|
+
@staticmethod
|
|
1964
|
+
def is_lambda(result: Optional[Any]) -> bool:
|
|
1965
|
+
return isinstance(result, parser.Parser.Symbol) and result._jsonata_lambda
|
|
1966
|
+
|
|
1967
|
+
#
|
|
1968
|
+
# Return value from an object for a given key
|
|
1969
|
+
# @param {Object} input - Object/Array
|
|
1970
|
+
# @param {String} key - Key in object
|
|
1971
|
+
# @returns {*} Value of key in object
|
|
1972
|
+
#
|
|
1973
|
+
@staticmethod
|
|
1974
|
+
def lookup(input: Union[Mapping, Optional[Sequence]], key: Optional[str]) -> Optional[Any]:
|
|
1975
|
+
# lookup the 'name' item in the input
|
|
1976
|
+
result = None
|
|
1977
|
+
if isinstance(input, list):
|
|
1978
|
+
_input = input
|
|
1979
|
+
result = utils.Utils.create_sequence()
|
|
1980
|
+
for _, inp in enumerate(_input):
|
|
1981
|
+
res = Functions.lookup(inp, key)
|
|
1982
|
+
if res is not None:
|
|
1983
|
+
if isinstance(res, list):
|
|
1984
|
+
result.extend(res)
|
|
1985
|
+
else:
|
|
1986
|
+
result.append(res)
|
|
1987
|
+
elif isinstance(input, dict):
|
|
1988
|
+
result = input.get(key)
|
|
1989
|
+
# Detect the case where the value is null:
|
|
1990
|
+
if result is None and key in input:
|
|
1991
|
+
result = utils.Utils.NULL_VALUE
|
|
1992
|
+
return result
|
|
1993
|
+
|
|
1994
|
+
@staticmethod
|
|
1995
|
+
def test(a: Optional[str], b: Optional[str]) -> str:
|
|
1996
|
+
return a + b
|
|
1997
|
+
|
|
1998
|
+
@staticmethod
|
|
1999
|
+
def get_function(clz: Optional[Type], name: Optional[str]) -> Optional[Any]:
|
|
2000
|
+
if name is None:
|
|
2001
|
+
return None
|
|
2002
|
+
return getattr(clz, name)
|
|
2003
|
+
|
|
2004
|
+
@staticmethod
|
|
2005
|
+
def call(clz: Optional[Type], name: Optional[str], args: Optional[Sequence]) -> Optional[Any]:
|
|
2006
|
+
return Functions._call(Functions.get_function(clz, name), args)
|
|
2007
|
+
|
|
2008
|
+
@staticmethod
|
|
2009
|
+
def _call(m: Callable, args: Optional[Sequence]) -> Optional[Any]:
|
|
2010
|
+
nargs = len(inspect.signature(m).parameters)
|
|
2011
|
+
|
|
2012
|
+
call_args = list(args)
|
|
2013
|
+
while len(call_args) < nargs:
|
|
2014
|
+
# Add default arg null if not enough args were provided
|
|
2015
|
+
call_args.append(None)
|
|
2016
|
+
|
|
2017
|
+
res = m(*call_args)
|
|
2018
|
+
if utils.Utils.is_numeric(res):
|
|
2019
|
+
res = utils.Utils.convert_number(res)
|
|
2020
|
+
return res
|
|
2021
|
+
|
|
2022
|
+
#
|
|
2023
|
+
# DateTime
|
|
2024
|
+
#
|
|
2025
|
+
|
|
2026
|
+
#
|
|
2027
|
+
# Converts an ISO 8601 timestamp to milliseconds since the epoch
|
|
2028
|
+
#
|
|
2029
|
+
# @param {string} timestamp - the timestamp to be converted
|
|
2030
|
+
# @param {string} [picture] - the picture string defining the format of the timestamp (defaults to ISO 8601)
|
|
2031
|
+
# @throws ParseException
|
|
2032
|
+
# @returns {Number} - milliseconds since the epoch
|
|
2033
|
+
#
|
|
2034
|
+
@staticmethod
|
|
2035
|
+
def datetime_to_millis(timestamp: Optional[str], picture: Optional[str]) -> Optional[int]:
|
|
2036
|
+
# undefined inputs always return undefined
|
|
2037
|
+
if timestamp is None:
|
|
2038
|
+
return None
|
|
2039
|
+
|
|
2040
|
+
if picture is None:
|
|
2041
|
+
if Functions.is_numeric(timestamp):
|
|
2042
|
+
dt = datetime.datetime.strptime(timestamp, "%Y")
|
|
2043
|
+
else:
|
|
2044
|
+
dt = datetime.datetime.fromisoformat(timestamp)
|
|
2045
|
+
dt = dt.replace(tzinfo=datetime.timezone.utc)
|
|
2046
|
+
return int(dt.timestamp() * 1000)
|
|
2047
|
+
# try:
|
|
2048
|
+
# size = len(timestamp)
|
|
2049
|
+
# if size > 5:
|
|
2050
|
+
# if timestamp[size - 5] == '+' or timestamp[size - 5] == '-':
|
|
2051
|
+
# if (timestamp[size - 4]).isdigit() and (timestamp[size - 3]).isdigit() and (
|
|
2052
|
+
# timestamp[size - 2]).isdigit() and (timestamp[size - 1]).isdigit():
|
|
2053
|
+
# timestamp = timestamp[0:size - 2] + ':' + timestamp[size - 2:size]
|
|
2054
|
+
# return java.time.OffsetDateTime.parse(timestamp).toInstant().toEpochMilli()
|
|
2055
|
+
# except RuntimeError as e:
|
|
2056
|
+
# ldt = java.time.LocalDate.parse(timestamp, java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd"))
|
|
2057
|
+
# return ldt.atStartOfDay().atZone(java.time.ZoneId.of("UTC")).toInstant().toEpochMilli()
|
|
2058
|
+
else:
|
|
2059
|
+
return datetimeutils.DateTimeUtils.parse_datetime(timestamp, picture)
|
|
2060
|
+
|
|
2061
|
+
# Adapted from: org.apache.commons.lang3.StringUtils
|
|
2062
|
+
@staticmethod
|
|
2063
|
+
def is_numeric(cs: Optional[Sequence]) -> bool:
|
|
2064
|
+
if cs is None or len(cs) == 0:
|
|
2065
|
+
return False
|
|
2066
|
+
sz = len(cs)
|
|
2067
|
+
for i in range(0, sz):
|
|
2068
|
+
if not cs[i].isdigit():
|
|
2069
|
+
return False
|
|
2070
|
+
return True
|
|
2071
|
+
|
|
2072
|
+
#
|
|
2073
|
+
# Converts milliseconds since the epoch to an ISO 8601 timestamp
|
|
2074
|
+
# @param {Number} millis - milliseconds since the epoch to be converted
|
|
2075
|
+
# @param {string} [picture] - the picture string defining the format of the timestamp (defaults to ISO 8601)
|
|
2076
|
+
# @param {string} [timezone] - the timezone to format the timestamp in (defaults to UTC)
|
|
2077
|
+
# @returns {String} - the formatted timestamp
|
|
2078
|
+
#
|
|
2079
|
+
@staticmethod
|
|
2080
|
+
def datetime_from_millis(millis: Optional[float], picture: Optional[str], timezone: Optional[str]) -> Optional[str]:
|
|
2081
|
+
# undefined inputs always return undefined
|
|
2082
|
+
if millis is None:
|
|
2083
|
+
return None
|
|
2084
|
+
|
|
2085
|
+
return datetimeutils.DateTimeUtils.format_datetime(int(millis), picture, timezone)
|
|
2086
|
+
|
|
2087
|
+
#
|
|
2088
|
+
# Formats an integer as specified by the XPath fn:format-integer function
|
|
2089
|
+
# See https://www.w3.org/TR/xpath-functions-31/#func-format-integer
|
|
2090
|
+
# @param {number} value - the number to be formatted
|
|
2091
|
+
# @param {string} picture - the picture string that specifies the format
|
|
2092
|
+
# @returns {string} - the formatted number
|
|
2093
|
+
#
|
|
2094
|
+
@staticmethod
|
|
2095
|
+
def format_integer(value: Optional[float], picture: Optional[str]) -> Optional[str]:
|
|
2096
|
+
if value is None:
|
|
2097
|
+
return None
|
|
2098
|
+
return datetimeutils.DateTimeUtils.format_integer(int(value), picture)
|
|
2099
|
+
|
|
2100
|
+
#
|
|
2101
|
+
# parse a string containing an integer as specified by the picture string
|
|
2102
|
+
# @param {string} value - the string to parse
|
|
2103
|
+
# @param {string} picture - the picture string
|
|
2104
|
+
# @throws ParseException
|
|
2105
|
+
# @returns {number} - the parsed number
|
|
2106
|
+
#
|
|
2107
|
+
@staticmethod
|
|
2108
|
+
def parse_integer(value: Optional[str], picture: Optional[str]) -> Optional[int]:
|
|
2109
|
+
if value is None:
|
|
2110
|
+
return None
|
|
2111
|
+
return datetimeutils.DateTimeUtils.parse_integer(value, picture)
|
|
2112
|
+
|
|
2113
|
+
#
|
|
2114
|
+
# Clones an object
|
|
2115
|
+
# @param {Object} arg - object to clone (deep copy)
|
|
2116
|
+
# @returns {*} - the cloned object
|
|
2117
|
+
#
|
|
2118
|
+
@staticmethod
|
|
2119
|
+
def function_clone(arg: Optional[Any]) -> Optional[Any]:
|
|
2120
|
+
# undefined inputs always return undefined
|
|
2121
|
+
if arg is None:
|
|
2122
|
+
return None
|
|
2123
|
+
|
|
2124
|
+
res = json.loads(Functions.string(arg, False))
|
|
2125
|
+
return res
|
|
2126
|
+
|
|
2127
|
+
#
|
|
2128
|
+
# parses and evaluates the supplied expression
|
|
2129
|
+
# @param {string} expr - expression to evaluate
|
|
2130
|
+
# @returns {*} - result of evaluating the expression
|
|
2131
|
+
#
|
|
2132
|
+
@staticmethod
|
|
2133
|
+
def function_eval(expr: Optional[str], focus: Optional[Any]) -> Optional[Any]:
|
|
2134
|
+
from jsonata import jsonata
|
|
2135
|
+
# undefined inputs always return undefined
|
|
2136
|
+
if expr is None:
|
|
2137
|
+
return None
|
|
2138
|
+
input = jsonata.Jsonata.CURRENT.jsonata.input # = this.input;
|
|
2139
|
+
if focus is not None:
|
|
2140
|
+
input = focus
|
|
2141
|
+
# if the input is a JSON array, then wrap it in a singleton sequence so it gets treated as a single input
|
|
2142
|
+
if (isinstance(input, list)) and not utils.Utils.is_sequence(input):
|
|
2143
|
+
input = utils.Utils.create_sequence(input)
|
|
2144
|
+
input.outer_wrapper = True
|
|
2145
|
+
|
|
2146
|
+
ast = None
|
|
2147
|
+
try:
|
|
2148
|
+
ast = jsonata.Jsonata(expr)
|
|
2149
|
+
except Exception as err:
|
|
2150
|
+
# error parsing the expression passed to $eval
|
|
2151
|
+
# populateMessage(err)
|
|
2152
|
+
raise jexception.JException("D3120", -1)
|
|
2153
|
+
result = None
|
|
2154
|
+
try:
|
|
2155
|
+
result = ast.evaluate(input, jsonata.Jsonata.CURRENT.jsonata.environment)
|
|
2156
|
+
except Exception as err:
|
|
2157
|
+
# error evaluating the expression passed to $eval
|
|
2158
|
+
# populateMessage(err)
|
|
2159
|
+
raise jexception.JException("D3121", -1)
|
|
2160
|
+
|
|
2161
|
+
return result
|
|
2162
|
+
|
|
2163
|
+
# environment.bind("now", defineFunction(function(picture, timezone) {
|
|
2164
|
+
# return datetime.fromMillis(timestamp.getTime(), picture, timezone)
|
|
2165
|
+
# }, "<s?s?:s>"))
|
|
2166
|
+
@staticmethod
|
|
2167
|
+
def now(picture: Optional[str], timezone: Optional[str]) -> Optional[str]:
|
|
2168
|
+
from jsonata import jsonata
|
|
2169
|
+
t = jsonata.Jsonata.CURRENT.jsonata.timestamp
|
|
2170
|
+
return Functions.datetime_from_millis(t, picture, timezone)
|
|
2171
|
+
|
|
2172
|
+
# environment.bind("millis", defineFunction(function() {
|
|
2173
|
+
# return timestamp.getTime()
|
|
2174
|
+
# }, "<:n>"))
|
|
2175
|
+
@staticmethod
|
|
2176
|
+
def millis() -> int:
|
|
2177
|
+
from jsonata import jsonata
|
|
2178
|
+
t = jsonata.Jsonata.CURRENT.jsonata.timestamp
|
|
2179
|
+
return t
|