Skip to content

Ontology‐Driven UI: A Framework‐Agnostic Approach to Semantic HTML Generation

Martynas Jusevičius edited this page Jul 4, 2025 · 2 revisions

Ontology-Driven UI: A Framework-Agnostic Approach to Semantic HTML Generation

Abstract

This document describes a novel approach to generating HTML user interfaces from RDF data using ontology-driven templates. The system separates semantic structure definition from CSS framework implementation, enabling framework-agnostic UI generation with dynamic CSS class mapping.

Rationale

Traditional approaches to generating HTML from RDF data typically hardcode presentation logic directly into XSLT stylesheets. This creates several problems:

  1. Tight Coupling: Presentation logic is mixed with transformation logic
  2. Maintenance Overhead: Adding new RDF classes requires XSLT modifications
  3. Framework Lock-in: Switching CSS frameworks requires rewriting XSLT templates
  4. Poor Reusability: Structure definitions cannot be easily shared or versioned
  5. Limited Dynamism: Templates cannot adapt based on instance data

Approach Overview

Our solution decouples semantic structure definition from presentation by:

  1. Semantic Templates: Store HTML structure as SPARQL CONSTRUCT queries attached to RDF classes
  2. Framework-Agnostic Generation: CONSTRUCT queries generate semantic XHTML structures
  3. CSS Framework Mapping: XSLT template modes map semantic classes to framework-specific classes
  4. Dynamic Rendering: Templates can conditionally include elements based on instance data

Architecture

graph TD
    A[RDF Instance Data] --> B[Class Template Lookup]
    B --> C[SPARQL CONSTRUCT Query]
    C --> D[Generated XHTML Structure]
    D --> E[Generic XSLT Processor]
    E --> F[CSS Framework Mapping]
    F --> G[Final HTML Output]
    
    H[CSS Framework Config] --> F
    I[Template Mode Selection] --> F
Loading

Benefits

1. Framework Independence

  • CONSTRUCT queries remain unchanged across CSS frameworks
  • Switch frameworks by changing XSLT template modes
  • Support multiple frameworks simultaneously

2. Semantic Clarity

  • HTML structure reflects domain semantics, not presentation concerns
  • CSS classes describe meaning, not appearance
  • Clear separation between content and presentation

3. Dynamic Adaptation

  • Templates can include/exclude elements based on data availability
  • Conditional rendering logic in SPARQL
  • Instance-specific customization

4. Maintainability

  • Templates are queryable RDF resources
  • Version control for structure definitions
  • Template inheritance for class hierarchies

5. Performance

  • Generated structures can be cached per instance
  • XSLT mapping is efficient during rendering
  • No runtime CSS framework switching overhead

CONSTRUCT Query Templates

Template Definition

Templates are defined as SPARQL CONSTRUCT queries attached to RDF classes using the ldh:template property:

@prefix ldh: <https://w3id.org/atomgraph/linkeddatahub#> .
@prefix xhtml: <http://www.w3.org/1999/xhtml#> .
@prefix sp: <http://spinrdf.org/sp#> .

:Person ldh:template [
    a sp:Construct ;
    sp:text """
        CONSTRUCT {
            [] a xhtml:div ;
               xhtml:about $this ;
               xhtml:class "person-card" ;
               xhtml:id ?personId ;
               rdf:_1 [
                   a xhtml:div ;
                   xhtml:class "person-header" ;
                   rdf:_1 [
                       a xhtml:div ;
                       xhtml:class "person-name" ;
                       rdf:value ?name
                   ] ;
                   rdf:_2 [
                       a xhtml:div ;
                       xhtml:class "person-title" ;
                       rdf:value ?title
                   ]
               ] ;
               rdf:_2 [
                   a xhtml:div ;
                   xhtml:class "person-contact" ;
                   rdf:_1 [
                       a xhtml:div ;
                       xhtml:class "person-email" ;
                       rdf:value ?email
                   ] ;
                   rdf:_2 [
                       a xhtml:div ;
                       xhtml:class "person-phone" ;
                       rdf:value ?phone
                   ]
               ]
        }
        WHERE {
            $this a :Person ;
                  :name ?name ;
                  :email ?email .
            
            OPTIONAL { $this :title ?title }
            OPTIONAL { $this :phone ?phone }
            
            BIND(CONCAT("person-", STRAFTER(STR($this), "#")) AS ?personId)
        }
    """
] .

Key Components

1. Structure Elements

  • xhtml:div, xhtml:span, xhtml:article etc. represent HTML element types
  • xhtml:about links the structure to the RDF resource (like RDFa @about)
  • xhtml:class defines semantic CSS classes
  • rdf:value contains the text content for elements

2. Sequence Properties

  • rdf:_1, rdf:_2, etc. preserve element ordering
  • Enable nested structures with proper DOM hierarchy
  • Natural representation of parent-child relationships

3. Dynamic Content

  • $this variable represents the current instance being rendered
  • OPTIONAL patterns handle missing data gracefully
  • BIND expressions generate dynamic values (IDs, computed content)

Multiple Context Templates

Different templates can be defined for different presentation contexts:

:Person ldh:template :PersonCardTemplate ;        # Default/card view
        ldh:listTemplate :PersonListTemplate ;    # List item view
        ldh:formTemplate :PersonFormTemplate .    # Form view

:PersonListTemplate a sp:Construct ;
    sp:text """
        CONSTRUCT {
            [] a xhtml:div ;
               xhtml:about $this ;
               xhtml:class "person-list-item" ;
               rdf:_1 [
                   a xhtml:span ;
                   xhtml:class "person-name" ;
                   rdf:value ?name
               ] ;
               rdf:_2 [
                   a xhtml:span ;
                   xhtml:class "person-email" ;
                   rdf:value ?email
               ]
        }
        WHERE { 
            $this a :Person ; 
                  :name ?name ; 
                  :email ?email . 
        }
    """ .

Conditional Structure Generation

Templates can include conditional logic to adapt structure based on data:

:Person ldh:template [
    a sp:Construct ;
    sp:text """
        CONSTRUCT {
            [] a xhtml:div ;
               xhtml:about $this ;
               xhtml:class "person-card" ;
               rdf:_1 ?nameSection ;
               rdf:_2 ?contactSection ;
               rdf:_3 ?managerSection
        }
        WHERE {
            $this a :Person ;
                  :name ?name ;
                  :email ?email .
            
            # Name section (always present)
            BIND([
                a xhtml:div ;
                xhtml:class "person-name" ;
                rdf:value ?name
            ] AS ?nameSection)
            
            # Contact section (always present)
            BIND([
                a xhtml:div ;
                xhtml:class "person-contact" ;
                rdf:_1 [
                    a xhtml:div ;
                    xhtml:class "person-email" ;
                    rdf:value ?email
                ]
            ] AS ?contactSection)
            
            # Manager section (only if manager exists)
            OPTIONAL {
                $this :manager ?manager .
                ?manager :name ?managerName .
                BIND([
                    a xhtml:div ;
                    xhtml:class "person-manager" ;
                    rdf:_1 [
                        a xhtml:div ;
                        xhtml:class "manager-label" ;
                        rdf:value "Manager:"
                    ] ;
                    rdf:_2 [
                        a xhtml:div ;
                        xhtml:class "manager-name" ;
                        rdf:value ?managerName
                    ]
                ] AS ?managerSection)
            }
        }
    """
] .

XSLT Template Processing

Generic Element Processing

The XSLT processor uses generic templates that work with any generated XHTML structure:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xhtml="http://www.w3.org/1999/xhtml#"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">

<!-- Framework selection parameter -->
<xsl:param name="css-framework" select="'bootstrap23'"/>

<!-- Generic div element processing -->
<xsl:template match="*[rdf:type/@rdf:resource='http://www.w3.org/1999/xhtml#div']">
    <div>
        <xsl:if test="xhtml:class">
            <xsl:attribute name="class">
                <xsl:apply-templates select="xhtml:class" mode="{$css-framework}"/>
            </xsl:attribute>
        </xsl:if>
        <xsl:if test="xhtml:id">
            <xsl:attribute name="id">
                <xsl:value-of select="xhtml:id"/>
            </xsl:attribute>
        </xsl:if>
        
        <!-- Process children in order -->
        <xsl:for-each select="rdf:_*">
            <xsl:sort select="substring-after(local-name(), '_')" data-type="number"/>
            <xsl:apply-templates select="."/>
        </xsl:for-each>
        
        <!-- Process text content -->
        <xsl:value-of select="rdf:value"/>
    </div>
</xsl:template>

<!-- Generic span element processing -->
<xsl:template match="*[rdf:type/@rdf:resource='http://www.w3.org/1999/xhtml#span']">
    <span>
        <xsl:if test="xhtml:class">
            <xsl:attribute name="class">
                <xsl:apply-templates select="xhtml:class" mode="{$css-framework}"/>
            </xsl:attribute>
        </xsl:if>
        
        <xsl:for-each select="rdf:_*">
            <xsl:sort select="substring-after(local-name(), '_')" data-type="number"/>
            <xsl:apply-templates select="."/>
        </xsl:for-each>
        
        <xsl:value-of select="rdf:value"/>
    </span>
</xsl:template>

<!-- Generic processing for sequence properties -->
<xsl:template match="rdf:_*">
    <xsl:apply-templates select="*"/>
</xsl:template>
</xsl:stylesheet>

CSS Framework Mapping Templates

Bootstrap 2.3.2 Mappings

<!-- Bootstrap 2.3.2 CSS mapping templates -->
<xsl:template match="xhtml:class[. = 'person-card']" mode="bootstrap23">
    <xsl:text>person-card well well-large</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-header']" mode="bootstrap23">
    <xsl:text>person-header well</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-contact']" mode="bootstrap23">
    <xsl:text>person-contact</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-name']" mode="bootstrap23">
    <xsl:text>person-name lead</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-title']" mode="bootstrap23">
    <xsl:text>person-title muted</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-email']" mode="bootstrap23">
    <xsl:text>person-email</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-phone']" mode="bootstrap23">
    <xsl:text>person-phone</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-list-item']" mode="bootstrap23">
    <xsl:text>person-list-item</xsl:text>
</xsl:template>

<!-- Default fallback for unmapped classes -->
<xsl:template match="xhtml:class" mode="bootstrap23">
    <xsl:value-of select="."/>
</xsl:template>

Bootstrap 5 Mappings

<!-- Bootstrap 5 CSS mapping templates -->
<xsl:template match="xhtml:class[. = 'person-card']" mode="bootstrap5">
    <xsl:text>person-card card</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-header']" mode="bootstrap5">
    <xsl:text>person-header card-header</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-contact']" mode="bootstrap5">
    <xsl:text>person-contact card-body</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-name']" mode="bootstrap5">
    <xsl:text>person-name card-title</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-title']" mode="bootstrap5">
    <xsl:text>person-title text-muted</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-email']" mode="bootstrap5">
    <xsl:text>person-email</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-phone']" mode="bootstrap5">
    <xsl:text>person-phone</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-list-item']" mode="bootstrap5">
    <xsl:text>person-list-item list-group-item</xsl:text>
</xsl:template>

<!-- Default fallback for unmapped classes -->
<xsl:template match="xhtml:class" mode="bootstrap5">
    <xsl:value-of select="."/>
</xsl:template>

Tailwind CSS Mappings

<!-- Tailwind CSS mapping templates -->
<xsl:template match="xhtml:class[. = 'person-card']" mode="tailwind">
    <xsl:text>person-card bg-white shadow-lg rounded-lg p-6 mb-4</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-header']" mode="tailwind">
    <xsl:text>person-header border-b border-gray-200 pb-4 mb-4</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-contact']" mode="tailwind">
    <xsl:text>person-contact space-y-2</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-name']" mode="tailwind">
    <xsl:text>person-name text-xl font-semibold text-gray-900</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-title']" mode="tailwind">
    <xsl:text>person-title text-gray-600</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-email']" mode="tailwind">
    <xsl:text>person-email text-blue-600 hover:text-blue-800</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-phone']" mode="tailwind">
    <xsl:text>person-phone text-gray-700</xsl:text>
</xsl:template>

<xsl:template match="xhtml:class[. = 'person-list-item']" mode="tailwind">
    <xsl:text>person-list-item flex items-center py-2 px-4 border-b border-gray-100</xsl:text>
</xsl:template>

<!-- Default fallback for unmapped classes -->
<xsl:template match="xhtml:class" mode="tailwind">
    <xsl:value-of select="."/>
</xsl:template>

Complete Implementation Example

1. Instance Data

@prefix : <http://example.org/> .

:JohnDoe a :Person ;
    :name "John Doe" ;
    :title "Senior Developer" ;
    :email "[email protected]" ;
    :phone "+1-555-0123" ;
    :manager :JaneSmith .

:JaneSmith a :Person ;
    :name "Jane Smith" ;
    :title "Engineering Manager" .

2. Template Execution

When rendering :JohnDoe, the system:

  1. Looks up template: Find ldh:template for :Person class
  2. Substitutes variables: Replace $this with :JohnDoe
  3. Executes CONSTRUCT: Generate XHTML structure RDF
  4. Applies XSLT: Convert to HTML with framework-specific classes

3. Generated XHTML Structure (RDF)

[] a xhtml:div ;
   xhtml:about :JohnDoe ;
   xhtml:class "person-card" ;
   xhtml:id "person-JohnDoe" ;
   rdf:_1 [
       a xhtml:div ;
       xhtml:class "person-header" ;
       rdf:_1 [
           a xhtml:div ;
           xhtml:class "person-name" ;
           rdf:value "John Doe"
       ] ;
       rdf:_2 [
           a xhtml:div ;
           xhtml:class "person-title" ;
           rdf:value "Senior Developer"
       ]
   ] ;
   rdf:_2 [
       a xhtml:div ;
       xhtml:class "person-contact" ;
       rdf:_1 [
           a xhtml:div ;
           xhtml:class "person-email" ;
           rdf:value "[email protected]"
       ] ;
       rdf:_2 [
           a xhtml:div ;
           xhtml:class "person-phone" ;
           rdf:value "+1-555-0123"
       ]
   ] .

4. Final HTML Output

Bootstrap 2.3.2 Output

<div class="person-card well well-large" id="person-JohnDoe">
    <div class="person-header well">
        <div class="person-name lead">John Doe</div>
        <div class="person-title muted">Senior Developer</div>
    </div>
    <div class="person-contact">
        <div class="person-email">[email protected]</div>
        <div class="person-phone">+1-555-0123</div>
    </div>
</div>

Bootstrap 5 Output

<div class="person-card card" id="person-JohnDoe">
    <div class="person-header card-header">
        <div class="person-name card-title">John Doe</div>
        <div class="person-title text-muted">Senior Developer</div>
    </div>
    <div class="person-contact card-body">
        <div class="person-email">[email protected]</div>
        <div class="person-phone">+1-555-0123</div>
    </div>
</div>

Tailwind CSS Output

<div class="person-card bg-white shadow-lg rounded-lg p-6 mb-4" id="person-JohnDoe">
    <div class="person-header border-b border-gray-200 pb-4 mb-4">
        <div class="person-name text-xl font-semibold text-gray-900">John Doe</div>
        <div class="person-title text-gray-600">Senior Developer</div>
    </div>
    <div class="person-contact space-y-2">
        <div class="person-email text-blue-600 hover:text-blue-800">[email protected]</div>
        <div class="person-phone text-gray-700">+1-555-0123</div>
    </div>
</div>

Framework Switching

Switching between CSS frameworks requires only changing the XSLT parameter:

# Bootstrap 2.3.2
saxon -s:data.rdf -xsl:templates.xsl -o:output.html css-framework=bootstrap23

# Bootstrap 5
saxon -s:data.rdf -xsl:templates.xsl -o:output.html css-framework=bootstrap5

# Tailwind CSS
saxon -s:data.rdf -xsl:templates.xsl -o:output.html css-framework=tailwind

Advanced Features

Template Inheritance

Subclasses can extend parent templates:

:Employee rdfs:subClassOf :Person ;
    ldh:template [
        a sp:Construct ;
        sp:text """
            CONSTRUCT {
                ?parentStructure rdf:_3 [
                    a xhtml:div ;
                    xhtml:class "employee-details" ;
                    rdf:_1 [
                        a xhtml:div ;
                        xhtml:class "employee-id" ;
                        rdf:value ?employeeId
                    ] ;
                    rdf:_2 [
                        a xhtml:div ;
                        xhtml:class "department" ;
                        rdf:value ?department
                    ]
                ]
            }
            WHERE {
                # First, generate parent structure
                SERVICE <parent-template> {
                    ?parentStructure ?p ?o .
                }
                
                # Then add employee-specific data
                $this a :Employee ;
                      :employeeId ?employeeId ;
                      :department ?department .
            }
        """
    ] .

Template Parameters

Templates can accept parameters for customization:

:Person ldh:template [
    a sp:Construct ;
    ldh:parameter [
        ldh:name "showManager" ;
        ldh:default "true" ;
        ldh:type xsd:boolean
    ] ;
    ldh:parameter [
        ldh:name "context" ;
        ldh:default "card" ;
        ldh:type xsd:string
    ] ;
    sp:text """
        CONSTRUCT {
            # Template logic with parameter conditions
            # IF (?showManager = "true") THEN include manager section
            # IF (?context = "list") THEN use compact layout
        }
        WHERE { /* ... */ }
    """
] .

Conclusion

This ontology-driven approach to UI generation provides a powerful abstraction that separates semantic structure from presentation concerns. By storing HTML structure templates as SPARQL CONSTRUCT queries and using XSLT template modes for CSS framework mapping, we achieve:

  • Framework independence with easy switching between CSS frameworks
  • Semantic clarity with meaningful HTML structure
  • Dynamic adaptation based on instance data
  • Maintainable architecture with clear separation of concerns
  • Reusable templates that can be shared and versioned

The system scales well for complex applications and provides the flexibility needed for modern web development while maintaining the semantic richness of RDF data.