diff --git a/ontopy/utils.py b/ontopy/utils.py index cf759d0b7..010e5e751 100644 --- a/ontopy/utils.py +++ b/ontopy/utils.py @@ -835,3 +835,38 @@ def recur(o): ) return layout + + +def copy_annotation(onto, src, dst): + """In all classes and properties in `onto`, copy annotation `src` to `dst`. + + Arguments: + onto: Ontology to work on. + src: Name of source annotation. + dst: Name or IRI of destination annotation. Use IRI if the + destination annotation is not already in the ontology. + """ + if onto.world[src]: + src = onto.world[src] + else: + src = onto[src] + + if onto.world[dst]: + dst = onto.world[dst] + elif dst in onto: + dst = onto[dst] + else: + if "://" not in dst: + raise ValueError( + "new destination annotation property must be provided as " + "a full IRI" + ) + name = min(dst.rsplit("#")[-1], dst.rsplit("/")[-1], key=len) + iri = dst + dst = onto.new_annotation_property(name, owlready2.AnnotationProperty) + dst.iri = iri + + for e in onto.get_entities(): + new = getattr(e, src.name).first() + if new and new not in getattr(e, dst.name): + getattr(e, dst.name).append(new) diff --git a/tests/tools/test_ontoconvert.py b/tests/tools/test_ontoconvert.py index 8a6c1e719..542387f2b 100644 --- a/tests/tools/test_ontoconvert.py +++ b/tests/tools/test_ontoconvert.py @@ -39,3 +39,33 @@ def test_run() -> None: assert re.search("@prefix : ", output2) assert re.search(" .* owl:Ontology", output2) assert re.search("testclass .* owl:Class", output2) + + # Test 3 - copy-annotation + ontoconvert.main( + [ + "-p", + "--iri=https://w3id.org/ex/testonto", + "--base-iri=https://w3id.org/ex/testonto#", + str(ontodir / "testonto.ttl"), + str(outdir / "test_ontoconvert3.ttl"), + ] + ) + input3 = (ontodir / "testonto.ttl").read_text() + output3 = (outdir / "test_ontoconvert3.ttl").read_text() + assert not re.search('rdfs:label "hasAnnotationProperty"@en', input3) + assert re.search('rdfs:label "hasAnnotationProperty"@en', output3) + + # Test 4 - copy-annotation with source as annotation label + ontoconvert.main( + [ + "-c prefLabel-->http://www.w3.org/2004/02/skos/core#hiddenLabel", + "--iri=https://w3id.org/ex/testonto", + "--base-iri=https://w3id.org/ex/testonto#", + str(ontodir / "testonto.ttl"), + str(outdir / "test_ontoconvert4.ttl"), + ] + ) + input4 = (ontodir / "testonto.ttl").read_text() + output4 = (outdir / "test_ontoconvert4.ttl").read_text() + assert not re.search('skos:hiddenLabel "hasAnnotationProperty"@en', input4) + assert re.search('skos:hiddenLabel "hasAnnotationProperty"@en', output4) diff --git a/tools/ontoconvert b/tools/ontoconvert index 934d12df8..5a75d1dd1 100755 --- a/tools/ontoconvert +++ b/tools/ontoconvert @@ -7,7 +7,7 @@ import warnings from rdflib.util import guess_format from ontopy import get_ontology -from ontopy.utils import annotate_source, rename_iris +from ontopy.utils import annotate_source, rename_iris, copy_annotation def main(argv: list = None): @@ -59,6 +59,29 @@ def main(argv: list = None): "The default is to append to it." ), ) + parser.add_argument( + "--copy-annotation", + "-c", + action="append", + default=[], + metavar="FROM-->TO", + help=( + "Copy annotation FROM to annotation TO in each class and " + "property in the ontology. FROM and TO may be given as " + "full IRIs or (if they already exists as annotations in the " + "ontology) as entity names. " + "This option be given multiple times." + ), + ) + parser.add_argument( + "--copy-preflabel", + "-p", + action="store_true", + help=( + "Alias for: `--copy-annotation=http://www.w3.org/2004/02/skos/" + "core#prefLabel-->http://www.w3.org/2000/01/rdf-schema#label`" + ), + ) parser.add_argument( "--no-catalog", "-n", @@ -72,7 +95,7 @@ def main(argv: list = None): "--infer", "-i", nargs="?", - const="FaCT++", + const="HermiT", choices=["HermiT", "Pellet", "FaCT++"], metavar="NAME", help=( @@ -188,6 +211,14 @@ def main(argv: list = None): if not output_format: output_format = "xml" + if args.copy_annotation is None: + args.copy_annotation = [] + if args.copy_preflabel: + args.copy_annotation.append( + "http://www.w3.org/2004/02/skos/core#prefLabel-->" + "http://www.w3.org/2000/01/rdf-schema#label" + ) + # Perform conversion with warnings.catch_warnings(record=True) as warnings_handle: warnings.simplefilter("always") @@ -218,6 +249,10 @@ def main(argv: list = None): debug=verbose, ) + for cpy in args.copy_annotation: + src, dst = cpy.split("-->", 1) + copy_annotation(onto, src.strip(), dst.strip()) + onto.save( args.output, format=output_format,