Add count_occurs() to ModelGroup

This commit is contained in:
Davide Brunato 2019-11-08 07:17:29 +01:00
parent d0f3a0f6c8
commit 2bcf78549c
6 changed files with 62 additions and 9 deletions

View File

@ -135,7 +135,6 @@ def normalize_url(url, base_url=None, keep_relative=False):
url_parts.query,
url_parts.fragment,
))
return filter_url(normalized_url)

View File

@ -542,6 +542,24 @@ class TestModelValidation(XsdValidatorTestCase):
self.assertEqual(model.element.name, 'elem1')
self.assertIsNone(schema.validate(xml_data))
def test_sequence_model_with_extended_occurs(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="unbounded">
<xs:element name="ax" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
""")
xml_data = '<root><ax/><ax/></root>'
self.assertIsNone(schema.validate(xml_data))
#
# Tests on issues
def test_issue_086(self):

View File

@ -988,16 +988,18 @@ class Xsd11Group(XsdGroup):
for item in restriction_items:
if other_item is item or item.is_restriction(other_item, check_occurs):
if max_occurs is not None:
if item.effective_max_occurs is None:
effective_max_occurs = item.effective_max_occurs
if effective_max_occurs is None:
max_occurs = None
else:
max_occurs = counter_func(max_occurs, item.effective_max_occurs)
max_occurs = counter_func(max_occurs, effective_max_occurs)
if other_max_occurs is not None:
if other_item.effective_max_occurs is None:
effective_max_occurs = other_item.effective_max_occurs
if effective_max_occurs is None:
other_max_occurs = None
else:
other_max_occurs = max(other_max_occurs, other_item.effective_max_occurs)
other_max_occurs = max(other_max_occurs, effective_max_occurs)
break
else:
continue

View File

@ -156,6 +156,33 @@ 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,
@ -462,17 +489,17 @@ class ModelVisitor(MutableSequence):
elif item_occurs:
self.match = True
if model == 'choice':
occurs[self.group] += max(1, self.group.count_occurs(self.occurs))
occurs[item] = 0
occurs[self.group] += 1
self.items, self.match = self.iter_group(), False
elif model == 'sequence' and item is self.group[-1]:
self.occurs[self.group] += 1
self.occurs[self.group] += max(1, self.group.count_occurs(self.occurs))
return item.is_missing(item_occurs)
elif model == 'sequence':
if self.match:
if item is self.group[-1]:
occurs[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

View File

@ -141,7 +141,7 @@ class XsdWildcard(XsdComponent, ValidationMixin):
return self.is_namespace_allowed('')
else:
return self.is_namespace_allowed('') or \
self.is_namespace_allowed(default_namespace)
self.is_namespace_allowed(default_namespace)
def is_namespace_allowed(self, namespace):
if self.not_namespace:

View File

@ -958,6 +958,13 @@ 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