File: add_attribute_substitutions.py

package info (click to toggle)
python-xsdata 24.1-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,936 kB
  • sloc: python: 29,257; xml: 404; makefile: 27; sh: 6
file content (95 lines) | stat: -rw-r--r-- 3,417 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
from collections import defaultdict
from typing import Dict, List, Optional

from xsdata.codegen.mixins import ContainerInterface, RelativeHandlerInterface
from xsdata.codegen.models import Attr, AttrType, Class
from xsdata.codegen.utils import ClassUtils
from xsdata.models.enums import Tag
from xsdata.utils import collections


class AddAttributeSubstitutions(RelativeHandlerInterface):
    """Apply substitution attributes to the given class recursively."""

    __slots__ = "substitutions"

    def __init__(self, container: ContainerInterface):
        super().__init__(container)
        self.substitutions: Optional[Dict[str, List[Attr]]] = None

    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)

    def process_attribute(self, target: Class, attr: Attr):
        """
        Check if the given attribute matches any substitution class in order to
        clone its attributes to the target class.

        The cloned attributes are placed below the attribute they are
        supposed to substitute.

        Guard against multiple substitutions in case of xs:groups.
        """
        index = target.attrs.index(attr)
        assert self.substitutions is not None

        for attr_type in attr.types:
            if attr_type.substituted:
                continue

            attr_type.substituted = True
            for substitution in self.substitutions.get(attr_type.qname, []):
                self.prepare_substituted(attr)

                clone = ClassUtils.clone_attribute(substitution, attr.restrictions)
                clone.restrictions.min_occurs = 0
                clone.restrictions.max_occurs = attr.restrictions.max_occurs

                attr.substitution = clone.substitution = attr_type.name

                pos = collections.find(target.attrs, clone)
                index = pos + 1 if pos > -1 else index
                target.attrs.insert(index, clone)

                self.process_attribute(target, clone)

    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:
            for qname in obj.substitutions:
                attr = self.create_substitution(obj)
                self.substitutions[qname].append(attr)

    @classmethod
    def prepare_substituted(cls, attr: Attr):
        attr.restrictions.min_occurs = 0
        if not attr.restrictions.choice:
            choice = id(attr)
            attr.restrictions.choice = choice
            attr.restrictions.path.append(("c", choice, 1, 1))

    @classmethod
    def create_substitution(cls, source: Class) -> Attr:
        """Create an attribute with type that refers to the given source class
        and namespaced qualified name."""

        return Attr(
            name=source.name,
            types=[AttrType(qname=source.qname)],
            tag=Tag.ELEMENT,
            namespace=source.namespace,
        )