Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-xmlschema for openSUSE:Factory checked in at 2023-04-17 17:40:59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-xmlschema (Old) and /work/SRC/openSUSE:Factory/.python-xmlschema.new.2023 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-xmlschema" Mon Apr 17 17:40:59 2023 rev:21 rq:1079725 version:2.2.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-xmlschema/python-xmlschema.changes 2023-03-29 23:26:35.651285561 +0200 +++ /work/SRC/openSUSE:Factory/.python-xmlschema.new.2023/python-xmlschema.changes 2023-04-17 17:41:00.706120724 +0200 @@ -1,0 +2,7 @@ +Sun Apr 16 08:16:21 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 2.2.3: + * Add support for Python 3.12 + * Detach content iteration methods from ModelVisitor + +------------------------------------------------------------------- Old: ---- xmlschema-2.2.2.tar.gz New: ---- xmlschema-2.2.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-xmlschema.spec ++++++ --- /var/tmp/diff_new_pack.2smXCs/_old 2023-04-17 17:41:01.234123809 +0200 +++ /var/tmp/diff_new_pack.2smXCs/_new 2023-04-17 17:41:01.238123833 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-xmlschema -Version: 2.2.2 +Version: 2.2.3 Release: 0 Summary: An XML Schema validator and decoder License: MIT ++++++ xmlschema-2.2.2.tar.gz -> xmlschema-2.2.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/CHANGELOG.rst new/xmlschema-2.2.3/CHANGELOG.rst --- old/xmlschema-2.2.2/CHANGELOG.rst 2023-03-05 21:30:24.000000000 +0100 +++ new/xmlschema-2.2.3/CHANGELOG.rst 2023-04-14 15:49:05.000000000 +0200 @@ -2,6 +2,11 @@ CHANGELOG ********* +`v2.2.3`_ (2023-04-14) +====================== +* Add support for Python 3.12 +* Detach content iteration methods from ModelVisitor + `v2.2.2`_ (2023-03-05) ====================== * Fix mixed content extension with empty content (issue #337) @@ -610,3 +615,4 @@ .. _v2.2.0: https://github.com/brunato/xmlschema/compare/v2.1.1...v2.2.0 .. _v2.2.1: https://github.com/brunato/xmlschema/compare/v2.2.0...v2.2.1 .. _v2.2.2: https://github.com/brunato/xmlschema/compare/v2.2.1...v2.2.2 +.. _v2.2.3: https://github.com/brunato/xmlschema/compare/v2.2.2...v2.2.3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/PKG-INFO new/xmlschema-2.2.3/PKG-INFO --- old/xmlschema-2.2.2/PKG-INFO 2023-03-05 22:10:04.662572100 +0100 +++ new/xmlschema-2.2.3/PKG-INFO 2023-04-14 15:52:42.466140500 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: xmlschema -Version: 2.2.2 +Version: 2.2.3 Summary: An XML Schema validator and decoder Home-page: https://github.com/sissaschool/xmlschema Author: Davide Brunato @@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/doc/conf.py new/xmlschema-2.2.3/doc/conf.py --- old/xmlschema-2.2.2/doc/conf.py 2023-03-05 21:30:24.000000000 +0100 +++ new/xmlschema-2.2.3/doc/conf.py 2023-04-14 15:49:05.000000000 +0200 @@ -81,7 +81,7 @@ # The short X.Y version. version = '2.2' # The full version, including alpha/beta/rc tags. -release = '2.2.2' +release = '2.2.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/setup.py new/xmlschema-2.2.3/setup.py --- old/xmlschema-2.2.2/setup.py 2023-03-05 21:30:24.000000000 +0100 +++ new/xmlschema-2.2.3/setup.py 2023-04-14 15:49:05.000000000 +0200 @@ -18,7 +18,7 @@ setup( name='xmlschema', - version='2.2.2', + version='2.2.3', packages=find_packages(include=['xmlschema*']), package_data={ 'xmlschema': ['py.typed', 'locale/**/*.mo', 'locale/**/*.po', 'schemas/*/*.xsd'], @@ -62,6 +62,7 @@ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/tests/test_cases/issues/issue_341/issue_341-ext.xsd new/xmlschema-2.2.3/tests/test_cases/issues/issue_341/issue_341-ext.xsd --- old/xmlschema-2.2.2/tests/test_cases/issues/issue_341/issue_341-ext.xsd 1970-01-01 01:00:00.000000000 +0100 +++ new/xmlschema-2.2.3/tests/test_cases/issues/issue_341/issue_341-ext.xsd 2023-04-14 15:49:05.000000000 +0200 @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="windows-1251"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> + <xs:element name="TEST"> + <xs:complexType> + <xs:sequence> + <xs:element name="TEST_EL" maxOccurs="1000"> + <xs:complexType> + <xs:sequence> + <xs:element name="TEST_EL_2"> + <xs:complexType> + <xs:sequence> + <xs:element name="exists_in_xml" type="test_type"/> + <xs:element name="not_exists_in_xml" type="test_type" minOccurs="0"/> + <xs:choice> + <xs:element name="choice_elem1" minOccurs="0"/> + <xs:element name="choice_elem2" minOccurs="0"/> + </xs:choice> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:sequence> + <xs:attribute name="Date" type="xs:date" use="required"/> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:complexType name="test_type"> + <xs:attribute name="test_attr"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + <xs:maxLength value="60"/> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="test_attr_2"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + <xs:maxLength value="60"/> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:complexType> +</xs:schema> \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/tests/test_cases/issues/issue_341/issue_341.xml new/xmlschema-2.2.3/tests/test_cases/issues/issue_341/issue_341.xml --- old/xmlschema-2.2.2/tests/test_cases/issues/issue_341/issue_341.xml 1970-01-01 01:00:00.000000000 +0100 +++ new/xmlschema-2.2.3/tests/test_cases/issues/issue_341/issue_341.xml 2023-04-14 15:49:05.000000000 +0200 @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<TEST> + <TEST_EL Date="2022-10-03"> + <TEST_EL_2> + <exists_in_xml test_attr="test_value_attr" /> + </TEST_EL_2> + </TEST_EL> +</TEST> \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/tests/test_cases/issues/issue_341/issue_341.xsd new/xmlschema-2.2.3/tests/test_cases/issues/issue_341/issue_341.xsd --- old/xmlschema-2.2.2/tests/test_cases/issues/issue_341/issue_341.xsd 1970-01-01 01:00:00.000000000 +0100 +++ new/xmlschema-2.2.3/tests/test_cases/issues/issue_341/issue_341.xsd 2023-04-14 15:49:05.000000000 +0200 @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="windows-1251"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> + <xs:element name="TEST"> + <xs:complexType> + <xs:sequence> + <xs:element name="TEST_EL" maxOccurs="1000"> + <xs:complexType> + <xs:sequence> + <xs:element name="TEST_EL_2"> + <xs:complexType> + <xs:sequence> + <xs:element name="exists_in_xml" type="test_type"/> + <xs:element name="not_exists_in_xml" type="test_type" minOccurs="0"/> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:sequence> + <xs:attribute name="Date" type="xs:date" use="required"/> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:complexType name="test_type"> + <xs:attribute name="test_attr"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + <xs:maxLength value="60"/> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="test_attr_2"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:minLength value="1"/> + <xs:maxLength value="60"/> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:complexType> +</xs:schema> \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/tests/validation/test_decoding.py new/xmlschema-2.2.3/tests/validation/test_decoding.py --- old/xmlschema-2.2.2/tests/validation/test_decoding.py 2023-02-11 11:41:50.000000000 +0100 +++ new/xmlschema-2.2.3/tests/validation/test_decoding.py 2023-04-14 15:49:05.000000000 +0200 @@ -30,7 +30,7 @@ from xmlschema.names import XSD_STRING, XSI_NIL from xmlschema.converters import UnorderedConverter -from xmlschema.validators import XMLSchema11 +from xmlschema.validators import XMLSchema11, ModelVisitor from xmlschema.testing import XsdValidatorTestCase, etree_elements_assert_equal VEHICLES_DICT = { @@ -375,7 +375,9 @@ xml_dict = self.vh_schema.to_dict(vh_xml_tree, namespaces=self.vh_namespaces) self.assertEqual(xml_dict, VEHICLES_DICT) - xml_dict = xmlschema.to_dict(vh_xml_tree, self.vh_schema.url, namespaces=self.vh_namespaces) + xml_dict = xmlschema.to_dict( + vh_xml_tree, self.vh_schema.url, namespaces=self.vh_namespaces + ) self.assertEqual(xml_dict, VEHICLES_DICT) xml_dict = self.col_schema.to_dict(col_xml_tree) @@ -1375,6 +1377,92 @@ body_text = result['Demonstrative_Examples']['Demonstrative_Example'][0]['Body_Text'] self.assertListEqual(body_text, expected) + def test_fill_missing_elements__issue_341(self): + xsd_file = self.casepath('issues/issue_341/issue_341.xsd') + xml_file = self.casepath('issues/issue_341/issue_341.xml') + schema = self.schema_class(xsd_file) + + expected = {'TEST_EL': [ + {'@Date': '2022-10-03', + 'TEST_EL_2': { + 'exists_in_xml': { + '@test_attr': 'test_value_attr', '@test_attr_2': None + } + }} + ]} + xml_dict = schema.decode(xml_file, fill_missing=True) + self.assertDictEqual(xml_dict, expected) + + def fill_missing_content(element_data: ElementData, _xsd_element, xsd_type): + group = xsd_type.model_group + if group is None: + return element_data # an element with simple content + + filled_content = [] + model = ModelVisitor(group) + xsd_element = None + for name, value, xsd_element in element_data.content: + if isinstance(name, int) or xsd_element is None: + filled_content.append((name, value, xsd_element)) + continue + + while model.element is not None: + if model.element is xsd_element: + filled_content.append((name, value, xsd_element)) + for _err in model.advance(True): + pass + break + + if model.element.max_occurs != 0: + filled_content.append((model.element.name, None, model.element)) + for _err in model.advance(False): + pass + else: + filled_content.append((name, value, xsd_element)) + + while model.element is not None: + if model.element is not xsd_element and model.element.max_occurs != 0: + filled_content.append((model.element.name, None, model.element)) + for _err in model.advance(False): + pass + + return ElementData( + element_data.tag, + element_data.text, + filled_content, + element_data.attributes + ) + + expected = {'TEST_EL': [ + {'@Date': '2022-10-03', + 'TEST_EL_2': { + 'exists_in_xml': { + '@test_attr': 'test_value_attr', '@test_attr_2': None + }, + 'not_exists_in_xml': None + }} + ]} + xml_dict = schema.decode(xml_file, element_hook=fill_missing_content, fill_missing=True) + self.assertDictEqual(xml_dict, expected) + + # Resolving more complex schemas requires more checks in hook function + xsd_file = self.casepath('issues/issue_341/issue_341-ext.xsd') + schema = self.schema_class(xsd_file) + + expected = {'TEST_EL': [ + {'@Date': '2022-10-03', + 'TEST_EL_2': { + 'exists_in_xml': { + '@test_attr': 'test_value_attr', '@test_attr_2': None + }, + 'not_exists_in_xml': None, + 'choice_elem1': None, + 'choice_elem2': None, # this is wrong (at most one element for a choice) + }} + ]} + xml_dict = schema.decode(xml_file, element_hook=fill_missing_content, fill_missing=True) + self.assertDictEqual(xml_dict, expected) + class TestDecoding11(TestDecoding): schema_class = XMLSchema11 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/tests/validators/test_models.py new/xmlschema-2.2.3/tests/validators/test_models.py --- old/xmlschema-2.2.2/tests/validators/test_models.py 2022-07-18 16:19:15.000000000 +0200 +++ new/xmlschema-2.2.3/tests/validators/test_models.py 2023-04-14 15:49:05.000000000 +0200 @@ -18,7 +18,8 @@ from xmlschema.exceptions import XMLSchemaValueError from xmlschema.validators.exceptions import XMLSchemaValidationError from xmlschema.validators.particles import ParticleMixin -from xmlschema.validators.models import distinguishable_paths, ModelVisitor +from xmlschema.validators.models import distinguishable_paths, ModelVisitor, \ + sort_content, iter_collapsed_content from xmlschema.validators.groups import XsdGroup from xmlschema.validators.elements import XsdElement from xmlschema.testing import XsdValidatorTestCase @@ -115,7 +116,7 @@ model = ModelVisitor(group) model.occurs[group[1]] = 1 - self.assertListEqual(list(model.items), group[1:]) + self.assertEqual(list(model.items), group[1:]) group = ModelGroup('all') group.append(ParticleMixin()) @@ -124,7 +125,7 @@ model = ModelVisitor(group) model.occurs[group[1]] = 1 - self.assertListEqual(list(model.items), group[2:]) + self.assertEqual(list(model.items), group[2:]) # --- Vehicles schema --- @@ -985,33 +986,24 @@ </xs:complexType> """) - model = ModelVisitor(schema.types['A_type'].content) + group = schema.types['A_type'].content self.assertListEqual( - model.sort_content([('B2', 10), ('B1', 'abc'), ('B3', True)], restart=False), + sort_content([('B2', 10), ('B1', 'abc'), ('B3', True)], group), [('B1', 'abc'), ('B2', 10), ('B3', True)] ) self.assertListEqual( - model.sort_content([('B2', 10), ('B1', 'abc'), ('B3', True)]), + sort_content([('B3', True), ('B2', 10), ('B1', 'abc')], group), [('B1', 'abc'), ('B2', 10), ('B3', True)] ) self.assertListEqual( - model.sort_content([('B2', 10), ('B1', 'abc'), ('B3', True)], restart=False), - [('B2', 10), ('B1', 'abc'), ('B3', True)] - ) - - self.assertListEqual( - model.sort_content([('B3', True), ('B2', 10), ('B1', 'abc')]), - [('B1', 'abc'), ('B2', 10), ('B3', True)] - ) - self.assertListEqual( - model.sort_content([('B2', 10), ('B4', None), ('B1', 'abc'), ('B3', True)]), + sort_content([('B2', 10), ('B4', None), ('B1', 'abc'), ('B3', True)], group), [('B1', 'abc'), ('B2', 10), ('B3', True), ('B4', None)] ) content = [('B2', 10), ('B4', None), ('B1', 'abc'), (1, 'hello'), ('B3', True)] self.assertListEqual( - model.sort_content(content), + sort_content(content, group), [(1, 'hello'), ('B1', 'abc'), ('B2', 10), ('B3', True), ('B4', None)] ) @@ -1019,7 +1011,7 @@ (2, 'world!'), ('B2', 10), ('B4', None), ('B1', 'abc'), (1, 'hello'), ('B3', True) ] self.assertListEqual( - model.sort_content(content), + sort_content(content, group), [(1, 'hello'), ('B1', 'abc'), (2, 'world!'), ('B2', 10), ('B3', True), ('B4', None)] ) @@ -1028,7 +1020,7 @@ (5, 'five'), (4, 'four'), (2, 'two'), (3, 'three'), (1, 'one') ] self.assertListEqual( - model.sort_content(content), + sort_content(content, group), [(1, 'one'), ('B1', 'abc'), (2, 'two'), ('B2', 10), (3, 'three'), ('B3', True), (4, 'four'), ('B4', None), (5, 'five'), (6, 'six')] ) @@ -1036,26 +1028,30 @@ # With a dict-type argument content = dict([('B2', [10]), ('B1', ['abc']), ('B3', [True])]) self.assertListEqual( - model.sort_content(content), [('B1', 'abc'), ('B2', 10), ('B3', True)] + sort_content(content, group), [('B1', 'abc'), ('B2', 10), ('B3', True)] ) content = dict([('B2', [10]), ('B1', ['abc']), ('B3', [True]), (1, 'hello')]) self.assertListEqual( - model.sort_content(content), [(1, 'hello'), ('B1', 'abc'), ('B2', 10), ('B3', True)] + sort_content(content, group), + [(1, 'hello'), ('B1', 'abc'), ('B2', 10), ('B3', True)] ) # With partial content - self.assertListEqual(model.sort_content([]), []) - self.assertListEqual(model.sort_content([('B1', 'abc')]), [('B1', 'abc')]) - self.assertListEqual(model.sort_content([('B2', 10)]), [('B2', 10)]) - self.assertListEqual(model.sort_content([('B3', True)]), [('B3', True)]) + self.assertListEqual(sort_content([], group), []) + self.assertListEqual(sort_content([('B1', 'abc')], group), [('B1', 'abc')]) + self.assertListEqual(sort_content([('B2', 10)], group), [('B2', 10)]) + self.assertListEqual(sort_content([('B3', True)], group), [('B3', True)]) self.assertListEqual( - model.sort_content([('B3', True), ('B1', 'abc')]), [('B1', 'abc'), ('B3', True)] + sort_content([('B3', True), ('B1', 'abc')], group), + [('B1', 'abc'), ('B3', True)] ) self.assertListEqual( - model.sort_content([('B2', 10), ('B1', 'abc')]), [('B1', 'abc'), ('B2', 10)] + sort_content([('B2', 10), ('B1', 'abc')], group), + [('B1', 'abc'), ('B2', 10)] ) self.assertListEqual( - model.sort_content([('B3', True), ('B2', 10)]), [('B2', 10), ('B3', True)] + sort_content([('B3', True), ('B2', 10)], group), + [('B2', 10), ('B3', True)] ) def test_iter_collapsed_content_with_optional_elements(self): @@ -1074,19 +1070,26 @@ </xs:complexType> """) - model = ModelVisitor(schema.types['A_type'].content) + group = schema.types['A_type'].content + model = ModelVisitor(group) content = [('B3', 10), ('B4', None), ('B5', True), ('B6', 'alpha'), ('B7', 20)] model.restart() self.assertListEqual( list(model.iter_collapsed_content(content)), content ) + self.assertListEqual( + list(iter_collapsed_content(content, group)), content + ) content = [('B3', 10), ('B5', True), ('B6', 'alpha'), ('B7', 20)] # Missing B4 model.restart() self.assertListEqual( list(model.iter_collapsed_content(content)), content ) + self.assertListEqual( + list(iter_collapsed_content(content, group)), content + ) def test_iter_collapsed_content_with_repeated_elements(self): schema = self.get_schema(""" @@ -1104,27 +1107,19 @@ </xs:complexType> """) - model = ModelVisitor(schema.types['A_type'].content) + group = schema.types['A_type'].content content = [ ('B3', 10), ('B4', None), ('B5', True), ('B5', False), ('B6', 'alpha'), ('B7', 20) ] - self.assertListEqual( - list(model.iter_collapsed_content(content)), content - ) + self.assertListEqual(list(iter_collapsed_content(content, group)), 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 - ) + self.assertListEqual(list(iter_collapsed_content(content, group)), 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 - ) + self.assertListEqual(list(iter_collapsed_content(content, group)), content) def test_iter_collapsed_content_with_repeated_groups(self): schema = self.get_schema(""" @@ -1137,38 +1132,33 @@ </xs:complexType> """) - model = ModelVisitor(schema.types['A_type'].content) + group = schema.types['A_type'].content content = [('B1', 1), ('B1', 2), ('B2', 3), ('B2', 4)] self.assertListEqual( - list(model.iter_collapsed_content(content)), + list(iter_collapsed_content(content, group)), [('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) + self.assertListEqual(list(iter_collapsed_content(content, group)), content) content = [('B1', 1), ('X', None), ('B1', 2), ('B2', 3), ('B2', 4)] - model.restart() - self.assertListEqual(list(model.iter_collapsed_content(content)), content) + self.assertListEqual(list(iter_collapsed_content(content, group)), content) content = [('B1', 1), ('B1', 2), ('X', None), ('B2', 3), ('B2', 4)] - model.restart() - self.assertListEqual(list(model.iter_collapsed_content(content)), content) + self.assertListEqual(list(iter_collapsed_content(content, group)), content) content = [('B1', 1), ('B1', 2), ('B2', 3), ('X', None), ('B2', 4)] - model.restart() self.assertListEqual( - list(model.iter_collapsed_content(content)), + list(iter_collapsed_content(content, group)), [('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)), + list(iter_collapsed_content(content, group)), [('B1', 1), ('B2', 3), ('B1', 2), ('B2', 4), ('X', None)] ) @@ -1184,34 +1174,28 @@ </xs:complexType> """) - model = ModelVisitor(schema.types['A_type'].content) + group = schema.types['A_type'].content content = [('B1', 'abc'), ('B2', 10), ('B3', False)] - model.restart() - self.assertListEqual(list(model.iter_collapsed_content(content)), content) + self.assertListEqual(list(iter_collapsed_content(content, group)), content) content = [('B3', False), ('B1', 'abc'), ('B2', 10)] - model.restart() - self.assertListEqual(list(model.iter_collapsed_content(content)), content) + self.assertListEqual(list(iter_collapsed_content(content, group)), content) content = [('B1', 'abc'), ('B3', False), ('B2', 10)] - model.restart() - self.assertListEqual(list(model.iter_collapsed_content(content)), content) + self.assertListEqual(list(iter_collapsed_content(content, group)), content) content = [('B1', 'abc'), ('B1', 'def'), ('B2', 10), ('B3', False)] - model.restart() self.assertListEqual( - list(model.iter_collapsed_content(content)), + list(iter_collapsed_content(content, group)), [('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) + self.assertListEqual(list(iter_collapsed_content(content, group)), content) content = [('X', None), ('B1', 'abc'), ('B2', 10), ('B3', False)] - model.restart() - self.assertListEqual(list(model.iter_collapsed_content(content)), content) + self.assertListEqual(list(iter_collapsed_content(content, group)), content) class TestModelPaths(unittest.TestCase): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/tox.ini new/xmlschema-2.2.3/tox.ini --- old/xmlschema-2.2.2/tox.ini 2023-03-05 21:30:24.000000000 +0100 +++ new/xmlschema-2.2.3/tox.ini 2023-04-14 15:49:05.000000000 +0200 @@ -1,6 +1,6 @@ [tox] -envlist = py{37,38,39,310,311}, pypy3, ep{40}, docs, - flake8, mypy-py{37,38,39,310,311}, coverage, pytest +envlist = py{37,38,39,310,311,312,py3}, ep{40}, docs, + flake8, mypy-py{37,38,39,310,311,312,py3}, coverage, pytest skip_missing_interpreters = true work_dir = {tox_root}/../.tox/xmlschema @@ -15,10 +15,12 @@ coverage: coverage commands = python -m unittest -allowlist_externals = make -[testenv:pypy3] -commands = python -m unittest +[testenv:py312] +deps = + elementpath>=4.0.0, <5.0.0 + # lxml: skip for now + jinja2 [testenv:ep40] deps = @@ -31,6 +33,7 @@ make -C doc latexpdf SPHINXOPTS="-W -n" make -C doc doctest SPHINXOPTS="-W -n" sphinx-build -W -n -T -b man doc build/sphinx/man +allowlist_externals = make [flake8] max-line-length = 100 @@ -44,17 +47,17 @@ [testenv:mypy-py37] deps = - mypy==1.0.1 - elementpath==4.0.1 + mypy==1.2.0 + elementpath==4.1.1 lxml-stubs jinja2 commands = mypy --config-file {toxinidir}/mypy.ini xmlschema -[testenv:mypy-py{38,39,310,311}] +[testenv:mypy-py{38,39,310,311,312,py3}] deps = - mypy==1.0.1 - elementpath==4.0.1 + mypy==1.2.0 + elementpath==4.1.1 lxml-stubs jinja2 commands = @@ -74,7 +77,7 @@ elementpath>=4.0.0, <5.0.0 lxml jinja2 - mypy==1.0.1 + mypy==1.2.0 lxml-stubs commands = pytest tests -ra diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/xmlschema/__init__.py new/xmlschema-2.2.3/xmlschema/__init__.py --- old/xmlschema-2.2.2/xmlschema/__init__.py 2023-03-05 21:30:24.000000000 +0100 +++ new/xmlschema-2.2.3/xmlschema/__init__.py 2023-04-14 15:49:05.000000000 +0200 @@ -31,7 +31,7 @@ XsdComponent, XsdType, XsdElement, XsdAttribute ) -__version__ = '2.2.2' +__version__ = '2.2.3' __author__ = "Davide Brunato" __contact__ = "brun...@sissa.it" __copyright__ = "Copyright 2016-2023, SISSA" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/xmlschema/resources.py new/xmlschema-2.2.3/xmlschema/resources.py --- old/xmlschema-2.2.2/xmlschema/resources.py 2023-02-11 11:41:50.000000000 +0100 +++ new/xmlschema-2.2.3/xmlschema/resources.py 2023-04-14 15:49:05.000000000 +0200 @@ -9,6 +9,8 @@ # import copy import os.path +import ntpath +import posixpath import platform import re import string @@ -45,13 +47,12 @@ A version of pathlib.PurePath adapted for managing the creation from URIs and the simple normalization of paths. """ - _from_parts: Any - _flavour: Any + _path_module = os.path def __new__(cls, *args: str) -> '_PurePath': if cls is _PurePath: cls = _PureWindowsPath if os.name == 'nt' else _PurePosixPath - return cast('_PurePath', cls._from_parts(args)) + return super().__new__(cls, *args) @classmethod def from_uri(cls, uri: str) -> '_PurePath': @@ -98,12 +99,21 @@ def as_uri(self) -> str: if not self.is_absolute(): - uri: str = self._flavour.make_uri(self) - while uri.startswith('file:/'): - uri = uri.replace('file:/', 'file:', 1) - return uri + # Converts relative paths to not RFC 8089 compliant relative + # file URIs because urlopen() doesn't accept simple paths + drive = self.drive + if len(drive) == 2 and drive[1] == ':': + prefix = 'file:' + drive + path = self.as_posix()[2:] + elif drive: + prefix = 'file:' + path = self.as_posix() + else: + prefix = 'file:' + path = str(self) + return prefix + quote_from_bytes(os.fsencode(path)) - uri = cast(str, self._flavour.make_uri(self)) + uri = super().as_uri() if isinstance(self, _PureWindowsPath) and str(self).startswith(r'\\'): # UNC format case: use the format where the host part is included # in the path part, to let urlopen() works. @@ -112,15 +122,17 @@ return uri def normalize(self) -> '_PurePath': - normalized_path = self._flavour.pathmod.normpath(str(self)) - return cast('_PurePath', self._from_parts((normalized_path,))) + normalized_path = self._path_module.normpath(str(self)) + return self.__class__(normalized_path) class _PurePosixPath(_PurePath, PurePosixPath): + _path_module = posixpath __slots__ = () class _PureWindowsPath(_PurePath, PureWindowsPath): + _path_module = ntpath __slots__ = () diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/xmlschema/validators/global_maps.py new/xmlschema-2.2.3/xmlschema/validators/global_maps.py --- old/xmlschema-2.2.2/xmlschema/validators/global_maps.py 2023-03-05 21:30:24.000000000 +0100 +++ new/xmlschema-2.2.3/xmlschema/validators/global_maps.py 2023-04-14 15:49:05.000000000 +0200 @@ -31,6 +31,7 @@ XMLSchemaParseError from .xsdbase import XsdValidator, XsdComponent from .builtins import xsd_builtin_types_factory +from .models import check_model from . import XsdAttribute, XsdSimpleType, XsdComplexType, XsdElement, XsdAttributeGroup, \ XsdGroup, XsdNotation, XsdIdentity, XsdAssert, XsdUnion, XsdAtomicRestriction @@ -731,7 +732,7 @@ _group.parse_error(msg, validation=validation) try: - xsd_type.content.check_model() + check_model(xsd_type.content) except XMLSchemaModelDepthError: msg = _("can't verify the content model of {!r} " "due to exceeding of maximum recursion depth") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/xmlschema/validators/groups.py new/xmlschema-2.2.3/xmlschema/validators/groups.py --- old/xmlschema-2.2.2/xmlschema/validators/groups.py 2023-03-05 21:41:28.000000000 +0100 +++ new/xmlschema-2.2.3/xmlschema/validators/groups.py 2023-04-14 15:49:05.000000000 +0200 @@ -34,7 +34,7 @@ from .particles import ParticleMixin, OccursCalculator from .elements import XsdElement, XsdAlternative from .wildcards import XsdAnyElement, Xsd11AnyElement -from .models import ModelVisitor, distinguishable_paths +from .models import ModelVisitor, iter_unordered_content, iter_collapsed_content if TYPE_CHECKING: from .complex_types import XsdComplexType @@ -765,78 +765,6 @@ else: return other_max_occurs >= max_occurs * self.max_occurs - def check_model(self) -> None: - """ - Checks if the model group is deterministic. Element Declarations Consistent and - Unique Particle Attribution constraints are checked. - :raises: an `XMLSchemaModelError` at first violated constraint. - """ - def safe_iter_path() -> Iterator[SchemaElementType]: - iterators: List[Iterator[ModelParticleType]] = [] - particles = iter(self) - - while True: - for item in particles: - if isinstance(item, XsdGroup): - current_path.append(item) - iterators.append(particles) - particles = iter(item) - if len(iterators) > limits.MAX_MODEL_DEPTH: - raise XMLSchemaModelDepthError(self) - break - else: - yield item - else: - try: - current_path.pop() - particles = iterators.pop() - except IndexError: - return - - paths: Any = {} - current_path: List[ModelParticleType] = [self] - try: - any_element = self.parent.open_content.any_element # type: ignore[union-attr] - except AttributeError: - any_element = None - - for e in safe_iter_path(): - - previous_path: List[ModelParticleType] - for pe, previous_path in paths.values(): - # EDC check - if not e.is_consistent(pe) or any_element and not any_element.is_consistent(pe): - msg = _("Element Declarations Consistent violation between {0!r} and {1!r}" - ": match the same name but with different types").format(e, pe) - raise XMLSchemaModelError(self, msg) - - # UPA check - if pe is e or not pe.is_overlap(e): - continue - elif pe.parent is e.parent: - if pe.parent.model in {'all', 'choice'}: - if isinstance(pe, Xsd11AnyElement) and not isinstance(e, XsdAnyElement): - pe.add_precedence(e, self) - elif isinstance(e, Xsd11AnyElement) and not isinstance(pe, XsdAnyElement): - e.add_precedence(pe, self) - else: - msg = _("{0!r} and {1!r} overlap and are in the same {2!r} group") - raise XMLSchemaModelError(self, msg.format(pe, e, pe.parent.model)) - elif pe.is_univocal(): - continue - - if distinguishable_paths(previous_path + [pe], current_path + [e]): - continue - elif isinstance(pe, Xsd11AnyElement) and not isinstance(e, XsdAnyElement): - pe.add_precedence(e, self) - elif isinstance(e, Xsd11AnyElement) and not isinstance(pe, XsdAnyElement): - e.add_precedence(pe, self) - else: - msg = _("Unique Particle Attribution violation between {0!r} and {1!r}") - raise XMLSchemaModelError(self, msg.format(pe, e)) - - paths[e.name] = e, current_path[:] - def check_dynamic_context(self, elem: ElementType, xsd_element: SchemaElementType, model_element: SchemaElementType, @@ -1137,9 +1065,7 @@ if not obj.content: content = [] elif isinstance(obj.content, MutableMapping) or kwargs.get('unordered'): - content = ModelVisitor(self).iter_unordered_content( - obj.content, default_namespace - ) + content = iter_unordered_content(obj.content, self, default_namespace) elif not isinstance(obj.content, MutableSequence): wrong_content_type = True content = [] @@ -1152,9 +1078,7 @@ elif converter.losslessly: content = obj.content else: - content = ModelVisitor(self).iter_collapsed_content( - obj.content, default_namespace - ) + content = iter_collapsed_content(obj.content, self, default_namespace) for index, (name, value) in enumerate(content): if isinstance(name, int): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/xmlschema/validators/models.py new/xmlschema-2.2.3/xmlschema/validators/models.py --- old/xmlschema-2.2.2/xmlschema/validators/models.py 2022-06-23 22:06:57.000000000 +0200 +++ new/xmlschema-2.2.3/xmlschema/validators/models.py 2023-04-14 15:49:05.000000000 +0200 @@ -8,19 +8,24 @@ # @author Davide Brunato <brun...@sissa.it> # """ -This module contains a function and a class for validating XSD content models. +This module contains a function and a class for validating XSD content models, +plus a set of functions for manipulating encoded content. """ from collections import defaultdict, deque -from typing import Any, Counter, Dict, Iterable, Iterator, List, Optional, Tuple, Union +from typing import Any, Counter, Dict, Iterable, Iterator, List, \ + MutableMapping, MutableSequence, Optional, Tuple, Union from ..exceptions import XMLSchemaValueError from ..aliases import ModelGroupType, ModelParticleType, SchemaElementType +from ..translation import gettext as _ +from .. import limits +from .exceptions import XMLSchemaModelError, XMLSchemaModelDepthError +from .wildcards import XsdAnyElement, Xsd11AnyElement from . import groups AdvanceYieldedType = Tuple[ModelParticleType, int, List[SchemaElementType]] -EncodedContentType = Union[Dict[Union[int, str], List[Any]], - List[Tuple[Union[int, str], List[Any]]]] ContentItemType = Tuple[Union[int, str], Any] +EncodedContentType = Union[MutableMapping[Union[int, str], Any], Iterable[ContentItemType]] def distinguishable_paths(path1: List[ModelParticleType], path2: List[ModelParticleType]) -> bool: @@ -88,6 +93,81 @@ (before1 or (before2 or univocal2) and (path2[-1].is_univocal() or after2)) +def check_model(group: ModelGroupType) -> None: + """ + Checks if the model group is deterministic. Element Declarations Consistent and + Unique Particle Attribution constraints are checked. + + :param group: the model group to check. + :raises: an `XMLSchemaModelError` at first violated constraint. + """ + def safe_iter_path() -> Iterator[SchemaElementType]: + iterators: List[Iterator[ModelParticleType]] = [] + particles = iter(group) + + while True: + for item in particles: + if isinstance(item, groups.XsdGroup): + current_path.append(item) + iterators.append(particles) + particles = iter(item) + if len(iterators) > limits.MAX_MODEL_DEPTH: + raise XMLSchemaModelDepthError(group) + break + else: + yield item + else: + try: + current_path.pop() + particles = iterators.pop() + except IndexError: + return + + paths: Any = {} + current_path: List[ModelParticleType] = [group] + try: + any_element = group.parent.open_content.any_element # type: ignore[union-attr] + except AttributeError: + any_element = None + + for e in safe_iter_path(): + + previous_path: List[ModelParticleType] + for pe, previous_path in paths.values(): + # EDC check + if not e.is_consistent(pe) or any_element and not any_element.is_consistent(pe): + msg = _("Element Declarations Consistent violation between {0!r} and {1!r}" + ": match the same name but with different types").format(e, pe) + raise XMLSchemaModelError(group, msg) + + # UPA check + if pe is e or not pe.is_overlap(e): + continue + elif pe.parent is e.parent: + if pe.parent.model in {'all', 'choice'}: + if isinstance(pe, Xsd11AnyElement) and not isinstance(e, XsdAnyElement): + pe.add_precedence(e, group) + elif isinstance(e, Xsd11AnyElement) and not isinstance(pe, XsdAnyElement): + e.add_precedence(pe, group) + else: + msg = _("{0!r} and {1!r} overlap and are in the same {2!r} group") + raise XMLSchemaModelError(group, msg.format(pe, e, pe.parent.model)) + elif pe.is_univocal(): + continue + + if distinguishable_paths(previous_path + [pe], current_path + [e]): + continue + elif isinstance(pe, Xsd11AnyElement) and not isinstance(e, XsdAnyElement): + pe.add_precedence(e, group) + elif isinstance(e, Xsd11AnyElement) and not isinstance(pe, XsdAnyElement): + e.add_precedence(pe, group) + else: + msg = _("Unique Particle Attribution violation between {0!r} and {1!r}") + raise XMLSchemaModelError(group, msg.format(pe, e)) + + paths[e.name] = e, current_path[:] + + class ModelVisitor: """ A visitor design pattern class that can be used for validating XML data related to an XSD @@ -105,6 +185,8 @@ _groups: List[Tuple[ModelGroupType, Iterator[ModelParticleType], bool]] element: Optional[SchemaElementType] + __slots__ = '_groups', 'root', 'occurs', 'element', 'group', 'items', 'match' + def __init__(self, root: ModelGroupType) -> None: self._groups = [] self.root = root @@ -246,7 +328,7 @@ 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 + occurs[self.group] += 1 break min_group_occurs = max(1, item_occurs // (item2.max_occurs or item_occurs)) @@ -258,25 +340,25 @@ return item.is_missing(max(occurs[item], occurs[(item,)])) - element, occurs = self.element, self.occurs - if element is None: + occurs = self.occurs + if self.element is None: raise XMLSchemaValueError("cannot advance, %r is ended!" % self) if match: - occurs[element] += 1 + occurs[self.element] += 1 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 not element.is_over(occurs[element]): + elif not self.element.is_over(occurs[self.element]): return - elif self.group.model == 'choice' and element.is_ambiguous(): + elif self.group.model == 'choice' and self.element.is_ambiguous(): return obj = None try: - element_occurs = occurs[element] - if stop_item(element): - yield element, element_occurs, [element] + element_occurs = occurs[self.element] + if stop_item(self.element): + yield self.element, element_occurs, [self.element] while True: while self.group.is_over(max(occurs[self.group], occurs[(self.group,)])): @@ -333,131 +415,156 @@ elif self.group.max_occurs is not None and self.group.max_occurs < occurs[self.group]: yield self.group, occurs[self.group], self.expected - def sort_content(self, content: EncodedContentType, restart: bool = True) \ - -> List[ContentItemType]: - if restart: - self.restart() - return [(name, value) for name, value in self.iter_unordered_content(content)] - + # Kept for backward compatibility def iter_unordered_content( self, content: EncodedContentType, default_namespace: Optional[str] = None) -> Iterator[ContentItemType]: - """ - Takes an unordered content stored in a dictionary of lists and yields the - content elements sorted with the ordering defined by the model. Character - data parts are yielded at start and between child elements. - - Ordering is inferred from ModelVisitor instance with any elements that - don't fit the schema placed at the end of the returned sequence. Checking - the yielded content validity is the responsibility of method *iter_encode* - of class :class:`XsdGroup`. - - :param content: a dictionary of element names to list of element contents \ - or an iterable composed of couples of name and value. In case of a \ - dictionary the values must be lists where each item is the content \ - of a single element. - :param default_namespace: the default namespace to apply for matching names. - """ - consumable_content: Dict[str, Any] + return iter_unordered_content(content, self.root, default_namespace) - if isinstance(content, dict): - cdata_content = sorted( - ((k, v) for k, v in content.items() if isinstance(k, int)), reverse=True - ) - consumable_content = {k: deque(v) for k, v in content.items() if not isinstance(k, int)} - else: - cdata_content = sorted(((k, v) for k, v in content if isinstance(k, int)), reverse=True) - consumable_content = defaultdict(deque) - for k, v in content: - if isinstance(k, str): - consumable_content[k].append(v) - - if cdata_content: - yield cdata_content.pop() - - while self.element is not None and consumable_content: # pragma: no cover - for name in consumable_content: - if self.element.is_matching(name, default_namespace, group=self.group): - yield name, consumable_content[name].popleft() - if not consumable_content[name]: - del consumable_content[name] - for _ in self.advance(True): - pass - if cdata_content: - yield cdata_content.pop() - break - else: - # Consume the return of advance otherwise we get stuck in an infinite loop. - for _ in self.advance(False): - pass + def iter_collapsed_content( + self, content: Iterable[ContentItemType], + default_namespace: Optional[str] = None) -> Iterator[ContentItemType]: + return iter_collapsed_content(content, self.root, default_namespace) - # Add the remaining consumable content onto the end of the data. - for name, values in consumable_content.items(): - for v in values: - yield name, v + +# +# Functions for manipulating encoded content + +def iter_unordered_content( + content: EncodedContentType, + group: ModelGroupType, + default_namespace: Optional[str] = None) -> Iterator[ContentItemType]: + """ + Takes an unordered content stored in a dictionary of lists and yields the + content elements sorted with the ordering defined by the model group. Character + data parts are yielded at start and between child elements. + + Ordering is inferred from ModelVisitor instance with any elements that + don't fit the schema placed at the end of the returned sequence. Checking + the yielded content validity is the responsibility of method *iter_encode* + of class :class:`XsdGroup`. + + :param content: a dictionary of element names to list of element contents \ + or an iterable composed of couples of name and value. In case of a \ + dictionary the values must be lists where each item is the content \ + of a single element. + :param group: the model group related to content. + :param default_namespace: the default namespace to apply for matching names. + """ + consumable_content: Dict[str, Any] + + if isinstance(content, MutableMapping): + cdata_content = sorted( + ((k, v) for k, v in content.items() if isinstance(k, int)), reverse=True + ) + consumable_content = { + k: deque(v) if isinstance(v, MutableSequence) else deque([v]) + for k, v in content.items() if not isinstance(k, int) + } + else: + cdata_content = sorted(((k, v) for k, v in content if isinstance(k, int)), reverse=True) + consumable_content = defaultdict(deque) + for k, v in content: + if isinstance(k, str): + consumable_content[k].append(v) + + if cdata_content: + yield cdata_content.pop() + + model = ModelVisitor(group) + while model.element is not None and consumable_content: # pragma: no cover + for name in consumable_content: + if model.element.is_matching(name, default_namespace, group=group): + yield name, consumable_content[name].popleft() + if not consumable_content[name]: + del consumable_content[name] + for _err in model.advance(True): + pass if cdata_content: yield cdata_content.pop() + break + else: + # Consume the return of advance otherwise we get stuck in an infinite loop. + for _err in model.advance(False): + pass - while cdata_content: - yield cdata_content.pop() - - def iter_collapsed_content( - self, content: Iterable[Tuple[Union[int, str], Any]], - default_namespace: Optional[str] = None) -> Iterator[ContentItemType]: - """ - Iterates a content stored in a sequence of couples *(name, value)*, yielding - items in the same order of the sequence, except for repetitions of the same - tag that don't match with the current element of the :class:`ModelVisitor` - instance. These items are included in an unsorted buffer and yielded asap - when there is a match with the model's element or at the end of the iteration. - - This iteration mode, in cooperation with the method *iter_encode* of the class - XsdGroup, facilitates the encoding of content formatted with a convention that - collapses the children with the same tag into a list (eg. BadgerFish). + # Add the remaining consumable content onto the end of the data. + for name, values in consumable_content.items(): + for v in values: + yield name, v + if cdata_content: + yield cdata_content.pop() + + while cdata_content: + yield cdata_content.pop() + + +def sort_content(content: EncodedContentType, + group: ModelGroupType, + default_namespace: Optional[str] = None) -> List[ContentItemType]: + return [x for x in iter_unordered_content(content, group, default_namespace)] + + +def iter_collapsed_content( + content: Iterable[ContentItemType], + group: ModelGroupType, + default_namespace: Optional[str] = None) -> Iterator[ContentItemType]: + """ + Iterates a content stored in a sequence of couples *(name, value)*, yielding + items in the same order of the sequence, except for repetitions of the same + tag that don't match with the current element of the :class:`ModelVisitor` + instance. These items are included in an unsorted buffer and yielded asap + when there is a match with the model's element or at the end of the iteration. + + This iteration mode, in cooperation with the method *iter_encode* of the class + XsdGroup, facilitates the encoding of content formatted with a convention that + collapses the children with the same tag into a list (eg. BadgerFish). + + :param content: an iterable containing couples of names and values. + :param group: the model group related to content. + :param default_namespace: the default namespace to apply for matching names. + """ + prev_name = None + unordered_content: Dict[str, Any] = defaultdict(deque) - :param content: an iterable containing couples of names and values. - :param default_namespace: the default namespace to apply for matching names. - """ - prev_name = None - unordered_content: Dict[str, Any] = defaultdict(deque) + model = ModelVisitor(group) + for name, value in content: + if isinstance(name, int) or model.element is None: + yield name, value + continue - for name, value in content: - if isinstance(name, int) or self.element is None: + while model.element is not None: + if model.element.is_matching(name, default_namespace, group=group): yield name, value - continue + prev_name = name + for _err in model.advance(True): + pass + break - while self.element is not None: - if self.element.is_matching(name, default_namespace, group=self.group): - yield name, value - prev_name = name - for _ in self.advance(True): - pass + for key in unordered_content: + if model.element.is_matching(key, default_namespace, group=group): + break + else: + if prev_name == name: + unordered_content[name].append(value) break - for key in unordered_content: - if self.element.is_matching(key, default_namespace, group=self.group): - break - else: - if prev_name == name: - unordered_content[name].append(value) - break - - for _ in self.advance(False): - pass - continue + for _err in model.advance(False): + pass + continue - try: - yield key, unordered_content[key].popleft() - except IndexError: - del unordered_content[key] - else: - for _ in self.advance(True): - pass + try: + yield key, unordered_content[key].popleft() + except IndexError: + del unordered_content[key] else: - yield name, value - prev_name = name + for _err in model.advance(True): + pass + else: + yield name, value + prev_name = name - # Add the remaining consumable content onto the end of the data. - for name, values in unordered_content.items(): - for v in values: - yield name, v + # Add the remaining consumable content onto the end of the data. + for name, values in unordered_content.items(): + for v in values: + yield name, v diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/xmlschema.egg-info/PKG-INFO new/xmlschema-2.2.3/xmlschema.egg-info/PKG-INFO --- old/xmlschema-2.2.2/xmlschema.egg-info/PKG-INFO 2023-03-05 22:10:04.000000000 +0100 +++ new/xmlschema-2.2.3/xmlschema.egg-info/PKG-INFO 2023-04-14 15:52:42.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: xmlschema -Version: 2.2.2 +Version: 2.2.3 Summary: An XML Schema validator and decoder Home-page: https://github.com/sissaschool/xmlschema Author: Davide Brunato @@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/xmlschema-2.2.2/xmlschema.egg-info/SOURCES.txt new/xmlschema-2.2.3/xmlschema.egg-info/SOURCES.txt --- old/xmlschema-2.2.2/xmlschema.egg-info/SOURCES.txt 2023-03-05 22:10:04.000000000 +0100 +++ new/xmlschema-2.2.3/xmlschema.egg-info/SOURCES.txt 2023-04-14 15:52:42.000000000 +0200 @@ -274,6 +274,9 @@ tests/test_cases/issues/issue_334/issue_334.xml tests/test_cases/issues/issue_334/issue_334.xsd tests/test_cases/issues/issue_334/issue_334.zip +tests/test_cases/issues/issue_341/issue_341-ext.xsd +tests/test_cases/issues/issue_341/issue_341.xml +tests/test_cases/issues/issue_341/issue_341.xsd tests/test_cases/mypy/extra_validator.py tests/test_cases/mypy/schema_source.py tests/test_cases/mypy/simple_types.py