def genargs() -> ArgumentParser: """ Create a command line parser :return: parser """ parser = ArgumentParser() parser.add_argument("infile", help="Input ShExC specification") parser.add_argument("-nj", "--nojson", help="Do not produce json output", action="store_true") parser.add_argument("-nr", "--nordf", help="Do not produce rdf output", action="store_true") parser.add_argument("-j", "--jsonfile", help="Output ShExJ file (Default: {infile}.json)") parser.add_argument( "-r", "--rdffile", help="Output ShExR file (Default: {infile}.{fmt suffix})") parser.add_argument("--context", help="Alternative @context") parser.add_argument( "-f", "--format", choices=list( set(x.name for x in rdflib_plugins(None, rdflib_Serializer) if '/' not in str(x.name))), help="Output format (Default: turtle)", default="turtle") return parser
class RDFGenerator(Generator): generatorname = os.path.basename(__file__) generatorversion = "0.0.2" valid_formats = [ x.name for x in rdflib_plugins(None, rdflib_Parser) if '/' not in str(x.name) ] visit_all_class_slots = False def __init__(self, schema: Union[str, TextIO, SchemaDefinition], fmt: str = 'ttl') -> None: super().__init__(schema, fmt) def _data(self, g: Graph) -> str: return g.serialize( format='turtle' if self.format == 'ttl' else self.format).decode() def end_schema(self, output: Optional[str], context: str = biolink_context) -> None: jsonld_str = JSONLDGenerator(self.schema).serialize(context=context) graph = Graph() # TODO: retrieve the base out of the JSON_LD (generalize how it is fetched from the schema when generating # context graph.parse(data=jsonld_str, format="json-ld", base="http://bioentity.io/vocab/") if output: with open(output, 'w') as outf: outf.write(self._data(graph)) else: print(self._data(graph))
def genargs() -> ArgumentParser: """ Generate an input string parser :return: parser """ possible_formats = sorted( list( set(x.name for x in rdflib_plugins(None, rdflib_Parser) if '/' not in str(x.name)))) parser = ArgumentParser(prog="generate_namespace", description="Generate a DefinedNamespace") parser.add_argument( "prefix", help='Prefix to generate namespace for (example: skos)') parser.add_argument( "uri", help="Prefix URI (example: http://www.w3.org/2004/02/skos/core#)") parser.add_argument("rdf_file", help="Location or URL of RDF file to parse") parser.add_argument("-f", "--format", help="RDF file format", choices=possible_formats, default="turtle") return parser
class RDFGenerator(Generator): generatorname = os.path.basename(__file__) generatorversion = "0.1.1" valid_formats =['ttl'] + sorted([x.name for x in rdflib_plugins(None, rdflib_Parser) if '/' not in str(x.name)]) visit_all_class_slots = False def __init__(self, schema: Union[str, TextIO, SchemaDefinition], **kwargs) -> None: super().__init__(schema, **kwargs) def _data(self, g: Graph) -> str: return g.serialize(format='turtle' if self.format == 'ttl' else self.format).decode() def end_schema(self, output: Optional[str] = None, context: str = METAMODEL_CONTEXT_URI, **_) -> None: gen = JSONLDGenerator(self, fmt=JSONLDGenerator.valid_formats[0], emit_metadata=self.emit_metadata, importmap=self.importmap) # Iterate over permissible text strings making them URI compatible for e in gen.schema.enums.values(): for pv in e.permissible_values.values(): pv.text = urlparse.quote(pv.text) jsonld_str = gen.serialize(context=context) graph = Graph() graph.parse(data=jsonld_str, format="json-ld", base=self.namespaces._base, prefix=True) if output: with open(output, 'w') as outf: outf.write(self._data(graph)) else: print(self._data(graph))
def genargs() -> ArgumentParser: """ Create a command line parser :return: parser """ parser = ArgumentParser() parser.add_argument("infile", help="Input ShExC specification") parser.add_argument("-nj", "--nojson", help="Do not produce json output", action="store_true") parser.add_argument("-nr", "--nordf", help="Do not produce rdf output", action="store_true") parser.add_argument("-j", "--jsonfile", help="Output ShExJ file (Default: {infile}.json)") parser.add_argument("-r", "--rdffile", help="Output ShExR file (Default: {infile}.{fmt suffix})") parser.add_argument("--context", help="Alternative @context") parser.add_argument("-f", "--format", choices=list(set(x.name for x in rdflib_plugins(None, rdflib_Serializer) if '/' not in str(x.name))), help="Output format (Default: turtle)", default="turtle") return parser
def genargs() -> ArgumentParser: """ Generate an input string parser :return: parser """ parser = ArgumentParser() parser.add_argument("indir", help="Location of input Turtle RDF files") parser.add_argument("outdir", help="Location of output files") parser.add_argument( "-f", "--format", choices=list( set(x.name for x in rdflib_plugins(None, rdflib_Serializer) if '/' not in str(x.name))), help="Output format (Default: json-ld)", default="json-ld") return parser
class OwlSchemaGenerator(Generator): generatorname = os.path.basename(__file__) generatorversion = "0.0.2" valid_formats = [ x.name for x in rdflib_plugins(None, rdflib_Parser) if '/' not in str(x.name) ] visits_are_sorted = True def __init__(self, schema: Union[str, TextIO, SchemaDefinition], fmt: str = 'ttl') -> None: super().__init__(schema, fmt) self.graph: Graph = None self.metamodel = SchemaLoader(LOCAL_YAML_PATH) self.metamodel.resolve() self.top_value_uri: URIRef = None def visit_schema(self, output: Optional[str] = None): base = URIRef(self.schema.id) self.graph = Graph(identifier=base) for prefix in self.metamodel.schema.emit_prefixes: self.graph.bind(prefix, self.metamodel.namespaces[prefix]) self.graph.add((base, RDF.type, OWL.Ontology)) self._add_element_properties(base, self.schema) # add the model types for name in [ 'class_definition', 'type_definition', 'slot_definition', 'subset_definition' ]: self._add_metamodel_class(name) # add value placeholder self.top_value_uri = self.metamodel.namespaces[METAMODEL_LOCAL_NAME][ 'topValue'] self.graph.add((self.top_value_uri, RDF.type, OWL.DatatypeProperty)) self.graph.add((self.top_value_uri, RDFS.label, Literal("value"))) def end_schema(self, output: Optional[str] = None) -> None: data = self.graph.serialize( format='turtle' if self.format == 'ttl' else self.format).decode() if output: with open(output, 'w') as outf: outf.write(data) else: print(data) def visit_class(self, cls: ClassDefinition) -> bool: cls_uri = self._class_uri(cls.name) self.graph.add((cls_uri, RDF.type, OWL.Class)) self.graph.add( (cls_uri, RDF.type, self.metamodel.namespaces[METAMODEL_LOCAL_NAME] [camelcase('class definition')])) self._add_element_properties(cls_uri, cls) # Parent classes # TODO: reintroduce this # if not cls.defining_slots: if True: if cls.is_a: self.graph.add( (cls_uri, RDFS.subClassOf, self._class_uri(cls.is_a))) if cls.mixin: self.graph.add( (cls_uri, RDFS.subClassOf, METAMODEL_NAMESPACE.mixin)) for mixin in sorted(cls.mixins): self.graph.add( (cls_uri, RDFS.subClassOf, self._class_uri(mixin))) if cls.name in self.synopsis.applytorefs: for appl in sorted( self.synopsis.applytorefs[cls.name].classrefs): self.graph.add( (cls_uri, RDFS.subClassOf, self._class_uri(appl))) else: raise NotImplementedError("Defining slots need to be implemented") # If defining slots, we generate an equivalentClass entry # equ_node = BNode() # self.graph.add((cls_uri, OWL.equivalentClass, equ_node)) # self.graph.add((equ_node, RDF.type, OWL.Class)) # # elts = [] # if cls.is_a: # elts.append(self._class_uri(cls.is_a)) # if cls.mixin: # self.graph.add((cls_uri, RDFS.subClassOf, META_NS.mixin)) # for mixin in cls.mixins: # self.graph.add((cls_uri, RDFS.subClassOf, self._class_uri(mixin))) # if cls.name in self.synopsis.applytorefs: # for appl in self.synopsis.applytorefs[cls.name].classrefs: # self.graph.add((cls_uri, RDFS.subClassOf, self._class_uri(appl))) # # for slotname in cls.defining_slots: # restr_node = BNode() # slot = self.schema.slots[slotname] # # self.graph.add((restr_node, RDF.type, OWL.Restriction)) # self.graph.add((restr_node, OWL.onProperty, self._prop_uri(slotname))) # self._add_cardinality(restr_node, slot) # # TODO: fix this # # self.graph.add((restr_node, OWL.someValuesFrom, self._build_range(slot))) # elts.append(restr_node) # # coll_bnode = BNode() # Collection(self.graph, coll_bnode, elts) # self.graph.add((equ_node, OWL.intersectionOf, coll_bnode)) # TODO: see whether unions belong # if cls.union_of: # union_node = BNode() # Collection(self.graph, union_coll, [self.class_uri(union_node) for union_node in cls.union_of]) # self.graph.add((union_node, OWL.unionOf, union_coll)) # self.graph.add((cls_uri, RDFS.subClassOf, union_node)) elts = [] for sn in sorted(self.own_slot_names(cls)): # TODO: Reintroduce this # if sn not in cls.defining_slots: if False: slot = self.schema.slots[sn] slot_node = BNode() self.graph.add((slot_node, RDF.type, OWL.Restriction)) self.graph.add((slot_node, OWL.someValuesFrom if slot.required else OWL.allValuesFrom, self._range_uri(slot))) self._add_cardinality(slot_node, slot) self.graph.add((slot_node, OWL.onProperty, self._prop_uri(self.aliased_slot_name(slot)))) elts.append(slot_node) if elts: coll_bnode = BNode() Collection(self.graph, coll_bnode, elts) subclass_bnode = BNode() self.graph.add((subclass_bnode, OWL.unionOf, coll_bnode)) self.graph.add((cls_uri, RDFS.subClassOf, subclass_bnode)) return True def visit_slot(self, slot_name: str, slot: SlotDefinition) -> None: """ Add a slot definition per slot @param slot_name: @param slot: @return: """ # Note: We use the raw name in OWL and add a subProperty arc slot_uri = self._prop_uri(slot.name) self._add_element_properties(slot_uri, slot) self.graph.add( (slot_uri, RDF.type, OWL.ObjectProperty if slot.inherited else OWL.AnnotationProperty)) self.graph.add((slot_uri, RDF.type, self.metamodel.namespaces[METAMODEL_LOCAL_NAME][ camelcase('slot definition')])) self.graph.add((slot_uri, RDFS.range, self._range_uri(slot))) self.graph.add((slot_uri, RDFS.domain, self._class_uri(slot.domain))) # Parent slots if slot.is_a: self.graph.add( (slot_uri, RDFS.subPropertyOf, self._prop_uri(slot.is_a))) for mixin in slot.mixins: self.graph.add( (slot_uri, RDFS.subPropertyOf, self._prop_uri(mixin))) if slot.name in self.synopsis.applytorefs: for appl in self.synopsis.applytorefs[slot.name].slotrefs: self.graph.add( (slot_uri, RDFS.subClassOf, self._prop_uri(appl))) def visit_type(self, typ: TypeDefinition) -> None: type_uri = self._type_uri(typ.name) self.graph.add((type_uri, RDF.type, OWL.Class)) self.graph.add((type_uri, RDF.type, self.metamodel.namespaces[METAMODEL_LOCAL_NAME][ camelcase('type definition')])) self._add_element_properties(type_uri, typ) if typ.typeof: self.graph.add( (type_uri, RDFS.subClassOf, self._type_uri(typ.typeof))) else: restr = BNode() self.graph.add((restr, RDF.type, OWL.Restriction)) self.graph.add((restr, OWL.qualifiedCardinality, Literal(1))) self.graph.add((restr, OWL.onProperty, self.top_value_uri)) self.graph.add( (restr, OWL.onDataRange, self.namespaces.uri_for(typ.uri))) self.graph.add((type_uri, RDFS.subClassOf, restr)) def _add_element_properties(self, uri: URIRef, el: Element) -> None: for k, v in el.__dict__.items(): if k in self.metamodel.schema.slots: defining_slot = self.metamodel.schema.slots[k] if v is not None and 'owl' in defining_slot.in_subset: ve = v if isinstance(v, list) else [v] for e in ve: self.graph.add( (uri, URIRef( self.metamodel.namespaces.uri_for( defining_slot.slot_uri)), Literal(e))) def _add_cardinality(self, subj: Union[BNode, URIRef], slot) -> None: """ Add cardinality restrictions to node """ if slot.required: if slot.multivalued: self.graph.add((subj, OWL.minCardinality, Literal(1))) else: self.graph.add((subj, OWL.cardinality, Literal(1))) elif not slot.multivalued: self.graph.add((subj, OWL.maxCardinality, Literal(1))) def _range_uri(self, slot: SlotDefinition) -> URIRef: if slot.range in self.schema.types: return self._type_uri(cast(TypeDefinitionName, slot.range)) else: return self._class_uri(cast(ClassDefinitionName, slot.range)) def _class_uri(self, cn: ClassDefinitionName) -> URIRef: return self.namespaces[METAMODEL_LOCAL_NAME][camelcase(cn)] def _prop_uri(self, pn: SlotDefinitionName) -> URIRef: return self.namespaces[METAMODEL_LOCAL_NAME][underscore(pn)] def _type_uri(self, tn: TypeDefinitionName) -> URIRef: return self.namespaces[METAMODEL_LOCAL_NAME][underscore(tn)] def _add_metamodel_class(self, cname: str) -> None: metac = self.metamodel.schema.classes[cname] metac_uri = self.metamodel.namespaces[METAMODEL_LOCAL_NAME][camelcase( metac.name)] self.graph.add((metac_uri, RDF.type, OWL.Class)) self._add_element_properties(metac_uri, metac)
class OwlSchemaGenerator(Generator): generatorname = os.path.basename(__file__) generatorversion = "0.1.1" valid_formats = ['owl', 'ttl'] + [ x.name for x in rdflib_plugins(None, rdflib_Parser) if '/' not in str(x.name) ] visits_are_sorted = True def __init__(self, schema: Union[str, TextIO, SchemaDefinition], **kwargs) -> None: super().__init__(schema, **kwargs) self.graph: Optional[Graph] = None self.metamodel = SchemaLoader(LOCAL_METAMODEL_YAML_FILE, importmap=kwargs.get('importmap', None), mergeimports=self.merge_imports) \ if os.path.exists(LOCAL_METAMODEL_YAML_FILE) else\ SchemaLoader(METAMODEL_YAML_URI, base_dir=META_BASE_URI, importmap=kwargs.get('importmap', None), mergeimports=self.merge_imports) self.metamodel.resolve() self.top_value_uri: Optional[URIRef] = None def visit_schema(self, output: Optional[str] = None, **_): base = URIRef(self.schema.id) self.graph = Graph(identifier=base) for prefix in self.metamodel.schema.emit_prefixes: self.graph.bind(prefix, self.metamodel.namespaces[prefix]) self.graph.add((base, RDF.type, OWL.Ontology)) self._add_element_properties(base, self.schema) # add the model types for name in [ 'class_definition', 'type_definition', 'slot_definition', 'subset_definition' ]: self._add_metamodel_class(name) # add value placeholder self.top_value_uri = self.metamodel.namespaces[ METAMODEL_NAMESPACE_NAME]['topValue'] self.graph.add((self.top_value_uri, RDF.type, OWL.DatatypeProperty)) self.graph.add((self.top_value_uri, RDFS.label, Literal("value"))) def end_schema(self, output: Optional[str] = None, **_) -> None: data = self.graph.serialize(format='turtle' if self.format in ['owl', 'ttl'] else self.format).decode() if output: with open(output, 'w') as outf: outf.write(data) else: print(data) def add_metadata(self, e: SchemaDefinition, uri: URIRef) -> None: if e.aliases is not None: for s in e.aliases: self.graph.add((uri, SKOS.altLabel, Literal(s))) if e.mappings is not None: for m in e.mappings: m_uri = self.namespaces.uri_for(m) if m_uri is not None: self.graph.add((uri, SKOS.exactMatch, m_uri)) else: logging.warning(f'No URI for {m}') if e.exact_mappings is not None: for m in e.exact_mappings: m_uri = self.namespaces.uri_for(m) if m_uri is not None: self.graph.add((uri, SKOS.exactMatch, m_uri)) else: logging.warning(f'No URI for {m}') if e.close_mappings is not None: for m in e.close_mappings: m_uri = self.namespaces.uri_for(m) if m_uri is not None: self.graph.add((uri, SKOS.closeMatch, m_uri)) else: logging.warning(f'No URI for {m}') if e.narrow_mappings is not None: for m in e.narrow_mappings: m_uri = self.namespaces.uri_for(m) if m_uri is not None: self.graph.add((uri, SKOS.narrowMatch, m_uri)) else: logging.warning(f'No URI for {m}') if e.broad_mappings is not None: for m in e.broad_mappings: m_uri = self.namespaces.uri_for(m) if m_uri is not None: self.graph.add((uri, SKOS.broadMatch, m_uri)) else: logging.warning(f'No URI for {m}') if e.related_mappings is not None: for m in e.related_mappings: m_uri = self.namespaces.uri_for(m) if m_uri is not None: self.graph.add((uri, SKOS.relatedMatch, m_uri)) else: logging.warning(f'No URI for {m}') def visit_class(self, cls: ClassDefinition) -> bool: cls_uri = self._class_uri(cls.name) self.add_metadata(cls, cls_uri) self.graph.add((cls_uri, RDF.type, OWL.Class)) self.graph.add((cls_uri, RDF.type, self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][ camelcase('class definition')])) self._add_element_properties(cls_uri, cls) # Parent classes # TODO: reintroduce this # if not cls.defining_slots: if True: if cls.is_a: self.graph.add( (cls_uri, RDFS.subClassOf, self._class_uri(cls.is_a))) if cls.mixin: self.graph.add( (cls_uri, RDFS.subClassOf, METAMODEL_NAMESPACE.mixin)) for mixin in sorted(cls.mixins): self.graph.add( (cls_uri, RDFS.subClassOf, self._class_uri(mixin))) if cls.name in self.synopsis.applytorefs: for appl in sorted( self.synopsis.applytorefs[cls.name].classrefs): self.graph.add( (cls_uri, RDFS.subClassOf, self._class_uri(appl))) else: raise NotImplementedError("Defining slots need to be implemented") # If defining slots, we generate an equivalentClass entry # equ_node = BNode() # self.graph.add((cls_uri, OWL.equivalentClass, equ_node)) # self.graph.add((equ_node, RDF.type, OWL.Class)) # # elts = [] # if cls.is_a: # elts.append(self._class_uri(cls.is_a)) # if cls.mixin: # self.graph.add((cls_uri, RDFS.subClassOf, META_NS.mixin)) # for mixin in cls.mixins: # self.graph.add((cls_uri, RDFS.subClassOf, self._class_uri(mixin))) # if cls.name in self.synopsis.applytorefs: # for appl in self.synopsis.applytorefs[cls.name].classrefs: # self.graph.add((cls_uri, RDFS.subClassOf, self._class_uri(appl))) # # for slotname in cls.defining_slots: # restr_node = BNode() # slot = self.schema.slots[slotname] # # self.graph.add((restr_node, RDF.type, OWL.Restriction)) # self.graph.add((restr_node, OWL.onProperty, self._prop_uri(slotname))) # self._add_cardinality(restr_node, slot) # # TODO: fix this # # self.graph.add((restr_node, OWL.someValuesFrom, self._build_range(slot))) # elts.append(restr_node) # # coll_bnode = BNode() # Collection(self.graph, coll_bnode, elts) # self.graph.add((equ_node, OWL.intersectionOf, coll_bnode)) # TODO: see whether unions belong # if cls.union_of: # union_node = BNode() # Collection(self.graph, union_coll, [self.class_uri(union_node) for union_node in cls.union_of]) # self.graph.add((union_node, OWL.unionOf, union_coll)) # self.graph.add((cls_uri, RDFS.subClassOf, union_node)) for sn in sorted(self.own_slot_names(cls)): # Defining_slots are covered above if sn not in cls.defining_slots: slot = self.schema.slots[sn] # Non-inherited slots are annotation properties if self.is_slot_object_property(slot): slot_node = BNode() self.graph.add((cls_uri, RDFS.subClassOf, slot_node)) # required multivalued if slot.required: if slot.multivalued: # y y intersectionOf(restriction(slot only type) restriction(slot some type) restr1 = BNode() self.graph.add((restr1, RDF.type, OWL.Restriction)) self.graph.add((restr1, OWL.allValuesFrom, self._range_uri(slot))) self.graph.add( (restr1, OWL.onProperty, self._prop_uri(self.aliased_slot_name(slot)))) restr2 = BNode() self.graph.add((restr2, RDF.type, OWL.Restriction)) self.graph.add((restr2, OWL.someValuesFrom, self._range_uri(slot))) self.graph.add( (restr2, OWL.onProperty, self._prop_uri(self.aliased_slot_name(slot)))) coll_bnode = BNode() Collection(self.graph, coll_bnode, [restr1, restr2]) self.graph.add( (slot_node, OWL.intersectionOf, coll_bnode)) self.graph.add((slot_node, RDF.type, OWL.Class)) else: # y n restriction(slot exactly 1 type) self.graph.add( (slot_node, RDF.type, OWL.Restriction)) self.graph.add( (slot_node, OWL.qualifiedCardinality, Literal(1))) self.graph.add( (slot_node, OWL.onProperty, self._prop_uri(self.aliased_slot_name(slot)))) self.graph.add((slot_node, OWL.onClass, self._range_uri(slot))) else: if slot.multivalued: # n y restriction(slot only type) self.graph.add( (slot_node, RDF.type, OWL.Restriction)) self.graph.add((slot_node, OWL.allValuesFrom, self._range_uri(slot))) self.graph.add( (slot_node, OWL.onProperty, self._prop_uri(self.aliased_slot_name(slot)))) else: # n n intersectionOf(restriction(slot only type) restriction(slot max 1 type)) self.graph.add( (slot_node, RDF.type, OWL.Restriction)) self.graph.add((slot_node, OWL.onClass, self._range_uri(slot))) self.graph.add( (slot_node, OWL.maxQualifiedCardinality, Literal(1))) self.graph.add( (slot_node, OWL.onProperty, self._prop_uri(self.aliased_slot_name(slot)))) return True def visit_slot(self, slot_name: str, slot: SlotDefinition) -> None: """ Add a slot definition per slot @param slot_name: @param slot: @return: """ # Note: We use the raw name in OWL and add a subProperty arc slot_uri = self._prop_uri(slot.name) self._add_element_properties(slot_uri, slot) # Inherited slots are object or data properties # All others are annotation properties self.graph.add( (slot_uri, RDF.type, OWL.ObjectProperty if self.is_slot_object_property(slot) else OWL.AnnotationProperty)) self.graph.add((slot_uri, RDF.type, self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][ camelcase('slot definition')])) self.graph.add((slot_uri, RDFS.range, self._range_uri(slot))) if slot.domain: self.graph.add( (slot_uri, RDFS.domain, self._class_uri(slot.domain))) if slot.inverse: self.graph.add( (slot_uri, OWL.inverseOf, self._prop_uri(slot.inverse))) if slot.symmetric: self.graph.add((slot_uri, RDF.type, OWL.SymmetricProperty)) # Parent slots if slot.is_a: self.graph.add( (slot_uri, RDFS.subPropertyOf, self._prop_uri(slot.is_a))) for mixin in slot.mixins: self.graph.add( (slot_uri, RDFS.subPropertyOf, self._prop_uri(mixin))) if slot.name in self.synopsis.applytorefs: for appl in self.synopsis.applytorefs[slot.name].slotrefs: self.graph.add( (slot_uri, RDFS.subClassOf, self._prop_uri(appl))) def visit_type(self, typ: TypeDefinition) -> None: type_uri = self._type_uri(typ.name) self.graph.add((type_uri, RDF.type, OWL.Class)) self.graph.add((type_uri, RDF.type, self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][ camelcase('type definition')])) self._add_element_properties(type_uri, typ) if typ.typeof: self.graph.add( (type_uri, RDFS.subClassOf, self._type_uri(typ.typeof))) else: restr = BNode() self.graph.add((restr, RDF.type, OWL.Restriction)) self.graph.add((restr, OWL.qualifiedCardinality, Literal(1))) self.graph.add((restr, OWL.onProperty, self.top_value_uri)) self.graph.add( (restr, OWL.onDataRange, self.namespaces.uri_for(typ.uri))) self.graph.add((type_uri, RDFS.subClassOf, restr)) def _add_element_properties(self, uri: URIRef, el: Element) -> None: for k, v in el.__dict__.items(): if k in self.metamodel.schema.slots: defining_slot = self.metamodel.schema.slots[k] if v is not None and 'owl' in defining_slot.in_subset: ve = v if isinstance(v, list) else [v] for e in ve: self.graph.add( (uri, URIRef( self.metamodel.namespaces.uri_for( defining_slot.slot_uri)), Literal(e))) def _add_cardinality(self, subj: Union[BNode, URIRef], slot) -> None: """ Add cardinality restrictions to node """ if slot.required: if slot.multivalued: self.graph.add((subj, OWL.minCardinality, Literal(1))) else: self.graph.add((subj, OWL.cardinality, Literal(1))) elif not slot.multivalued: self.graph.add((subj, OWL.maxCardinality, Literal(1))) 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 _class_uri(self, cn: ClassDefinitionName) -> URIRef: c = self.schema.classes[cn] return URIRef(c.definition_uri) def _enum_uri(self, en: EnumDefinitionName) -> URIRef: # TODO: enums e = self.schema.enums[en] return URIRef(f"http://UNKNOWN.org/{en}") def _prop_uri(self, pn: SlotDefinitionName) -> URIRef: p = self.schema.slots.get(pn, None) if p is None or p.definition_uri is None: return self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][ underscore(pn)] else: return URIRef(p.definition_uri) def _type_uri(self, tn: TypeDefinitionName) -> URIRef: t = self.schema.types[tn] return URIRef(t.definition_uri) def _add_metamodel_class(self, cname: str) -> None: metac = self.metamodel.schema.classes[cname] metac_uri = self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][ camelcase(metac.name)] self.graph.add((metac_uri, RDF.type, OWL.Class)) self._add_element_properties(metac_uri, metac) def is_slot_object_property(self, slot: SlotDefinition) -> bool: return True
class OwlSchemaGenerator(Generator): generatorname = os.path.basename(__file__) generatorversion = "0.0.2" valid_formats = [x.name for x in rdflib_plugins(None, rdflib_Parser) if '/' not in str(x.name)] def __init__(self, schema: Union[str, TextIO, SchemaDefinition], fmt: str = 'ttl') -> None: super().__init__(schema, fmt) self.graph: Graph = None def visit_schema(self, output: Optional[str]): # TODO: Is it even possible to have a schema without an ID? base = URIRef(self.schema.id) if self.schema.id else self.class_uri(self.schema.name) self.graph = Graph(identifier=base) self.graph.bind("obo", str(OBO)) self.graph.bind("dcterms", str(DCTERMS)) self.graph.bind("owl", str(OWL)) self.graph.bind("bioentity", str(BIOENTITY)) self.graph.bind("meta", str(META)) self.graph.add((base, RDF.type, OWL.Ontology)) self.graph.add((base, RDF.type, OWL.Ontology)) self.graph.add((base, RDFS.label, Literal(self.schema.name))) if self.schema.license: self.graph.add((base, DCTERMS.license, Literal(self.schema.license))) else: logging.warning("No license!") def end_schema(self, output: Optional[str]) -> None: data = lambda: self.graph.serialize(format='turtle' if self.format == 'ttl' else self.format).decode() if output: with open(output, 'w') as outf: outf.write(data()) else: print(data()) def visit_class(self, cls: ClassDefinition) -> bool: cls_uri = self.class_uri(cls.name) self.graph.add((cls_uri, RDF.type, OWL.Class)) self.graph.add((cls_uri, RDFS.label, Literal(cls.name))) if cls.description: self.graph.add((cls_uri, OBO.IAO_0000115, Literal(cls.description))) # Parent classes if cls.is_a and not cls.defining_slots: self.graph.add((cls_uri, RDFS.subClassOf, self.class_uri(cls.is_a))) if cls.mixin: self.graph.add((cls_uri, RDFS.subClassOf, META.mixin)) for mixin in cls.mixins: self.graph.add((cls_uri, RDFS.subClassOf, self.class_uri(mixin))) # TODO: Add apply_to injections if cls.union_of: union_node = BNode() union_coll = BNode() Collection(self.graph, union_coll, [self.class_uri(union_node) for union_node in cls.union_of]) self.graph.add((union_node, OWL.unionOf, union_coll)) self.graph.add((cls_uri, RDFS.subClassOf, union_node)) # Defining slots give us an equivalent class if cls.defining_slots: equ_node = BNode() self.graph.add((cls_uri, OWL.equivalentClass, equ_node)) self.graph.add((equ_node, RDF.type, OWL.Class)) elts = [] if cls.is_a: elts.append(self.class_uri(cls.is_a)) for slotname in cls.defining_slots: slot = self.schema.slots[slotname] restr_node = BNode() self.graph.add((restr_node, RDF.type, OWL.Restriction)) self.graph.add((restr_node, OWL.onProperty, self.prop_uri(slotname))) self.add_cardinality(restr_node, slot) self.graph.add((restr_node, OWL.someValuesFrom, self.build_range(slot))) elts.append(restr_node) coll_bnode = BNode() Collection(self.graph, coll_bnode, elts) self.graph.add((equ_node, OWL.intersectionOf, coll_bnode)) for slotname in cls.slots: if slotname not in cls.defining_slots: subc_node = BNode() slot = self.schema.slots[slotname] slot_alias = slot.alias if slot.alias else slot.name self.graph.add((subc_node, RDF.type, OWL.Restriction)) self.graph.add((subc_node, OWL.onProperty, self.prop_uri(slot_alias))) self.add_cardinality(subc_node, slot) self.graph.add((subc_node, OWL.someValuesFrom, self.build_range(slot))) self.graph.add((cls_uri, RDFS.subClassOf, subc_node)) return True def add_cardinality(self, subj: Union[BNode, URIRef], slot) -> None: if slot.required or slot.primary_key: if slot.multivalued: self.graph.add((subj, OWL.minCardinality, Literal(1))) else: self.graph.add((subj, OWL.cardinality, Literal(1))) elif not slot.multivalued: self.graph.add((subj, OWL.maxCardinality, Literal(1))) def build_range(self, slot) -> Union[BNode, URIRef]: # if slot.range in builtin_names or slot.range in self.schema.types: # datatype_node = BNode() # self.graph.add((datatype_node, RDF.type, RDFS.DataProperty)) # self.graph.add((datatype_node, OWL.onDataType, self.range_uri(slot))) # return datatype_node # else: return self.range_uri(slot) def range_uri(self, slot: SlotDefinition) -> URIRef: if slot.range in builtin_names or not slot.range: return URIRef(builtin_uri(slot.range, expand=True)) elif slot.range in self.schema.types: return self.type_uri(slot.range) else: return self.class_uri(slot.range) @staticmethod def class_uri(cn: ClassDefinitionName) -> URIRef: return BIOENTITY[camelcase(cn)] @staticmethod def prop_uri(pn: SlotDefinitionName) -> URIRef: return BIOENTITY[underscore(pn)] @staticmethod def type_uri(tn: TypeDefinitionName) -> URIRef: return BIOENTITY[underscore(tn)] def visit_slot(self, slot_name: str, slot: SlotDefinition) -> None: """ Add a slot definition per slot @param slot_name: @param slot: @return: """ # Note: We use the raw name in OWL and add a subProperty arc slot_uri = self.prop_uri(slot.name) # Parent slots if slot.is_a: self.graph.add((slot_uri, RDFS.subPropertyOf, self.prop_uri(slot.is_a))) for mixin in slot.mixins: self.graph.add((slot_uri, RDFS.subPropertyOf, self.prop_uri(mixin))) # Slot range if not slot.range or slot.range in builtin_names: self.graph.add((slot_uri, RDF.type, OWL.DatatypeProperty if slot.object_property else OWL.AnnotationProperty)) self.graph.add((slot_uri, RDFS.range, URIRef(builtin_uri(slot.range, expand=True)))) elif slot.range in self.schema.types: self.graph.add((slot_uri, RDF.type, OWL.DatatypeProperty if slot.object_property else OWL.AnnotationProperty)) self.graph.add((slot_uri, RDFS.range, self.type_uri(slot.range))) else: self.graph.add((slot_uri, RDF.type, OWL.ObjectProperty if slot.object_property else OWL.AnnotationProperty)) self.graph.add((slot_uri, RDFS.range, self.class_uri(slot.range))) # Slot domain if slot.domain: self.graph.add((slot_uri, RDFS.domain, self.class_uri(slot.domain))) # Annotations self.graph.add((slot_uri, RDFS.label, Literal(slot.name))) if slot.description: self.graph.add((slot_uri, OBO.IAO_0000115, Literal(slot.description)))