Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid using DerivedElement with known elements for mixed wildcards #696

Merged
merged 1 commit into from
Aug 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,21 @@ Code Generation
examples/docstrings
examples/xml-modeling
examples/json-modeling
examples/pycode-serializer
examples/dtd-modeling
examples/compound-fields
examples/dataclasses-features


Data Binding
============

.. toctree::
:maxdepth: 1

examples/pycode-serializer
examples/working-with-wildcards


Advance Topics
==============

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
How to work with wildcard fields?
==================================
======================
Working with wildcards
======================


One of the xml schema traits is to support any extensions with wildcards.

Expand Down Expand Up @@ -39,6 +41,9 @@ The generator will roughly create this class for you.
... )


Generics
========

xsdata comes with two generic models that are used during parsing and you can also use
to generate any custom xml element.

Expand Down Expand Up @@ -87,3 +92,63 @@ to generate any custom xml element.
</bar>
</MetadataType>
<BLANKLINE>


Mixed content
=============

For mixed content with known choices you can skip wrapping your instances with a
generic model. During data binding xsdata will try first to match one of the qualified
choices.

.. doctest::

>>> @dataclass
... class Beta:
... class Meta:
... name = "beta"
...
>>> @dataclass
... class Alpha:
... class Meta:
... name = "alpha"
...
>>> @dataclass
... class Doc:
... class Meta:
... name = "doc"
...
... content: List[object] = field(
... default_factory=list,
... metadata={
... "type": "Wildcard",
... "namespace": "##any",
... "mixed": True,
... "choices": (
... {
... "name": "a",
... "type": Alpha,
... "namespace": "",
... },
... {
... "name": "b",
... "type": Beta,
... "namespace": "",
... },
... ),
... }
... )
...
>>> obj = Doc(
... content=[
... Alpha(),
... Beta(),
... ]
... )
...
>>> print(serializer.render(obj))
<doc>
<a/>
<b/>
</doc>
<BLANKLINE>
9 changes: 8 additions & 1 deletion tests/fixtures/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,14 @@ class Meta:

content: List[object] = field(
default_factory=list,
metadata=dict(type="Wildcard", namespace="##any", mixed=True),
metadata=dict(
type="Wildcard",
namespace="##any",
mixed=True,
choices=(
dict(name="span", type=Span),
),
)
)


Expand Down
24 changes: 18 additions & 6 deletions tests/formats/dataclass/parsers/nodes/test_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,26 +266,38 @@ def test_bind_wild_list_var(self):
self.assertEqual(expected, params)

def test_prepare_generic_value(self):
actual = self.node.prepare_generic_value(None, 1)
var = XmlVarFactory.create(
index=2,
xml_type=XmlType.WILDCARD,
qname="a",
types=(object,),
elements={"known": XmlVarFactory.create()},
)

actual = self.node.prepare_generic_value(None, 1, var)
self.assertEqual(1, actual)

actual = self.node.prepare_generic_value("a", 1)
actual = self.node.prepare_generic_value("a", 1, var)
expected = AnyElement(qname="a", text="1")
self.assertEqual(expected, actual)

actual = self.node.prepare_generic_value("a", "foo")
actual = self.node.prepare_generic_value("a", "foo", var)
expected = AnyElement(qname="a", text="foo")
self.assertEqual(expected, actual)

fixture = make_dataclass("Fixture", [("content", str)])
actual = self.node.prepare_generic_value("a", fixture("foo"))
actual = self.node.prepare_generic_value("a", fixture("foo"), var)
expected = DerivedElement(qname="a", value=fixture("foo"), type="Fixture")
self.assertEqual(expected, actual)

actual = self.node.prepare_generic_value("a", expected)
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"))
actual = self.node.prepare_generic_value("Fixture", fixture("foo"), var)
expected = DerivedElement(qname="Fixture", value=fixture("foo"))
self.assertEqual(expected, actual)

Expand Down
13 changes: 9 additions & 4 deletions xsdata/formats/dataclass/parsers/nodes/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def bind_wild_var(self, params: Dict, var: XmlVar, qname: str, value: Any) -> bo
generic instance add the current value as a child object.
"""

value = self.prepare_generic_value(qname, value)
value = self.prepare_generic_value(qname, value, var)

if var.list_element:
items = params.get(var.name)
Expand All @@ -240,11 +240,14 @@ def bind_mixed_objects(self, params: Dict, var: XmlVar, objects: List):

pos = self.position
params[var.name] = [
self.prepare_generic_value(qname, value) for qname, value in objects[pos:]
self.prepare_generic_value(qname, value, var)
for qname, value in objects[pos:]
]
del objects[pos:]

def prepare_generic_value(self, qname: Optional[str], value: Any) -> Any:
def prepare_generic_value(
self, qname: Optional[str], value: Any, var: XmlVar
) -> Any:
"""Prepare parsed value before binding to a wildcard field."""

if qname:
Expand All @@ -253,7 +256,9 @@ def prepare_generic_value(self, qname: Optional[str], value: Any) -> Any:

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)):
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)
Expand Down
10 changes: 7 additions & 3 deletions xsdata/formats/dataclass/serializers/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,15 @@ def write_dataclass(
yield XmlWriterEvent.END, qname

def write_xsi_type(self, value: Any, var: XmlVar, namespace: NoneStr) -> Generator:
"""Produce an events stream from a dataclass for the given var with
with xsi abstract type check for non wildcards."""
"""Produce an events stream from a dataclass for the given var with xsi
abstract type check for non wildcards."""

if var.is_wildcard:
yield from self.write_dataclass(value, namespace)
choice = var.find_value_choice(value, True)
if choice:
yield from self.write_value(value, choice, namespace)
else:
yield from self.write_dataclass(value, namespace)
else:
xsi_type = self.xsi_type(var, value, namespace)
yield from self.write_dataclass(
Expand Down