Source code for xsdata.formats.dataclass.models.elements
from dataclasses import dataclass
from dataclasses import field
from dataclasses import InitVar
from dataclasses import is_dataclass
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Type
from xsdata.exceptions import XmlContextError
from xsdata.models.enums import NamespaceType
from xsdata.utils.namespaces import split_qname
NoneType = type(None)
[docs]@dataclass
class XmlVar:
"""
Dataclass field binding metadata.
:param name: Field name
:param qname: Qualified name
:param init: Include field in the constructor
:param mixed: Field supports mixed content type values
:param tokens: Field is derived from xs:list
:param format: Value format information
:param derived: Wrap parsed values with
:class:`~xsdata.formats.dataclass.models.generics.DerivedElement`
:param any_type: Field supports dynamic value types
:param nillable: Field supports nillable content
:param dataclass: Field value is bound to a dataclass
:param sequential: Render values in sequential mode
:param list_element: Field is a list of elements
:param default: Field default value or factory
:param text: Field is derived from xs:simpleType
:param element: Field is derived from xs:element
:param elements: Field is derived from xs:choice
:param wildcard: Field is derived from xs:anyType
:param attribute: Field is derived from xs:attribute
:param attributes: Field is derived from xs:attributes
:param types: List of all the supported data types
:param choices: List of repeatable choice elements
:param namespaces: List of the supported namespaces
"""
name: str
qname: str
init: bool = True
mixed: bool = False
tokens: bool = False
format: Optional[str] = None
derived: bool = False
any_type: bool = False
nillable: bool = False
dataclass: bool = False
sequential: bool = False
list_element: bool = False
default: Any = None
text: bool = False
element: bool = False
elements: bool = False
wildcard: bool = False
attribute: bool = False
attributes: bool = False
types: List[Type] = field(default_factory=list)
choices: List["XmlVar"] = field(default_factory=list)
namespaces: List[str] = field(default_factory=list)
xml_type: InitVar[Optional[str]] = None
def __post_init__(self, xml_type: Optional[str]):
if xml_type == XmlType.ELEMENT:
self.element = True
elif xml_type == XmlType.ELEMENTS:
self.elements = True
elif xml_type == XmlType.ATTRIBUTE:
self.attribute = True
self.any_type = False
elif xml_type == XmlType.ATTRIBUTES:
self.attributes = True
self.any_type = False
elif xml_type == XmlType.WILDCARD:
self.wildcard = True
self.any_type = False
elif xml_type == XmlType.TEXT:
self.text = True
self.any_type = False
elif xml_type:
raise XmlContextError(f"Unknown xml type `{xml_type}`")
@property
def lname(self) -> str:
"""Local name."""
_, name = split_qname(self.qname)
return name
@property
def clazz(self) -> Optional[Type]:
"""Return the first type if field is bound to a dataclass."""
return self.types[0] if self.dataclass else None
@property
def is_clazz_union(self) -> bool:
return self.dataclass and len(self.types) > 1
[docs] def matches(self, qname: str) -> bool:
"""
Match the field qualified local name to the given qname.
Return True automatically if the local name is a wildcard.
"""
if self.elements:
return self.matches_choice(qname)
if self.wildcard:
return self.matches_wildcard(qname)
return qname in (self.qname, "*")
[docs] def matches_choice(self, qname: str) -> bool:
"""Return whether a choice element matches the given qualified name."""
return self.find_choice(qname) is not None
[docs] def find_choice(self, qname: str) -> Optional["XmlVar"]:
"""Match and return a choice field by its qualified name."""
for choice in self.choices:
if choice.matches(qname):
return choice
return None
[docs] def find_value_choice(self, value: Any) -> Optional["XmlVar"]:
"""Match and return a choice field that matches the given value
type."""
if isinstance(value, list):
tp = type(None) if not value else type(value[0])
tokens = True
check_subclass = False
else:
tp = type(value)
tokens = False
check_subclass = is_dataclass(value)
return self.find_type_choice(tp, tokens, check_subclass)
[docs] def find_type_choice(
self, tp: Type, tokens: bool, check_subclass: bool
) -> Optional["XmlVar"]:
"""Match and return a choice field that matches the given type."""
for choice in self.choices:
if choice.any_type or tokens != choice.tokens:
continue
if tp is NoneType:
if choice.nillable:
return choice
elif self.match_type(tp, choice.types, check_subclass):
return choice
return None
@classmethod
def match_type(cls, tp: Type, types: List[Type], check_subclass: bool) -> bool:
for candidate in types:
if tp == candidate or (check_subclass and issubclass(tp, candidate)):
return True
return False
[docs] def matches_wildcard(self, qname: str) -> bool:
"""Match the given qname to the wildcard allowed namespaces."""
if qname == "*":
return True
namespace, tag = split_qname(qname)
if not self.namespaces and namespace is None:
return True
return any(self.match_namespace(ns, namespace) for ns in self.namespaces)
@staticmethod
def match_namespace(source: Optional[str], cmp: Optional[str]) -> bool:
if not source and cmp is None:
return True
if source == cmp:
return True
if source == NamespaceType.ANY_NS:
return True
if source and source[0] == "!" and source[1:] != cmp:
return True
return False
class FindMode:
"""Find switches to be used to find a specific var."""
ALL = 0
ATTRIBUTE = 1
ATTRIBUTES = 2
TEXT = 3
WILDCARD = 4
MIXED_CONTENT = 5
NOT_WILDCARD = 6
find_predicates = {
FindMode.ALL: lambda x: True,
FindMode.ATTRIBUTE: lambda x: x.attribute,
FindMode.ATTRIBUTES: lambda x: x.attributes,
FindMode.TEXT: lambda x: x.text,
FindMode.WILDCARD: lambda x: x.wildcard,
FindMode.MIXED_CONTENT: lambda x: x.mixed,
FindMode.NOT_WILDCARD: lambda x: not x.wildcard,
}
class XmlType:
"""Xml node types."""
TEXT = "Text"
ELEMENT = "Element"
ELEMENTS = "Elements"
WILDCARD = "Wildcard"
ATTRIBUTE = "Attribute"
ATTRIBUTES = "Attributes"