ISSUES CLOSED: #2
15 KiB
Class Hierarchy Conversion
CleverRDFLib provides a conversion system that allows you to transform the class hierarchy into custom object representations using the Visitor pattern. This is useful when you need to convert the framework's internal representation to a format more suitable for your application.
Purpose
The conversion system enables:
- Custom representations: Convert to application-specific object structures
- Format transformation: Transform to different data formats (JSON, XML, custom classes)
- Selective conversion: Convert only the elements you need
- Relationship preservation: Maintain parent-child and property relationships
Conversion Architecture
The conversion system uses the Visitor pattern:
ClassHierarchyConverter: Interface defining conversion operationsClassHierarchyConverterVisitor: Visitor that traverses the hierarchy and calls converter methods- Your implementation: Custom converter that builds your target representation
Creating a Custom Converter
To create a custom converter, implement the ClassHierarchyConverter interface:
from cleverrdf_lib.core.interfaces.interfaces import ClassHierarchyConverter
from cleverrdf_lib.core.class_hierarchy import PropertyType
class MyCustomConverter(ClassHierarchyConverter):
"""Custom converter that converts to a dictionary representation."""
def __init__(self):
self.classes = {}
self.properties = {}
self.datatypes = {}
def build_class(self, iri: str, label: str, comment: str):
"""Build a class in the target representation."""
class_obj = {
"iri": iri,
"label": label,
"comment": comment,
"parents": [],
"children": [],
"object_properties": [],
"datatype_properties": []
}
self.classes[iri] = class_obj
return class_obj
def build_object_property(
self,
ptype: PropertyType,
iri: str,
type_iri: str,
label: str,
comment: str
):
"""Build an object property in the target representation."""
prop_obj = {
"iri": iri,
"type": ptype.value,
"type_iri": type_iri,
"label": label,
"comment": comment,
"domain": None,
"range": None
}
self.properties[iri] = prop_obj
return prop_obj
def build_datatype_property(
self,
ptype: PropertyType,
iri: str,
type_iri: str,
label: str,
comment: str
):
"""Build a datatype property in the target representation."""
prop_obj = {
"iri": iri,
"type": ptype.value,
"type_iri": type_iri,
"label": label,
"comment": comment,
"domain": None,
"range": None
}
self.properties[iri] = prop_obj
return prop_obj
def build_datatype(self, iri: str, label: str, comment: str):
"""Build a datatype in the target representation."""
dt_obj = {
"iri": iri,
"label": label,
"comment": comment
}
self.datatypes[iri] = dt_obj
return dt_obj
def add_parent_class(self, child, parent):
"""Add a parent-child relationship."""
if child and parent:
child["parents"].append(parent["iri"])
parent["children"].append(child["iri"])
def add_child_class(self, parent, child):
"""Add a parent-child relationship (alternative method)."""
if parent and child:
parent["children"].append(child["iri"])
child["parents"].append(parent["iri"])
def assign_object_property(self, property_obj, domain_class, range_class):
"""Assign domain and range to an object property."""
if property_obj and domain_class:
property_obj["domain"] = domain_class["iri"]
if property_obj and range_class:
property_obj["range"] = range_class["iri"]
def assign_datatype_property(self, property_obj, domain_class, range_datatype):
"""Assign domain and range to a datatype property."""
if property_obj and domain_class:
property_obj["domain"] = domain_class["iri"]
if property_obj and range_datatype:
property_obj["range"] = range_datatype["iri"]
def add_object_property_domain(self, domain_class, property_obj):
"""Add an object property to a class's domain properties."""
if domain_class and property_obj:
domain_class["object_properties"].append(property_obj["iri"])
def add_object_property_range(self, range_class, property_obj):
"""Add an object property to a class's range properties."""
if range_class and property_obj:
range_class["object_properties"].append(property_obj["iri"])
def add_datatype_property_domain(self, domain_class, property_obj):
"""Add a datatype property to a class's domain properties."""
if domain_class and property_obj:
domain_class["datatype_properties"].append(property_obj["iri"])
Using the Converter
Once you have a custom converter, use it with the ClassHierarchyConverterVisitor:
from cleverrdf_lib.core.class_hierarchy_converter_visitor import ClassHierarchyConverterVisitor
from cleverrdf_lib import OntologyLoader
# Load ontology and get hierarchy
loader = OntologyLoader()
result = loader.load_ontology("ontology.ttl")
hierarchy = result.class_hierarchy
# Create converter
converter = MyCustomConverter()
# Create visitor
visitor = ClassHierarchyConverterVisitor(converter)
# Convert the hierarchy
visitor.visit_classes_from_the_root(hierarchy)
# Access converted data
print("Converted classes:")
for iri, class_obj in converter.classes.items():
print(f" {iri}: {class_obj}")
print("Converted properties:")
for iri, prop_obj in converter.properties.items():
print(f" {iri}: {prop_obj}")
Conversion Process
The conversion process follows these steps:
- Visitor creation: Create a
ClassHierarchyConverterVisitorwith your converter - Traversal: Call
visit_classes_from_the_root()to start conversion - Class conversion: Classes are converted starting from root classes
- Relationship building: Parent-child relationships are established
- Property conversion: Properties are converted and linked to classes
- Datatype conversion: Datatypes are converted and linked to properties
Conversion Order
The visitor ensures proper conversion order:
- Root classes first: Root classes are visited first
- Parent-child traversal: Parents and children are visited recursively
- Property linking: Properties are linked after classes are converted
- Datatype linking: Datatypes are linked to datatype properties
Selective Conversion
!!! warning "Selective Conversion Limitations"
Returning None from converter methods will raise exceptions if other elements of the same type have already been converted, or if relationships need to be established with converted elements. The visitor enforces consistency: you cannot selectively skip some elements while converting others of the same type.
If you need selective conversion, you have two options:
Option 1: Convert all elements but mark skipped ones
from cleverrdf_lib.core.interfaces.interfaces import ClassHierarchyConverter
class SelectiveConverter(ClassHierarchyConverter):
"""Converter that marks some classes as skipped."""
def __init__(self, allowed_namespaces: set[str]):
self.allowed_namespaces = allowed_namespaces
self.classes = {}
self.skipped_classes = set()
def build_class(self, iri: str, label: str, comment: str):
"""Convert all classes, but mark some as skipped."""
if not any(iri.startswith(ns) for ns in self.allowed_namespaces):
# Mark as skipped but still create the object
class_obj = {"iri": iri, "label": label, "comment": comment, "skipped": True}
self.skipped_classes.add(iri)
else:
class_obj = {"iri": iri, "label": label, "comment": comment, "skipped": False}
self.classes[iri] = class_obj
return class_obj # Always return an object
# ... implement other required methods ...
Option 2: Filter before conversion
Filter the hierarchy before conversion to only include elements you want to convert:
from cleverrdf_lib.core.class_hierarchy_converter_visitor import ClassHierarchyConverterVisitor
from cleverrdf_lib.core.interfaces.interfaces import ClassHierarchyConverter
# Assuming MyCustomConverter is defined elsewhere
converter = MyCustomConverter()
visitor = ClassHierarchyConverterVisitor(converter)
# Filter classes before conversion
allowed_namespaces = {"http://example.org/allowed/"}
# Only convert classes from allowed namespaces (iterate over dictionary values)
classes_to_convert = [
cls for cls in hierarchy.get_all_classes().values()
if any(cls.iri.startswith(ns) for ns in allowed_namespaces)
]
# Then convert only the filtered classes
for class_node in classes_to_convert:
visitor.visit_class(class_node)
Conversion to JSON
Example converter that produces JSON-serializable dictionaries:
import json
from cleverrdf_lib.core.interfaces.interfaces import ClassHierarchyConverter
from cleverrdf_lib.core.class_hierarchy_converter_visitor import ClassHierarchyConverterVisitor
class JSONConverter(ClassHierarchyConverter):
"""Converter that produces JSON-serializable output."""
def __init__(self):
self.output = {
"classes": {},
"properties": {},
"datatypes": {}
}
def build_class(self, iri: str, label: str, comment: str):
class_obj = {
"iri": iri,
"label": label,
"comment": comment,
"parents": [],
"children": []
}
self.output["classes"][iri] = class_obj
return class_obj
# ... implement other methods ...
def to_json(self) -> str:
"""Convert to JSON string."""
return json.dumps(self.output, indent=2)
# Use the converter
converter = JSONConverter()
visitor = ClassHierarchyConverterVisitor(converter)
visitor.visit_classes_from_the_root(hierarchy)
# Get JSON output
json_output = converter.to_json()
print(json_output)
Conversion to Custom Classes
Example converter that creates custom class instances:
from cleverrdf_lib.core.interfaces.interfaces import ClassHierarchyConverter
from cleverrdf_lib.core.class_hierarchy import PropertyType
class MyClass:
"""Custom class representation."""
def __init__(self, iri: str, label: str, comment: str):
self.iri = iri
self.label = label
self.comment = comment
self.parents = []
self.children = []
self.properties = []
class MyProperty:
"""Custom property representation."""
def __init__(self, iri: str, ptype: str):
self.iri = iri
self.type = ptype
self.domain = None
self.range = None
class CustomClassConverter(ClassHierarchyConverter):
"""Converter that creates custom class instances."""
def __init__(self):
self.classes = {}
self.properties = {}
def build_class(self, iri: str, label: str, comment: str):
class_obj = MyClass(iri, label, comment)
self.classes[iri] = class_obj
return class_obj
def build_object_property(
self,
ptype: PropertyType,
iri: str,
type_iri: str,
label: str,
comment: str
):
prop_obj = MyProperty(iri, ptype.value)
self.properties[iri] = prop_obj
return prop_obj
def build_datatype_property(
self,
ptype: PropertyType,
iri: str,
type_iri: str,
label: str,
comment: str
):
prop_obj = MyProperty(iri, ptype.value)
self.properties[iri] = prop_obj
return prop_obj
def assign_object_property(self, property_obj, domain_class, range_class):
"""Assign domain and range to an object property."""
if property_obj and domain_class:
property_obj.domain = domain_class
if property_obj and range_class:
property_obj.range = range_class
def assign_datatype_property(self, property_obj, domain_class, range_datatype):
"""Assign domain and range to a datatype property."""
if property_obj and domain_class:
property_obj.domain = domain_class
if property_obj and range_datatype:
property_obj.range = range_datatype
# ... implement other methods ...
Error Handling
The conversion process can raise exceptions:
ClassConversionException: Raised when class conversion failsPropertyConversionException: Raised when property conversion failsDataTypeConversionException: Raised when datatype conversion failsInconsistentClassHierarchyException: Raised when inconsistencies are detected
from cleverrdf_lib.core.exceptions import (
ClassConversionException,
PropertyConversionException
)
try:
visitor.visit_classes_from_the_root(hierarchy)
except ClassConversionException as e:
print(f"Class conversion failed: {e}")
except PropertyConversionException as e:
print(f"Property conversion failed: {e}")
Best Practices
- Implement all methods: Implement all required methods of
ClassHierarchyConverter - Handle None values: Converter methods can return
Noneto skip conversion - Preserve relationships: Ensure parent-child and property relationships are maintained
- Validate consistency: Check for inconsistencies during conversion
- Test thoroughly: Test converters with various ontology structures
Converter Interface
The ClassHierarchyConverter interface requires these methods:
build_class(iri, label, comment): Build a classbuild_object_property(ptype, iri, type_iri, label, comment): Build an object propertybuild_datatype_property(ptype, iri, type_iri, label, comment): Build a datatype propertybuild_datatype(iri, label, comment): Build a datatypeadd_parent_class(child, parent): Add parent-child relationshipadd_child_class(parent, child): Add parent-child relationshipassign_object_property(property, domain, range): Assign domain and range to an object propertyassign_datatype_property(property, domain, range): Assign domain and range to a datatype propertyadd_object_property_domain(domain, property): Add object property to domainadd_object_property_range(range, property): Add object property to rangeadd_datatype_property_domain(domain, property): Add datatype property to domain
Next Steps
- Learn about Class Hierarchy Navigation
- Explore Loading Result API
- Understand Observers API