Stylesheets
Built-in XSLT, CSS and JavaScript assets, and how to override them
XSLT
XSLT is a functional, Turing-complete XML transformation language.
LinkedDataHub's XSLT 3.0 stylesheets work by transforming the RDF/XML response body from the underlying HTTP API. Additional metadata from RDF vocabularies is used to improve the user experience.
Plain RDF/XML
RDF/XML is an important RDF syntax which functions as a bridge to the XML technology stack. The stylesheets use Jena's "plain" RDF/XML output which groups statements by subject and does not nest resource descriptions. This allows for predictable XPath patterns:
| XPath | Represents |
|---|---|
/rdf:RDF |
The RDF graph |
/rdf:RDF/rdf:Description or /rdf:RDF/*[*][@rdf:about] | /rdf:RDF/*[*][@rdf:nodeID] |
Resource description which contains properties |
/rdf:RDF/rdf:Description/@rdf:about |
Subject resource URI |
/rdf:RDF/rdf:Description/@rdf:nodeID |
Subject blank node ID |
/rdf:RDF/rdf:Description/* |
Predicate (e.g. rdf:type) whose URI is concat(namespace-uri(), local-name()) |
/rdf:RDF/rdf:Description/*/@rdf:resource |
Object resource |
/rdf:RDF/rdf:Description/*/@rdf:nodeID |
Object blank node ID |
/rdf:RDF/rdf:Description/*/text() |
Literal value |
The Northwind Chai product in that shape — the input that the templates match:
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:schema="https://schema.org/">
<rdf:Description rdf:about="https://localhost:4443/products/1/#this">
<rdf:type rdf:resource="https://schema.org/Product"/>
<schema:name>Chai</schema:name>
<schema:category rdf:resource="https://localhost:4443/categories/1/#this"/>
<schema:provider rdf:resource="https://localhost:4443/suppliers/1/#this"/>
</rdf:Description>
</rdf:RDF>
Stylesheet structure
XSLT stylesheet components used by LinkedDataHub:
- Includes
<xsl:include>is used to include one stylesheet into another. The inclusion mechanism is specified in 3.10.2 Stylesheet Inclusion of the XSLT 3.0 specification. The templates from the included stylesheets have the same priority as those of the including stylesheet.- Imports
<xsl:import>is used to import one stylesheet into another. The import mechanism is specified in 3.10.3 Stylesheet Import of the XSLT 3.0 specification. The templates from the imported stylesheets have lower priority than those of the importing stylesheet.- Parameters
- XSD-typed global parameters passed to the stylesheet
- Keys
- Lookup keys
- Templates
- Template rules for XML node processing
One XSLT stylesheet can be specified per dataspace. In order to reuse LinkedDataHub's built-in templates, it should import the system stylesheet layout.xsl and only override the necessary templates. That is not a requirement, however; a stylesheet can also use its own independent transformation logic.
If there is no stylesheet specified for the dataspace, the system stylesheet is used. It defines the overall layout and imports resource-level and container-specific stylesheets, as well as per-vocabulary stylesheets.
Note that LinkedDataHub itself imports stylesheets from Web-Client, which uses the same template modes but produces a much simpler layout.
The same XSLT 3.0 stylesheets run in two environments. On the server they are executed by Saxon-HE to produce the initial HTML response. In the browser they are executed by Saxon-JS 3, which provides IXSL (Interactive XSLT extensions) for reading and manipulating the browser DOM.
There is a dedicated client-side stylesheet client.xsl that drives the browser. After the server-rendered page loads, its main template renders the layout client-side — the left sidebar, navigation, document
and tab panes, content blocks, forms and modal dialogs are injected into the DOM using
IXSL (e.g. ixsl:append-content). It also handles all subsequent navigation, so following links and switching documents
re-renders in place without a full page reload. It imports and reuses the same document-,
resource- and property-level templates as the server-side system stylesheet, but avoids
loading per-vocabulary stylesheets in order to improve page load time. Templates of
the client-side stylesheet can also be overridden.
Namespaces
| Prefix | Namespace | Vocabulary | Description |
|---|---|---|---|
rdf: |
http://www.w3.org/1999/02/22-rdf-syntax-ns# |
The RDF Concepts Vocabulary | Namespace for the RDF/XML elements, mostly used for matching input data |
srx: |
http://www.w3.org/2005/sparql-results# |
SPARQL Query Results XML Format | Namespace for the SPARQL Query Results XML elements, mostly used for matching input data |
xsl: |
http://www.w3.org/1999/XSL/Transform |
XSL Transformations (XSLT) 3.0 | Namespace for the XSLT stylesheet elements |
ixsl: |
http://saxonica.com/ns/interactiveXSLT |
Saxon Interactive XSLT extensions | Namespace for the Interactive XSL extensions |
ldh: |
https://w3id.org/atomgraph/linkeddatahub# |
LinkedDataHub vocabulary | LinkedDataHub concepts, also used for LinkedDataHub's own component template modes (named after the design system's components) |
xhtml: |
http://www.w3.org/1999/xhtml |
XHTML 1.0 | XHTML namespace, also used for generic (X)HTML templates |
ac: |
https://w3id.org/atomgraph/client# |
Web-Client vocabulary | Client-side concepts |
lds: |
https://w3id.org/atomgraph/linkeddatahub/dataspaces# |
LinkedDataHub dataspace ontology | LinkedDataHub dataspace concepts |
lacl: |
https://w3id.org/atomgraph/linkeddatahub/admin/acl# |
LinkedDataHub ACL ontology | ACL concepts |
Templates
XSLT template components:
- Match
- XPath-based match pattern which either does or does not match an XML node
- Mode
- Groups templates and distinguishes them from other groups that share the same match patterns (e.g. different layout modes)
- Parameters
- XSD-typed parameters passed to the template invocation
- Body
- Contains the XML output nodes as well as XSLT processing instructions
XSLT processing starts at the root of the RDF/XML document and produces HTML elements by applying templates to all of the RDF/XML nodes while moving down the XML tree. In other words, it starts at the graph level, moves down to resource description elements, then to property elements, and ends with identifier attributes and literal text nodes.
Templates are applied (invoked) using <xsl:apply-templates>. Mode can be specified, e.g. <xsl:apply-templates mode="ac:BlockHeader">. To stay in the current mode without explicitly specifying it, use <xsl:apply-templates mode="#current">. <xsl:with-param> is used to supply parameters.
LinkedDataHub provides the following default template modes, which are used to render the layout modes:
- Graph-level modes that apply to
rdf:RDF- default mode renders full resource descriptions
ac:Listrenders a list of resourcesac:ResultsTablerenders a table with resources as rows and properties as columnsac:Gridrenders a gallery of thumbnailsac:Maprenders resources with coordinates on a mapac:Graphrenders the resources as a node-and-edge graphac:ResourceFormrenders an RDF/POST form for creating new resources or editing an existing one
- Resource-level template modes that apply to
rdf:Description- default mode renders one full resource description (by default header and property list)
ac:BlockHeaderrenders resource header (by default with type information)ac:PropertyEditorrenders definition list with property names and values (by default grouped by resource types)
When adding new user-defined modes, it is recommended to choose a new namespace for them as well as a user-defined prefix.
An example of a template that matches rdf:Description:
<xsl:template match="*[*][@rdf:about] | *[*][@rdf:nodeID]">
<xsl:param name="id" as="xs:string?"/>
<xsl:param name="class" as="xs:string?"/>
<div>
<xsl:if test="$id">
<xsl:attribute name="id" select="$id"/>
</xsl:if>
<xsl:if test="$class">
<xsl:attribute name="class" select="$class"/>
</xsl:if>
<xsl:apply-templates select="." mode="ac:BlockHeader"/>
<xsl:apply-templates select="." mode="ac:PropertyEditor"/>
</div>
</xsl:template>
Applied to the Chai description above, it produces markup of this shape (abridged):
<div about="https://localhost:4443/products/1/#this" typeof="https://schema.org/Product">
<h2>Chai</h2>
<dl>
<dt>Name</dt>
<dd>Chai</dd>
<dt>Category</dt>
<dd><a href="https://localhost:4443/categories/1/#this">Beverages</a></dd>
<dt>Provider</dt>
<dd><a href="https://localhost:4443/suppliers/1/#this">Exotic Liquids</a></dd>
</dl>
</div>
Live: /products/1/ — the same description, rendered by the full stylesheet
ldh:ContentList mode renders the content specified by the rdf:_1, rdf:_2, … values of the current document.
There are a few special template modes such as ac:label and ac:description, together with the related ac:label() and ac:description() functions, which are
used not to render layout but to extract metadata from resource descriptions. They
can be used to retrieve a resource label and description no matter which RDF
vocabularies are used in the data. They do so by invoking templates of the respective
mode from vocabulary-specific stylesheets.
Overriding templates
Templates are overridden by redefining them in the importing stylesheet and providing the same or more specific match pattern and the same mode. The XSLT specification defines exactly how template priorities are determined in 6.4 Conflict Resolution for Template Rules.
The overriding template can then get the output of the overridden template by invoking
either <xsl:apply-imports> or <xsl:next-match>. Read more in 6.7 Overriding Template Rules.
Always override the most specific template, i.e. if you want to change how a property is rendered, do not override the template for the resource description — override only the one for the property.
Keys
Keys are a lookup mechanism. They are defined on the stylesheet level using <xsl:key> and invoked using the
key() function. For example:
<xsl:key name="resources" match="*[*][@rdf:about] | *[*][@rdf:nodeID]" use="@rdf:about | @rdf:nodeID"/>
<xsl:template match="*">
<xsl:for-each select="key('resources', ldh:base-uri(.))">
<xsl:value-of select="ac:label(.)"/>
</xsl:for-each>
</xsl:template>
The key definition matches rdf:Description elements and uses their identifiers (URI or blank node ID). The template then looks
up the RDF description of the current resource, i.e. the resource with URI that equals ldh:base-uri(.) which is the absolute URI of the current document, and outputs its label.
Loading data
The stylesheet processes one main RDF/XML document at a time, supplied by LinkedDataHub's
HTML writer. However, it is possible to load additional XML documents over HTTP
using the document() XSLT function. To avoid XSLT errors on any possible error responses, it is advisable
to do a conditional check using the doc-available() function before doing the actual document() call.
For example, instead of hardcoding the title of this document as Stylesheets, you can use the following code to load it and output it on the fly:
<xsl:value-of select="key('resources', 'https://docs.linkeddatahub.com/reference/stylesheets/', document('https://docs.linkeddatahub.com/reference/stylesheets/'))"/>
In case this document changes its title, all such references would automatically render the updated title. On the other hand, it incurs the overhead of making an HTTP request.
LinkedDataHub's default stylesheets use this feature extensively. In fact, one HTML page is rendered from a dozen RDF/XML documents.
Built-in ontologies, as well as some other system and well-known ontologies, have a local copy in
each LinkedDataHub instance. As a result, retrieving their descriptions by dereferencing
their URIs using document() does not incur an HTTP request and is much faster. The URI-to-file mapping
is defined as Jena's location mapping and can be found in
location-mapping.ttl and prefix-mapping.ttl.
Client-side stylesheets use <ixsl:schedule-action> (deprecated) and <ixsl:promise> to load XML documents asynchronously. The metadata required to render forms — constructors,
SHACL shapes, and property and object descriptions — is loaded client-side through
chains of <ixsl:promise>, so the browser fetches it on demand rather than the server fetching everything up
front.
Parameters
Both global (i.e. stylesheet-level) and template parameters are declared using <xsl:param>. LinkedDataHub sets the following global parameters:
| Parameter | Type | Description |
|---|---|---|
$lds:Context |
document-node() |
RDF/XML description of the configured dataspaces; the current dataspace is looked up in it by origin |
$lds:origin |
xs:anyURI |
Origin of the application shell that serves the static assets (the pane-scoped counterpart
is the lds:origin() function) |
$foaf:Agent |
document-node()? |
RDF/XML metadata of the authenticated agent (if any) |
$acl:agent |
xs:anyURI? |
URI of the authenticated agent |
$ldh:requestUri |
xs:anyURI |
Full request URI including query string |
$ldh:httpHeaders |
map(xs:string, xs:string*) |
HTTP response headers by name — Content-Language, Link, Memento-Datetime etc. are read from it |
$ldh:ajaxRendering |
xs:boolean |
Whether client-side (Saxon-JS) rendering is enabled |
$ldh:renderSystemResources |
xs:boolean |
Whether system resources are included in the rendering |
$ac:contextUri |
xs:anyURI |
Root deployment URI, against which system endpoints such as oauth2/* are resolved |
$ac:uri |
xs:anyURI? |
URI of the external resource when the request goes through the Linked Data proxy; absent for local documents (prefer the ac:uri() function) |
Former parameters $ac:mode, $acl:mode, $sd:endpoint, $ac:langs and $lds:Dataspace have been
replaced by the functions ac:mode(), acl:mode(), sd:endpoint(), ac:langs() and lds:dataspace() —
their values can change per pane or per response, which a global parameter cannot
express.
Functions
LinkedDataHub and Web-Client provide XSLT functions available to all stylesheets.
URI utility functions
| Function | Returns | Description |
|---|---|---|
ac:absolute-path($href as xs:anyURI) |
xs:anyURI |
Strips query string and fragment from a URI, returning the path-only form |
ac:build-uri( |
xs:anyURI? |
Appends a percent-encoded query string built from the parameter map to $path |
ac:document-uri($uri as item()) |
xs:anyURI |
Strips the fragment identifier from a URI, making it suitable for use with document() |
ac:fragment-id($uri as xs:anyURI) |
xs:string? |
Extracts the fragment identifier (the part after #) |
ac:uri() |
xs:anyURI? |
URI of the external resource being viewed through the Linked Data proxy; absent for local documents. Works in both environments, unlike the server-side $ac:uri parameter it wraps |
lds:origin() |
xs:anyURI |
Returns the origin (scheme, host and port) of the current dataspace, e.g. https://localhost:4443/. Used to build absolute URIs for static resources and same-site requests |
lds:base() |
xs:anyURI |
Returns the base URI of the current dataspace |
ldh:request-uri() |
xs:anyURI |
Returns the full request URI including query string (server-side only) |
ldh:base-uri($node as node()) |
xs:anyURI |
Returns the base URI of an XML node; wraps the built-in base-uri() |
ldh:href($uri as xs:anyURI?) |
xs:anyURI |
Resolves a URI to a local href, proxying external URIs through the LinkedDataHub proxy.
Overloads accept $query-params as map(xs:string, xs:string*) and $fragment as xs:string? |
ldh:parse-href($href as xs:anyURI) |
map(xs:string, item()?) |
Inverse of ldh:href() — parses a local href back into its target URI, query parameters and fragment. Shared
by the client-side navigation handlers |
ldh:query-params() |
map(xs:string, xs:string*) |
Parses the current request URI's query string into a map of parameter names to values |
ldh:build-query($mode as xs:anyURI*) |
map(xs:string, xs:string*) |
Builds the mode query parameter map that selects the given layout mode(s) |
ldh:parse-query-params($query-string as xs:string) |
map(xs:string, xs:string*) |
Parses a URL query string into a map of key to value(s) |
ldh:url-decode($encoded-string as xs:string) |
xs:string |
Percent-decodes a URL-encoded string |
RDF metadata functions
| Function | Returns | Description |
|---|---|---|
ac:label($resource as element()) |
xs:string? |
Extracts a human-readable label from an RDF resource description, trying multiple vocabulary properties |
ac:description($resource as element()) |
xs:string? |
Extracts a human-readable description from an RDF resource description |
ac:image($resource as element()) |
attribute()* |
Extracts image references (e.g. foaf:depiction, foaf:img) from a resource description |
ac:property-label($property as element()) |
xs:string? |
Label of a predicate element, capitalized — resolved by the ac:property-label mode templates. An overload accepts $property-metadata as document-node(), the property descriptions loaded for the current document |
ac:object-label($object as node()) |
xs:string? |
Label of a property's object: a resource-valued object is looked up in the current
graph, the loaded metadata or its dereferenced document, falling back to the URI's
fragment or last path segment; a literal yields its lexical value. An overload accepts
$object-metadata as document-node() |
ac:mode($doc as document-node()) |
xs:anyURI |
The active layout mode of the given document. Replaces the former $ac:mode parameter |
ac:langs() |
xs:string* |
The reader's accepted languages, deduplicated to primary subtags and always ending
with en as a fallback — negotiated server-side, read from navigator.languages in the browser |
ac:lang-rank($value as element()) |
xs:integer |
Ranks a property value's language against ac:langs(), so values sort by the reader's preference |
ldh:date-time($value as xs:string?) |
xs:dateTime? |
Parses a lexical value into a xs:dateTime where possible |
ldh:datatype-family($datatype as xs:anyURI?) |
xs:string |
Groups an XSD datatype into a family (numeric, dateTime, string etc.) — drives table column sorting |
ldh:sort-key($key as xs:string?, $datatype as xs:anyURI?) |
xs:anyAtomicType? |
Converts a lexical value into a typed sort key for its datatype family |
ldh:sort-key-lexical($resource as element(), $predicates as xs:anyURI*) |
xs:string? |
The lexical sort key of a resource: coalesces across $predicates in order, preferring values whose language matches the reader's first accepted language.
Feeds ldh:sort-key() |
ldh:sort-datatype($resources as element()*, $predicates as xs:anyURI*) |
xs:anyURI? |
The datatype shared by a sort column's literals; absent when the column mixes datatypes
or carries plain literals or resources. Keys the typed re-sort that keeps a view block's
client-side order agreeing with its SPARQL ORDER BY |
HTTP and SPARQL functions
| Function | Returns | Description |
|---|---|---|
ldh:query-result($endpoint as xs:anyURI, $query as xs:string) |
document-node() |
Executes a SPARQL query against the given endpoint and returns the result document (cached) |
ldh:send-request( |
document-node()? |
Makes an HTTP request and returns the response as a document (server-side extension function) |
ldh:link-targets($link-header as xs:string?, $marker as xs:string) |
xs:anyURI* |
Extracts the target URIs of the Link response header entries whose parameters contain $marker (e.g. a rel value) — the parser behind acl:mode(), sd:endpoint() and ldh:timemap() (client-side) |
ldh:parse-html($string as xs:string, $mime-type as xs:string) |
document-node() |
Parses a markup string into a document |
ldh:reserialize($doc as document-node()) |
document-node() |
Round-trips a document through serialization, normalizing it |
Dataspace and access functions
| Function | Returns | Description |
|---|---|---|
sd:endpoint() |
xs:anyURI |
SPARQL endpoint of the current data pane — the local endpoint, or the remote dataspace's
when browsing another dataspace through the proxy. Replaces the former $sd:endpoint parameter |
lds:dataspace() |
xs:anyURI? |
URI of the dataspace resource describing the current dataspace. Replaces the former
$lds:Dataspace parameter (its description is looked up in $lds:Context) |
acl:mode() |
xs:anyURI* |
Access modes granted on the current document, read from the response's Link headers. Replaces the former $acl:mode parameter |
Versioning functions
| Function | Returns | Description |
|---|---|---|
ldh:memento-datetime() |
xs:string? |
The Memento-Datetime of the currently viewed historical version, absent on the live document |
ldh:timemap() |
xs:anyURI? |
The document's TimeMap URI from the Link response header; absent when the document is not versioned |
ldh:snapshot-params($query-params as map(xs:string, xs:string*)) |
map(xs:string, xs:string*) |
Filters a query parameter map down to the snapshot parameters (version, timemap), so they survive URL rebuilds |
Utility functions
| Function | Returns | Description |
|---|---|---|
ac:uuid() |
xs:string |
Generates a random UUID |
ldh:hash-code($string as xs:string) |
xs:integer |
Deterministic 32-bit djb2 hash of a string's codepoints — the counterpart to ac:uuid() for identifiers that must survive a reload because they are re-derived rather than
stored (client-side) |
ac:value-intersect($arg1 as xs:anyAtomicType*, $arg2 as xs:anyAtomicType*) |
xs:anyAtomicType* |
Distinct values present in both sequences |
ac:value-except($arg1 as xs:anyAtomicType*, $arg2 as xs:anyAtomicType*) |
xs:anyAtomicType* |
Distinct values of $arg1 that are not present in $arg2 |
The many remaining ac: and ldh: functions are internal machinery, not a stable API, and are deliberately not documented
here: the client-side render-*, load-*, set-*, *-response and *-thunk families
together with the promise, progress, cursor and DOM helpers they call; the constructor,
form and SPARQL-manipulation helpers behind the editing
UI; and Web-Client functions that LinkedDataHub has superseded, such as ac:construct() and the SVG rendering helpers.
Other assets
CSS
The stylesheet links are emitted by the ac:Stylesheets XSLT template mode, in cascade order: the vendored design-system stylesheets — fonts.css, colors_and_type.css (tokens), app.css (components; it imports core.css, which imports controls.css and overlays.css) and retro.css (the skin) — then ldh.css, LinkedDataHub's one app layer over the kits, loaded last so it wins the ties it
is written to win. Feature-specific stylesheets are linked ahead of them and only
when needed: rdfa-editor.css for rich-text editing when an agent is authenticated, yasqe.css for the SPARQL editor. Dataspace stylesheets that import layout.xsl can override the ac:Stylesheets template mode to add or replace stylesheets.
Components are named by class rather than by element: ac-* for the design system's own components (ac-btn, ac-alert, ac-card, ac-badge) and ldh-* for the ones LinkedDataHub adds over them (ldh-block, ldh-actionbar, ldh-address). A component is varied by stacking one class from each axis onto it, not by a compound
name — the action bar's Actions button is ac-btn in-neutral ap-outline sz-md, a form's Save button ac-btn in-primary ap-solid sz-md.
| Axis | Classes |
|---|---|
| Intent | in-primary, in-neutral, in-accent, in-destructive, in-negative, in-inverse |
| Appearance | ap-solid, ap-outline, ap-ghost, ap-fixed |
| Size | sz-xs, sz-sm, sz-md, sz-lg, sz-xl |
| Variant | va-informative, va-positive, va-negative, va-neutral, va-emphasis, va-enclosed, va-line, va-hero, va-inverse, va-fixed — carried by components that vary by kind rather than by intent, such as ac-alert |
That vocabulary is the contract the templates emit, so restyling is a matter of writing
rules against those classes. Changes belong in a stylesheet of the dataspace's own:
ldh.css is the platform's app layer and is replaced wholesale on upgrade. Override the ac:Stylesheets template mode and call xsl:next-match first, which emits the platform's links and leaves yours last in the cascade, where
they win the ties they need to win. Omitting xsl:next-match drops the vendored kits along with ldh.css and leaves the components unstyled.
JavaScript
The script links are emitted by the xhtml:Script XSLT template mode. It loads functions.js, the Saxon-JS 3 runtime together with the compiled client stylesheet (when client-side
rendering is enabled), and feature-specific libraries: the YASQE SPARQL editor, the
SPARQL builder, OpenLayers for maps, Google Charts for charts, and three.js with 3d-force-graph
for the 3D graph mode.
LinkedDataHub only uses JavaScript for the functionality that cannot be achieved using client-side XSLT.
To customize the built-in stylesheets, follow the Change layout and Build apps guides