Source code for xsdata.formats.dataclass.models.elements
from dataclasses import dataclass
from dataclasses import field
from dataclasses import is_dataclass
from enum import auto
from enum import IntEnum
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Type
from xsdata.models.enums import NamespaceType
from xsdata.utils.namespaces import split_qname
[docs]@dataclass(frozen=True)
class XmlVar:
"""
Dataclass field bind metadata.
:param name: Field name.
:param qname: The namespace qualified local name.
:param init: Field is present in the constructor parameters.
:param mixed: Field supports mixed content.
:param tokens: Use a list to map simple values.
:param derived: Use derived elements to bind data.
:param nillable: Allow empty content elements rendering.
:param dataclass: Specify whether the field type is a dataclass.
:param sequential: Switch to sequential rendering with other sequential siblings
:param list_element: Specify whether the field represents a list of elements
:param default: Field default value or factory
:param choices: Field compound element choices.
:param types: Field simple types.
:param namespaces: Field list of the all the possible namespaces.
"""
name: str
qname: str
init: bool = True
mixed: bool = False
tokens: bool = False
derived: bool = False
nillable: bool = False
dataclass: bool = False
sequential: bool = False
list_element: bool = False
default: Any = None
choices: List["XmlVar"] = field(default_factory=list)
types: List[Type] = field(default_factory=list)
namespaces: List[str] = field(default_factory=list)
@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_any_type(self) -> bool:
"""Return whether the field type is xs:anyType."""
return False
@property
def is_attribute(self) -> bool:
"""Return whether the field is derived from xs:attribute."""
return False
@property
def is_attributes(self) -> bool:
"""Return whether the field is derived from xs:anyAttributes."""
return False
@property
def is_element(self) -> bool:
"""Return whether the field is derived from xs:element."""
return False
@property
def is_elements(self) -> bool:
"""Return whether the field is a compound of other elements."""
return False
@property
def is_list(self) -> bool:
"""Return whether the field is a list of elements."""
return self.list_element
@property
def is_mixed_content(self) -> bool:
"""Return whether the field is a mixed content list of of elements."""
return False
@property
def is_clazz_union(self) -> bool:
return self.dataclass and len(self.types) > 1
@property
def is_text(self) -> bool:
"""Return whether the field is a text element."""
return False
@property
def is_wildcard(self) -> bool:
"""Return whether the field is a text element."""
return False
@property
def local_name(self) -> str:
"""The element local name."""
return split_qname(self.qname)[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.
"""
return qname in (self.qname, "*")
[docs] def find_choice(self, qname: str) -> Optional["XmlVar"]:
"""Match and return a choice field by its qualified name."""
return None
[docs] def find_value_choice(self, value: Any) -> Optional["XmlVar"]:
"""Match and return a choice field that matches the given value."""
return None
[docs]@dataclass(frozen=True)
class XmlElement(XmlVar):
"""Dataclass field bind metadata for xml elements."""
@property
def is_element(self) -> bool:
return True
@property
def is_any_type(self) -> bool:
return len(self.types) == 1 and self.types[0] is object
[docs]@dataclass(frozen=True)
class XmlElements(XmlVar):
"""Dataclass field bind metadata for compound list of xml elements."""
@property
def is_elements(self) -> bool:
return True
[docs] def matches(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."""
if isinstance(value, list):
tp = None if not value else type(value[0])
tokens = True
is_class = False
else:
tp = type(value)
tokens = False
is_class = is_dataclass(value)
def match_type(match: Type, types: List[Type]) -> bool:
for candidate in types:
if tp == candidate or (is_class and issubclass(match, candidate)):
return True
return False
for choice in self.choices:
if choice.is_any_type or tokens != choice.tokens:
continue
if (not tp and choice.nillable) or (tp and match_type(tp, choice.types)):
return choice
return None
[docs]@dataclass(frozen=True)
class XmlWildcard(XmlVar):
"""Dataclass field bind metadata for xml wildcard elements."""
@property
def is_mixed_content(self) -> bool:
return self.mixed
@property
def is_wildcard(self) -> bool:
return True
[docs] def matches(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)
[docs] @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:
return True
if source and source[0] == "!" and source[1:] != cmp:
return True
return False
[docs]@dataclass(frozen=True)
class XmlAttribute(XmlVar):
"""Dataclass field bind metadata for xml attributes."""
@property
def is_attribute(self) -> bool:
return True
[docs]@dataclass(frozen=True)
class XmlAttributes(XmlVar):
"""Dataclass field bind metadata for xml wildcard attributes."""
@property
def is_attributes(self) -> bool:
return True
[docs]@dataclass(frozen=True)
class XmlText(XmlVar):
"""Dataclass field bind metadata for xml text content."""
@property
def is_text(self) -> bool:
return True
[docs]class FindMode(IntEnum):
"""Find switches to be used to find a specific var."""
ALL = auto()
ATTRIBUTE = auto()
ATTRIBUTES = auto()
TEXT = auto()
WILDCARD = auto()
MIXED_CONTENT = ()
NOT_WILDCARD = auto()
find_predicates = {
FindMode.ALL: lambda x: True,
FindMode.ATTRIBUTE: lambda x: x.is_attribute,
FindMode.ATTRIBUTES: lambda x: x.is_attributes,
FindMode.TEXT: lambda x: x.is_text,
FindMode.WILDCARD: lambda x: x.is_wildcard,
FindMode.MIXED_CONTENT: lambda x: x.is_mixed_content,
FindMode.NOT_WILDCARD: lambda x: not x.is_wildcard,
}