diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f1417d1..213513e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,12 +2,13 @@ CHANGELOG ********* -`v1.0.15`_ (2019-10-11) +`v1.0.15`_ (2019-10-13) ======================= * Improved XPath 2.0 bindings -* Added logging for schema initialization and building +* Added logging for schema initialization and building (handled with argument *loglevel*) +* Update encoding of collapsed contents with a new model based reordering method * Removed XLink namespace from meta-schema (loaded from a fallback location like XHTML) -* Fixed half of failed W3C instance tests (remain 266 over 15344 tests) +* Fixed half of failed W3C instance tests (remain 255 over 15344 tests) `v1.0.14`_ (2019-08-27) ======================= diff --git a/publiccode.yml b/publiccode.yml index ce4e5e4..bfe5e7b 100644 --- a/publiccode.yml +++ b/publiccode.yml @@ -6,7 +6,7 @@ publiccodeYmlVersion: '0.2' name: xmlschema url: 'https://github.com/sissaschool/xmlschema' landingURL: 'https://github.com/sissaschool/xmlschema' -releaseDate: '2019-10-11' +releaseDate: '2019-10-13' softwareVersion: v1.0.15 developmentStatus: stable platforms: diff --git a/xmlschema/tests/test_factory/validation_tests.py b/xmlschema/tests/test_factory/validation_tests.py index 3374cbd..dfd2d50 100644 --- a/xmlschema/tests/test_factory/validation_tests.py +++ b/xmlschema/tests/test_factory/validation_tests.py @@ -98,7 +98,11 @@ def make_validator_test_class(test_file, test_args, test_num, schema_class, chec for _ in iter_nested_items(data1, dict_class=ordered_dict_class): pass - elem1 = self.schema.encode(data1, path=root.tag, converter=converter, **kwargs) + try: + elem1 = self.schema.encode(data1, path=root.tag, converter=converter, **kwargs) + except XMLSchemaValidationError as err: + raise AssertionError(str(err) + msg_tmpl % "error during re-encoding") + if isinstance(elem1, tuple): # When validation='lax' if converter is not ParkerConverter: diff --git a/xmlschema/tests/test_models.py b/xmlschema/tests/test_models.py index 60618e8..3748ead 100644 --- a/xmlschema/tests/test_models.py +++ b/xmlschema/tests/test_models.py @@ -580,6 +580,7 @@ class TestModelValidation11(TestModelValidation): class TestModelBasedSorting(XsdValidatorTestCase): def test_sort_content(self): + # test of ModelVisitor's sort_content/iter_unordered_content schema = self.get_schema(""" @@ -641,6 +642,161 @@ class TestModelBasedSorting(XsdValidatorTestCase): model.sort_content([('B3', True), ('B2', 10)]), [('B2', 10), ('B3', True)] ) + def test_iter_collapsed_content_with_optional_elements(self): + schema = self.get_schema(""" + + + + + + + + + + + + + """) + + model = ModelVisitor(schema.types['A_type'].content_type) + + content = [('B3', 10), ('B4', None), ('B5', True), ('B6', 'alpha'), ('B7', 20)] + model.restart() + self.assertListEqual( + list(model.iter_collapsed_content(content)), content + ) + + content = [('B3', 10), ('B5', True), ('B6', 'alpha'), ('B7', 20)] # Missing B4 + model.restart() + self.assertListEqual( + list(model.iter_collapsed_content(content)), content + ) + + def test_iter_collapsed_content_with_repeated_elements(self): + schema = self.get_schema(""" + + + + + + + + + + + + + """) + + model = ModelVisitor(schema.types['A_type'].content_type) + + content = [ + ('B3', 10), ('B4', None), ('B5', True), ('B5', False), ('B6', 'alpha'), ('B7', 20) + ] + self.assertListEqual( + list(model.iter_collapsed_content(content)), content + ) + + content = [('B3', 10), ('B3', 11), ('B3', 12), ('B4', None), ('B5', True), + ('B5', False), ('B6', 'alpha'), ('B7', 20), ('B7', 30)] + model.restart() + self.assertListEqual( + list(model.iter_collapsed_content(content)), content + ) + + content = [('B3', 10), ('B3', 11), ('B3', 12), ('B4', None), ('B5', True), ('B5', False)] + model.restart() + self.assertListEqual( + list(model.iter_collapsed_content(content)), content + ) + + def test_iter_collapsed_content_with_repeated_groups(self): + schema = self.get_schema(""" + + + + + + + + """) + + model = ModelVisitor(schema.types['A_type'].content_type) + + content = [('B1', 1), ('B1', 2), ('B2', 3), ('B2', 4)] + self.assertListEqual( + list(model.iter_collapsed_content(content)), + [('B1', 1), ('B2', 3), ('B1', 2), ('B2', 4)] + ) + + # Model broken by unknown element at start + content = [('X', None), ('B1', 1), ('B1', 2), ('B2', 3), ('B2', 4)] + model.restart() + self.assertListEqual(list(model.iter_collapsed_content(content)), content) + + content = [('B1', 1), ('X', None), ('B1', 2), ('B2', 3), ('B2', 4)] + model.restart() + self.assertListEqual(list(model.iter_collapsed_content(content)), content) + + content = [('B1', 1), ('B1', 2), ('X', None), ('B2', 3), ('B2', 4)] + model.restart() + self.assertListEqual(list(model.iter_collapsed_content(content)), content) + + content = [('B1', 1), ('B1', 2), ('B2', 3), ('X', None), ('B2', 4)] + model.restart() + self.assertListEqual( + list(model.iter_collapsed_content(content)), + [('B1', 1), ('B2', 3), ('B1', 2), ('X', None), ('B2', 4)] + ) + + content = [('B1', 1), ('B1', 2), ('B2', 3), ('B2', 4), ('X', None)] + model.restart() + self.assertListEqual( + list(model.iter_collapsed_content(content)), + [('B1', 1), ('B2', 3), ('B1', 2), ('B2', 4), ('X', None)] + ) + + def test_iter_collapsed_content_with_single_elements(self): + schema = self.get_schema(""" + + + + + + + + + """) + + model = ModelVisitor(schema.types['A_type'].content_type) + + content = [('B1', 'abc'), ('B2', 10), ('B3', False)] + model.restart() + self.assertListEqual(list(model.iter_collapsed_content(content)), content) + + content = [('B3', False), ('B1', 'abc'), ('B2', 10)] + model.restart() + self.assertListEqual(list(model.iter_collapsed_content(content)), content) + + content = [('B1', 'abc'), ('B3', False), ('B2', 10)] + model.restart() + self.assertListEqual(list(model.iter_collapsed_content(content)), content) + + content = [('B1', 'abc'), ('B1', 'def'), ('B2', 10), ('B3', False)] + model.restart() + self.assertListEqual( + list(model.iter_collapsed_content(content)), + [('B1', 'abc'), ('B2', 10), ('B3', False), ('B1', 'def')] + ) + + content = [('B1', 'abc'), ('B2', 10), ('X', None)] + model.restart() + self.assertListEqual(list(model.iter_collapsed_content(content)), content) + + content = [('X', None), ('B1', 'abc'), ('B2', 10), ('B3', False)] + model.restart() + self.assertListEqual(list(model.iter_collapsed_content(content)), content) + if __name__ == '__main__': from xmlschema.tests import print_test_header diff --git a/xmlschema/tests/validation/test_encoding.py b/xmlschema/tests/validation/test_encoding.py index 30a90d5..ffa6623 100644 --- a/xmlschema/tests/validation/test_encoding.py +++ b/xmlschema/tests/validation/test_encoding.py @@ -374,8 +374,8 @@ class TestEncoding(XsdValidatorTestCase): """) - with self.assertRaises(XMLSchemaChildrenValidationError): - schema.to_etree({"A": [1, 2], "B": [3, 4]}) + root = schema.to_etree(ordered_dict_class([('A', [1, 2]), ('B', [3, 4])])) + self.assertListEqual([e.text for e in root], ['1', '3', '2', '4']) root = schema.to_etree({"A": [1, 2], "B": [3, 4]}, converter=UnorderedConverter) self.assertListEqual([e.text for e in root], ['1', '3', '2', '4']) diff --git a/xmlschema/validators/exceptions.py b/xmlschema/validators/exceptions.py index a7c6ea9..3ed988f 100644 --- a/xmlschema/validators/exceptions.py +++ b/xmlschema/validators/exceptions.py @@ -225,7 +225,11 @@ class XMLSchemaValidationError(XMLSchemaValidatorError, ValueError): if hasattr(self.validator, 'tostring'): msg.append("Schema:\n\n%s\n" % self.validator.tostring(' ', 20)) if is_etree_element(self.elem): - elem_as_string = etree_tostring(self.elem, self.namespaces, ' ', 20) + try: + elem_as_string = etree_tostring(self.elem, self.namespaces, ' ', 20) + except (ValueError, TypeError): + elem_as_string = repr(self.elem) + if hasattr(self.elem, 'sourceline'): msg.append("Instance (line %r):\n\n%s\n" % (self.elem.sourceline, elem_as_string)) else: diff --git a/xmlschema/validators/groups.py b/xmlschema/validators/groups.py index 57dcb60..e5345b1 100644 --- a/xmlschema/validators/groups.py +++ b/xmlschema/validators/groups.py @@ -712,12 +712,12 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin): cdata_index = 0 if isinstance(element_data.content, dict) or kwargs.get('unordered'): content = model.iter_unordered_content(element_data.content) + elif not isinstance(element_data.content, list): + content = [] elif converter.losslessly: content = element_data.content - elif isinstance(element_data.content, list): - content = model.iter_collapsed_content(element_data.content) else: - content = [] + content = ModelVisitor(self).iter_collapsed_content(element_data.content) for index, (name, value) in enumerate(content): if isinstance(name, int): diff --git a/xmlschema/validators/models.py b/xmlschema/validators/models.py index e09ea7b..7a904f4 100644 --- a/xmlschema/validators/models.py +++ b/xmlschema/validators/models.py @@ -607,26 +607,42 @@ class ModelVisitor(MutableSequence): """ prev_name = None unordered_content = defaultdict(deque) + for name, value in content: if isinstance(name, int) or self.element is None: yield name, value - elif prev_name != name: + continue + + while self.element is not None: + if self.element.is_matching(name): + yield name, value + prev_name = name + for _ in self.advance(True): + pass + break + + for key in unordered_content: + if self.element.is_matching(key): + break + else: + if prev_name == name: + unordered_content[name].append(value) + break + + for _ in self.advance(False): + pass + continue + + try: + yield key, unordered_content[key].popleft() + except IndexError: + del unordered_content[key] + else: + for _ in self.advance(True): + pass + else: yield name, value prev_name = name - elif self.element.is_matching(name): - yield name, value - else: - unordered_content[name].append(value) - while self.element is not None and unordered_content: - for key in unordered_content: - if self.element.is_matching(key): - try: - yield name, unordered_content[key].popleft() - except IndexError: - del unordered_content[key] - break - else: - break # Add the remaining consumable content onto the end of the data. for name, values in unordered_content.items(): diff --git a/xmlschema/validators/schema.py b/xmlschema/validators/schema.py index 2f4791d..321809f 100644 --- a/xmlschema/validators/schema.py +++ b/xmlschema/validators/schema.py @@ -178,7 +178,9 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin): namespace has been overridden by an import. Ignored if the argument *global_maps* is provided. :type use_meta: bool :param loglevel: for setting a different logging level for schema initialization \ - and building. For default is WARNING (30). + and building. For default is WARNING (30). For INFO level set it with 20, for \ + DEBUG level with 10. The default loglevel is restored after schema building, \ + when exiting the initialization method. :type loglevel: int :cvar XSD_VERSION: store the XSD version (1.0 or 1.1).