Phát triển ứng dụng cho iPhone và iPad - part 33 pps

10 268 0
Phát triển ứng dụng cho iPhone và iPad - part 33 pps

Đang tải... (xem toàn văn)

Thông tin tài liệu

// Called when the parser finishes parsing the document - (void)parserDidEndDocument:(NSXMLParser *)parser { NSLog (@”parserDidEndDocument”); } RSSSampleViewController.m The code is relatively straightforward. First, for illustrative purposes, you log the name of the method that you are executing. This will prove instructive when you look at the console log. Examining the order in which the parser calls the delegate methods will help you to better understand how the parser works. Additionally, it is useful for debugging purposes should you encounter an error in your parsing logic. In the parserDidStartDocument : method, you also initialize the inItemElement fl ag to NO because you are not currently in an item element. In the parserDidEndDocument method, you log that you have reached the end of the document. Next, you implement the start and end element functions. Here is the parser:didStartElement: namespaceURI:qualifiedName:attributes: method implementation: // Called when the parser encounters a start element - (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict { NSLog (@”didStartElement”); // Check to see which element we have found if ([elementName isEqualToString:@”item”]) { // We are in an item element inItemElement = YES; } // If we are in an item and found a title if (inItemElement & & [elementName isEqualToString:@”title”]) { // Initialize the capturedCharacters instance variable capturedCharacters = [[NSMutableString alloc] initWithCapacity:100]; } } RSSSampleViewController.m This method receives the name of the element that has started as a parameter. You check the name of the element and set the inItemElement fl ag if you have started an item element. Next, check to see if you have started a title element. You may be wondering why you are checking to see if you have started a title element when you already know that you have started an item XML and the iPhone SDK ❘ 289 CH010.indd 289CH010.indd 289 9/18/10 10:01:27 AM9/18/10 10:01:27 AM 290 ❘ CHAPTER 10 WORKING WITH XML ON THE IPHONE element. Remember that the parser calls this method each time an element begins. Because XML is typically organized as a tree data structure, you will almost certainly have elements nested within other elements. Therefore, each time that the parser calls this method, you could be starting a new element without having ended another element. Notice that when you check for starting a title element, you also verify that you are in an item element. If you encounter a title element and you are not in an item , you don ’ t need to do anything. If, however, you hit a title element and you are already in an item element, you initialize the capturedCharacters instance variable. You will use this NSMutableString to aggregate the characters from the title element in the call to parser:foundCharacters: . You will do some similar processing in the didEndElement method: // Called when the parser encounters an end element - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { NSLog (@”didEndElement”); // Check to see which element we have ended // If we are in an item and ended a title if (inItemElement & & [elementName isEqualToString:@”title”]) { NSLog (@”capturedCharacters: %@” , capturedCharacters); self.textView.text = [self.textView.text stringByAppendingFormat:@”%@\n\n”,capturedCharacters]; // Release the capturedCharacters instance variable [capturedCharacters release]; capturedCharacters = nil; } if ([elementName isEqualToString:@”item”]) { // We are no longer in an item element inItemElement = NO; } } RSSSampleViewController.m First, check to see if you are in an item element and are ending a title element. If so, log the characters that you captured, append them to the textView , and release the capturedCharacters instance variable because you are fi nished with it for now. Next, check to see if the parser called this element because you are ending an item element. If that is the case, you set the inItemElement fl ag to NO . Remember that the parser will call this method each time an element ends. It is up to the developer to determine what element has ended and to act accordingly. CH010.indd 290CH010.indd 290 9/18/10 10:01:27 AM9/18/10 10:01:27 AM The last delegate method that you will implement is parser : foundCharacters :. In this method, you simply check to make sure that the capturedCharacters variable is not nil , and then append the characters passed into the method to the capturedCharacters mutable string. Keep in mind that the parser may call this method multiple times for the text inside of a single element. That is why you are using a mutable string and appending the characters to it each time that the parser calls this method. Here is the implementation: // Called when the parser finds characters contained within an element - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { if (capturedCharacters != nil) { [capturedCharacters appendString:string]; } } RSSSampleViewController.m You are now ready to build and run the application. As long as you have an active Internet connection, the application should download and parse the RSS feed and display the top headlines from CNN.com in the text view. Generating XML with libxml Sometimes when you are building a connected application, you will need to generate XML from scratch. Thus far, you have only explored consuming XML. In this section, you learn how to build XML dynamically at runtime. There are a several ways to build XML in your application. You should choose an appropriate method based on the needs of your application. If you need to send a specifi c XML message with only limited dynamic data, your best bet is to build a format string using the XML and placeholders for your variables. For instance, if you were implementing an online address book, a message that you send to the server to add an entry may look something like this: < address > < name > John Smith < /name > < street > 1 Ocean Blvd < /street > < city > Isle Of Palms < /city > < state > SC < /state > < zip > 29451 < /zip > < /address > In this case, there is no need to build the XML document structure at runtime because you already know the structure of the message at compile time. Simply use a format string like this: NSString *s = [[NSString alloc] initWithFormat: “ < address > ” “ < name > %@ < /name > ” “ < street > %@ < /street > ” XML and the iPhone SDK ❘ 291 CH010.indd 291CH010.indd 291 9/18/10 10:01:28 AM9/18/10 10:01:28 AM 292 ❘ CHAPTER 10 WORKING WITH XML ON THE IPHONE “ < city > %@ < /city > ” “ < state > %@ < /state > ” “ < zip > %@ < /zip > ” “ < /address > ”,name,street,city,state,zip]; On the other hand, if you are building an order entry system where the number of items in an order could change based on conditions in the application at runtime, you probably will need to generate the entire XML tree dynamically. While generating XML with the NSXMLParser is possible as outlined in Apple ’ s “ Event - Driven XML Programming Guide for Cocoa, ” it is diffi cult and not intuitive. It is far easier to build an XML document using the DOM model for XML parsing. Apple does not provide a DOM parser on the iPhone, but there is a C XML library called libxml installed on the iPhone that you can use for DOM processing. It is free and available under the MIT license, which you can fi nd at http://www.opensource.org/licenses/mit-license.html . While generating XML dynamically at runtime, using libxml2 is far easier than using NSXMLParser . Unfortunately, the library is in C, so it is a little more diffi cult to work with than an Objective - C library. If you think that you will be building dynamic XML often, you may want to consider wrapping libxml in your own Objective - C wrappers. Additionally, there are several open source wrappers for libxml; however, you will not be looking at them. You will better understand how to use libxml by using the library directly in your code. Using libxml in your project is as simple as adding the libxml2.dylib to your project and editing your header search paths so that the compiler can fi nd the headers. Then you only need to include the headers that you plan to use in your source code and you are ready to go. You may also want to use libxml if your application needs the capabilities of a DOM parser as opposed to the Apple provided SAX parser. The libxml library also supports XPath. XPath is a query language that you can use to search your XML. Additionally, libxml also supports validating XML fi les against a DTD (Document Type Defi nition). You can think of a DTD as the specifi cation for the XML used in a document. XML Generation Sample In this section, you will build a very simple application that generates a simple XML document tree, as shown in Figure 10 - 7. The XML that you generate will look like this: < ?xml version=”1.0”? > < rootNode > < ChildNode1 > Child Node Content < /ChildNode1 > < AttributedNode attribute1=”First” attribute2=”Second” > < AttributedChild > Attributed Node Child Node < /AttributedChild > < /AttributedNode > < AttachedNode > Attached Node Text < /AttachedNode > < ! This is an XML Comment > < /rootNode > CH010.indd 292CH010.indd 292 9/18/10 10:01:28 AM9/18/10 10:01:28 AM Granted, this is not the most complex XML document, but the code will demonstrate everything that you will need to generate XML documents of limitless complexity. Create a new View - based Application called MakeXML. The fi rst thing that you have to do is tell Xcode that you will be using the libxml library. Right - click on the project in the left pane of Xcode and select Add New Group. Call the new group C Libs . Next, add a reference to the libxml2.dylib dynamic library. Right - click on the new group and select Add ➪ Existing Frameworks. In the dialog, select Dylibs from the drop - down and select libxml2.dylib . You should see libxml2.dylib appear in the C Libs group in Xcode. You now have to alter the search path in Xcode so that Xcode can fi nd the headers for libxml2 when you are ready to compile. Open the project build settings by selecting Project ➪ Edit Project Settings from the menu bar. In the Search Paths area, add the path /Developer/Platforms/ iPhoneOS.platform/Developer/SDKs/iPhoneOS3.1.3.sdk/usr/ include/libxml2 to the Header Search Paths item. Enable Xcode to search this directory and below by checking the recursive box. Note that you will have to change the SDK in the path based on which SDK you are compiling with. This example assumes that you are using the iPhone SDK version 3.1.3 and that you have installed the SDK in the default location. Now you will build the UI for the application, as shown in Figure 10 - 8. In the MakeXMLViewController.h header, add a UITextView instance variable and outlet. Add a method generateXML that you will call to generate the XML. Here is the complete MakeXMLViewController.h header: #import < UIKit/UIKit.h > @interface MakeXMLViewController : UIViewController { rootNode ChildNode1 AttachedNodeAttributedNode attribute1 attribute2 AttributedChild FIGURE 10 - 7: Tree representation of generated XML FIGURE 10 - 8: MakeXML sample UI XML and the iPhone SDK ❘ 293 CH010.indd 293CH010.indd 293 9/18/10 10:01:28 AM9/18/10 10:01:28 AM 294 ❘ CHAPTER 10 WORKING WITH XML ON THE IPHONE UITextView *textView; } @property (nonatomic, retain) IBOutlet UITextView *textView; -(void) generateXML; @end MakeXMLViewController.h In the MakeXMLViewController.m implementation fi le, synthesize the textView property: @synthesize textView; Next, implement viewDidUnload and dealloc to clean up the textView property and instance variable: - (void)viewDidUnload { // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; self.textView=nil; } - (void)dealloc { [textView dealloc]; [super dealloc]; } MakeXMLViewController.m Open the MakeXMLViewController.xib fi le in Interface Builder and add a UITextView control. Resize the control to take up the whole screen. Connect the UITextView in Interface Builder to the textView property in File ’ s Owner. In the MakeXMLViewController.m implementation fi le, add the import statements for the libxml parser and libxml tree headers: #import < libxml/parser.h > #import < libxml/tree.h > You will construct the XML in the generateXML method. I want to demonstrate a few different things in this method, so it ’ s long. Let ’ s walk through the method section by section, and then I will show the whole method in its entirety. You fi rst declare an xmlDocPtr object. xmlDocPtr is a typedef for a pointer to the libxml xmlDoc structure. This structure represents the entire XML document that you will be creating. - (void) generateXML { xmlDocPtr doc; CH010.indd 294CH010.indd 294 9/18/10 10:01:30 AM9/18/10 10:01:30 AM Every XML document consists of a set of nodes. At the top of the tree of nodes is a special node called the root node. You need to declare the root node as an xmlNodePtr object. xmlNodePtr is a typedef to a pointer to an xmlNode struct. The library uses the xmlNode struct to represent all XML nodes. Here is the declaration of the root node: xmlNodePtr rootNode; Next, you call the xmlNewDoc library function to create a new document model. You assign the return to the doc object: // Create a new xml document doc = xmlNewDoc(BAD_CAST “1.0”); You may have noticed the frightening looking BAD_CAST parameter in the function call. BAD_CAST is simply a macro defi ned in the xmlstring.h header that you can use to cast a string to the xmlChar* type. You will likely use BAD_CAST anywhere that you use a string object with the library. Remember that libxml is a C library so you should omit the @ symbol that you use when defi ning NSString objects in Objective - C. In this case, you are using a C - style char* string. The next step is to create the root node. You call the xmlNewNode function to create a new node. The method takes an xmlNsPtr and an xmlChar* as its parameters. You can use the xmlNsPtr parameter to specify a namespace for your node instance. You use namespaces to ensure that the XML elements defi ned in your document do not confl ict with elements with the same name in other XML documents. You will not be using namespaces in this sample. The xmlChar* is the name of your new node. In this case, let ’ s call the node rootNode : // Create the root node rootNode = xmlNewNode(NULL, BAD_CAST “rootNode”); Because the root element is the starting point for the document, you must explicitly set the root element. Call the xmlDocSetRootElement function and pass in a pointer to the document and the root node: xmlDocSetRootElement(doc, rootNode); Now you are ready to start adding additional nodes to the tree. The easiest way to add new nodes to your XML document tree is to use the xmlNewChild function. This function accepts a pointer to the parent node for the new node, a namespace pointer if you are using namespaces, the name of the new node, and the textual content of the node. You create ChildNode1 as follows: // Create a new child off the root xmlNewChild(rootNode, NULL, BAD_CAST “ChildNode1”, BAD_CAST “Child Node Content”); This statement creates a node called ChildNode1 that is a child of the rootNode and contains the string Child Node Content . Again, you can see that you use the BAD_CAST macro to pass string data into the library. XML and the iPhone SDK ❘ 295 CH010.indd 295CH010.indd 295 9/18/10 10:01:30 AM9/18/10 10:01:30 AM 296 ❘ CHAPTER 10 WORKING WITH XML ON THE IPHONE Next, you create a node that contains XML attributes. Attributes are another way to add data to an element node. Because you will append a child to this node, you declare an xmlNodePtr so that you can hold a reference to the new node. Then, you use the xmlNewChild function that you have already seen to create a new node. You will call the xmlNewProp function to add two attributes to the attributedNode , as follows: // Add a node with attributes xmlNodePtr attributedNode; attributedNode = xmlNewChild (rootNode, NULL, BAD_CAST “AttributedNode”, NULL); xmlNewProp(attributedNode, BAD_CAST “attribute1”, BAD_CAST “First”); xmlNewProp(attributedNode, BAD_CAST “attribute2”, BAD_CAST “Second”); MakeXMLViewController.m So far, you have only added nodes as children of the root node. Part of the power of XML is the ability to express hierarchical data in a tree structure. To demonstrate this capability, let ’ s add the next node as a child of the attributed node. You can accomplish this by passing the pointer to the attributed node into the call to xmlNewChild : // Create a node as a child of the attributed node xmlNewChild (attributedNode, NULL, BAD_CAST “AttributedChild”, BAD_CAST “Attributed Node Child Node”); MakeXMLViewController.m Instead of appending the new AttributedChild node to the rootNode , this code appends the AttributedChild node to the attributedNode . Sometimes it may be more convenient to build the node and its associated text separately. You can do this and then add the node to the tree after you have populated it with text or other subnodes. A new node and its text content are created in this snippet. Then, you add the text to the node and append the node to the root node, as follows: // You can also build nodes and text separately then and add them // to the tree later xmlNodePtr attachNode = xmlNewNode(NULL, BAD_CAST “AttachedNode”); xmlNodePtr nodeText = xmlNewText(BAD_CAST “Attached Node Text”); // Add the text to the node xmlAddChild(attachNode, nodeText); // Add the node to the root xmlAddChild(rootNode, attachNode); MakeXMLViewController.m It is even possible to include XML comments using the xmlNewComment function: CH010.indd 296CH010.indd 296 9/18/10 10:01:31 AM9/18/10 10:01:31 AM // You can even include comments xmlNodePtr comment; comment = xmlNewComment(BAD_CAST “This is an XML Comment”); xmlAddChild(rootNode, comment); MakeXMLViewController.m Now that you have built the complete tree for the document, you have to tell the library to output the contents so that you can put the document into an NSString . Because libxml is a C library, there is a function that outputs the document to a memory buffer. You need to declare a memory buffer of type xmlChar* and an int variable to hold the length of the buffer. Once you have these variables set up, you can call the xmlDocDumpFormatMemory function. This function outputs the XML document to the buffer that you pass in and returns the size of the buffer in a reference variable that you also pass in to the function. The last parameter to the xmlDocDumpFormatMemory function indicates that you would like space characters added to format the output. Here is the code: // Write the doc xmlChar *outputBuffer; int bufferSize; // You are responsible for freeing the buffer using xmlFree // Dump the document to a buffer xmlDocDumpFormatMemory(doc, & outputBuffer, & bufferSize, 1); MakeXMLViewController.m Fortunately, because Objective - C is a superset of C, Apple has created plenty of helper functions for getting data from C types into Objective - C types. For instance, the NSString class has a method called initWithBytes:length:encoding: that accepts a C char* buffer, a length, and an encoding type, and initializes an NSString with that data. You use that function to create an NSString from your XML document dump: // Create an NSString from the buffer NSString *xmlString = [[NSString alloc] initWithBytes:outputBuffer length:bufferSize encoding:NSUTF8StringEncoding]; Next, you log the XML document string to the console, put the string in the textView in the user interface, and release the NSString : // Log the XML string that we created NSLog (@”output: \n%@”, xmlString); // Display the text in the textview [self.textView setText:xmlString]; // Free the xml string [xmlString release]; MakeXMLViewController.m XML and the iPhone SDK ❘ 297 CH010.indd 297CH010.indd 297 9/18/10 10:01:31 AM9/18/10 10:01:31 AM 298 ❘ CHAPTER 10 WORKING WITH XML ON THE IPHONE Finally, you call some cleanup functions from libxml to free the memory that you allocated when creating your XML document. First, you free the output buffer that you just created with a call to xmlFree . Then, you free the memory used by the XML document with a call to xmlFreeDoc . You end with a call to xmlCleanupParser because you are completely fi nished with the XML parser: // Clean up // Free the output buffer xmlFree(outputBuffer); // Release all of the structures in the document including the tree xmlFreeDoc(doc); xmlCleanupParser(); MakeXMLViewController.m You should be careful to ensure that you are not using the XML parser anywhere else in your code before calling xmlCleanupParser . This method cleans up any remaining memory that the XML parser allocated, including global memory. You should only call this function when your application is completely fi nished using libxml and all documents that you have created with the library. Listing 10 - 1 shows the entire generateXML method call. LISTING 10 - 1: The generateXML method - (void) generateXML { xmlDocPtr doc; xmlNodePtr rootNode; // Create a new xml document doc = xmlNewDoc(BAD_CAST “1.0”); // Create the root node rootNode = xmlNewNode(NULL, BAD_CAST “rootNode”); // Set the root of the document xmlDocSetRootElement(doc, rootNode); // Create a new child off the root xmlNewChild(rootNode, NULL, BAD_CAST “ChildNode1”, BAD_CAST “Child Node Content”); // Add a node with attributes xmlNodePtr attributedNode; attributedNode = xmlNewChild (rootNode, NULL, BAD_CAST “AttributedNode”, NULL); xmlNewProp(attributedNode, BAD_CAST “attribute1”, BAD_CAST “First”); xmlNewProp(attributedNode, BAD_CAST “attribute2”, BAD_CAST “Second”); CH010.indd 298CH010.indd 298 9/18/10 10:01:32 AM9/18/10 10:01:32 AM . AttachedNodeAttributedNode attribute1 attribute2 AttributedChild FIGURE 10 - 7: Tree representation of generated XML FIGURE 10 - 8: MakeXML sample UI XML and the iPhone SDK ❘ 293 CH010.indd 293CH010.indd 293 9/18/10. that you have created with the library. Listing 10 - 1 shows the entire generateXML method call. LISTING 10 - 1: The generateXML method - (void) generateXML { xmlDocPtr doc; xmlNodePtr. for XML parsing. Apple does not provide a DOM parser on the iPhone, but there is a C XML library called libxml installed on the iPhone that you can use for DOM processing. It is free and available

Ngày đăng: 04/07/2014, 21:20

Tài liệu cùng người dùng

Tài liệu liên quan