Exemple #1
0
    def process_attribute_default_enum(self, target: Class, attr: Attr):
        """
        Convert string literal default value for enum fields.

        Loop through all attributes types and search for enum sources.
        If an enum source exist map the default string literal value to
        a qualified name. If the source class in inner promote it to
        root classes.
        """

        source_found = False
        for attr_type in attr.types:
            source = self.find_enum(target, attr_type)
            if not source:
                continue

            source_found = True
            source_attr = next(
                (x for x in source.attrs if x.default == attr.default), None)
            if source_attr:
                if attr_type.forward:
                    self.promote_inner_class(target, source)

                attr.default = f"@enum@{source.name}::{source_attr.name}"
                return

        if source_found:
            logger.warning(
                "No enumeration member matched %s.%s default value `%s`",
                target.name,
                attr.local_name,
                attr.default,
            )
            attr.default = None
Exemple #2
0
    def copy_attribute_properties(cls, source: Class, target: Class,
                                  attr: Attr, attr_type: AttrType):
        """
        Replace the given attribute type with the types of the single field
        source class.

        Ignore enumerations and gracefully handle dump types with no attributes.

        :raises: AnalyzerValueError if the source class has more than one attributes
        """
        source_attr = source.attrs[0]
        index = attr.types.index(attr_type)
        attr.types.pop(index)

        for source_attr_type in source_attr.types:
            clone_type = source_attr_type.clone()
            attr.types.insert(index, clone_type)
            index += 1

            ClassUtils.copy_inner_class(source, target, attr, clone_type)

        restrictions = source_attr.restrictions.clone()
        restrictions.merge(attr.restrictions)
        attr.restrictions = restrictions
        attr.help = attr.help or source_attr.help

        if source.nillable:
            restrictions.nillable = True
Exemple #3
0
    def process_attribute_restrictions(cls, attr: Attr):
        """Sanitize attribute required flag by comparing the min/max
        occurrences restrictions."""
        restrictions = attr.restrictions
        min_occurs = restrictions.min_occurs or 0
        max_occurs = restrictions.max_occurs or 0

        if attr.is_attribute:
            restrictions.min_occurs = None
            restrictions.max_occurs = None
        elif attr.is_tokens:
            restrictions.required = None
            if max_occurs <= 1:
                restrictions.min_occurs = None
                restrictions.max_occurs = None
        elif attr.xml_type is None or min_occurs == max_occurs == 1:
            restrictions.required = True
            restrictions.min_occurs = None
            restrictions.max_occurs = None
        elif min_occurs == 0 and max_occurs < 2:
            restrictions.required = None
            restrictions.min_occurs = None
            restrictions.max_occurs = None
            attr.default = None
            attr.fixed = False
        else:  # max_occurs > 1
            restrictions.min_occurs = min_occurs
            restrictions.required = None
            attr.fixed = False

        if attr.default or attr.fixed or attr.restrictions.nillable:
            restrictions.required = None
Exemple #4
0
    def create_default_attribute(cls, item: Class, extension: Extension):
        """Add a default value field to the given class based on the extension
        type."""
        if extension.type.native_code == DataType.ANY_TYPE.code:
            attr = Attr(
                name="any_element",
                local_name="any_element",
                index=0,
                default=list if extension.restrictions.is_list else None,
                types=[extension.type.clone()],
                tag=Tag.ANY,
                namespace=NamespaceType.ANY.value,
                restrictions=extension.restrictions.clone(),
            )
        else:
            attr = Attr(
                name="value",
                local_name="value",
                index=0,
                default=None,
                types=[extension.type.clone()],
                tag=Tag.EXTENSION,
                restrictions=extension.restrictions.clone(),
            )

        item.attrs.insert(0, attr)
        item.extensions.remove(extension)
    def process(cls, target: Class):
        """Add or update an existing an xs:anyType derived attribute if the
        target class supports mixed content."""

        if not target.mixed:
            return

        wildcard = first(attr for attr in target.attrs if attr.tag == Tag.ANY)

        if wildcard:
            wildcard.mixed = True
            if not wildcard.is_list:
                wildcard.restrictions.min_occurs = 0
                wildcard.restrictions.max_occurs = sys.maxsize
        else:
            attr = Attr(
                name="content",
                types=[AttrType(qname=str(DataType.ANY_TYPE), native=True)],
                tag=Tag.ANY,
                mixed=True,
                namespace=NamespaceType.ANY_NS,
                restrictions=Restrictions(min_occurs=0,
                                          max_occurs=sys.maxsize),
            )
            target.attrs.insert(0, attr)
Exemple #6
0
    def group_fields(self, target: Class, attrs: List[Attr]):
        """Group attributes into a new compound field."""

        pos = target.attrs.index(attrs[0])
        names = []
        choices = []
        min_occurs = []
        max_occurs = []
        for attr in attrs:
            target.attrs.remove(attr)
            names.append(attr.local_name)
            min_occurs.append(attr.restrictions.min_occurs)
            max_occurs.append(attr.restrictions.max_occurs)
            choices.append(self.build_attr_choice(attr))

        name = "choice" if len(names) > 3 else "_Or_".join(names)
        target.attrs.insert(
            pos,
            Attr(
                name=name,
                index=0,
                types=[AttrType(qname=str(DataType.ANY_TYPE), native=True)],
                tag=Tag.CHOICE,
                restrictions=Restrictions(
                    min_occurs=min((x for x in min_occurs if x is not None),
                                   default=0),
                    max_occurs=max((x for x in max_occurs if x is not None),
                                   default=0),
                ),
                choices=choices,
            ),
        )
Exemple #7
0
    def process_simple_dependency(cls, source: Class, target: Class,
                                  attr: Attr, attr_type: AttrType):
        """
        Replace the given attribute type with the types of the single field
        source class.

        Ignore enumerations and gracefully handle dump types with no attributes.

        :raises: AnalyzerValueError if the source class has more than one attributes
        """
        if source.is_enumeration:
            return

        total = len(source.attrs)
        if total == 0:
            cls.reset_attribute_type(attr_type)
        elif total == 1:
            source_attr = source.attrs[0]
            index = attr.types.index(attr_type)
            attr.types.pop(index)

            for source_attr_type in source_attr.types:
                clone_type = source_attr_type.clone()
                attr.types.insert(index, clone_type)
                index += 1

            restrictions = source_attr.restrictions.clone()
            restrictions.merge(attr.restrictions)
            attr.restrictions = restrictions
            ClassUtils.copy_inner_classes(source, target)
        else:
            raise AnalyzerValueError(
                f"{source.type.__name__} with more than one attribute: `{source.name}`"
            )
Exemple #8
0
    def build_class_attribute(
        cls, target: Class, obj: ElementBase, parent_restrictions: Restrictions
    ):
        """Generate and append an attribute field to the target class."""

        target.ns_map.update(obj.ns_map)
        types = cls.build_class_attribute_types(target, obj)
        restrictions = Restrictions.from_element(obj)

        if obj.class_name in (Tag.ELEMENT, Tag.ANY):
            restrictions.merge(parent_restrictions)

        if restrictions.prohibited:
            return

        name = obj.real_name
        target.attrs.append(
            Attr(
                index=obj.index,
                name=name,
                local_name=name,
                default=obj.default_value,
                fixed=obj.is_fixed,
                types=types,
                tag=obj.class_name,
                help=obj.display_help,
                namespace=cls.element_namespace(obj, target.qname.namespace),
                restrictions=restrictions,
            )
        )
Exemple #9
0
 def create(
     cls,
     name: Optional[str] = None,
     index: Optional[int] = None,
     types: Optional[List[AttrType]] = None,
     choices: Optional[List[Attr]] = None,
     tag: Optional[str] = None,
     namespace: Optional[str] = None,
     default: Any = None,
     fixed: bool = False,
     mixed: bool = False,
     restrictions: Optional[Restrictions] = None,
     **kwargs: Any,
 ) -> Attr:
     name = name or f"attr_{cls.next_letter()}"
     return Attr(
         name=name,
         index=cls.counter if index is None else index,
         types=types or [AttrTypeFactory.native(DataType.STRING)],
         choices=choices or [],
         tag=tag or random.choice(cls.tags),
         namespace=namespace or None,
         default=default or None,
         fixed=fixed,
         mixed=mixed,
         restrictions=restrictions or Restrictions(),
         **kwargs,
     )
Exemple #10
0
    def process_attribute_default(self, target: Class, attr: Attr):
        """
        Sanitize attribute default value.

        Cases:
            1. List fields can not have a fixed value.
            2. Optional fields or xsi:type can not have a default or fixed value.
            3. Convert string literal default value for enum fields.
        """
        if attr.is_list:
            attr.fixed = False

        if attr.is_optional or attr.is_xsi_type:
            attr.fixed = False
            attr.default = None

        if attr.default and not attr.is_enumeration:
            self.process_attribute_default_enum(target, attr)
    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=source.type.__name__,
            namespace=source.namespace,
        )
Exemple #12
0
    def get_or_create_attribute(cls, target: Class, name: str,
                                tag: str) -> Attr:
        """Find or create for the given parameters an attribute in the target
        class."""
        for attr in target.attrs:
            if attr.name == attr.local_name == name and attr.tag == tag:
                return attr

        attr = Attr(name=name, tag=tag)
        target.attrs.insert(0, attr)
        return attr
Exemple #13
0
    def get_or_create_attribute(cls, target: Class, name: str, tag: str) -> Attr:
        """Find or create for the given parameters an attribute in the target
        class."""
        for attr in target.attrs:
            if attr.name == name:
                return attr

        attr = Attr(name=name, tag=tag)
        attr.restrictions.min_occurs = 1
        attr.restrictions.max_occurs = 1
        target.attrs.insert(0, attr)
        return attr
Exemple #14
0
    def process_attribute_name(cls, attr: Attr):
        """
        Sanitize attribute name in preparation for duplicate attribute names
        handler.

        Steps:
            1. Remove non alpha numerical values
            2. Handle Enum negative numerical values
            3. Remove namespaces prefixes
            4. Ensure name not empty
            5. Ensure name starts with a letter
        """
        if attr.is_enumeration:
            attr.name = attr.default
            if re.match(r"^-\d*\.?\d+$", attr.name):
                attr.name = f"value_minus_{attr.name}"
            else:
                attr.name = re.sub("[^0-9a-zA-Z]", " ", attr.name).strip()
        else:
            attr.name = re.sub("[^0-9a-zA-Z]", " ", attr.name).strip()

        if not attr.name:
            attr.name = "value"
        elif not attr.name[0].isalpha():
            attr.name = f"value_{attr.name}"
Exemple #15
0
    def rename_attr_dependencies(self, attr: Attr, reference: int, replace: str):
        """Search and replace the old qualified attribute type name with the
        new one in the attr types, choices and default value."""
        for attr_type in attr.types:
            if attr_type.reference == reference:
                attr_type.qname = replace

                if isinstance(attr.default, str) and attr.default.startswith("@enum@"):
                    member = text.suffix(attr.default, "::")
                    attr.default = f"@enum@{replace}::{member}"

        for choice in attr.choices:
            self.rename_attr_dependencies(choice, reference, replace)
Exemple #16
0
    def rename_attr_dependencies(self, attr: Attr, search: str, replace: str):
        """Search and replace the old qualified attribute type name with the
        new one in the attr types, choices and default value."""
        for attr_type in attr.types:
            if attr_type.qname == search:
                attr_type.qname = replace

                if isinstance(attr.default,
                              str) and attr.default.startswith("@enum@"):
                    attr.default = attr.default.replace(search, replace)

        for choice in attr.choices:
            self.rename_attr_dependencies(choice, search, replace)
Exemple #17
0
    def process(cls, target: Class):
        """Add an xs:anyType attribute to the given class if it supports mixed
        content and doesn't have a wildcard attribute yet."""

        if not target.mixed or target.has_wild_attr:
            return

        attr = Attr(
            name="content",
            local_name="content",
            index=0,
            types=[AttrType(name=DataType.ANY_TYPE.code, native=True)],
            tag=Tag.ANY,
            namespace=NamespaceType.ANY.value,
        )
        target.attrs.insert(0, attr)
Exemple #18
0
    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,
        )
Exemple #19
0
 def build_attr(
     cls,
     name: str,
     qname: str,
     native: bool = False,
     forward: bool = False,
     namespace: Optional[str] = None,
     default: Optional[str] = None,
 ) -> Attr:
     """Builder method for attributes."""
     return Attr(
         tag=Tag.ELEMENT,
         name=name,
         namespace=namespace,
         default=default,
         types=[AttrType(qname=qname, forward=forward, native=native)],
     )
Exemple #20
0
    def build_attribute(
        cls,
        target: Class,
        qname: str,
        attr_type: AttrType,
        target_namespace: Optional[str] = None,
        tag: str = Tag.ELEMENT,
        sequential: bool = False,
    ):

        namespace, name = split_qname(qname)
        namespace = cls.select_namespace(namespace, target_namespace, tag)
        index = len(target.attrs)

        attr = Attr(index=index, name=name, tag=tag, namespace=namespace)
        attr.types.append(attr_type)
        attr.restrictions.sequential = sequential or None
        cls.add_attribute(target, attr)
Exemple #21
0
    def clone_attribute(cls,
                        attr: Attr,
                        restrictions: Restrictions,
                        prefix: Optional[str] = None) -> Attr:
        """
        Clone the given attribute and merge its restrictions with the given
        instance.

        Prepend the given namespace prefix to the attribute name if
        available.
        """
        clone = attr.clone()
        clone.restrictions.merge(restrictions)
        if prefix:
            for attr_type in clone.types:
                if not attr_type.native and attr_type.name.find(":") == -1:
                    attr_type.name = f"{prefix}:{attr_type.name}"

        return clone
Exemple #22
0
 def build_attr(
     cls,
     name: str,
     qname: str,
     native: bool = False,
     forward: bool = False,
     namespace: Optional[str] = None,
     default: Optional[str] = None,
 ) -> Attr:
     """Builder method for attributes."""
     occurs = 1 if default is not None else None
     return Attr(
         tag=Tag.ELEMENT,
         name=name,
         namespace=namespace,
         default=default,
         types=[AttrType(qname=qname, forward=forward, native=native)],
         restrictions=Restrictions(min_occurs=occurs, max_occurs=occurs),
     )
Exemple #23
0
    def build_attr_choice(cls, attr: Attr) -> Attr:
        """
        Converts the given attr to a choice.

        The most important part is the reset of certain restrictions
        that don't make sense as choice metadata like occurrences.
        """
        restrictions = attr.restrictions.clone()
        restrictions.min_occurs = None
        restrictions.max_occurs = None
        restrictions.sequential = None

        return Attr(
            name=attr.local_name,
            namespace=attr.namespace,
            default=attr.default,
            types=attr.types,
            tag=attr.tag,
            help=attr.help,
            restrictions=restrictions,
        )
Exemple #24
0
    def group_fields(self, target: Class, attrs: List[Attr]):
        """Group attributes into a new compound field."""

        pos = target.attrs.index(attrs[0])
        choice = attrs[0].restrictions.choice
        sum_occurs = choice and choice.startswith("effective_")
        names = []
        choices = []
        min_occurs = []
        max_occurs = []
        for attr in attrs:
            target.attrs.remove(attr)
            names.append(attr.local_name)
            min_occurs.append(attr.restrictions.min_occurs or 0)
            max_occurs.append(attr.restrictions.max_occurs or 0)
            choices.append(self.build_attr_choice(attr))

        if len(names) > 3 or len(names) != len(set(names)):
            name = "choice"
        else:
            name = "_Or_".join(names)

        target.attrs.insert(
            pos,
            Attr(
                name=name,
                index=0,
                types=[AttrType(qname=str(DataType.ANY_TYPE), native=True)],
                tag=Tag.CHOICE,
                restrictions=Restrictions(
                    min_occurs=sum(min_occurs)
                    if sum_occurs else min(min_occurs),
                    max_occurs=sum(max_occurs)
                    if sum_occurs else max(max_occurs),
                ),
                choices=choices,
            ),
        )
Exemple #25
0
 def clone_attribute(cls, attr: Attr, restrictions: Restrictions) -> Attr:
     """Clone the given attribute and merge its restrictions with the given
     instance."""
     clone = attr.clone()
     clone.restrictions.merge(restrictions)
     return clone