Fix ambiguous choice models visiting

- Fixed registering max occurs for tuple (group,)
  - TODO: maybe the same solution for 1-length sequence groups
This commit is contained in:
Davide Brunato 2019-11-14 11:14:51 +01:00
parent 7c4cd8b4d3
commit 79cf89af86
2 changed files with 125 additions and 60 deletions

View File

@ -558,8 +558,48 @@ class TestModelValidation(XsdValidatorTestCase):
</xs:schema>
""")
xml_data = '<root><a/><a/></root>'
self.assertIsNone(schema.validate(xml_data))
self.assertIsNone(schema.validate('<root><a/><a/></root>'))
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="0" maxOccurs="unbounded">
<xs:element name="a" minOccurs="2" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
""")
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>'))
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="0" maxOccurs="unbounded">
<xs:group ref="group1" minOccurs="2" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:group name="group1">
<xs:choice>
<xs:element name="ax" maxOccurs="unbounded"/>
<xs:element name="b"/>
<xs:element name="c"/>
</xs:choice>
</xs:group>
</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>'))
def test_choice_model_with_extended_occurs(self):
schema = self.schema_class(
@ -568,17 +608,34 @@ class TestModelValidation(XsdValidatorTestCase):
<xs:element name="root">
<xs:complexType>
<xs:choice maxOccurs="unbounded" minOccurs="0">
<xs:element maxOccurs="5" minOccurs="3" name="ax"/>
<xs:element maxOccurs="5" minOccurs="3" name="bx"/>
<xs:element maxOccurs="5" minOccurs="3" name="a"/>
<xs:element maxOccurs="5" minOccurs="3" name="b"/>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
""")
self.assertIsNone(schema.validate('<root><ax/><ax/><ax/></root>'))
self.assertIsNone(schema.validate('<root><ax/><ax/><ax/><ax/><ax/></root>'))
self.assertIsNone(schema.validate('<root><ax/><ax/><ax/><ax/><ax/><ax/></root>'))
self.assertIsNone(schema.validate('<root><a/><a/><a/></root>'))
self.assertIsNone(schema.validate('<root><a/><a/><a/><a/><a/></root>'))
self.assertIsNone(schema.validate('<root><a/><a/><a/><a/><a/><a/></root>'))
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:choice minOccurs="2" maxOccurs="3">
<xs:element name="a" maxOccurs="unbounded"/>
<xs:element name="b" maxOccurs="unbounded"/>
<xs:element name="c"/>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
""")
self.assertIsNone(schema.validate('<root><a/><a/><a/></root>'))
#
# Tests on issues

View File

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