diff --git a/tests/formats/dataclass/parsers/nodes/test_element.py b/tests/formats/dataclass/parsers/nodes/test_element.py index 4a52f63a..878c5e7a 100644 --- a/tests/formats/dataclass/parsers/nodes/test_element.py +++ b/tests/formats/dataclass/parsers/nodes/test_element.py @@ -235,15 +235,16 @@ def test_bind_objects(self, mock_warning): def test_bind_wild_var(self): self.node.meta = self.context.build(ExtendedType) + x = make_dataclass("x", [("value", int)]) params = {} - objects = [("x", 1), ("x", 2), ("z", 3.0)] + objects = [("x", x(1)), ("y", 2), ("z", 3.0)] self.node.bind_objects(params, objects) expected = { "wildcard": AnyElement( children=[ - AnyElement(qname="x", text="1"), - AnyElement(qname="x", text="2"), + x(1), + AnyElement(qname="y", text="2"), AnyElement(qname="z", text="3.0"), ] ) @@ -287,20 +288,8 @@ def test_prepare_generic_value(self): fixture = make_dataclass("Fixture", [("content", str)]) actual = self.node.prepare_generic_value("a", fixture("foo"), var) - expected = DerivedElement(qname="a", value=fixture("foo"), type="Fixture") - self.assertEqual(expected, actual) - - fixture = make_dataclass("Fixture", [("content", str)]) - actual = self.node.prepare_generic_value("known", fixture("foo"), var) self.assertEqual(fixture("foo"), actual) - actual = self.node.prepare_generic_value("a", expected, var) - self.assertIs(expected, actual) - - actual = self.node.prepare_generic_value("Fixture", fixture("foo"), var) - expected = DerivedElement(qname="Fixture", value=fixture("foo")) - self.assertEqual(expected, actual) - def test_child(self): var = XmlVarFactory.create(xml_type=XmlType.ELEMENT, qname="a", types=(TypeC,)) attrs = {"a": "b"} @@ -316,10 +305,10 @@ def test_child(self): def test_child_with_unique_element(self): single = XmlVarFactory.create( - index=1, xml_type=XmlType.ELEMENT, qname="a", types=(TypeC,) + index=1, xml_type=XmlType.ELEMENT, qname="cc", types=(TypeC,) ) wildcard = XmlVarFactory.create( - index=2, xml_type=XmlType.WILDCARD, qname="a", types=(object,) + index=2, xml_type=XmlType.WILDCARD, qname="cc", types=(object,) ) self.meta.elements[single.qname] = [single] self.meta.wildcards.append(wildcard) @@ -328,11 +317,11 @@ def test_child_with_unique_element(self): ns_map = {"ns0": "xsdata"} position = 1 - actual = self.node.child("a", attrs, ns_map, position) + actual = self.node.child("cc", attrs, ns_map, position) self.assertIsInstance(actual, ElementNode) self.assertIn(single.index, self.node.assigned) - actual = self.node.child("a", attrs, ns_map, position) + actual = self.node.child("cc", attrs, ns_map, position) self.assertIsInstance(actual, WildcardNode) self.assertNotIn(wildcard.index, self.node.assigned) @@ -364,7 +353,7 @@ def test_build_node_with_dataclass_union_var(self): ) attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} - actual = self.node.build_node(var, attrs, ns_map, 10) + actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, UnionNode) self.assertEqual(10, actual.position) @@ -391,7 +380,7 @@ def test_build_node_with_dataclass_var(self, mock_ctx_fetch, mock_xsi_type): attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} - actual = self.node.build_node(var, attrs, ns_map, 10) + actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, ElementNode) self.assertEqual(10, actual.position) @@ -420,7 +409,7 @@ def test_build_node_with_dataclass_var_and_mismatch_xsi_type( attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} - actual = self.node.build_node(var, attrs, ns_map, 10) + actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, ElementNode) self.assertEqual(10, actual.position) @@ -439,13 +428,13 @@ def test_build_node_with_dataclass_var_validates_nillable(self, mock_ctx_fetch): mock_ctx_fetch.side_effect = [self.meta, self.meta, nillable_meta] attrs = {QNames.XSI_NIL: "false"} - self.assertIsNotNone(self.node.build_node(var, attrs, ns_map, 10)) + self.assertIsNotNone(self.node.build_node(var.qname, var, attrs, ns_map, 10)) attrs = {QNames.XSI_NIL: "true"} - self.assertIsNotNone(self.node.build_node(var, attrs, ns_map, 10)) + self.assertIsNone(self.node.build_node(var.qname, var, attrs, ns_map, 10)) attrs = {QNames.XSI_NIL: "false"} - self.assertIsNone(self.node.build_node(var, attrs, ns_map, 10)) + self.assertIsNone(self.node.build_node(var.qname, var, attrs, ns_map, 10)) def test_build_node_with_any_type_var_with_matching_xsi_type(self): var = XmlVarFactory.create( @@ -457,7 +446,7 @@ def test_build_node_with_any_type_var_with_matching_xsi_type(self): ) attrs = {QNames.XSI_TYPE: "bk:books"} ns_map = {"bk": "urn:books"} - actual = self.node.build_node(var, attrs, ns_map, 10) + actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, ElementNode) self.assertEqual(10, actual.position) @@ -476,7 +465,7 @@ def test_build_node_with_any_type_var_with_datatype(self): ) attrs = {QNames.XSI_TYPE: "xs:hexBinary"} ns_map = {Namespace.XS.prefix: Namespace.XS.uri} - actual = self.node.build_node(var, attrs, ns_map, 10) + actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, StandardNode) self.assertEqual(ns_map, actual.ns_map) @@ -487,12 +476,12 @@ def test_build_node_with_any_type_var_with_no_matching_xsi_type(self): var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", - qname="a", + qname="aaa", types=(object,), any_type=True, ) attrs = {QNames.XSI_TYPE: "noMatch"} - actual = self.node.build_node(var, attrs, {}, 10) + actual = self.node.build_node(var.qname, var, attrs, {}, 10) self.assertIsInstance(actual, WildcardNode) self.assertEqual(10, actual.position) @@ -504,12 +493,12 @@ def test_build_node_with_any_type_var_with_no_xsi_type(self): var = XmlVarFactory.create( xml_type=XmlType.ELEMENT, name="a", - qname="a", + qname="aaaa", types=(object,), any_type=True, ) attrs = {} - actual = self.node.build_node(var, attrs, {}, 10) + actual = self.node.build_node(var.qname, var, attrs, {}, 10) self.assertIsInstance(actual, WildcardNode) self.assertEqual(10, actual.position) @@ -517,10 +506,29 @@ def test_build_node_with_any_type_var_with_no_xsi_type(self): self.assertEqual(attrs, actual.attrs) self.assertEqual({}, actual.ns_map) + def test_build_node_with_any_type_var_with_no_xsi_type_and_type_Exists(self): + var = XmlVarFactory.create( + xml_type=XmlType.ELEMENT, + name="a", + qname="a", + types=(object,), + any_type=True, + ) + attrs = {} + a = make_dataclass("a", [("a", int)]) + actual = self.node.build_node(var.qname, var, attrs, {}, 10) + + self.assertIsInstance(actual, ElementNode) + self.assertEqual(10, actual.position) + self.assertEqual(self.context.build(a), actual.meta) + self.assertEqual(attrs, actual.attrs) + self.assertEqual({}, actual.ns_map) + self.assertFalse(actual.mixed) + def test_build_node_with_wildcard_var(self): - var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, qname="a") + var = XmlVarFactory.create(xml_type=XmlType.WILDCARD, qname="aaaaa") - actual = self.node.build_node(var, {}, {}, 10) + actual = self.node.build_node(var.qname, var, {}, {}, 10) self.assertIsInstance(actual, WildcardNode) self.assertEqual(10, actual.position) @@ -532,7 +540,7 @@ def test_build_node_with_primitive_var(self): ) attrs = {"a": "b"} ns_map = {"ns0": "xsdata"} - actual = self.node.build_node(var, attrs, ns_map, 10) + actual = self.node.build_node(var.qname, var, attrs, ns_map, 10) self.assertIsInstance(actual, PrimitiveNode) self.assertEqual(ns_map, actual.ns_map) diff --git a/tests/formats/dataclass/serializers/test_xml.py b/tests/formats/dataclass/serializers/test_xml.py index 45af76cd..ee0146ce 100644 --- a/tests/formats/dataclass/serializers/test_xml.py +++ b/tests/formats/dataclass/serializers/test_xml.py @@ -534,6 +534,29 @@ def test_write_choice_with_raw_value(self): self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) + def test_write_choice_when_no_matching_choice_exists_but_value_is_model(self): + var = XmlVarFactory.create( + xml_type=XmlType.ELEMENTS, + name="compound", + qname="compound", + elements={ + "a": XmlVarFactory.create( + xml_type=XmlType.WILDCARD, qname="a", types=(object,) + ) + }, + ) + + ebook = make_dataclass("eBook", [], bases=(BookForm,)) + expected = [ + (XmlWriterEvent.START, "eBook"), + (XmlWriterEvent.ATTR, "lang", "en"), + (XmlWriterEvent.END, "eBook"), + ] + + result = self.serializer.write_value(ebook(), var, None) + self.assertIsInstance(result, Generator) + self.assertEqual(expected, list(result)) + def test_write_choice_when_no_matching_choice_exists(self): var = XmlVarFactory.create( xml_type=XmlType.ELEMENTS, diff --git a/tests/models/test_config.py b/tests/models/test_config.py index 742f4a3a..2ed8cece 100644 --- a/tests/models/test_config.py +++ b/tests/models/test_config.py @@ -167,7 +167,8 @@ def test_use_union_type_requires_310(self): ) else: - self.assertTrue(GeneratorOutput(union_type=True).union_type) + output = GeneratorOutput(union_type=True, postponed_annotations=True) + self.assertTrue(output.union_type) def test_format_slots_requires_310(self): if sys.version_info < (3, 10): diff --git a/xsdata/formats/dataclass/parsers/nodes/element.py b/xsdata/formats/dataclass/parsers/nodes/element.py index d9c97cc3..41bc22ae 100644 --- a/xsdata/formats/dataclass/parsers/nodes/element.py +++ b/xsdata/formats/dataclass/parsers/nodes/element.py @@ -17,7 +17,6 @@ from xsdata.formats.dataclass.parsers.utils import PendingCollection from xsdata.logger import logger from xsdata.models.enums import DataType -from xsdata.utils import namespaces class ElementNode(XmlNode): @@ -222,8 +221,9 @@ def bind_wild_var(self, params: Dict, var: XmlVar, qname: str, value: Any) -> bo items.append(value) elif var.name in params: previous = params[var.name] - if previous.qname: - factory = self.context.class_type.any_element + factory = self.context.class_type.any_element + + if not isinstance(previous, factory) or previous.qname: params[var.name] = factory(children=[previous]) params[var.name].children.append(value) @@ -248,18 +248,9 @@ def prepare_generic_value( ) -> Any: """Prepare parsed value before binding to a wildcard field.""" - if qname: + if qname and not self.context.class_type.is_model(value): any_factory = self.context.class_type.any_element - derived_factory = self.context.class_type.derived_element - - if not self.context.class_type.is_model(value): - value = any_factory(qname=qname, text=converter.serialize(value)) - elif not isinstance( - value, (any_factory, derived_factory) - ) and not var.find_choice(qname): - meta = self.context.fetch(type(value)) - xsi_type = namespaces.real_xsi_type(qname, meta.target_qname) - value = derived_factory(qname=qname, value=value, type=xsi_type) + value = any_factory(qname=qname, text=converter.serialize(value)) return value @@ -336,7 +327,7 @@ def child(self, qname: str, attrs: Dict, ns_map: Dict, position: int) -> XmlNode for var in self.meta.find_children(qname): unique = 0 if not var.is_element or var.list_element else var.index if not unique or unique not in self.assigned: - node = self.build_node(var, attrs, ns_map, position) + node = self.build_node(qname, var, attrs, ns_map, position) if node: if unique: @@ -350,7 +341,7 @@ def child(self, qname: str, attrs: Dict, ns_map: Dict, position: int) -> XmlNode return nodes.SkipNode() def build_node( - self, var: XmlVar, attrs: Dict, ns_map: Dict, position: int + self, qname: str, var: XmlVar, attrs: Dict, ns_map: Dict, position: int ) -> Optional[XmlNode]: if var.is_clazz_union: return nodes.UnionNode( @@ -370,6 +361,7 @@ def build_node( return self.build_element_node( var.clazz, var.derived, + var.nillable, attrs, ns_map, position, @@ -399,6 +391,7 @@ def build_node( node = self.build_element_node( clazz, derived, + var.nillable, attrs, ns_map, position, @@ -410,6 +403,25 @@ def build_node( if node: return node + if var.process_contents != "skip": + clazz = self.context.find_type(qname) + + if clazz: + node = self.build_element_node( + clazz, + False, + var.nillable, + attrs, + ns_map, + position, + None, + xsi_type, + xsi_nil, + ) + + if node: + return node + return nodes.WildcardNode( var=var, attrs=attrs, @@ -422,6 +434,7 @@ def build_element_node( self, clazz: Type, derived: bool, + nillable: bool, attrs: Dict, ns_map: Dict, position: int, @@ -430,8 +443,9 @@ def build_element_node( xsi_nil: Optional[bool] = None, ) -> Optional[XmlNode]: meta = self.context.fetch(clazz, self.meta.namespace, xsi_type) + nillable = nillable or meta.nillable - if not meta or (meta.nillable and xsi_nil is False): + if not meta or (xsi_nil is not None and nillable != xsi_nil): return None if xsi_type and not derived and not issubclass(meta.clazz, clazz): diff --git a/xsdata/formats/dataclass/parsers/tree.py b/xsdata/formats/dataclass/parsers/tree.py index 57ed1adc..ed8fbd32 100644 --- a/xsdata/formats/dataclass/parsers/tree.py +++ b/xsdata/formats/dataclass/parsers/tree.py @@ -52,6 +52,7 @@ def start( format=None, derived=False, any_type=False, + process_contents="strict", required=False, nillable=False, sequence=None, diff --git a/xsdata/formats/dataclass/serializers/xml.py b/xsdata/formats/dataclass/serializers/xml.py index 58331597..652fc524 100644 --- a/xsdata/formats/dataclass/serializers/xml.py +++ b/xsdata/formats/dataclass/serializers/xml.py @@ -130,11 +130,15 @@ def write_xsi_type(self, value: Any, var: XmlVar, namespace: NoneStr) -> Generat yield from self.write_value(value, choice, namespace) else: yield from self.write_dataclass(value, namespace) - else: + elif var.is_element: xsi_type = self.xsi_type(var, value, namespace) yield from self.write_dataclass( value, namespace, var.qname, var.nillable, xsi_type ) + else: + # var elements + meta = self.context.fetch(value.__class__, namespace) + yield from self.write_dataclass(value, qname=meta.target_qname) def write_value(self, value: Any, var: XmlVar, namespace: NoneStr) -> Generator: """ @@ -251,7 +255,7 @@ def xsi_type(self, var: XmlVar, value: Any, namespace: NoneStr) -> Optional[str] clazz = var.clazz if clazz is None or self.context.is_derived(value, clazz): meta = self.context.fetch(value.__class__, namespace) - return meta.target_qname + return namespaces.real_xsi_type(var.qname, meta.target_qname) raise SerializerError( f"{value.__class__.__name__} is not derived from {clazz.__name__}" @@ -290,6 +294,10 @@ def write_choice(self, value: Any, var: XmlVar, namespace: NoneStr) -> Generator choice = var.find_value_choice(value, check_subclass) func = self.write_value + if not choice and check_subclass: + func = self.write_xsi_type + choice = var + if not choice: raise SerializerError( f"XmlElements undefined choice: `{var.name}` for `{type(value)}`"