2016年8月18日 星期四

如何取得回傳的 xml 值,使用 NSXMLParser 解析資料 on iOS

操作環境:Mac OS X El Capitan (10.11.6)、Xcode 7.3.1

目標:實際應用 NSXMLParser 解析 SOAP web service 回傳回來的 XML 資料。在這篇為使用 soap 傳送度C的數值,拿到伺服器傳回來的度F值。

  1. 建一個乾淨的 Single View Application 專案

  2. 修改 ViewController.m,implements 處理 xml 跟連線的 class,以及加入伺服器傳資料回來時,要儲存回傳資料的 variables
    @interface ViewController () <NSXMLParserDelegate, NSURLConnectionDataDelegate>
    
    @property NSMutableData *webData;
    
    // the dictionary in which we’ll store the value we seek for the Fahrenheit result.
    @property (nonatomic, strong) NSMutableDictionary *dataStorageDict;
    // will be used to store the found characters of the elements of interest.
    @property (nonatomic, strong) NSMutableString *foundValue;
    // will be assigned with the name of the element that is parsed at any moment.
    @property (nonatomic, strong) NSString *currentElement;
    
    @end

    如下圖

  3. 修改 viewDidLoad method
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        NSString *soapMessage = [NSString stringWithFormat:@"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                                 "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
                                 "<soap:Body>\n"
                                     "<CelsiusToFahrenheit xmlns=\"http://www.w3schools.com/xml/\">"
                                         "<Celsius>%@</Celsius>"
                                     "</CelsiusToFahrenheit>"
                                 "</soap:Body>\n"
                                 "</soap:Envelope>\n", @"30"];
        NSString *msgLength = [NSString stringWithFormat:@"%lu", (unsigned long)[soapMessage length]];
        
        NSURL *url = [NSURL URLWithString:@"http://www.w3schools.com/xml/tempconvert.asmx"];
        NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:url];
        
        [theRequest addValue:@"http://www.w3schools.com/xml/CelsiusToFahrenheit" forHTTPHeaderField:@"SOAPAction"];
        [theRequest addValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
        [theRequest addValue:msgLength forHTTPHeaderField:@"Content-Length"];
        [theRequest setHTTPMethod:@"POST"];
        [theRequest setHTTPBody:[soapMessage dataUsingEncoding:NSUTF8StringEncoding]];
        
        NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
        
        if (theConnection) {
            self.webData = [NSMutableData data];
        } else {
            NSLog(@"theConnection is NULL");
        }

  4. 加入 NSURLConnectionDataDelegate 需要的 methods 並修改
    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        NSLog(@"ERROR with theConenction");
    }
    
    -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        [self.webData setLength: 0];
    }
    
    -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        [self.webData appendData:data];
    }
    -(void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
        NSLog(@"DONE. Received Bytes: %lu", (unsigned long)[self.webData length]);
        printf("\n");
        NSString *theXML = [[NSString alloc]
                            initWithBytes:[self.webData mutableBytes]
                                   length:[self.webData length]
                                 encoding:NSUTF8StringEncoding];
        
        /*
         HTTP/1.1 200 OK
         Content-Type: text/xml; charset=utf-8
         Content-Length: length
         
         <?xml version="1.0" encoding="utf-8"?>
         <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
         <soap:Body>
             <CelsiusToFahrenheitResponse xmlns="http://www.w3schools.com/xml/">
                 <CelsiusToFahrenheitResult>string</CelsiusToFahrenheitResult>
             </CelsiusToFahrenheitResponse>
         </soap:Body>
         </soap:Envelope>
         */
        NSLog(@"theXML=%@", theXML);
        printf("\n");
        
        NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:self.webData];
        
        // Don't forget to set the delegate!
        xmlParser.delegate = self;
        
        // Run the parser
        BOOL parsingResult = [xmlParser parse];
        if (parsingResult) {
            NSLog(@"SUCCESS");
        } else {
            NSLog(@"FAILED");
        }
    }
    

  5. 取得伺服器回傳的 xml 後,parse 它時會執行的 methods 並修改
    -(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
        NSLog(@"%@", [parseError localizedDescription]);
    }
    
    -(void)parserDidStartDocument:(NSXMLParser *)parser {
        NSLog(@"in parserDidStartDocument()");
        
        // Initialize the mutable string that we'll use during parsing.
        self.foundValue = [[NSMutableString alloc] init];
    }
    
    -(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
        namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
    {
        NSLog(@"1 elementName=%@", elementName);
        
        // If the current element name is equal to "CelsiusToFahrenheitResult" then initialize the temporary dictionary.
        if ([elementName isEqualToString:@"CelsiusToFahrenheitResult"]) {
            self.dataStorageDict = [[NSMutableDictionary alloc] init];
        }
        
        // Keep the current element.
        self.currentElement = elementName;
    }
    
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
        NSLog(@"2 result=%@", string);
        
        // Store the found characters if only we're interested in the current element.
        if ([self.currentElement isEqualToString:@"CelsiusToFahrenheitResult"]) {
            if (![string isEqualToString:@"\n"]) {
                [self.foundValue appendString:string];
            }
        }
    }
    
    - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
        namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
    {
        NSLog(@"3 elementName=%@", elementName);
        
        if ([elementName isEqualToString:@"CelsiusToFahrenheitResult"]) {
            // If the Theme element was found then store it.
            [self.dataStorageDict setObject:[NSString stringWithString:self.foundValue] forKey:@"CelsiusToFahrenheitResult"];
        }
        
        // Clear the mutable string.
        [self.foundValue setString:@""];
    }
    
    -(void)parserDidEndDocument:(NSXMLParser *)parser {
        NSLog(@"in parserDidEndDocument()");
        printf("\n");
        
        NSString *celsiusToFahrenheitResult = [self.dataStorageDict objectForKey:@"CelsiusToFahrenheitResult"];
        NSLog(@"celsiusToFahrenheitResult=%@", celsiusToFahrenheitResult);
        printf("\n");
        
        // after data organised, do work
    }
    

  6. 執行程式 (command + R),輸出的 console log 可以明顯看出程式解析 xml 時執行的順序

  7. 如果遇到這個錯誤訊息「App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure」,請參照 解決Xcode iOS App無法外連外部網路

  8. Done


Sources:
(1) Understanding XML and JSON Parsing in iOS Programming
(2) TempConvert

沒有留言:

張貼留言