I have an OSX application that creates Objective-C code. In other words, writes to two files, .h and .m.
To write to a file, I am using NSString writeToFile atomically true, encoding NSUTF8StringEncoding.
Although my two files are created with the text correctly, all formatting is lost. Everything ends up aligned to the left, even when imported into Xcode. Although I know I can select the text and indent it using ctrl + i, this is not the solution I want.
Now, when I copy the NSString to the clipboard, and paste it into Xcode, the formatting stays how it should.
Does anyone know a way where I can keep the text indentation the way it was in Xcode without having to write rules about how many curly braces are open? Here is some modified example code of what I am doing so far:
NSString *code = #"for (int i = 0; i < 10; i++) {\n";
code = [code stringByAppendingString:#"NSLog(#\"HelloWorld\");\n"];
code = [code stringByAppendingString:#"}\n"];
// If I run the two lines below, then my NSString is copied to the clipboard. When I paste into Xcode, formatting stays as it should.
[[NSPasteboard generalPasteboard] clearContents];
[[NSPasteboard generalPasteboard] setString:code forType:NSStringPboardType];
// If I run the code below instead, then two files are created. But they do not keep the formatting.
NSSavePanel *panel = [NSSavePanel savePanel];
[panel setNameFieldStringValue:#"MyClass"];
[panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
NSError *errorH = nil;
NSError *errorM = nil;
NSString *pathH = [[[panel URL] path] stringByAppendingString:#".h"];
NSString *pathM = [[[panel URL] path] stringByAppendingString:#".m"];
[code writeToFile:pathH atomically:true encoding:NSUTF8StringEncoding error:&errorH];
[code writeToFile:pathM atomically:true encoding:NSUTF8StringEncoding error:&errorM];
if (!errorH && !errorM) {
NSLog(#"Success");
} else {
NSLog(#"Error Saving Files");
}
}
}];
EDIT: After not getting a clear way to do this elegantly, I simply had to write some of my own formatting code to mimic the formatting done by Xcode. It doesn't work 100% properly... for instance, when I am adding multiple items to an array using:
#[#"string1",
#"string2",
#"string3"];\n
This doesn't stay properly indented to where the array began. And I'm sure there are other cases that my code doesn't handle. Not a huge deal though. If a solution comes to mind later, I can implement it. But for now, the formatting simply won't be as pretty as intended. Here is a method, and a helper method, that anyone can use to implement similar functionality (handles tabs and curly braces only)
- (NSString *) formatWithIdentationForExport : (NSString *) theString {
NSString *s = #"";
NSMutableArray *fileLines = [[NSMutableArray alloc] initWithArray:[theString componentsSeparatedByString:#"\n"] copyItems: YES];
int numberOfCurlyBraces = 0;
for (int i = 0; i < fileLines.count; i++) {
NSString *currentLine = fileLines[i];
int numberOfOpenBracesInLine = [self getNumberOfOccurancesOf:#"{" inString:currentLine];
int numberOfCloseBracesInLine = [self getNumberOfOccurancesOf:#"}" inString:currentLine];
numberOfCurlyBraces -= numberOfCloseBracesInLine;
for (int j = 0; j < numberOfCurlyBraces; j++) {
currentLine = [NSString stringWithFormat:#"\t%#", currentLine];
}
currentLine = [currentLine stringByAppendingString:#"\n"];
s = [s stringByAppendingString:currentLine];
numberOfCurlyBraces += numberOfOpenBracesInLine;
}
return s;
}
- (int) getNumberOfOccurancesOf : (NSString *) substring inString : (NSString *) str {
int count = 0, length = (int)[str length];
NSRange range = NSMakeRange(0, length);
while(range.location != NSNotFound) {
range = [str rangeOfString:substring options:0 range:range];
if(range.location != NSNotFound) {
range = NSMakeRange(range.location + range.length, length - (range.location + range.length));
count++;
}
}
return count;
}
Use "\t" for an indent. I won't do it for every line of your code, but do something like
[[NSMutableString alloc] initWithString:#"for (int i = 0; i < 10; i++) {\n"];
[codeStr appendString:#"\t"];[codeStr appendString:#"NSLog(#\"center\");"];
When you generate your ObjC code, you should manually add indentation (tabs or spaces) into it, otherwise it will not be saved to file.
Related
Is it possible to activate (bring to the fore) a window based on the values returned from CGWindowListCopyWindowInfo? (i.e Using the window ID (kCGWindowNumber) or something else.)
Edit:
I should specify that my app (which would run with accessibility permissions) needs to be able to do this for windows of other apps.
Since posting the question I've discovered AXUIElementPerformAction. Am I going in the right direction with this?
Or is running AppleScript bridge within my code the best approach?
You can attach to a process by pid and get its windows. Then use kAXRaiseAction to bring them to front, like this:
AXUIElementRef element = AXUIElementCreateApplication(pid);
if (element) {
CFArrayRef array;
AXUIElementCopyAttributeValues(element, kAXWindowsAttribute, 0, 99999, &array);
if (array == nullptr)
return;
NSArray *windows = (NSArray *)CFBridgingRelease(array);
for (NSUInteger i = 0; i < windows.count; ++i) {
AXUIElementRef ref = (__bridge AXUIElementRef)(windows[i]);
AXError error = AXUIElementPerformAction(ref, kAXRaiseAction);
// handle error
}
}
CFRelease(element);
No need to release array or windows. Children in arrays are handled automatically and the array is bridged to an NSArray which is released by ARC.
My answer's a little overcomplicated compared to what was already shared by Mike Lischke, but I've already posted it on a different SO question and I think it is a tiny bit closer to what you need:
#import <Cocoa/Cocoa.h>
#import <libproc.h>
#import <string.h>
#import <stdlib.h>
#import <stdio.h>
bool activate_window_of_id(unsigned long wid) {
bool success = false;
const CGWindowLevel kScreensaverWindowLevel = CGWindowLevelForKey(kCGScreenSaverWindowLevelKey);
CFArrayRef windowArray = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
CFIndex windowCount = 0;
if ((windowCount = CFArrayGetCount(windowArray))) {
for (CFIndex i = 0; i < windowCount; i++) {
NSDictionary *windowInfoDictionary = (__bridge NSDictionary *)((CFDictionaryRef)CFArrayGetValueAtIndex(windowArray, i));
NSNumber *ownerPID = (NSNumber *)(windowInfoDictionary[(id)kCGWindowOwnerPID]);
NSNumber *level = (NSNumber *)(windowInfoDictionary[(id)kCGWindowLayer]);
if (level.integerValue < kScreensaverWindowLevel) {
NSNumber *windowID = windowInfoDictionary[(id)kCGWindowNumber];
if (wid == windowID.integerValue) {
CFIndex appCount = [[[NSWorkspace sharedWorkspace] runningApplications] count];
for (CFIndex j = 0; j < appCount; j++) {
if (ownerPID.integerValue == [[[[NSWorkspace sharedWorkspace] runningApplications] objectAtIndex:j] processIdentifier]) {
NSRunningApplication *appWithPID = [[[NSWorkspace sharedWorkspace] runningApplications] objectAtIndex:j];
[appWithPID activateWithOptions:NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps];
char buf[PROC_PIDPATHINFO_MAXSIZE];
proc_pidpath(ownerPID.integerValue, buf, sizeof(buf));
NSString *buffer = [NSString stringWithUTF8String:buf];
unsigned long location = [buffer rangeOfString:#".app/Contents/MacOS/" options:NSBackwardsSearch].location;
NSString *path = (location != NSNotFound) ? [buffer substringWithRange:NSMakeRange(0, location)] : buffer;
NSString *app = [#" of application \\\"" stringByAppendingString:[path lastPathComponent]];
NSString *index = [#"set index of window id " stringByAppendingString:[windowID stringValue]];
NSString *execScript = [[index stringByAppendingString:app] stringByAppendingString:#"\\\" to 1"];
char *pointer = NULL;
size_t buffer_size = 0;
NSMutableArray *array = [[NSMutableArray alloc] init];
FILE *file = popen([[[#"osascript -e \"" stringByAppendingString:execScript] stringByAppendingString:#"\" 2>&1"] UTF8String], "r");
while (getline(&pointer, &buffer_size, file) != -1)
[array addObject:[NSString stringWithUTF8String:pointer]];
char *error = (char *)[[array componentsJoinedByString:#""] UTF8String];
if (strlen(error) > 0 && error[strlen(error) - 1] == '\n')
error[strlen(error) - 1] = '\0';
if ([[NSString stringWithUTF8String:error] isEqualToString:#""])
success = true;
[array release];
free(pointer);
pclose(file);
break;
}
}
}
}
}
}
CFRelease(windowArray);
return success;
}
The code it is based on does not work as advertised for its original purpose. Although, it did help me a lot to get working all the stuff I needed to answer this question. The code my answer is based on can be found here.
I am working on an ARC based project. I am obtaining text from a large text file and need to remove white spaces or newline characters from it. The following code works fine on the simulator but crashes on an iPad and doesn't run completely (this may be a memory issues). For example if the loop needs to be run 2000 times, it crashes after running 1800 times on iPad.
- (BOOL)formatTheText {
NSString *content = [NSString stringWithContentsOfURL:_textFileURL
encoding:NSUTF8StringEncoding
error:NULL];
NSRange paraRange = {0,1};
NSString *modifiedContent = #"";
BOOL previousLineWasEmpty = NO;
int lineNumber = 0;
while (paraRange.location < [content length]) {
NSRange currentParaRange = [content paragraphRangeForRange:paraRange];
NSString *paragraph = [content substringWithRange:currentParaRange];
NSCharacterSet *newLineSet = [NSCharacterSet newlineCharacterSet];
NSArray *array = [paragraph componentsSeparatedByCharactersInSet:newLineSet];
NSString *currentParagraph = #"";
for (NSString *line in array) {
currentParagraph = [currentParagraph stringByAppendingString:line];
}
// Add a space when combining two lines
modifiedContent = [modifiedContent stringByAppendingFormat:#"%# ",currentParagraph];
paraRange.location += currentParaRange.length;
if ([currentParagraph length] == 0) {
// If previous line was empty just add a new line character
if (previousLineWasEmpty) {
modifiedContent = [modifiedContent stringByAppendingString:#"\n"];
} else {
// Add two lines for the start of a new paragraph
modifiedContent = [modifiedContent stringByAppendingString:#"\n\n"];
}
previousLineWasEmpty = YES;
} else {
previousLineWasEmpty = NO;
}
lineNumber++;
}
self.cleanedString = modifiedContent;
return YES;
}
I am making an iPad app for personal use and I m struggling with some character replacement in some strings. For example I got an NSString which contains "\t\t\t C D". Now what I want to do is replace every C and every D there is in there with C# and D#. I have managed to do that but unfortunately it doesn't look efficient at all to me.
Here is my code so far:
- (IBAction)buttonPressed:(id)sender
{
if(sender)
{
NSError *error;
NSString *newTab = [[NSString alloc] init];
NSRegularExpression *regexC = [NSRegularExpression regularExpressionWithPattern:#"C" options:0 error:&error];
NSRegularExpression *regexD = [NSRegularExpression regularExpressionWithPattern:#"D" options:0 error:&error];
newTab = [regexC stringByReplacingMatchesInString:self.tab options:0 range:NSMakeRange(0, self.tab.length) withTemplate:#"C#"];
NSString *newTabAfterFirstRegex = [[NSString alloc] initWithString:newTab];
newTabAfterFirstRegex = [regexD stringByReplacingMatchesInString:newTab options:0 range:NSMakeRange(0, newTab.length) withTemplate:#"D#"];
NSLog(#"%#",newTabAfterFirstRegex);
}
}
Plus this is just a small tester code. What I would really like to do is to have an algorithm that checks for instances of all music tabs (C C# D D# E F F# G G# A A# B) in a given string and when the IBAction is triggered I would like each one of them to be replaced by the next one (and B becomes C).
Any ideas would be very much appreciated!
Thank you very much!
You can set a regular expression (e.g. '[A-G]#?') to match certain strings. With method -matchesInString:options:range: you can loop through all the matches (it will give back a range for each match) and use that range to do the replacements.
Regular expressions seem a bit like overkill for this, you could just do two string replacements, so that you don't get all the overhead from regexes, using
- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target
withString:(NSString *)replacement
and just replace it twice. Also, you don't need to do the NSString allocations, because it creates a reference in the return.
I created the following methods for encryption the other day. I've tested it for your purpose, and it seems to work.
-(NSString *)ReplaceMe:(NSString *)s {
// Putting the source into an array
NSMutableArray *myArray = [[NSMutableArray alloc] init];
int i;
for (i = 0; i < s.length; i++) {
[myArray addObject: [self Mid:s :i :1]];
}
// Creating a string with the revised array
NSMutableString *myString = [NSMutableString new];
for (i = 0; i < s.length; i++) {
[myString appendString:[self Conversion:[myArray objectAtIndex:i]]];
}
// Final
return myString;
}
The method above requires two additional functions.
-(NSString *)Mid:(NSString *)str:(NSInteger)s:(NSInteger)l {
if ((s <= str.length-1) && (s + l <= str.length) && (s >= 0) && (l >= 1)) {
return [str substringWithRange:NSMakeRange(s, l)];
}
else {
return #"";
}
}
The other is...
-(NSString *)Conversion:(NSString *)s {
if ([s isEqualToString:#"C"]) {
return #"C#";
}
else if ([s isEqualToString:#"D"]) {
return #"D#";
}
else {
return s;
}
}
You can put other conversion pairs in the function above. The following is an example as to how to use ReplaceMe.
- (IBAction)clickAction:(id)sender {
textField2.text = [self ReplaceMe:textField1.text];
}
So it's ReplaceMe is quite easy to use.
The stringValue of some elements from an XML files contain BOM characters in them. The xml file is marked as UTF-8 encoding.
Some of those characters are at the beginning of the string (as it should be from what I read about it) but some are in the middle of the string (malformed string from whoever wrote the xml file maybe?).
I'm opening the file with:
NSURL *furl = [NSURL fileURLWithPath:fileName];
if (!furl) {
NSLog(#"Error: Can't open NML file '%#'.", fileName);
return kNxADbReaderTTError;
}
NSError *err=nil;
NSXMLDocument *xmlDoc = [[NSXMLDocument alloc] initWithContentsOfURL:furl options:NSXMLNodeOptionsNone error:&err];
And I query the element this way:
NSXMLElement *anElement;
NSString *name;
...
NSString *valueString = [[anElement attributeForName:name] stringValue];
My questions are:
Am I opening the file wrong? Is the file malformed? Am I querying the string value of the element wrong? How can I filter those characters out?
While fixing another issue, I found a relatively clean way of filtering out unwanted characters from the source of an NSXMLDocument. Pasting it here just in case someone encounters a similar issue:
#implementation NSXMLDocument (FilterIllegalCharacters)
- (NSXMLDocument *)initWithDataAndIgnoreIllegalCharacters:(NSData *)data illegalChars:(NSCharacterSet *)illegalChars error:(NSError **)error{
// -- Then, read the resulting XML string.
NSMutableString *str = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// -- Go through the XML, only caring about attribute value strings
NSMutableArray *charactersToRemove = [NSMutableArray array];
NSUInteger openQuotes = NSNotFound;
for (NSUInteger pos = 0; pos < str.length; ++pos) {
NSUInteger currentChar = [str characterAtIndex:pos];
if (currentChar == '\"') {
if (openQuotes == NSNotFound) {
openQuotes = pos;
}
else {
openQuotes = NSNotFound;
}
}
else if (openQuotes != NSNotFound) {
// -- If we find an illegal character, we make a note of its position.
if ([illegalChars characterIsMember:currentChar]) {
[charactersToRemove addObject:[NSNumber numberWithLong:pos]];
}
}
}
if (charactersToRemove.count) {
NSUInteger index = charactersToRemove.count;
// -- If we have characters to fix, we work thru them backwards, in order to not mess up our saved positions by modifying the XML.
do {
--index;
NSNumber *characterPos = charactersToRemove[index];
[str replaceCharactersInRange:NSMakeRange(characterPos.longValue, 1) withString:#""];
}
while (index > 0);
// -- Finally we update the data with our corrected version
data = [str dataUsingEncoding:NSUTF8StringEncoding];
}
return [[NSXMLDocument alloc] initWithData:data options:NSXMLNodeOptionsNone
error:error];
}
#end
You can pass any character set you want. Note that this sets the options for reading the XML document to none. You might want to change this for your own purposes.
This only filters the content of attributes strings, which is where my malformed string came from.
How can I optimise out this nested for loop?
The program should go through each word in the array created from the word text file, and if it's greater than 8 characters, add it to the goodWords array. But the caveat is that I only want the root word to be in the goodWords array, for example:
If greet is added to the array, I don't want greets or greetings or greeters, etc.
NSString *string = [NSString stringWithContentsOfFile:#"/Users/james/dev/WordParser/word.txt" encoding:NSUTF8StringEncoding error:NULL];
NSArray *words = [string componentsSeparatedByString:#"\r\n"];
NSMutableArray *goodWords = [NSMutableArray array];
BOOL shouldAddToGoodWords = YES;
for (NSString *word in words)
{
NSLog(#"Word: %#", word);
if ([word length] > 8)
{
NSLog(#"Word is greater than 8");
for (NSString *existingWord in [goodWords reverseObjectEnumerator])
{
NSLog(#"Existing Word: %#", existingWord);
if ([word rangeOfString:existingWord].location != NSNotFound)
{
NSLog(#"Not adding...");
shouldAddToGoodWords = NO;
break;
}
}
if (shouldAddToGoodWords)
{
NSLog(#"Adding word: %#", word);
[goodWords addObject:word];
}
}
shouldAddToGoodWords = YES;
}
How about something like this?
//load the words from wherever
NSString * allWords = [NSString stringWithContentsOfFile:#"/usr/share/dict/words"];
//create a mutable array of the words
NSMutableArray * words = [[allWords componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] mutableCopy];
//remove any words that are shorter than 8 characters
[words filterUsingPredicate:[NSPredicate predicateWithFormat:#"length >= 8"]];
//sort the words in ascending order
[words sortUsingSelector:#selector(caseInsensitiveCompare:)];
//create a set of indexes (these will be the non-root words)
NSMutableIndexSet * badIndexes = [NSMutableIndexSet indexSet];
//remember our current root word
NSString * currentRoot = nil;
NSUInteger count = [words count];
//loop through the words
for (NSUInteger i = 0; i < count; ++i) {
NSString * word = [words objectAtIndex:i];
if (currentRoot == nil) {
//base case
currentRoot = word;
} else if ([word hasPrefix:currentRoot]) {
//word is a non-root word. remember this index to remove it later
[badIndexes addIndex:i];
} else {
//no match. this word is our new root
currentRoot = word;
}
}
//remove the non-root words
[words removeObjectsAtIndexes:badIndexes];
NSLog(#"%#", words);
[words release];
This runs very very quickly on my machine (2.8GHz MBP).
A Trie seems suitable for your purpose. It is like a hash, and is useful for detecting if a given string is a prefix of an already seen string.
I used an NSSet to ensure that you only have 1 copy of a word added at a time. It will add a word if the NSSet does not already contain it. It then checks to see if the new word is a substring for any word that has already been added, if true then it won't add the new word. It's case-insensitive as well.
What I've written is a refactoring of your code. It's probably not that much faster but you really do want a tree data structure if you want to make it a lot faster when you want to search for words that have already been added to your tree.
Take a look at RedBlack Trees or B-Trees.
Words.txt
objective
objectively
cappucin
cappucino
cappucine
programme
programmer
programmatic
programmatically
Source Code
- (void)addRootWords {
NSString *textFile = [[NSBundle mainBundle] pathForResource:#"words" ofType:#"txt"];
NSString *string = [NSString stringWithContentsOfFile:textFile encoding:NSUTF8StringEncoding error:NULL];
NSArray *wordFile = [string componentsSeparatedByString:#"\n"];
NSMutableSet *goodWords = [[NSMutableSet alloc] init];
for (NSString *newWord in wordFile)
{
NSLog(#"Word: %#", newWord);
if ([newWord length] > 8)
{
NSLog(#"Word '%#' contains 8 or more characters", newWord);
BOOL shouldAddWord = NO;
if ( [goodWords containsObject:newWord] == NO) {
shouldAddWord = YES;
}
for (NSString *existingWord in goodWords)
{
NSRange textRange = [[newWord lowercaseString] rangeOfString:[existingWord lowercaseString]];
if( textRange.location != NSNotFound ) {
// newWord contains the a substring of existingWord
shouldAddWord = NO;
break;
}
NSLog(#"(word:%#) does not contain (substring:%#)", newWord, existingWord);
shouldAddWord = YES;
}
if (shouldAddWord) {
NSLog(#"Adding word: %#", newWord);
[goodWords addObject:newWord];
}
}
}
NSLog(#"***Added words***");
int count = 1;
for (NSString *word in goodWords) {
NSLog(#"%d: %#", count, word);
count++;
}
[goodWords release];
}
Output:
***Added words***
1: cappucino
2: programme
3: objective
4: programmatic
5: cappucine