3

I am trying to use Saxon with XSLT stylesheets and using the code examples in the XSLT2 spec (http://www.w3.org/TR/xslt20/#xsl-for-each-group)

<table xsl:version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <tr>
    <th>Position</th>
    <th>Country</th>
    <th>City List</th>
    <th>Population</th>
  </tr>
  <xsl:for-each-group select="cities/city" group-by="@country">
    <tr>
      <td><xsl:value-of select="position()"/></td>
      <td><xsl:value-of select="@country"/></td>
      <td>
        <xsl:value-of select="current-group()/@name" separator=", "/>
      </td>
      <td><xsl:value-of select="sum(current-group()/@pop)"/></td>
    </tr>
  </xsl:for-each-group>
</table>

I'm using the following in my pom.xml

<dependency>
  <groupId>net.sf.saxon</groupId>
  <artifactId>Saxon-HE</artifactId>
  <version>9.6.0-3</version>
</dependency>   

and the code to run it is:

    @Test
    public void testSaxonXslt2GroupTest1() throws Exception {

        File xml_file = Fixtures.XSLT2_TEST1_XML;
        File xsl_file = Fixtures.XSLT2_TEST1_XSL;


        TransformerFactory tfactory = net.sf.saxon.TransformerFactoryImpl.newInstance();
        Transformer transformer = tfactory.newTransformer(new StreamSource(xsl_file));
        File saxonDir = new File("target/saxon/");
        saxonDir.mkdirs();
        try {
            transformer.transform(new StreamSource(xml_file),  
                new StreamResult(new FileOutputStream(new File(saxonDir, "test1.xml"))));
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

This throws an error on the output console

SystemId Unknown; Line #13; Column #70; Could not find function: current-group
SystemId Unknown; Line #13; Column #70; function token not found.
(Location of error unknown)java.lang.NullPointerException

Is this function missing in the Saxon version I am using, or am I doing something wrong?

peter.murray.rust
  • 37,407
  • 44
  • 153
  • 217

2 Answers2

2

JAXP strikes again! The problem is, you are not actually running Saxon.

When you do this:

factory = net.sf.saxon.TransformerFactoryImpl.newInstance();

it really looks as if you are calling a Saxon method, doesn't it? But in Java, static methods can't be overridden in this way (I would if I could...). You are simply calling the newInstance() method on the base class, which searches the classpath for the first XSLT processor it finds lying around. If you want to invoke Saxon explicitly, it's much better to avoid the classpath search by doing

factory = new net.sf.saxon.TransformerFactoryImpl();
Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • Thanks. All makes sense now. There's the danger that there's more than one way to do this and so I have ended up cutting and pasting from known examples and confusing things. Hopefully the example will help others to get it right – peter.murray.rust Dec 24 '14 at 00:22
1

I have now found something that works.

    @Test
    public void testSaxonXslt2GroupTest1() throws Exception {
//      http://stackoverflow.com/questions/9925483/calling-java-from-xsl-saxon  

        File xml_file = Fixtures.XSLT2_TEST1_XML;
        File xsl_file = Fixtures.XSLT2_TEST1_XSL;
        LOG.debug(FileUtils.readFileToString(xsl_file));

// replacing the fully qualified class name with a property approach, seems to work

        System.setProperty("javax.xml.transform.TransformerFactory",
                "net.sf.saxon.TransformerFactoryImpl");
        TransformerFactory tfactory = TransformerFactory.newInstance();

//

  Transformer transformer = tfactory.newTransformer(new StreamSource(xsl_file));
            File saxonDir = new File("target/saxon/");
            saxonDir.mkdirs();
            try {
                transformer.transform(new StreamSource(xml_file),  
                    new StreamResult(new FileOutputStream(new File(saxonDir, "test1.xml"))));
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }

I had thought that using the precise constructor would be sufficient, but it seems not.

peter.murray.rust
  • 37,407
  • 44
  • 153
  • 217
  • I think you can either directly do `TransformerFactory tFactory = new TransformerFactoryImpl();` with an `import net.sf.saxon.TransformerFactoryImpl;` or you indeed need to set the system property. The `newInstance` method is inherited and there is no override for it in http://saxonica.com/html/documentation/javadoc/net/sf/saxon/TransformerFactoryImpl.html. – Martin Honnen Dec 23 '14 at 14:12
  • Thanks - that seems to explain it - maybe it should be overridden or trapped somehow – peter.murray.rust Dec 23 '14 at 14:42
  • If you think that should be fixed in Saxon, you could report a bug to Saxonica or notify @MichaelKay. – Mathias Müller Dec 23 '14 at 14:47