iPhone SDK: XML mystery, after adding child nodeforXPath returns nothing (found a hacky solution) - iphone

I have a big mystery here,
I have a Gdataxml document property:
GDataXMLDocument *doc;
I'm adding a new element to doc, interestingly, this method below looks perfect for other elements but not for the element I just added:
GDataXMLElement *newValueDefElement = [GDataXMLNode elementWithName:#"valuedefinition"];
[variableElement addChild:newValueDefElement];
and now when I query:
NSString *path = [NSString stringWithFormat:#"//inferenceresponse/state/variable[pageId=%d]/valuedefinition",pageID];
NSArray *valueElement = [self.doc nodesForXPath:path error:nil];
Now array comes with zero objects! new added element NOT found! but I can see it in debug as xml string, how on earth it can not find something which I can see it is there on the log? it is a cache problem or a namespace problem or a bug in GDataXML? again..Problem is adding a new child and it is somehow not updated in the doc, but I can get the other elements under same root when use the same Xpath query standard
in NSlog I can see that the new element is added to doc.
NSData *xmlData2 = self.doc.XMLData;
NSString *s= [[[NSString alloc] initWithBytes:[xmlData2 bytes] length:[xmlData2 length] encoding:NSUTF8StringEncoding] autorelease];
NSLog(s);
Also How can self.doc.XMLData give something different than [self.doc nodesForXPath]? so it fools me to thing my doc is ok but maybe I corrupted the doc or a wrong namespace while adding removing some elements in a previous method?
my xml starts like this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<inferenceresponse xmlns="">
<state goalreached="false">
..
..
Update
I just found a (hacky) solution; when I convert "doc" to NSData with "doc.XMLData" and then again convert back to doc, then it works! but this should not be real solution, that's lame to do that conversion back and forth to get a correct document object. What is the problem here? I guess it can not fix the namespaces for new child.

Your problem is here:
<inferenceresponse xmlns="">
The empty namespace attribute is obviously confusing the libxml XPath evaluation. If you step through GDataXMLNode's nodesForXPath:namespaces:error:, xmlXPathEval indeed returns an empty nodes set.
If you have control over the XML generation, I've got correct XPath results removing the empty attribute.
<inferenceresponse>
If modifying the server response is too hard, you can edit GDataXMLNode.m:
Find the method fixQualifiedNamesForNode:graftingToTreeNode: in GDataXMLNode implementation and replace the line
if (foundNS != NULL) {
// we found a namespace, so fix the ns pointer and the local name
with
if (foundNS != NULL && foundNS->href != NULL && strlen((char *)foundNS->href) != 0) {
// we found a namespace, so fix the ns pointer and the local name

Related

Why does SBJson JSON parsing only get the last key of interest?

I am using the following JSON: http://www.kb.dk/tekst/mobil/aabningstider_en.json
When I try to parse it by the key "location" as such:
// get response in the form of a utf-8 encoded json string
NSString *jsonString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
// get most parent node from json string
NSDictionary *json = [jsonString JSONValue];
// get key-path from jason up to the point of json object
NSDictionary *locations = [json objectForKey:#"location"];
NSLog( #"%#", locations );
// iterate through all of the location objects in the json
for (NSDictionary *loc in locations )
{
// pull library name from the json object
NSString *name = [loc valueForKey:#"name"];
// add library data table arrays respectively
[ libraryNames addObject: ( ( name == nil | name.length > 0 ) ? name : #"UnNamed" ) ];
}
When I print the the object locations via NSLog:
{
address = "Universitetsparken 4, 3. etage, 2100 K\U00f8benhavn \U00d8";
desc = "";
lastUpdated = "";
latlng = "55.703124,12.559596";
link = "http://www.farma.ku.dk/index.php?id=3742";
name = "Faculty of Pharmaceutical Sciences Library";
parts = {
part = {
hour = {
day = "5.June Constitution Day (Denmark)";
open = Closed;
};
hours = {
hour = {
day = Friday;
open = "10-16";
};
};
name = main;
};
};
}
Which is only the last value for the "location" keys. Am I doing something wrong?
I tried validating the JSON via http://jsonlint.com/, however when I'd put in the JSON URL as above, it said "valid" - still only the last "locations" key was shown", however if I copy-paste it, it will not validate the JSON, and has to be fixed by removing new-lines from the string.
Also, when i try to parse the JSON and get the "name" fields, I get the following exception:
2012-05-08 15:37:04.941 iPhone App Tabbed[563:f803] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x68bfe70> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'
*** First throw call stack:
(0x13dc052 0x156dd0a 0x13dbf11 0x9d2f0e 0x941841 0x940ca9 0x4593 0xf964e 0x114b89 0x1149bd 0x112f8a 0x112e2f 0x1148f4 0x13ddec9 0x365c2 0x3655a 0x25b569 0x13ddec9 0x365c2 0x3655a 0xdbb76 0xdc03f 0xdbbab 0x25dd1f 0x13ddec9 0x365c2 0x3655a 0xdbb76 0xdc03f 0xdb2fe 0x5ba30 0x5bc56 0x42384 0x35aa9 0x12c6fa9 0x13b01c5 0x1315022 0x131390a 0x1312db4 0x1312ccb 0x12c5879 0x12c593e 0x33a9b 0x281d 0x2785)
terminate called throwing an exception(lldb)
It would make more sense if the "locations" tag was an array object enclosed by square brackets ([]), however right now it's only an sequence of normal key-value pairs... Sadly, that's the JSON I have to work with.
Please help and thanks a great deal! :)
Sincerely,
Piotr.
The JSON you've got to work with may be valid, but it doesn't make much sense. It has one big dictionary with the location key repeated many times. Most JSON parser will simply return the last value for the repeated key. It would be best if you could change the structure to use an array instead, but if you cannot there's still hope. You can read the stream and stuff the values from the location keys into an array as they come out of it. This is how you'd do that:
#interface BadJsonHelper : NSObject
#property(strong) NSMutableArray *accumulator;
#end
#implementation BadJsonHelper
- (void)parser:(SBJsonStreamParser *)parser foundArray:(NSArray *)array {
// void
}
- (void)parser:(SBJsonStreamParser *)parser foundObject:(NSDictionary *)dict {
[accumulator addObject:dict];
}
#end
You can drop that little helper class at the top of your file, outside the #implementation section of the class where you're doing your work. (There's no need for the #interface and #implementation being in different files.)
In your code, you would use it like this:
BadJsonHelper *helper = [[BadJsonHelper alloc] init];
helper.accumulator = [NSMutableArray array];
SBJsonStreamParserAdapter *adapter = [[SBJsonStreamParserAdapter new] init];
adapter.delegate = helper;
adapter.levelsToSkip = 1;
SBJsonStreamParser *parser = [[SBJsonStreamParser alloc] init];
parser.delegate = adapter;
switch ([parser parse: responseData]) {
case SBJsonStreamParserComplete:
NSLog(#"%#", helper.accumulator);
break;
case SBJsonStreamParserWaitingForData:
NSLog(#"Didn't get all the JSON yet...");
break;
case SBJsonStreamParserError:
NSLog(#"Error: %#", parser.error);
break;
}
This example was originally adapted from the following test:
https://github.com/stig/json-framework/blob/master/Tests/StreamParserIntegrationTest.m
Update: I created a fully functional example project that loads the JSON asynchronously and parses it. This is available from github.
The JSON is valid, however there is a basic problem regarding the definition of the array of items.
Instead of defining an array of locations using brackets, the JSON redefines the same location key/value pair over and over again. In other words JSON initially says the value of location is the collection with name "The Black Diamond", but immediately after it redefines it with the collection with name "Faculty Library of Humanities" and so on till the last location Faculty of Pharmaceutical Sciences Library".
The same is true for parts and hours.
If you can't fix the result of the JSON and you really need to get it working you may want to modify the JSON removing the "location" keys and adding brackets properly.
Edit
Alternatively you may use an NSScanner and process the JSON result manually. Kinda hacky but it will work as long as the JSON format doesn't change significantly.
Edit
This snipped of code should do the work...
NSString *jsonString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
int indx = 1;
for (;;)
{
NSRange locationRange = [jsonString rangeOfString:#"\"location\":"];
if (locationRange.location == NSNotFound) break;
jsonString = [jsonString stringByReplacingCharactersInRange:locationRange
withString:[NSString stringWithFormat:#"\"location%d\":", indx++]];
}
NSDictionary *locations = [json objectForKey:#"location"];
As you can see, the result of JSON parsing by SBJson is a NSDictionary. A dictionary contains key/value pairs, and the keys are unique identifiers for the pairs.
The JSON data you need to handle is valid but not a good one. Per RFC 4627 - 2.2:
An object structure is represented as a pair of curly brackets surrounding zero or more name/value pairs (or members). A name is a string. A single colon comes after each name, separating the name from the value. A single comma separates a value from a following name. The names within an object SHOULD be unique.
Things like jQuery can parse the JSON also, but the result is the same as SBJson (the last one as the one). See Do JSON keys need to be unique?.
It is not a MUST, but it's still not a good practice. It would be much easier if you are able to change the structure of the JSON data on the server side (or even on the client side after receiving it) rather than parsing it as is.

Subscript and Superscripts in CDATA of an xml file. Using UILabel to display the parsed XML contents

I need to display subscripts and superscripts (only arabic numerals) within a UILabel. The data is taken from an XML file. Here is the snippet of XML file:
<text><![CDATA[Hello World X\u00B2 World Hello]]></text>
Its supposed to display X2 (2 as superscript). When I read the string from the NSXMLParser and display it in the UILabel, it displays it as X\u00B2. Any ideas on how to make it work?
I think you can do something like this, assuming the CDATA contents have been read into an NSString and passed into this function:
-(NSString *)removeUnicodeEscapes:(NSString *)stringWithUnicodeEscapes {
unichar codeValue;
NSMutableString *result = [stringWithUnicodeEscapes mutableCopy];
NSRange unicodeLocation = [result rangeOfString:#"\\u"];
while (unicodeLocation.location != NSNotFound) {
// Get the 4-character hex code
NSRange charCodeRange = NSMakeRange(unicodeLocation.location + 2, 4);
NSString *charCode = [result substringWithRange:charCodeRange];
[[NSScanner scannerWithString:charCode] scanHexInt:&codeValue];
// Convert it to an NSString and replace in original string
NSString *unicodeChar = [NSString stringWithFormat:%C", codeValue];
NSRange replacementRange = NSMakeRange(unicodeLocation.location, 6);
[result replaceCharactersInRange:replacementRange withString:unicodeChar];
unicodeLocation = [result rangeOfString:#"\\u"];
}
return result;
}
I haven't had a chance to try this out, but I think the basic approach would work
\u00B2 is not any sort of XML encoding for characters. Apparently your data source has defined their own encoding scheme (which, frankly, is pretty stupid as XML is capable of encoding these directly, using entities outside of CDATA blocks).
In any case, you'll have to write your own parser that handles \u#### and converts that to the correct character.
I asked the question to my colleague and he gave me a nice and simple workaround. Am describing it here, in case others also get stuck at this.
Firstly goto this link. It has a list of all subscripts and superscripts. For example, in my case, I clicked on "superscript 0". In the following HTML page detailing "superscript 0", goto "Java Data" section and copy the "⁰". You can either place this directly in XML or write a simple regex in obj-c to replace \u00B2 with "⁰". And you will get nice X⁰. Do the same fro anyother superscript or subscript that you might want to display.

NSXMLParser processing complex units of information

I'm processing a response from the server using NSXMLParser successfuly.
Something like this
<data>
<company id="">
<name>XXX</name>
<latitude></latitude>
<longitude></longitude>
</company>
<company id="">
<name>XXX</name>
<latitude></latitude>
<longitude></longitude>
</company>
</data>
I've been using the next methods
didStartElement:namespaceURI: ... to detect when the new company need to be parsed, then I allocate a new instance. And also, detect when an attribute starts
foundCharacters: process the content of every attribute
didEndElement: ... the company has been parsed completely and could be added to the internal list. And also, detect when an attribute has been processed, then set the value processed on the foundCharacters: method
Now, I also need to get the complete XML for one company, and store it in a local cache, anybody knows if there is any way using NSXMLParser to get all the content just for one company? Or maybe without using NSXMLParser. don't know.
<company id="">
<name>XXX</name>
<latitude></latitude>
<longitude></longitude>
</company>
Thank you,
Finally I decided to re-create the XML usign the SAX methods
parser:didStartElement:
// Adding the initial TAG of the xml
accountXML = [[NSString alloc] initWithFormat:#"<%#", elementName];
for (NSString *key in [attributeDict allKeys]){
accountXML = [accountXML stringByAppendingFormat:#" %#=\"%#\"", key
, [attributeDict valueForKey:key]];
}
accountXML = [accountXML stringByAppendingString:#">\n"];
and
parser:didEndElement:
// Add the xml to the account and release it
accountXML = [accountXML stringByAppendingFormat:#"</%#>\n", elementName];
[account setCompleteXML:accountXML];

Get objects from a NSDictionary

I get from an URL this result :
NSString *result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
it looks like this :
[{"modele":"Audi TT Coup\u00e9 2.0 TFSI","modele_annee":null,"annee":"2007","cilindre":"4 cyl","boite":"BVM","transmision":"Traction","carburant":"ES"},
{"modele":"Audi TT Coup\u00e9 2.0 TFSI","modele_annee":null,"annee":"2007","cilindre":"4 cyl","boite":"BVM","transmision":"Traction","carburant":"ES"}]
So it contains 2 dictionaries. I need to take the objects from all the keys from this result. How can I do this?
I tried this : NSDictionary vehiculesPossedeDictionary=(NSDictionary *)result;
and then this : [vehiculesPossedeDictinary objectForKey:#"modele"]; but this is not working.
Please help me... Thanks in advance
What you have is a JSON string which describes an "array" containing two "objects". This needs to be converted to Objective-C objects using a JSON parser, and when converted will be an NSArray containing two NSDictionarys.
You aren't going to be able to get your dictionary directly from a string of JSON. You are going to have to going to have to run it through a JSON parser first.
At this point, there is not one build into the iOS SDK, so you will have to download a third-party tool and include it in your project.
There are a number of different JSON parser, include TouchJSON, YAJL, etc. that you can find and compare. Personally, I am using JSONKit.
#MatthewGillingham suggests JSONKit. I imagine it does fine, but I've always used its competitor json-framework. No real reason, I just found it first and learned it first. I do think its interface is somewhat simpler, but plenty of people do fine with JSONKit too.
Using json-framework:
require JSON.h
...and then
NSString *myJsonString = #"[{'whatever': 'this contains'}, {'whatever', 'more content'}]";
NSArray *data = [myJsonString JSONValue];
foreach (NSDictionary *item in data) {
NSString *val = [item objectForKey:#"whatever"];
//val will contain "this contains" on the first time through
//this loop, then "more content" the second time.
}
If you have array of dictionary just assign objects in array to dictionary like
NSDictionary *dictionary = [array objectAtIndes:0];
and then use this dictionary to get values.

How to implement TouchXml Parser?

I have implemented demo using TouchXml parser.Its working fine.But I want to parse xml like below.
example:
<Root>
<tag1></tag1>
<tag2>
<temp1></temp1>
<temp2></temp2>
<temp3></temp3>
</tag2>
<tag3></tag3>
</Root>
How to parse this type of example?
TouchXML is nice and easy to use. First you'll want to parse the document:
NSError *theError = NULL;
CXMLDocument *theXMLDocument = [[[CXMLDocument alloc] initWithXMLString:input options:0 error:&theError] autorelease];
You can then query the structure of your document using XPath. For example to extract the Root element you might do this:
NSArray *foundRoots = [theXMLDocument nodesForXPath:#"//Root" error:&theError];
CXMLElement *root = [foundRoots objectAtIndex:0];
(You often get arrays back, so in your case you can just take the first element, assuming it exists in the document)
You can also do things like get all the child elements of an element. So if we wanted to get all the tags we could this:
NSArray *children = [root children];
Or we could get a tag with a particular name:
NSArray *tag1 = [root elementsForName:#"tag1"];
(Again you get an array, so do the right thing and check)
Does your data conform to any schema?