Source code for xsdata.codegen.handlers.attribute_substitution

from collections import defaultdict
from dataclasses import dataclass
from dataclasses import field
from typing import Dict
from typing import List
from typing import Optional

from lxml.etree import QName

from xsdata.codegen.mixins import ContainerInterface
from xsdata.codegen.mixins import HandlerInterface
from xsdata.codegen.models import Attr
from xsdata.codegen.models import AttrType
from xsdata.codegen.models import Class
from xsdata.utils import collections

Substitutions = Optional[Dict[QName, List[Attr]]]


[docs]@dataclass class AttributeSubstitutionHandler(HandlerInterface): """Apply substitution attributes to the given class recursively.""" container: ContainerInterface substitutions: Substitutions = field(init=False, default=None)
[docs] def process(self, target: Class): """ Search and process attributes not derived from xs:enumeration or xs:any. Build the substitutions map if it's not initialized yet. """ if self.substitutions is None: self.create_substitutions() for attr in list(target.attrs): if not (attr.is_enumeration or attr.is_wildcard): self.process_attribute(target, attr)
[docs] def process_attribute(self, target: Class, attr: Attr): """ Check if the given attribute matches any substitution class in order to clone it's attributes to the target class. The cloned attributes are placed below the attribute the are supposed to substitute. """ index = target.attrs.index(attr) qname = target.source_qname(attr.name) assert self.substitutions is not None for substitution in self.substitutions.get(qname, []): pos = collections.find(target.attrs, substitution) index = pos + 1 if pos > -1 else index clone = substitution.clone() clone.restrictions.merge(attr.restrictions) target.attrs.insert(index, clone) self.process_attribute(target, clone)
[docs] def create_substitutions(self): """Create reference attributes for all the classes substitutions and group them by their fully qualified name.""" self.substitutions = defaultdict(list) for obj in self.container.iterate(): for substitution in obj.substitutions: qname = obj.source_qname(substitution) attr = self.create_substitution(obj, qname) self.substitutions[qname].append(attr)
[docs] @classmethod def create_substitution(cls, source: Class, qname: QName) -> Attr: """Create an attribute with type that refers to the given source class and namespaced qualified name.""" prefix = None if qname.namespace != source.source_namespace: prefix = source.source_prefix reference = f"{prefix}:{source.name}" if prefix else source.name return Attr( name=source.name, local_name=source.name, index=0, default=None, types=[AttrType(name=reference)], tag=source.type.__name__, namespace=source.namespace, )