-
Notifications
You must be signed in to change notification settings - Fork 151
XHTML RDFa as LDH v6 Document Format
The Ontology-Driven UI wiki describes generating XHTML from RDF via SPARQL CONSTRUCT templates. The direction is:
Triplestore → SPARQL CONSTRUCT → XSLT → XHTML output
LDH v6 inverts this. XHTML+RDFa is the canonical representation of dh:Container/dh:Item documents. RDF triples are extracted from the XHTML+RDFa and synced to the triplestore as a queryable index. The direction becomes:
XHTML+RDFa (canonical document body) → RDFa extraction → SPARQL UPDATE → Triplestore
LDH v6 assumes that dh:Container and dh:Item documents contain XHTML+RDFa instead of ldh:XHTML/ldh:Object blocks enumerated via rdf:_1/rdf:_2.
The goal from the wiki article is preserved: semantic structure separated from CSS framework. But instead of SPARQL CONSTRUCT queries defining HTML structure, the XHTML+RDFa document itself IS the structure. XSLT derives CSS classes from @typeof at render time — no @class is ever stored in the document.
A dh:Item or dh:Container document is an XHTML+RDFa file. Its body contains HTML block-level elements (div, article, section, aside, etc.) carrying RDFa attributes. Blocks can be arbitrarily nested — to the extent that HTML block-level elements can be nested — which replaces the current flat rdf:_1/rdf:_2 sequence model.
<body prefix="foaf: http://xmlns.com/foaf/0.1/ schema: https://schema.org/
dct: http://purl.org/dc/terms/">
<article about="https://example.org/docs/report/"
typeof="schema:Report">
<h1 property="schema:name">Annual Report</h1>
<section about="https://example.org/docs/report/#intro"
typeof="schema:SectionOrPage"
property="schema:hasPart">
<h2 property="schema:name">Introduction</h2>
<div about="https://example.org/people/alice/"
typeof="foaf:Person"
property="schema:author">
<span property="foaf:name">Alice Smith</span>
</div>
</section>
</article>
</body>No @class is stored. The author declares @typeof — XSLT derives all CSS classes from it at render time. Framework classes are never persisted.
RDFa subject inheritance means inner elements without @about inherit the nearest ancestor's subject. The extracted triples form a proper RDF graph:
<report/> a schema:Report ; schema:name "Annual Report" ; schema:hasPart <intro/> .
<intro/> a schema:SectionOrPage ; schema:name "Introduction" ; schema:author <alice/> .
<alice/> a foaf:Person ; foaf:name "Alice Smith" .Save (author edits in RDFa-Editor, saves to LDH):
RDFa-Editor browser
└─ SaxonJS runs RDFa2RDFXML-v3.xsl on document DOM
├─ PUT <doc-uri> Content-Type: application/xhtml+xml → stores XHTML+RDFa
└─ PATCH <doc-uri> Content-Type: application/sparql-update
DELETE { ?s ?p ?o } WHERE { ?s ?p ?o FILTER(?s IN (<about1>, <about2>, ...)) }
INSERT DATA { <about1> a schema:Report ; ... . <alice/> foaf:name "Alice" . ... }
Load (browser requests document):
GET <doc-uri> Accept: application/xhtml+xml
└─ LDH returns stored XHTML+RDFa
└─ ldh:FrameworkClass XSLT mode injects @class from @typeof
└─ Browser renders styled page
SPARQL query:
SELECT ?name WHERE { <alice/> foaf:name ?name }
└─ Answered from Fuseki (the synced triple index, not the source of truth)
| Current (LDH v5) | LDH v6 |
|---|---|
ldh:XHTML block type |
Any XHTML element with @property children |
ldh:Object block type |
XHTML element with @about pointing to resource URI |
rdf:_1, rdf:_2, ... flat sequence |
DOM order + arbitrary nesting |
| SPARQL CONSTRUCT templates (wiki approach) | XHTML+RDFa document IS the structure |
| SPARQL UPDATE to reorder blocks | DOM manipulation + re-sync |
Rather than mapping to Bootstrap or Tailwind, LDH v6 defines its own minimal CSS layout system based on CSS flexbox. This eliminates the Bootstrap 2.3.2 dependency and makes the layout system owned by LDH.
ldh.css — a new LDH-native stylesheet:
/* Document structure */
.ldh-doc { display: flex; flex-direction: column; gap: 1rem; }
.ldh-section { display: flex; flex-direction: column; gap: 0.75rem; }
/* Content blocks */
.ldh-block { display: flex; flex-direction: column; padding: 1rem; }
.ldh-block--row { flex-direction: row; gap: 1rem; }
/* Common type-derived classes — grown organically as types are used */
.ldh-person { border: 1px solid #ddd; border-radius: 4px; padding: 1rem; }
.ldh-report { max-width: 60ch; }CSS classes are never authored by users. XSLT derives them from @typeof at render time via the ldh:FrameworkClass mode — the same pattern as the existing ldh:logo mode in resource.xsl:
<!-- LDH native flex profile (default) -->
<xsl:template match="*[@typeof[tokenize(., '\s+') = 'foaf:Person']]"
mode="ldh:FrameworkClass">
<xsl:attribute name="class" select="'ldh-block ldh-person'"/>
</xsl:template>
<xsl:template match="*[@typeof[tokenize(., '\s+') = 'schema:Report']]"
mode="ldh:FrameworkClass">
<xsl:attribute name="class" select="'ldh-block ldh-report'"/>
</xsl:template>
<!-- Default: structural block with no type-specific styling -->
<xsl:template match="*[@about or @typeof]" mode="ldh:FrameworkClass" priority="-1">
<xsl:attribute name="class" select="'ldh-block'"/>
</xsl:template>A third-party theme (Bootstrap 5, Tailwind, etc.) overrides these templates in its own XSLT layer — same mode, different @class values. Switching themes = importing a different XSLT layer. No external data files, no runtime lookups, no Bootstrap dependency in the core.
The ldh: XSLT modes are semantic UI components, not CSS concepts. Each mode describes what a piece of UI is:
Old bs2: mode |
New ldh: mode |
Semantic meaning |
|---|---|---|
bs2:Row |
ldh:Block |
Content block wrapper |
bs2:Header |
ldh:Header |
Block header section |
bs2:PropertyList |
ldh:PropertyList |
Property list display |
bs2:Left / bs2:Right
|
ldh:Navigation |
Sidebar sections |
bs2:ContentBody |
ldh:ContentBody |
Main content area |
bs2:Form |
ldh:Form |
Edit form |
bs2:TypeList |
ldh:TypeList |
Type badge/label list |
These live in a new xsl/ldh/ directory. The Bootstrap 2 XSLT directory (xsl/bootstrap/2.3.2/) and its CSS are removed.
Files: xsl/bootstrap/2.3.2/resource.xsl, imports/default.xsl, layout.xsl
-
Uncomment
@typeofon block wrapper<div>inresource.xsl— three locations (~lines 486-488, 547-549, 634-636). Already computed in$typeof; blocked were intentionally commented pending this work. -
Add
@property/@resource/@datatype/@langto the default property<span>template indefault.xslline ~469. Thexhtml:DefinitionDescription<dd>path already emits these; extend to the<span>fallback. -
Add
@prefixto the#content-bodydiv inlayout.xslbs2:ContentBodytemplate. Needed for CURIE resolution inRDFa2RDFXML-v3.xsl.
Result: existing pages output complete, valid RDFa that round-trips through the extractor.
Files: new XHTMLRDFaReader.java, XHTMLRDFaReaderFactory.java, Application.java
Model XHTMLRDFaReader after HtmlJsonLDReader.java. Uses Saxon HE (already a dependency) to run RDFa2RDFXML-v3.xsl on the incoming application/xhtml+xml body, producing a Jena Model. Register with Jena's RDFParserRegistry for application/xhtml+xml.
When PUT application/xhtml+xml to a dh:Item/dh:Container URI:
- Store the XHTML+RDFa as the document body (
rdf:value rdf:XMLLiteral— same pattern as existingldh:XHTMLblocks) - Populate the named graph with extracted triples
The existing DocumentHierarchyGraphStoreImpl.put(Model) receives the extracted model via JAX-RS content negotiation — no new Java method needed, only the reader registration.
Files: new xsl/ldh/css-mapping.xsl, updated client/block.xsl
Add ldh:FrameworkClass mode as described above. Apply it when rendering XHTML+RDFa document bodies. In client/block.xsl, apply the mode client-side via IXSL after document insertion into the DOM.
Files: RDFa-Editor/src/index.xsl, RDFa-Editor/src/RDFa2RDFXML-v3.xsl
- Add LDH namespace prefixes (
ldh:,dh:,dct:) to$default-prefixesinRDFa2RDFXML-v3.xsl. - Add "Save to LDH" action: serialize document body → PUT XHTML+RDFa → run extraction → PATCH SPARQL UPDATE.
- Add "Load from LDH" action: GET
application/xhtml+xmlfrom document URI → populate editor contenteditable area. - Accept
ldhEndpointanddocumentUrias XSLT parameters (via URL query params).
- Create
xsl/ldh/with framework-agnosticldh:*templates replacing allbs2:*templates - Replace Bootstrap 2 class strings with LDH-native flex classes from
ldh.css - Remove
rdf:_1/rdf:_2sequence handling fromdocument.xslandclient/block.xsl - Remove
ldh:XHTML/ldh:Objectblock type rendering; documents are pure XHTML+RDFa - Remove Bootstrap 2 CSS and JS from the build
- Provide migration XSLT to convert v5 documents to XHTML+RDFa format
| File | Change |
|---|---|
xsl/bootstrap/2.3.2/resource.xsl |
Phase A: uncomment @typeof (3 locations) |
xsl/bootstrap/2.3.2/imports/default.xsl |
Phase A: add @property/@resource/@datatype/@lang to <span> template |
xsl/bootstrap/2.3.2/layout.xsl |
Phase A: add @prefix to #content-body
|
io/XHTMLRDFaReader.java (new)
|
Phase B: Saxon HE + RDFa2RDFXML-v3.xsl reader |
io/XHTMLRDFaReaderFactory.java (new)
|
Phase B: registers application/xhtml+xml
|
Application.java |
Phase B: register XHTMLRDFaReaderFactory
|
xsl/ldh/css-mapping.xsl (new)
|
Phase C: ldh:FrameworkClass mode |
xsl/bootstrap/2.3.2/client/block.xsl |
Phase C: apply ldh:FrameworkClass client-side |
RDFa-Editor/src/index.xsl |
Phase D: LDH save/load actions |
RDFa-Editor/src/RDFa2RDFXML-v3.xsl |
Phase D: LDH namespace prefixes |
src/main/webapp/static/com/atomgraph/linkeddatahub/css/ldh.css (new)
|
Phase E: LDH-native flex layout |
xsl/ldh/ (new directory)
|
Phase E: ldh:Block, ldh:Header, etc. replacing all bs2:* modes |
-
Phase A: Load an existing LDH page, inspect DOM —
div[@about]has@typeof, property elements have@property. RunRDFa2RDFXML-v3.xslon page source and verify round-trip fidelity. -
Phase B:
curl -X PUT -H "Content-Type: application/xhtml+xml" <doc-uri>with a test document. Query Fuseki to verify extracted triples appear in the named graph. -
Phase C: Verify that changing the imported XSLT profile changes rendered
@classvalues without any change to stored XHTML+RDFa. -
Phase D: Open a document in RDFa-Editor, add a nested block with
@about/@typeof, save. Verify XHTML+RDFa updated on server and extracted triples present in Fuseki. -
Phase E: Load a v6 page — no Bootstrap CSS loaded, layout driven entirely by
ldh.css+ flex,@classvalues derived from@typeof.