Here is the parser:didStartElement: namespaceURI:qualifiedName:attributes: method implementation: // Called when the parser encounters a start element - void parser:NSXMLParser *parser d
Trang 1// 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
Trang 2element 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
Trang 3The 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 > ”
Trang 4“ < 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://w w w.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 >
Trang 5Granted, 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 >
rootNode
attribute1
attribute2
AttributedChild
FIGURE 10 - 7: Tree representation of generated XML
FIGURE 10 - 8: MakeXML sample UI
Trang 6UITextView *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;
Trang 7Every 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
Trang 8Next, 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:
Trang 9// 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];
Trang 10Finally, 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”);