Parsing Excel Data in Apple Swift - objective-c

My current workflow involves using Applescript to essentially delimit Excel data and format it into plain text files. We're pushing towards an all Swift environment, but I haven't yet found any sort of kits for parsing my Excel data into Swift.
The only thing I can think of is to use C or something and wrap it, but that's not ideal. Any better suggestions for parsing this data for use in Swift?
The goal is to eliminate Applescript, but I'm not sure if that will be possible while still interacting with Excel files. Scripting Excel via Applescript seems to be the only method.
EDIT: I don't have the option of eliminating Excel from this workflow. This is how the data will be coming to the application, thus I have to include it.
Being able to streamline the process of parsing this data then processing it will be paramount. I know Applescript has been good in the past with helping me to process it; however, it's getting a little too closed-off for me.
I've been looking at writing something in Swift/Cocoa, but that still might require the data to be extracted with an Applescript, right?
A big plus for pushing Swift is the readability. I don't know Objective-C all that well, and swift would be an easier transition, I feel.
My workflow on PC has been using the COM object, which as has been said, isn't available in the Mac Excel app. I'm only looking for data extraction at this point. Some previous apps did processing within the app, but I'm looking to make this very self-contained, thus all processing within the app I'm developing. Once the data is extracted from the .XLS or .XLSX files, I'll be doing some text editing via RegEx and perhaps a little number crunching. Nothing too crazy. As of now, it will run on the client side, but I'm looking to extend this to a server process.

In Mac OS X 10.6 Snow Leopard Apple introduced the AppleScriptObjC framework which makes it very easy to interact between Cocoa and AppleScript. AppleScript code and a Objective-C like syntax can be used in the same source file. It's much more convenient than Scripting Bridge and NSAppleScript.
AppleScriptObjC cannot be used directly in Swift because the command loadAppleScriptObjectiveCScripts of NSBundle is not bridged to Swift.
However you can use a Objective-C bridge class for example
ASObjC.h
#import Foundation;
#import AppleScriptObjC;
#interface NSObject (Excel)
- (void)openExcelDocument:(NSString *)filePath;
- (NSArray *)valueOfUsedRange;
#end
#interface ASObjC : NSObject
+ (ASObjC *)sharedASObjC;
#property id Excel;
#end
ASObjC.m
#import "ASObjC.h"
#implementation ASObjC
+ (void)initialize
{
if (self == [ASObjC class]) {
[[NSBundle mainBundle] loadAppleScriptObjectiveCScripts];
}
}
+ (ASObjC *)sharedASObjC
{
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[ASObjC alloc] init];
});
return sharedInstance;
}
- (instancetype)init
{
self = [super init];
if (self) {
_Excel = NSClassFromString(#"ASExcel");
}
return self;
}
#end
Create a AppleScript source file form the AppleScriptObjC template
ASExcel.applescript
script ASExcel
property parent: class "NSObject"
on openExcelDocument:filePath
set asFilePath to filePath as text
tell application "Microsoft Excel"
set sourceBook to open workbook workbook file name asFilePath
repeat
try
get workbooks
return
end try
delay 0.5
end repeat
end tell
end openDocument
on valueOfUsedRange()
tell application "Microsoft Excel"
tell active sheet
set activeRange to used range
return value of activeRange
end tell
end tell
end valueOfUsedRange
end script
Link to the AppleScriptObjC framework if necessary.
Create the Bridging Header and import ASObjC.h
Then you can call AppleScriptObjC from Swift with
ASObjC.sharedASObjC().Excel.openExcelDocument("Macintosh HD:Users:MyUser:Path:To:ExcelFile.xlsx")
or
let excelData = ASObjC.sharedASObjC().Excel.valueOfUsedRange() as! Array<[String]>

It's somewhat unclear if you're trying to eliminate Excel as a dependency (which is not unreasonable: it costs money and not everyone has it) or AppleScript as a language (totally understandable, but a bad practical move as Apple's alternatives for application automation all suck).
There are third-party Excel-parsing libraries available for other languages, e.g. I've used Python's openpyxl (for .xlsx files) and xlrd (for .xsl) libraries successfully in my own projects. And I see through the magicks of Googles that someone's written an ObjC framework, DHlibxls, which [assuming no dynamic trickery] should be usable directly from Swift, but I've not used it myself so can't tell you anything more.

You can use ScriptingBridge or NSAppleScript to interact with Apple Scriptable stuff
ScriptingBridge can generate a header file from the Apple Script dictionary.
NSAppleScript can execute any AppleScript for you by passing a String

1. Export to plaintext CSV
If all you're trying to do is extract data from Excel to use elsewhere, as opposed to capturing Excel formulas and formatting, then you probably should not try to read the .xls file. XLS is a complex format. It's good for Excel, not for general data interchange.
Similarly, you probably don't need to use AppleScript or anything else to integrate with Excel, if all you want to do is save the data as plaintext. Excel already knows how to save data as plaintext. Just use Excel's "Save As" command. (That's what it's called on the Mac. I don't know about PCs.)
The question is then what plaintext format to use. One obvious choice for this is a plaintext comma-separated value file (CSV) because it's a simple de facto standard (as opposed to a complex official standard like XML). This will make it easy to consume in Swift, or in any other language.
2. Export in UTF-8 encoding if possible, otherwise as UTF-16
So how do you do that exactly? Plaintext is wonderfully simple, but one subtlety that you need to keep track of is the text encoding. A text encoding is a way of representing characters in a plaintext file. Unfortunately, you cannot reliably tell the encoding of a file just by inspecting the file, so you need to choose an encoding when you save it and remember to use that encoding when you read it. If you mess this up, accented characters, typographer's quotation marks, dashes, and other non-ASCII characters will get mangled. So what text encoding should you use? The short answer is, you should always use UTF-8 if possible.
But if you're working with an older version of Excel, then you may not be able to use UTF-8. In that case, you should use UTF-16. In particular, UTF-16 is, I believe, the only export option in Excel 2011 for Mac which produces a predictable result which will not depend in surprising ways on obscure locale settings or Microsoft-specific encodings.
So if you're on Excel 2011 for Mac, for instance, choose "UTF-16 Unicode Text" from Excel's Save As command.
This will cause Excel to save the file so that every row is a line of text, and every column is separated by a tab character. (So technically, this is a tab-separated value files, rather than a comma-separated value file.)
3. Import with Swift
Now you have a plaintext file, which you know was saved in a UTF-8 (or UTF-16) encoding. So now you can read it and parse it in Swift.
If your Excel data is complicated, you may need a full-featured CSV parser. The best choice is probably CHCSVParser.
Using CHCSV, you can parse the file with the following code:
NSURL * const inputFileURL = [NSURL fileURLWithPath:#"/path/to/exported/file.txt"];
unichar tabCharacter = '\t';
NSArray *rows = [NSArray arrayWithContentsOfCSVFile:inputFilePath options:CHCSVParserOptionsSanitizesFields
delimiter:tabCharacter];
(You could also call it from Swift, of course.)
On the other hand, if you're data is relatively simple (for instance, it has no escaped characters), then you might not need to use an external library at all. You can write some Swift code that parses tab-separated values just by reading in the file as a string, splitting on newlines, and then splitting on tabs.
This function will take a String representing TSV data and return an array of dictionaries:
/**
Reads a multiline, tab-separated String and returns an Array<NSictionary>, taking column names from the first line or an explicit parameter
*/
func JSONObjectFromTSV(tsvInputString:String, columnNames optionalColumnNames:[String]? = nil) -> Array<NSDictionary>
{
let lines = tsvInputString.componentsSeparatedByString("\n")
guard lines.isEmpty == false else { return [] }
let columnNames = optionalColumnNames ?? lines[0].componentsSeparatedByString("\t")
var lineIndex = (optionalColumnNames != nil) ? 0 : 1
let columnCount = columnNames.count
var result = Array<NSDictionary>()
for line in lines[lineIndex ..< lines.count] {
let fieldValues = line.componentsSeparatedByString("\t")
if fieldValues.count != columnCount {
// NSLog("WARNING: header has %u columns but line %u has %u columns. Ignoring this line", columnCount, lineIndex,fieldValues.count)
}
else
{
result.append(NSDictionary(objects: fieldValues, forKeys: columnNames))
}
lineIndex = lineIndex + 1
}
return result
}
So you only need to read the file into a string and pass it to this function. That snippet comes from this gist for a tsv-to-json converter. And if you need to know more about which text encodings Microsoft products produce, and which ones Cocoa can auto-detect, then this repo on text encoding contains the research on export specimens which led to the conclusion that UTF-16 is the way to go for old Microsoft products on the Mac.
(I realize I'm linking to my own repos here. Apologies?)

There is no need to export Excel files to CSV for Swift as you can use an existing open-source library for parsing XLSX files. If you use CocoaPods or Swift
Package Manager for integrating 3rd-party libraries, CoreXLSX supports those. After the library is integrated, you can use it like this:
import CoreXLSX
guard let file = XLSXFile(filepath: "./file.xlsx") else {
fatalError("XLSX file corrupted or does not exist")
}
for path in try file.parseWorksheetPaths() {
let ws = try file.parseWorksheet(at: path)
for row in ws.sheetData.rows {
for c in row.cells {
print(c)
}
}
}
This will open file.xlsx and print all cells within that file. You can also filter cells by references and access only cell data that you need for your automation.

Related

How to print something to the console in Xcode?

How do you print something to the console of Xcode, and is it possible to view the Xcode console from the app itself?
Thanks!
How to print:
NSLog(#"Something To Print");
Or
NSString * someString = #"Something To Print";
NSLog(#"%#", someString);
For other types of variables, use:
NSLog(#"%#", someObject);
NSLog(#"%i", someInt);
NSLog(#"%f", someFloat);
/// etc...
Can you show it in phone?
Not by default, but you could set up a display to show you.
Update for Swift
print("Print this string")
print("Print this \(variable)")
print("Print this ", variable)
print(variable)
#Logan has put this perfectly. Potentially something worth pointing out also is that you can use
printf(whatever you want to print);
For example if you were printing a string:
printf("hello");
3 ways to do this:
In C Language (Command Line Tool) Works with Objective C, too:
printf("Hello World");
In Objective C:
NSLog(#"Hello, World!");
In Objective C with variables:
NSString * myString = #"Hello World";
NSLog(#"%#", myString);
In the code with variables, the variable created with class, NSString was outputted be NSLog. The %# represents text as a variable.
#Logan said it perfectly. but i would like to add an alternative here,
if you want to view logs from just your application then you can make
a custom method that keeps saving the log to a file in documents
directory & then you can view that log file from your application.
There is one good advantage for developers of the app after the app has been released & users are downloading it. Because your app will be able to send logs & crash reports to the developers (of course with the permissions of the device user !!!) & it'll be the way to improve your application.
Let me know (To other SO users), if there is another way of doing the same thing. (Like default Apple feature or something)
Let me know if it helps or you want some more idea.
You can also use breakpoints. Assuming the value you want is defined within the scope of your breakpoint you have 3 options:
print it in console doing:
po some_paramter
Bare in mind in objective-c for properties you can't use self.
po _someProperty
po self.someProperty // would not work
po stands for print object.
Or can just use Xcode 'Variable Views' . See the image
I highly recommend seeing Debugging with Xcode from Apple
Or just hover over within your code. Like the image below.
In some environments, NSLog() will be unresponsive. But there are other ways to get output...
NSString* url = #"someurlstring";
printf("%s", [url UTF8String]);
By using printf with the appropriate parameters, we can display things this way. This is the only way I have found to work on online Objective-C sandbox environments.
In Swift with Xcode you can use either print() or NSLog().
print() just outputs your text. Nothing more.
NSLog() additionally to your text outputs time and other useful info to debug console.

Files Copied to Clipboard are empty and "Missing Sandbox Extension" When Pasted

I have an application that is copying files to the clipboard. Here is the relevant code (arguments is an array of NSStrings containing file paths):
NSMutableArray *filesToCopy = [[NSMutableArray alloc] init];
int i;
for (i=1; i < [arguments count]; i++) {
NSString* pathToFile = [arguments objectAtIndex:i];
NSURL* fileURL = [[NSURL alloc] initFileURLWithPath:pathToFile];
[filesToCopy addObject:fileURL];
}
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSInteger changeCount = [pasteboard clearContents];
BOOL OK = [pasteboard writeObjects:filesToCopy];
However, in testing when trying to paste a file copied to the clipboard by the program into an empty email in Mail, often the file shows up as zero bytes and the following error appears on the console:
11/13/13 6:27:12.173 AM Mail[627]: Failed to obtain a valid sandbox extension for item: [789514] of flavor: [public.file-url] from the pasteboard.
11/13/13 6:27:12.174 AM Mail[627]: Failed to get a sandbox extensions for itemIdentifier (789514). The data for the sandbox extension was NULL
and then that is followed by the following error on the console:
11/13/13 8:24:41.947 AM sandboxd[172]: ([627]) Mail(627) deny file-read-xattr [full path of file]
What is strange is that if I copy the file from Finder then it pastes just fine with no errors every time. In other words, somehow Finder copies the file to the clipboard with different information than the way I am doing it. To verify this, I did a simple AppleScript to return clipboard info. After I copy a file to the clipboard that returns the following:
{{«class furl», 115}, {«class utf8», 115}, {«class ut16», 232}, {string, 115}, {Unicode text, 230}}
After I copy the same file to the clipboard using Finder, the Applescript returns the following:
{{«class furl», 33}, {«class icns», 795020}, {«class ut16», 112}, {«class utf8», 55}, {«class 8BPS», 1630436}, {«class BMP », 4194358}, {«class TPIC», 1059291}, {TIFF picture, 4197954}, {«class PNGf», 392648}, {«class jp2 », 213480}, {GIF picture, 121307}, {JPEG picture, 116181}, {Unicode text, 110}, {string, 55}}
So Finder is putting more information about the file on the clipboard and different information. For example, the furl class has a different length. This extra information is obviously what is cause Mail to successfully past a file copied from Finder while it has an error pasting a file copied by my program.
Any clues to what information I'm missing when I put the file on the clipboard or what extra information I should be adding to the paste? I'm guessing that Finder is pasting not just an array of NSURL's, but an array of Dictionary Keys that includes other file information. It also seems to be creating the furl class differently than I am. I've spent time pouring over documentation and I'm stuck on this one.
I believe I found the problem. It seems like when command line applications copy to the pasteboard, there is a permission related to sandboxing that is not transferred. The files copied could be pasted fine into any non-sandboxed app, but not into a sandboxed application. The solution in this case was to just create a regular Cocoa based .app program. I'm still not sure how, but it copies the files in a way that the permissions are properly transferred and the file can be pasted in both non-sandboxed and sandboxed applications.
There is is lot written out there about avoiding triggering the Sandboxing mechanism in Sandboxed Apps.
But all answers lack the most obvious trouble that can trigger Sandboxing.
When you make a copy of a NSURL (which is still based on NSString) into NSPasteboard without properly escaped characters still containing non-valid signs and spaces before transforming into 'NSURL'.
This is one of the ways URLs can become malicious, so it is only a little bit annoying that the Error Message you get while trying to expose the link to pasteboard is not telling you what the obvious reason was in detail. But maybe this is also because, "never give too much informations how to circumvent the safety barriers".
It just says:
[sandbox] CreateSandboxExtensionData failed: urlData: 0x0000000000XYZ length: 0 (-1 means null)
then you have to properly escape the string with
NSString *encodedStringForURL = [yourString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
this will exchange special characters that should not be part of n URL.
After that you can transform to NSURL as usually.
or
Failed to obtain a valid sandbox extension
then you also used a wrong NSPasteboardType that exposes a URL without proper type. In example when you exposed a String that poses as Link because you tried to avoid the [sandbox] trigger by being clever and telling the pasteboard this is only a random string. The Sandbox is still more clever when you prepend the string with file://.
Then the solution is to use NSPasteboardTypeFileURL and/or NSPasteboardTypeURL. This will inform the sandbox that you exposed links on purpose with proper type.
There are more pitfalls, but also remember you have to use YourURL.absoluteString when you paste to Pasteboard.

How to dynamically typecast objects to support different versions of an application's ScriptingBridge header files?

Currently I'm trying to implement support for multiple versions of iTunes via ScriptingBridge.
For example the method signature of the property playerPosition changed from (10.7)
#property NSInteger playerPosition; // the player’s position within the currently playing track in seconds.
to (11.0.5)
#property double playerPosition; // the player’s position within the currently playing track in seconds
With the most current header file in my application and an older iTunes version the return value of this property would always be 3. Same thing goes the other way around.
So I went ahead and created three different iTunes header files, 11.0.5, 10.7 and 10.3.1 via
sdef /path/to/application.app | sdp -fh --basename applicationName
For each version of iTunes I adapted the basename to inlcude the version, e.g. iTunes_11_0_5.h.
This results in the interfaces in the header files to be prefixed with their specific version number.
My goal is/was to typecast the objects I'd use with the interfaces of the correct version.
The path to iTunes is fetched via a NSWorkspace method, then I'm creating a NSBundle from it and extract the CFBundleVersion from the infoDictionary.
The three different versions (11.0.5, 10.7, 10.3.1) are also declared as constants which I compare to the iTunes version of the user via
[kiTunes_11_0_5 compare:versionInstalled options:NSNumericSearch]
Then I check if each result equals NSOrderedSame, so I'll know which version of iTunes the user has installed.
Implementing this with if statement got a bit out of hand, as I'd need to do these typecasts at many different places in my class and I then started to realize that this will result in a lot of duplicate code and tinkered around and thought about this to find a different solution, one that is more "best practice".
Generally speaking, I'd need to dynamically typecast the objects I use, but I simply can't find a solution which wouldn't end in loads of duplicated code.
Edit
if ([kiTunes_11_0_5 compare:_versionString options:NSNumericSearch] == NSOrderedSame) {
NSLog(#"%#, %#", kiTunes_11_0_5, _versionString);
playerPosition = [(iTunes_11_0_5_Application*)_iTunes playerPosition];
duration = [(iTunes_11_0_5_Track*)_currentTrack duration];
finish = [(iTunes_11_0_5_Track*)_currentTrack finish];
} else if [... and so on for each version to test and cast]
[All code directly entered into answer.]
You could tackle this with a category, a proxy, or a helper class, here is a sketch of one possible design for the latter.
First create a helper class which takes and instance of your iTunes object and the version string. Also to avoid doing repeated string comparisons do the comparison once in the class setup. You don't give the type of your iTunes application object so we'll randomly call it ITunesAppObj - replace with the correct type:
typedef enum { kEnumiTunes_11_0_5, ... } XYZiTunesVersion;
#implementation XYZiTunesHelper
{
ITunesAppObj *iTunes;
XYZiTunesVersion version;
}
- (id) initWith:(ITunesAppObj *)_iTunes version:(NSString *)_version
{
self = [super self];
if (self)
{
iTunes = _iTunes;
if ([kiTunes_11_0_5 compare:_version options:NSNumericSearch] == NSOrderedSame)
version = kEnumiTunes_11_0_5;
else ...
}
return self;
}
Now add an item to this class for each item which changes type between versions, declaring it with whatever "common" type you pick. E.g. for playerPosition this might be:
#interface XYZiTunesHelper : NSObject
#property double playerPosition;
...
#end
#implementation XYZiTunesHelper
// implement getter for playerPosition
- (double) playerPosition
{
switch (version)
{
case kEnumiTunes_11_0_5:
return [(iTunes_11_0_5_Application*)_iTunes playerPosition];
// other cases - by using an enum it is both fast and the
// compiler will check you cover all cases
}
}
// now implement the setter...
Do something similar for track type. Your code fragment then becomes:
XYZiTunesHelper *_iTunesHelper = [[XYZiTunesHelper alloc] init:_iTunes
v ersion:_versionString];
...
playerPosition = [_iTunesHelper playerPosition];
duration = [_currentTrackHelper duration];
finish = [_currentTrackHelper finish];
The above is dynamic as you requested - at each call there is a switch to invoke the appropriate version. You could of course make the XYZiTunesHelper class abstract (or an interface or a protocol) and write three implementations of it one for each iTunes version, then you do the test once and select the appropriate implementation. This approach is more "object oriented", but it does mean the various implementations of, say, playerPosition are not together. Pick whichever style you feel most comfortable with in this particular case.
HTH
Generating multiple headers and switching them in and out based on the application's version number is a really bad 'solution': aside from being horribly complicated, it is very brittle since it couples your code to specific iTunes versions.
Apple events, like HTTP, were designed by people who understood how to construct large, flexible long-lived distributed systems whose clients and servers could evolve and change over time without breaking each other. Scripting Bridge, like a lot of the modern 'Web', was not.
...
The correct way to retrieve a specific type of value is to specify your required result type in the 'get' event. AppleScript can do this:
tell app "iTunes" to get player position as real
Ditto objc-appscript, which provides convenience methods specifically for getting results as C numbers:
ITApplication *iTunes = [ITApplication applicationWithBundleID: #"com.apple.itunes"];
NSError *error = nil;
double pos = [[iTunes playerPosition] getDoubleWithError: &error];
or, if you'd rather get the result as an NSNumber:
NSNumber *pos = [[iTunes playerPosition] getWithError: &error];
SB, however, automatically sends the 'get' event for you, giving you no what to tell it what type of result you want before it returns it. So if the application decides to return a different type of value for any reason, SB-based ObjC code breaks from sdp headers onwards.
...
In an ideal world you'd just ditch SB and go use objc-appscript which, unlike SB, knows how to speak Apple events correctly. Unfortunately, appscript is no longer maintained thanks to Apple legacying the original Carbon Apple Event Manager APIs without providing viable Cocoa replacements, so isn't recommended for new projects. So you're pretty much stuck with the Apple-supplied options, neither of which is good or pleasant to use. (And then they wonder why programmers hate everything AppleScript so much...)
One solution would be to use AppleScript via the AppleScript-ObjC bridge. AppleScript may be a lousy language, but at least it knows how to speak Apple events correctly. And ASOC, unlike Cocoa's crappy NSAppleScript class, takes most of the pain out of gluing AS and ObjC code together in your app.
For this particular problem though, it is possible to monkey-patch around SB's defective glues by dropping down to SB's low-level methods and raw four-char codes to construct and send the event yourself. It's a bit tedious to write, but once it's done it's done (at least until the next time something changes...).
Here's a category that shows how to do this for the 'player position' property:
#implementation SBApplication (ITHack)
-(double)iTunes_playerPosition {
// Workaround for SB Fail: older versions of iTunes return typeInteger while newer versions
// return typeIEEE64BitFloatingPoint, but SB is too stupid to handle this correctly itself
// Build a reference to the 'player position' property using four-char codes from iTunes.sdef
SBObject *ref = [self propertyWithCode:'pPos'];
// Build and send the 'get' event to iTunes (note: while it is possible to include a
// keyAERequestedType parameter that tells the Apple Event Manager to coerce the returned
// AEDesc to a specific number type, it's not necessary to do so as sendEvent:id:parameters:
// unpacks all numeric AEDescs as NSNumber, which can perform any needed coercions itself)
NSNumber *res = [self sendEvent:'core' id:'getd' parameters: '----', ref, nil];
// The returned value is an NSNumber containing opaque numeric data, so call the appropriate
// method (-integerValue, -doubleValue, etc.) to get the desired representation
return [res doubleValue];
}
#end
Notice I've prefixed the method name as iTunes_playerPosition. Unlike objc-appscript, which uses static .h+.m glues, SB dynamically creates all of its iTunes-specific glue classes at runtime, so you can't add categories or otherwise patch them directly. All you can do is add your category to the root SBObject/SBApplication class, making them visible across all classes in all application glues. Swizzling the method names should avoid any risk of conflict with any other applications' glue methods, though obviously you still need to take care to call them on the right objects otherwise you'll likely get unexpected results or errors.
Obviously, you'll have to repeat this patch for any other properties that have undergone the same enhancement in iTunes 11, but at least once done you won't have to change it again if, say, Apple revert back to integers in a future release or if you've forgotten to include a previous version in your complicated switch block. Plus, of course, you won't have to mess about generating multiple iTunes headers: just create one for the current version and remember to avoid using the original -playerPosition and other broken SB methods in your code and use your own robust iTunes_... methods instead.

NSMutableArray Meta-Data when exported to CSV file

Looking for some advice with an iOS application.
Essentially what my app does is generate a CSV file that logs certain events within a period of time. So users can press a button and an entry will be added to the log saying "Event of type X happened at Time T"
The way I'm doing this is by maintaining an NSMutableArray which stores NSStrings. Each event adds a string to the NSMutableArray.
When the user is done with a session, they can "export" the file. I'm using the NSMutableArray's writeToFile; then I use an e-mail interface to send that file as a CSV to a target e-mail.
It all works, except the CSV file that is generated has some meta-data in it. Specifically, I believe at the top of the file I see and at the beginning of each row of cells when opened in excel.
Is this something inherent in the data structure (NSMutableArray) or data type (NSString) that I am using? Is there a way for me to just get the raw data?
I can upload code if need be (I'm not near the work computer now though, so I'm testing the waters to see if there is something simple I can do to stop seeing this meta-data).
Thank you!
CSV is a very simple format. You can separate the strings with semi-colons and then write everything to a file using NSOutputStream.
This code assumes you already have a string array with CSV rows:
NSOutputStream* csvoutput = [NSOutputStream outputStreamToFileAtPath:filepath append:NO];
[csvoutput open];
for (NSString* str in array) {
NSString* tempStr = [str stringByAppendingString:#"\n"]; //new line
NSData *strData = [tempStr dataUsingEncoding:NSUTF8StringEncoding];
[csvoutput write:(uint8_t *)[strData bytes] maxLength:[strData length]];
}
[csvoutput close];
You better create a model class (Event) and fill the array with Event-instances instead of strings. Thats cleaner and more efficient. Then you would create the CSV-strings when exporting to a file.

How to use NSFileHandle to read integer from file

I want to read a list of integers from a text file, I just want to write code like
int temp;
fin>>temp;
But when I read the Cocoa documentation, I found NSFileHandle is suggested, and there is no method like I assumed, only one related method:
- (NSData *)readDataOfLength:(NSUInteger)length
Is there any class/method can help me do this in Objective C? Thanks.
I want to read a list of integers from a text file, I just want to
write code like
int temp; fin>>temp;
You have a lot of choices for file access. If you want to use C++-style access, you can, but you naturally need to open the file using the appropriate C++ file or stream methods. If you use Objective-C++, though, you can easily mix C++ code into your Objective-C.
You can also use the C standard library file routines like fopen(), fread(), etc.
Using C or C++ to read files is often a good choice if the files are coming from a source other than your program, something beyond your control.
But when I read Cocoa document, I found NSFileHandle is suggested, and
there is no method like I assumed, only one related method:
Again, lots of choices. Yes, you can use NSFileHandle to read bytes from the file into a NSData object, and then you can get ranges of bytes out of the data object. A much more common way to write and read data, though, is to use NSKeyedArchiver and NSKeyedUnarchiver:
NSData *data = [NSData dataWithContentsOfFile:pathToFile];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
int age = [unarchiver decodeIntForKey:#"Age"];
int weight = [unarchiver decodeIntForKey:#"Weight"];
NSString *name = [unarchiver decodeObjectForKey:#"Name"];
That's just the tip of the iceberg, though. It seems like a lot of code compared to what you were looking for, but it can also be a lot less work. Because objects and their relationships can be stored and read, you can read in a complex graph of objects with very little code:
OrgChart *chart = [NSUnarchiver unarchiveObjectWithFile:pathToFile];
Another option is to use property lists, which are very easy to use, but limited in the data types that can be used.
If you want to learn more about these topics, read Archives and Serializations Programming Guide, Property List Programming Guide, and File System Programming Guide.
You could use Objective-C++ and iostreams as you're used to.
You could use the C I/O functions like fopen and fscanf. (Probably what I'd do.)
Apple provides NSScanner for parsing, but it only reads from a string, not from an input stream. If you really want to use it, first you'll have to read your whole file (or a large chunk) into an NSString (or an NSData and then convert that to NSString).
If you can require the text file to be in JSON format, you can read it in one gulp and use NSJSONSerialization to parse it.