Wednesday, May 17, 2017

Custom Functions using Saxon HE

INTRODUCTION

SOFTWARES & TOOLS

  1. Eclipse IDE (Mars) with Maven Plugin
  2. Java 8
  3. Saxon-HE-9.7.0-18

EXAMPLE

Saxon has many inbuilt functions but after 9.2 version, those functions are moved to licensed versions PE & HE. We need to have proper license file to use the inbuilt functions.

In this post, we are going to learn how to write custom functions using Saxon-HE. These custom functions also called integrated extension functions.

Follow the below steps to calculate & display age using the date of birth of the employees.

Step 1

Create a Maven Project in eclipse and copy paste the below code into pom.xml dependency.
<dependency>
     <groupId>net.sf.saxon</groupId>
     <artifactId>Saxon-HE</artifactId>
     <version>9.7.0-18</version>
</dependency>



Step 2

Create a Java Class AgeCalculator.java which extends ExtensionFunctionDefinition.java as below.
package jbr.saxon;

import java.time.LocalDate;
import java.time.Period;

import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.ExtensionFunctionCall;
import net.sf.saxon.lib.ExtensionFunctionDefinition;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.StringValue;

public class AgeCalculator extends ExtensionFunctionDefinition {

@Override
public SequenceType[] getArgumentTypes() {
return new SequenceType[] { SequenceType.SINGLE_STRING };
}

@Override
public StructuredQName getFunctionQName() {
return new StructuredQName("emp", "http://example.com/saxon-extension", "ageCalc");
}

@Override
public SequenceType getResultType(SequenceType[] arg0) {
return SequenceType.SINGLE_STRING;
}

@Override
public ExtensionFunctionCall makeCallExpression() {
return new ExtensionFunctionCall() {

@Override
public Sequence call(XPathContext ctx, Sequence[] args) throws XPathException {
String output = null;

String[] input = args[0].iterate().next().getStringValue().split("-");
int year = Integer.valueOf(input[0]);
int month = Integer.valueOf(input[1]);
int dayOfMonth = Integer.valueOf(input[2]);

LocalDate dob = LocalDate.of(year, month, dayOfMonth);
output = String.valueOf(Period.between(dob, LocalDate.now()).getYears());

return StringValue.makeStringValue(output);
}

};
}
}

Note:
  1. Mention no. of arguments and its types at getArgumentTypes() method
  2. Define the output type at getResultType() method
  3. I have written inner class in makeCallExpression() method for my simple requirement, you can create separate class and extend ExtensionFunctionCall.java
  4. http://example.com/saxon-extension - you can give any url.
  5. Also, you can choose your own name for emp & ageCalc

Step 3

Create a Java class AgeCalculatorMain.java to test the custom function as below.
package jbr.saxon;

import java.io.File;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import net.sf.saxon.Configuration;
import net.sf.saxon.TransformerFactoryImpl;
import net.sf.saxon.trans.XPathException;

public class AgeCalculatorMain {

public static void main(String[] args) throws XPathException {
// Set saxon as your transformer.
System.setProperty("javax.xml.transform.TransformerFactory", "net.sf.saxon.TransformerFactoryImpl");

// specify input and output
transform("testdata/input/employees.xml", "testdata/input/employees.xsl", "testdata/output/output.html");
}

public static void transform(String sourcePath, String xsltPath, String resultDir) {
try {
TransformerFactory factory = TransformerFactory.newInstance();

if (factory instanceof TransformerFactoryImpl) {
TransformerFactoryImpl tFactoryImpl = (TransformerFactoryImpl) factory;
Configuration config = tFactoryImpl.getConfiguration();
config.registerExtensionFunction(new AgeCalculator());
}

Transformer transformer = factory.newTransformer(new StreamSource(new File(xsltPath)));
transformer.transform(new StreamSource(new File(sourcePath)), new StreamResult(new File(resultDir)));

System.out.println("Output generated successfully at: " + resultDir);
} catch (TransformerException e) {
e.printStackTrace();
}
}
}


Step 4

Create an input xml file (employees.xml)
<?xml version="1.0" encoding="UTF-8"?>
<employees>
 <employee>
   <name>Anbu</name>
   <address>Chennai</address>
   <dob>1990-05-15</dob>
   <phone>9600096000</phone>
   <email>anbu@email.com</email>
 </employee>
 <employee>
   <name>Bala</name>
   <address>Hydrabad</address>
   <dob>1989-01-04</dob>
   <phone>9700097000</phone>
   <email>bala@email.com</email>
 </employee>
 <employee>
   <name>Chandru</name>
   <address>Mumbai</address>
   <dob>1995-11-10</dob>
   <phone>9900099000</phone>
   <email>chandru@email.com</email>
 </employee>
</employees>


Step 5

Create an XSLT file (employees.xsl). Define the custom function and call it.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:emp="http://example.com/saxon-extension">
 <xsl:template match="/">
   <html>
     <body>
       <h2>Employee Details</h2>
       <table border="1">
         <tr bgcolor="#9acd32">
           <th style="text-align:left">Name</th>
           <th style="text-align:left">Address</th>
           <th style="text-align:left">Date of Birth</th>
           <th style="text-align:left">Age</th>
           <th style="text-align:left">Phone</th>
           <th style="text-align:left">Email</th>
         </tr>
         <xsl:for-each select="employees/employee">
           <tr>
             <td>
               <xsl:value-of select="name" />
             </td>
             <td>
               <xsl:value-of select="address" />
             </td>
             <td>
               <xsl:value-of select="dob" />
             </td>
             <td>
               <xsl:value-of select="emp:ageCalc(dob)" />
             </td>
             <td>
               <xsl:value-of select="phone" />
             </td>
             <td>
               <xsl:value-of select="email" />
             </td>
           </tr>
         </xsl:for-each>
       </table>
     </body>
   </html>
 </xsl:template>
</xsl:stylesheet>


OUTPUT

Now Run the AgeCalculatorMain.java and the output html file will be copied to /testdata/output/output.html
Open the output.html in any browser.
That’s all about writing a custom function using Saxon HE version. Please share your comments.
Happy Knowledge Sharing!!!

No comments :

Post a Comment