Source code for xsdata.parser
import pathlib
import sys
from dataclasses import dataclass
from dataclasses import field
from typing import Any
from typing import Callable
from typing import Optional
from typing import Type
from typing import TypeVar
from lxml import etree
from xsdata.formats.dataclass.parsers import QueueItem
from xsdata.formats.dataclass.parsers import XmlParser
from xsdata.models import elements as xsd
from xsdata.models.enums import FormType
from xsdata.utils import text
T = TypeVar("T")
[docs]@dataclass
class SchemaParser(XmlParser):
name_generator: Callable = field(default=text.camel_case)
"""
A simple parser to convert an xsd schema to an easy to handle data
structure based on dataclasses.
The parser is a dummy as possible but it will try to normalize
certain things like apply parent properties to children.
"""
element_form: Optional[FormType] = field(init=False, default=None)
attribute_form: Optional[FormType] = field(init=False, default=None)
target_namespace: Optional[str] = field(default=None)
[docs] def from_xsd_string(self, source: str) -> xsd.Schema:
return super().from_string(source, xsd.Schema)
[docs] def from_xsd_path(self, path: pathlib.Path) -> xsd.Schema:
schema = super().from_path(path, xsd.Schema)
schema.location = path
return schema
[docs] def end_node(self, element: etree.Element) -> Optional[T]:
"""Override parent method to skip empty elements and to set the object
index."""
if not element.attrib and element.text is None:
self.queue.pop()
return None
item = self.queue[-1]
obj = super(SchemaParser, self).end_node(element)
# Make sure queue item is not part of mixed content
if obj is None or item is None:
return None
obj.index = item.index
return obj
[docs] def start_schema(self, element: etree.Element, item: QueueItem):
"""Collect the schema's default form for attributes and elements for
later usage."""
self.element_form = element.attrib.get("elementFormDefault", None)
self.attribute_form = element.attrib.get("attributeFormDefault", None)
[docs] def end_schema(self, obj: T, element: etree.Element):
"""Collect the schema's default form for attributes and elements for
later usage."""
if isinstance(obj, xsd.Schema):
if self.element_form:
obj.element_form_default = FormType(self.element_form)
if self.attribute_form:
obj.attribute_form_default = FormType(self.attribute_form)
obj.nsmap = element.nsmap
if obj.target_namespace is None and self.target_namespace is not None:
obj.target_namespace = self.target_namespace
for child_element in obj.elements:
child_element.form = FormType.QUALIFIED
for child_attribute in obj.attributes:
child_attribute.form = FormType.QUALIFIED
[docs] def end_element(self, obj: T, element: etree.Element):
"""Assign the schema's default form for elements if the given element
form is None."""
if isinstance(obj, xsd.Element) and obj.form is None and self.element_form:
obj.form = FormType(self.element_form)
[docs] def end_attribute(self, obj: T, element: etree.Element):
"""Assign the schema's default form for attributes if the given
attribute form is None."""
if isinstance(obj, xsd.Attribute) and obj.form is None and self.attribute_form:
obj.form = FormType(self.attribute_form)
[docs] @staticmethod
def end_choice(obj: T, element: etree.Element):
"""Elements inside a choice are by definition optional, reset their min
occurs counter."""
if isinstance(obj, xsd.Choice):
for child in obj.elements:
child.min_occurs = 0
if child.max_occurs is None:
child.max_occurs = obj.max_occurs
[docs] @staticmethod
def end_all(obj: T, element: etree.Element):
"""Elements inside an all element can by definition appear at most
once, reset their max occur counter."""
if isinstance(obj, xsd.All):
for child in obj.elements:
child.max_occurs = 1
if child.min_occurs is None:
child.min_occurs = obj.min_occurs
[docs] @staticmethod
def end_sequence(obj: T, element: etree.Element):
"""Elements inside a sequence inherit min|max occur counter if it is
not set."""
if isinstance(obj, xsd.Sequence):
for child in obj.elements:
if child.min_occurs is None:
child.min_occurs = obj.min_occurs
if child.max_occurs is None:
child.max_occurs = obj.max_occurs
[docs] @classmethod
def parse_value(cls, tp: Type, value: Any) -> Any:
if tp is int and value == "unbounded":
return sys.maxsize
try:
return super().parse_value(tp, value)
except ValueError:
return str(value)