I'm using SAX Parser to parse the XML file of RSS feeds on an Android App and sometimes the parsing of the pubDate of an item isn't completed (incomplete characters).
Ex:
Actual PubDate Thu, 02 Apr 2015 12:23:41 +0000
PubDate Result of the parse: Thu,
Here is the code that I'm using in the parser handler:
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if ("item".equalsIgnoreCase(localName)) {
currentItem = new RssItem(url);
} else if ("title".equalsIgnoreCase(localName)) {
parsingTitle = true;
} else if ("link".equalsIgnoreCase(localName)) {
parsingLink = true;
} else if ("pubDate".equalsIgnoreCase(localName)) {
parsingDate = true;
}
}
#Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("item".equalsIgnoreCase(localName)) {
rssItems.add(currentItem);
currentItem = null;
} else if ("title".equalsIgnoreCase(localName)) {
parsingTitle = false;
} else if ("link".equalsIgnoreCase(localName)) {
parsingLink = false;
} else if ("pubDate".equalsIgnoreCase(localName)) {
parsingDate = false;
}
}
#Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (parsingTitle) {
if (currentItem != null) {
currentItem.setTitle(new String(ch, start, length));
parsingTitle = false;
}
} else if (parsingLink) {
if (currentItem != null) {
currentItem.setLink(new String(ch, start, length));
parsingLink = false;
}
} else if (parsingDate) {
if (currentItem != null) {
currentItem.setDate(new String(ch, start, length));
parsingDate = false;
}
}
}
The loss of characters is pretty random, it happens in different XML items every time I run the app.
You are assuming that there is exactly one characters() call per element. That is not a safe assumption. Build up your string over 1+ calls to characters(), then apply it in endElement().
Or, better yet, use any one of a number of existing RSS parser libraries.
Related
I'm trying to use a SAX parser in order to return the contents of an RSS feed from a URL - http://pitchfork.com/rss/news/, but often characters are lost in displaying the title, showing partial text or just a closing tag ">"
How can i modify my handler class to prevent this? I think I should probably use StringBuilder or StringBuffer, but i'm not sure how to implement it.
ParseHandler.java
public class RssParseHandler extends DefaultHandler {
//Parsed items
private List<RssItem> rssItems;
private RssItem currentItem;
private boolean parsingTitle;
private boolean parsingLink;
private boolean parsing_id;
private boolean parsingDescription;
public RssParseHandler() {
rssItems = new ArrayList<RssItem>();
}
public List<RssItem> getItems() {
return rssItems;
}
//Creates empty RssItem object during the process of an item start tag
//Indicators are set to true when particular tag is being processed
#Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if ("item".equals(qName)) {
currentItem = new RssItem();
} else if ("title".equals(qName)) {
parsingTitle = true;
} else if ("link".equals(qName)) {
parsingLink = true;
} else if ("_id".equals(qName)) {
parsing_id = true;
} else if ("description".equals(qName)) {
parsingDescription = true;
}
}
//Current RssItem is added to the list following process of end tag
#Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("item".equals(qName)) {
rssItems.add(currentItem);
currentItem = null;
} else if ("title".equals(qName)) {
parsingTitle = false;
} else if ("link".equals(qName)) {
parsingLink = false;
} else if ("_id".equals(qName)) {
parsing_id = false;
} else if ("description".equals(qName)) {
parsingDescription = false;
}
}
#Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (parsingTitle) {
if (currentItem != null)
currentItem.setTitle(new String(ch, start, length));
} else if (parsingLink) {
if (currentItem != null) {
currentItem.setLink(new String(ch, start, length));
parsingLink = false;
}
} else if (parsing_id) {
if (currentItem != null) {
currentItem.set_id(new String(ch, start, length));
parsing_id = false;
}
} else if (parsingDescription) {
if (currentItem != null) {
currentItem.setDescription(new String(ch, start, length));
parsingDescription = false;
}
}
}}//rssHandlerClass
Use a StringBuilder to build the tag, rather than using a new String instance as the documentation says:
The Parser will call this method to report each chunk of character data. SAX parsers may return all contiguous character data in a single chunk, or they may split it into several chunks; however, all of the characters in any single event must come from the same external entity so that the Locator provides useful information.
And #CommonWares says this exactly in his post Here.
Build your tag as it is found using StringBuilder, since there is chunks coming in at once rather than the entire string (This explains the incomplete tags!). You may or may not need the isBuilding flag, but I don't know your entire implementation so I added it incase.
StringBuilder mSb;
boolean isBuilding;
#Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
mSb = new StringBuilder();
isBuilding = true;
if(qName.equals("title")){
parsingTitle = true;
}
...
...
}
#Override
public void characters (char ch[], int start, int length) {
if (mSb !=null && isBuilding) {
for (int i=start; i<start+length; i++) {
mSb.append(ch[i]);
}
}
}
#Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if(parsingTitle){
currentItem.setTitle(sb.toString().trim());
parsingTitle = false;
isBuilding = false;
}
}
i need to read this xml file using sax parser , but the problem is beacuse of the same child node name in different nodes. This is the file:
<MasterData>
<node1>200000</node1>
<Location>
<oa:Code>88</oa:Code>
<oa:Description languageID="en-us">addres1</oa:Description>
<oa:Description languageID="ar-ae">addres2</oa:Description>
</Location>
<address>
<oa:Code>55</oa:Code>
<oa:Description languageID="en-us">Loc1</oa:Description>
<oa:Description languageID="ar-ae">Loc2</oa:Description>
</address>
</MasterData>
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class MyHandler2 extends DefaultHandler {
private MasterData masterdata= null;
private IssueAuthority issueAuth = null ;
private List<IssueAuthority> issueAuthList = null ;
public MasterData getMasterData() {
return masterdata;
}
boolean bmaster = false ;
boolean bLiceNum = false;
boolean bissueAuth = false ;
boolean bautCode = false ;
boolean bautDescAr = false ;
boolean bautDescEn = false ;
#Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (qName.equalsIgnoreCase("MasterData")) {
//initialize Employee object and set id attribute
masterdata = new MasterData();
System.out.println("MasterData");
}else if (qName.equalsIgnoreCase("LicenseNumber")) {
bLiceNum = true;
System.out.println("qName"+qName);
}else if (qName.equalsIgnoreCase("IssueAuthority")) {
bissueAuth = true;
}else if (qName.equalsIgnoreCase("oa:Code")) {
bautCode = true;
}
}
#Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equalsIgnoreCase("IssueAuthority")) {
issueAuthList = new ArrayList<IssueAuthority>() ;
issueAuthList.add(issueAuth) ;
}
if (qName.equalsIgnoreCase("MasterData")) {
//add Employee object to list
masterdata.setIssueAuthority(issueAuthList);
}
}
#Override
public void characters(char ch[], int start, int length) throws SAXException {
if (bLiceNum) {
masterdata.setLicenseNumber(new String(ch, start, length));
bLiceNum = false;
} else if (bissueAuth) {
issueAuth = new IssueAuthority();
bissueAuth = false ;
}else if(bautCode){
issueAuth.setCode(Integer.parseInt(new String(ch, start, length)));
bautCode = false ;
}
}
}
this my handler class
You can differentiate between the location & adress Description child nodes by flagging the entry & exit on the parent. Like that you know exactly in which element you child list you are when accessing the text on the child nodes.
The code snippet below illustrates this:
List<String> locationDescriptions = new ArrayList<String>();
List<String> addressDescriptions = new ArrayList<String>();
boolean insideAddress = false ;
boolean insideLocation = false ;
#Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
...
if (qName.equalsIgnoreCase("Location")) {
insideLocation = true;
}
if (qName.equalsIgnoreCase("address")) {
insideAddress = true;
}
...
}
#Override
public void endElement(String uri, String localName, String qName) throws SAXException {
...
if (qName.equalsIgnoreCase("Location")) {
insideLocation = false;
}
if (qName.equalsIgnoreCase("address")) {
insideAddress = false;
}
...
}
#Override
public void characters(char ch[], int start, int length) throws SAXException {
..
if (insideLocation && (bautDescAr || bautDescEn) {
locationDescriptions.add(new String(ch, start, length));
}
if (insideAddress && (bautDescAr || bautDescEn) {
addressDescriptions.add(new String(ch, start, length));
}
...
}
I am developing a RSS feed reader for Android, and for parsing XML files, I am using SAX APIs. The problem is that while parsing the data, some of the text is truncated in a random fashion in some randomly selected tags (I mean different instances of same tag). To me more clear, I have added a screenshot.
Here is my Handler class:
public class RssParseHandler extends DefaultHandler {
private List<RssItem> rssItems;
private RssItem currentItem;
private boolean parsingTitle;
private boolean parsingLink;
//StringBuilder temp;
public RssParseHandler() {
rssItems = new ArrayList<RssItem>();
//temp = new StringBuilder();
}
public List<RssItem> getItems() {
return rssItems;
}
#Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if ("item".equals(qName)) {
currentItem = new RssItem();
} else if ("title".equals(qName)) {
parsingTitle = true;
} else if ("link".equals(qName)) {
parsingLink = true;
}
}
#Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if ("item".equals(qName)) {
rssItems.add(currentItem);
currentItem = null;
} else if ("title".equals(qName)) {
//currentItem.setTitle(new String(temp));
//temp = new StringBuilder();
parsingTitle = false;
} else if ("link".equals(qName)) {
//currentItem.setLink(new String(temp));
//temp = new StringBuilder();
parsingLink = false;
}
}
#Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (parsingTitle) {
if (currentItem != null)
{
//temp.append(ch, start, length);
currentItem.setTitle(new String(ch, start, length));
}
} else if (parsingLink) {
if (currentItem != null) {
//temp.append(ch, start, length);
currentElement.setLink(new String(ch, start, length));
parsingLink = false;
}
}
}
}
The methods setTitle(String str) and setLink(String str) are setter methods of class RSSItem.
I googled this problem and read somewhere to use StringBuilder instead. Hence I tried by using StringBuilder. ( I have commented the code when I used StringBuilder). But then I started receiving NullPointerException.
Any suggestions to get rid of this problem ?
From the doc
The Parser will call this method to report each chunk of character
data. SAX parsers may return all contiguous character data in a single
chunk, or they may split it into several chunks; however, all of the
characters in any single event must come from the same external entity
so that the Locator provides useful information.
So propably you are getting a partial chunk of data. A possible solution could be:
if (currentItem != null) {
//temp.append(ch, start, length);
String tmpLink = currentElement.getLink();
tmpLink += new String(ch, start, length);
currentElement.setLink(tmpLink);
}
of course currentElement.getLink() should return an empty String and not a null reference.
Your problem is that you assume characters method will handle all characters inside the element, which is not true.
You should save and concatenate new characters with previous characters if any.
Using StringBuilder is good for your cause. You just need to handle the NPE you've got.
I am trying to parse the description tag in the xml but it only outputs one line:
description: <img src=http://www.ovations365.com/sites/ovations365.com/images/event/441705771/sparkswebsite_medium.jpg alt="SPARKS: Understanding Energy">
That is only a small part of the text in the CDATA and I'm trying to output the description for multiple items. Why can't I get the whole CDATA?
The XML is located: http://feeds.feedburner.com/Events-Ovations365
package com.example.ovations_proj;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.example.ovations_proj.RssItem;
public class RssParseHandler extends DefaultHandler {
private List<RssItem> rssItems;
// Used to reference item while parsing
private RssItem currentItem;
// Parsing title indicator
private boolean parsingTitle;
// Parsing link indicator
private boolean parsingLink;
private boolean parsingDes;
public RssParseHandler() {
rssItems = new ArrayList<RssItem>();
}
public List<RssItem> getItems() {
return rssItems;
}
#Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
System.out.println("Start Element :" + qName);
if ("item".equals(qName)) { //item
currentItem = new RssItem();
} else if ("title".equals(qName)) { //title
parsingTitle = true;
} else if ("link".equals(qName)) { //link
parsingLink = true;
}else if ("description".equals(qName) ) { //description
parsingDes = true;
}
}
#Override
public void endElement(String uri, String localName, String qName) throws SAXException {
System.out.println("End Element :" + qName);
if ("item".equals(qName)) {
rssItems.add(currentItem);//item
currentItem = null;
} else if ("title".equals(qName)) {//title
parsingTitle = false;
} else if ("link".equals(qName)) {//link
parsingLink = false;
} else if ("description".equals(qName) ) { //description
parsingDes = false;
}
}
#Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (parsingTitle) {
if (currentItem != null){
currentItem.setTitle(new String(ch, start, length));
}
} else if (parsingLink) {
if (currentItem != null) {
currentItem.setLink(new String(ch, start, length));
parsingLink = false;
}
} else if (parsingDes) {
if (currentItem != null) {
currentItem.setDes(new String(ch, start, length));
System.out.println("description: " + currentItem.getDes());
parsingDes = false;
}
}
}
}
It seems that the character data in the <![CDATA[...]]> sections is being sent in multiple chunks, i.e. in multiple calls to the characters method.
The ContentHandler documentation for the characters method mentions that SAX parsers are free to do this:
SAX parsers may return all contiguous character data in a single chunk, or they may split it into several chunks[....]
You'll therefore need to adjust your characters method to handle being called multiple times for the same chunk of contiguous character data.
I receive XML files that need parsing. I code in java regularly, so java SAX was my natural
first choice. The XML files have a combination of text elements and one binary element (.xls file).
My parser handler is as:
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException{
if(qName.equalsIgnoreCase("To")){
toFlag = true;
}
if(qName.equalsIgnoreCase("Subject")){
subjectFlag = true;
}
if(qName.equalsIgnoreCase("OutDocumentId")){
outdocmentIdFlag = true;
}
if(qName.equalsIgnoreCase("Filename")){
filenameFlag = true;
}
if(qName.equalsIgnoreCase("EmailType")){
emailTypeFlag = true;
}
if(qName.equalsIgnoreCase("Context")){
contextTypeFlag = true;
}
if(qName.equalsIgnoreCase("Blob")){
blobTypeFlag = true;
}
}
And the element data is parsed here:
public void characters(char ch[], int start, int length) throws SAXException{
String text = null;
if (toFlag) {
text = new String(ch, start, length);
getRequest().setRecipientEmail(text);
toFlag = false;
}
if (subjectFlag) {
text = new String(ch, start, length);
getRequest().setSubject(text);
subjectFlag = false;
}
if (outdocmentIdFlag) {
text = new String(ch, start, length);
getRequest().setOutDocId(text);
outdocmentIdFlag = false;
}
if (filenameFlag) {
text = new String(ch, start, length);
getRequest().setFilename(text);
filenameFlag = false;
}
if(emailTypeFlag) {
text = new String(ch, start, length);
getRequest().setEmailType(Integer.parseInt(text));
emailTypeFlag = false;
}
if(contextTypeFlag) {
text = new String(ch, start, length);
getRequest().setContext(text);
contextTypeFlag = false;
}
if(blobTypeFlag) {
text = new String(ch, start, length);
try {
getRequest().setBlob(Hibernate.createBlob(text.getBytes("UTF-16")));
} catch (UnsupportedEncodingException e) {
System.out.println("Error creating blob");
e.printStackTrace();
}
blobTypeFlag = false;
}
}
}
The problem is with the blob element, its being read in as a char[]
(which I believe is incorrect ) ... because that's what they parent
class allow to override during event processing.
Does anybody know how to use the SAX parse when one element, is not
text but binary instead?
Greatly appreciated
Take the char data and send it to a Base64 decoder.