XmlPullParser - Parse nested tag

6k views Asked by At

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();
    }
7

There are 7 answers

3
Amrut Bidri On
       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];
    }

}
1
Alex K On

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.

0
mmlooloo On

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();
    }
0
Cfx On

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);
}

}

1
ajitksharma On

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 !

0
MysticMagicϡ On

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.

0
Kaushik Bose On

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;
}
}