1+ import sys
12from collections import defaultdict
23from typing import Optional , List , Set , Any , Union , Dict , Tuple
34from dataclasses import dataclass , field
1516from linkml_runtime .index .object_index import ObjectIndex
1617from linkml_runtime .utils .inference_utils import infer_all_slot_values , infer_slot_value , Config
1718
18- from rdflib import URIRef
19+ from rdflib import URIRef , Graph
1920
2021from linkml_runtime .linkml_model .meta import ClassDefinition , SchemaDefinition , SlotDefinition , Definition , \
2122 ClassDefinitionName
@@ -168,20 +169,26 @@ def to_ontology_document(self, element: Union[YAMLRoot, List[YAMLRoot]], schema:
168169 doc .prefixDeclarations .append (Prefix (pfx .prefix_prefix , pfx .prefix_reference ))
169170 return doc
170171
171- def dumps (self , element : YAMLRoot , schema : SchemaDefinition = None , schemaview : SchemaView = None , iri = None ) -> str :
172+ def dumps (self , element : YAMLRoot , schema : SchemaDefinition = None , schemaview : SchemaView = None , iri = None , output_type = None ) -> str :
172173 """
173174 Dump a linkml instance tree to a function syntax OWL ontology string
174175
175176 :param element:
176177 :param schema:
177178 :param schemaview:
178179 :param iri:
180+ :param output_type:
179181 :return:
180182 """
181183 if schemaview :
182184 schema = schemaview .schema
183185 doc = self .to_ontology_document (element , schema , iri = iri )
184- return str (doc )
186+ if output_type == "ttl" :
187+ g = Graph ()
188+ doc .to_rdf (g )
189+ return g .serialize (format = "ttl" )
190+ else :
191+ return str (doc )
185192
186193 def transform (self , element : YAMLRoot , schema : SchemaDefinition , is_element_an_object = True ) -> Any :
187194 """
@@ -296,7 +303,7 @@ def transform(self, element: YAMLRoot, schema: SchemaDefinition, is_element_an_o
296303 axiom_annotations .append (Annotation (ann_slot_iri , ann_val ))
297304 # templates
298305 for tmpl in owl_templates :
299- self .add_axioms_from_template (tmpl , element )
306+ self .add_axioms_from_template (tmpl , element , schema = schema )
300307 if schema_level_slot .slot_uri is not None :
301308 slot_uri = self ._get_IRI_str (schema_level_slot .slot_uri )
302309 else :
@@ -524,8 +531,9 @@ def _get_inferred_slot_annotations(self, slot: SlotDefinition, ann_key: str,
524531 anc_slots = [slot ]
525532 sv = self .schemaview
526533 for anc_c in sv .class_ancestors (class_name , reflexive = True ):
527- induced_slot = sv .induced_slot (slot .name , anc_c )
528- anc_slots .append (induced_slot )
534+ if slot .name in sv .class_slots (anc_c ):
535+ induced_slot = sv .induced_slot (slot .name , anc_c )
536+ anc_slots .append (induced_slot )
529537 for a in sv .slot_ancestors (slot .name , reflexive = True ):
530538 anc_slots .append (sv .get_slot (a ))
531539 for s in anc_slots :
@@ -634,20 +642,20 @@ def parse_axioms_string(self, owl_str: str, schemaview: SchemaView = None) -> On
634642 for prefix , url in schemaview .namespaces ().items ():
635643 prefix_lines .append (f'Prefix( { prefix } : = <{ url } > )' )
636644 header = "\n " .join (prefix_lines )
637- owl_str = f'{ header } \n Ontology(\n { owl_str } \n )'
645+ owl_str = f'{ header } \n Ontology(<http://example.org> \n { owl_str } \n )'
638646 logging .debug (owl_str )
639647 try :
640648 doc = to_python (owl_str )
641649 except Exception as e :
642650 logging .error (f'Error parsing generated OWL: { owl_str } ' )
643651 raise e
644- from funowl .writers .FunctionalWriter import FunctionalWriter
645- from rdflib import Graph
646- g = Graph ()
647- for p in doc .prefixDeclarations :
648- g .namespace_manager .bind (p .prefixName , p .fullIRI )
649- fw = FunctionalWriter (g = g )
650- owl_str_roundtrip = doc .to_functional (fw )
652+ # from funowl.writers.FunctionalWriter import FunctionalWriter
653+ # from rdflib import Graph
654+ # g = Graph()
655+ # for p in doc.prefixDeclarations:
656+ # g.namespace_manager.bind(p.prefixName, p.fullIRI)
657+ # fw = FunctionalWriter(g=g)
658+ # owl_str_roundtrip = doc.to_functional(fw)
651659 #logging.debug(f'ROUNDTRIP = {owl_str_roundtrip}')
652660 return doc
653661
@@ -686,6 +694,12 @@ def tr(e: YAMLRoot):
686694 else :
687695 d ["tr" ] = tr
688696 jt = Template (tstr )
697+
698+ def _tr (x ):
699+ fw = FunctionalWriter ()
700+ expr = self .transform (x , schema )
701+ return expr .to_functional (fw )
702+ d ["tr" ] = _tr
689703 owl_str = jt .render (** d )
690704 axioms = self .parse_axioms_string (owl_str ).ontology .axioms
691705 self .ontology .axioms += axioms
@@ -724,34 +738,42 @@ def populate_missing_values(self, element: YAMLRoot):
724738 help = "Path to python datamodel module" )
725739@click .option ("--format" , "-f" ,
726740 help = "Input format (will be inferred from file suffix if not specified)" )
727- @click .option ('-o' , '--output' , required = True ,
741+ @click .option ('-o' , '--output' ,
742+ type = click .File (mode = "w" ),
743+ default = sys .stdout ,
728744 help = "Path to OWL functional syntax output" )
745+ @click .option ('-O' , '--output-type' ,
746+ type = click .Choice (["ofn" , "ttl" ]),
747+ help = "Output format" )
729748@click .option ("--autofill/--no-autofill" ,
730749 default = False ,
731750 show_default = True ,
732751 help = "If True, fill missing data slots using string_serialization" )
733752@click .argument ('inputfile' )
734- def cli (inputfile : str , schema : str , target_class , module , output , format , autofill : bool , verbose : int , quiet : bool , ** args ):
753+ def cli (inputfile : str , schema : str , target_class , module , output , output_type , format , autofill : bool , verbose : int , quiet : bool , ** args ):
735754 """
736755 Dump LinkML instance data as OWL
737756
738757 Examples:
739758
740759 Convert a CSV to OWL
741760
742- linkml-data2owl -s tests/inputs/ owl_dumper_test.yaml tests/inputs/ parts.csv -o parts.ofn
761+ linkml-data2owl -s owl_dumper_test.yaml parts.csv -o parts.ofn
743762
744- Note in this example, there must be a class type designator column `@type` in the CSV
763+ Note in this example, there must be a class type designator column `@type` in the CSV
745764
746765 Convert a CSV to OWL, homogeneous classes:
747766
748- linkml-data2owl -C EquivGenusAndPartOf -s tests/inputs/ owl_dumper_test.yaml \
749- tests/inputs/ parts_implicit_type.csv -o parts.ofn
767+ linkml-data2owl -C EquivGenusAndPartOf -s owl_dumper_test.yaml \
768+ parts_implicit_type.csv -o parts.ofn
750769
751770 Convert YAML or JSON to OWL:
752771
753- linkml-data2owl -s tests/inputs/ owl_dumper_test.yaml tests/inputs/ owl_dumper_test_data.yaml -o ont.ofn
772+ linkml-data2owl -s owl_dumper_test.yaml owl_dumper_test_data.yaml -o ont.ofn
754773
774+ More documentation:
775+
776+ https://linkml.io/linkml-owl/
755777 """
756778 logger = logging .getLogger ()
757779 if verbose >= 2 :
@@ -775,12 +797,8 @@ def cli(inputfile: str, schema: str, target_class, module, output, format, autof
775797 dumper = OWLDumper ()
776798 if autofill :
777799 dumper .autofill = True
778- doc = dumper .dumps (element , schemaview = sv )
779- if output is None :
780- print (str (doc ))
781- else :
782- with open (output , 'w' ) as stream :
783- stream .write (str (doc ))
800+ doc = dumper .dumps (element , schemaview = sv , output_type = output_type )
801+ output .write (str (doc ))
784802
785803
786804if __name__ == '__main__' :
0 commit comments