Using converters, we can open any input format, even if they're not XML files. So instead of a XML document viewer, we get a XML-based document viewer. However, the resulting XML might not be what we really want to see. It would be nice if we could add custom icons and labels to the nodes and hide or delete some of those nodes. We can do that with preprocessing stylesheets.
Even after that, we'll only get a (prettier) tree. What if we want to see a single node in detail, with context-dependant information and links to other nodes of interest? That's where view stylesheets come into play. Currently, XHTML node views are supported, but other formats could be implemented in the future. JavaFX would be nice, for instance.
All these stylesheets are integrated into a pipeline, which takes the input file and produces tree and node views.
Basically, they're XSLT 1.0 stylesheets, slightly extended in some ways to allow for:
We'll get more into detail below. Depending on the context, “stylesheet” might mean a coherent group of XSLT 1.0 stylesheets (such as “the xml view stylesheet”), or an individual XSLT 1.0 stylesheet file.
What about performance? XMLEye uses Xalan as its XSLT engine, and stylesheets are lazily compiled into Java bytecode. The first node might take a second or two to show up, but after that it's blazing fast. Or so I think.
Installing a new stylesheet is as easy as placing the XSLT files under a subdirectory of the right section of the stylesheet repository (see below for details). Its location varies depending on what installation method you used. Preprocessing stylesheets go under the preproc subdirectory, and view stylesheets go under the view subdirectory. For example, the xml view stylesheet installed through the XMLEye Debian package has all the required XSLT files under /usr/share/xmleye/xslt/view/xml.
As usual, you don't have to worry about these details if you just use the Debian packages in the XMLEye private repository.
You'll probably only be interested in this if you want to define new stylesheets.
All stylesheets used in XMLEye are installed in a so-called stylesheet repository. It is located under the xslt subdirectory of wherever the xmleye.jar was run from. Debian packages use /usr/share/xmleye, but it could be pretty much anywhere. See the installation instructions for more details.
It will always contain at least these files:
preproc.xsl: all preprocessing transformations start from here. It defines some useful variables and imports two stylesheets: util.xsl and the stylesheet referred to by the special URI current_preproc. This is an XMLEye-specific extension: that URI is resolved into the entry point of the preprocessing stylesheet selected currently by the user. We'll talk more about entry points later.view.xsl: this is the starting point for all view transformations. It uses current_view as we used current_preproc before, but it needs to take care of one more thing: switching the context node to that selected currently by the user. Its identifier is passed to the stylesheet through the selectedUID parameter, which contains the unique ID of this node. Where does this UID come from? It is generated in the preprocessing stage, actually. It's one of the few things which must always be done. So long as your preprocessing stylesheet inherits from the xml base stylesheet, you won't have anything to worry about. Oh, and looking for the node with a specific UID is done through an index, and certainly not using a full document scan.util.xsl: this stylesheet includes some common useful functions. I try to use pure XPath functions whenever possible. There's util:to-lower, util:to-upper, and util:substring-after-last, with their expected meanings. Maybe after I switch to XSLT 2.0 these won't be necessary, but they don't hurt either, so feel free to use them.preproc: this subdirectory contains one directory for each preprocessing stylesheet, which consists of one or more XSLT 1.0 stylesheet files.view: same as the previous directory, but for view stylesheets.One of XMLEye's main design concerns has always been internationalization. Everything can be easily localized: that includes all help files, the Swing UI and of course, the stylesheets.
There's a small problem, though: how do we localize a stylesheet and avoid code duplication at the same time? Well, it's actually pretty simple. We saw before that all preprocessing and view transformations use preproc.xsl and view.xsl XSLT stylesheets as entry points. Actually, those import in turn our stylesheet's entry point through current_preproc and current_view. Finally, these stylesheet-specific entry points import all the required logic.
The trick here is that there can be more than one entry point. XMLEye follows the usual practice in most localization frameworks, such as gettext. Let L be the current locale's ISO 639 language code and P be the ISO 3166 country code. Should the H preprocessing stylesheet be selected, XMLEye would try to resolve the special current_preproc URI into these actual XSLT stylesheet file paths, in the same order:
Of course, the stylesheet author must take this into account. Normally, the stylesheet would be split into 2 parts:
Oh, and one last thing: when including specific XSLT files, relative paths from the stylesheet repository root directory should always be used. xml.xsl includes main.xsl like this:
<xsl:include href="view/xml/main.xsl"/>
XSLT already includes support for inheritance: the xsl:import element brings all the templates from another XSLT file using a lower precedence, so we can replace and refine some of its rules with our own. However, we can only import files in specific locations in this way. This means that we wouldn't be able to use the locale-aware entry point resolution we described above.
We'll need to use special URIs again: preproc_X is resolved to the X preprocessing stylesheet's entry point. view_X does the same thing, but for view stylesheets.
For instance, the ppACL2 preprocessing stylesheet inherits from the xml stylesheet like this:
<xsl:import href="preproc_xml"/>
We'd get an import/include tree like this:
No restrictions are imposed on the number of inheritance levels. Take as an example the summaries and reverse preprocessing stylesheets, which inherit from the ppACL2 stylesheet.
As said above, every preprocessing stylesheet must add UIDs to every node, so the user can select them and obtain node views. The xml stylesheet takes care of this, as long as we follow some rules in our own stylesheet.
The xml stylesheet immediately changes to the process-node mode, which contains a default rule that follows these steps:
This is just an implementation of the Template Method pattern. In short, what this means is that:
process-children mode.But that's not all. There's a few special attributes in the default namespace 1) that XMLEye understands:
yaxml stylesheet to hide all _key nodes, like this:<!-- Hide all yaml:_key elements --> <xsl:template match="*[local-name(.)='_key']"> <xsl:attribute name="hidden">1</xsl:attribute> </xsl:template>
ppACL2 stylesheet uses this to indicate whether a specific element's proof was successful or not.<!-- Label map values using their keys --> <xsl:template match="*[local-name(.)='_value']"> <xsl:attribute name="nodelabel"> <xsl:value-of select="preceding-sibling::*[local-name(.)='_key'][1]"/> </xsl:attribute> </xsl:template>
Most of the time, we'll need all views to follow a basic structure. This is most easily done through a named template, which will receive the result tree fragment with the XHTML code to be included in the body of the document.
We can use the skeleton named template defined in the xml view stylesheet. It produces links to all direct children and ancestors of the current node. This is all optional, of course: we could use our own skeleton if we wanted, or none at all.
What's more important is how to create hyperlinks between different nodes. We can use standard XHTML links to external sites and anchors, but we'll need to do some extra work to create links between nodes and between documents.
These special links follow the syntax #xpointer(path), where path can be any path which uniquely identifies the node. We can use the getPath XPath extension function from the xalan://es.uca.xmleye.xpath.XPathPathManager namespace to generate it, like this:
<xsl:template match="encapsulate|local" mode="ppacl2_printnode"> <h2 class="section"> <a href="#xpointer({ext:getPath(.,/)})"> <xsl:value-of select="name(.)"/> </a> </h2> <pre> <xsl:value-of select="@formula"/> </pre> </xsl:template>
As we can see, the function takes two arguments: the node to which we want to link, and the root node. In this case, we have linked to the current context node.
Links to nodes in other documents can be defined as path_to_the_file#xpointer(path). There's no XPath function to help us in this case: we'll have to build a XPath path on our own, using what we know about the XML format used.