From a60532a3ab01d71074964cf3bdd388933783f7ed Mon Sep 17 00:00:00 2001 From: Davide Brunato Date: Fri, 15 Nov 2019 17:49:46 +0100 Subject: [PATCH] Fix sequence model stop criteria --- CHANGELOG.rst | 8 ++++--- xmlschema/documents.py | 2 +- xmlschema/tests/test_models.py | 31 +++++++++++++++++++++------ xmlschema/validators/models.py | 39 +++++++++++++++++++++------------- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0af63c5..f884662 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,11 +2,12 @@ CHANGELOG ********* -`v1.0.16`_ (2019-10-XX) +`v1.0.16`_ (2019-11-15) ======================= -* Improved XMLResource with zip files interface and lazy +* Improved XMLResource class for working with compressed files * Fix for validation with XSD wildcards and 'lax' process content -* Fix for issue #1... +* Fix ambiguous items validation for xs:choice and xs:sequence models +* Dozens of W3C's failed tests fixed `v1.0.15`_ (2019-10-13) ======================= @@ -271,3 +272,4 @@ v0.9.6 (2017-05-05) .. _v1.0.13: https://github.com/brunato/xmlschema/compare/v1.0.11...v1.0.13 .. _v1.0.14: https://github.com/brunato/xmlschema/compare/v1.0.13...v1.0.14 .. _v1.0.15: https://github.com/brunato/xmlschema/compare/v1.0.14...v1.0.15 +.. _v1.0.16: https://github.com/brunato/xmlschema/compare/v1.0.15...v1.0.16 diff --git a/xmlschema/documents.py b/xmlschema/documents.py index 439a8c9..02e6471 100644 --- a/xmlschema/documents.py +++ b/xmlschema/documents.py @@ -171,7 +171,7 @@ def from_json(source, schema, path=None, converter=None, json_options=None, **kw :param source: can be a string or a :meth:`read()` supporting file-like object \ containing the JSON document. - :param schema: an :class:`XMLSchema` instance. + :param schema: an :class:`XMLSchema` or an :class:`XMLSchema11` instance. :param path: is an optional XPath expression for selecting the element of the schema \ that matches the data that has to be encoded. For default the first global element of \ the schema is used. diff --git a/xmlschema/tests/test_models.py b/xmlschema/tests/test_models.py index b671cd6..a02b9b7 100644 --- a/xmlschema/tests/test_models.py +++ b/xmlschema/tests/test_models.py @@ -551,14 +551,14 @@ class TestModelValidation(XsdValidatorTestCase): - + """) - self.assertIsNone(schema.validate('')) + self.assertIsNone(schema.validate('')) schema = self.schema_class( """ @@ -577,6 +577,8 @@ class TestModelValidation(XsdValidatorTestCase): self.assertIsNone(schema.validate('')) self.assertIsNone(schema.validate('')) + def test_sequence_model_with_nested_choice_model(self): + schema = self.schema_class( """ @@ -589,7 +591,7 @@ class TestModelValidation(XsdValidatorTestCase): - + @@ -597,9 +599,26 @@ 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('')) + + def test_sequence_model_with_optional_elements(self): + schema = self.schema_class( + """ + + + + + + + + + + + """) + + self.assertIsNone(schema.validate('')) def test_choice_model_with_extended_occurs(self): schema = self.schema_class( diff --git a/xmlschema/validators/models.py b/xmlschema/validators/models.py index c26859d..96cfaaf 100644 --- a/xmlschema/validators/models.py +++ b/xmlschema/validators/models.py @@ -453,12 +453,12 @@ class ModelVisitor(MutableSequence): if isinstance(item, ModelGroup): self.group, self.items, self.match = self.pop() - item_occurs = occurs[item] if self.group.model == 'choice': + item_occurs = occurs[item] if not item_occurs: return False - 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)) @@ -468,31 +468,40 @@ class ModelVisitor(MutableSequence): self.items = self.iter_group() self.match = False - return item.is_missing(max(item_occurs, occurs[(item,)])) + return item.is_missing(item_max_occurs) elif self.group.model == 'all': return False - elif item_occurs: - self.match = True elif self.match: pass + elif occurs[item]: + self.match = True elif item.is_emptiable(): return False - elif self.group.min_occurs <= occurs[self.group] or self: + elif self.group.min_occurs <= max(occurs[self.group], 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): - self.occurs[self.group] += 1 - else: - 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) + for k, item2 in enumerate(self.group, start=1): + item_occurs = occurs[item2] + if not item_occurs: + continue - return item.is_missing(max(item_occurs, occurs[(item,)])) + item_max_occurs = occurs[(item2,)] or item_occurs + if item_max_occurs == 1 or any(not x.is_emptiable() for x in self.group[k:]): + self.occurs[self.group] += 1 + break + + min_group_occurs = max(1, item_occurs // (item2.max_occurs or item_occurs)) + max_group_occurs = max(1, item_max_occurs // (item2.min_occurs or 1)) + + occurs[self.group] += min_group_occurs + occurs[(self.group,)] += max_group_occurs + break + + return item.is_missing(max(occurs[item], occurs[(item,)])) element, occurs = self.element, self.occurs if element is None: @@ -514,7 +523,7 @@ class ModelVisitor(MutableSequence): yield element, occurs[element], [element] while True: - while self.group.is_over(occurs[self.group]): + while self.group.is_over(max(occurs[self.group], occurs[(self.group,)])): stop_item(self.group) obj = next(self.items, None)