Parsing XML XmlPullParser android - android

i'm using xmlpullparser in android to parse an xml document that looks like :
<top>
<category>
<name></name>
<desc></desc>
<songs>
<song>
<clip></clip>
<thumb></thumb>
</song>
<song>
<clip></clip>
<thumb></thumb>
</song>
</songs>
</category>
</top>
I tried this :
while (eventType != XmlPullParser.END_DOCUMENT && !done){
String name = null;
switch (eventType){
case XmlPullParser.START_DOCUMENT:
categoriesSong = new ArrayList<TopMousika>();
break;
case XmlPullParser.START_TAG:
name = parser.getName();
if (name.equalsIgnoreCase(CATEGORY)){
currentCategory = new TopMousika();
currentCategory.setId(parser.getAttributeValue(0));
currentCategory.setId(parser.getAttributeValue(1));
} else if (currentCategory != null){
if (name.equalsIgnoreCase(NAME)){
currentCategory.setName(parser.nextText());
} else if (name.equalsIgnoreCase(DESCRIPTION)){
currentCategory.setDescription(parser.nextText());
} else if (name.equalsIgnoreCase(THUMBNAIL)){
currentCategory.setThumbnail(parser.nextText());
} else if (name.equalsIgnoreCase(SONGS)){
songs = new ArrayList<SongMousika>();
if(name.equalsIgnoreCase(SONG)){
currentSong = new SongMousika();
currentSong.setId(parser.getAttributeValue(0));
Log.d("TEST", "OK");
songs.add(currentSong);
} else if (name.equalsIgnoreCase(TITLE)){
Log.d("TEST", "OK2");
currentSong.setTitle(parser.nextText());
} else if (name.equalsIgnoreCase(SINGER)){
currentSong.setTitle(parser.nextText());
} else if (name.equalsIgnoreCase(THUMBNAIL)){
currentSong.setTitle(parser.nextText());
} else if (name.equalsIgnoreCase(PUBLICATION_DATE)){
currentSong.setTitle(parser.nextText());
} else if (name.equalsIgnoreCase(CLIP)){
currentSong.setTitle(parser.nextText());
}
currentCategory.setSongs(songs);
}
}
break;
case XmlPullParser.END_TAG:
name = parser.getName();
if (name.equalsIgnoreCase(CATEGORY) &&
currentCategory != null){
currentCategory.setSongs(songs);
categoriesSong.add(currentCategory);
} else if (name.equalsIgnoreCase(TOP)){
done = true;
}
break;
}
eventType = parser.next();
}
but I can not retrieve my Songs List.
can any one help me please ?

You seem to be expecting name to change magically between checks:
if (name.equalsIgnoreCase(SONGS)) {
songs = new ArrayList<SongMousika>();
if(name.equalsIgnoreCase(SONG)) {
How is it going to be SONG and SONGS? You need to keep pulling XML and react to each element name differently in the loop. So you'll probably have a bunch of if/else if statements without any nesting when reacting to a START_TAG event. (It's very likely to be worth pulling the handling of that into a separate method, by the way.)
EDIT: Okay, so you need to make each iteration of the loop just react to one tag. So your handling for a start tag would be something like:
case XmlPullParser.START_TAG:
name = parser.getName();
if (name.equalsIgnoreCase(CATEGORY)){
currentCategory = new TopMousika();
currentCategory.setId(parser.getAttributeValue(0));
currentCategory.setId(parser.getAttributeValue(1));
} else if (currentCategory != null) {
if (name.equalsIgnoreCase(NAME)){
currentCategory.setName(parser.nextText());
} else if (name.equalsIgnoreCase(DESCRIPTION)){
currentCategory.setDescription(parser.nextText());
} else if (name.equalsIgnoreCase(THUMBNAIL)){
currentCategory.setThumbnail(parser.nextText());
} else if (name.equalsIgnoreCase(SONGS)){
songs = new ArrayList<SongMousika>();
} else if (songs != null) {
if(name.equalsIgnoreCase(SONG)) {
currentSong = new SongMousika();
currentSong.setId(parser.getAttributeValue(0));
Log.d("TEST", "OK");
songs.add(currentSong);
} else if (currentSong != null) {
else if (name.equalsIgnoreCase(TITLE)) {
Log.d("TEST", "OK2");
currentSong.setTitle(parser.nextText());
} else if (name.equalsIgnoreCase(SINGER)){
currentSong.setSinger(parser.nextText());
} else if (name.equalsIgnoreCase(THUMBNAIL))
// etc
}
}
}
Note how in any path through there we never check for name having multiple values - we say:
Are we starting a new category?
If so, create it and remember it - then continue with the next iteration.
If not (and if we've got a category), are we starting a new song list?
If so, create it and remember it - then continue with the next iteration.
If not (and if we've got a song list), are we starting a new song?
If so, create it and remember it - then continue with the next iteration.
If not (and if we've got a song)...
Are we reading the title? If so, read the text and set it, then continue.
Are we reading the singer? If so, read the text and set it, then continue.
Are we reading the thumbnail? If so, read the text and set it, then continue.
etc

Related

Creating TOpenDialog manually in C++ Builder XE 8 (firemonkey)

I am using C++ Builder XE8. As the TOpenDialog doesn't work on Android, I am trying to make such thing myself. My logic is very simple. It'll start to check file and folders from "/storage" and show all items on TListView. If I touch a folder (name) it'll open that folder and if I touch a file, it should show the name on a label. So I assigned a function to TListView's OnItemClick event.
Here is code. fpath is String, Label1 is showing current folder and Label2 is showing selected file.
void __fastcall TForm1::lviewitemclck(TObject * const Sender, TListViewItem * const AItem)
{
if (AItem->Text == "<< BACK") {
if (!fpath.LastDelimiter("/") == 0) {
fpath = fpath.SubString(0, fpath.LastDelimiter("/"));
Label1->Text = fpath;
Form1->showfiles(fpath);
}
}
else if ( DirectoryExists(fpath+ AItem->Text)) {
fpath = fpath+ AItem->Text;
Label1->Text = fpath;
Form1->showfiles(fpath);
}
else if (FileExists(fpath+ AItem->Text)) {
Label2->Text ="File: "+ fpath+ AItem->Text;
}
}
Below is the code of function to scan for files & folders and show them. stringlist is TStringList.
void __fastcall TForm1::showfiles (String path)
{
TSearchRec sr; // for scaning files and folders
TSearchRec fr; // to check whether the folder is accessible or not.
if (FindFirst(path+"/*", faAnyFile, sr) == 0)
{
stringlist->Clear();
stringlist->Add("<< BACK"); // being used to replace the ".."
do{
if(sr.Name != "." && sr.Name != ".."){
if (DirectoryExists(path+"/"+sr.Name)) {
if (FindFirst(path+"/"+sr.Name+"/*", faAnyFile, fr) == 0) { // to check if the folder is accessible
stringlist->Add("/"+ sr.Name);
}
FindClose(fr);
}
else{
stringlist->Add("/"+ sr.Name);
}
}
} while (FindNext(sr) == 0);
}
FindClose(sr);
stringlist->Sort();
Form1->Item->Free();
Form1->ListView1->BeginUpdate();
Form1->ListView1->ClearItems();
for( int i =0;i< stringlist->Count; i++){
Form1->Item = Form1->ListView1->Items->Add();
Form1->Item->Text = stringlist->Strings[i];
}
Form1->ListView1->EndUpdate();
}
Here the problem is, if I use ListView1->ClearItems() in TForm1::showfiles it shows me an error saying "Access violation at address (random no), accessing address 00000009". And if I dont use ClearItems() it just add more lines with already existed lines. I am a beginer, so I dont know where I am doing wrong.
You should use:
ListView1->Items->Clear
The best way I have found so far is to dynamically create TListView and delete it each time you want to add new items( or calling showfiles function). I have written a small function (named it refresh) to release already created TListView and call another function(named it create_lview ) which can create an instance again then it calls the showfiles method.
void __fastcall TForm1::refresh()
{
if (!lview1->Released()) {
try{
lview1->Release();
Form1->create_lview();
Form1->showfiles(fpath);
}
catch(...){
Label2->Text = "error in cleaning";
}
}
}
Here is the code to create the TListView whenever you want.
void __fastcall TForm1::create_lview()
{
lview1 = new TListView(Form1);
lview1->Parent = Form1;
lview1->Height = 600;
lview1->Width = 400;
lview1->Position->X = 0;
lview1->Position->Y = 0;
lview1->Visible = true;
lview1->Enabled = true;
lview1->OnItemClick = lviewitemclck;
lview1->CanSwipeDelete = false;
}
Please comment if you find any mistake or you can do it more efficiently.
I have tried another way to avoid the error by replacing the Clear method with updating the Item Text, then deletes the unused last row of the ListView within the ListView1Change event.
void __fastcall TForm1::ListView1Change(TObject *Sender)
{
//Delete last item
while (ListView1->Items->Count>stringlist->Count){
ListView1->Items->Delete(ListView1->Items->Count-1);
}
}
void __fastcall TForm1::showfiles (String path)
{
TSearchRec sr; // for scaning files and folders
TSearchRec fr; // to check whether the folder is accessible or not.
//path+PathDelim+
if (FindFirst(path+PathDelim+'*', faAnyFile, sr) == 0)
{
stringlist->Clear();
stringlist->Add("<<--BACK"); // being used to replace the ".."
do{
if(sr.Name != "." && sr.Name != ".."){
if (DirectoryExists(path+PathDelim+sr.Name)) {
if (FindFirst(path+PathDelim+sr.Name+PathDelim+"*", faAnyFile, fr) == 0) { // to check if the folder is accessible
stringlist->Add(sr.Name);
}
FindClose(fr);
}
else{
stringlist->Add(sr.Name);
}
}
} while (FindNext(sr) == 0);
}
FindClose(sr);
stringlist->Sort();
for( int i =0;i< ListView1->Items->Count; i++){
ListView1->Items->Item[i]->Text="";
}
ListView1->BeginUpdate();
try {
for( int i =0;i< stringlist->Count; i++)
{
if (ListView1->Items->Count-1<i)
{
TListViewItem* Item=ListView1->Items->Add();
Item->Text=stringlist->Strings[i];
} else
{
TListViewItem* Item=ListView1->Items->Item[i];
Item->Text=stringlist->Strings[i];
}
}
}
catch (...) {
}
ListView1->EndUpdate();
/* */
}

Android, XMLParser - Reading XML

im currently trying to read and XML from a web server in my Android App, but im not sure how to search for the TAGS, and all the examples i see are not unlike mine:
<document>
<producer/>
<metadata></metadata>
<recorddata count="111">
<row>
<field name="numint" value="MTAwMQ=="/>
<field name="Grupo" value="NQ=="/>
<field name="Link" value="Q29ycmllbnRlcw=="/>
<field name="Nombre" value="Q29ycmllbnRlcw=="/>
<field name="Valor" value="MzQwMC4wMA=="/>
</row>
</recorddata>
</document>
I need to read the ROWS inside RECORDATA, but im not sure how to address this problem, this is my code:
private void parseXML(XmlPullParser parser) throws XmlPullParserException,IOException
{
ArrayList<Record> Records = null;
int eventType = parser.getEventType();
Record currentRecord = null;
while (eventType != XmlPullParser.END_DOCUMENT){
String name = null;
switch (eventType){
case XmlPullParser.START_DOCUMENT:
Records = new ArrayList();
break;
case XmlPullParser.START_TAG:
name = parser.getName();
if (name == "row"){
currentRecord = new Record();
} else if (currentRecord != null){
if (name == "numint"){
currentRecord.numint = parser.getText();
} else if (name == "Grupo"){
currentRecord.group = parser.getText();
} else if (name == "Link"){
currentRecord.link= parser.getText();
}else if (name == "Nombre") {
currentRecord.name= parser.getText();
}else if (name == "Valor") {
currentRecord.value= parser.getText();
}
}
break;
case XmlPullParser.END_TAG:
name = parser.getName();
if (name.equalsIgnoreCase("row") && currentRecord != null){
Records.add(currentRecord);
}
}
eventType = parser.next();
}
printProducts(Records);
}
BUT, the Records List IS EMPTY at the end of the process, and i can tell is doing something, because there are a lot of records and it takes like 2 minutes till the process is over.
Im not sure if im using the getName() correctly, should i be checking for field??? and if so, how do i know in which field im on. Should i be using nextToken() instead???
I Solved the problem by rewriting my code as follows:
case XmlPullParser.START_TAG:
name = parser.getName();
if (name.equals("row")){
currentRecord = new Record();
} else if (currentRecord != null) {
if(parser.getName().equals("field")) {
if(parser.getAttributeValue(null, "name").equalsIgnoreCase("numint")) {
currentRecord.numint = parser.getAttributeValue(null, "value");
}else if (parser.getAttributeValue(null, "name").equalsIgnoreCase("Grupo")) {
currentRecord.group = parser.getAttributeValue(null, "value");
}else if (parser.getAttributeValue(null, "name").equalsIgnoreCase("Link")) {
currentRecord.link = parser.getAttributeValue(null, "value");
}else if (parser.getAttributeValue(null, "name").equalsIgnoreCase("Nombre")) {
currentRecord.name = parser.getAttributeValue(null, "value");
}else if (parser.getAttributeValue(null, "name").equalsIgnoreCase("Valor")) {
currentRecord.value = parser.getAttributeValue(null, "value");
}
}
}
break;
Taking something the answer by Andreaoid, and a little tweaking, i could the read de xml file without issue
To get an attribute you can use the method:
parser.getAttributeValue(null, "value");
Definition:
public abstract String getAttributeValue (String namespace, String name)
Taken from help:
*Added in API level 1. Returns the attributes value identified by namespace URI and namespace localName. If namespaces are disabled namespace must be null. If current event type is not START_TAG then IndexOutOfBoundsException will be thrown.*
Pay attention: Replace all the String comparisons!
name == "row"
With:
name.equals("row")
Hope it helps.

How to get XML content with matching tags in Pull Parser for Android?

I am trying to use the Pull Parser in Android to get values of image2 and image3.
<item>
<title>Title Goes Here!</title>
<picture><![CDATA[http://blahblah.image.jpg]]></picture>
<picture><![CDATA[http://blahblah.image2.jpg]]></picture>
<picture><![CDATA[http://blahblah.image3.jpg]]></picture>
</item>
...
case XmlPullParser.END_TAG
if (tagname.equalsIgnoreCase("item")
_feed.addItem(_item);
} else if (tagname.equalsIgnoreCase("title")) {
_item.setTitle(theString)
} else if (tagname.equalsIgnoreCase("picture")) {
_item.setLargeImage(theString)
} else if (tagname.equalsIgnoreCase("picture")) {
_item.setLargeImage2(theString)
} else if (tagname.equalsIgnoreCase("picture")) {
_item.setLargeImage3(theString)
}
break;
...
I can parse and load the first image but, I don't know what the next step is to get the other images? Thanks for the help.
There's a logic error in your code:
} else if (tagname.equalsIgnoreCase("picture")) {
_item.setLargeImage(theString)
} else if (tagname.equalsIgnoreCase("picture")) {
_item.setLargeImage2(theString)
} else if (tagname.equalsIgnoreCase("picture")) {
_item.setLargeImage3(theString)
}
The code that follows the second and third else if condition test will never be executed, if the encountered tag is picture, the control flow will end at the first if (tagname.equalsIgnoreCase("picture")).
You may do the following to achieve what you want:
List<String> imageList = new ArrayList<String>();
} else if (tagname.equalsIgnoreCase("picture")) {
imageList.add(theString);
}
// after you finish parsing the xml file, set the image URLs on your item.
_item.setLargeImage(imageList.get(0));
_item.setLargeImage2(imageList.get(1));
_item.setLargeImage3(imageList.get(2));

If statement within If statement's else

Still new to Java/android so trying to figure out the best way to code a multilevel if statement. What I'm trying to do is for a combat system that needs to check if player/npc is alive. If they are alive it then will check to see if they scored a critical hit. If they didn't critical hit then will see if they hit or missed.
combat = mydbhelper.getCombat();
startManagingCursor(combat);
if (playerCurHp == 0) {
combat.moveToPosition(11);
npcCombatStory = combat.getString(combat.getColumnIndex(dbhelper.KEY_COMBATDESC));
} else {
if (playerCritFlag.equals("Critical")) {
combat.moveToPosition(2);
playerCombatStory = combat.getString(combat.getColumnIndex(dbhelper.KEY_COMBATDESC));
} else {
if (playerHitFlag.equals("Hit")) {
combat.moveToPosition(1);
playerCombatStory = combat.getString(combat.getColumnIndex(dbhelper.KEY_COMBATDESC));
}
if (playerHitFlag.equals("Miss")) {
combat.moveToPosition(3);
playerCombatStory = combat.getString(combat.getColumnIndex(dbhelper.KEY_COMBATDESC));
}
}
}
if (npcCurHp == 0) {
combat.moveToPosition(10);
npcCombatStory = combat.getString(combat.getColumnIndex(dbhelper.KEY_COMBATDESC));
} else {
if (npcCritFlag.equals("Critical")) {
combat.moveToPosition(5);
npcCombatStory = combat.getString(combat.getColumnIndex(dbhelper.KEY_COMBATDESC));
} else {
if (npcHitFlag.equals("Hit")) {
combat.moveToPosition(4);
npcCombatStory = combat.getString(combat.getColumnIndex(dbhelper.KEY_COMBATDESC));
}
if(npcHitFlag.equals("Miss")) {
combat.moveToPosition(6);
npcCombatStory = combat.getString(combat.getColumnIndex(dbhelper.KEY_COMBATDESC));
}
}
}
Is what I'm using. Was working when I had the if statements all separate. But it would check each one and do actions I don't need (If they hit, pull String, if crit pull another, then if dead pull again). Trying to make it stop when it finds the "Flag" that matches. When doing my rolls if the player hits it sets the flag to "Hit" like below code.
Random attackRandom = new Random();
int attackRoll = attackRandom.nextInt(100);
totalAtt = attackRoll + bonusAttack + weaponAtt + stanceAtt;
Random defensiveRandom = new Random();
int defenseRoll = defensiveRandom.nextInt(100);
npcDef = defenseRoll + npcDodge + npcBonusDodge;
if (totalAtt > npcDef) {
playerHitFlag = "Hit";
playerDamage();
} else {
playerHitFlag = "Miss";
npcAttack();
}
At the end it takes these playerCombatStory and npcCombatStory strings and uses them to setText to show the player what happened on that turn of combat.
I think you are looking for the else if statement:
if (condition) {
}
else if (other_condition) {
}
else if (another_condition) {
}
else {
// There can only be one else statement in a given if-else block
}
Your question isn't clear. But meaningful advice can still be offered.
Personally, I find this code hard to read. I think it'll be hard to maintain in the future as your logic becomes more complex.
I think you need to decouple the logic of what's done from how you decide. Encapsulate what's done in a Command object and use a map or state machine to look up what to do.
I would change the type of npcCritFlag to int or enum. Then use switch statement with case
This should look much better and easier to understand

XMLPullParser Out of Memory (Android)

I'm stuck in trying to handle an out of memory error in Android while trying to parse a response from a HTTPTransfer using SOAP. Overall the transport is fine until I ask for a large image. The image is about 901KB is size, but for some reason it causes Android to run out of memory while parsing it. Here is the code:
public void parseWithPullParser(InputStream is) {
try {
XmlPullParser parser = GenericHandler.createParser(this.parserTypeName); // new
// org.xmlpull.mxp1.MXParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(is, null);
Log.d(TAG, "Name of class being parsed: " + resultClassName);
for (int eventType = parser.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = parser
.next()) {
switch (eventType) {
case XmlPullParser.START_DOCUMENT: {
break;
}
case XmlPullParser.START_TAG: {
String name = parser.getName();
String prefix = null;
if ("Envelope".equals(name) || "Header".equals(name) || "Body".equals(name)
|| "return".equals(name)) {
prefix = "env:"; // TODO: Hack-Hack-Hack... :)
}
name = prefix == null ? name : prefix + ":" + name;
this.startElement(name);
break;
}
case XmlPullParser.TEXT: {
String text = parser.getText();
if (text != null) {
if (resultClassName.contains("ImageSingle")) {
Log.d(TAG, "Text passage: " + text);
}
if (content == null) {
content = new String();
}
content = text; // Original system used a string builder
// but only for a single section, for
// large images this was a problem, but
// a single string object appears to
// have the same affect
// char[] ch = text.toCharArray(); //original
// this.characters(ch, 0, ch.length); //original
}
break;
}
case XmlPullParser.END_TAG: {
String name = parser.getName();
String prefix = null;
if ("Envelope".equals(name) || "Header".equals(name) || "Body".equals(name)
|| "return".equals(name)) {
prefix = "env:"; // TODO: Hack-Hack-Hack... :)
}
name = prefix == null ? name : prefix + ":" + name;
this.endElement(name);
break;
}
default: {
break;
}
}
}
} catch (Exception except) {
Log.e(this.getClass().getSimpleName(), except.toString(), except);
}
}
I found the library here. The issue (I believe) is when it does parser.next() because it reads in the image data (which is sent to me in a Base64 encoded string) and then tries to do parser.getText(). If I am understanding everything properly the way it outputs the string is by repetitive calls to the internal stringbuilder that will keep repeating .toString() to itself until it generates the parsed string. The image in question is about 1.2 million characters and as each character is 2 bytes, that implies 2.4 MB (the image though is 901 KB originally..but I guess there's extra data that gets parsed?) if I understand this correctly. But the heap expands to over 16 MB which causes the app to crash on stock VM settings when this method is called.
I doubt this is a unique situation and as such would love to hear how others have handled this problem. I've thought about maybe just throwing the string to a file on the SD card to keep it out of memory but it seems that for me to get the string I need parser.getText...which therein lies the problem.
Pursuant to Stephan Branczyk's comment here is my comment extracted and marked as the answer to my question.
For anyone that comes across this, I eventually ended up using a sax parser, I got the idea from here : helloandroid.com/tutorials/newsdroid-rss-reader

Categories

Resources