Files
CoreRasurae f14c0bad8c
CI / lint (pull_request) Successful in 1m35s
CI / typecheck (pull_request) Successful in 1m38s
CI / behave (3.11) (pull_request) Successful in 1m48s
CI / behave (3.12) (pull_request) Successful in 1m47s
CI / behave (3.13) (pull_request) Successful in 1m43s
CI / build (pull_request) Successful in 1m33s
CI / lint (push) Successful in 1m31s
CI / typecheck (push) Successful in 1m29s
CI / behave (3.11) (push) Successful in 1m37s
CI / behave (3.12) (push) Successful in 1m44s
CI / behave (3.13) (push) Successful in 1m36s
CI / build (push) Successful in 1m31s
docs: Initial documentation of CleverRDFLib
ISSUES CLOSED: #2
2025-12-22 10:50:03 +00:00

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 operations
  • ClassHierarchyConverterVisitor: 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:

  1. Visitor creation: Create a ClassHierarchyConverterVisitor with your converter
  2. Traversal: Call visit_classes_from_the_root() to start conversion
  3. Class conversion: Classes are converted starting from root classes
  4. Relationship building: Parent-child relationships are established
  5. Property conversion: Properties are converted and linked to classes
  6. Datatype conversion: Datatypes are converted and linked to properties

Conversion Order

The visitor ensures proper conversion order:

  1. Root classes first: Root classes are visited first
  2. Parent-child traversal: Parents and children are visited recursively
  3. Property linking: Properties are linked after classes are converted
  4. 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 fails
  • PropertyConversionException: Raised when property conversion fails
  • DataTypeConversionException: Raised when datatype conversion fails
  • InconsistentClassHierarchyException: 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

  1. Implement all methods: Implement all required methods of ClassHierarchyConverter
  2. Handle None values: Converter methods can return None to skip conversion
  3. Preserve relationships: Ensure parent-child and property relationships are maintained
  4. Validate consistency: Check for inconsistencies during conversion
  5. Test thoroughly: Test converters with various ontology structures

Converter Interface

The ClassHierarchyConverter interface requires these methods:

  • build_class(iri, label, comment): Build a class
  • build_object_property(ptype, iri, type_iri, label, comment): Build an object property
  • build_datatype_property(ptype, iri, type_iri, label, comment): Build a datatype property
  • build_datatype(iri, label, comment): Build a datatype
  • add_parent_class(child, parent): Add parent-child relationship
  • add_child_class(parent, child): Add parent-child relationship
  • assign_object_property(property, domain, range): Assign domain and range to an object property
  • assign_datatype_property(property, domain, range): Assign domain and range to a datatype property
  • add_object_property_domain(domain, property): Add object property to domain
  • add_object_property_range(range, property): Add object property to range
  • add_datatype_property_domain(domain, property): Add datatype property to domain

Next Steps