def reset_attribute_type(cls, attr_type: AttrType, use_str: bool = True): """Reset the attribute type to string or any simple type.""" attr_type.qname = str( DataType.STRING if use_str else DataType.ANY_SIMPLE_TYPE) attr_type.native = True attr_type.circular = False attr_type.forward = False
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)
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, ), )
def copy_inner_class(cls, source: Class, target: Class, attr: Attr, attr_type: AttrType): """ Check if the given attr type is a forward reference and copy its inner class from the source to the target class. Checks: 1. Update type if inner class in a circular reference 2. Copy inner class, rename it if source is a simple type. """ if not attr_type.forward: return # This will fail if no inner class is found, too strict??? inner = ClassUtils.find_inner(source, attr_type.qname) if inner is target: attr_type.circular = True else: clone = inner.clone() clone.package = target.package clone.module = target.module # Simple type, update the name if clone.name == "@value": namespace, _ = split_qname(clone.qname) clone.qname = attr_type.qname = build_qname( namespace, attr.name) target.inner.append(clone)
def process(cls, target: Class): """Process classes that contain attributes derived from xs:enumeration and any other xs element.""" if target.is_enumeration or not any(attr.is_enumeration for attr in target.attrs): return enumerations = [] for attr in list(target.attrs): if attr.is_enumeration: target.attrs.remove(attr) enumerations.append(attr) if len(target.attrs) > 1: raise AnalyzerValueError( "Mixed enumeration with more than one normal field.") enum_inner = next( (inner for inner in target.inner if inner.is_enumeration), None) if not enum_inner: enum_inner = Class( name="value", type=SimpleType, module=target.module, package=target.package, mixed=False, abstract=False, nillable=False, ) target.attrs[0].types.append(AttrType(name="value", forward=True)) target.inner.append(enum_inner) enum_inner.attrs.extend(enumerations)
def process_dependency_type(self, target: Class, attr: Attr, attr_type: AttrType): """ Process an attributes type that depends on any global type. Strategies: 1. Reset absent or dummy attribute types with a warning 2. Copy attribute properties from a simple type 3. Copy format restriction from an enumeration 4. Set circular flag for the rest """ source = self.find_dependency(attr_type, attr.tag) if not source or (not source.attrs and not source.extensions): logger.warning("Reset dummy type: %s", attr_type.name) use_str = not source or not source.is_complex self.reset_attribute_type(attr_type, use_str) elif source.is_simple_type: self.copy_attribute_properties(source, target, attr, attr_type) elif source.is_enumeration: attr.restrictions.format = collections.first( x.restrictions.format for x in source.attrs if x.restrictions.format) attr_type.reference = id(source) else: self.set_circular_flag(source, target, attr_type)
def reset_unsupported_types(cls, target: Class): for attr in target.attrs: if not cls.validate_default_value(attr, target.ns_map): attr.types.clear() attr.types.append(AttrType(qname=str(DataType.STRING), native=True)) attr.restrictions.format = None attr.types = collections.unique_sequence(attr.types, key="qname")
def build_class_attribute_types( self, target: Class, obj: ElementBase ) -> List[AttrType]: """Convert real type and anonymous inner types to an attribute type list.""" types = [ self.build_data_type(target, name) for name in (obj.real_type or "").split(" ") if name ] for inner in self.build_inner_classes(obj): target.inner.append(inner) types.append(AttrType(name=inner.name, forward=True)) if len(types) == 0: types.append(AttrType(name=obj.default_type.code, native=True)) return types
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, )
def test_create_substitution(self): item = ClassFactory.elements(1, qname=build_qname("foo", "bar")) actual = self.processor.create_substitution(item) expected = AttrFactory.create( name=item.name, default=None, types=[AttrType(qname=build_qname("foo", "bar"))], tag=item.type.__name__, ) self.assertEqual(expected, actual)
def build_data_type( cls, target: Class, name: str, index: int = 0, forward: bool = False ) -> AttrType: """Create an attribute type for the target class.""" prefix, suffix = text.split(name) native = False namespace = target.ns_map.get(prefix) if Namespace.get_enum(namespace) and DataType.get_enum(suffix): name = suffix native = True return AttrType(name=name, index=index, native=native, forward=forward,)
def build_data_type( cls, target: Class, name: str, index: int = 0, forward: bool = False ) -> AttrType: """Create an attribute type for the target class.""" prefix, suffix = text.split(name) namespace = target.ns_map.get(prefix, target.qname.namespace) native = ( Namespace.get_enum(namespace) is not None and DataType.get_enum(suffix) is not None ) return AttrType( qname=QName(namespace, suffix), index=index, native=native, forward=forward, )
def build_class_attribute(cls, target: Class, name: str, value: Any): if isinstance(value, list): for val in value: cls.build_class_attribute(target, name, val) target.attrs[-1].restrictions.max_occurs = sys.maxsize else: if isinstance(value, dict): inner = cls.build_class(value, name) attr_type = AttrType(qname=inner.qname, forward=True) target.inner.append(inner) else: attr_type = ElementMapper.build_attribute_type(name, value) ElementMapper.build_attribute(target, name, attr_type)
def build_class(cls, element: AnyElement, target_namespace: Optional[str]) -> Class: assert element.qname is not None namespace, name = split_qname(element.qname) target = Class( qname=build_qname(target_namespace, name), namespace=cls.select_namespace(namespace, target_namespace), tag=Tag.ELEMENT, module="", ) children = [c for c in element.children if isinstance(c, AnyElement)] sequential_set = cls.sequential_names(children) for key, value in element.attributes.items(): attr_type = cls.build_attribute_type(key, value) cls.build_attribute(target, key, attr_type, target_namespace, Tag.ATTRIBUTE) for child in children: assert child.qname is not None if child.tail: target.mixed = True if child.attributes or child.children: inner = cls.build_class(child, target_namespace) attr_type = AttrType(qname=inner.qname, forward=True) target.inner.append(inner) else: attr_type = cls.build_attribute_type(child.qname, child.text) cls.build_attribute( target, child.qname, attr_type, target_namespace, Tag.ELEMENT, child.qname in sequential_set, ) if element.text: attr_type = cls.build_attribute_type("value", element.text) cls.build_attribute(target, "value", attr_type, None, Tag.SIMPLE_TYPE) return target
def build_data_type(cls, target: Class, name: str, forward: bool = False) -> AttrType: """Create an attribute type for the target class.""" prefix, suffix = text.split(name) namespace = target.ns_map.get(prefix, target.target_namespace) qname = build_qname(namespace, suffix) datatype = DataType.from_qname(qname) return AttrType( qname=qname, native=datatype is not None, forward=forward, )
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)
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, )
def build_class_attribute_types(cls, target: Class, obj: ElementBase) -> List[AttrType]: """Convert real type and anonymous inner types to an attribute type list.""" types = [cls.build_data_type(target, tp) for tp in obj.attr_types] module = target.module namespace = target.target_namespace for inner in cls.build_inner_classes(obj, module, namespace): target.inner.append(inner) types.append(AttrType(qname=inner.qname, forward=True)) if len(types) == 0: types.append(cls.build_data_type(target, name=obj.default_type)) return collections.unique_sequence(types)
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)], )
def build_attribute_type(cls, qname: str, value: Any) -> AttrType: def match_type(val: Any) -> DataType: if not isinstance(val, str): return DataType.from_value(val) for tp in converter.explicit_types(): if converter.test(val, [tp]): return DataType.from_type(tp) return DataType.STRING if qname == QNames.XSI_TYPE: data_type = DataType.QNAME elif value is None or value == "": data_type = DataType.ANY_SIMPLE_TYPE else: data_type = match_type(value) return AttrType(qname=str(data_type), native=True)
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), )
def create( cls, qname: Optional[str] = None, alias: Optional[str] = None, native: bool = False, forward: bool = False, circular: bool = False, **kwargs: Any, ) -> AttrType: if not qname: qname = build_qname("xsdata", f"attr_{cls.next_letter()}") return AttrType( qname=str(qname), alias=alias, native=native, circular=circular, forward=forward, )
def test_process(self): item = ClassFactory.create() self.processor.process(item) self.assertEqual(0, len(item.attrs)) item = ClassFactory.elements(2, mixed=True) self.processor.process(item) expected = AttrFactory.create( name="content", index=0, types=[AttrType(name=DataType.ANY_TYPE.code, native=True)], tag=Tag.ANY, namespace="##any", ) self.assertEqual(expected, item.attrs[0]) self.assertEqual(3, len(item.attrs)) self.processor.process(item) self.assertEqual(3, len(item.attrs))
def test_create_substitution(self): item = ClassFactory.elements(1) actual = self.processor.create_substitution(item, QName("foo")) expected = AttrFactory.create( name=item.name, index=0, default=None, types=[AttrType(name=f"{item.source_prefix}:{item.name}")], tag=item.type.__name__, ) self.assertEqual(expected, actual) actual = self.processor.create_substitution(item, item.source_qname("foo")) self.assertEqual(item.name, actual.types[0].name) item.source_namespace = None actual = self.processor.create_substitution(item, QName("foo")) self.assertEqual(item.name, actual.types[0].name)
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, ), )
def filter_types(cls, types: List[AttrType]) -> List[AttrType]: """ Remove duplicate and invalid types. Invalid: 1. xs:error 2. xs:anyType and xs:anySimpleType when there are other types present """ types = collections.unique_sequence(types, key="qname") types = collections.remove(types, lambda x: x.datatype == DataType.ERROR) if len(types) > 1: types = collections.remove( types, lambda x: x.datatype in (DataType.ANY_TYPE, DataType.ANY_SIMPLE_TYPE), ) if not types: types.append(AttrType(qname=str(DataType.STRING), native=True)) return types
def set_circular_flag(self, source: Class, target: Class, attr_type: AttrType): """Update circular reference flag.""" attr_type.circular = self.is_circular_dependency(source, target, set())
def reset_attribute_type(cls, attr_type: AttrType): """Reset the attribute type to native string.""" attr_type.qname = QName(Namespace.XS.uri, DataType.STRING.code) attr_type.native = True attr_type.circular = False attr_type.forward = False
def reset_attribute_type(cls, attr_type: AttrType): """Reset the attribute type to native string.""" attr_type.name = DataType.STRING.code attr_type.native = True attr_type.circular = False attr_type.forward = False