Saturday, July 18, 2015

How To Develop And Test Custom Xpath Function For Xalan.

Introduction
  XSLT 2.0 offers a rich set of transformation tools for xml transformation. But still in some cases we may encounter very specific business data transformations which can not be handled by generic transformation function. To handle those cases we may have to use the extension capability of the underling processing engine.

Problem
 Lets imagine a legacy backend system responding with following xml payload.
 It follows the upper camel case format to construct the xml payload.

<?xml version="1.0" encoding="UTF-8"?>
<Customers>
<Customer>
<Name>Kalpa</Name>
<CardNum>786543-8739-6373899</CardNum>
</Customer>
</Customers>
view raw input hosted with ❤ by GitHub
 And the mobile client which consumes the system payload expects lower camel case format to process the payload.

<?xml version="1.0" encoding="UTF-8"?>
<customers>
<customer>
<name>Kalpa</name>
<cardNum>786543-8739-6373899</cardNum>
</customer>
</customers>
view raw out hosted with ❤ by GitHub

Solution
  We are going to implement extension function using Xalan XSLT processor. Let's divide the solution in to few steps.

   1.Create the Java implementation of the logic.

       Google guvava api has a rich set of string manipulation tools. We are going to reuse one of those for converting UpperCamelCase string to lowerCamelCase string.
package custom.xpath;
import com.google.common.base.CaseFormat;
public class CustomXpath {
/**
* Returns lower camel case of the given String input.
*
* @param elementName local name of the xml element.
* @return Converted String as lower camel case.
* @see CaseFormat
*/
public static String toLowerCamelCase(String elementName) {
return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, elementName);
}
}
view raw CustomXpath hosted with ❤ by GitHub

   2. Plug the custom function to XSLT.
       
       We have define new namespace with following pattern.
       xmlns: prefix ="xalan:// packagename.classname "

       In our example we have 
          xmlns:custom = "xalan://custom.xpath.CustomXpath"

       And we can call the function as follows
         {custom:toLowerCamelCase(local-name())}

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:custom="xalan://custom.xpath.CustomXpath">
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes" />
<xsl:template match="node()"/>
<xsl:element name="{custom:toLowerCamelCase(local-name())}">
<xsl:value-of select="text()"></xsl:value-of>
<xsl:apply-templates select="node()|@*" />
</xsl:element>
</xsl:template>
<xsl:template match="text() | @*">
</xsl:template>
<xsl:template match="@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
view raw customXpath hosted with ❤ by GitHub

    3. Test the custom Xpath function.

       As the final step we are creating a Junit test for test the custom Xpath function. One thing to note here is we are not using the default xslt processor instead we set the transformerfactory as "org.apache.xalan.processor.TransformerFactoryImpl" so that transformation will be done using Xalan apis.

package custom.xpath;
import java.io.File;
import java.io.FileNotFoundException;
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 junit.framework.TestCase;
import org.junit.Test;
public class CustomXpathTest extends TestCase {
public CustomXpathTest() {
}
@Test
public void testToLowerCase() throws FileNotFoundException,
TransformerException {
System.setProperty("javax.xml.transform.TransformerFactory",
"org.apache.xalan.processor.TransformerFactoryImpl");
transform("src/test/resources/input.xml",
"src/test/resources/customXpath.xslt",
"src/test/resources/out.xml");
}
public static void transform(String sourcePath, String xsltPath,
String resultDir) {
TransformerFactory tFactory = TransformerFactory.newInstance();
try {
Transformer transformer = tFactory.newTransformer(new StreamSource(
new File(xsltPath)));
transformer.transform(new StreamSource(new File(sourcePath)),
new StreamResult(new File(resultDir)));
} catch (Exception e) {
e.printStackTrace();
}
}
}
view raw CustomXpathTest hosted with ❤ by GitHub

Conclusion
Developing a custom Xpath function and plugin it to your code is not that hard but always look for existing Xpath function or existing extension function set like EXSLT. After all its good old Java , open for you to play with.  
You can find the eclipse project with the sample code at  https://github.com/KalpaD/xalan_custom_xpath_sample