Parsing XML file using NSXMLParser - objective-c

I have to parse an XML file using NSXMLParser. There are so many HTML tags in them, so when I am trying to parse it, it will store the string up to that and then again go to found character method and starts to append it.
my code is:
if (Bio_CResults) {
[BioResults appendString: string];
[Info appendString:string];
[stringarr addobject:Info];
NSLog(#"bio==%#",BioResults);
NSLog(#"string==%#",string);
}
and I want to add it in string array, but here it will make create extra object of array. i.e.
stringarr objectAtIndex 0 = abc
stringarr objectAtIndex 1 = def
stringarr objectAtIndex 2 = ghi
but actually I want all of them together in one object because they are actually one string only..
plz help me for that

If you don't even need multiple string objects you can use an NSMutableString instead of an array. Just use the appendString: method to add to the end of the string:
NSMutableString *string = [NSMutableString string];
[string appendString:#"abc"];
[string appendString:#"def"];
NSLog(#"New string is %#", string);
This will log "New string is abcdef".
If you really want an array, use an NSMutableArray instead of an NSArray. That way you can change an object in-place (replace a string with a new string created by appending another string). So for example:
// First create an array with #"abc"
NSMutableArray *mArray = [NSMutableArray arrayWithObject:#"abc"];
// Next get the first object as a string and append #"def" to it in-place
NSString *string = (NSString *)[mArray objectAtIndex:0];
[mArray replaceObjectAtIndex:0 withObject:[string stringByAppendingString:#"def"]];
// Now get the new first object
NSLog(#"First object is %#", (NSString *)[mArray objectAtIndex:0]);
This will log the message "First object is abcdef".

I really dont like NSXmlParser. You might want to read my blog post on using RegxKitLite
http://blog.bluespark.co.nz/?p=51
It might be of some help. Hopefully it wont lead you in the wrong direction.
Cheers, John.

There are so many HTML tags in them…
Be aware that HTML is usually not valid XML. If you're parsing HTML, an XML parser will throw an error some part of the way through the document.
You may be better off creating a WebView, loading the HTML content into it, and then getting a DOMDocument from the view's main frame and traversing the DOM hierarchy. You don't have to put the view into a window; you can just use it to get a DOMDocument.
If you are parsing XML and meant to say “XML tags”, you can disregard this answer.

Related

Writing FileURL's name to Pasteboard

I want to write some fileURL's to general pasteboard using [pasteboard writeObjects:pasteboardArray]; where pasteboard is an object of general pasteboard and pasteboardArray is a NSArray of NSURL's. It writes urls on pasteboard like
file://localhost/Usr/
file://localhost/Vol/
...and so on
I want to write these URL's as following
Usr
Vol
..and so on
i.e only the name of file/Directory as the system does on copying any file/Directory.
For each item on the pasteboard, there can be multiple types of data. When you write an NSURL to the pasteboard, it puts types such as public.file-url (the URL as UTF-8 text), NSFilenamesPboardType (an array of file path strings, serialized to an XML property list), "Apple URL pasteboard type" (an array of file URL strings, serialized to an XML property list), and public.utf8-plain-text (the URL as UTF-8 text).
When you copy a file in the Finder, it puts many of those same types, although it seems to convert the URLs to file reference URLs first. In addition, it puts the file's icon as an ICNS and a TIFF image. However, the public.utf8-plain-text type has just the file's name, not its URL. That string is also present with type public.utf16-plain-text.
So, presumably, the Finder is not simply using -writeObjects: to populate the pasteboard. It may be using the older approach of -declareTypes:owner: and/or -addTypes:owner: followed by -set...:forType: (e.g. -setString:forType:). Or it may be constructing instances of NSPasteboardItem with the desired types and data using its -set...:forType: methods and then writing those items to the pasteboard.
For your needs it would probably be sufficient to do:
[pasteboard writeObjects:arrayOfURLs];
NSMutableString* names = [NSMutableString string];
for (NSURL* url in arrayOfURLs)
{
NSString* name;
if ([url getResourceValue:&name forKey:NSURLLocalizedNameKey error:NULL])
{
if (names.length)
[names appendString:#"\r"];
[names appendString:name];
}
}
if (names.length)
{
[pasteboard addTypes:#[(__bridge NSString*)kUTTypeUTF8PlainText] owner:nil];
[pasteboard setString:names forType:(__bridge NSString*)kUTTypeUTF8PlainText];
}
Note: when you write multiple URLs to the pasteboard, there are multiple items, each with multiple types. However, certain types are for the collection as a whole and those only go on the first item. The plain string types and NSFilenamesPboardType are like this. The above code puts the plain string on the first item, and includes display names for all of the URLs separated by a carriage return (which is what the Finder does).
All of the above said, it's not clear if you wanted the pasteboard to contain just the display names for the URLs and not the URLs themselves in any representation. In that case, just create an array of string from the display names of the URLs and pass that array to -writeObjects: and you're done.
Update:
I've found a way to more closely match what the Finder does while taking a bit of a shortcut:
NSMutableArray* arrayOfPaths = [arrayOfURLs valueForKey:#"path"];
[pasteboard setPropertyList:arrayOfPaths forType:NSFilenamesPboardType];
NSMutableString* names = [NSMutableString string];
BOOL first = YES;
for (NSURL* url in arrayOfURLs)
{
NSString* name;
if (![url getResourceValue:&name forKey:NSURLLocalizedNameKey error:NULL])
name = #"";
if (first)
first = NO;
else
[names appendString:#"\r"];
[names appendString:name];
}
if (names.length)
{
[pasteboard addTypes:#[(__bridge NSString*)kUTTypeUTF8PlainText] owner:nil];
[pasteboard setString:names forType:(__bridge NSString*)kUTTypeUTF8PlainText];
}
When you set the data for type NSFilenamesPboardType, Cocoa automatically sets up multiple items, one for each element in the array. The second and subsequent ones each have just the single type public.file-url. The first item has that type plus most of the others that the Finder puts on the pasteboard, other than the image types. Then, it's just a matter of setting the string value on the first item.
It's important to include the NSFilenamesPboardType type because that's how multiple files were represented on the pasteboard before support for multiple pasteboard items was added. Some apps may still depend on that.
Also, if you provide the string data for each item separately (as done in your own answer), then an app which asks for the string from the pasteboard as a whole (i.e. [pasteboard stringForType:NSStringPboardType]) gets them concatenated with newlines. That's slightly different from what the app would get when files are copied from the Finder, where the strings are concatenated with carriage returns. Some apps may not cope well with the difference.
NSMutableArray *archive=[NSMutableArray array];
for (int i = 0; i < [pasteboardArray count]; i++) {
NSPasteboardItem *item = [[NSPasteboardItem alloc] init];
NSURL *url = [pasteboardArray objectAtIndex:i];
if (url != nil) {
[item setString:[url lastPathComponent] forType:(__bridge NSString*)kUTTypeUTF8PlainText];
NSString *str = [NSString stringWithFormat:#"%#",url];
[item setData:[str dataUsingEncoding:NSUTF8StringEncoding] forType:(__bridge NSString*)kUTTypeFileURL];
}
[archive addObject:item];
}
[pasteboard writeObjects:archive];
This worked for me, where pasteboard is an object of general pasteboard and pasteboardArray is a NSArray of NSURL's. Any better solutions are welcome...

How do I concatenate strings together in Objective-C?

So I am trying to concatenate a bunch of input strings together as one string so I can save that to a text file.
So far I am trying to write something like this
NSString *tempString = [[NSString alloc]initWithFormat:#"%#%#%#", text1, text2, text3];
The only problem with this is that I need a total of 30 strings stored this way. I need a way to do this without typing out each string name. Is there a way to use a for loop or something to accomplish this? Type the strings like this perhaps?
text(i)
So that the variable name would change each time it went through the for loop. I've tried doing something like this and I can't get it to work. If you can help me with this method or another way that you know to do it I would be very thankful.
Okay, so all of the answers here take the wrong approach (sorry guys).
The fundamental problem is that you are using your "text boxes" as a data source, when they should simply be views. When someone changes the text, you should immediately store them in your model (which could be a simple array) and then reference that model later. (This is part of MVC. Look it up if you aren't familiar, as you should be if you are programming for iOS!)
Here is what I would do. (I'm assuming that your "text boxes" are UITextField's.)
Set the delegate for each text field to your view controller.
Set the tag for each text field to a number which represents the order that you want the strings joined in. (ie 1-30)
If you don't have a separate class for your data model, then setup a declared property in your view controller which stores a reference to a NSMutableArray which can contain all of the strings in order. Let's call it dataSource. In viewDidLoad: set this to an actual mutable array filled with empty values (or previously stored values if you are saving them). The reason that we store empty values is so that we can replace them with the user entered strings for any index, even if they are entered out of order:
- (void)viewDidLoad
{
[super viewDidLoad];
self.dataSource = [[NSMutableArray alloc] initWithCapacity:20];
for(int i = 0; i < 30; i++)
[self.dataSource addObject:#""];
}
Then, use the following text field delegate method which stores the strings into the array as they are entered:
// This is called every time that a text field finishes editing.
- (void)textFieldDidEndEditing:(UITextField *)textField
{
if (textField.tag > 0)
[self.dataSource replaceObjectAtIndex:textField.tag-1 withObject:textField.text];
}
Congratulations! All of your strings are now stored in one array. Now we just have to combine them all:
NSMutableString *theString = [self.dataSource componentsJoinedByString:#""];
Note that I have NOT tested all of this so there may be typos. This should get you pointed in the right direction though!
If you set up your text boxes in Interface Builder with an IBOutletCollection(UITextField) you would have an array of text boxes that you could access the text value using KVC and join them.
//interface
...
#property (nonatomic, strong) IBOutletCollection(UITextField) NSArray *textBoxes;
//implementation
...
NSString *tempString = [[textBoxes valueForKey:#"text"]
componentsJoinedByString:#""];
Using iOS 4's IBOutletCollection
If you programmatically create your text boxes then add them to an array as you create them.
NSMutableString's appendString: method is your friend.
NSArray *strings = [NSArray arrayWithObjects: #"Hi", #" there", #" dude", nil];
NSMutableString *result = [[NSMutableString alloc] init];
for (NSString *string in strings) {
[result appendString:string];
}
NSLog(#"result: %#", result); // result: Hi there dude

Reading in from a plist, but accepting only certain strings

The goal is to have an array where all strings are of length n.
So at the moment what I have my code doing is reading in a plist (which is just 250,000 strings) into an array, and then iterating over the array in order to find which ones are/aren't of length n. Of course, for the sake of efficiency, I'd prefer being able to read in from the plist STRING BY STRING so, as I'm reading in, I may the length then before inserting into the array. I'm just starting to learn objective-c, but I was struggling to Google around for a solution =P
EDIT: Well I just found out I can find much more documentation typing property list rather than plist into google :) so I may be able to figure this out myself
You can parse plist into tree (NSMutableDictionary). Dict will have keys with name of string length.
for example
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSString *str in [plistDict allObjects]) {
NSString *key = [NSString stringWithFormat:#"%d", [str length]];
NSMutableArray *array = [result objectForKey:key];
if (!array) {
array = [NSMutableArray array];
}
[array addObject:str];
[result setObject:array forKey:key];
}
than you can access array with needed strings length
NSArray *string4Lenght = [result objectForKey:#"4"];
Apple doesn't provide an API for incrementally parsing a plist.
If you store your plist in XML format, you could use NSXMLParser to parse it. The schema is pretty simple and somewhat described in the Property List Programming Guide.
If you want to incrementally parse the binary format, you're going to have to do more work. There's no official documentation for the format. Apple's source code for reading and writing the format is open source (CFBinaryPList.c) and there are some useful comments along with the actual code.
If you really need to do it incrementally, I suggest going the XML route. If you do, you might want to subclass NSInputStream to be able to read from a gzip or bzip2 file and decompress on the fly.

NSScanner & Array Loops

I am trying to use NSScanner is an NSArray enumeration loop. This however fails:
-[NSXMLElement length]: unrecognized selector sent to instance 0x280da30
No, I am not calling length anywhere. It seems like the value of found gets changed before the scanner completes, hence making it blank, causing the length error.
If I remove the loop and hardcode the string (i don't want that, it's just a test) the code below works. (excluding the enumeration of course)
It never gets as far as calling the NSLogs either...
Here's my code:
for (NSString*found in arr)
{
NSLog(#"Found %#",found);
NSString*search = [NSString stringWithString:found];
NSString *separatorString = #"\"";
NSString *container = nil;
NSScanner *aScanner = [NSScanner scannerWithString:search];
[aScanner setScanLocation:0];
[aScanner scanUpToString:#"src=\"" intoString:nil];
[aScanner scanString:#"src=\"" intoString:nil];
[aScanner scanUpToString:separatorString intoString:&container];
NSLog(#"scanned");
NSLog(#"%#",container);
NSImage*previewImage = [[NSImage alloc] initWithContentsOfFile:[relativeDir stringByAppendingPathComponent:container]];
[preview setImage:previewImage];
[previewImage release];
progressed = progressed + 1;
[convertProgress setDoubleValue:[convertProgress doubleValue] + progressed];
}
How can I get this to work?
Edit:
Here's the input:
NSXMLDocument*doc = [[NSXMLDocument alloc] initWithData:[NSData dataWithContentsOfFile:webPath] options:NSXMLDocumentTidyHTML error:nil];
NSArray*arr = [doc nodesForXPath:#"//img[#src]" error:nil];
From the documentation:
The nodesForXPath:error: method
returns an array of NSXMLNode objects
Your array is not full of strings. It is full of various NSXML* objects. They aren't garbage. This is exactly how it is supposed to work.
You may not be calling length, but stringWithString: is probably calling length to figure out how much space to allocate in the new string.
Conversion to a string can mean a lot of things. An XML node may be a standalone tag, it may be some inline CDATA, it may be a tag with various attributes. As well, an XML document is highly structured. It is rarely meaningful to walk through the contents of a document as a straight enumeration of tags/contents without applying some amount of structural meaning.
You are likely going to need to recurse or otherwise walk the tags and interpret their contents appropriately.
The first step would be to remove everything but that initial NSLog() and see what you got. Then figure out how to walk the tree of nodes. Then figure out how to parse 'em appropriately.
Are you sure that your arr contains NSString objects? The fact that NSXMLElement is throwing an error looks fishy to me.
Edit
Yep, looks like the docs say the array from nodesForXPath doesn't contain NSString objects, but NSXMLElement objects.
http://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSXMLNode_Class/Reference/Reference.html#//apple_ref/occ/instm/NSXMLNode/nodesForXPath:error:

Objective-C: How do you append a string to an NSMutableString?

URL Download
http://code.google.com/p/mwiphonesdk/source/browse/#svn/trunk/iMADE/PrepTasks/08
I have code at the location at the link above and I am using NSMutableString to append strings as data is downloaded. But I am finding that using appendString does not increase the length of the mutable string. I must be doing something wrong.
And when I am done I need to convert NSMutableString to NSString to return it. I have looked for examples to do this but I do not see any yet.
I am most familiar with Java and C# where there is a StringBuffer/StringBuilder which allows you to append pieces and them simply call toString at the end to get a String. It does not appear to be this easy in Objective-C.
NSString* str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
#pragma mark TODO Confirm this is appending a value to the mutable string
[self.mutableString appendString:str];
NSLog(#"str length: %d, %d", [str length], [self.mutableString length]);
Above is the section of code that calls appendString.
I see that str has a length > 0 but calling appendString does not increase the length of self.mutableString.
What am I doing wrong here?
As for having an NSMutableString* and needing to return an NSString*, the former is a subclass of the latter so anywhere you see an NSString* an NSMutableString* will suffice as-is.
Your code looks OK from what you've posted. The only thing I can think of is that perhaps there isn't any data to speak of when initializing the str variable. In such a case appending an empty string will do nothing to mutableString.
You'll also want to make sure self.mutableString has been properly allocated and initialized. You can send messages to NSObject*s that are nil which may be misleading when [self.mutableString length] returns 0.
I have fixed the problem. I simply was not initializing the NSMutableString value and it was transparently not doing anything.
Before appending the string I put the following code.
if (_mutableString == nil){
_mutableString = [[NSMutableString alloc] init];
}
Thanks everyone for answering. And it is good to know that I can use NSMutableString in place of NSString. (that is too easy) :)