package com.ghc.custom.functions; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringWriter; import java.util.Vector; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.ghc.ghTester.expressions.EvalUtils; import com.ghc.ghTester.expressions.Function; /** * Changes a value in an XML file on the path to the value passed in. The node * to update can be either an element or an attribute. If you want to update an * attribute, just pass <code>null</code> for it and the parser will look just * for the matching element name. Not intended to solve every XML update * problem, but just handle some of the boilerplate in certain cases. * <p> * <b>Examples:</b><br/> * * <pre> * * "/project/@name" (update the value of the "name" * attribute in the "project" element.) * * "/project/test[last()]/@dude" (update the value of the * "dude" attribute in the last element called "test") * * "/project/test[@price>50.00]" (update the value of the test * content for all test elements that have an attribute called "price" * whose value is greater than 50.00 ) * * "/project/test[@price>50.00]/@dude" (update the value of the * "dude" attribute for all test elements that have an attribute * called "price" whose value is greater than 50.00 ) * * "/project/test[@dude='old']" (update the value of the * "test" element for all test elements that have an attribute * called "dude" whose value is equal to the string 'old') * </pre> * * </p> * <p> * This implementation uses DOM, so it may not be suitable for very large (many * megabyte) files. * * @param fileIn * the available XML file you want to update. * * @param xpathExpression * the valid XPath expression to determine which nodes will get * updated. * * See <a * href="link http://www.w3schools.com/xpath/xpath_syntax.asp">W3 * Schools XML Tutorial</a> for details on XPath syntax. * * @param newValue * the string you want to set this tag or attribute to. * * @throws IOException * if anything goes wrong, this wraps all of the other exception * types possible from setting up the factories and transformers. */ public class XPathUpdate extends Function { Function m_fXmlInput; Function m_fXPathInput; Function m_fValueInput; public XPathUpdate() { } public XPathUpdate( Function f1, Function f2, Function f3 ) { this.m_fXmlInput = f1; this.m_fXPathInput = f2; this.m_fValueInput = f3; } @Override public Function create( int size, Vector params ) { return new XPathUpdate( (Function)params.get( 0 ), (Function)params.get( 1 ), (Function)params.get( 2 ) ); } @Override public synchronized Object evaluate( Object data ) { String inputXML = m_fXmlInput.evaluateAsString( data ); String xpath = EvalUtils .getString( m_fXPathInput.evaluateAsString( data ) ); String value = m_fValueInput.evaluateAsString( data ); try { return updateValueInXml( inputXML, xpath, value ); } catch ( Exception e ) { e.printStackTrace(); } return null; } public String updateValueInXml( String xmlIn, String xpathExpression, String newValue ) throws IOException { // Set up the DOM evaluator final DocumentBuilderFactory docFactory = DocumentBuilderFactory .newInstance(); try { final DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); byte[] byteArray = xmlIn.getBytes(); ByteArrayInputStream baos = new ByteArrayInputStream( byteArray ); final Document doc = docBuilder.parse( baos ); final XPath xpath = XPathFactory.newInstance().newXPath(); NodeList nodes = (NodeList)xpath.evaluate( xpathExpression, doc, XPathConstants.NODESET ); // Update the nodes we found for ( int i = 0, len = nodes.getLength(); i < len; i++ ) { Node node = nodes.item( i ); node.setTextContent( newValue ); } // Get file ready to write final Transformer transformer = TransformerFactory.newInstance() .newTransformer(); transformer.setOutputProperty( OutputKeys.INDENT, "yes" ); transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" ); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult( writer ); transformer.transform( new DOMSource( doc ), result ); // Write file out //result.getWriter().flush(); return ( writer.toString() ); } catch ( XPathExpressionException xpee ) { throw new IOException( "Cannot parse XPath.", xpee ); } catch ( DOMException dome ) { throw new IOException( "Cannot create DOM tree", dome ); } catch ( TransformerConfigurationException tce ) { throw new IOException( "Cannot create transformer.", tce ); } catch ( IllegalArgumentException iae ) { throw new IOException( "Illegal Argument.", iae ); } catch ( ParserConfigurationException pce ) { throw new IOException( "Cannot create parser.", pce ); } catch ( SAXException saxe ) { throw new IOException( "Error reading XML document.", saxe ); } catch ( TransformerFactoryConfigurationError tfce ) { throw new IOException( "Cannot create transformer factory.", tfce ); } catch ( TransformerException te ) { throw new IOException( "Cannot write values.", te ); } } public static void main( String[] args ) { XPathUpdate xpu = new XPathUpdate(); try { System.out .println( xpu .updateValueInXml( "<?xml version=\"1.0\" ?> <earth> <country>us</country> </earth>", "/earth/country", "Azerbaijan" ) ); } catch ( Exception e ) { e.printStackTrace(); } } }