diff --git a/src/jsonata/functions.py b/src/jsonata/functions.py index 262ee21..e6a42fd 100644 --- a/src/jsonata/functions.py +++ b/src/jsonata/functions.py @@ -224,14 +224,18 @@ def validate_input(arg: Optional[Any]) -> None: # @staticmethod def substring(string: Optional[str], start: Optional[float], length: Optional[float]) -> Optional[str]: + if string is utils.Utils.NULL_VALUE or start is utils.Utils.NULL_VALUE or length is utils.Utils.NULL_VALUE: + raise jexception.JException("T0410", -1) + # undefined inputs always return undefined if string is None: return None - if string is utils.Utils.NULL_VALUE: - raise jexception.JException("T0410", -1) + # match JS: undefined start returns the whole string, or "" if a length is given + if start is None: + return string if length is None else "" - start = int(start) if start is not None else None + start = int(start) length = int(length) if length is not None else None # not used: var strArray = stringToArray(string) @@ -308,13 +312,13 @@ def substr(string: str, start: int, length: int) -> str: # @staticmethod def substring_before(string: Optional[str], chars: Optional[str]) -> Optional[str]: + if string is utils.Utils.NULL_VALUE or chars is utils.Utils.NULL_VALUE: + raise jexception.JException("T0410", -1) + # undefined inputs always return undefined if string is None: return None - if string is utils.Utils.NULL_VALUE: - raise jexception.JException("T0410", -1) - if chars is None: return string @@ -332,12 +336,15 @@ def substring_before(string: Optional[str], chars: Optional[str]) -> Optional[st # @staticmethod def substring_after(string: Optional[str], chars: Optional[str]) -> Optional[str]: + if string is utils.Utils.NULL_VALUE or chars is utils.Utils.NULL_VALUE: + raise jexception.JException("T0410", -1) + # undefined inputs always return undefined if string is None: return None - if string is utils.Utils.NULL_VALUE: - raise jexception.JException("T0410", -1) + if chars is None: + return string pos = string.find(chars) if pos > -1: @@ -433,22 +440,25 @@ def trim(string: Optional[str]) -> Optional[str]: # @staticmethod def pad(string: Optional[str], width: Optional[int], char: Optional[str]) -> Optional[str]: + if string is utils.Utils.NULL_VALUE or width is utils.Utils.NULL_VALUE or char is utils.Utils.NULL_VALUE: + raise jexception.JException("T0410", -1) + # undefined inputs always return undefined if string is None: return None - if string is utils.Utils.NULL_VALUE: - raise jexception.JException("T0410", -1) - if char is None or not char: char = " " + # match JS: undefined width adds no padding + if width is None: + return string + # match JS: truncate width to integer - if width is not None: - try: - width = int(width) - except Exception: - width = 0 + try: + width = int(width) + except Exception: + width = 0 if width < 0: result = Functions.left_pad(string, -width, char) diff --git a/tests/string_test.py b/tests/string_test.py index 7f20608..cae54e6 100644 --- a/tests/string_test.py +++ b/tests/string_test.py @@ -1,4 +1,5 @@ import jsonata +import pytest from jsonata.regex_engine import default_regex_engine @@ -147,6 +148,23 @@ def test_trim(self): assert jsonata.Jsonata("$trim(\"\")").evaluate(None) == "" assert jsonata.Jsonata("$trim(notthere)").evaluate(None) is None + def test_undefined_args(self): + assert jsonata.Jsonata("$substring('abc', $nothing)").evaluate(None) == "abc" + assert jsonata.Jsonata("$substring('abc', $nothing, 1)").evaluate(None) == "" + assert jsonata.Jsonata("$substring(?, $nothing)('abc')").evaluate(None) == "abc" + assert jsonata.Jsonata("$substringAfter('abc', $nothing)").evaluate(None) == "abc" + assert jsonata.Jsonata("$pad('abc', $nothing)").evaluate(None) == "abc" + + def test_null_args(self): + for expr in ["$substring('abc', null)", "$substring('abc', 1, null)", "$substring('abc', $nothing, null)", + "$substring($nothing, null)", "$substring($nothing, 1, null)", + "$pad('abc', null)", "$pad('abc', 5, null)", "$pad($nothing, null)", + "$substringBefore('abc', null)", "$substringBefore($nothing, null)", + "$substringAfter('abc', null)", "$substringAfter($nothing, null)"]: + with pytest.raises(jsonata.JException) as exc_info: + jsonata.Jsonata(expr).evaluate(None) + assert exc_info.value.error == "T0410" + def test_match_limit(self): res = jsonata.Jsonata("$match('ababab', /ab/)").evaluate(None) assert res is not None