Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
0 Kudos

http://www.sdn.sap.com/irj/servlet/prt/portal/prtroot/docs/webcontent/uuid/858903f9-0b01-0010-a994-b...

http://www.sdn.sap.com/irj/servlet/prt/portal/prtroot/docs/webcontent/uuid/c73003f9-0b01-0010-aa8b-8...


Introduction

Correctly parsing XML using JAXP (SAX or DOM) is fairly straight-forward and familiar to many. However, correctly emitting XML is a little more complicated, and many resort to using simple String manipulation rather than using the API. This almost invariably results in difficulties with special characters such as & and

Parse

Parsing in DOM is extremely simple and can be done with 3 lines of code.

Modify

Modifying the structure is also quite simple. We basically iterate over the children of the Orders node, i.e. the collection of Header and Line nodes, keeping a reference to the last Header node encountered. Each Line node is then moved from its initial position as a child of Orders to be a child of the last encountered Header node.

Emit

Finally, to emit the resulting document as XML, we use a javax.xml.transform.Transformer object. This class essentially takes XML from a source and outputs it to a sink, possibly performing a transformation along the way. The transformation performed would usually be XSLT. For our purposes, we'll feed it our DOM tree as source and our OutputStream wrapped in a StreamResult as sink, and we won't have it do any actual transformation.

Complete code

Here is the complete code for the above JavaMappingDOM class.

package dk.applicon.tns.sdn.jaxp;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.InputStream;

import java.io.OutputStream;

import java.util.Map;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import javax.xml.transform.OutputKeys;

import javax.xml.transform.Transformer;

import javax.xml.transform.TransformerFactory;

import javax.xml.transform.dom.DOMSource;

import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

import org.w3c.dom.Node;

import org.w3c.dom.NodeList;

import com.sap.aii.mapping.api.StreamTransformation;

import com.sap.aii.mapping.api.StreamTransformationException;

/**

  • @author Thorsten Nordholm Søbirk

*/

public class JavaMappingDOM implements StreamTransformation {

public void setParameter(Map arg0) {

}

public void execute(InputStream inputStream, OutputStream outputStream)

  throws StreamTransformationException {

 

  try {

   // create a DOM parser

   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

   DocumentBuilder builder = factory.newDocumentBuilder();

  

   // parse input to create document tree

   Document doc = builder.parse(inputStream);

  

   // transform the tree

   Node orders = doc.getFirstChild();           // gets the root element

   NodeList children = orders.getChildNodes();  // gets children of the root

   Node lastHeader = null;

   for (int item = 0; item < children.getLength(); item++) {
Node node = children.item(item);
if ("Header".equals(node.getNodeName())) {
lastHeader = node; // store reference to last Header node
} else if ("Line".equals(node.getNodeName())) {
orders.removeChild(node); // remove Line node from root
lastHeader.appendChild(node); // add Line node to last Header
}
}

// create transformer for producing XML output
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");

// create source and result wrappers and perform transformation
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(outputStream);
transformer.transform(source, result);
} catch (Exception e) {
throw new StreamTransformationException("Mapping failed", e);
}
}


h4. About SAX


Below I'll show how to implement the same mapping using SAX. SAX uses a completely different programming model than DOM and may for many seem much less intuitive to use. As we saw above, a DOM parser produces a complete object tree representation of the XML input, leaving you the entire document to work with at once. A SAX parser works in an entirely different way. No object tree is created by the parser. Instead, as the parser works its way through the XML input, it fires parse events via callbacks to a ContentHandler that you have provided. Possible callbacks include startElement , [characters | http://java.sun.com/j2se/1.4.2/docs/api/org/xml/sax/ContentHandler.html#characters(char[], int, int)], and endElement , which are called with appropriate parameters when the parser encounters something like:

package dk.applicon.tns.sdn.jaxp;

import javax.xml.transform.Result;

import javax.xml.transform.sax.SAXTransformerFactory;

import javax.xml.transform.sax.TransformerHandler;

import javax.xml.transform.stream.StreamResult;

import org.xml.sax.Attributes;

import org.xml.sax.helpers.AttributesImpl;

/**

  • @author Thorsten Nordholm Søbirk

*/

public class XMLHelloWorld {

public static void main(String[] args) {

  try {

   // create a TransformerHandler for producing XML output

   SAXTransformerFactory factory =

    (SAXTransformerFactory) SAXTransformerFactory.newInstance();

   TransformerHandler th = factory.newTransformerHandler();

   Result r = new StreamResult(System.out);

   th.setResult(r);

     

   // send SAX events to TransformerHandler to produce XML

   th.startDocument();

   Attributes atts = new AttributesImpl();

   th.startElement("", "", "root", atts);

   char[] helloWorld = "Hello World!".toCharArray();

   th.characters(helloWorld, 0, helloWorld.length);

   th.endElement("", "", "root");

   th.endDocument();

  } catch (Exception e) {

   e.printStackTrace();

  }

}

}


h4. SAX Mapping

Before showing you how to implement the mapping using SAX, I need to briefly explain the XMLFilterImpl class. Instances of this class sit between an XMLReader (basically the SAX parser) and the application and (as the name implies) filters the SAX events coming from the XMLReader before passing them on to the application. XMLFilterImpl itself is actually a non-filter - it just passes on all events unchanged. But by subclassing XMLFilterImpl and overriding the relevant methods, we can filter the SAX events to perform the desired mapping.

Filter Implementation

For the mapping example in this blog, we only need to filter the startElement and endElement events. Recall that we want to end up with the Line elements nested under their preceding Header element. This can be achieved by withholding endElement events for Header elements and outputting them only after we have parsed past the Line elements to be nested. How do we know when we have passed all the relevant Line elements? Either when we encounter the next Header start element or when we reach the Orders end element.

Below is the complete code for the MyFilter class which extends XMLFilterImpl. Notice how we call super.startElement(...) and super.endElement(...) to pass along SAX events as required.

package dk.applicon.tns.sdn.jaxp;

import org.xml.sax.Attributes;

import org.xml.sax.SAXException;

import org.xml.sax.XMLReader;

import org.xml.sax.helpers.XMLFilterImpl;

/**

  • @author Thorsten Nordholm Søbirk

*/

public class MyFilter extends XMLFilterImpl {

private boolean holdingHeaderEnd;

private int level = 0;

public MyFilter (XMLReader parent) {

  super(parent);

}

public void startElement(

  String uri,

  String localName,

  String qName,

  Attributes atts)

  throws SAXException {

  if ((!"Line".equals(qName)) && level == 1 && holdingHeaderEnd) {

   // encountered non-Line element under root while withholding Header end

   // output the Header end

   super.endElement("", "", "Header");

   holdingHeaderEnd = false;

  }

  // always output element start

  super.startElement(uri, localName, qName, atts);

  level++;

}

public void endElement(String uri, String localName, String qName)

  throws SAXException {

  level--;

  if ("Header".equals(qName) && level == 1) {

   // withhold Header end element under root

   holdingHeaderEnd = true;

  } else if ("Orders".equals(qName) && level == 0 && holdingHeaderEnd) {

   // reached end of root (Orders) while withholding Header end element

   // output the Header end element

   super.endElement("", "", "Header");

   // output the root end element

   super.endElement(uri, localName, qName);

  } else {

   // just output other elements' end

   super.endElement(uri, localName, qName);

  }

}

}</textarea>

Mapping program

Although the actual mapping work is carried out by the MyFilter class, we still need a bit of boiler-plate code in the mapping program itself, i.e. in the class which implements StreamTransformation. This class basically just connects an XMLReader to one end of MyFilter to be able to parse the XML input, connects a TransformerHandler to the other end of the filter in order to produce XML output, and starts the parser by calling the filter's parse method. Here is the source code for the mapping program.

package dk.applicon.tns.sdn.jaxp;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.InputStream;

import java.io.OutputStream;

import java.util.Map;

import javax.xml.transform.Result;

import javax.xml.transform.sax.SAXTransformerFactory;

import javax.xml.transform.sax.TransformerHandler;

import javax.xml.transform.stream.StreamResult;

import org.xml.sax.InputSource;

import org.xml.sax.XMLReader;

import org.xml.sax.helpers.XMLFilterImpl;

import org.xml.sax.helpers.XMLReaderFactory;

import com.sap.aii.mapping.api.StreamTransformation;

import com.sap.aii.mapping.api.StreamTransformationException;

/**

  • @author Thorsten Nordholm Søbirk

*/

public class JavaMappingSAX implements StreamTransformation {

public void setParameter(Map arg0) {

}

public void execute(InputStream inputStream, OutputStream outputStream)

  throws StreamTransformationException {

 

  try {

   // create a TransformerHandler for producing XML output

   SAXTransformerFactory factory =

    (SAXTransformerFactory) SAXTransformerFactory.newInstance();

   TransformerHandler th = factory.newTransformerHandler();

   Result result = new StreamResult(outputStream);

   th.setResult(result); 

   // create a MyFilter instance based on standard XMLReader

   XMLReader reader = XMLReaderFactory.createXMLReader();

   XMLFilterImpl filter = new MyFilter(reader);

   filter.setContentHandler(th);

   // parse input, filtering to produce output

   InputSource source = new InputSource(inputStream);

   filter.parse(source);

  } catch (Exception e) {

   throw new StreamTransformationException("Mapping failed", e);

  }

}

}</textarea>

4 Comments