====== What are stylesheets for? ======
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 [[TransformationPipeline|pipeline]], which takes the input file and produces tree and node views.
====== What are those stylesheets, then? ======
Basically, they're XSLT 1.0 stylesheets, slightly extended in some ways to allow for:
* Runtime tab-specific view and preprocessing stylesheet switching by the user
* Easier creation of links between documents and/or nodes
* Special URIs which allow for both localization and inheritance at the same time
* Easier installation: unpack some files in the right place and restart XMLEye
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. :-D
====== Installation ======
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.
====== Implementation details ======
You'll probably only be interested in this if you want to define new stylesheets.
===== Stylesheet repositories =====
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 [[start#how_do_i_obtain_xmleye|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.
===== Localization =====
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:
- xslt/preproc/H/H_L_P.xsl
- xslt/preproc/H/H_L.xsl
- xslt/preproc/H/H.xsl
Of course, the stylesheet author must take this into account. Normally, the stylesheet would be split into 2 parts:
- Entry points would be limited to declaring variables and named templates which will produce the localized text, importing all parent stylesheets and including its own XSLT stylesheet files which implement the actual functionality.
- Included XSLT stylesheet files wouldn't contain any text strings of their own. They'll always refer to the previously defined variables in the entry point.
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:
===== Inheritance =====
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:
We'd get an import/include tree like this:
{{ :stylesheet_hierarchy.png |Stylesheet hierarchy for the ppACL2 view stylesheet}}
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.
===== Preprocessing specifics =====
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:
- The current element is copied, along with its attributes.
- A new UID attribute is added with an identifier guaranteed to be unique to that node.
- Applies the XSLT templates available in the default mode. Nothing is done by default, but we can add whatever we need.
- Copies all text children.
- Applies the XSLT templates available in the process-children mode. All children are processed by default, but we can redefine this so some nodes can be entirely removed from the tree.
This is just an implementation of the Template Method pattern. In short, what this means is that:
- If you want to add new attributes or content, define new templates in the default mode.
- If you want to filter out some nodes, define new templates in the ''process-children'' mode.
But that's not all. There's a few special attributes in the default namespace ((They will be moved to a namespace of its own in a later release.)) that XMLEye understands:
* **hidden**: when set to "1", the node and its children will be hidden in the tree view. It won't be deleted, though, so you will be able to access its information to generate views for other nodes. This is used in the ''yaxml'' stylesheet to hide all ''_key'' nodes, like this:
1
* **leaf**: when set to "1", it will be treated as a leaf node, and all of its children will be hidden.
* **nodeicon**: contains the relative path from the stylesheet repository root directory to the 16x16 pixel icon to be used for the node. For instance, the ''ppACL2'' stylesheet uses this to indicate whether a specific element's proof was successful or not.
* **nodelabel**: contains the label to be used for the node. This helps alleviate the usual restrinctions on XML node names, which are a problem when viewing YAML/JSON documents, for instance. See this code:
===== View specifics =====
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:
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.