Using file pointers in Objective-C for iOS - objective-c

I am trying to use a library (cfitsio) in an iOS application that heavily depends on using a file pointer to open and maintain access to a data file. I have successfully built an Objective-C Mac app that does what I need to do on iOS. Cocoa touch has methods to load a file in to NSData but I don't see anyway to get a file pointer directly, probably because of the stricter privacy around the file system on iOS. Is there a way to get a file pointer directly or use NSData to make a file pointer for temporary use with the library?
The c function I would be using to open the file is declared below. The file pointer continues to be used in many of the other library functions.
int ffopen(fitsfile **fptr, /* O - FITS file pointer */
const char *name, /* I - full name of file to open */
int mode, /* I - 0 = open readonly; 1 = read/write */
int *status); /* IO - error status */

Try using the NSFileManager which creats a NSFileHandle when you open a file.
This handle has a getter for the fileDescriptor:
Returns the file descriptor associated with the receiver.
- (int)fileDescriptor
Return Value
The POSIX file descriptor associated with the receiver.
see also NSFileHandle

It seems this library declares a type "fitsfile" and uses pointers "fitsfile *".
The function ffopen looks very much like it opens the file and creates the fitsfile* which it returns using a fitsfile**. So you don't have to worry about these files at all, the library does it.
So you'd probably write something like
NSString* path = <Objective-C code to get a path>;
BOOL readOnly = <YES or NO, your choice>
fitsfile* fptr = NULL;
int fstatus = 0;
int fresult = ffopen (&fptr, path.UTF8String, (readonly ? 0 : 1), &fstatus);
Since this library wants the data stored in a file that has a path, the only way to do this is to store the data in a file and pass the path to that file to ffopen.

Related

Parsing Excel Data in Apple Swift

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.

Add constants issues Xcode 4.5

I've got a class for storing constants.
So, there are two files that call Constant.h and Constant.m
This is what I have in .h file:
#import <Foundation/Foundation.h>
enum kParams {
kFirstName = 0,
kLastName = 1
};
extern NSString * const kNotificationUpdateMainMenu;
This is what I have in .m file:
#import "Constants.h"
NSString * const kNotificationUpdateMainMenu = #"kNotificationUpdateMainMenu";
For first time it works good, but when I try to add some other const (kNotificationFbLoginSuccsess for example) other classes don't see it.
This is a message that shows me which problem I have. But I don't understand how my other constants work without this issue (just new constant that I add get this error).
/Users/developer/Documents/Projects/Test/Test/Test/AppDelegate.m:121:64: Use of undeclared identifier 'kNotificationFbLoginSuccsess'
I found some way how to fix it:
Open organizer
Clear derived data
Delete project.xcworkspace file and xcuserdata
Close Project
Relaunch Xcode
but as I think is too much operations that I can add one constant. How come?
Your "global" constant is not actually external (separately compiled and later linked together). Take the easy way out and place NSString * const kNotificationUpdateMainMenu = #"kNotificationUpdateMainMenu"; into the header file. The method file needs nothing.
I would use #define kNotificationUpdateMainMenu #"kNotificationUpdateMainMenu" to perform the spell checking. The compiler will create one shared instance of the constant string for the entire compilation.

How to embed SQL schema in an Objective-C command-line app?

I have an SQL schema that I need to feed to SQLite in my Objective-C command-line app. I’d like the schema to be a part of the binary, so that I can distribute just one file. Unlike a regular Mac or iOS app, the binary has no resource bundle, so the traditional way of storing resources inside the app bundle is out. Is there an elegant way to include the schema in the source? I know I can simply store it as a multiline string in a header, but that sucks.
Schema.h
extern NSString * someSchema;
Schema.m
NSString * someSchema = #"CREATE TABLE IF EXISTS blah...."
#"More SQL here"
#"more SQL here";
What about creating an array of sql statements, and then you use an enum to access the statements.
Schema.h file:
static const NSString *sqlStatements[] = {
#"CREATE TABLE...",
#"SELECT * FROM ...",
... // Lots of other statements
#"DELETE ..."
};
typedef enum {
SQLCREATECommand=0,
SQLSelectCommand,
... // Matching enums
SQLDeleteCommand
} SQLCommands;
Used in some other file:
NSString *stmt = sqlStatements[SQLCREATECommand];
The benefit of doing it this way is that the code becomes more maintainable.
One interesting solution I have come up with is extended attributes. It’s possible to add a Run Script phase to the Xcode build process that does something like:
xattr -w com.company.Schema "`cat SQL/schema.sql`" \
${BUILT_PRODUCTS_DIR}/${EXECUTABLE_NAME}
And then in runtime:
const size_t maxSchemaSize = 1000;
char schema[maxSchemaSize] = {0};
getxattr(argv[0], "com.company.Schema", schema, maxSchemaSize, 0, 0);
NSLog(#"%s", schema);
This way I can keep the schema in a separate file without mangling it into a header file. The obvious downside is that the extended attribute might not survive some file operations.

Using a file which is located within the iPhone sandbox

I'm creating an iPhone app that needs the location of .wav which is located in the sandbox. I do this with the following:
recordFilePath = (CFStringRef)[NSTemporaryDirectory() stringByAppendingPathComponent: #"recordedFile.wav"];
How can I convert recordFilePath to a string that can then be passed into a C function?
EDIT 1
By the way, this is the statement that is going to receive the c string:
freopen ("locationOfFile/recordedFile.wav","r",stdin);
That will depend on what type your c function expects to get - you can write a c function that accepts NSString* object as parameter. If you want to convert your NSString to char* then you can use one of the following functions:
– cStringUsingEncoding:
– getCString:maxLength:encoding:
– UTF8String

In my code how to launch application responsible for an UTI

My Mac OS X application receives a file over the network (in this case, text/x-vcard). In my code, how can I open the related application (typically the Address Book) without hard-coding paths or application name so that it processes the file ?
You'll be able to do this by linking in the ApplicationServices framework, which has a really handy "LSCopyApplicationForMIMEType" function. It works like this:
CFURLRef appURL = nil;
OSStatus err = LSCopyApplicationForMIMEType(CFSTR("text/x-vcard"), kLSRolesAll, &appURL);
if (err != kLSApplicationNotFoundErr) {
NSLog(#"URL: %#", (NSURL *)appURL);
}
CFRelease(appURL);
I'll explain what the parameters mean. The first parameter is a CFStringRef of the MIME type you're looking up. The second parameter indicates what kind of application you're looking for, ie an app that can edit this file, or an app that can view this file, etc. kLSRolesAll means you don't care. The final parameter is a pointer to the CFURLRef where the function will stick the app's URL (if it can find one).
On my machine, this prints out:
2009-08-01 12:38:58.159 EmptyFoundation[33121:a0f] URL: file://localhost/Applications/Address%20Book.app/
One of the cool things about CFURLRefs is that they're toll-free bridged to NSURL. This means you can take a CFURLRef and cast it to an NSURL, and vice versa. Once you've got your NSURL of the app, it's pretty trivial to use something like NSWorkspace's -launchApplicationAtURL:options:configuration:error: method to open the application.
If you want to open a specific file in that application (like the file from which you got the MIME type), you could use something like -[NSWorkspace openFile:withApplication:].
If you can't get the MIME type (despite what you say in your question), there are a bunch of similar LaunchServices functions. You can read all about them here.
Rather than even bothering to try to find the application you can use LSOpenItemsWithRole.
//Opens items specified as an array of values of type FSRef with a specified role.
OSStatus LSOpenItemsWithRole (
const FSRef *inItems,
CFIndex inItemCount,
LSRolesMask inRole,
const AEKeyDesc *inAEParam,
const LSApplicationParameters *inAppParams,
ProcessSerialNumber *outPSNs,
CFIndex inMaxPSNCount
);