From d65c6b23d60c18068e0ab81b123a2dbbb0cd52be Mon Sep 17 00:00:00 2001 From: "@weibo.zhou" Date: Thu, 25 Sep 2025 19:23:17 +0800 Subject: [PATCH] feat: Add support for ObjectHasValue constraints in OWL import engine - Add handling for ObjectHasValue expressions in SubClassOf axioms - Convert ObjectHasValue to slot_usage.equals_string constraints - Preserve semantic information that was previously lost - Fix "cannot handle anon parent classes" errors - Add comprehensive test coverage - Maintain backward compatibility --- .../importers/owl_import_engine.py | 7 +++ .../test_owl_object_has_value.py | 60 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/test_importers/test_owl_object_has_value.py diff --git a/schema_automator/importers/owl_import_engine.py b/schema_automator/importers/owl_import_engine.py index 921923c..79153c7 100644 --- a/schema_automator/importers/owl_import_engine.py +++ b/schema_automator/importers/owl_import_engine.py @@ -160,6 +160,13 @@ def set_cardinality(p, min_card, max_card): lit = lit.literal set_slot_usage(p, 'equals_string', str(lit)) #slot_usage_map[child][p]['equals_string'] = str(lit) + elif isinstance(a.superClassExpression, ObjectHasValue): + x = a.superClassExpression + p = self.iri_to_name(x.objectPropertyExpression) + # Get the individual name + individual_name = self.iri_to_name(x.individual) + # Store as equals_string constraint (LinkML doesn't have object equals, so we use string) + set_slot_usage(p, 'equals_string', individual_name) else: logging.error(f"cannot handle anon parent classes for {a}") else: diff --git a/tests/test_importers/test_owl_object_has_value.py b/tests/test_importers/test_owl_object_has_value.py new file mode 100644 index 0000000..82808f8 --- /dev/null +++ b/tests/test_importers/test_owl_object_has_value.py @@ -0,0 +1,60 @@ +""" +Test case for ObjectHasValue support in OWL import engine +""" + +import unittest +import tempfile +import os +from schema_automator.importers.owl_import_engine import OwlImportEngine + + +class TestObjectHasValue(unittest.TestCase): + """Test ObjectHasValue constraint handling""" + + def test_object_has_value_constraint(self): + """Test that ObjectHasValue constraints are properly converted to equals_string""" + + # Simple OWL functional syntax with ObjectHasValue constraint + owl_content = ''' +Prefix(:=) +Prefix(owl:=) +Prefix(rdf:=) +Prefix(rdfs:=) + +Ontology( + +Declaration(Class(:TestClass)) +Declaration(ObjectProperty(:hasState)) +Declaration(NamedIndividual(:SolidState)) + +SubClassOf(:TestClass ObjectHasValue(:hasState :SolidState)) + +)''' + + # Write to temporary file + with tempfile.NamedTemporaryFile(mode='w', suffix='.ofn', delete=False) as f: + f.write(owl_content) + temp_file = f.name + + try: + # Convert using our engine + engine = OwlImportEngine() + schema = engine.convert(temp_file, name='test') + + # Check that the constraint was properly converted + self.assertIn('TestClass', schema.classes) + test_class = schema.classes['TestClass'] + + # Should have slot_usage with equals_string constraint + self.assertIn('slot_usage', test_class) + self.assertIn('hasState', test_class['slot_usage']) + self.assertIn('equals_string', test_class['slot_usage']['hasState']) + self.assertEqual(test_class['slot_usage']['hasState']['equals_string'], 'SolidState') + + finally: + # Clean up + os.unlink(temp_file) + + +if __name__ == '__main__': + unittest.main()