Skip to content

# add_attribute_substitutions

## `xsdata.codegen.handlers.add_attribute_substitutions`

### `AddAttributeSubstitutions`

Bases: `RelativeHandlerInterface`

Apply substitution attributes to the given class recursively.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`container` | `ContainerInterface` | The class container instance | required

Attributes:

Name | Type | Description
--- | --- | ---
`substitutions` | `dict[str, list[Attr]] | None` | Mapping of type names to attr values

Source code in `xsdata/codegen/handlers/add_attribute_substitutions.py`

     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
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122

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

        Args:
            container: The class container instance

        Attributes:
            substitutions: Mapping of type names to attr values
        """

        __slots__ = "substitutions"

        def __init__(self, container: ContainerInterface):
            """Initialize the class."""
            super().__init__(container)
            self.substitutions: dict[str, list[Attr]] | None = None

        def process(self, target: Class) -> None:
            """Process the given class attrs for substitution groups.

            This method will ignore attrs in the class derived from
            a xs:enumeration, xs:anyType and xs:any. If this is the
            first time we call the method, build the substitution
            map.

            Args:
                target: The target class instance
            """
            if self.substitutions is None:
                self.create_substitutions()

            for attr in target.attrs.copy():
                if not (attr.is_enumeration or attr.is_wildcard):
                    self.process_attribute(target, attr)

        def process_attribute(self, target: Class, attr: Attr) -> None:
            """Add substitution attrs that refer to the attr type.

            If the given attr is referenced in substitution groups
            clone all substitution attrs and place them bellow
            the original attr. Convert all the attrs of the group
            to repeatable choice elements.

            Guard against multiple substitutions in case of xs:groups.

            Args:
                target: The target class instance
                attr: The source attr instance to check and process
            """
            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) -> None:
            """Build the substitutions mapping of type names to attr values.

            The values are simple reference attrs that we can easily
            clone later on demand.
            """
            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):
            """Prepare the original attr for substitutions.

            Effectively place the attr inside a xs:choice container
            with min occurs zero.
            """
            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 a reference attr to the source class qname.

            Args:
                source: The source class to reference

            Returns:
                The reference to the source class attr.
            """
            return Attr(
                name=source.name,
                types=[AttrType(qname=source.qname)],
                tag=Tag.ELEMENT,
                namespace=source.namespace,
            )

#### `__init__(container)`

Initialize the class.

Source code in `xsdata/codegen/handlers/add_attribute_substitutions.py`

    22
    23
    24
    25

    def __init__(self, container: ContainerInterface):
        """Initialize the class."""
        super().__init__(container)
        self.substitutions: dict[str, list[Attr]] | None = None

#### `process(target)`

Process the given class attrs for substitution groups.

This method will ignore attrs in the class derived from a xs:enumeration, xs:anyType and xs:any. If this is the first time we call the method, build the substitution map.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance | required

Source code in `xsdata/codegen/handlers/add_attribute_substitutions.py`

    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43

    def process(self, target: Class) -> None:
        """Process the given class attrs for substitution groups.

        This method will ignore attrs in the class derived from
        a xs:enumeration, xs:anyType and xs:any. If this is the
        first time we call the method, build the substitution
        map.

        Args:
            target: The target class instance
        """
        if self.substitutions is None:
            self.create_substitutions()

        for attr in target.attrs.copy():
            if not (attr.is_enumeration or attr.is_wildcard):
                self.process_attribute(target, attr)

#### `process_attribute(target, attr)`

Add substitution attrs that refer to the attr type.

If the given attr is referenced in substitution groups clone all substitution attrs and place them bellow the original attr. Convert all the attrs of the group to repeatable choice elements.

Guard against multiple substitutions in case of xs:groups.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`target` | `Class` | The target class instance | required
`attr` | `Attr` | The source attr instance to check and process | required

Source code in `xsdata/codegen/handlers/add_attribute_substitutions.py`

    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

    def process_attribute(self, target: Class, attr: Attr) -> None:
        """Add substitution attrs that refer to the attr type.

        If the given attr is referenced in substitution groups
        clone all substitution attrs and place them bellow
        the original attr. Convert all the attrs of the group
        to repeatable choice elements.

        Guard against multiple substitutions in case of xs:groups.

        Args:
            target: The target class instance
            attr: The source attr instance to check and process
        """
        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)

#### `create_substitutions()`

Build the substitutions mapping of type names to attr values.

The values are simple reference attrs that we can easily clone later on demand.

Source code in `xsdata/codegen/handlers/add_attribute_substitutions.py`

    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92

    def create_substitutions(self) -> None:
        """Build the substitutions mapping of type names to attr values.

        The values are simple reference attrs that we can easily
        clone later on demand.
        """
        self.substitutions = defaultdict(list)
        for obj in self.container:
            for qname in obj.substitutions:
                attr = self.create_substitution(obj)
                self.substitutions[qname].append(attr)

#### `prepare_substituted(attr)` `classmethod`

Prepare the original attr for substitutions.

Effectively place the attr inside a xs:choice container with min occurs zero.

Source code in `xsdata/codegen/handlers/add_attribute_substitutions.py`

     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105

    @classmethod
    def prepare_substituted(cls, attr: Attr):
        """Prepare the original attr for substitutions.

        Effectively place the attr inside a xs:choice container
        with min occurs zero.
        """
        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))

#### `create_substitution(source)` `classmethod`

Create a reference attr to the source class qname.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`source` | `Class` | The source class to reference | required

Returns:

Type | Description
--- | ---
`Attr` | The reference to the source class attr.

Source code in `xsdata/codegen/handlers/add_attribute_substitutions.py`

    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122

    @classmethod
    def create_substitution(cls, source: Class) -> Attr:
        """Create a reference attr to the source class qname.

        Args:
            source: The source class to reference

        Returns:
            The reference to the source class attr.
        """
        return Attr(
            name=source.name,
            types=[AttrType(qname=source.qname)],
            tag=Tag.ELEMENT,
            namespace=source.namespace,
        )