Fix sequence model stop criteria

This commit is contained in:
Davide Brunato 2019-11-15 17:49:46 +01:00
parent 8207284c5a
commit a60532a3ab
4 changed files with 55 additions and 25 deletions

View File

@ -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

View File

@ -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.

View File

@ -551,14 +551,14 @@ class TestModelValidation(XsdValidatorTestCase):
<xs:element name="root">
<xs:complexType>
<xs:sequence minOccurs="2" maxOccurs="unbounded">
<xs:element name="a" maxOccurs="unbounded"/>
<xs:element name="ax" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
""")
self.assertIsNone(schema.validate('<root><a/><a/></root>'))
self.assertIsNone(schema.validate('<root><ax/><ax/></root>'))
schema = self.schema_class(
"""<?xml version="1.0" encoding="UTF-8"?>
@ -577,6 +577,8 @@ class TestModelValidation(XsdValidatorTestCase):
self.assertIsNone(schema.validate('<root><a/><a/><a/></root>'))
self.assertIsNone(schema.validate('<root><a/><a/><a/><a/><a/><a/></root>'))
def test_sequence_model_with_nested_choice_model(self):
schema = self.schema_class(
"""<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
@ -589,7 +591,7 @@ class TestModelValidation(XsdValidatorTestCase):
</xs:element>
<xs:group name="group1">
<xs:choice>
<xs:element name="ax" maxOccurs="unbounded"/>
<xs:element name="a" maxOccurs="unbounded"/>
<xs:element name="b"/>
<xs:element name="c"/>
</xs:choice>
@ -597,9 +599,26 @@ class TestModelValidation(XsdValidatorTestCase):
</xs:schema>
""")
self.assertIsNone(schema.validate('<root><ax/><ax/></root>'))
# self.assertIsNone(schema.validate('<root><a/><a/><a/></root>'))
# self.assertIsNone(schema.validate('<root><a/><a/><a/><a/><a/><a/></root>'))
self.assertIsNone(schema.validate('<root><a/><a/></root>'))
self.assertIsNone(schema.validate('<root><a/><a/><a/></root>'))
self.assertIsNone(schema.validate('<root><a/><a/><a/><a/><a/><a/></root>'))
def test_sequence_model_with_optional_elements(self):
schema = self.schema_class(
"""<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="root">
<xs:complexType>
<xs:sequence minOccurs="2" maxOccurs="2">
<xs:element name="a" minOccurs="1" maxOccurs="2" />
<xs:element name="b" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
""")
self.assertIsNone(schema.validate('<root><a/><a/><b/></root>'))
def test_choice_model_with_extended_occurs(self):
schema = self.schema_class(

View File

@ -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)