XmlPullParser - Parse nested tag
Asked Answered
B

7

10

I have this XML:

<menu>
    <day name="monday">
        <meal name="BREAKFAST">
            <counter name="Bread">
               <dish>
                   <name>Plain Bagel</name>
               </dish>
            <counter/>
        <meal/>
    <day/>
    <day name="tuesday">
        <meal name="LUNCH">
            <counter name="Other">
               <dish>
                   <name>Cheese Bagel</name>
               </dish>
            <counter/>
        <meal/>
    <day/>
<menu/>

Now here is what I am trying to do, if the day tag's attribute is equal to monday. And then meals tag attribute is equal to BREAKFAST, then I want to get the counter's attribute. "Bread".

I have set up xml pull parser, but I am struggling getting this value. Here is what I have tried, I now I see that it can't and won't work... So any help on how I could set it up to do this would be great.

while (eventType != XmlResourceParser.END_DOCUMENT) {
        String tagName = xmlData.getName();

        switch (eventType) {
            case XmlResourceParser.START_TAG:
                if (tagName.equalsIgnoreCase("day")) {
                    if (xmlData.getAttributeValue(null, "name").equalsIgnoreCase(day)) {
                        if (tagName.equalsIgnoreCase("meal")) {
                            mealArray.add(xmlData.getAttributeValue(null, "name"));
                            Log.i(TAG, xmlData.getAttributeValue(null, "name"));
                        }
                    }


                }
                break;
            case XmlResourceParser.TEXT:
                break;
            case XmlPullParser.END_TAG:

                break;
        }
        eventType = xmlData.next();
    }
Briefing answered 11/12, 2014 at 4:37 Comment(0)
G
7

You would need to add logic for parsing nested tags:

A very simple example to help you move ahead:

I parsed this String:

<menu><day name=\"monday\"><meal name=\"BREAKFAST\"><meal/><day/></menu>

Code:

try {
    factory = XmlPullParserFactory.newInstance();
    factory.setNamespaceAware(true);
    XmlPullParser xpp = factory.newPullParser();

    xpp.setInput(new StringReader("<menu><day name=\"monday\"><meal name=\"BREAKFAST\"><meal/><day/></menu>"));
    int eventType = xpp.getEventType();
    while (eventType != XmlResourceParser.END_DOCUMENT) {
        String tagName = xpp.getName();

        switch (eventType) {
            case XmlResourceParser.START_TAG:
                if (tagName.equalsIgnoreCase("day")) {
                    if (xpp.getAttributeValue(null, "name").equalsIgnoreCase("MONDAY")) {
                        int eventType2 = xpp.next();
                        while (eventType2 != XmlResourceParser.END_DOCUMENT) {
                            String tagName2 = xpp.getName();
                            switch (eventType2) {
                            case XmlResourceParser.START_TAG:
                                if (tagName2.equalsIgnoreCase("meal")) {
                                    Log.i("tag", "meal: " + xpp.getAttributeValue(null, "name"));
                                }
                                break;
                            case XmlResourceParser.TEXT:
                                break;
                            case XmlPullParser.END_TAG:
                                break;
                            }
                            eventType2 = xpp.next();
                        }
                    }
                }
            break;
            case XmlResourceParser.TEXT:
            break;
            case XmlPullParser.END_TAG:
            break;
        }
        eventType = xpp.next();
    }

} catch (XmlPullParserException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} catch (Exception e) {
    e.printStackTrace();
}

You see the difference, right?

I added this, basically, just after getting the day what I wanted. (In my case, hard coded String "Monday".)

int eventType2 = xpp.next();

And based upon that eventType2, retrieved tagName2 which would be for "meal"

A better example to help you write your logic in a nice manner.

Hope this helps.

Gipsy answered 19/12, 2014 at 14:1 Comment(0)
S
3

Well, just looking logically at your switch statement right now:

String tagName = xmlData.getName();


            if (tagName.equalsIgnoreCase("day")) {
                if( . . . ) {
                    if (tagName.equalsIgnoreCase("meal")) {
                      //do something
                    }
                }

Do you see the issue right there? You won't EVER have tagName.equals("day") and tagName.equals("meal") both be true! Keep getting the child of the xmlData and get it's name, then do another if statement.

You need to update the value of tagName after every if.

Stinkhorn answered 15/12, 2014 at 2:57 Comment(1)
yes I see the problem, just not sure how to fix it. And I kind of see what your saying could you post some code or more to explain it some more? Cause I only want to the when its Monday and BREAKFASTBriefing
A
3

Main Idea

store your information in a local variable and then check if visited tags is equal to your desired pattern or not, if it is, do what you want. here is the idea:

  String currentDay;
  String mealOfCurrentDay;

  while (eventType != XmlResourceParser.END_DOCUMENT) {
        String tagName = xmlData.getName();

        switch (eventType) {
            case XmlResourceParser.START_TAG:
                if (tagName.equalsIgnoreCase("day")) 
                       currentDay = xmlData.getAttributeValue(null, "name");
                }
                if (tagName.equalsIgnoreCase("meal")) 
                       mealOfCurrentDay = xmlData.getAttributeValue(null, "name");
                }
                if (tagName.equalsIgnoreCase("counter ")){
                    // now we must check our stored pattern
                    // step one: day must be monday
                    if(currentDay != null && currentDay.equalsIgnoreCase("monday")){
                         // step tow: meal must be BREAKFAST
                         if(mealOfCurrentDay!= null && mealOfCurrentDay.equalsIgnoreCase("BREAKFAST")){
                              counter = xmlData.getAttributeValue(null, "name");
                              // Wow we have done :-)
                         }
                         else{
                          // no, it is not my desierd pattern so I clear my history
                             currentDay = "";
                             mealOfCurrentDay = "";
                         }  
                    }else{
                        // no, it is not my desierd pattern so I clear my history
                        currentDay = "";
                        mealOfCurrentDay = "";
                    }
                }
                break;
            case XmlResourceParser.TEXT:
                break;
            case XmlPullParser.END_TAG:

                break;
        }
        eventType = xmlData.next();
    }
Abnegate answered 15/12, 2014 at 4:8 Comment(0)
B
3

Firstly your XML is not in correct format. Check at any website. Say on this XML Validator

The closing tag should be like this

< meal > < /meal >

not => < meal >< meal />

After correcting XML as response string below, You can try this code, its working

    String response = "<menu><day name=\"monday\"><meal name=\"BREAKFAST\"><counter name=\"Bread\"><dish><name>Plain Bagel</name></dish></counter></meal></day><day name=\"tuesday\"><meal name=\"LUNCH\"><counter name=\"Other\"><dish><name>Cheese Bagel</name></dish></counter></meal></day></menu>";
    Document doc;
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = dbf.newDocumentBuilder();
    InputSource isr = new InputSource();
    isr.setCharacterStream(new StringReader(response));

    doc = db.parse(isr);
    
    try{
        doc.getDocumentElement().normalize();
        NodeList nodes = doc.getElementsByTagName("day");
        for (int i = 0; i <= nodes.getLength(); i++) {
            Node nNode = nodes.item(i);
            
            if (nNode.getNodeType() == Node.ELEMENT_NODE) {
                
                
                Element eElement = (Element) nNode;
                System.out.println("day"+eElement.getAttribute("name"));
                System.out.println("meal"+eElement.getElementsByTagName("meal").item(0).getAttributes().getNamedItem("name").getNodeValue());
                System.out.println("counter"+eElement.getElementsByTagName("counter").item(0).getAttributes().getNamedItem("name").getNodeValue());
                System.out.println("dish :" + eElement.getElementsByTagName("name").item(0).getTextContent());

            }
        }
    
    }
    catch(Exception e){
        
    }

Hope this helps !

Butanone answered 18/12, 2014 at 10:59 Comment(1)
So basically you are recommending NOT using XMLPullParser for getting nested tags?Avigation
O
2
       while (eventType != XmlResourceParser.END_DOCUMENT) {
        String tagName = xmlData.getName();

        switch (eventType) {
           case XmlResourceParser.START_TAG:
                if (tagName.equalsIgnoreCase("day")) {
                    day = xmlData.getAttributeValue(null, "name");
                }
                if (tagName.equalsIgnoreCase("meal")) {
                    meal = xmlData.getAttributeValue(null, "name");
                }
                if (tagName.equalsIgnoreCase("counter")) {
                    counter = xmlData.getAttributeValue(null, "name");
                }

                break;
            case XmlResourceParser.TEXT:
                data += xmlData.getText();
                if (tagName.equalsIgnoreCase("name")) {
                    name= xmlData.getText();
                }
                break;
            case XmlPullParser.END_TAG:
                if (tagName.equals("day")) {
                    recordsFound++;

                }
                break;
        }
        publishProgress(new String[]{day,meal,counter});
        eventType = xmlData.next();

    }

First, breaks are missing in your switch-case statement. Secondly, attributes are always parsed from START_TAG case. text inside tags are parsed in TEXT case and END_TAG is useful for making objects or arraylists based on its nesting.

onProgressUpdate must look like this:

@Override
protected void onProgressUpdate(String... values) {
   super.onProgressUpdate(values);
   if (values.length == 0) {
        Log.i(TAG, "no data");
    }
    else {
       String day = values[0];
       String meal= values[1];
       String counter= values[2];
    }

}
Orectic answered 11/12, 2014 at 4:55 Comment(3)
what do I change in onProgressUpdateBriefing
okay I think the publish progress needs to be different because or something because when I add logs in the onProgressUpdate it logs it way to many timesBriefing
how do I not get all of the nested tags just the ones under a certain day? could you look at this question, #27476859Briefing
B
2

First, your XML is broken, look at the end tags. Should be:

<menu>
<day name="monday">
    <meal name="BREAKFAST">
        <counter name="Bread">
           <dish>
               <name>Plain Bagel</name>
           </dish>
        </counter>
    /</meal>
</day>
<day name="tuesday">
    <meal name="LUNCH">
        <counter name="Other">
           <dish>
               <name>Cheese Bagel</name>
           </dish>
       </counter>
    </meal>
</day>

Second, consider using not a pull parser, you are using a lot of code lines but there are shorter ways to your goal (e.g. XMLBeam (Disclosure: I'm affiliated with that project)):

public class Test {
@XBDocURL("res://menu.xml")
public interface Menu {
    @XBRead("//day[@name='monday']/meal[@name='BREAKFAST']/counter/@name")
    String getCounterName();
}

@Test
public void testMenu() throws IOException {
    String name = new XBProjector().io().fromURLAnnotation(Menu.class).getCounterName();
    assertEquals("Bread", name);
}

}

Bronze answered 15/12, 2014 at 13:4 Comment(0)
M
2

Use XPath and be in peace

package xml;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

public class XPathTest {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true); // never forget this!
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("sample.xml");

    XPathFactory xpathfactory = XPathFactory.newInstance();
    XPath xpath = xpathfactory.newXPath()
XPathExpression expr = xpath.compile("string(/menu/day[@name='monday']/meal[@name='BREAKFAST']/counter/@name))");
    Object result = expr.evaluate(doc, XPathConstants.NODESET);
return (String)result;
}
}
Marten answered 21/12, 2014 at 20:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.