Getting the value of an Element in Cocoa using TouchXML - cocoa-touch

I am using TouchXml because of the given limitations of NSXML on the actual iPhone. Anyway, I'm just starting out with Objective-C, I come from a C# background, and felt like learning something new..anyhow.. here is my xml file...
<?xml version="1.0" encoding="utf-8"?>
<FundInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://tempuri.org/webservices">
<FundsReleaseDate>2009-02-11T00:00:00</FundsReleaseDate>
<FundValue>7800</FundValue>
<FundShares>1000</FundShares>
</FundInfo>
I'm trying to get 7800, i.e FundValue. Can someone point me in the correct direction, I am using the TouchXml CXMLDocument and myParser is of type CXMLDocument, I have tried
NSArray *nodes = [myParser nodesForXPath:#"//FundInfo" error:&err];
Basically nodes evaluates to nil
if ([nodes count] > 0 ) {
amount = [nodes objectAtIndex:1];
}
UPDATE 1: I have abandoned parsing using XPath, and have replaced it with NSURLRequest, so now I have the entire XML in a string, but my rude introduction to objective-C continues...I just realized how spoiled I have become to the .NET BCL where things like Regex are so easily available.
UPDATE2: I've figured out how to use RegexKitLite for regex.
So the question now is, how do I get the "7800" from inside FundValue which is now one big string. I have confirmed I have it in my NSString by writing it using NSLog.

The problem is that your XML has a namespace, which means your XPath does not actually match. Unfortunately there is no default mapping, and XPath does not actually define a good way to handle this internally, so you can't fix it simply by changing the XPath. Instead you need to inform the interpreter how you want to map it in your XPath.
It looks like TouchXML implements support for this via:
- (NSArray *)nodesForXPath:(NSString *)xpath namespaceMappings:(NSDictionary *)inNamespaceMappings error:(NSError **)error;
So you can try something like:
NSDictionary *mappings = [NSDictionary dictionaryWithObject:#"http://tempuri.org/webservices" forKey:#"tempuri"];
[myParser nodesForXPath:#"//tempuri:FundInfo" namespaceMappings:mappings error:&err];

I had a similar problem. ANY selection on an XML that has a namespace specified (xmlns="http://somenamespace") will result in no nodes found. Very strange considering it does support the additional namespace formats such as xmlns:somenamespace="http://somenamespace".
In any case, by far the easiest thing to do is do a string replace and replace xmlns="http://tempuri.org/webservices with an empty string.
Overall I like touchxml, but I can't believe this bug still exists.

Try
NSArray *nodes = [myParser nodesForXPath:#"/FundInfo/*" error:&err];

Related

Why should I not be using [NSDictionary objectForKey:]?

I recently started using Faux Pas (http://fauxpasapp.com/), and for my project I get the following warning a number of times:
Old, verbose Objective-C syntax
-[NSDictionary objectForKey:] is called. Consider using an Objective-C subscript expression instead.
I'm unsure what the subscript expression is, and I'm not having much luck finding anything on it. I was curious if anyone here could help.
Thanks!
There is no performance difference, just that the literal syntax is more clear, less verbose and has been available for several years now.
If the current code is like:
id var = [dictionary objectForKey:#"key"];
replace it with:
id var = dictionary[#"key"];
Well typically you would have written something like this:
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:42] forKey:#"foo"];
id value = [dictionary objectForKey:#"foo"];
And now you would write something like this:
NSDictionary *dictionary = #{#"foo": #42};
id value = dictionary[#"foo"];
Which, I think you will agree is a lot simpler and nicer to look at.
Some nice information about Object Subscripting can be found at NSHipster
You want to learn about Objective-C 2.0. The best way is to go straight to the source:
http://clang.llvm.org/docs/ObjectiveCLiterals.html
As you can see, a number of language features were introduced: NSNumber literals and "boxing" expressions, along with subscripting of NSArray and NSDictionary (and your own classes if you like). This is considered the "modern" way.
Xcode will refactor your code into "modern Objective-C" for you (see under Edit > Refactor), so you can modernize your code without doing any work!

Generating large NSString xml

I have a plist and i want to convert it to xml. The xml itself is going to be around 1.2mb in size. What the best way to generate this xml? Simply with a NSMutableString? I am just worried about the performance issues and wether there is a better way to generate xml.
Thanks
For those wondering, what I have right now is something like this:
NSString *xml = [NSString stringWithFormat:#"<Sheet>%#</Sheet>", [self getSheetXMLString]];
and then, in getSheetXMLString method, i have more methods like above which drill down deep until the plist is fully transversed.
Thanks again.
What do you plan to do with the XML, if it is to output over a network or write to a file then instead of creating a NSString you could just write straight out to the network/file. If you plan to do manipulation if the XML you may want to consider libxml2, which is a C library included in iOS.

NSXMLElement, without an NSXMLDocument

I have an NSXMLElement that is a copy from somewhere else in my code. After it's been used, it no longer is connected to an NSXMLDocument. It's (what I believe to be) not linked to anything.
If I NSLog the NSXMLElement, I am given the contents. I can see it.
But, if I try to use nodesForXPath, it returns nothing. It's blank. It cannot find anything (unless I just search for *, in which case it returns everything with /t/t/n/t/t).
Now, if I make that NSXMLElement the root of a NEW NSXMLDocument, and then search the new document, I can search the XPath perfectly!
I am trying to understand the logic there. I have been reading the documentation, but I haven't found anything that explains what is happening here (or at least, I have no understood it if I did find it in the documentation).
I would really appreciate someone helping me understand exactly why this is happening, and whether or not I need to use this NSXMLDocument.
Here is my code:
(element is an NSXMLElement coming from an NSArray of stored NSXMLElements. They are copies from a document used in another method)
NSArray* scene = [element nodesForXPath:#"scene[1]" error:nil];
NSLog(#"scene: %#", [[scene objectAtIndex:0] stringValue]);
But if I do this, I get a result:
NSXMLDocument* docElement = [[NSXMLDocument alloc] initWithRootElement:element];
NSArray* scene = [docElement nodesForXPath:#"scene[1]" error:nil];
NSLog(#"scene: %#", [[scene objectAtIndex:0] stringValue]);
It's probably because the xpath context used by libxml2 holds a reference to the document. If there is no document, it probably doesn't know how to operate. The behavior if you search for * may just be a special-case because it knows it has to return everything so it doesn't even try to search.

next line character a huge influence on xmlparser?

I have question about a basic xml file I'm parsing and just putting in simple nextlines(Enters).
I'll try to explain my problem with this next example.
I'm( still) building an xml tree and all it has to do ( this is a testtree ) is put the summary in an itemlist. I then export it to a plist so I can see if everything is done correctly.
A method that does this is in the parser which looks like this
if([elementName isEqualToString:#"Book"]) {
[appDelegate.books addObject:aBook];
[aBook release];
aBook = nil;
}
else
{
[aBook setValue:currentElementValue forKey:elementName];
NSString *directions = [NSString stringWithFormat:currentElementValue];
[directionTree = setObject:directions forKey:#"directions"];
}
[currentElementValue release];
currentElementValue = nil;
}
the export for the plistfile happens at the endtag of books.
Below is the first xmlfile
<?xml version="1.0" encoding="UTF-8"?>
<Books><Book id="1"><summary>Ero adn the ancient quest to measure the globe.</summary></Book><Book id="2"><summary>how the scientific revolution began.</summary></Book></Books>
This is my output
http://img139.imageshack.us/img139/9175/picture6rtn.png
If I make some adjustments like here
<?xml version="1.0" encoding="UTF-8"?>
<Books><Book id="1">
<summary>Ero adn the ancient quest to measure the globe.</summary>
</Book>
<Book id="2">
<summary>how the scientific revolution began.</summary>
</Book>
</Books>
My directions key with type string remains empty...
http://img248.imageshack.us/img248/5838/picture7y.png
I never knew that if I just put in an enter it would have such an influence.
Does anyone know a solution to this since my real xml file looks like this.
ps. the funny thing is I can actually see ( when debugging)my directions string (NSString directions ) fill up with the currentElementValue in both cases.
Instrument your code; specifically, just above the line that reads...
[directionTree setObject:directions forKey:#"directions"];
... (I removed a stray =) try adding ...
NSLog(#"setting directions to '%#'", directions);
I bet you'll see the above is logged multiple times per element. Specifically, the newline between the </summary> and the </book> tag is, in and of itself, an element just like the text in the <summary></summary> tag is an element.
Now, you could continue down the path of trying to special case for this that and the other, but that would be wrong.
You need to parse the XML as a structured document -- as a tree of nodes. Specifically, you should be looking for the <summary> tag somewhere and then grabbing the element that hangs below it (that should be of, IIRC, the TEXT type in XML parlance -- been a while).
Or, better yet, use one of the XML parsing APIs on the system. NSXMLDocument comes immediately to mind. If working on the iPhone (which this question didn't indicate), you'll need to use NSXMLParser and not NSXMLDocument as it is not available.
Or, even better, since this looks like pretty straightforward XML encapsulation of a regular data schema, use CoreData. CoreData is ideal for storing this kind of information. If your XML is intended to be an interchange format, you won't want to use CoreData as the XML it produces is entirely of its own design.

Regex to get value within tag

I have a sample set of XML returned back:
<rsp stat="ok">
<site>
<id>1234</id>
<name>testAddress</name>
<hostname>anotherName</hostname>
...
</site>
<site>
<id>56789</id>
<name>ba</name>
<hostname>alphatest</hostname>
...
</site>
</rsp>
I want to extract everything within <name></name> but not the tags themselves, and to have that only for the first instance (or based on some other test select which item).
Is this possible with regex?
<disclaimer>I don't use Objective-C</disclaimer>
You should be using an XML parser, not regexes. XML is not a regular language, hence not easely parseable by a regular expression. Don't do it.
Never use regular expressions or basic string parsing to process XML. Every language in common usage right now has perfectly good XML support. XML is a deceptively complex standard and it's unlikely your code will be correct in the sense that it will properly parse all well-formed XML input, and even it if does, you're wasting your time because (as just mentioned) every language in common usage has XML support. It is unprofessional to use regular expressions to parse XML.
You could use Expat, with has Objective C bindings.
Apple's options are:
The CF xml parser
The tree based Cocoa parser (10.4 only)
Without knowing your language or environment, here are some perl expressions. Hopefully it will give you the right idea for your application.
Your regular expression to capture the text content of a tag would look something like this:
m/>([^<]*)</
This will capture the content in each tag. You will have to loop on the match to extract all content. Note that this does not account for self-terminated tags. You would need a regex engine with negative lookbehinds to accomplish that. Without knowing your environment, it's hard to say if it would be supported.
You could also just strip all tags from your source using something like:
s/<[^>]*>//g
Also depending on your environment, if you can use an XML-parsing library, it will make your life much easier. After all, by taking the regex approach, you lose everything that XML really offers you (structured data, context awareness, etc).
The best tool for this kind of task is XPath.
NSURL *rspURL = [NSURL fileURLWithPath:[#"~/rsp.xml" stringByExpandingTildeInPath]];
NSXMLDocument *document = [[[NSXMLDocument alloc] initWithContentsOfURL:rspURL options:NSXMLNodeOptionsNone error:NULL] autorelease];
NSArray *nodes = [document nodesForXPath:#"/rsp/site[1]/name" error:NULL];
NSString *name = [nodes count] > 0 ? [[nodes objectAtIndex:0] stringValue] : nil;
If you want the name of the site which has id 56789, use this XPath: /rsp/site[id='56789']/name instead. I suggest you read W3Schools XPath tutorial for a quick overview of the XPath syntax.
As others say, you should really be using NSXMLParser for this sort of thing.
HOWEVER, if you only need to extract the stuff in the name tags, then RegexKitLite can do it quite easily:
NSString * xmlString = ...;
NSArray * captures = [xmlString arrayOfCaptureComponentsMatchedByRegex:#"<name>(.*?)</name>"];
for (NSArray * captureGroup in captures) {
NSLog(#"Name: %#", [captureGroup objectAtIndex:1];
}
Careful about namespaces:
<prefix:name xmlns:prefix="">testAddress</prefix:name>
is equivalent XML that will break regexp based code. For XML, use an XML parser. XPath is your friend for things like this. The XPath code below will return a sequence of strings with the info you want:
./rsp/site/name/text()
Cocoa has NSXML support for XPath.