How to get XML element path using stax/stax2? - java

I want to get element path while parsing XML using java StAX2 parser.
How to get information about the current element path?
<root>
<a><b>x</b></a>
</root>
In this example the path is /root/a/b.

Keep a stack. Push the element name on START_ELEMENT and pop it on END_ELEMENT.
Here's a short example. It does nothing other than print the path of the element being processed.
public static void main(String[] args) throws IOException, XMLStreamException {
try (FileInputStream in = new FileInputStream("test.xml")) {
XMLInputFactory factory = XMLInputFactory.newFactory();
XMLStreamReader reader = factory.createXMLStreamReader(in);
LinkedList<String> path = new LinkedList<>();
int next;
while ((next = reader.next()) != XMLStreamConstants.END_DOCUMENT) {
switch (next) {
case XMLStreamConstants.START_ELEMENT:
// push the name of the current element onto the stack
path.addLast(reader.getLocalName());
// print the path with '/' delimiters
System.out.println("Reading /" + String.join("/", path));
break;
case XMLStreamConstants.END_ELEMENT:
// pop the name of the element being closed
path.removeLast();
break;
}
}
}
}

"The chronicler's duty"
Method 1: dedicated stack, #teppic suggestion
try (InputStream in = new ByteArrayInputStream(xml.getBytes())) {
final XMLInputFactory2 factory = (XMLInputFactory2) XMLInputFactory.newInstance();
final XMLStreamReader2 reader = (XMLStreamReader2) factory.createXMLStreamReader(in);
Stack<String> pathStack = new Stack<>();
while (reader.hasNext()) {
reader.next();
if (reader.isStartElement()) {
pathStack.push(reader.getLocalName());
processPath('/' + String.join("/", pathStack));
} else if (reader.isEndElement()) {
pathStack.pop();
}
}
}
Method 2 (ugly): hacking Woodstox's InputElementStack
Implementing adapter to access InputElementStack, its protected mCurrElement and interate parents (this slows down algoritm).
package com.ctc.wstx.sr;
import java.util.LinkedList;
public class StackUglyAdapter {
public static String PATH_SEPARATOR = "/";
private InputElementStack stack;
public StackUglyAdapter(InputElementStack stack) {
this.stack = stack;
}
public String getCurrElementLocalName() {
return this.stack.mCurrElement.mLocalName;
}
public String getCurrElementPath() {
LinkedList<String> list = new LinkedList<String>();
Element el = this.stack.mCurrElement;
while (el != null) {
list.addFirst(el.mLocalName);
el = el.mParent;
}
return PATH_SEPARATOR+String.join(PATH_SEPARATOR,list);
}
}
example of use:
try (final InputStream in = new ByteArrayInputStream(xml.getBytes())) {
final XMLInputFactory2 factory =
(XMLInputFactory2) XMLInputFactory.newInstance();
final XMLStreamReader2 reader =
(XMLStreamReader2) factory.createXMLStreamReader(in);
final StackUglyAdapter stackAdapter =
new StackUglyAdapter(((StreamReaderImpl) reader).getInputElementStack());
while (reader.hasNext()) {
reader.next();
if (reader.isStartElement()) {
processPath(stackAdapter.getCurrElementPath());
}
}
}
Method 1 with dedicated stack is better, because is API implementation-independent and is just as fast as the Method 2.

Related

stop stax parser from executing

I am looking for a way to stop parser once i get my tag content. How can i do this? Also how i can know now i got a hit on my tag i am searching.
public class XmlParser {
public String getValue(final String xml, final String tag) throws XMLStreamException {
String tagContent = null;
final XMLInputFactory factory = XMLInputFactory.newInstance();
final XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xml));
while (reader.hasNext()) {
final int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
break;
case XMLStreamConstants.CHARACTERS:
tagContent = reader.getText().trim();
break;
case XMLStreamConstants.END_ELEMENT:
break;
}
}
return tagContent;
}
}
boolean finished = false;
while(!finished && reader.hasNext()) {
final int event = reader.next();
//set finished = true if you hit an event that means you want to stop parsing
}
//don't forget to close the reader and the I/O streams

Transform XML to declare all namespaces on root element

Is there a simple Java method way of "moving" all XML namespace declarations of an XML document to the root element? Due to a bug in parser implementation of an unnamed huge company, I need to programmatically rewrite our well formed and valid RPC requests in a way that the root element declares all used namespaces.
Not OK:
<document-element xmlns="uri:ns1">
<foo>
<bar xmlns="uri:ns2" xmlns:ns3="uri:ns3">
<ns3:foobar/>
<ns1:sigh xmlns:ns1="uri:ns1"/>
</bar>
</foo>
</document-element>
OK:
<document-element xmlns="uri:ns1" xmlns:ns1="uri:ns1" xmlns:ns2="uri:ns2" xmlns:ns3="uri:ns3">
<foo>
<ns2:bar>
<ns3:foobar/>
<ns1:sigh/>
</ns2:bar>
</foo>
</document-element>
Generic names for missing prefixes are acceptable. Default namespace may stay or be replaced/added as long as it is defined on the root element. I don't really mind which specific XML technology is used to achieve this (I would prefer to avoid DOM though).
To clarify, this answer refers to what I'd like to achieve as redeclaring namespace declarations within root element scope (entire document) on the root element. Essentially the related question is asking why oh why would anyone implement what I now need to work around.
Wrote a two-pass StAX reader/writer, which is simple enough.
import java.io.*;
import java.util.*;
import javax.xml.stream.*;
import javax.xml.stream.events.*;
public class NamespacesToRoot {
private static final String GENERATED_PREFIX = "pfx";
private final XMLInputFactory inputFact;
private final XMLOutputFactory outputFact;
private final XMLEventFactory eventFactory;
private NamespacesToRoot() {
inputFact = XMLInputFactory.newInstance();
outputFact = XMLOutputFactory.newInstance();
eventFactory = XMLEventFactory.newInstance();
}
public String transform(String xmlString) throws XMLStreamException {
Map<String, String> pfxToNs = new HashMap<String, String>();
XMLEventReader reader = null;
// first pass - analyze
try {
if (xmlString == null || xmlString.isEmpty()) {
throw new IllegalArgumentException("xmlString is null or empty");
}
StringReader stringReader = new StringReader(xmlString);
XMLStreamReader streamReader = inputFact.createXMLStreamReader(stringReader);
reader = inputFact.createXMLEventReader(streamReader);
while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
if (event.isStartElement()) {
buildNamespaces(event, pfxToNs);
}
}
System.out.println(pfxToNs);
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (XMLStreamException ex) {
}
}
// reverse mapping, also gets rid of duplicates
Map<String, String> nsToPfx = new HashMap<String, String>();
for (Map.Entry<String, String> entry : pfxToNs.entrySet()) {
nsToPfx.put(entry.getValue(), entry.getKey());
}
List<Namespace> namespaces = new ArrayList<Namespace>(nsToPfx.size());
for (Map.Entry<String, String> entry : nsToPfx.entrySet()) {
namespaces.add(eventFactory.createNamespace(entry.getValue(), entry.getKey()));
}
// second pass - rewrite
XMLEventWriter writer = null;
try {
StringWriter stringWriter = new StringWriter();
writer = outputFact.createXMLEventWriter(stringWriter);
StringReader stringReader = new StringReader(xmlString);
XMLStreamReader streamReader = inputFact.createXMLStreamReader(stringReader);
reader = inputFact.createXMLEventReader(streamReader);
boolean rootElement = true;
while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
if (event.isStartElement()) {
StartElement origStartElement = event.asStartElement();
String prefix = nsToPfx.get(origStartElement.getName().getNamespaceURI());
String namespace = origStartElement.getName().getNamespaceURI();
String localName = origStartElement.getName().getLocalPart();
Iterator attributes = origStartElement.getAttributes();
Iterator namespaces_;
if (rootElement) {
namespaces_ = namespaces.iterator();
rootElement = false;
} else {
namespaces_ = null;
}
writer.add(eventFactory.createStartElement(
prefix, namespace, localName, attributes, namespaces_));
} else {
writer.add(event);
}
}
writer.flush();
return stringWriter.toString();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (XMLStreamException ex) {
}
try {
if (writer != null) {
writer.close();
}
} catch (XMLStreamException ex) {
}
}
}
private void buildNamespaces(XMLEvent event, Map<String, String> pfxToNs) {
System.out.println("el: " + event);
StartElement startElement = event.asStartElement();
Iterator nsIternator = startElement.getNamespaces();
while (nsIternator.hasNext()) {
Namespace nsAttr = (Namespace) nsIternator.next();
if (nsAttr.isDefaultNamespaceDeclaration()) {
System.out.println("need to generate a prefix for " + nsAttr.getNamespaceURI());
generatePrefix(nsAttr.getNamespaceURI(), pfxToNs);
} else {
System.out.println("add prefix binding for " + nsAttr.getPrefix() + " --> " + nsAttr.getNamespaceURI());
addPrefix(nsAttr.getPrefix(), nsAttr.getNamespaceURI(), pfxToNs);
}
}
}
private void generatePrefix(String namespace, Map<String, String> pfxToNs) {
int i = 1;
String prefix = GENERATED_PREFIX + i;
while (pfxToNs.keySet().contains(prefix)) {
i++;
prefix = GENERATED_PREFIX + i;
}
pfxToNs.put(prefix, namespace);
}
private void addPrefix(String prefix, String namespace, Map<String, String> pfxToNs) {
String existingNs = pfxToNs.get(prefix);
if (existingNs != null) {
if (existingNs.equals(namespace)) {
// nothing to do
} else {
// prefix clash, need to rename this prefix or reuse an existing
// one
if (pfxToNs.values().contains(namespace)) {
// reuse matching prefix
} else {
// rename
generatePrefix(namespace, pfxToNs);
}
}
} else {
// need to add this prefix
pfxToNs.put(prefix, namespace);
}
}
public static void main(String[] args) throws XMLStreamException {
String xmlString = "" +
"<document-element xmlns=\"uri:ns1\" attr=\"1\">\n" +
" <foo>\n" +
" <bar xmlns=\"uri:ns2\" xmlns:ns3=\"uri:ns3\">\n" +
" <ns3:foobar ns3:attr1=\"meh\" />\n" +
" <ns1:sigh xmlns:ns1=\"uri:ns1\"/>\n" +
" </bar>\n" +
" </foo>\n" +
"</document-element>";
System.out.println(xmlString);
NamespacesToRoot transformer = new NamespacesToRoot();
System.out.println(transformer.transform(xmlString));
}
}
Note that this is just fast example code which could use some tweaks, but is also a good start for anyone with a similar problem.
Below is a simple app does the namespace re-declaration... based on XPath and VTD-XML.
import com.ximpleware.*;
import java.io.*;
public class moveNSDeclaration {
public static void main(String[] args) throws IOException, VTDException{
// TODO Auto-generated method stub
VTDGen vg = new VTDGen();
String xml="<document-element xmlns=\"uri:ns1\">\n"+
"<foo>\n"+
"<bar xmlns=\"uri:ns2\" xmlns:ns3=\"uri:ns3\">\n"+
"<ns3:foobar/>\n"+
"<ns1:sigh xmlns:ns1=\"uri:ns1\"/>\n"+
"</bar>\n"+
"</foo>\n"+
"</document-element>\n";
vg.setDoc(xml.getBytes());
vg.parse(false); // namespace unaware to all name space nodes addressable using xpath #*
VTDNav vn = vg.getNav();
XMLModifier xm = new XMLModifier(vn);
FastIntBuffer fib = new FastIntBuffer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// get the index value of xmlns declaration of root element
AutoPilot ap =new AutoPilot (vn);
ap.selectXPath("//#*");
int i=0;
//remove all ns node under root element
//save those nodes to be re-inserted into the root element up on verification of uniqueness
while((i=ap.evalXPath())!=-1){
if (vn.getTokenType(i)==VTDNav.TOKEN_ATTR_NS){
xm.remove(); //remove all ns node
fib.append(i);
}
}
//remove redundant ns nodes
for (int j=0;j<fib.size();j++){
if (fib.intAt(j)!=-1){
for (i=j+1;i<fib.size();i++){
if (fib.intAt(i)!=-1)
if (vn.compareTokens(fib.intAt(j), vn, fib.intAt(i))==0){
fib.modifyEntry(i, -1);
}
}
}
}
// compose a string to insert back into the root element containing all subordinate ns nodes
for (int j=0;j<fib.size();j++){
if (fib.intAt(j)!=-1){
int os = vn.getTokenOffset(fib.intAt(j));
int len = vn.getTokenOffset(fib.intAt(j)+1)+vn.getTokenLength(fib.intAt(j)+1)+1-os;
//System.out.println(" os len "+ os + " "+len);
//System.out.println(vn.toString(os,len));
baos.write(" ".getBytes());
baos.write(vn.getXML().getBytes(),os,len);
}
}
byte[] attrBytes = baos.toByteArray();
vn.toElement(VTDNav.ROOT);
xm.insertAttribute(attrBytes);
//System.out.println(baos.toString());
baos.reset();
xm.output(baos);
System.out.println(baos.toString());
}
}
Output looks like
<document-element xmlns="uri:ns2" xmlns:ns3="uri:ns3" xmlns:ns1="uri:ns1" >
<foo>
<bar >
<ns3:foobar/>
<ns1:sigh />
</bar>
</foo>
</document-element>

JSON to JSON conversion in Java

I am trying to acheive JSON to JSON conversion in java based on the specification given on runtime.
Example : if at runtime source : com.gsdetails.gname,target : com.track.trackName (i.e source field should be mapped to target field in generated JSON)
My approach was to create N-array tree for the specification part and do breadth first travesal (get queue with it to craeate structure for resulting json)
I am using Jackson api to create tree from input JSON and traversing both queue(bfs) and input tree to create resulting json.
Unable to get expected output
PS : I thought of using JOLT api but it will not serve my purpose
Tree (for specification)
public class TrieBuilder {
public static void main(String[] args) throws Exception {
createSpec();
}
public static Trie createSpec() throws Exception {
BufferedReader reader = new BufferedReader(
new FileReader(""));
String currentLine = reader.readLine();
Trie trie = new Trie();
while (currentLine != null) {
String[] lines = currentLine.split(",");
String sourceLine = lines[0];
String targetLine = lines[1];
String sourcePath = sourceLine.split("=")[1];
String targetPath = targetLine.split("=")[1];
trie.insertWord(sourcePath.trim(), targetPath.trim());
currentLine = reader.readLine();
}
return trie;
}
}
class TrieNode {
String source;// consider this as content/reference point of a tree
String target;
boolean isEnd;
int count;
List childList;
boolean isRoot;
/* Constructor */
public TrieNode(String source, String target) {
childList = new ArrayList<TrieNode>();
isEnd = false;
count = 0;
this.source = source;
this.target = target;
}
public TrieNode subNodeWord(String word) {
if (childList != null) {
for (TrieNode eachChild : childList)
if (eachChild.source.equals(word))
return eachChild;
}
return null;
}
}
class Trie {
public TrieNode root;
/* Constructor */
public Trie() {
root = new TrieNode("", "");
}
public void insertWord(String sourceWords, String targetWords) {
if (searchWord(sourceWords) == true)
return;
TrieNode current = root;
String[] sourceArray = sourceWords.split(":");
String[] targetArray = targetWords.split(":");
for (int i = 0; i < sourceArray.length; i++) {
TrieNode child = current.subNodeWord(sourceArray[i]);
if (child != null) {
current = child;
} else {
current.childList.add(new TrieNode(sourceArray[i],
targetArray[i]));
current = current.subNodeWord(sourceArray[i]);
}
current.count++;
}
current.isEnd = true;
}
public boolean searchWord(String words) {
TrieNode current = root;
for (String word : words.split(":")) {
if (current.subNodeWord(word) == null) {
return false;
} else {
current = current.subNodeWord(word);
}
}
if (current.isEnd == true)
return true;
return false;
}
public Queue<TrieNode> bfsTraversal(TrieNode node) {
// TODO need to add logic for bfs/dfs for traversing the trie
Queue<TrieNode> queue = new LinkedList<>();
Queue<TrieNode> tempQueue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
TrieNode tempNode = queue.poll();
tempQueue.add(tempNode);
int counter = tempNode.childList.size(), i = 0;
if (tempNode == null)
break;
if (!tempNode.source.isEmpty())
System.out.println("Source :" + tempNode.source
+ " Target : " + tempNode.target);
while (i < counter) {
queue.add(tempNode.childList.get(i++));
}
}
tempQueue.poll();
return tempQueue;
}
Source to target mapping file :
source = com:track:trackDetails:fname, target = gsUser:gsProp:gsDetails:gsFirstName
source = com:track:trackDetails:lname, target = gsUser:gsProp:gsDetails:gsLastName
helper class (Actual transform):
public class JsonHelperClass{
// private Files file = null;// create a tempfile
private JsonNodeFactory factory;
private JsonFactory jsonFactory;
private ObjectMapper mapper;
private JsonNode jsonRoot;
private Queue<TrieNode> queue;
// private JsonParser jsonParser =
public JsonHelperClass() throws JsonProcessingException, IOException {
this.factory = JsonNodeFactory.instance;
this.jsonFactory = new JsonFactory();
this.mapper = new ObjectMapper();
this.jsonRoot = mapper.readTree(new File("json with data"));
}
public static void main(String[] args) throws Exception, Exception {
JsonHelperClass helperClass = new JsonHelperClass();
helperClass.jsonCreator();
ObjectNode objectNode = null;
ObjectNode result = helperClass.createJsonRecursively(objectNode);
System.out.println(result.toString());
}
public void jsonCreator() throws Exception {
Trie trie = TrieBuilder.createSpec();
queue = trie.bfsTraversal(trie.root);
}
public ObjectNode createJsonRecursively(ObjectNode outputJson) throws Exception {
TrieNode nodeOfQueue = queue.poll();
if(outputJson == null){
// create a root of the JSON
outputJson = factory.objectNode();
outputJson.put(nodeOfQueue.target, createJsonRecursively(outputJson));
}else if (jsonRoot.get(nodeOfQueue.source).isObject()){
// create an object to conatin other values/object
ObjectNode objectNode = factory.objectNode();
objectNode.put(nodeOfQueue.target,createJsonRecursively(outputJson));
outputJson.putAll(objectNode);
}else if(jsonRoot.get(nodeOfQueue.source).isArray()){
// create an array node and call for to create value it contains
ArrayNode arrayNode = factory.arrayNode();
int size = jsonRoot.get(nodeOfQueue.source).size();
for(int index = 0 ; index < size ; index++){
arrayNode.add(jsonRoot.get(nodeOfQueue.source).get(index));
}
outputJson.put(nodeOfQueue.target,arrayNode);
}else if(nodeOfQueue.isEnd){
// create leaf node
outputJson.put(nodeOfQueue.target, jsonRoot.get(nodeOfQueue.source));
return outputJson;
}
return outputJson;
}

Read escaped quote as escaped quote from xml

I load xml file into DOM model and analyze it.
The code for that is:
public class MyTest {
public static void main(String[] args) {
Document doc = XMLUtils.fileToDom("MyTest.xml");//Loads xml data to DOM
Element rootElement = doc.getDocumentElement();
NodeList nodes = rootElement.getChildNodes();
Node child1 = nodes.item(1);
Node child2 = nodes.item(3);
String str1 = child1.getTextContent();
String str2 = child2.getTextContent();
if(str1 != null){
System.out.println(str1.equals(str2));
}
System.out.println();
System.out.println(str1);
System.out.println(str2);
}
}
MyTest.xml
<tests>
<test name="1">ff1 "</test>
<test name="2">ff1 "</test>
</tests>
Result:
true
ff1 "
ff1 "
Desired result:
false
ff1 "
ff1 "
So I need to distinguish these two cases: when the quote is escaped and is not.
Please help.
Thank you in advance.
P.S. The code for XMLUtils#fileToDom(String filePath), a snippet from XMLUtils class:
static {
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
dFactory.setNamespaceAware(false);
dFactory.setValidating(false);
try {
docNonValidatingBuilder = dFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
}
}
public static DocumentBuilder getNonValidatingBuilder() {
return docNonValidatingBuilder;
}
public static Document fileToDom(String filePath) {
Document doc = getNonValidatingBuilder().newDocument();
File f = new File(filePath);
if(!f.exists())
return doc;
try {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
DOMResult result = new DOMResult(doc);
StreamSource source = new StreamSource(f);
transformer.transform(source, result);
} catch (Exception e) {
return doc;
}
return doc;
}
I've take a look on source code of apache xerces and propose my solution (but it is monkey patch).
I've wrote simple class
package a;
import java.io.IOException;
import org.apache.xerces.impl.XMLDocumentScannerImpl;
import org.apache.xerces.parsers.NonValidatingConfiguration;
import org.apache.xerces.xni.XMLString;
import org.apache.xerces.xni.XNIException;
import org.apache.xerces.xni.parser.XMLComponent;
public class MyConfig extends NonValidatingConfiguration {
private MyScanner myScanner;
#Override
#SuppressWarnings("unchecked")
protected void configurePipeline() {
if (myScanner == null) {
myScanner = new MyScanner();
addComponent((XMLComponent) myScanner);
}
super.fProperties.put(DOCUMENT_SCANNER, myScanner);
super.fScanner = myScanner;
super.fScanner.setDocumentHandler(this.fDocumentHandler);
super.fLastComponent = fScanner;
}
private static class MyScanner extends XMLDocumentScannerImpl {
#Override
protected void scanEntityReference() throws IOException, XNIException {
// name
String name = super.fEntityScanner.scanName();
if (name == null) {
reportFatalError("NameRequiredInReference", null);
return;
}
super.fDocumentHandler.characters(new XMLString(("&" + name + ";")
.toCharArray(), 0, name.length() + 2), null);
// end
if (!super.fEntityScanner.skipChar(';')) {
reportFatalError("SemicolonRequiredInReference",
new Object[] { name });
}
fMarkupDepth--;
}
}
}
You need to add only next line to your main method before start parsing
System.setProperty(
"org.apache.xerces.xni.parser.XMLParserConfiguration",
"a.MyConfig");
And you will have expected result:
false
ff1 "
ff1 "
Looks like you can get the TEXT_NODE child and use getNodeValue (assuming it's not NULL):
public static String getRawContent(Node n) {
if (n == null) {
return null;
}
Node n1 = getChild(n, Node.TEXT_NODE);
if (n1 == null) {
return null;
}
return n1.getNodeValue();
}
Grabbed that from:
http://www.java2s.com/Code/Java/XML/Gettherawtextcontentofanodeornullifthereisnotext.htm
There is no way to do this for the internal entities. XML does not support this concept. Internal entities are just a different way to write the same PSVI content into the text, they are not distinctive.

Reading multiple xml documents from a socket in java

I'm writing a client which needs to read multiple consecutive small XML documents over a socket. I can assume that the encoding is always UTF-8 and that there is optionally delimiting whitespace between documents. The documents should ultimately go into DOM objects. What is the best way to accomplish this?
The essense of the problem is that the parsers expect a single document in the stream and consider the rest of the content junk. I thought that I could artificially end the document by tracking the element depth, and creating a new reader using the existing input stream. E.g. something like:
// Broken
public void parseInputStream(InputStream inputStream) throws Exception
{
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLOutputFactory xof = XMLOutputFactory.newInstance();
XMLEventFactory eventFactory = XMLEventFactory.newInstance();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document doc = documentBuilder.newDocument();
XMLEventWriter domWriter = xof.createXMLEventWriter(new DOMResult(doc));
XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(inputStream);
XMLEventReader reader = factory.createXMLEventReader(xmlStreamReader);
int depth = 0;
while (reader.hasNext()) {
XMLEvent evt = reader.nextEvent();
domWriter.add(evt);
switch (evt.getEventType()) {
case XMLEvent.START_ELEMENT:
depth++;
break;
case XMLEvent.END_ELEMENT:
depth--;
if (depth == 0)
{
domWriter.add(eventFactory.createEndDocument());
System.out.println(doc);
reader.close();
xmlStreamReader.close();
xmlStreamReader = factory.createXMLStreamReader(inputStream);
reader = factory.createXMLEventReader(xmlStreamReader);
doc = documentBuilder.newDocument();
domWriter = xof.createXMLEventWriter(new DOMResult(doc));
domWriter.add(eventFactory.createStartDocument());
}
break;
}
}
}
However running this on input such as <a></a><b></b><c></c> prints the first document and throws an XMLStreamException. Whats the right way to do this?
Clarification: Unfortunately the protocol is fixed by the server and cannot be changed, so prepending a length or wrapping the contents would not work.
Length-prefix each document (in bytes).
Read the length of the first document from the socket
Read that much data from the socket, dumping it into a ByteArrayOutputStream
Create a ByteArrayInputStream from the results
Parse that ByteArrayInputStream to get the first document
Repeat for the second document etc
IIRC, XML documents can have comments and processing-instructions at the end, so there's no real way of telling exactly when you have come to the end of the file.
A couple of ways of handling the situation have already been mentioned. Another alternative is to put in an illegal character or byte into the stream, such as NUL or zero. This has the advantage that you don't need to alter the documents and you never need to buffer an entire file.
just change to whatever stream
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;
public class LogParser {
private XMLInputFactory inputFactory = null;
private XMLStreamReader xmlReader = null;
InputStream is;
private int depth;
private QName rootElement;
private static class XMLStream extends InputStream
{
InputStream delegate;
StringReader startroot = new StringReader("<root>");
StringReader endroot = new StringReader("</root>");
XMLStream(InputStream delegate)
{
this.delegate = delegate;
}
public int read() throws IOException {
int c = startroot.read();
if(c==-1)
{
c = delegate.read();
}
if(c==-1)
{
c = endroot.read();
}
return c;
}
}
public LogParser() {
inputFactory = XMLInputFactory.newInstance();
}
public void read() throws Exception {
is = new XMLStream(new FileInputStream(new File(
"./myfile.log")));
xmlReader = inputFactory.createXMLStreamReader(is);
while (xmlReader.hasNext()) {
printEvent(xmlReader);
xmlReader.next();
}
xmlReader.close();
}
public void printEvent(XMLStreamReader xmlr) throws Exception {
switch (xmlr.getEventType()) {
case XMLStreamConstants.END_DOCUMENT:
System.out.println("finished");
break;
case XMLStreamConstants.START_ELEMENT:
System.out.print("<");
printName(xmlr);
printNamespaces(xmlr);
printAttributes(xmlr);
System.out.print(">");
if(rootElement==null && depth==1)
{
rootElement = xmlr.getName();
}
depth++;
break;
case XMLStreamConstants.END_ELEMENT:
System.out.print("</");
printName(xmlr);
System.out.print(">");
depth--;
if(depth==1 && rootElement.equals(xmlr.getName()))
{
rootElement=null;
System.out.println("finished element");
}
break;
case XMLStreamConstants.SPACE:
case XMLStreamConstants.CHARACTERS:
int start = xmlr.getTextStart();
int length = xmlr.getTextLength();
System.out
.print(new String(xmlr.getTextCharacters(), start, length));
break;
case XMLStreamConstants.PROCESSING_INSTRUCTION:
System.out.print("<?");
if (xmlr.hasText())
System.out.print(xmlr.getText());
System.out.print("?>");
break;
case XMLStreamConstants.CDATA:
System.out.print("<![CDATA[");
start = xmlr.getTextStart();
length = xmlr.getTextLength();
System.out
.print(new String(xmlr.getTextCharacters(), start, length));
System.out.print("]]>");
break;
case XMLStreamConstants.COMMENT:
System.out.print("<!--");
if (xmlr.hasText())
System.out.print(xmlr.getText());
System.out.print("-->");
break;
case XMLStreamConstants.ENTITY_REFERENCE:
System.out.print(xmlr.getLocalName() + "=");
if (xmlr.hasText())
System.out.print("[" + xmlr.getText() + "]");
break;
case XMLStreamConstants.START_DOCUMENT:
System.out.print("<?xml");
System.out.print(" version='" + xmlr.getVersion() + "'");
System.out.print(" encoding='" + xmlr.getCharacterEncodingScheme()
+ "'");
if (xmlr.isStandalone())
System.out.print(" standalone='yes'");
else
System.out.print(" standalone='no'");
System.out.print("?>");
break;
}
}
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
new LogParser().read();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void printName(XMLStreamReader xmlr) {
if (xmlr.hasName()) {
System.out.print(getName(xmlr));
}
}
private static String getName(XMLStreamReader xmlr) {
if (xmlr.hasName()) {
String prefix = xmlr.getPrefix();
String uri = xmlr.getNamespaceURI();
String localName = xmlr.getLocalName();
return getName(prefix, uri, localName);
}
return null;
}
private static String getName(String prefix, String uri, String localName) {
String name = "";
if (uri != null && !("".equals(uri)))
name += "['" + uri + "']:";
if (prefix != null)
name += prefix + ":";
if (localName != null)
name += localName;
return name;
}
private static void printAttributes(XMLStreamReader xmlr) {
for (int i = 0; i < xmlr.getAttributeCount(); i++) {
printAttribute(xmlr, i);
}
}
private static void printAttribute(XMLStreamReader xmlr, int index) {
String prefix = xmlr.getAttributePrefix(index);
String namespace = xmlr.getAttributeNamespace(index);
String localName = xmlr.getAttributeLocalName(index);
String value = xmlr.getAttributeValue(index);
System.out.print(" ");
System.out.print(getName(prefix, namespace, localName));
System.out.print("='" + value + "'");
}
private static void printNamespaces(XMLStreamReader xmlr) {
for (int i = 0; i < xmlr.getNamespaceCount(); i++) {
printNamespace(xmlr, i);
}
}
private static void printNamespace(XMLStreamReader xmlr, int index) {
String prefix = xmlr.getNamespacePrefix(index);
String uri = xmlr.getNamespaceURI(index);
System.out.print(" ");
if (prefix == null)
System.out.print("xmlns='" + uri + "'");
else
System.out.print("xmlns:" + prefix + "='" + uri + "'");
}
}
A simple solution is to wrap the documents on the sending side in a new root element:
<?xml version="1.0"?>
<documents>
... document 1 ...
... document 2 ...
</documents>
You must make sure that you don't include the XML header (<?xml ...?>), though. If all documents use the same encoding, this can be accomplished with a simple filter which just ignores the first line of each document if it starts with <?xml
Found this forum message (which you probably already saw), which has a solution by wrapping the input stream and testing for one of two ascii characters (see post).
You could try an adaptation on this by first converting to use a reader (for proper character encoding) and then doing element counting until you reach the closing element, at which point you trigger the EOM.
Hi
I also had this problem at work (so won't post resulting the code). The most elegant solution that I could think of, and which works pretty nicely imo, is as follows
Create a class for example DocumentSplittingInputStream which extends InputStream and takes the underlying inputstream in its constructor (or gets set after construction...).
Add a field with a byte array closeTag containing the bytes of the closing root node you are looking for.
Add a field int called matchCount or something, initialised to zero.
Add a field boolean called underlyingInputStreamNotFinished, initialised to true
On the read() implementation:
Check if matchCount == closeTag.length, if it does, set matchCount to -1, return -1
If matchCount == -1, set matchCount = 0, call read() on the underlying inputstream until you get -1 or '<' (the xml declaration of the next document on the stream) and return it. Note that for all I know the xml spec allows comments after the document element, but I knew I was not going to get that from the source so did not bother handling it - if you can not be sure you'll need to change the "gobble" slightly.
Otherwise read an int from the underlying inputstream (if it equals closeTag[matchCount] then increment matchCount, if it doesn't then reset matchCount to zero) and return the newly read byte
Add a method which returns the boolean on whether the underlying stream has closed.
All reads on the underlying input stream should go through a separate method where it checks if the value read is -1 and if so, sets the field "underlyingInputStreamNotFinished" to false.
I may have missed some minor points but i'm sure you get the picture.
Then in the using code you do something like, if you are using xstream:
DocumentSplittingInputStream dsis = new DocumentSplittingInputStream(underlyingInputStream);
while (dsis.underlyingInputStreamNotFinished()) {
MyObject mo = xstream.fromXML(dsis);
mo.doSomething(); // or something.doSomething(mo);
}
David
I had to do something like this and during my research on how to approach it, I found this thread that even though it is quite old, I just replied (to myself) here wrapping everything in its own Reader for simpler use
I was faced with a similar problem. A web service I'm consuming will (in some cases) return multiple xml documents in response to a single HTTP GET request. I could read the entire response into a String and split it, but instead I implemented a splitting input stream based on user467257's post above. Here is the code:
public class AnotherSplittingInputStream extends InputStream {
private final InputStream realStream;
private final byte[] closeTag;
private int matchCount;
private boolean realStreamFinished;
private boolean reachedCloseTag;
public AnotherSplittingInputStream(InputStream realStream, String closeTag) {
this.realStream = realStream;
this.closeTag = closeTag.getBytes();
}
#Override
public int read() throws IOException {
if (reachedCloseTag) {
return -1;
}
if (matchCount == closeTag.length) {
matchCount = 0;
reachedCloseTag = true;
return -1;
}
int ch = realStream.read();
if (ch == -1) {
realStreamFinished = true;
}
else if (ch == closeTag[matchCount]) {
matchCount++;
} else {
matchCount = 0;
}
return ch;
}
public boolean hasMoreData() {
if (realStreamFinished == true) {
return false;
} else {
reachedCloseTag = false;
return true;
}
}
}
And to use it:
String xml =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<root>first root</root>" +
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<root>second root</root>";
ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes());
SplittingInputStream splitter = new SplittingInputStream(is, "</root>");
BufferedReader reader = new BufferedReader(new InputStreamReader(splitter));
while (splitter.hasMoreData()) {
System.out.println("Starting next stream");
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println("line ["+line+"]");
}
}
I use JAXB approach to unmarshall messages from multiply stream:
MultiInputStream.java
public class MultiInputStream extends InputStream {
private final Reader source;
private final StringReader startRoot = new StringReader("<root>");
private final StringReader endRoot = new StringReader("</root>");
public MultiInputStream(Reader source) {
this.source = source;
}
#Override
public int read() throws IOException {
int count = startRoot.read();
if (count == -1) {
count = source.read();
}
if (count == -1) {
count = endRoot.read();
}
return count;
}
}
MultiEventReader.java
public class MultiEventReader implements XMLEventReader {
private final XMLEventReader reader;
private boolean isXMLEvent = false;
private int level = 0;
public MultiEventReader(XMLEventReader reader) throws XMLStreamException {
this.reader = reader;
startXML();
}
private void startXML() throws XMLStreamException {
while (reader.hasNext()) {
XMLEvent event = reader.nextEvent();
if (event.isStartElement()) {
return;
}
}
}
public boolean hasNextXML() {
return reader.hasNext();
}
public void nextXML() throws XMLStreamException {
while (reader.hasNext()) {
XMLEvent event = reader.peek();
if (event.isStartElement()) {
isXMLEvent = true;
return;
}
reader.nextEvent();
}
}
#Override
public XMLEvent nextEvent() throws XMLStreamException {
XMLEvent event = reader.nextEvent();
if (event.isStartElement()) {
level++;
}
if (event.isEndElement()) {
level--;
if (level == 0) {
isXMLEvent = false;
}
}
return event;
}
#Override
public boolean hasNext() {
return isXMLEvent;
}
#Override
public XMLEvent peek() throws XMLStreamException {
XMLEvent event = reader.peek();
if (level == 0) {
while (event != null && !event.isStartElement() && reader.hasNext()) {
reader.nextEvent();
event = reader.peek();
}
}
return event;
}
#Override
public String getElementText() throws XMLStreamException {
throw new NotImplementedException();
}
#Override
public XMLEvent nextTag() throws XMLStreamException {
throw new NotImplementedException();
}
#Override
public Object getProperty(String name) throws IllegalArgumentException {
throw new NotImplementedException();
}
#Override
public void close() throws XMLStreamException {
throw new NotImplementedException();
}
#Override
public Object next() {
throw new NotImplementedException();
}
#Override
public void remove() {
throw new NotImplementedException();
}
}
Message.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "Message")
public class Message {
public Message() {
}
#XmlAttribute(name = "ID", required = true)
protected long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Override
public String toString() {
return "Message{id=" + id + '}';
}
}
Read multiply messages:
public static void main(String[] args) throws Exception{
StringReader stringReader = new StringReader(
"<Message ID=\"123\" />\n" +
"<Message ID=\"321\" />"
);
JAXBContext context = JAXBContext.newInstance(Message.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
XMLInputFactory inputFactory = XMLInputFactory.newFactory();
MultiInputStream multiInputStream = new MultiInputStream(stringReader);
XMLEventReader xmlEventReader = inputFactory.createXMLEventReader(multiInputStream);
MultiEventReader multiEventReader = new MultiEventReader(xmlEventReader);
while (multiEventReader.hasNextXML()) {
Object message = unmarshaller.unmarshal(multiEventReader);
System.out.println(message);
multiEventReader.nextXML();
}
}
results:
Message{id=123}
Message{id=321}

Categories