diff --git a/xmlschema/tests/test_models.py b/xmlschema/tests/test_models.py index 4f101c9..df19ae1 100644 --- a/xmlschema/tests/test_models.py +++ b/xmlschema/tests/test_models.py @@ -516,6 +516,8 @@ class TestModelValidation(XsdValidatorTestCase): self.check_advance_true(model) # match choice with self.assertIsNone(model.element) + # + # Test pathological cases def test_empty_choice_groups(self): schema = self.schema_class(""" @@ -549,17 +551,35 @@ class TestModelValidation(XsdValidatorTestCase): - + """) - xml_data = '' - + xml_data = '' self.assertIsNone(schema.validate(xml_data)) + def test_choice_model_with_extended_occurs(self): + schema = self.schema_class( + """ + + + + + + + + + + + """) + + self.assertIsNone(schema.validate('')) + self.assertIsNone(schema.validate('')) + self.assertIsNone(schema.validate('')) + # # Tests on issues def test_issue_086(self): diff --git a/xmlschema/validators/exceptions.py b/xmlschema/validators/exceptions.py index d47d60a..b766ac4 100644 --- a/xmlschema/validators/exceptions.py +++ b/xmlschema/validators/exceptions.py @@ -346,7 +346,7 @@ class XMLSchemaChildrenValidationError(XMLSchemaValidationError): if not expected_tags: pass # reason += " No child element is expected at this point." <-- this can be misleading elif len(expected_tags) == 1: - reason += " Tag %s expected." % expected_tags[0] + reason += " Tag %r expected." % expected_tags[0] else: reason += " Tag (%s) expected." % ' | '.join(expected_tags) diff --git a/xmlschema/validators/models.py b/xmlschema/validators/models.py index fc4b9af..c63ca14 100644 --- a/xmlschema/validators/models.py +++ b/xmlschema/validators/models.py @@ -156,33 +156,6 @@ class ModelGroup(MutableSequence, ParticleMixin): else: return self.max_occurs * sum(e.max_occurs for e in self) <= other.max_occurs - def count_occurs(self, occurs): - """ - Calculates the current model group occurrences from the occurs of its items. - """ - group_occurs = None - if self.model == 'sequence': - for item in filter(lambda x: occurs[x], self): - if group_occurs is not None: - return 1 - group_occurs = item.min_occurs_reps(occurs) - - elif self.model == 'choice': - for item in filter(lambda x: occurs[x], self): - group_occurs = item.min_occurs_reps(occurs) - break - - else: - for item in filter(lambda x: occurs[x], self): - group_occurs = min(1, item.min_occurs_reps(occurs)) - - if group_occurs is None: - return 0 - elif self.is_over(group_occurs): - return self.max_occurs - else: - return group_occurs - def iter_model(self, depth=0): """ A generator function iterating elements and groups of a model group. Skips pointless groups, @@ -486,27 +459,47 @@ class ModelVisitor(MutableSequence): if model == 'all': return False - elif item_occurs: + elif model == 'choice': + if not item_occurs: + return False + self.match = True - if model == 'choice': - occurs[self.group] += max(1, self.group.count_occurs(self.occurs)) + + 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 + + if group_occurs == 1: occurs[item] = 0 - self.items, self.match = self.iter_group(), False - elif model == 'sequence' and item is self.group[-1]: - self.occurs[self.group] += max(1, self.group.count_occurs(self.occurs)) + else: + item_occurs %= item.min_occurs + occurs[item] = item_occurs + + self.items, self.match = self.iter_group(), False return item.is_missing(item_occurs) - elif model == 'sequence': - if self.match: - if item is self.group[-1]: - occurs[self.group] += max(1, self.group.count_occurs(self.occurs)) - return not item.is_emptiable() - elif item.is_emptiable(): - return False - elif self.group.min_occurs <= occurs[self.group] or self: - return stop_item(self.group) + elif item_occurs: + self.match = True + elif self.match: + pass + elif item.is_emptiable(): + return False + elif self.group.min_occurs <= occurs[self.group] or self: + return stop_item(self.group) + else: + return True + + if item is self.group[-1]: + if any(occurs[x] for x in self if x is not item): + group_occurs = 1 else: - return True + group_occurs = max(1, occurs[item] // (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) element, occurs = self.element, self.occurs if element is None: diff --git a/xmlschema/validators/xsdbase.py b/xmlschema/validators/xsdbase.py index 65ae512..aab0b89 100644 --- a/xmlschema/validators/xsdbase.py +++ b/xmlschema/validators/xsdbase.py @@ -958,13 +958,6 @@ class ParticleMixin(object): def is_over(self, occurs): return self.max_occurs is not None and self.max_occurs <= occurs - def min_occurs_reps(self, occurs): - """Returns the repetitions of minimum occurrences.""" - if not self.min_occurs: - return occurs[self] - else: - return occurs[self] // self.min_occurs - def has_occurs_restriction(self, other): if self.min_occurs == self.max_occurs == 0: return True