How does NSXMLParser differentiate between different elements? - objective-c

I just did a tutorial on NSXMLParser. What I am completely at a loss at is how NSXMLParser differentiates between different elements. To me it seems undefined.
This is my XML
<?xml version="1.0" encoding="UTF-8"?>
<Prices>
<Price id="1">
<name>Rosie O'Gradas</name>
<Beer>4.50</Beer>
<Cider>4.50</Cider>
<Guinness>4</Guinness>
</Price>
</Prices>
And this is my Parser
-(void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"Prices"]) {
app.listArray = [[NSMutableArray alloc] init];
NSLog(#"The Prices Count");
}
else if ([elementName isEqualToString:#"Price"]) {
thelist = [[List alloc] init];
thelist.drinkID = [[attributeDict objectForKey:#"id"]integerValue];
}
}
-(void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentElementValue) {
currentElementValue = [[NSMutableString alloc]initWithString:string];
} else {
[currentElementValue appendString:string];
}
}
-(void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"Prices"]) {
return;
}
if ([elementName isEqualToString:#"Price"]) {
[app.listArray addObject:thelist];
thelist = nil;
} else {
[thelist setValue:currentElementValue forKey:elementName];
currentElementValue = nil;
}
}
I did notice that the names of the Properties in the data object were the same as in the parser. So I understood that at least.
What I am at a loss at is where it assigns these properties their value.
So at the beginning it initializes the data object with
thelist = [[List alloc] init];
(List is the Data object) But then it does the first thing that I don't understand
thelist.drinkID = [[attributeDict objectForKey:#"id"]integerValue];
Because it is in an if statement won't it get overwritten every time it finds an id attribute. Or is the 'theList' declaration creating multiple objects?
In the found characters I really have no idea what is going on. As much as I can tell foundCharaters string is every bit of text inside the elements. So current element value is really just a bundle of strings appended together (but I can't tell as for some reason I can't NSLOG it).
From there in the didEndElement section, I wonder if this is the correct interpretation of the code.
if ([elementName isEqualToString:#"Price"]) {
[app.listArray addObject:thelist];
thelist = nil;
}
I understand that every time that the parser hits the element Price that the app.list array object (declared in another class) has the object added to it 'thelist'.
But here is bit where my lack of understanding in the earlier method takes effect
else {
[thelist setValue:currentElementValue forKey:elementName];
currentElementValue = nil;
}
What are they doing here? From what I see the current element value is just a jumble of characters from the XML file. How is it organized? With the Element Name?
One more question (sorry for the length) why isn't the element name case sensitive, I was experimenting and I found it wasn't. Both languages are case sensitive.

If I interpret your question correctly, it is just about understanding the code which is working fine.
In your XML you have 4 child elements to Price with id=1: name, Beer, Cider and Guinness.
The foundCharacters method will find the characters inside these 4 xml tags, i.e. what is written between <name> and </name>, <Beer> and </Beer>, etc. In your case this is the string Rosie O'Gradas for name, then the string 4.50 for Beer etc.
When characters are found, the method first checks if a container string exists, if not it creates one as currentElementValue. If it does exist, it appends the found characters.
What happens next, logically? It will hit the didEndElement method, in the first case the tag </name>. In this case it will assign the collected text in currentElementValue to the key #"name" and put this key-value pair into the list. The list is of type List, which is defined somewhere else, but it seems to be essentially an NSDictionary.
Because currentElementValue has been stored successfully, it should be destroyed, so the check for its existence next time it hits foundCharacters will work.
Clear?

Related

How to choose between two elements of the same name when parsing xml

I'm working on parsing xml in school, and I'm using Twitter's API to work with. I'm trying to grab the date the tweet was posted, but I'm running into an issue: there are two elements with the same name that hold different values inside of the xml. One is nested further in than the other, however, so logically I would want to check for the one that is nested. How would I go about doing that?
Everything is running perfectly right now. I just want to add more to my project.
Here's an example of the didEndElement method I'm calling.
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI: (NSString *)namespaceURI qualifiedName:(NSString *)qName
{
//Creates an instance of the singleton to grab the array stored there
DataArraySingleton *singleton = [DataArraySingleton sharedArray];
//easy access to array
NSMutableArray *array = singleton.theArray;
//If the element ends with text
if ([elementName isEqualToString:#"text"])
{
//Sets the value/key pair for text
[tweets setValue:currentXMLValue forKey:elementName];
}
//Same as above
if ([elementName isEqualToString:#"screen_name"])
{
[tweets setValue:currentXMLValue forKey:elementName];
}
//Same as above
if ([elementName isEqualToString:#"profile_image_url"])
{
[tweets setValue:currentXMLValue forKey:elementName];
}
//If the element ends with status
if ([elementName isEqualToString:#"status"])
{
//Adds the objects collected above to the array
[array addObject:tweets];
//Resets the object TweetInformation to be called again above
tweets = nil;
}
//Resets xml value
currentXMLValue = nil;
}
Thanks guys
Add code to check for the parent tag of the nested date element that you want. When you encounter the start tag, set a flag in your code. Now when you encounter the date tag, check if the flag is set or not. Ignore the date tag if the flag isn't set. When you detect the end tag for the parent, reset the flag.

Parsing XML CDATA Blocks

I'm attempting to parse an XML file (using NSXMLParser) from the website librarything.com. This is the first file I have ever parsed, but for the most part it seems fairly straight forward. My problem occurs when trying to parse a CDATA block; the method parser:foundCDATA: isn't called, and I can't understand why. I know my parser is set up properly because the parser:foundCharacters: method works fine. The XML data I am trying to parse looks like this http://www.librarything.com/services/rest/1.1/?method=librarything.ck.getwork&isbn=030788743X&apikey=d231aa37c9b4f5d304a60a3d0ad1dad4 and the CDATA block occurs inside the element with the attribute name "description".
Any help as to why the method is not being called would be greatly appreciated!
EDIT: I ran the parser:foundCharacters: method on the description CDATA block and it returned "<". I'm assuming this means that the parser is not seeing the CDATA tag correctly. Is there anything that can be done on my end to fix this?
It appears the CDATA contents in the <fact> tags is being returned incrementally over multiple call backs in parser:foundCharacters. In you class where you are conforming to NSXMLParserDelegate try building up the CDATA by appending it to an NSMutableString instance, like so:
(Note: here _currentElement is an NSString property and _factString is an NSMutableString property)
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
self.currentElement = elementName;
if ([_currentElement isEqualToString:#"fact"]) {
// Make a new mutable string to store the fact string
self.factString = [NSMutableString string];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"fact"]) {
// If fact string starts with CDATA tags then just get the CDATA without the tags
NSString *prefix = #"<![CDATA[";
if ([_factString hasPrefix:prefix]) {
NSString *cdataString = [_factString substringWithRange:NSMakeRange((prefix.length+1), _factString.length - 3 -(prefix.length+1))];
// Do stuff with CDATA here...
NSLog(#"%#", cdataString);
// No longer need the fact string so make a new one ready for next XML CDATA
self.factString = [NSMutableString string];
}
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if ([_currentElement isEqualToString:#"fact"]) {
// If we are at a fact element, append the string
// CDATA is returned to this method in more than one go, so build the string up over time
[_factString appendString:string];
}
}

Cut out a part of a long NSString

In my app I want to show a String that contains news. This string is being loaded just from a free Website, so the plain source code of the website does not contain only my string, its is more os less like this:
Stuff
More Stuff
More HTML Stuff
My String
More HTML Stuff
Final Stuff
And of course i want to cut off all the html stuff that i don't want in my NSString. Since i am going to change the String fron time to time the overall length of the Source code from the website changes. This means that substringFromIndex wont work. Is there any other way to Convert the complete source code to just the String that i need?
There are zillions of ways to manipulate text. I would start with regular expressions. If you give more details about the specifics of your problem, you can get more specific help.
Edit
Thanks for the link to the website. That gives me more to work with. If you will always know the id of the div whose contents you want, you can use NSXMLParser to extract the text of the div. This will set the text of an NSTextField to the contents of the div with id "I3_sys_txt". I did this on the Mac but I believe it will work on the iPhone as well.
-(IBAction)buttonPressed:(id)sender {
captureCharacters = NO;
NSURL *theURL = [NSURL URLWithString:#"http://maxnerios.yolasite.com/"];
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:theURL];
[parser setDelegate:self];
[parser parse];
[parser release];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqual:#"div"] && [[attributeDict objectForKey:#"id"] isEqual:#"I3_sys_txt"]) {
captureCharacters = YES;
divCharacters = [[NSMutableString alloc] initWithCapacity:500];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (captureCharacters) {
//from parser:foundCharacters: docs:
//The parser object may send the delegate several parser:foundCharacters: messages to report the characters of an element.
//Because string may be only part of the total character content for the current element, you should append it to the current
//accumulation of characters until the element changes.
[divCharacters appendString:string];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if (captureCharacters) {
captureCharacters = NO;
[textField setStringValue:divCharacters];
[divCharacters release];
}
}
Here is the NSRegularExpression class that you need to use: NSRegularExpression
Here is a 'beginning' tutorial on how to use the class: Tutorial
Here is a primer on what regular expressions are: Regex Primer
Here is an online regular expression tester: Tester
The tester may not work exactly as NSRegularExpression but it will help you understand regex definitions in general. Regular expressions are a key tool for software developers, a little daunting at first, but they can be used to great effect when searching or manipulating strings.
Although this looks like a lot of work - there is no 'quick answer' to what you are attempting. You say "is there any other way to Convert the complete source code to just the String I need?' - the answer is yes - regular expressions. But you need to define what 'just the String that I need' means, and regular expressions are one important way.

How should I do this? (NSXMLParser)

I am pretty new to NSXMLParser and I need some advice. Here's my situation:
I am sending SOAP request to a server to get a list of "Orders" which return a list of orders in this format:
<Order>
<OrderID> string </OrderID>
<OrderName> string </OrderName>
</Order>
So, I parse the xml with bunch of these Orders, and I populate my mutable nsarray with mutable dictionaries, so in the end it looks like this:
(
{
OrderID = 2011417335319;
OrderNumber = 100;
},
{
OrderID = 2011340029503;
OrderNumber = TestOrder3;
},
{
OrderID = 20113223404613;
OrderNumber = 1234;
},
{
OrderID = 20113692554635;
OrderNumber = EricOrder;
},
{
OrderID = 2;
OrderNumber = TestOrder2;
},
{
OrderID = 201144231410461;
OrderNumber = TestOrder4;
}
)
Now, for each of these Orders (base on the OrderID), I need to send another request, which will return a list of Units. Units have property "Unit Number and Name". Now this is where I am confused. After parsing Order, how do I, for each of the Orders I got:
Send a SOAP request
When received response, create an NSXMLParser
and then parse it.
How can I do that dynamically? When parsing order, everything was simple as I just had to do this:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:#"GetOrdersResult"]) {
if (array) {
[array release];
}
array = [[NSMutableArray alloc] init];
} else if ([elementName isEqualToString:#"Order"]) {
if (dict) {
[dict release];
}
dict = [[NSMutableDictionary alloc] initWithCapacity:2];
} else if ([elementName isEqualToString:#"OrderID"]) {
sections = E_OrderID;
} else if ([elementName isEqualToString:#"OrderNumber"]) {
sections = E_OrderNumber;
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (sections == E_OrderID) {
[dict setObject:string forKey:#"OrderID"];
} else {
[dict setObject:string forKey:#"OrderNumber"];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"Order"]) {
[array addObject:dict];
}
}
But now, there's going to be multiple NSXMLParsers running at the same time, so I am a bit confused as how I should do this.
Also, quick question. I also have a request that return around 2,000 elements with alot of sub elements. What's the best way to store that in memory, search through it, and then populate an nstableview?
Thanks.
When running multiple nsxmlparsers on various levels of data hierarchy, I would keep another variable in memory which describes the current object being parsed. When you finish parsing the initial data, set the variable to indicate that you are up to the second level of parsing. Then, in your parsing methods, you can have your nsxmlparser behave differently, based on the value of the "progress" variable.
As far as I understand your case, what I would do is having each NSXMLParser instance have it's own kind of NSXMLParserDelegate that would do the proper processing of the parsed elements. just as an example, I would have:
XXOrderListParsingDelegate, with its own delegates method (didStartElement, didEndElement, foundCharacters, the ones you have)
XXOrderParsingDelegate, with the delegate methods suitable for this case;
When you create an NSXMLParser, assign to it the proper delegate for the given case.
those delegates should have all access to your model (dict, sections, whatever), this could require some change in your design.
This should get it right.
as to your second question, One possibility is using sqlite. here you have a tutorial.
1 http://blog.objectgraph.com/index.php/2010/04/08/how-to-use-sqlite-with-uitableview-in-iphone-sdk/
Try this out
for (NSManagedObject* managedObject in "YourArray") {
//You can get the OrderID's individually like this
[managedObject valueForKey:#"OrderID"];
//With the orderID you can send requests for each
}
For you Quick question... You can go for Coredata Concepts. Easy and interesting. There are many good tutorials available.

How to set up my XMLParser to store a non-specific number of objects?

I am trying to parse an XML with various similar sets of tags. The whole point on me reading this file is so that my app can receive a list of documents that have been updated.
The situation I'm faced with right now is trying to figure out how to store an unknown number of similar objects into an array for me to access somewhere else in the program.
My XMLParser class has the parser method as follows (so far):
-(void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
if (currentTextString) {
[currentTextString release];
currentTextString = nil;
}
if ([elementName isEqualToString:#"reference_number"]) {
pdfDocument = [[PDFDocument alloc] init];
pdfDocument.educationDocumentReference = currentTextString;
} else if ([elementName isEqualToString:#"name"]) {
pdfDocument.educationDocumentName = currentTextString;
} else if ([elementName isEqualToString:#"type"]) {
pdfDocument.educationDocumentType = currentTextString;
} else if ([elementName isEqualToString:#"date"]) {
pdfDocument.educationDocumentDate = [dateformatter dateFromString:currentTextString];
} else if ([elementName isEqualToString:#"url"]) {
pdfDocument.educationDocumentURL = currentTextString;
[resultsMutableArray insertObject:pdfDocument atIndex:resultsArrayCount];
resultsArrayCount++;
}
}
I am trying to create a pdfDocument object when the parser finds the first tag and store the same object when the last tag of a document is read into a mutable array. This might work for the first object, but when the next set of data is read, it would just not work at all.
What is the proper way to make something like this happen? Can anyone point me in the right direction here? I guess the question is can I dynamically name the document objects. The only option that I have found is to create a temporary table and store the information there, and after I have all the information I compare it to my permanent storage table.
Creating the temporary table was probably the best option. I set up the app to read the xml with all the data and store that in a temporary table. Once downloaded, I compare to my permanent table and whichever entry is marked as modified or new would be store to permanent and the temporary table would be cleared.