-
Notifications
You must be signed in to change notification settings - Fork 139
Ontology‐Driven UI: A Framework‐Agnostic Approach to Semantic HTML Generation
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.
Traditional approaches to generating HTML from RDF data typically hardcode presentation logic directly into XSLT stylesheets. This creates several problems:
- Tight Coupling: Presentation logic is mixed with transformation logic
- Maintenance Overhead: Adding new RDF classes requires XSLT modifications
- Framework Lock-in: Switching CSS frameworks requires rewriting XSLT templates
- Poor Reusability: Structure definitions cannot be easily shared or versioned
- Limited Dynamism: Templates cannot adapt based on instance data
Our solution decouples semantic structure definition from presentation by:
- Semantic Templates: Store HTML structure as SPARQL CONSTRUCT queries attached to RDF classes
- Framework-Agnostic Generation: CONSTRUCT queries generate semantic XHTML structures
- CSS Framework Mapping: XSLT template modes map semantic classes to framework-specific classes
- Dynamic Rendering: Templates can conditionally include elements based on instance data
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
-
CONSTRUCT
queries remain unchanged across CSS frameworks - Switch frameworks by changing XSLT template modes
- Support multiple frameworks simultaneously
- HTML structure reflects domain semantics, not presentation concerns
- CSS classes describe meaning, not appearance
- Clear separation between content and presentation
- Templates can include/exclude elements based on data availability
- Conditional rendering logic in SPARQL
- Instance-specific customization
- Templates are queryable RDF resources
- Version control for structure definitions
- Template inheritance for class hierarchies
- Generated structures can be cached per instance
- XSLT mapping is efficient during rendering
- No runtime CSS framework switching overhead
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)
}
"""
] .
-
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
-
rdf:_1
,rdf:_2
, etc. preserve element ordering - Enable nested structures with proper DOM hierarchy
- Natural representation of parent-child relationships
-
$this
variable represents the current instance being rendered - OPTIONAL patterns handle missing data gracefully
- BIND expressions generate dynamic values (IDs, computed content)
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 .
}
""" .
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)
}
}
"""
] .
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>
<!-- 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 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 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>
@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" .
When rendering :JohnDoe
, the system:
-
Looks up template: Find
ldh:template
for:Person
class -
Substitutes variables: Replace
$this
with:JohnDoe
- Executes CONSTRUCT: Generate XHTML structure RDF
- Applies XSLT: Convert to HTML with framework-specific classes
[] 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"
]
] .
<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>
<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>
<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>
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
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 .
}
"""
] .
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 { /* ... */ }
"""
] .
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.