ioscocoa-touchxml-parsingtbxml

Use TBXML to grab an unknown number of elements


My question is: How do I extract an unknown number of XML elements with a predictable element name and grab their attribute values?

I'm using TBXML (love it so far) to parse weather observation data from the national weather service.

Here is an example of the XML (the <METAR> element is the pertinent part)

<METAR>
    <raw_text>
        KDEN 181953Z 01006KT 10SM FEW080 SCT110 SCT150 31/06 A3007 RMK AO2 SLP093 OCNL LTGCG DSNT SW VIRGA S-SW CB DSNT SW MOV E T03110061
    </raw_text>
    <station_id>KDEN</station_id>
    <observation_time>2014-08-18T19:53:00Z</observation_time>
    <latitude>39.85</latitude>
    <longitude>-104.65</longitude>
    <temp_c>31.1</temp_c>
    <dewpoint_c>6.1</dewpoint_c>
    <wind_dir_degrees>10</wind_dir_degrees>
    <wind_speed_kt>6</wind_speed_kt>
    <visibility_statute_mi>10.0</visibility_statute_mi>
    <altim_in_hg>30.070866</altim_in_hg>
    <sea_level_pressure_mb>1009.3</sea_level_pressure_mb>
    <quality_control_flags>
        <auto_station>TRUE</auto_station>
    </quality_control_flags>
    <sky_condition sky_cover="FEW" cloud_base_ft_agl="8000"/>
    <sky_condition sky_cover="SCT" cloud_base_ft_agl="11000"/>
    <sky_condition sky_cover="SCT" cloud_base_ft_agl="15000"/>
    <flight_category>VFR</flight_category>
    <metar_type>METAR</metar_type>
    <elevation_m>1640.0</elevation_m>
</METAR>

The important part are those <sky_condition> elements and their attributes.

As the weather changes the number of <sky_condition> elements will change. There may be as few as one, and as many as lots.

I will eventually display them all in order of iteration; that is the first <sky_condition> element is the the most important, followed by the next, ect.

I'm using the traverseElement method to iterate over the XML. I need to grab the attributes of however many <sky_condition> elements there are and store them for display.

Here is the traverseElement method implementation. I am passing the argument metarElement which represents the XML pasted above.

- (void) traverseElement:(TBXMLElement *)element {

    do {
        // Display the name of the element
        NSLog(@"%@",[TBXML elementName:element]);

        // Obtain first attribute from element
        TBXMLAttribute * attribute = element->firstAttribute;

        while (attribute) {
            // Display name and value of attribute to the log window
            NSLog(@"%@->%@ = %@",  [TBXML elementName:element],
                  [TBXML attributeName:attribute],
                  [TBXML attributeValue:attribute]);

            // Obtain the next attribute
            attribute = attribute->next;
        }

        // if the element has child elements, process them
        if (element->firstChild)
            [self traverseElement:element->firstChild];

        // Obtain next sibling element
    } while ((element = element->nextSibling));
}

I know I need to extract the attribute values from the <sky_condition> elements and store them in an array (arrays?), but I can't get my head around the conditionals I need to write to accomplish this.

Any help or hints would be much appreciated.


Solution

  • I accomplished this by creating some NSMutableArrays that will store the attributes I need. In my @interface:

    @property (nonatomic) BOOL isSkyClear;
    @property (strong, nonatomic) NSMutableArray *fewArray;
    @property (strong, nonatomic) NSMutableArray *sctArray;
    @property (strong, nonatomic) NSMutableArray *bknArray;
    @property (strong, nonatomic) NSMutableArray *ovcArray; 
    

    And method implementation - (void) traverseElement:(TBXMLElement *)element {:

     do {
            // Display the name of the element
            NSLog(@"%@",[TBXML elementName:element]);
    
            // Obtain first attribute from element
            TBXMLAttribute * attribute = element->firstAttribute;
    
            while (attribute) {
                // Display name and value of attribute to the log window
    
                NSLog(@"%@->%@ = %@",  [TBXML elementName:element],
                    [TBXML attributeName:attribute],
                    [TBXML attributeValue:attribute]);
    
                if ([[TBXML attributeValue:attribute] isEqualToString:@"CLR"]) {
                    self.isSkyClear = YES;
    
                } else if ([[TBXML attributeValue:attribute] isEqualToString:@"FEW"]) {
                    attribute = attribute->next;
                    NSString *cloudBase = [TBXML attributeValue:attribute];
                    [self.fewArray addObject:cloudBase];
                    break;
    
                } else if ([[TBXML attributeValue:attribute] isEqualToString:@"SCT"]) {
                    attribute = attribute->next;
                    NSString *cloudBase = [TBXML attributeValue:attribute];
                    [self.sctArray addObject:cloudBase];
                    break;
    
                } else if ([[TBXML attributeValue:attribute] isEqualToString:@"BKN"]) {
                    attribute = attribute->next;
                    NSString *cloudBase = [TBXML attributeValue:attribute];
                    [self.bknArray addObject:cloudBase];
                    break;
    
                } else if ([[TBXML attributeValue:attribute] isEqualToString:@"OVC"]) {
                    attribute = attribute->next;
                    NSString *cloudBase = [TBXML attributeValue:attribute];
                    [self.ovcArray addObject:cloudBase];
                    break;                    
                } 
    
                attribute = attribute->next;   
            }
    
            // Obtain next sibling element
        } while ((element = element->nextSibling));
    }