diff --git a/aas_core_codegen/parse/retree/_render.py b/aas_core_codegen/parse/retree/_render.py index c0f17c1f3..2806e5c6b 100644 --- a/aas_core_codegen/parse/retree/_render.py +++ b/aas_core_codegen/parse/retree/_render.py @@ -178,7 +178,7 @@ def transform_quantifier( if node.maximum == 1: quantifier = "?" else: - quantifier = f"{{,{node.maximum}}}" + quantifier = f"{{0,{node.maximum}}}" else: quantifier = f"{{{node.minimum},{node.maximum}}}" else: diff --git a/aas_core_codegen/xsd/main.py b/aas_core_codegen/xsd/main.py index 4b6fffa74..7f8da7e7b 100644 --- a/aas_core_codegen/xsd/main.py +++ b/aas_core_codegen/xsd/main.py @@ -127,6 +127,12 @@ def _undo_escaping_backslash_x_u_and_U_in_pattern(pattern: str) -> str: class _AnchorRemover(parse_retree.BaseVisitor): """ Remove anchors from a regex in-place. + + We need to remove the anchors (``^``, ``$``) since patterns in the XSD are always + anchored. + + This is necessary since otherwise the schema validation fails. + See: https://stackoverflow.com/questions/4367914/regular-expression-in-xml-schema-definition-fails """ def visit_concatenation(self, node: parse_retree.Concatenation) -> None: @@ -146,15 +152,10 @@ def visit_concatenation(self, node: parse_retree.Concatenation) -> None: @ensure(lambda result: (result[0] is not None) ^ (result[1] is not None)) -def _remove_anchors_in_pattern(pattern: str) -> Tuple[Optional[str], Optional[str]]: - """ - We need to remove the anchors (``^``, ``$``) since schemas are always anchored. - - This is necessary since otherwise the schema validation fails. - See: https://stackoverflow.com/questions/4367914/regular-expression-in-xml-schema-definition-fails +def _translate_pattern(pattern: str) -> Tuple[Optional[str], Optional[str]]: + """Translate the pattern to obtain the equivalent in XSD.""" + pattern = _undo_escaping_backslash_x_in_pattern(pattern) - Return pattern without anchors, or error message. - """ parsed, error = parse_retree.parse(values=[pattern]) if error is not None: regex_line, pointer_line = parse_retree.render_pointer(error.cursor) @@ -176,18 +177,6 @@ def _remove_anchors_in_pattern(pattern: str) -> Tuple[Optional[str], Optional[st return "".join(parts), None -@ensure(lambda result: (result[0] is not None) ^ (result[1] is not None)) -def _translate_pattern(pattern: str) -> Tuple[Optional[str], Optional[str]]: - """Translate the pattern to obtain the equivalent in XSD.""" - result, error = _remove_anchors_in_pattern( - _undo_escaping_backslash_x_in_pattern(pattern) - ) - if error is not None: - return None, error - - return result, None - - def _generate_xs_restriction( base_type: intermediate.PrimitiveType, len_constraint: Optional[infer_for_schema.LenConstraint], diff --git a/test_data/parse_retree/expected/quantifier/greedy/at_most_3/rendered_regex.txt b/test_data/parse_retree/expected/quantifier/greedy/at_most_3/rendered_regex.txt index f5b5fb7c9..d265c9ef2 100644 --- a/test_data/parse_retree/expected/quantifier/greedy/at_most_3/rendered_regex.txt +++ b/test_data/parse_retree/expected/quantifier/greedy/at_most_3/rendered_regex.txt @@ -1 +1 @@ -a{,3} \ No newline at end of file +a{0,3} \ No newline at end of file diff --git a/test_data/parse_retree/expected/quantifier/non_greedy/at_most_3/rendered_regex.txt b/test_data/parse_retree/expected/quantifier/non_greedy/at_most_3/rendered_regex.txt index d8bc4a5f1..7ea8b3a61 100644 --- a/test_data/parse_retree/expected/quantifier/non_greedy/at_most_3/rendered_regex.txt +++ b/test_data/parse_retree/expected/quantifier/non_greedy/at_most_3/rendered_regex.txt @@ -1 +1 @@ -a{,3}? \ No newline at end of file +a{0,3}? \ No newline at end of file diff --git a/tests/xsd/test_main.py b/tests/xsd/test_main.py index 16127d741..079172b4b 100644 --- a/tests/xsd/test_main.py +++ b/tests/xsd/test_main.py @@ -69,18 +69,36 @@ def test_complex(self) -> None: ) -class Test_removing_anchors_in_patterns(unittest.TestCase): +class Test_translate_pattern(unittest.TestCase): # NOTE (mristin, 2022-06-18): # This is relevant since XSD are always anchored. # See: https://stackoverflow.com/questions/4367914/regular-expression-in-xml-schema-definition-fails - def test_table(self) -> None: + def test_table_for_removing_anchors(self) -> None: for pattern, expected, identifier in [ ("^$", "", "empty"), ("^something$", "something", "simple_literal"), ("(^.*$)", "(.*)", "within_a_group"), ]: - fixed, error = xsd_main._remove_anchors_in_pattern(pattern) + fixed, error = xsd_main._translate_pattern(pattern) + assert error is None, identifier + assert fixed is not None, identifier + + self.assertEqual(expected, fixed, identifier) + + def test_table_for_rendering_quantifiers(self) -> None: + # NOTE (mristin, 2024-03-22): + # We explicitly test for quantifiers to make sure that they all comply with + # XSD patterns. For example, when only the maximum quantifier is given, + # the minimum quantifier of 0 must be indicated explicitly. + for pattern, expected, identifier in [ + ("a{1}", "a{1}", "exact repetition"), + ("a{1,}", "a+", "min 1 repetition"), + ("a{2,}", "a{2,}", "more than 1 min repetition"), + ("a{,2}", "a{0,2}", "only max repetition"), + ("a{1,2}", "a{1,2}", "min and max repetition"), + ]: + fixed, error = xsd_main._translate_pattern(pattern) assert error is None, identifier assert fixed is not None, identifier