package com.yahoo.dtf.config.transform; import java.io.ByteArrayOutputStream; import java.util.List; import javax.xml.transform.TransformerException; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.commons.jxpath.JXPathContext; import org.apache.xerces.dom.DeferredAttrImpl; import org.w3c.dom.Document; import org.w3c.dom.Node; import com.yahoo.dtf.exception.ParseException; import com.yahoo.dtf.xml.JXPathHelper; import com.yahoo.dtf.xml.XMLTransformerCache; import com.yahoo.dtf.xml.XMLUtil; /** * @dtf.feature XPath Transformer * @dtf.feature.group Transformers * @dtf.feature.desc * * <p> * We've already seen that properties can be identified by using the following * syntax ${propertyname}. Now this is simple and straightforward but sometimes * the contents the property may contain more than just plain text and in those * cases we may want to only reference certain elements within the structured * data. For this we currently have the ability to apply transformations on the * property's data using various data query languages. Currently we support * XPath on the any XML data within a property and here's a simple example of * how this works: * </p> * * <p> * So lets say we have the property myxml that contains the following xml * snipplet: * </p> * {@dtf.xml * <list> * <item>1</item> * <item>2</item> * <item>3</item> * <item>4</item> * <item>5</item> * <item>6</item> * </list>} * * <p> * Now lets say we wanted to reference the myxml property but we only want the * forth value of the child item in the XML data. This is how we would achieve * that: * </p> * * <pre> * <log>${myxml:xpath://list/item[3]}</log> * </pre> * * <p> * The Xpath language is a standard and all of the information on this standard * can be easily found by searching for XPath examples online. * </p> */ public class XPathTransformer implements Transformer { public String apply(String data, String expression) throws ParseException { Document document = XMLUtil.parseXML(data); JXPathContext ctx = JXPathContext.newContext(document); javax.xml.transform.Transformer transformer = XMLTransformerCache.checkOut(); if ( expression.contains(",[") ) { String[] parts = expression.split(",\\["); expression = parts[0]; JXPathHelper.registerNamespaces(ctx, parts[1].replace("]", "")); } List<Node> nodes = ctx.selectNodes(expression); try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); StreamResult result = new StreamResult(baos); if ( nodes.size() == 1 ) { Object obj = nodes.get(0); if ( obj instanceof DeferredAttrImpl ) { return ((DeferredAttrImpl)obj).getValue(); } else if ( !(obj instanceof Node) ) { return obj.toString(); } } /* * Default its an XML node that needs to be returned as XML */ for (int i = 0; i < nodes.size(); i++) { Node aux = nodes.get(i); DOMSource source = new DOMSource(aux); transformer.transform(source, result); } return baos.toString(); } catch (TransformerException e) { throw new ParseException("Unable to transform the xml.",e); } finally { XMLTransformerCache.checkIn(transformer); } } }