def _visit(self, node: Any) -> Optional[Any]: if isinstance(node, (YAMLRoot, dict)) or is_YAMLROOT(node): if isinstance(node, YAMLRoot) or is_YAMLROOT(node): node = self._add_type(node) for k, v in list(node.items()): if v: new_v = self._visit(v) if new_v is not None: node[k] = new_v elif isinstance(node, list): for i in range(0, len(node)): new_v = self._visit(node[i]) if new_v is not None: node[i] = new_v elif isinstance(node, set): for v in list(node): new_v = self._visit(v) if new_v is not None: node.remove(v) node.add(new_v) elif isinstance(node, ClassDefinitionName): return ClassDefinitionName(camelcase(node)) elif isinstance(node, SlotDefinitionName): return SlotDefinitionName(underscore(node)) elif isinstance(node, TypeDefinitionName): return TypeDefinitionName(underscore(node)) elif isinstance(node, SubsetDefinitionName): return SubsetDefinitionName(underscore(node)) elif isinstance(node, ElementName): return ClassDefinitionName(camelcase(node)) if node in self.schema.classes else \ SlotDefinitionName(underscore(node)) if node in self.schema.slots else \ SubsetDefinitionName(camelcase(node)) if node in self.schema.subsets else \ TypeDefinitionName(underscore(node)) if node in self.schema.types else None return None
def visit_schema(self, classes: Set[ClassDefinitionName] = None, directory: Optional[str] = None, load_image: bool = True, **_) -> None: if directory: os.makedirs(directory, exist_ok=True) if classes is not None: for cls in classes: if cls not in self.schema.classes: raise ValueError(f"Unknown class name: {cls}") self.box_generated = set() self.associations_generated = set() self.focus_classes = classes if classes: self.gen_classes = self.neighborhood( list(classes)).classrefs.union(classes) else: self.gen_classes = self.synopsis.roots.classrefs self.referenced = self.gen_classes self.generated = set() yumlclassdef: List[str] = [] while self.referenced.difference(self.generated): cn = sorted(list(self.referenced.difference(self.generated)), reverse=True)[0] self.generated.add(cn) assocs = self.class_associations(ClassDefinitionName(cn), cn in self.referenced) if assocs: yumlclassdef.append(assocs) else: yumlclassdef.append(self.class_box(ClassDefinitionName(cn))) yuml_url = str(YUML) + ','.join(yumlclassdef) + \ (('.' + self.format) if self.format not in ('yuml', 'svg') else '') file_suffix = '.svg' if self.format == 'yuml' else '.' + self.format if directory: self.output_file_name = os.path.join( directory, camelcase(sorted(classes)[0] if classes else self.schema.name) + file_suffix) if load_image: resp = requests.get(yuml_url, stream=True) if resp.ok: with open(self.output_file_name, 'wb') as f: for chunk in resp.iter_content(chunk_size=2048): f.write(chunk) else: self.logger.error(f"{resp.reason} accessing {yuml_url}") else: print(str(YUML) + ','.join(yumlclassdef), end='')
def add_ref( self, fromtype: RefType, fromname: ElementName, totype: RefType, toname: ElementName, ) -> None: """ Add an inverse reference, indicating that to type/name is referenced by from type/name :param fromtype: Referencer type :param fromname: Referencer name :param totype: Referencee type :param toname: Referencee name :return: """ if totype is ClassType: self.classrefs.setdefault(ClassDefinitionName(toname), References()).addref(fromtype, fromname) elif totype is SlotType: self.slotrefs.setdefault(SlotDefinitionName(toname), References()).addref(fromtype, fromname) elif totype is TypeType: self.typerefs.setdefault(TypeDefinitionName(toname), References()).addref(fromtype, fromname) elif totype is SubsetType: self.subsetrefs.setdefault(SubsetDefinitionName(toname), References()).addref( fromtype, fromname) elif totype is EnumType: self.enumrefs.setdefault(SlotDefinitionName(toname), References()).addref(fromtype, fromname) else: raise TypeError("Unknown typ: {typ}")
def class_identifier_path(self, cls_or_clsname: Union[str, ClassDefinition], force_non_key: bool) -> List[str]: """ Return the path closure to a class identifier if the class has a key and force_non_key is false otherwise return a dictionary closure. :param cls_or_clsname: class definition :param force_non_key: True means inlined even if the class has a key :return: path """ cls = cls_or_clsname if isinstance(cls_or_clsname, ClassDefinition) \ else self.schema.classes[ClassDefinitionName(cls_or_clsname)] # Determine whether the class has a key identifier_slot = None if not force_non_key: identifier_slot = self.class_identifier(cls) # No key or inlined, its closure is a dictionary if identifier_slot is None: return ['dict', self.class_or_type_name(cls.name)] # We're dealing with a reference pathname = camelcase(cls.name + ' ' + self.aliased_slot_name(identifier_slot)) if cls.is_a: parent_identifier_slot = self.class_identifier(cls.is_a) if parent_identifier_slot: return self.class_identifier_path(cls.is_a, False) + [pathname] return self.slot_range_path(identifier_slot) + [pathname]
def _range_uri(self, slot: SlotDefinition) -> URIRef: if slot.range in self.schema.types: return self._type_uri(TypeDefinitionName(slot.range)) elif slot.range in self.schema.enums: # TODO: enums fill this in return self._enum_uri(EnumDefinitionName(slot.range)) else: return self._class_uri(ClassDefinitionName(slot.range))
def adjust_slot(self, slot: SlotDefinition) -> None: if slot.range in self.schema.classes: slot.range = ClassDefinitionName(camelcase(slot.range)) elif slot.range in self.schema.slots: slot.range = SlotDefinitionName(underscore(slot.range)) elif slot.range in self.schema.types: slot.range = TypeDefinitionName(underscore(slot.range)) slot.slot_uri = self.namespaces.uri_for(slot.slot_uri)
def class_or_type_for(self, name: str) -> Optional[Element]: """ Return the corresponding class or type for name """ if name in self.schema.classes: return self.schema.classes[ClassDefinitionName(name)] elif name in self.schema.types: return self.schema.types[TypeDefinitionName(name)] elif name in self.schema.enums: return self.schema.enums[EnumDefinitionName(name)] return None
def addref(self, fromtype: RefType, fromname: ElementName) -> None: if fromtype is ClassType: self.classrefs.add(ClassDefinitionName(fromname)) elif fromtype is TypeType: self.typerefs.add(TypeDefinitionName(fromname)) elif fromtype is SlotType: self.slotrefs.add(SlotDefinitionName(fromname)) elif fromtype is SubsetType: self.subsetrefs.add(SubsetDefinitionName(fromname)) elif fromtype is EnumType: self.slotrefs.add(EnumDefinitionName(fromname)) else: raise TypeError(f"Unknown typ: {fromtype}")
def class_associations(self, cn: ClassDefinitionName, must_render: bool = False) -> str: """ Emit all associations for a focus class. If none are specified, all classes are generated @param cn: Name of class to be emitted @param must_render: True means render even if this is a target (class is specifically requested) @return: YUML representation of the association """ # NOTE: YUML diagrams draw in the opposite order in which they are created, so we work from bottom to top and # from right to left assocs: List[str] = [] if cn not in self.associations_generated and ( not self.focus_classes or cn in self.focus_classes): cls = self.schema.classes[cn] # Slots that reference other classes for slot in self.filtered_cls_slots( cn, False, lambda s: s.range in self.schema.classes)[::-1]: # Swap the two boxes because, in the case of self reference, the last definition wins if not slot.range in self.associations_generated and cn in slot.domain_of: rhs = self.class_box(cn) lhs = self.class_box(cast(ClassDefinitionName, slot.range)) assocs.append( lhs + '<' + self.aliased_slot_name(slot) + self.prop_modifier(cls, slot) + self.cardinality(slot, False) + (yuml_inline_rev if slot.inlined else yuml_ref) + rhs) # Slots in other classes that reference this for slotname in sorted(self.synopsis.rangerefs.get(cn, [])): slot = self.schema.slots[slotname] # Don't do self references twice # Also, slot must be owned by the class if cls.name not in slot.domain_of and cls.name not in self.associations_generated: for dom in [ self.schema.classes[dof] for dof in slot.domain_of ]: assocs.append( self.class_box(dom.name) + (yuml_inline if slot.inlined else yuml_ref) + self.aliased_slot_name(slot) + self.prop_modifier(dom, slot) + self.cardinality(slot, False) + '>' + self.class_box(cn)) # Mixins used in the class for mixin in cls.mixins: assocs.append( self.class_box(cn) + yuml_uses + self.class_box(mixin)) # Classes that use the class as a mixin if cls.name in self.synopsis.mixinrefs: for mixin in sorted( self.synopsis.mixinrefs[cls.name].classrefs, reverse=True): assocs.append( self.class_box(ClassDefinitionName(mixin)) + yuml_uses + self.class_box(cn)) # Classes that inject information if cn in self.synopsis.applytos.classrefs: for injector in sorted(self.synopsis.applytorefs[cn].classrefs, reverse=True): assocs.append( self.class_box(cn) + yuml_injected + self.class_box(ClassDefinitionName(injector))) self.associations_generated.add(cn) # Children if cn in self.synopsis.isarefs: for is_a_cls in sorted(self.synopsis.isarefs[cn].classrefs, reverse=True): assocs.append( self.class_box(cn) + yuml_is_a + self.class_box(ClassDefinitionName(is_a_cls))) # Parent if cls.is_a and cls.is_a not in self.associations_generated: assocs.append( self.class_box(cls.is_a) + yuml_is_a + self.class_box(cn)) return ','.join(assocs)