Friday, October 02, 2009

EXPath Packaging System prototype implementation for Saxon

Introduction

After having released a first implementation of EXPath Packaging System for eXist, here is a version for Saxon. You can read this previous blog entry to get more information on the packaging system; in particular, it says: "The concept is quite simple: defining a package format to enable users to install libraries in their processor with just a few clicks, and to enable library authors to provide a single package to be installed on every processors, without the need to document (and maintain) the installation process for each of them."

The package manager for Saxon is a graphical application (a textual front-end will be provided soon,) and is provided as a single JAR file. Go to the implementations page, or use this following direct link to get the JAR. Run it as usual, for instance by double-clicking on it or by executing the command java -jar expath-pkg-saxon-0.1.jar. That will launch the package manager window.

Repositories

The implementation for Saxon differs from the one for eXist in a fundamental way: Saxon does not have a home directory where you can put the installed packaged, and you can invoke Saxon in so many different ways (while the eXist core is always started the same way.) That involves two different aspects regarding package management with Saxon: the package manager itself that installs and remove packages, and a way to configure Saxon itself, regardless with the way you invoke it. In addition, the homeless property of Saxon needs to introduce the concept of package repository.

A repository is a directory dedicated to installing packages, and should only be modified through the package manager. It contains the packages themselves (under a form usable by Saxon) as well as administrative informations to be able to use them (like catalogs, etc.) The graphical package manager allows one to create a new repository directly from the graphical interface, as well as switching between different repositories (if you need to maintain several repositories for several purposes.)

Importing stylesheet

But as I said above, having a repository full of packages is not enough. You have to configure Saxon to use this repository. Because you can invoke Saxon in a plenty of ways, the configuration itself is implemented as a Java helper class that you can use in your own code if you invoke Saxon from within Java (for instance in a Java EE web application.) If you use Saxon from the command line, there is a script that takes care of configuring everything for you.

But before looking in details at how to configure Saxon to use a repository, let's have a look at how a stylesheet can use an installed package. This is the whole point of the packaging system, after all. The goal is simply to be able to use a public import URI in an import statement, this URI being automatically resolved to its local copy in the repository. Like a namespace URI is just a kind of identifier (it is just used as a string, your processor does not try to actually access anything at that address,) the public import URI is an identifier to a specific stylesheet. This machanism supports also having functions implemented in Java. So all you need to do is to use this public URI, like the following:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:h="http://www.example.org/hello"
                version="2.0">

   <xsl:import href="http://www.example.org/hello.xsl"/>

   <xsl:template ...>
      ...
      <xsl:value-of select="h:hello('world')"/>

For XQuery, this is a bit different as XQuery does have a module system. But this is actually very similar. XQuery library modules are identified by their namespace URI. Once again, it can be seen as a public identifier for that XQuery module. So let's say we have an XQuery library module for the namespace URI http://www.example.org/hello, then you can simply write a module that imports it as following:

import module namespace h = "http://www.example.org/hello";
h:hello('world')

And that's it! In the package samples section below, you can see completes examples of such importing stylesheets and queries, as well as the packages they use.

Java configuration

To configure Saxon to use a repository from Java, you need to get a Configuration object. This is a central class in Saxon, which is used almost everywhere in the Saxon code base. You can get it from a Saxon TransformerFactory or from a S9API Processor. With that object on the one hand, and a File object pointing to the repository directory on the other hand, you can just call:

// the repo directory
File          repo   = ...;
// the Saxon config object
Configuration config = ...;
// the EXPath Pkg configurer
ConfigHelper  helper = new ConfigHelper(repo);
// actually configure Saxon
helper.config(config);

Besides the Java code itself, you have to be sure 1/ to have an actual repository at the location you pass to the ConfigHelper constructor and 2/ to have the JAR files used by and containing the extension functions written in Java into your classpath. The only exception to this rule is when you register such an extension function (written in Java) to Saxon 9.2; in this case EXPath Pkg will try to dynamically add the JAR files from the repository to the classpath. But playing with the classpath at runtime is not something I would recommend in Java.

Shell script

When using Saxon from the command line, EXPath Pkg comes with an alternate class to launch Saxon (this class automatically uses ConfigHelper to configure Saxon) as well as with a shell script to launch Saxon with the correct classpath.

To use this shell script (only available on Unix-like systems for now, including Cygwin under Windows) you have to set the environment variables SAXON_HOME to the directory where you put the Saxon JAR files, EXPATH_PKG_JAR to the EXPath Pkg JAR file, and APACHE_XML_RESOLVER_JAR to the XML Resolver JAR file from Apache. Additionally, you can set EXPATH_REPO to the repository directory, to not have to explicitely give it as an option each time you invoke Saxon. If all the above environment variables have been correctly set, and the script added to your PATH, you can just invoke Saxon as usual: saxon -s:source.xml -xsl:stylesheet.xsl.

Use saxon --help to get the usage help of this script. You can set the EXPath repository (and thus override EXPATH_REPO if it is set) with the option --repo=. You can add items to the classpath with the option --add-cp=. You can set the classpath (so overriding SAXON_HOME and other environment variables) with the option --cp=. The script detects if Saxon SA is present, and if so will use the SA version. You can force either B or SA version with either --b or --sa. You can also set any option to the Java Virtual Machine by using --java=, for instance to set a system property, and --mem= to set the amount of memory of the virtual machine (shortcut for the Java option -Xmx) And finally, you can also set the HTTP and HTTPS proxy information with --proxy=host:port (for instance --proxy=proxyhost:8080.)

Package samples

The first example is a packaged version of Priscilla Walmsley's FunctX. This package contains both the XSLT and the XQuery versions of this library. Of course, the XQuery module defines a module namespace, but the XSLT stylesheet does not have any public import URI (as this is behind the standard.) I chose the URI http://www.functx.com/functx-1.0.xsl, but keep in mind this is not official by any means, this is just the URI I chose. It is intended that library authors package their own libraries and choose the public URIs themselves.

The package itself is a plain ZIP file. If you open it or unzip it with your preffered tool, you can see that at the top level, there is a file named expath-pkg.xml. This is the package descriptor, that defines what the package contains (at least what is publicly exported from the package, so what can be used from within a stylesheet or a query.) In the case of this FunctX package, this descriptor looks like:

<package xmlns="http://expath.org/mod/expath-pkg">
   <module version="1.0" name="functx">
      <title>FunctX library for XQuery 1.0 and XSLT 2.0</title>
      <xsl>
         <import-uri>http://www.functx.com/functx-1.0.xsl</import-uri>
         <file>functx-1.0-doc-2007-01.xsl</file>
      </xsl>
      <xquery>
         <namespace>http://www.functx.com</namespace>
         <file>functx-1.0-doc-2007-01.xq</file>
      </xquery>
   </module>
</package>

To install the package, just download it to a temporary location, launch the package manager as explained at the beginning of this blog post, choose "install" in the file menu, and choose the package on your filesystem. To test if it is correctly installed, write the following stylesheet:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:f="http://www.functx.com"
                version="2.0">

   <xsl:import href="http://www.functx.com/functx-1.0.xsl"/>

   <xsl:template match="/" name="main">
      <result>
         <xsl:sequence select="f:date(1979, 9, 1)"/>
      </result>
   </xsl:template>

</xsl:stylesheet>

and/or the following XQuery main module (depending on what you want to test):

import module namespace f = "http://www.functx.com";

<result> {
   f:date(1979, 9, 1)
}
</result>

To evaluate them, make sure you configured the shell script correctly, as explained above, then open a shell and type one of the following command (or both) where style.xsl is the file where you saved the above stylesheet and query.xq is the file where your saved the above query:

$ saxon -xsl:style.xsl -it:main
<result>1979-09-01</result>
$ saxon --xq query.xq
<result>1979-09-01</result>
$ 

If you prefer to test from Java, just write a simple main class that evaluates the above stylesheet and/or query, taking care of using ConfigHelper to set up the Saxon Configure object. For instance, if you want to use the S9API, you can configure the Processor object like the following (don't forget to add the EXPath Pkg and the Apache XML resolver JAR files to your classpath):

// the repo directory
File         repo   = new File("...");
// the EXPath Pkg configurer
ConfigHelper helper = new ConfigHelper(repo);
// the Saxon processor
Processor    proc   = new Processor(false);
// actually configure Saxon
helper.config(proc.getUnderlyingConfiguration());
// then use 'proc' as usual...

The second sample package provides a single function: ext:hello($who). It is written in Java. Besides other stuff related to the packaging itself, it contains a JAR file with the implementation of that extension function. To test it, just follow the same steps as for the FunctX package, except that you have to add the installed JAR file (from within the repository) to your claspath (this is done automatically for you if you use the shell script, but not if you test it from a Java program.)

Conclusion

This is just a prototype implementation of a package manager for Saxon, which is consistent with the one for eXist. The main issue is the configuration of the classpath, but I think this is best let to the user than having to deal with the classpath, in particular within the context of a Java EE application. This issue shows up also in your IDE configuration. For now, I configure oXygen by adding the catalogs from the repository to the oXygen's main catalog list, and the extension JAR files to the oXygen classpath, so the built-in Saxon processors can be used exactly as usual. But such issues can be resolved by native support right into the processors ad IDEs.

Besides this classpath issue, I am convinced that package management will really improve the current situation, and maybe could be the missing piece to distribute real general-purpose libraries for XQuery and XSLT, and one of the basis to other systems, like an implementation-independent XRX system.

Labels: , , ,

Saturday, September 12, 2009

EXPath Packaging System prototype implementation for eXist

During the past few weeks, I have been working on the Packaging System for EXPath (see also this blog entry for more info.) The concept is quite simple: defining a package format to enable users to install libraries in their processor with just a few clicks, and to enable library authors to provide a single package to be installed on every processors, without the need to document (and maintain) the installation process for each of them.

Of course, this system should be supported right into the processors themselves, as this is intimately related to the way each processor manages its queries and/or stylesheets. But to convince vendors, we first have to show something that does work, and to show that users are actually interested. So I have written a prototype implementation for eXist (as well as one for Saxon, but it still needs some cosmetic work in order to be released.)

The package manager for eXist is a graphical application (a textual front-end will be provided soon,) and is provided as a single JAR file. Go to the implementations page, or use this following direct link to get the JAR. Run it as usual, for instance by double-clicking on it or by executing the command java -jar expath-pkg-exist-0.1.jar. That will launch the package manager window.

The manager acts on an eXist instance, that is, the directory where you installed eXist on your machine (the one that contains the conf.xml file.) You can have several instances installed on a same computer, but the package manager only acts on a single one at a time. You can select it via the File menu, item Open instance. Just select the correct directory. By default, if the environment variable EXIST_HOME is set, the manager will use this directory. If not, it will display a warning and wait for you to select an instance:

The manger stores some info in a file created in the instance directory. If this file does not exist, the manager asks you to confirm to create it. That is kind of initializing this instance for the EXPath Packaging system. You can safely answer yes:

Once an eXist instance is selected, the manager shows a list of the packages installed in this instance. Using the menu, you can deleting a package previously installed, or install a new one. To install a new package, you have to select it through the file selection dialog. Package files have the *.xar extension.

In order to test the system, you can use the following packages. The first one is actually nothing else than Priscilla Walmsley's FunctX for XQuery, that I packaged (just because this is a more interesting and useful example of a library written in standard XQuery than a simple hello world example...) The second one is a simple XQuery module that use eXist extension functions (remember that a XAR is simply a ZIp file, so you can open it and see the actual XQuery file within it.) Technically, it is deployed exactly as a standard XQuery file, but in the package it is flagged as dependent on eXist. And the last one is a simple module written in Java. It provides a simple function that says hello.

If you want to test the installation, start your eXist instance (or restart it, but I would say it is safer to stop it to install packages) and try one of those queries:

(: test the first package: FunctX... :)
import module namespace f = "http://www.functx.com";
f:date(1979, 9, 1)

(: ...or test the second package... :)
import module namespace v = "http://www.example.com/version";
v:info()

(: ...or test the third package :)
import module namespace t = "http://www.example.com/ext";
t:hello('you')

How does it work? The easiest is maybe to open the XAR files and see what they contain. For instance, the package for FunctX contains (when opened as a ZIP file) a file expath-pkg.xml and a file functx/functx-1.0-doc-2007-01.xq. The later is simply the XQuery file, exactly as you can find it on the FunctX website. The former is the package descriptor. This is a little XML file describing the content of the package. In this case, it contains:

<package xmlns="http://expath.org/mod/expath-pkg">
   <module version="1.0" name="functx">
      <title>FunctX library for XQuery 1.0</title>
      <xquery>
         <namespace>http://www.functx.com</namespace>
         <file>functx-1.0-doc-2007-01.xq</file>
      </xquery>
   </module>
</package>

You can see that this descriptor contains the name of the library (here functx) as well as a user-friendly title, and its version number. Then the XQuery file is registered with its target namespace. This info is used by the manager to update the eXist's conf.xml file. The actual XQuery file is wrapped in a JAR file that is put in the eXist classpath, to enable eXist to find it as a resource. If you open the package descriptor of the 2 other packages, you will discover that they are slightly different (to differenciate the different kinds of libraries, in particular those written in Java.)

If you have any comment, idea or criticism, please tell us on the EXPath mailing list. I hope to release the package manager for Saxon quite soon, I will announce it on the list.

Labels: , ,

Friday, July 31, 2009

eXist extension functions in Java

Investigating into opportunities for an implementation of EXPath Packaging for eXist, I am looking at the way to implement extension functions with Java. I detail here the key points I found to write Java extensions for eXist using NetBeans. But those info should be generic enough to be used with any Java IDE.

If you want to provide an extension function written in Java within eXist, you actually have to provide a full module in Java. You cannot provide the module part in XQuery and part in Java (though you can provide an XQuery module that imports a private module written in Java.)

The module is represented by a class that extends the eXist abstract class AbstractInternalModule. Along some information like the namespace URI of the module, the default prefix to use, and a description, you have to provide a list of extension functions through the base class constructor:

public class SimpleModule extends AbstractInternalModule
{
    public SimpleModule() {
        super(FUNCTIONS);
    }
    public String getNamespaceURI() {
        return NAMESPACE_URI;
    }
    public String getDefaultPrefix() {
        return PREFIX;
    }
    public String getDescription() {
        return "A simple example module";
    }

    static final String PREFIX = "ext";
    static final String NAMESPACE_URI = "http://www.example.org/project/ext";

    private final static FunctionDef[] FUNCTIONS = {
        new FunctionDef(SimpleFunction.SIGNATURE, SimpleFunction.class)
    };
}

This module contains one single function, implemented by the class SimpleFunction. An extension function is a Java class that extends the eXist abstract class BasicFunction. It has to provide some information to the base class constructor (its qualified name, a documentation string, its parameter list and its return type.) The logic of the function itself is located in the eval function:

// implements the function "ext:hello($who as xs:string) as element()".
public class SimpleFunction extends BasicFunction
{
    public SimpleFunction(XQueryContext ctxt) {
        super(ctxt, SIGNATURE);
    }

    /** return an element <hello> with the value of the string param as content */
    public Sequence eval(Sequence[] args, Sequence unused) throws XPathException {
        String type = Integer.toString(args[0].getItemType());
        MemTreeBuilder builder = context.getDocumentBuilder();
        builder.startDocument();
        builder.startElement(new QName("hello", null, null), null);
        builder.characters(args[0].getStringValue());
        builder.endElement();
        builder.endDocument();
        return (Sequence) builder.getDocument().getDocumentElement();
    }

    private static final FunctionParameterSequenceType PARAM_WHO =
            new FunctionParameterSequenceType("who", Type.STRING, Cardinality.EXACTLY_ONE, "Who?");
    private static final FunctionReturnSequenceType RETURN_TYPE =
            new FunctionReturnSequenceType(Type.ELEMENT, Cardinality.EXACTLY_ONE, "Hello element");

    static final FunctionSignature SIGNATURE = new FunctionSignature(
        new QName("hello", SimpleModule.NAMESPACE_URI, SimpleModule.PREFIX),
        "Return greetings."
        + " This method returns an element 'hello' with the name passed as param.",
        new SequenceType[] { PARAM_WHO },
        RETURN_TYPE
      );
}

In order to compile those files with NetBeans, you have to add two JAR files to the build path of the project you used for those files. Right-click on the project and choose Properties, then Libraries and the button Add JAR/Folder. You will find the JARs in the eXist install directory: $EXIST_HOME/exist.jar and $EXIST_HOME/lib/core/xmldb.jar. Then you compile your project to get the JAR file with your both class (the module and its single function) compiled. The best choice for the project's type is a library of Java classes.

Now you have the JAR file containing the Java implementation of your module and its function. The next step is to plug them within an eXist instance. Copy the JAR file to $EXIST_HOME/lib/extensions/ and configure it in $EXIST_HOME/conf.xml. The configuration only requires to add a new module element to the builtin-modules element:

<module class="org.example.project.SimpleModule"
        uri="http://www.example.org/project/ext"/>

Just restart your eXist instance, and try the following query in the admin console:

import module namespace ext="http://www.example.org/project/ext";
ext:hello('you')

This will call the method eval on a SimpleFunction object, discovered through a SimpleModule object configured in conf.xml. Soon, you should be able to package the JAR file to an EXPath package and install it within eXist through a graphical console...

Labels: , ,

Thursday, June 25, 2009

Divide and Conquer, or XPath, XSLT, XQuery and XProc packaging

Packaging of various X* technologies seems to be of interest for a lot of people for now. And of course it is for me. But it seems everyone comes with its own idea of packaging, as well as a different scope. So to add to the complexity yet, I will present here my own ideas on that matter. Hopefully, I will try to tidy up the different concepts and to identify the different needs. And as always, I like to speak about concrete. To ease further discussions, if only that. So I will introduce a prototype of a packaging system for X* libraries and extensions for Saxon.

Packaging is nothing in itself. It is always related to something else (a language, a technology, a framework...) Packaging is just a mean to ease sharing and delivering something in the scope of that "something else." The several files in an ODF document are packaged in a single ZIP file, with a pre-defined structure, to make it possible for an application to use its content. The important point is not the structure in itself, but rather the information it gathers.

I have followed some very interesting discussions about X* packging during the last few weeks, with very interesting people. Rapidly, I have seen everyone were talking about slightly (or not) different things. The most important point where people have different views IMHO, is the scope of packaging.

As with most of modern languages, an XML developer may have to deliver different pieces of software, depending on the project: libraries, standalone applications, or web applications built for a specific framework. If you look at Java for instance, this is reflected quite clearly in its various packaging formats: JAR files for libraries and applications, WAR files for web applications, EAR files for entire enterprise applications...

WAR files contain Java classes, as JAR files. But the structure is quite different, and there are a few other files, describing what is in the package: "that class is a servlet class, conforming to the definition of servlet and coded to live in a servlet container, with a precise lifecycle," or "the package depends on this JAR file."

The same way, you can package XSLT libraries or XQuery modules, telling a processor that when a stylesheet or a module imports a specific URI, some functions are available (provided as plain XSLT stylesheets, XQuery modules, or extension functions.) Or you can package an entire web application using XProc to control the overall processes, XQuery to query XML databases and XSLT for the presentation layer (sounds very MVC, doesn't it?) But those packages are really different beasts: when the first example just need to package some XSLT, XQuery, Java, whatever code, alonside a simple cataloging system, the second example require to define a complete web framework, its lifecycle, how script can plug into this and exchange information with it ("this XProc pipeline has to be evaluated on an HTTP GET on http://www.example/app/theuri, it knows you will provide it with request information as a wa:http-request element, as we agreed upon, and that XSLT stylesheet has to be applied to its result; by the way it will access runtime information by using the extension functions you provide.")

There has been some work on XRX frameworks, and clearly it would be beneficial for anybody (users, but also implementors,) to have such a standard packaging format for entire applications following their rules (as WAR and EAR files can be to Java.) And they would benefit also from a more low-level packaging format dedicated to package X* libraries, and would build upon them. But they really are at different levels, and I think it is fundamental to make the distinction between both concepts.

As part of the EXPath project, and because I think this is the first step X* technologies need for several years to enable the delivery of libraries, I am particularly interested in a library packaging format.

To illustrate that, I've built a very simple prototype of a package manager for Saxon. On the one hand you have a simple GUI to install and delete packages in a repository, and on the other hand you have a shell script to launch Saxon (setting the classpath for extension functions and setting catalogs to resolve XSLT imports refering to libraries.) If those tools are built around a well-defined, open package format, other implementations could be written (for eXist, for MarkLogic, XQilla, Zorba... but also for oXygen, providing a one-click implementation to install a package and then being able to enable it in some scenarii.)

You can find the manager at http://www.fgeorges.org/purl/20090624/. You should be able to run it simply by clicking on one of the links on the launch.html page (through Java Web Start,) but you can also download the JAR file (look also in the lib/ sub-directory,) putting both JAR files in the classpath and running Java the usual way, with the main class org.expath.pkg.saxon.PackageManagerGUI (there is also a text interface with org.expath.pkg.saxon.PackageManagerTextUI.) You first have to set up an environment variable EXPATH_REPO, pointing to a directory (that will be your EXPath Packaging repository, just create an empty directory.) The interface is very simple: choose the install item in the file menu, and select the package file you want to install. To remove a package, select it in the list of installed modules and select delete in the menu.

Once a module is installed, you can use it via Saxon by adding the additional JARs to the classpath as needed (for extension functions) and by setting up the XML Catalogs support. The following script does that for you: http://www.fgeorges.org/purl/20090624/saxon. It needs a few environment variables: EXPATH_REPO as explained above, APACHE_XML_RESOLVER_JAR must point to the Apache XML Commons Resolver (see http://xml.apache.org/commons/, and be sure to pick the resolver JAR) and SAXON_HOME must point to the directory containing the Saxon JARs.

But what about the package format itself? In this prototype, this is a simple ZIP file, with the following structure:

expath-pkg.xml
expath-http-client/
   saxon/
      xsl/
         expath-http-client-saxon.xsl
      jar/
         expath-http-client-saxon.jar
      lib/
         commons-codec-1.3.jar
         ...jar

where expath-pkg.xml is the package descriptor, and expath-http-client is the directory containing one module (here the EXPath HTTP Client module.) This module is implemented as a Java extension, besides a frontend XSLT stylesheet that take care of Saxon-specifics to bind to the Java functions. During the install, an XML Catalogs file is created, to resolve the URI http://www.expath.org/mod/http-client.xsl to that stylesheet, in the local repository. One stylesheet can then simply import that URI and use the functions of the module. The real package for the HTTP Client can be downloaded at the same place: http://www.fgeorges.org/purl/20090624/expath-http-client-saxon-0.3.zip.

There are of course still a lot of work defining exactly the package format, how to handle dependencies, improving the implementation... But I think that gives the big picture. If you are interested, here is what the package descriptor looks like:

<package xmlns="http://expath.org/mod/expath-pkg">
   <module version="0.3" name="expath-http-client">
      <title>EXPath HTTP Client</title>
      <xsl>
         <import-uri>http://www.expath.org/mod/http-client.xsl</import-uri>
         <file>saxon/xsl/expath-http-client-saxon.xsl</file>
      </xsl>
   </module>
</package>

We can see the package contains one module, namely "EXPath HTTP Client," version 0.3. The URIs are used to create an XML catalog. This version of the package contains all the dependencies (the JARs used by the Java implementation of the extension functions,) but they can be also left out, and configured with the following element:

<saxon>
   <dep type="jar">
      <title>Apache Commons Codec 1.3</title>
      <home>http://jakarta.apache.org/commons/codec/</home>
   </dep>
   <dep type="jar">
      <title>Apache Commons Logging 1.1.1</title>
      <home>http://commons.apache.org/logging/</home>
   </dep>
   <dep type="jar">
      <title>Apache HTTP Client 4.0-beta2</title>
      <home>http://hc.apache.org/</home>
   </dep>
   <dep type="jar">
      <title>Apache HTTP Core 4.0</title>
      <home>http://hc.apache.org/</home>
   </dep>
   <dep type="jar">
      <title>Tagsoup 1.2</title>
      <home>http://home.ccil.org/~cowan/XML/tagsoup/</home>
      <href>http://home.ccil.org/~cowan/XML/tagsoup/tagsoup-1.2.jar</href>
   </dep>
</saxon>

The GUI does not take them into account yet, but it should propose to automatically download JARs when possible, and give the user a list of libraries and their homepage when a manual download is required. But of course, the same format can be used to package standard XSLT stylesheets, without any Java features, just by mapping the main entry point files to their public URIs.

Of course, this format will be particularly useful once precisely defined in an open spec, and if several processors support it (either natively, or through external managers.)

To end this post, I would like to introduce an idea from Jim Fuller: CXAN. I am sure most of you know CTAN for TeX, or CPAN for Perl. They are central, organized repositories of libraries for those languages, accessible throught HTTP. With a proper packaging format, it would be possible to set up such a web repository gathering XPath, XSLT, XQuery and XProc libraries and applications, installable automatically with a manager that would install a package from its name, handling dependencies and the like. But for sure, that is yet a step forward.

Labels: , , ,

Saturday, March 21, 2009

SOA Design Patterns and Web Service Contract Design & Versioning for SOA

A few weeks ago, I received the final, paper version of the book "SOA Design Patterns" that I contributed to. I was used to the drafts, and I am glad to say the final layout is really nice. Same for the previous book, "Web Service Contract Design & Versioning for SOA." More info at http://www.soapatterns.com/ and http://www.soabooks.com/.

Friday, February 27, 2009

XSLStyle and oXygen

On almost every XSLT projects I worked on, I used Ken Holman's XSLStyleTM. It enables one to document each stylesheet component (template, function, variable, module, etc.) using an XML vocabulary (DocBook and DITA are supported out of the box.) For instance, the following exerpt shows how to document a simple named template with a parameter, assuming the vocabulary has been set to DocBook:

<doc:template>
   <para>Create a paragraph with a greetings message.</para>
   <doc:param name="who">
      <para>The name of the person to address the greetings to.</para>
   </doc:param>
</doc:template>
<xsl:template name="greetings">
   <xsl:param name="who" as="xs:string"/>
   <h:p>
      <xsl:value-of select="concat('Hello, ', $who, '!')"/>
   </h:p>
</xsl:template>

XSLStyleTM is a set of stylesheets to extract this information and format it to an HTML page. Besides this formating tasks, it also checks for best-practices (did you declare the type for parameters?, etc.) I think this is a very important piece in the XSLT writer's toolbox.

Seting a project up to use XSLStyle is as easy as adding a tranform task using the XSLStyle's stylesheets to transform the project's stylesheet to HTML pages. And of course to add documentation to the stylesheets. All that is very easy, but you always have to remember where to installed XSLStyle, check for the namespace URIs to use, and the exact element names to use to document your code. Once again, that's very easy, but repetitive and time-consuming.

I use oXygen more and more for a few months. And I really enjoy it. It helps a lot automating repetitive, time-consuming tasks: create a new stylesheet with the right namespace URIs, code completion for known vocabularies, tansform scenarii, etc. And for schemas, XQuery modules and WSDL definitions, it provides actions to generate documentation, the same way XSLStyle generates documentation for XSLT modules. Unfortunately, it does not support XSLStyle yet. Actually, the scenario attached to almost all of my stylesheets is the scenario using XSLStyle.

But having XSLStyle integrated within oXygen would bring several advantages. It would always be installed alongside with oXygen. I am a freelance consultant and move a lot between different companies, using several computers, with different system administration policies. Thanks to the per-person-license model of oXygen, and its platform independence, I can take it with me everywhere, and be quickly productive, without having to install separately a JRE, Saxon, FOP, Xerces, DocBook environment, RELAX NG tools, Schematron skeletons, Emacs with nXML and others modes, and even Cygwin to be able to create shell scripts and Makefile to automate tasks. But I still have to download XSLStyle separately, open one of its stylesheets to copy and paste a documentation sample and the correct namespace bindings. Of course, it would be time saving to have an XSLStyle framework directly in oXygen.

Besides that point, oXygen could offer editing facilities for stylesheet components documentation as well. When you want to document a named template with several parameters, you have to create the documentation structure as showed above, and create a doc:param element for each parameter, with the correct name. This could be automatically done with a specific action, to add an empty documentation structure to a particular component or even to each non-documented yet componnents in the stylesheet:

There would not have anymore the need to attach the stylesheet to an XSLStyle scenario neither. When I work on a stylesheet, I like to have its current scenario bound regarding what I am working on in particular. Switching back and forth between this scenario and the XSLStyle scenario does not really make sense. It would be more productive to have a Tools > Generate Documentation > XSLT Documentation... action as for the other file type, in my humble opinion.

And finally, oXygen would even be able to report some documentation errors directly in the editor pane, as wrong names in documenting parameters.

I hope oXygen team will be intersted to add support for XSLStyle, as this would be convenient, but also would spread XSLT writing best practices advocated by XSLStyle. Anyway, thanks to the team for listening to user requests, this is too rarely the case in other companies, and of course to Ken for XSLStyle.

Labels: ,

Tuesday, December 09, 2008

FXSL currying and nestable sequences

After an interesting discussion on the FXSL Help forum, the problem of currying and nested sequences showed up again. The FXSL project provides, among other things, first-class citizen functions. Basically, it represents a function as an element. When executing such a function, the dispatching to the code is done by applying templates on that element.

An interesting feature of FXSL is the ability to curry parameters to a function, to create an other function of a lesser order. The principle is to attach parameters to the function. This new function can then be used as any other function, with specified parameters bound to specified values.

The result of currying is then another first-class citizen function. So it has to be a node, because f:apply() applies templates on it to find the code to execute. And it has to be a single item, in order to be used as any other items (in particular its behaviour in sequence handling and atomization.) The later point makes it impossible to use a sequence as result of currying.

The approach taken by FXSL for now is to create an XML element as the result of f:curry(). This element contains several information: the child fun holds the curried function (may be itself a currying), cnArgs is the cardinality of the curried function and then the childs arg hold the curried values. For instance, the expression f:curry(my:add(), 2, 1024) will return the following element:

<f-curry:f-curry xmlns:f-curry="http://fxsl.sf.net/curry">
   <fun>
      <my:add xmlns:my="urn:X-FGeorges.org:tests:curry-sref.xsl"/>
   </fun>
   <cnArgs>2</cnArgs>
   <arg t="xs:integer">1024</arg>
</f-curry:f-curry>

This approach is convenient because we can use any structure we need to represent currying. Unfortunately, the semantics of adding items to an XML tree implies to copy nodes and to make nodes from atomic values. That means that if the curried argument is an XML element, piece of a whole document, it will be copied to the element representing currying. For example if the curried function uses the ancestor axis on this curried element, it will see the f-curry:f-curry element, instead of the ancestors in the original document. That was actually the problem reported by Christoph Lange on the FXSL forum.

And this leads to other problems related to identity. For instance, items are transformed to nodes. FXSL resolves that problem by recording the initial type in the currying structure, and convert the node back to that type. While this is ok for standard simple types, that can't be applied to user-defined simple types. Another example is for validated nodes; they loose their type annotations when added to the currying structure, which can be a problem for the curried function. You can find more about this topic in Type-preserving copy in XSLT 2.0.

Actually, all those problem could be solved with a simple feature that does not exist in standard XPath: the ability to nest sequence. If we could nest sequences, or if we had a special type of sequences that wouldn't atomize when added to another sequence, we could use them as the result of currying. Even if that's not a node anymore, we could adapt f:apply() to handle those particular sequences and use its, say, first item as the node to apply templates on.

The good news is that this is simple to implement such a sequence as a Java extension in Saxon. Here is a very simple implementation. I have called it SRef, for Sequence Reference. I guess we would need something more elaborated to be efficient and general-purpose, but this is just a proof-of-concept:

package org.fgeorges.saxon;

import java.util.ArrayList;
import java.util.List;
import net.sf.saxon.om.ArrayIterator;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.trans.XPathException;

/**
 * XPath sequence reference, or non-atomizable XPath sequence.
 *
 * @author Florent Georges - fgeorges.org
 * @date 2006-12-01
 */
public class SequenceRef
{
    public SequenceRef(SequenceIterator seq) throws XPathException
    {
        myIter = seq.getAnother();
    }

    public SequenceIterator getSequence()
    {
        return myIter;
    }

    static public boolean isSequenceRef(Object obj)
    {
        return obj instanceof SequenceRef;
    }

    @Override
    public String toString()
    {
        throw new RuntimeException("toString not supported, cannot be added to a tree!");
    }

    private SequenceIterator myIter = null;
}

This implementation in Java is coupled to an simple API in XPath. Three functions are created: sref:make-sref() takes a sequence and returns an sref for this sequence, sref:sequence() takes an sref and return the original sequence, and sref:is-sref() get an item and return true if it is an sref. The following XSLT module defines those functions:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:sref="http://www.fgeorges.org/xslt/sref"
                xmlns:impl="java:org.fgeorges.saxon.SequenceRef"
                exclude-result-prefixes="xs sref impl"
                version="2.0">

   <xsl:function name="sref:make-sref" as="item()">
      <xsl:param name="seq" as="item()*"/>
      <xsl:sequence select="impl:new($seq)"/>
   </xsl:function>

   <xsl:function name="sref:sequence" as="item()*">
      <xsl:param name="ref" as="item()"/>
      <xsl:sequence select="impl:getSequence($ref)"/>
   </xsl:function>

   <xsl:function name="sref:is-sref" as="xs:boolean">
      <xsl:param name="ref" as="item()"/>
      <xsl:sequence select="impl:isSequenceRef($ref)"/>
   </xsl:function>

   <xsl:function name="sref:atomize" as="item()*">
      <xsl:param name="seq" as="item()*"/>
      <xsl:sequence select="
          for $item in $seq return
            if ( sref:is-sref($item) ) then
              sref:sequence($item)
            else
              $item"/>
   </xsl:function>

   <xsl:function name="sref:deep-atomize" as="item()*">
      <xsl:param name="seq" as="item()*"/>
      <xsl:sequence select="
          for $item in $seq return
            if ( sref:is-sref($item) ) then
              sref:deep-atomize(sref:sequence($item))
            else
              $item"/>
   </xsl:function>

</xsl:stylesheet>

With those simple functions, it is then possible to modify f:curry() and f:apply() to support (to take advantage of) SRefs. The folowing is a simple example (supporting only currying a function of cardinality 2 with a single argument). I create a first-citizen function my:add() that takes two integers and returns their sum, I write new versions of f:apply() and f:curry(), then I call my:add() both directly and with currying:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:f="http://fxsl.sf.net/"
                xmlns:my="urn:X-FGeorges.org:tests:curry-sref.xsl"
                xmlns:sref="http://www.fgeorges.org/xslt/sref"
                xmlns:impl="java:org.fgeorges.saxon.SequenceRef"
                exclude-result-prefixes="xs f my sref impl"
                version="2.0">

   <xsl:import href="sref.xsl"/>

   <xsl:output indent="yes"/>

   <!--
      The my:add() first class function.
   -->
   <xsl:variable name="my:add" as="element()">
      <my:add/>
   </xsl:variable>

   <xsl:function name="my:add" as="node()">
      <xsl:sequence select="$my:add"/>
   </xsl:function>

   <xsl:function name="my:add" as="xs:integer">
      <xsl:param name="lhs" as="xs:integer"/>
      <xsl:param name="rhs" as="xs:integer"/>
      <xsl:sequence select="$lhs + $rhs"/>
   </xsl:function>

   <xsl:template match="my:add" mode="f:FXSL">
      <xsl:param name="arg1"/>
      <xsl:param name="arg2"/>
      <xsl:sequence select="my:add($arg1, $arg2)"/>
   </xsl:template>

   <!--
      Apply on SRefs.
   -->
   <xsl:function name="f:apply-sref">
      <xsl:param name="pFunc" as="item()"/>
      <xsl:param name="arg1" as="item()*"/>
      <xsl:variable name="seq" select="sref:sequence($pFunc)"/>
      <xsl:apply-templates select="$seq[1]" mode="f:FXSL">
         <xsl:with-param name="seq" select="$seq"/>
         <xsl:with-param name="arg1" select="$arg1"/>
      </xsl:apply-templates>
   </xsl:function>

   <!--
      Currying using SRefs.
   -->
   <xsl:function name="f:curry-sref" xmlns:f-c-s="http://fxsl.sf.net/curry-sref">
      <xsl:param name="pFun" as="node()"/>
      <xsl:param name="pNargs" as="xs:integer"/>
      <xsl:param name="arg1"/>
      <xsl:variable name="curry-fun" as="element()">
         <f-c-s:f-c-s/>
      </xsl:variable>
      <xsl:sequence select="
          sref:make-sref(($curry-fun, $pFun, $pNargs, sref:make-sref($arg1)))"/>
   </xsl:function>

   <xsl:template match="f-c-s:*" mode="f:FXSL"
       xmlns:f-c-s="http://fxsl.sf.net/curry-sref">
      <xsl:param name="seq" as="item()*"/>
      <xsl:param name="arg1" as="item()*"/>
      <xsl:apply-templates select="$seq[2]" mode="f:FXSL">
         <xsl:with-param name="arg1" select="sref:sequence($seq[position() gt 3])"/>
         <xsl:with-param name="arg2" select="$arg1"/>
      </xsl:apply-templates>      
   </xsl:template>

   <!--
      The testing template.
   -->
   <xsl:template match="/">
      <root>
         <test-1>
            <xsl:sequence select="my:add(512, 1024)"/>
         </test-1>
         <test-2>
            <xsl:variable name="fun" select="f:curry-sref(my:add(), 2, 1024)"/>
            <xsl:sequence select="f:apply-sref($fun, 512)"/>
         </test-2>
      </root>
   </xsl:template>

</xsl:stylesheet>

Thanks to Christoph Lange for the original problem and to Dimitre for his ideas.

Labels: , ,

Sunday, November 09, 2008

XProc with XSLT completion in oXygen

[[ Note on 2009-11-12: oXygen 11.0 comes with built-in support for XProc and Calabash, so you shouldn't need this anymore if you have oXygen 11+. ]]

After having played a little bit with XProc, and having written a few simple XProc definitions with oXygen, I was tired to always check the step names spelling and to use copy & paste intensively. So I decided to add support for the XProc document type in oXygen.

Thanks to the XProc WG, who has published a schema as part of the current WD (and has done so in various schema languages,) the first step was quite straigthforward. Download the two RNC modules from the current WD, in appendix "D Pipeline Language Summary" (direct links: xproc.rnc and steps.rnc.) While editing an XProc definition, click the Associate Schema... button (see the screenshot below.) In the dialog box, choose RelaxNG Schema, choose the option Compact syntax and select the xproc.rnc file you have just downloaded. The only configuration to change is in Preferences / XML / XML Parser / RELAX NG, and unselect the option Check ID/IDREF (thanks, George.)

Now, you can validate your XProc definition while editing it, as well as enjoy the completion from oXygen. So far, so good. But while editing XProc definitions, you will often use small inline XSLT stylesheets (at least, I do.) And it would be great to have validation and completion for those stylesheet as well. So you have to combine the XProc schemas with XSLT schemas. And thanks to Norman Walsh, there is an RNC schema that validates both XSLT 1.0 and 2.0. You can download them from his blog (direct links: xslt.rnc, xslt10.rnc and xslt20.rnc.)

So far, you have then the schemas for XSLT, and the schemas for XProc, without XSLT. So you have to plug the former within the later. Unfortunately, the XProc RNC schema use the same pattern for p:inline for all steps (that patterns simply accepts anything.) The simple approach here is to redefine that pattern to accept anything except elements in the XSLT namespace, or to accept the xsl:stylesheet element defined in the XSLT schemas. The drawback is that this redefinition occurs for all steps; but so far, it hasn't been a restriction. The custom RNC file is just:

default namespace p = "http://www.w3.org/ns/xproc"
namespace xsl = "http://www.w3.org/1999/XSL/Transform"

include "xproc.rnc" {
   Inline =
      element inline {
         exclude-inline-prefixes.attr?,
         common.attributes,
         ( xslt | AnyButXSLT )
         # I am not sure which one is better...
         # ( xslt | AnyButStylesheet )
      }
}

xslt = external "xslt.rnc"

AnyButXSLT =
   element (* - xsl:*) {
      (_any.attr | text | Any)*
   }
AnyButStylesheet =
   element (* - (xsl:stylesheet|xsl:transform)) {
      (_any.attr | text | Any)*
   }

Just copy & paste this code to a file, for instance xproc-with-xslt.rnc (take care to adapt the two paths to the other RNC schemas as needed.) Then remove the <?oxygen RNGSchema...?> previously added by oXygen to your XProc definition, and associate now your new RNC grammar. That's all!

Labels: , ,

Thursday, October 30, 2008

Poor man's Calabash integration into oXygen

[[ Note on 2009-11-12: oXygen 11.0 comes with built-in support for XProc and Calabash, so you don't need this anymore if you have oXygen 11+. ]]

XML Calabash, the XProc processor from Norman Walsh, becomes more mature from day to day. Here is a very simple (but very limited too) way to integrate it into the great oXygen XML IDE. Well, the word integrate is maybe too much for this simple trick, that will just add a button in the toolbar to execute the currently edited XProc definition file. But at least that will prevent you to switch between your IDE and a console.

You have to register Calabash as an external tool within oXygen. Go to Tools > External Tools > Preferences > New, and fill the various fields. The point is to correctly set the working directory to ${cfd} and the command line to something like:

java -cp ".../calabash.jar:.../saxon9.jar:.../saxon9-s9api.jar"
    com.xmlcalabash.drivers.Main ${cfne}

Of course, you have to set the absolute path to the JAR files on your machine. Be sure to use ":" as the path separator on Linux and ";" on Windows. You can also set additional options like -Dcom.xmlcalabash.phonehome.email=your@email.com. In other words, just use the command line you usually use to launch Calabash.

When this is done, you will have a new button on your toolbar, called Calabash (be sure to have selected the External Tools toolbar.) When your are editing an XProc definition, you can press that button to execute it with Calabash, viewing the output in the result panel.

Labels: , ,

Wednesday, April 02, 2008

Simple SVG chart generation with XSLT

This week, for my job, I have to create a report generator for a financial company. The reports must be in PDF, so I naturally decided to use XSL-FO. Among other things, the reports contain graphical charts with, you know, financial stuff. The client wants its developers to be able to generate JPEG files themselves, so for the charts I just have to include external graphic files.

But I was curious to see if SVG was adapted to fit in this scenario. So this evening, after my working hours, I created a sample input files and started to learn a little bit about SVG. It was incredible as I was able to quickly get the result I wanted for a static SVG document.

Then the fun part started: create the XSLT stylesheet to transform the input document to the final SVG chart. The goal is of course to have a simple stylesheet that is generic enough to not be bound to specific lengths or other magic values.

And I think the result is quite interesting, assuming it was written in a few hours, without knowledge of SVG at the beginning. Of course the kind of chart is fixed, as well the input format is fixed. But it is flexible enough to adapt to various lengths, various Y axis scales, and such.

This stylesheet is not at all aimed to be used as such, but I think it can be a good strating point for similar SVG charts generation with XSLT. If I have the time, and if I want to, I'd try to make it more configurable, especially in the way the input is provided (I think FXSL can be of great help here to provide adapters for any input document type.)

Basically the input looks like the following. Those numbers are the number of post to XSL List by month, for 2007 (stolen from MarkMail.org):

<input min-value="500" step-value="100" step-number="5">
   <val v="784">Jan-07</val>
   <val v="765">Feb-07</val>
   <val v="910">Mar-07</val>
   <val v="734">Apr-07</val>
   <val v="907">May-07</val>
   <val v="626">Jun-07</val>
   <val v="865">Jul-07</val>
   <val v="682">Aug-07</val>
   <val v="790">Sep-07</val>
   <val v="725">Oct-07</val>
   <val v="649">Nov-07</val>
   <val v="577">Dec-07</val>
</input>

The result of the transformation looks like this (screenshot of the Firefox rendering of the SVG document):

And finally here is the stylesheet itself:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:svg="http://www.w3.org/2000/svg"
                xmlns:my="http://www.fgeorges.org/TMP/svg/charts#internals"
                version="2.0">

   <xsl:output indent="yes"/>

   <!--
       +- - - - - - - - - - - - - - - - - - - - - - - -+
       |  +- - - - - - - - - - - - - - - - - - - -+ nn |
       |  |                                       |    |
       |  |                                   o   |    |
       |  | . . . . . . . . . . .o. . . . . oo. . | nn |
       |  |                    oo oo      oo      |    |
       |  |         o        oo     o   oo        |    |
       |  | . . . oo oo . ooo . . . .ooo. . . . . | nn |
       |  |    ooo     ooo                        |    |
       |  |   o                                   |    |
       |  +- -|- -|- -|- -|- -|- -|- -|- -|- -|- -+ nn |
       |      x   x   x   x   x   x   x   x   x        |
       +- - - - - - - - - - - - - - - - - - - - - - - -+
       
       The outer box is the whole space that the diagram will occupy.
       The length between that imaginary box and the top-left corner
       of the diagram is represented by '$init-x' and '$init-y'.
       
       The length of the rectangle of the diagram itself (the inner
       box in the picture) is represented by '$width' and '$height'.
       
       The number of steps on the right-hand Y axis (in the picture
       there are 3 steps, that is 3 steps "between" the various "nn"s)
       is represented by @step-number in the input document.  The
       numeric value between two steps is @step-value.
       
       The diagram's plot (the "o"s) and the X axis (the "x"s) are
       represented by the input document.  Exemple of input:
       
           <input min-value="100" step-value="5" step-number="5">
              <val v="110">Jan-08</val>
              <val v="107">Feb-08</val>
              <val v="123">Mar-08</val>
           </input>
       
       In this example, the Y axis will be from 100 to 125, with a
       line (and a label) from 5 to 5.  The X axis will have 3 labels
       (from Jan to Mar) and the plot will be computed from 3 values:
       110, 107 and finally 123.
   -->
   <xsl:param name="init-x" as="xs:double" select="10"/>
   <xsl:param name="init-y" as="xs:double" select="10"/>
   <xsl:param name="width"  as="xs:double" select="500"/>
   <xsl:param name="height" as="xs:double" select="250"/>

   <xsl:variable name="baseline"  select="$height + $init-y"/>

   <!--
       For test purpose: an root SVG element that should be ok for the
       default values of the global parameters, provided an input of
       10 or 20 values.
   -->
   <xsl:template match="/">
      <svg:svg width="540" height="300">
         <svg:g>
            <xsl:apply-templates select="*"/>
         </svg:g>
      </svg:svg>
   </xsl:template>

   <!--
       The Y axis, the X axis and the plot line.
   -->
   <xsl:template match="input">
      <!-- the diagram's box -->
      <svg:rect x="{ $init-x }" y="{ $init-y }"
                width="{ $width }" height="{ $height }"
                fill="#fff" stroke="#000"/>
      <!-- the Y axis's labels and their lines -->
      <xsl:sequence select="my:lines(@min-value, @step-value, @step-number)"/>
      <xsl:variable name="len" select="$width div count(*)"/>
      <!-- the X axis's labels -->
      <xsl:apply-templates select="*">
         <xsl:with-param name="len" select="$len"/>
      </xsl:apply-templates>
      <!-- the plot line -->
      <svg:path stroke="blue" stroke-width="1" fill="none">
         <xsl:attribute name="d">
            <xsl:apply-templates select="*" mode="path">
               <xsl:with-param name="len"       select="$len"/>
               <xsl:with-param name="min"       select="@min-value"/>
               <xsl:with-param name="one-y-len" select="
                   ( $height div @step-number ) div @step-value"/>
            </xsl:apply-templates>
         </xsl:attribute>
      </svg:path>
   </xsl:template>

   <!--
       Draw a label on the X axis.
   -->
   <xsl:template match="val">
      <xsl:param name="len" as="xs:double"/>
      <xsl:variable name="x" select="my:x-pos($len, position())"/>
      <svg:path d="M { $x },{ $baseline } L { $x },{ $baseline + 5 }" stroke="#000"/>
      <svg:text x="{ $x + 10 }" y="{ $baseline + 15 }"
                transform="rotate(-45 { $x + 10 } { $baseline + 15 })"
                font-size="10px" text-anchor="end">
         <xsl:value-of select="."/>
      </svg:text>
   </xsl:template>

   <!--
       Compute one single step of an SVG path's @d, to draw the plot.
   -->
   <xsl:template match="val" mode="path">
      <xsl:param name="len"       as="xs:double"/>
      <xsl:param name="min"       as="xs:double"/>
      <xsl:param name="one-y-len" as="xs:double"/>
      <xsl:value-of select="if ( position() eq 1 ) then 'M' else 'L'"/>
      <xsl:text> </xsl:text>
      <xsl:value-of select="my:x-pos($len, position())"/>
      <xsl:text>,</xsl:text>
      <xsl:value-of select="my:y-pos($one-y-len, @v, $min)"/>
      <xsl:text> </xsl:text>
   </xsl:template>

   <!--
       The lines for each Y step, as well as the label for each Y
       step.
   -->
   <xsl:function name="my:lines" as="element()+">
      <xsl:param name="min"      as="xs:double"/>
      <xsl:param name="step-val" as="xs:double"/>
      <xsl:param name="step-num" as="xs:integer"/>
      <xsl:variable name="step-len" select="$height div $step-num"/>
      <!-- the N - 1 lines -->
      <xsl:for-each select="1 to ($step-num - 1)">
         <xsl:variable name="y" select="(. * $step-len) + $init-y"/>
         <svg:path d="M { $init-x },{ $y } L { $width + $init-x },{ $y }" stroke="#AAA"/>
      </xsl:for-each>
      <!-- the N + 1 labels -->
      <xsl:for-each select="0 to $step-num">
         <xsl:variable name="y" select="(. * $step-len) + $init-y"/>
         <svg:text x="{ $width + $init-x + 25 }" y="{ $y + 4 }" font-size="10px"
                   text-align="end" text-anchor="end" font-family="Helvetica Condensed">
            <xsl:value-of select="$min + ($step-num - .) * $step-val"/>
         </svg:text>
      </xsl:for-each>
   </xsl:function>

   <!--
       Compute the absolute X position from the ordinal position and
       the length of one X step.
   -->
   <xsl:function name="my:x-pos" as="xs:double">
      <xsl:param name="step-len" as="xs:double"/>
      <xsl:param name="position" as="xs:integer"/>
      <xsl:sequence select="($step-len * $position) - ($step-len div 2) + $init-x"/>
   </xsl:function>

   <!--
       Compute the absolute Y position for one point of the diagram's
       plot.  $one-len is the length of 1 on the Y axis, $value is the
       Y value of the plot's point, and $min is the minimal value on
       the Y axis.
   -->
   <xsl:function name="my:y-pos" as="xs:double">
      <xsl:param name="one-len" as="xs:double"/>
      <xsl:param name="value"   as="xs:double"/>
      <xsl:param name="min"     as="xs:double"/>
      <xsl:variable name="mid" select="$height div 2"/>
      <!-- scale the value to the scale [min - max] -->
      <xsl:variable name="val" select="($value - $min) * $one-len"/>
      <!-- reverse 0->$height and $height->0, 'cause in SVG y=0 is at top -->
      <xsl:variable name="rev" select="(- ($val - $mid)) + $mid"/>
      <!-- slide because our graph begins at y=$init-y -->
      <xsl:value-of select="$rev + $init-y"/>
   </xsl:function>

</xsl:stylesheet>

Labels: ,