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/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