HOWTO: Modify Virtuoso default /sparql endpoint Form page using XSLT

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:

  1. Upload the replacement XSLT to a web (Virtuoso WebDAV) or file system (vsp folder) or other accessible location.

  2. Use the Virtuoso registry_set() function to register the replacement XSLT file with the special sparql_endpoint_xsl registry key as follows:

     registry_set('sparql_endpoint_xsl','http://local.virt/DAV/sparql_endpoint.xslt');
    
  3. 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 one h1 in a proper POSH document anyway
  • //a[contains(@class,'navbar-brand')] - "a link where the class contains navbar-brand" is sufficiently precise that it will probably only match one link on the page, the one in the primary navbar; the relationship of body > 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.

Related