RTF to HTML conversion using Cocoa - cocoa

I am a QT programmer and Cocoa is new for me. How can convert Rich Text Format(RTF text with images and hyper link) to HTML using cocoa under Mac OS X. I have rich text into a char type buffer.

Create an NSAttributedString instance with the RTF data. Walk the attribute ranges of the string (this includes the attachments / pictures) and translate to HTML (append the proper HTML to an NSMutableString) as you go. This lets you convert any attributes you'd like while leaving behind those you don't want.
A helpful NSAttributedString method would be -enumerateAttributesInRange:options:usingBlock:. Inside the block you can determine whether you want to handle or ignore a given attribute. See the Handling Attachments section for information regarding dealing with your images (considered an attachment in RTFD, just another attribute type for a "character" in the attributed string).

You can perform the conversion of RTF to HTML using NSAttributedString and the additions to it provided by Application Kit - read NSAttributedString Application Kit Additions Reference.
The are methods to create an NSAttributedString from RTF, such as initWithRTF:documentAttributes: and initWithURL:documentAttributes:.
To create HTML you can use dataFromRange:documentAttributes:error: specifying appropriate attributes, you'll need to specify at least NSHTMLTextDocumentType.

Depends really on your output requirements… i could convert the string "hello world" to valid html, but it might not be what you were expecting… anyway as an alternative approach to #Joshua's…
The textutil utility can be useful for all sorts of conversions. You can use it from Cocoa via NSTask
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/textutil"];
[task setArguments: #[#"-format", #"rtf", #"-convert", #"html", #"-stdin", #"-stdout"]];
[task setStandardInput:[NSPipe pipe]];
[task setStandardOutput:[NSPipe pipe]];
NSFileHandle *taskInput = [[task standardInput] fileHandleForWriting];
[taskInput writeData:[NSData dataWithBytes:cString length:cStringLength]];
[task launch];
[taskInput closeFile];
Synchronously
NSData *outData = [[[task standardOutput] fileHandleForReading] readDataToEndOfFile];
NSString *outStr = [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
or async
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(readCompleted:) name:NSFileHandleReadToEndOfFileCompletionNotification object:[[task standardOutput] fileHandleForReading]];
[[[task standardOutput] fileHandleForReading] readToEndOfFileInBackgroundAndNotify];
- (void)readCompleted:(NSNotification *)notification {
NSData *outData = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
NSString *outStr = [[NSString alloc] initWithData:outData encoding:NSUTF8StringEncoding];
NSLog(#"Read data: %#", outStr);
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleReadToEndOfFileCompletionNotification object:[notification object]];
}
Please don't just copy and paste this though.. it's just an example, not tested production code.

Related

Using NSPipe with NSTask to simulate command line input

I'm writing a very simple GUI tool for a command line app. It only has 2 buttons. Connect and Quit.
In applicationDidFinishLaunching I run the following
NSPipe *pipe = [[NSPipe alloc] init];
writer = [pipe fileHandleForWriting];
NSTask *runTask = [[[NSTask alloc] init] autorelease];
NSString *exefile = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"vpngui"];
[runTask setLaunchPath: exefile];
NSString *exeDir = [[NSBundle mainBundle] resourcePath];
NSArray *pargs;
pargs = [NSArray arrayWithObjects: exeDir, nil];
[runTask setArguments: pargs];
[runTask setStandardInput:pipe];
[runTask launch];
Then when the Connect button is clicked the following line of code is run
writer writeData:[#"Connect" dataUsingEncoding:NSUTF8StringEncoding]];
and for the Quit button
writer writeData:[#"Quit" dataUsingEncoding:NSUTF8StringEncoding]];
Somehow the command line app never gets the Connect and Quit commands
The data is probably buffered.
Try forcing to flush the file handle by calling the synchronizeFile: method:
synchronizeFile
Causes all in-memory data and attributes of the file represented by the receiver to be written to permanent storage.
I meet the same problem and
closeFile: works for me.
closeFile: to indicate that you have finished writing.
But next time you want to write you'd have to create a new fileHandle instance because the old one has been closed.

Mac OS X: Execute scripts with hooks from an application

Im building an cocoa app that monitors something™ and I am planning to have some hooks for users. So I want to enable the user to put a script (Bash, Ruby, Python you name it) with a specified name (let's say after_event) into the Application Support directory and that script gets executed after a certain event in my code. Ideally I could pass some variables to the script so the script knows what happened.
Any ideas on this?
So problem one is: How do I get the path of the Application Support "the SDK way"? problem two is: How do I execute script with variables like THAT_APPEND="foo"?
Thanks,
Philip
Because sharing is caring here is the method that executes the scripts:
-(void) runScript:(NSString*)scriptName withVariables:(NSDictionary *)variables
{
NSString *appSupportPath = [NSFileManager defaultManager] applicationSupportDirectory];
NSArray *arguments;
NSString* newpath = [NSString stringWithFormat:#"%#/%#",appSupportPath, scriptName];
if ([[NSFileManager defaultManager]fileExistsAtPath:newpath]){
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: newpath];
NSLog(#"Executing hook: %#",newpath);
arguments = [NSArray arrayWithObjects:newpath, nil];
[task setArguments: arguments];
[task setEnvironment:variables];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (#"script returned:\n%#", string);
}
}
}
UPDATE: I updated the code to be more generic. Now NSTask will tell the kernel to execute the script directly so your user can not online use Bash scripts but also python, perl, php whatever she likes. The only thing she needs to use is a Shebang in that file.
The NSFileManager Category can be found here.
Look for NSTask documentation. There's an environment member you can manipulate. Also adding command line parameters in a form -name = value should be trivial.

How to gradually retrieve data from NSTask?

I am working on a GUI (Cocoa) for a command-line tool to make it more accessible to people. Despite it being a GUI, I would like to display the output to an NSTextView. The problem is that the output is large and the analysis the tool carries out can take hours/days.
Normally, when working with NSTask and NSPipe, the output is displayed only after the task is completely finished (which can take a long time). What I want to do is split the output up and display it gradually (updating every minute for example).
So far I have placed the processing of the data in a separate thread:
[NSThread detachNewThreadSelector:#selector(processData:) toTarget:self withObject:raxmlHandle];
- (void)processData:(id)sender {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *startString = [results string];
NSString *newString = [[NSString alloc] initWithData:[raxmlHandle readDataToEndOfFile] encoding:NSASCIIStringEncoding];
[results setString:[startString stringByAppendingString:newString]];
[startString release];
[newString release];
[pool release];
}
All this is still a bit of voodoo to me and I am not exactly sure how to deal with this challenge.
Do you have any suggestions or recommendations?
Thanks!
You need to use a notification provided by NSFileHandle.
First, add yourself as an observer to the NSFileHandleReadCompletionNotification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(outputReceived:)
name:NSFileHandleReadCompletionNotification
object:nil];
Then, prepare a task, call readInBackgrounAndNotify of the file handle of the pipe, and launch the task.
NSTask*task=[[NSTask alloc] init];
[task setLaunchPath:...];
NSPipe*pipe=[NSPipe pipe];
[task setStandardOutput:pipe];
[task setStandardError:pipe];
// this causes the notification to be fired when the data is available
[[pipe fileHandleForReading] readInBackgroundAndNotify];
[task launch];
Now, to actually receive the data, you need to define a method
-(void)outputReceived:(NSNotification*)notification{
NSFileHandle*fh=[notification object];
// it might be good to check that this file handle is the one you want to read
...
NSData*d=[[aNotification userInfo] objectForKey:#"NSFileHandleNotificationDataItem"];
... do something with data ...
}
You might want to read Notification Programming Topics to understand what is going on.

Saving System Profiler info in a .spx file using NSTask

In Cocoa, I am trying to implement a button, which when the user clicks on will capture the System profiler report and paste it on the Desktop.
Code
NSTask *taskDebug;
NSPipe *pipeDebug;
taskDebug = [[NSTask alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:selfselector:#selector(taskFinished:) name:NSTaskDidTerminateNotification object:taskDebug];
[profilerButton setTitle:#"Please Wait"];
[profilerButton setEnabled:NO];
[taskDebug setLaunchPath: #"/usr/sbin/system_profiler"];
NSArray *args = [NSArray arrayWithObjects:#"-xml",#"-detailLevel",#"full",#">", #"
~/Desktop/Profiler.spx",nil];
[taskDebug setArguments:args];
[taskDebug launch];
But this does not save the file to the Desktop. Having
NSArray *args = [NSArray arrayWithObjects:#"-xml",#"-detailLevel",#"full",nil]
works and it drops the whole sys profiler output in the Console Window.
Any tips on why this does not work or how to better implement this ? I am trying to refrain from using a shell script or APpleScript to get the system profiler. If nothing work's that would be my final option.
Thanks in advance.
NSArray *args = [NSArray arrayWithObjects:#"-xml",#"-detailLevel",#"full",#">", #"~/Desktop/Profiler.spx",nil];
That won't work because you aren't going through the shell, and > is a shell operator. (Also, ~ isn't special except when you expand it using stringByExpandingTildeInPath.)
Create an NSFileHandle for writing to that Profiler.spx file, making sure to use the full absolute path, not the tilde-abbreviated path. Then, set that NSFileHandle as the task's standard output. This is essentially what the shell does when you use a > operator in it.
This got it done ( thanks to Peter and Costique)
[taskDebug setLaunchPath: #"/usr/sbin/system_profiler"];
NSArray *args = [NSArray arrayWithObjects:#"-xml",#"- detailLevel",#"full",nil];
[taskDebug setArguments:args];
[[NSFileManager defaultManager] createFileAtPath: [pathToFile stringByExpandingTildeInPath] contents: nil attributes: nil];
outFile = [ NSFileHandle fileHandleForWritingAtPath:[pathToFile stringByExpandingTildeInPath]];
[taskDebug setStandardOutput:outFile];
[taskDebug launch];
Create an NSPipe, send [taskDebug setStandardOutput: myPipe] and read from the pipe's file handle.

Sending an E-Mail with attachments in Cocoa

I have an NSTextView with text & images in it, which is supposed to send both in an e-mail.I know that the message.framework is deprecated,so I came up with the idea to send it via NSTask, since mail is integrated.I came up with the code below, however in the log I get this:
*** -[NSCFDictionary setObject:forKey:]: attempt to insert
nil value (key:
_NSTaskInputFileHandle)
This is the code I am using:
NSError *error;
if([textView writeRTFDToFile:#"/Library/Application Support/log.rtfd" atomically:NO])
{
NSArray *args = [NSArray arrayWithObjects:#"-s", [subject stringValue], [sendto stringValue], nil];
NSTask *task = [[[NSTask alloc] init] autorelease];
[task setLaunchPath:#"/usr/bin/mailx"];
[task setArguments:args];
[task setStandardInput:[NSFileHandle fileHandleForReadingAtPath:#"/Library/Application Support/log.rtfd"]];
[task launch];
[task waitUntilExit];
Can someone tell me what I am doing wrong?
You can also try the Scripting Bridge. See Apple's SBSendEmail example.

Resources