HOWTO: Modify Virtuoso default /sparql
endpoint Form page using XSLT
Overview
This document serves as a quick primer in using XSLT to re-style/modify/customize the Virtuoso default /sparql
endpoint Form page:
The steps are as follow:
-
Upload the replacement XSLT to a web (Virtuoso WebDAV) or file system (
vsp
folder) or other accessible location. -
Use the Virtuoso
registry_set()
function to register the replacement XSLT file with the specialsparql_endpoint_xsl
registry key as follows:registry_set('sparql_endpoint_xsl','http://local.virt/DAV/sparql_endpoint.xslt');
-
Reload the Virtuoso
/sparql
endpoint to see the effects of the change.
Note: you may have to run xslt_stale('http://local.virt/DAV/sparql_endpoint.xslt');
(or restart the server) to force an HTTP cache refresh to see the effects of any changes made to the XSLT file.
The registry_remove()
function can be used to remove the sparql_endpoint_xsl
registry setting and revert back to the default built-in /sparql
endpoint Form page Virtuoso ships with as follows:
registry_remove('sparql_endpoint_xsl');
Notes on placements & protocols
It is recommended to use a location in WebDAV for the sparql-endpoint.xslt
. This allows use of the http://local.virt/DAV/
protocol which is the quickest way to access DAV from PL.
registry_set('sparql_endpoint_xsl','http://local.virt/DAV/sparql_endpoint.xslt');
Alternatively, as a second-best option, the XSLT file can also be placed on the file system, as follows:
registry_set('sparql_endpoint_xsl','file:///sparql_endpoint.xslt')
As usual with file://
, remove two leading slashes as part of the protocol; if what remains still starts with a /
then it is treated as relative to ServerRoot=../vsp/
in virtuoso.ini
. If not, or particularly if it starts with ./
, then it is relative to the database directory itself. In either case, ensure Virtuoso grants access with DirsAllowed= ., ../vsp/
in virtuoso.ini
.
Finally, the path to sparql-endpoint.xslt may be a full HTTP or HTTPS URL. This is discouraged as it requires an extra HTTP session which will slow everything down and risk locking or transaction issues. XSLT stylesheets located within the same Virtuoso instance are much better accessed via http://local.virt/DAV/
as above.
Sample XSLT
Below is sample source for sparql_endpoint.xslt
.
Depending on version of binary (pre- or post-bootstrap enhancements), this will either intercept and replace the h1
element (becomes SPARQL Query Editor: Something Else...
) or the branding in the top-level navbar (becomes OpenLink SPARQL Query Editor
).
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn2="http://www.w3.org/2004/07/xpath-functions"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
version="1.0"
exclude-result-prefixes="xsl fn2 xhtml">
<xsl:output
method="xhtml"
doctype-public="-//W3C//DTD XHTML+RDFa 1.0//EN"
doctype-system="http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd"
indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates mode="copy"/>
<xsl:comment>SPARQL Endpoint XSLT applied</xsl:comment>
</xsl:template>
<!-- Binary with old sparql_ui -->
<!-- intercept first heading -->
<xsl:template match="//h1[1]" mode="copy">
<h1>SPARQL Query Editor: Something Else...</h1>
</xsl:template>
<!-- Binary with newer bootstrap-enabled sparql_ui -->
<xsl:template match="//a[contains(@class,'navbar-brand')]" mode="copy">
<xsl:element name="{name(.)}">
<xsl:apply-templates select="@*" mode="copyattr" />
OpenLink SPARQL Query Editor
</xsl:element>
</xsl:template>
<!-- couple of templates for implementing a passthroguh -->
<xsl:template match="*" mode="copy">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="@*" mode="copyattr"/>
<xsl:apply-templates mode="copy"/>
</xsl:element>
</xsl:template>
<xsl:template match="comment()" mode="copy" priority="10">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="@*" mode="copyattr">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
For which the resultant form page with the <h1>
tag change would be:
Or in the newer bootstrap binary (the masthead gains the word “OpenLink”):
In both cases, to aid debugging, a comment is injected at the very bottom of the output HTML to show XSLT has been performed.
Work-through
- The fundamental paradigm with the XSLT is whether to pass-through or intercept the incoming element stream.
- The last two templates match all HTML elements and attributes, recreating elements and attributes of the same names and values; this acts as a fallback.
- The intercept template identifies the first heading
h1
element and emits its own content instead. - Note the use of
mode="copy"
throughout, both throwing and matching templates.
Defensive XPATH
The same principles apply in designing XPATH expressions as in designing regexps - a balance of parts that should be fixed yet flexibility where to find them in a larger context.
In the above XSLT,
-
//h1[1]
- “the first h1 anywhere in the document” is meaningful enough - there should only be oneh1
in a proper POSH document anyway -
//a[contains(@class,'navbar-brand')]
- "a link where the class containsnavbar-brand
" is sufficiently precise that it will probably only match one link on the page, the one in the primary navbar; the relationship ofbody > div.container-fluid > nav
may change, or other classes may be added to that link, but it will continue to match what it needs.
Possible Ideas
To inject styles or javascript in the header, you can add a template like:
<xsl:template match="head" mode="copy">
<xsl:apply-templates mode="copy" />
<link rel="stylesheet" href="mystyle.css" />
<script type="text/javascript">
function irksome() {
alert("annoying, isn't it?");
};
</script>
</xsl:template>
To invoke the new JS from the bottom of the HTML body
:
<xsl:template match="body" mode="copy">
<xsl:apply-templates mode="copy" />
<script type="text/javascript">
irksome();
</script>
</xsl:template>
Explanations
In both cases we intercept the head
or body
element respectively, let the passthrough copy all the child elements, and finally, at the end, emit our own new code. This forms the basis for incremental development above the existing SPARQL endpoint appearance.