stop stax parser from executing - java

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

Related

XmlPullParser skip from START_DOCUMENT to END_DOCUMENT eventType

I'm trying to parse xml file in Android app. When I'm trying to process it I don't receive eventType except START_DOCUMENT and END_DOCUMENT right after START. It looks like I will deliver empty file or something. The stranges thing is that I tryied this code on 5 different xml files and it worked (worked I mean I get different eventTypes than START and END) for corrupted one (there was missing some end tags). I was thinking that maybe I created wrong xml files but I even download some sample xml files and it didn't worked too.
some code:
public static void parseXML(Activity activity) throws XmlPullParserException, IOException {
XmlPullParserFactory parserFactory;
parserFactory = XmlPullParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
XmlPullParser parser = parserFactory.newPullParser();
InputStream inputStream = activity.getAssets().open("XML_RENAME.xml");
InputStreamReader isReader = new InputStreamReader(inputStream);
BufferedReader reader = new BufferedReader(isReader);
parser.setInput(reader);
processParces(parser);
}
private static void processParces(XmlPullParser parser) throws XmlPullParserException, IOException {
int eventType = parser.getEventType();
String tagname = "";
String text = "";
while(eventType != XmlPullParser.END_DOCUMENT)
{
tagname = parser.getName();
switch(eventType)
{
case XmlPullParser.START_TAG:
if (tagname.equalsIgnoreCase(KEY_REGION)) {
}
break;
case XmlPullParser.TEXT:
//grab the current text so we can use it in END_TAG event
text = parser.getText();
Log.e("Text: ", text);
break;
case XmlPullParser.END_TAG:
if (tagname.equalsIgnoreCase(KEY_SECTOR)) {
Log.e("XML ",KEY_SECTOR);
} else if (tagname.equalsIgnoreCase(KEY_DIRECTIONS)) {
Log.e("XML ",KEY_DIRECTIONS );
} else if (tagname.equalsIgnoreCase(KEY_CONDITIONS)) {
Log.e("XML ",KEY_CONDITIONS );
} else if (tagname.equalsIgnoreCase(KEY_NEIGHBORS)) {
Log.e("XML ",KEY_NEIGHBORS );
} else if (tagname.equalsIgnoreCase(KEY_CONTINUATIONS)) {
Log.e("XML ",KEY_CONTINUATIONS );
} else if (tagname.equalsIgnoreCase(KEY_BLOCKS)) {
Log.e("XML ",KEY_BLOCKS );
}
break;
default:
break;
}
eventType = parser.next();
}
}
I found a problem. Some xml files contains at start some extra bytes, to be specific "EF BB BF". It's called BOM (Byte-Order-Mark). When xml contains this extra bytes our XmlPullParser doesn't work properly and behave like there is no START_TAG event and goes to END_DOCUMENT.

How to get XML element path using stax/stax2?

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.

How to parse an XML file which give me back a List? PullParse method in Java Android Studio

I'm trying to create an app which allows me to download xml file from a page, I found some tutorials on the internet , one was simple enough for me, anyway there is a class which is parsing XML file and gives back the list of objects.
public class SiteXmlPullParser {
static final String KEY_SITE = "site";
static final String KEY_NAME = "name";
static final String KEY_LINK = "link";
static final String KEY_ABOUT = "about";
static final String KEY_IMAGE_URL = "image";
public static List<StackSite> getStackSitesFromFile(Context ctx) {
// List of StackSites that we will return
List<StackSite> stackSites;
stackSites = new ArrayList<StackSite>();
// temp holder for current StackSite while parsing
StackSite curStackSite = null;
// temp holder for current text value while parsing
String curText = "";
try {
// Get our factory and PullParser
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xpp = factory.newPullParser();
// Open up InputStream and Reader of our file.
FileInputStream fis = ctx.openFileInput("StackSites.xml");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
// point the parser to our file.
xpp.setInput(reader);
// get initial eventType
int eventType = xpp.getEventType();
// Loop through pull events until we reach END_DOCUMENT
while (eventType != XmlPullParser.END_DOCUMENT) {
// Get the current tag
String tagname = xpp.getName();
// React to different event types appropriately
switch (eventType) {
case XmlPullParser.START_TAG:
if (tagname.equalsIgnoreCase(KEY_SITE)) {
// If we are starting a new <site> block we need
//a new StackSite object to represent it
curStackSite = new StackSite();
}
break;
case XmlPullParser.TEXT:
//grab the current text so we can use it in END_TAG event
curText = xpp.getText();
break;
case XmlPullParser.END_TAG:
if (tagname.equalsIgnoreCase(KEY_SITE)) {
// if </site> then we are done with current Site
// add it to the list.
stackSites.add(curStackSite);
} else if (tagname.equalsIgnoreCase(KEY_NAME)) {
// if </name> use setName() on curSite
curStackSite.setName(curText);
} else if (tagname.equalsIgnoreCase(KEY_LINK)) {
// if </link> use setLink() on curSite
curStackSite.setLink(curText);
} else if (tagname.equalsIgnoreCase(KEY_ABOUT)) {
// if </about> use setAbout() on curSite
curStackSite.setAbout(curText);
} else if (tagname.equalsIgnoreCase(KEY_IMAGE_URL)) {
// if </image> use setImgUrl() on curSite
curStackSite.setImgUrl(curText);
}
break;
default:
break;
}
//move on to next iteration
eventType = xpp.next();
}
} catch (Exception e) {
e.printStackTrace();
}
// return the populated list.
return stackSites;
}
}
This XML file looks like this -> https://dl.dropboxusercontent.com/u/5724095/XmlParseExample/stacksites.xml
so the structure here is fine but I don't exactly know how to fix the code to download my XML file which looks like this : http://www.nbp.pl/kursy/xml/dir.txt
Can sb help me?

Why does XmlPullParserException exception occurs?

I try to parse simple xml file with XmlPullParser.
<?xml version="1.0" encoding="utf-8"?>
<tests>
<test>
<name>Jack</name>
</test>
<test>
<name>Brad</name>
</test>
<test>
<name>Tom</name>
</test>
</tests>
This is the MainActivity code:
public class MainActivity extends ActionBarActivity {
ViewPager viewPager;
PagerAdapter pagerAdapter;
ArrayList<Quote> quotes;
ArrayList<Pers> persons;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InputStream inputStream = getResources().openRawResource(R.xml.test);
Fetcher fetcher = new Fetcher();
persons = fetcher.parse(inputStream);
String str = new String();
for(int i = 0; i < persons.size(); i++) {
str += persons.get(i).getName();
}
Log.d("1", str);
}
}
This is the Fetcher class code:
public class Fetcher {
private ArrayList<Pers> persons;
private Pers person;
private String text;
public Fetcher() {
persons = new ArrayList<Pers>();
}
public ArrayList<Pers> parse(InputStream is) {
XmlPullParserFactory factory = null;
XmlPullParser parser = null;
try {
factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
parser = factory.newPullParser();
parser.setInput(is, null);
int eventType = parser.getEventType();
while(eventType != XmlPullParser.END_DOCUMENT) {
String tagname = parser.getName();
switch(eventType) {
case XmlPullParser.START_TAG:
if(tagname.equalsIgnoreCase("test")) {
person = new Pers();
}
break;
case XmlPullParser.TEXT:
text = parser.getText();
break;
case XmlPullParser.END_TAG:
if(tagname.equalsIgnoreCase("test")) {
persons.add(person);
} else if(tagname.equalsIgnoreCase("name")) {
person.setName(text);
}
break;
default:
break;
}
eventType = parser.next();
}
} catch(XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return persons;
}
}
Why does XmlPullParserException exception occurs?
org.xmlpull.v1.XmlPullParserException: unterminated entity ref (position:TEXT ��������������������...#1:49 in java.io.InputStreamReader#405534d8)
I changed code, now it works. Thank those who tried to help me.
public class Fetcher {
private ArrayList<Pers> persons;
private Pers person;
private String text;
public Fetcher() {
persons = new ArrayList<Pers>();
}
public ArrayList<Quote> parse(Activity activity) throws XmlPullParserException, IOException {
Resources resources = activity.getResources();
XmlResourceParser xmlResourceParser = resources.getXml(R.xml.quotes);
person = new Pers();
xmlResourceParser.next();
int eventType = xmlResourceParser.getEventType();
while(eventType != XmlPullParser.END_DOCUMENT) {
String tagName = xmlResourceParser.getName();
switch(eventType) {
case XmlPullParser.START_TAG: {
if(tagName.equalsIgnoreCase("test")) {
person = new Pers();
}
break;
}
case XmlPullParser.TEXT: {
text = xmlResourceParser.getText();
break;
}
case XmlPullParser.END_TAG: {
if(tagName.equalsIgnoreCase("test")) {
persons.add(person);
} else if(tagName.equalsIgnoreCase("name")) {
person.setId(Integer.parseInt(text));
}
break;
}
default:
break;
}
eventType = xmlResourceParser.next();
}
return persons;
}
}
I imagine it's this line: parser.setInput(is, null);
Because the xml encoding is UTF-8, I believe you have to change it to parser.setInput(is,"UTF-8");
I think that the problem is in different input to what you have shown us. Or maybe the source code is different.
The error message seems to refer to a position that does not exist - line 1, character position 49 (or vice versa).
The error message seems to indicate some garbled characters (or a string of NULs), but there is nothing problematic in the sample XML.
A bit late but, in case it helps anyone else, check:
There are no characters that need escaping in the XML file e.g. & which should be &#amp;
Make sure the attributes are correctly enclosed in quotes (in my case, eclipse had been helpful putting close quotes in, so when I added the close quotes at the end of my text, it gave an error
I checked through my XML by starting with the outermost tags, deleting the content and re-adding in bits, running each time, to find where the errors were. Debugging XML files is a real pain!

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