From 79cf89af86efb95ef1c9db747f9194a45c735bc3 Mon Sep 17 00:00:00 2001 From: Davide Brunato Date: Thu, 14 Nov 2019 11:14:51 +0100 Subject: [PATCH] Fix ambiguous choice models visiting - Fixed registering max occurs for tuple (group,) - TODO: maybe the same solution for 1-length sequence groups --- xmlschema/tests/test_models.py | 71 ++++++++++++++++++-- xmlschema/validators/models.py | 114 ++++++++++++++++++--------------- 2 files changed, 125 insertions(+), 60 deletions(-) diff --git a/xmlschema/tests/test_models.py b/xmlschema/tests/test_models.py index df19ae1..b671cd6 100644 --- a/xmlschema/tests/test_models.py +++ b/xmlschema/tests/test_models.py @@ -558,8 +558,48 @@ class TestModelValidation(XsdValidatorTestCase): """) - xml_data = '' - self.assertIsNone(schema.validate(xml_data)) + self.assertIsNone(schema.validate('')) + + schema = self.schema_class( + """ + + + + + + + + + + """) + + self.assertIsNone(schema.validate('')) + self.assertIsNone(schema.validate('')) + self.assertIsNone(schema.validate('')) + + schema = self.schema_class( + """ + + + + + + + + + + + + + + + + + """) + + self.assertIsNone(schema.validate('')) + # self.assertIsNone(schema.validate('')) + # self.assertIsNone(schema.validate('')) def test_choice_model_with_extended_occurs(self): schema = self.schema_class( @@ -568,17 +608,34 @@ class TestModelValidation(XsdValidatorTestCase): - - + + """) - self.assertIsNone(schema.validate('')) - self.assertIsNone(schema.validate('')) - self.assertIsNone(schema.validate('')) + self.assertIsNone(schema.validate('')) + self.assertIsNone(schema.validate('')) + self.assertIsNone(schema.validate('')) + + schema = self.schema_class( + """ + + + + + + + + + + + + """) + + self.assertIsNone(schema.validate('')) # # Tests on issues diff --git a/xmlschema/validators/models.py b/xmlschema/validators/models.py index c63ca14..eb79aec 100644 --- a/xmlschema/validators/models.py +++ b/xmlschema/validators/models.py @@ -393,7 +393,9 @@ class ModelVisitor(MutableSequence): break elif item: self.append((self.group, self.items, self.match)) - self.group, self.items, self.match = item, iter(item), False + self.group = item + self.items = self.iter_group() + self.match = False @property def expected(self): @@ -426,16 +428,13 @@ class ModelVisitor(MutableSequence): yield e def iter_group(self): + """Returns an iterator for the current model group.""" if self.group.model != 'all': - for item in self.group: - yield item + return iter(self.group) elif not self.occurs: - for e in self.group.iter_elements(): - yield e + return self.group.iter_elements() else: - for e in self.group.iter_elements(): - if not e.is_over(self.occurs[e]): - yield e + return (e for e in self.group.iter_elements() if not e.is_over(self.occurs[e])) def advance(self, match=False): """ @@ -444,6 +443,17 @@ class ModelVisitor(MutableSequence): :param match: provides current element match. """ + def get_choices(self, occurs): + max_group_occurs = max(1, occurs // (self.min_occurs or 1)) + if self.max_occurs is None: + return [x for x in range(1, max_group_occurs + 1)] + else: + delta_occurs = self.max_occurs - self.min_occurs + 1 + if occurs % max_group_occurs > delta_occurs: + return [] + else: + return [x for x in range(1, max_group_occurs + 1)] + def stop_item(item): """ Stops element or group matching, incrementing current group counter. @@ -455,30 +465,24 @@ class ModelVisitor(MutableSequence): self.group, self.items, self.match = self.pop() item_occurs = occurs[item] - model = self.group.model - if model == 'all': - return False - - elif model == 'choice': + if self.group.model == 'choice': if not item_occurs: return False - self.match = True + item_max_occurs = occurs[(item,)] or item_occurs + min_group_occurs = max(1, item_occurs // (item.max_occurs or item_occurs)) + max_group_occurs = max(1, item_max_occurs // (item.min_occurs or 1)) - group_occurs = min(1, occurs[item] // (item.min_occurs or 1)) - if self.group.is_over(group_occurs): - group_occurs = self.group.max_occurs - occurs[self.group] += group_occurs + occurs[self.group] += min_group_occurs + occurs[(self.group,)] += max_group_occurs + occurs[item] = 0 - if group_occurs == 1: - occurs[item] = 0 - else: - item_occurs %= item.min_occurs - occurs[item] = item_occurs - - self.items, self.match = self.iter_group(), False - return item.is_missing(item_occurs) + self.items = self.iter_group() + self.match = False + return item.is_missing(max(item_occurs, occurs[(item,)])) + elif self.group.model == 'all': + return False elif item_occurs: self.match = True elif self.match: @@ -494,12 +498,11 @@ class ModelVisitor(MutableSequence): if any(occurs[x] for x in self if x is not item): group_occurs = 1 else: - group_occurs = max(1, occurs[item] // (item.min_occurs or 1)) + group_occurs = max(1, item_occurs // (item.min_occurs or 1)) if self.group.is_over(group_occurs): group_occurs = self.group.max_occurs self.occurs[self.group] += max(1, group_occurs) - - return item.is_missing(item_occurs) + return item.is_missing(max(item_occurs, occurs[(item,)])) element, occurs = self.element, self.occurs if element is None: @@ -510,6 +513,9 @@ class ModelVisitor(MutableSequence): self.match = True if self.group.model == 'all': self.items = (e for e in self.group.iter_elements() if not e.is_over(occurs[e])) + elif self.group.model == 'choice': # or len(self.group) == 1: + if not element.is_over(occurs[element]) or element.is_ambiguous(): + return elif not element.is_over(occurs[element]): return @@ -523,40 +529,42 @@ class ModelVisitor(MutableSequence): stop_item(self.group) obj = next(self.items, None) - if obj is None: - if not self.match: - if self.group.model == 'all': - if all(e.min_occurs <= occurs[e] for e in self.group.iter_elements()): - occurs[self.group] = 1 - group, expected = self.group, self.expected - if stop_item(group) and expected: - yield group, occurs[group], expected - elif self.group.model != 'all': - self.items, self.match = self.iter_group(), False - elif any(not e.is_over(occurs[e]) for e in self.group): - self.items = self.iter_group() - self.match = False - else: - occurs[self.group] = 1 + if isinstance(obj, ModelGroup): + # inner 'sequence' or 'choice' XsdGroup + self.append((self.group, self.items, self.match)) + self.group = obj + self.items = self.iter_group() + self.match = False + occurs[obj] = 0 - elif not isinstance(obj, ModelGroup): # XsdElement or XsdAnyElement + elif obj is not None: + # XsdElement or XsdAnyElement self.element = obj - if self.group.model != 'all': + if self.group.model == 'sequence': occurs[obj] = 0 return + elif not self.match: + if self.group.model == 'all': + if all(e.min_occurs <= occurs[e] for e in self.group.iter_elements()): + occurs[self.group] = 1 + + group, expected = self.group, self.expected + if stop_item(group) and expected: + yield group, occurs[group], expected + + elif self.group.model != 'all': + self.items, self.match = self.iter_group(), False + elif any(not e.is_over(occurs[e]) for e in self.group): + self.items = self.iter_group() + self.match = False else: - self.append((self.group, self.items, self.match)) - self.group, self.items, self.match = obj, iter(obj), False - occurs[obj] = 0 - if obj.model == 'all': - for e in obj: - occurs[e] = 0 + occurs[self.group] = 1 except IndexError: # Model visit ended self.element = None - if self.group.is_missing(occurs[self.group]): + if self.group.is_missing(max(occurs[self.group], occurs[(self.group,)])): if self.group.model == 'choice': yield self.group, occurs[self.group], self.expected elif self.group.model == 'sequence':