8

Basically I have an XML response that is returned and a string, and i need to loops through the xml and store all the information in an array. here is the xml

<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schema.2sms.com/2.0/schema/0310_ResponseReportStandard.xsd" Version="1.0">
    <Error>
        <ErrorCode>00</ErrorCode>
        <ErrorReason>OK</ErrorReason>
    </Error>
    <ResponseData>
        <Identification>
            <UserID>jonathan.pink@2sms.com</UserID>
        </Identification>
        <Result>2 records were returned</Result>
        <Detail>
            <ReportTitle>Message Summary: Today</ReportTitle>
            <Record>
                <Destination>447790686158</Destination>
                <Status>WithNetwork</Status>
                <GUID><![CDATA[2011-03-22T10:54:22.097Z]]></GUID>
                <DateSubmitted>2011-03-22T10:54:22.097</DateSubmitted>
                <DateToSend></DateToSend>
                <DateSent>2011-03-22T10:54:22.533</DateSent>
                <DateReceived></DateReceived>
                <Message><![CDATA[Yet again another test]]></Message>
                <ID>2011-03-22 10:54:22.250HIHIIOJTFVETW85TS</ID>
            </Record>
            <Record>
                <Destination>447790686158</Destination>
                <Status>SUCCESS</Status>
                <GUID><![CDATA[2011-03-22T10:50:40.064Z]]></GUID>
                <DateSubmitted>2011-03-22T10:50:40.063</DateSubmitted>
                <DateToSend></DateToSend>
                <DateSent>2011-03-22T10:50:42.473</DateSent>
                <DateReceived>2011-03-22T10:50:54.570</DateReceived>
                <Message><![CDATA[This is a test]]></Message>
                <ID>2011-03-22 10:50:40.210DRUDVMCEZGETW85TS</ID>
            </Record>
            <ReportPage ReportID="775797" ItemsPerPage="25" Page="1" TotalItems="2" />
        </Detail>
    </ResponseData>
</Response>

I need those 2 <records> and all there data to be stored in an array. so....

an records array -> array of records -> array of each records, data....

I have been sitting here trying to work this out using TBXML which is easy enough to grab a single node.... but I can't do this :(

MrPink
  • 1,325
  • 4
  • 18
  • 27
  • 2
    Please see if you can accept more answers. You seem to have reasonable answers to some of your questions that are left unaccepted. – occulus Mar 23 '11 at 15:02

4 Answers4

14

Alright, your first step would be to make a class that will parse the data. Call it RecordParser, for example. We now need to add a couple methods in the header, as well as a NSMutableArray.

@interface RecordParser : NSObject {
    NSMutableArray *records;    
}
@property(nonatomic,retain)NSMutableArray *records;

-(void)loadRecords:(NSString *)records;
-(void)traverseElement:(TBXMLElement *)element;

@end

Now, go ahead and charge into your implementation. We now need to implement those two methods to do what we want them to do.

- (void)loadRecords:(NSString *)records {
    NSString *someXML = @"http://www.something.com/somexml.xml";
    TBXML *tbxml = [[TBXML tbxmlWithURL:[NSURL URLWithString:someXML]] retain];

    records = [NSMutableArray array];
    [records retain];

    if (tbxml.rootXMLElement)
        [self traverseElement:tbxml.rootXMLElement];
    [tbxml release];
}

Basically that method will grab the XML file in question and begin the parsing process. Also, you're initializing your array and retaining it. Now we come to the cheese.

- (void) traverseElement:(TBXMLElement *)element {
    do {
        if (element->firstChild) 
            [self traverseElement:element->firstChild];

        if ([[TBXML elementName:element] isEqualToString:@"Record"]) {
            TBXMLElement *destination = [TBXML childElementNamed:@"Destination" parentElement:element];
            TBXMLElement *status = [TBXML childElementNamed:@"Status" parentElement:element];
            TBXMLElement *guid = [TBXML childElementNamed:@"GUID" parentElement:element];
            TBXMLElement *dateSub = [TBXML childElementNamed:@"DateSubmitted" parentElement:element];
            TBXMLElement *dateToSend = [TBXML childElementNamed:@"DateToSend" parentElement:element];
            TBXMLElement *dateSent = [TBXML childElementNamed:@"DateSent" parentElement:element];
            TBXMLElement *dateReceived = [TBXML childElementNamed:@"DateReceived" parentElement:element];
            TBXMLElement *message = [TBXML childElementNamed:@"Message" parentElement:element];
            TBXMLElement *id = [TBXML childElementNamed:@"ID" parentElement:element];

            [records addObject:[NSArray arrayWithObjects:
                                  [TBXML textForElement:destination],
                                  [TBXML textForElement:status],
                                  [TBXML textForElement:guid],
                                  [TBXML textForElement:dateSub],
                                  [TBXML textForElement:dateToSend],
                                  [TBXML textForElement:dateSent],
                                  [TBXML textForElement:dateReceived],
                                  [TBXML textForElement:message],
                                  [TBXML textForElement:id],nil]];  
        }
    } while ((element = element->nextSibling));  
}

Basically what the method does is transverse the XML file looking for an element with the name you're looking for, then it grabs the data from the child nodes. Additionally, the data is added to the records array. So basically, when it's done it should have the data you're wanting in your records array, which you can manipulate all you want.

This is completely untested. Don't blame me if it blows up your computer and kills your cat. I normally wouldn't take all the work to write a complete method like this, but I happen to like TBXML. Please let me know if it works. I really would appreciate knowing.

sudo rm -rf
  • 29,408
  • 19
  • 102
  • 161
  • YES! thanks it works exactly how i want it too! But i'm onto another problem with selecting paticular entrys from the array to be used in a tableview cell.... But thanks so much its cured my 2 days headache! – MrPink Mar 23 '11 at 17:03
  • @MrPink: You just need to put which `objectAtIndex` of the array you want to be displayed in your table in `cellForRowAtIndexPath`. – sudo rm -rf Mar 23 '11 at 17:08
  • cell.textLabel.text = [NSString stringWithFormat:@"%@",[records objectAtIndex:indexPath.row]]; this is what i am using however it is printing out everything that is within each record of the array – MrPink Mar 23 '11 at 17:09
  • @MrPink: What data needs to be displayed? ID? GUID? – sudo rm -rf Mar 23 '11 at 17:10
  • @MrPink: Can you make a new question that has the data in question (the `records` array above) in your question? Then I can answer it more clearly. – sudo rm -rf Mar 23 '11 at 17:14
  • yes ok, please check my profile and questions in a few seconds. – MrPink Mar 23 '11 at 17:17
1

I wrote recursive function to parse any properly created xml with TBXML library.

In my project I have a class to parse XML. It has a Class Method named: + (id) infoOfElement: (TBXMLElement*) element

How to use:

TBXML *tbxml = [TBXML tbxmlWithXMLData:yourData];
TBXMLElement *rootXMLElement = tbxml.rootXMLElement;

id parsedData = [self infoOfElement: rootXMLElement];

    //return NSString or NSDictionary ot NSArray of parsed data
    + (id) infoOfElement: (TBXMLElement*) element
    {
        if (element->text)
            return [TBXML textForElement:element];
        NSMutableDictionary *info = [NSMutableDictionary new];
        TBXMLAttribute *attribute = element->firstAttribute;
        if (attribute) {
            do {
                [info setValue:[TBXML attributeValue:attribute] forKey:[TBXML attributeName:attribute]];
                attribute = attribute -> next;
            } while (attribute);
        }
        TBXMLElement *child = element->firstChild;
        if (child){
            TBXMLElement *siblingOfChild = child->nextSibling;
            //If we have array of children with equal name -> create array of this elements
            if ([[TBXML elementName:siblingOfChild] isEqualToString:[TBXML elementName:child]]){
                NSMutableArray *children = [NSMutableArray new];
                do {
                    [children addObject:[self infoOfElement:child]];
                    child = child -> nextSibling;
                } while (child);
                return [NSDictionary dictionaryWithObject:children forKey:[TBXML elementName:element]];
            }
            else{
                do {
                    [info setValue:[self infoOfElement:child] forKey:[TBXML elementName:child]];
                    child = child -> nextSibling;
                } while (child);
            }
        }            
        return info;
    }
Nikolay Shubenkov
  • 3,133
  • 1
  • 29
  • 31
0

Use Apple's NSXMLParser; it made be old-school and all but it's really efficient.

Setup your XMLParser accordingly (use the NSXMLParserDelegate protocol).

Once your parser hits call the parser:didStartElement:namespaceURI:qualifiedName:attributes: delegate call back and the elementName is equal to Record (from what you seem to want).

Alloc' init' an NSMutableDictionary. Then like above if elementName is equal to Destination then [myDictionary setObject: elementName forKey: @"Destination"] and et cætera.

Hope that helped :).

Little side note: prefer using Apple's "technology" instead of 3rd parties: it's more efficient and the possibilities are endless.

G33kz0r
  • 750
  • 1
  • 8
  • 17
  • 3
    However, TBXML has been proven to be far faster at parsing than `NSXMLParser`. I use TBXML and I love it. :) – sudo rm -rf Mar 23 '11 at 15:11
  • Its true and this is how i'd like to do it. sudo how would u use tbxml to parse through my xml above? – MrPink Mar 23 '11 at 15:48
-2

It's better to use NSXMLParser, because it's an official release of Apple.

All the documentation of NSXMLParser is here.

Also, here's a NSXMLParser Tutorial.

rdurand
  • 7,342
  • 3
  • 39
  • 72
Mohammad Kalim
  • 33
  • 1
  • 2
  • 6