I have an XCode project, with the XIB interface files built using Interface Builder. I'm building localized XIB files by using ibtool to extract strings, translating them, and using ibtool again to build localized XIB files.
However, doing this means I have to translate all items in the application menus, including those that are completely standard (File, Save, Open, Minimize, etc.). Is there a way to avoid that?
i've developed a solution to this problem.
https://www.corecode.io/index_opensource.html
look for "Translator", it will translate your MainMenu.strings file into a dozen languages with the standard Apple translations for the standard menu item strings.
if you find some strings or languages missing that Aapple has included in their base apps, please send a patch over.
So, apparently no way around this.
I have been looking for a similar solution for a while and I found this resource
http://www.bdunagan.com/2009/03/15/ibtool-localization-made-easy/
It quotes toward the end of the article:
ibtool will look through MainMenu.xib for every user-visible string and insert that string with an associated ObjectID into MainMenu.strings, essentially a dictionary of strings keyed by ObjectID. Even better, the tool sorts them by ObjectID, so versioned .strings files are nicely diff’able. I can easily see what strings are added, removed, or just changed. Let me repeat this because it’s so incredibly handy: .strings files diff well! Of course, these .strings files are unicode, so they are not grep’able. Below is an example of the output for a string:
Go ahead and take a look I really hope it helps you as much as it helped me!
Translator by https://github.com/core-code/MiscApps/blob/master/Translator/Translator/MainMenuTranslations.plist
is cool but if you do not want to deal with 30 MainMenu.string files in your build (I personally don't) - you can just add MainMenuTranslations.plist to your resources (230KB uncompressed is tiny) and do it on the fly like this:
- (void) processMenu: (NSString*) app {
NSDictionary* d = [self loadMenuTranslations: app];
NSMenu* mm = NSApplication.sharedApplication.mainMenu;
for (int i = 0; i < mm.numberOfItems; i++) {
NSMenuItem* mi = [mm itemAtIndex: i];
mi.title = [self translateMenu: mi.title withDictionary: d];
NSMenu* sm = [[mm itemAtIndex: i] submenu];
sm.title = [self translateMenu: sm.title withDictionary: d];
for (int j = 0; j < sm.numberOfItems; j++) {
NSMenuItem* mi = [sm itemAtIndex: j];
mi.title = [self translateMenu: mi.title withDictionary: d];
}
}
}
- (NSString*) translateMenu: (NSString*) key withDictionary: (NSDictionary*) dictionary {
for (NSString* lang in dictionary) {
NSDictionary* translation = dictionary[lang];
NSString* t = translation[key];
if (t != null) {
return t;
}
}
return key;
}
- (NSDictionary*) loadMenuTranslations: (NSString*) app {
NSArray* langs = [NSUserDefaults.standardUserDefaults objectForKey: #"AppleLanguages"];
NSURL* url = [NSBundle.mainBundle URLForResource:#"MainMenuTranslations.plist" withExtension: null];
NSMutableDictionary* r = NSMutableDictionary.new;
NSDictionary* translations = [NSDictionary dictionaryWithContentsOfURL: url];
for (NSString* lang in langs) {
NSString* locale = [NSString stringWithFormat:#"%#.lproj", lang];
NSDictionary* translation = translations[locale];
NSMutableDictionary* d = [NSMutableDictionary.alloc initWithCapacity: translations.count * 3 / 2];
for (NSString* k in translation) {
NSString* v = translation[k];
NSString* key = k;
if ([k indexOf: #"APPLICATIONNAME"] >= 0) {
key = [k stringByReplacingOccurrencesOfString: #"APPLICATIONNAME" withString: app];
}
if ([v indexOf: #"APPLICATIONNAME"] >= 0) {
v = [v stringByReplacingOccurrencesOfString: #"APPLICATIONNAME" withString: app];
}
d[key] = v;
}
if (d.count > 0) {
r[lang] = d;
}
}
return r;
}
just call it from
- (void) applicationDidFinishLaunching: (NSNotification*) n {
// ...
[self processMenu: #"<your app name>"];
}
I wish there is a UniversalTranslation.plist somewhere (which could be probably collected automatically via creative use of translate.google.com)
Related
I'm trying to create a text field similar to Finder's file labels. I would like the last (second) line to be truncated in the middle.
I started with a multi-line NSTextField.
However, calling [self.cell setLineBreakMode:NSLineBreakByTruncatingMiddle]; results in a the text field showing only a single truncated line (no line breaks anymore).
Here is what it looks like in Finder:
If you want to wrap text like finder labels, using two labels doesn't do you any good since you need to know what the maximum breakable amount of text is on the first line. Plus, if you're building something that will display a lot of items two labels will overburden the GUI needlessly.
Set your NSTextField.cell like this:
[captionLabel.cell setLineBreakMode: NSLineBreakByCharWrapping];
Then find the code for "NS(Attributed)String+Geometrics" (Google it, it's out there). You must #import "NS(Attributed)String+Geometrics.h"
to measure text. It monkey patches NSString and NSAttributedString
I include the following code to wrap text exactly how Finder does in its captions. Using one label below the icon it assumes that, like Finder, there will be two lines of caption.
First this is how you will call the following code in your code:
NSString *caption = self.textInput.stringValue;
CGFloat w = self.captionLabel.bounds.size.width;
NSString *wrappedCaption = [self wrappedCaptionText:self.captionLabel.font caption:caption width:w];
self.captionLabel.stringValue = wrappedCaption ? [self middleTruncatedCaption:wrappedCaption withFont:self.captionLabel.font width:w] : caption;
Now for the main code:
#define SINGLE_LINE_HEIGHT 21
/*
This is the way finder captions work -
1) see if the string needs wrapping at all
2) if so find the maximum amount that will fit on the first line of the caption
3) See if there is a (word)break character somewhere between the maximum that would fit on the first line and the begining of the string
4) If there is a break character (working backwards) on the first line- insert a line break then return a string so that the truncation function can trunc the second line
*/
-(NSString *) wrappedCaptionText:(NSFont*) aFont caption:(NSString*)caption width:(CGFloat)captionWidth
{
NSString *wrappedCaption = nil;
//get the width for the text as if it was in a single line
CGFloat widthOfText = [caption widthForHeight:SINGLE_LINE_HEIGHT font:aFont];
//1) nothing to wrap
if ( widthOfText <= captionWidth )
return nil;
//2) find the maximum amount that fits on the first line
NSRange firstLineRange = [self getMaximumLengthOfFirstLineWithFont:aFont caption:caption width:captionWidth];
//3) find the first breakable character on the first line looking backwards
NSCharacterSet *notAlphaNums = [NSCharacterSet alphanumericCharacterSet].invertedSet;
NSCharacterSet *whites = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSRange range = [caption rangeOfCharacterFromSet:notAlphaNums options:NSBackwardsSearch range:firstLineRange];
NSUInteger splitPos;
if ( (range.length == 0) || (range.location < firstLineRange.length * 2 / 3) ) {
// no break found or break is too (less than two thirds) far to the start of the text
splitPos = firstLineRange.length;
} else {
splitPos = range.location+range.length;
}
//4) put a line break at the logical end of the first line
wrappedCaption = [NSString stringWithFormat:#"%#\n%#",
[[caption substringToIndex:splitPos] stringByTrimmingCharactersInSet:whites],
[[caption substringFromIndex:splitPos] stringByTrimmingCharactersInSet:whites]];
return wrappedCaption;
}
/*
Binary search is great..but when we split the caption in half, we dont have far to go usually
Depends on the average length of text you are trying to wrap filenames are not usually that long
compared to the captions that hold them...
*/
-(NSRange) getMaximumLengthOfFirstLineWithFont:(NSFont *)aFont caption:(NSString*)caption width:(CGFloat)captionWidth
{
BOOL fits = NO;
NSString *firstLine = nil;
NSRange range;
range.length = caption.length /2;
range.location = 0;
NSUInteger lastFailedLength = caption.length;
NSUInteger lastSuccessLength = 0;
int testCount = 0;
NSUInteger initialLength = range.length;
NSUInteger actualDistance = 0;
while (!fits) {
firstLine = [caption substringWithRange:range];
fits = [firstLine widthForHeight:SINGLE_LINE_HEIGHT font:aFont] < captionWidth;
testCount++;
if ( !fits ) {
lastFailedLength = range.length;
range.length-= (lastFailedLength - lastSuccessLength) == 1? 1 : (lastFailedLength - lastSuccessLength)/2;
continue;
} else {
if ( range.length == lastFailedLength -1 ) {
actualDistance = range.length - initialLength;
#ifdef DEBUG
NSLog(#"# of tests:%d actualDistance:%lu iteration better? %#", testCount, (unsigned long)actualDistance, testCount > actualDistance ? #"YES" :#"NO");
#endif
break;
} else {
lastSuccessLength = range.length;
range.length += (lastFailedLength-range.length) / 2;
fits = NO;
continue;
}
}
}
return range;
}
-(NSString *)middleTruncatedCaption:(NSString*)aCaption withFont:(NSFont*)aFont width:(CGFloat)captionWidth
{
NSArray *components = [aCaption componentsSeparatedByString:#"\n"];
NSString *secondLine = [components objectAtIndex:1];
NSString *newCaption = aCaption;
CGFloat widthOfText = [secondLine widthForHeight:SINGLE_LINE_HEIGHT font:aFont];
if ( widthOfText > captionWidth ) {
//ignore the fact that the length might be an odd/even number "..." will always truncate at least one character
int middleChar = ((int)secondLine.length-1) / 2;
NSString *newSecondLine = nil;
NSString *leftSide = secondLine;
NSString *rightSide = secondLine;
for (int i=1; i <= middleChar; i++) {
leftSide = [secondLine substringToIndex:middleChar-i];
rightSide = [secondLine substringFromIndex:middleChar+i];
newSecondLine = [NSString stringWithFormat:#"%#…%#", leftSide, rightSide];
widthOfText = [newSecondLine widthForHeight:SINGLE_LINE_HEIGHT font:aFont];
if ( widthOfText <= captionWidth ) {
newCaption = [NSString stringWithFormat:#"%#\n%#", [components objectAtIndex:0], newSecondLine];
break;
}
}
}
return newCaption;
}
Cheers!
PS Tested in prototype works great probably has bugs...find them
I suspect there are two labels there. The top one contains the first 20 characters of a file name, and the second contains any overflow, truncated.
The length of the first label is probably restricted based on the user's font settings.
NOTE: I'm developing for Mac, not iOS!
- (void)recordTransaction:(SKPaymentTransaction *)transaction
{
if ([transaction.payment.productIdentifier isEqualToString:kInAppProIdentifier])
{
[[NSUserDefaults standardUserDefaults] setValue:transaction.transactionReceipt forKey:#"proUpgradeTransactionReceipt" ];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
I get this error from the compiler:
error: property 'transactionReceipt' not found on object of type 'SKPaymentTransaction *'; did you mean 'transactionDate'? [3]
Also, I can't find the transactionReceipt property in the reference for the SKPaymentTransaction class! (Even though this page contains some references to "receipts", there's not a transactionReceipt property).
But the documentation says it is supposed to exist!
A successful transaction includes a transactionIdentifier property and a transactionReceipt property that record the details of the processed payment. Your application is not required to do anything with this information. You may wish to record this information to establish an audit trail for the transaction. If your application uses a server to deliver content, the receipt can be sent to your server and validated by the App Store.
What is wrong with this?
The property is private and returns an empty string on OS X.
As stated in Apple's docs here:
https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/StoreKitGuide/VerifyingStoreReceipts/VerifyingStoreReceipts.html#//apple_ref/doc/uid/TP40008267-CH104-SW1
"On iOS, this is the value of the transaction's transactionReceipt
property. On OS X, this is the entire contents of the receipt file
inside the application bundle. Encode the receipt data using base64
encoding."
To get the receipt, use:
[NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]
An example of this working for both iOS and OS X to get the receipt and sending it to a server for verification (with a macro set for the OS X build):
NSData *tr ;
#ifdef OSX
tr = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]] ;
#else
tr = [transaction transactionReceipt];
#endif
NSString *jsonObjectString = [[self encode:(uint8_t *)[tr bytes] length:[tr length]] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *completeString = [NSString stringWithFormat:#"https://ssl.myserver.com/verify.php?%#", jsonObjectString];
NSURL *urlForValidation = [NSURL URLWithString:completeString];
NSMutableURLRequest *validationRequest = [[NSMutableURLRequest alloc] initWithURL:urlForValidation];
[validationRequest setHTTPMethod:#"GET"];
NSData *responseData = [NSURLConnection sendSynchronousRequest:validationRequest returningResponse:nil error:nil];
Be careful where verifying, the json object for OS X and iOS receipts are different so you'll likely need separate server side code for validation.
Update: Adding function to encode receipt for posting:
+ (NSString *)encode:(const uint8_t *)input length:(NSInteger)length {
static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
NSMutableData *data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t *output = (uint8_t *)data.mutableBytes;
for (NSInteger i = 0; i < length; i += 3) {
NSInteger value = 0;
for (NSInteger j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
NSInteger index = (i / 3) * 4;
output[index + 0] = table[(value >> 18) & 0x3F];
output[index + 1] = table[(value >> 12) & 0x3F];
output[index + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '=';
output[index + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '=';
}
return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
}
I class-dumped StoreKit to find out if this method was present and it is. Here is the header:
#interface SKPaymentTransaction : NSObject
{
id _internal;
}
- (id)init;
- (id)initWithDictionary:(id)arg1;
- (id)initWithPayment:(id)arg1;
- (void)dealloc;
#property(readonly) NSError *error;
#property(readonly) SKPaymentTransaction *originalTransaction;
#property(readonly) SKPayment *payment;
#property(readonly) NSDate *transactionDate;
#property(readonly) NSString *transactionIdentifier;
- (id)transactionReceipt;
#property(readonly) long long transactionState;
- (BOOL)canMergeWithTransaction:(id)arg1;
- (id)matchingIdentifier;
- (BOOL)mergeWithTransaction:(id)arg1;
- (id)_transactionIdentifier;
#end
You can use this method with [transaction transactionReceipt] (and get a warning). Apple may or may not consider this use of a private method and reject your application; but, for testing purposes, it is there.
According to Apple's official 10.7 documentation of SKPaymentTransaction, there is no transactionReceipt property.
When I call -description on an NSData object, I see a pretty Hex string of the NSData object's bytes like:
<f6e7cd28 0fc5b5d4 88f8394b af216506 bc1bba86 4d5b483d>
I'd like to get this representation of the data (minus the lt/gt quotes) into an in-memory NSString so I can work with it.. I'd prefer not to call -[NSData description] and then just trim the lt/gt quotes (because I assume that is not a guaranteed aspect of NSData's public interface and is subject change in the future).
What's the simplest way to get this representation of an NSData object into an NSString object (other than calling -description)?
Keep in mind that any String(format: ...) solution will be terribly slow (for large data)
NSData *data = ...;
NSUInteger capacity = data.length * 2;
NSMutableString *sbuf = [NSMutableString stringWithCapacity:capacity];
const unsigned char *buf = data.bytes;
NSInteger i;
for (i=0; i<data.length; ++i) {
[sbuf appendFormat:#"%02X", (NSUInteger)buf[i]];
}
If you need something more performant try this:
static inline char itoh(int i) {
if (i > 9) return 'A' + (i - 10);
return '0' + i;
}
NSString * NSDataToHex(NSData *data) {
NSUInteger i, len;
unsigned char *buf, *bytes;
len = data.length;
bytes = (unsigned char*)data.bytes;
buf = malloc(len*2);
for (i=0; i<len; i++) {
buf[i*2] = itoh((bytes[i] >> 4) & 0xF);
buf[i*2+1] = itoh(bytes[i] & 0xF);
}
return [[NSString alloc] initWithBytesNoCopy:buf
length:len*2
encoding:NSASCIIStringEncoding
freeWhenDone:YES];
}
Swift version
private extension Data {
var hexadecimalString: String {
let charA: UInt8 = 0x61
let char0: UInt8 = 0x30
func byteToChar(_ b: UInt8) -> Character {
Character(UnicodeScalar(b > 9 ? charA + b - 10 : char0 + b))
}
let hexChars = flatMap {[
byteToChar(($0 >> 4) & 0xF),
byteToChar($0 & 0xF)
]}
return String(hexChars)
}
}
I agree on the solution not to call description which is to be reserved for debugging, so good point and good question :)
The easiest solution is to loop thru the bytes of the NSData and construct the NSString from it. Use [yourData bytes] to access the bytes, and build the string into an NSMutableString.
Here is an example by implementing this using a category of NSData
#interface NSData(Hex)
-(NSString*)hexRepresentationWithSpaces_AS:(BOOL)spaces;
#end
#implementation NSData(Hex)
-(NSString*)hexRepresentationWithSpaces_AS:(BOOL)spaces
{
const unsigned char* bytes = (const unsigned char*)[self bytes];
NSUInteger nbBytes = [self length];
//If spaces is true, insert a space every this many input bytes (twice this many output characters).
static const NSUInteger spaceEveryThisManyBytes = 4UL;
//If spaces is true, insert a line-break instead of a space every this many spaces.
static const NSUInteger lineBreakEveryThisManySpaces = 4UL;
const NSUInteger lineBreakEveryThisManyBytes = spaceEveryThisManyBytes * lineBreakEveryThisManySpaces;
NSUInteger strLen = 2*nbBytes + (spaces ? nbBytes/spaceEveryThisManyBytes : 0);
NSMutableString* hex = [[NSMutableString alloc] initWithCapacity:strLen];
for(NSUInteger i=0; i<nbBytes; ) {
[hex appendFormat:#"%02X", bytes[i]];
//We need to increment here so that the every-n-bytes computations are right.
++i;
if (spaces) {
if (i % lineBreakEveryThisManyBytes == 0) [hex appendString:#"\n"];
else if (i % spaceEveryThisManyBytes == 0) [hex appendString:#" "];
}
}
return [hex autorelease];
}
#end
Usage:
NSData* data = ...
NSString* hex = [data hexRepresentationWithSpaces_AS:YES];
Just wanted to add that #PassKits's method can be written very elegantly using Swift 3 since Data now is a collection.
extension Data {
var hex: String {
var hexString = ""
for byte in self {
hexString += String(format: "%02X", byte)
}
return hexString
}
}
Or ...
extension Data {
var hex: String {
return self.map { b in String(format: "%02X", b) }.joined()
}
}
Or even ...
extension Data {
var hex: String {
return self.reduce("") { string, byte in
string + String(format: "%02X", byte)
}
}
}
I liked #Erik_Aigner's answer the best. I just refactored it a bit:
NSData *data = [NSMutableData dataWithBytes:"acani" length:5];
NSUInteger dataLength = [data length];
NSMutableString *string = [NSMutableString stringWithCapacity:dataLength*2];
const unsigned char *dataBytes = [data bytes];
for (NSInteger idx = 0; idx < dataLength; ++idx) {
[string appendFormat:#"%02x", dataBytes[idx]];
}
In Swift you can create an extension.
extension NSData {
func toHexString() -> String {
var hexString: String = ""
let dataBytes = UnsafePointer<CUnsignedChar>(self.bytes)
for (var i: Int=0; i<self.length; ++i) {
hexString += String(format: "%02X", dataBytes[i])
}
return hexString
}
}
Then you can simply use:
let keyData: NSData = NSData(bytes: [0x00, 0xFF], length: 2)
let hexString = keyData.toHexString()
println("\(hexString)") // Outputs 00FF
Sadly there's no built-in way to produce hex from an NSData, but it's pretty easy to do yourself. The simple way is to just pass successive bytes into sprintf("%02x") and accumulate those into an NSMutableString. A faster way would be to build a lookup table that maps 4 bits into a hex character, and then pass successive nybbles into that table.
While it may not be the most efficient way to do it, if you're doing this for debugging, SSCrypto has a category on NSData which contains two methods to do this (one for creating an NSString of the raw byte values, and one which shows a prettier representation of it).
http://www.septicus.com/SSCrypto/trunk/SSCrypto.m
Seeing there is a Swift 1.2 snippet in the comments, here's the Swift 2 version since C style for loops are deprecated now.
Gist with MIT license and two simple unit tests if you care.
Here's the code for your convenience:
import Foundation
extension NSData {
var hexString: String {
let pointer = UnsafePointer<UInt8>(bytes)
let array = getByteArray(pointer)
return array.reduce("") { (result, byte) -> String in
result.stringByAppendingString(String(format: "%02x", byte))
}
}
private func getByteArray(pointer: UnsafePointer<UInt8>) -> [UInt8] {
let buffer = UnsafeBufferPointer<UInt8>(start: pointer, count: length)
return [UInt8](buffer)
}
}
Assuming you have already set:
NSData *myData = ...;
Simple solution:
NSString *strData = [[NSString alloc]initWithData:myData encoding:NSUTF8StringEncoding];
NSLog(#"%#",strData);
I'm using an NSTask to grab the output from /usr/bin/man. I'm getting the output but without formatting (bold, underline). Something that should appear like this:
Bold text with underline
(note the italic text is actually underlined, there's just no formatting for it here)
Instead gets returned like this:
BBoolldd text with _u_n_d_e_r_l_i_n_e
I have a minimal test project at http://cl.ly/052u2z2i2R280T3r1K3c that you can download and run; note the window does nothing; the output gets logged to the Console.
I presume I need to somehow interpret the NSData object manually but I have no idea where to start on that. I'd ideally like to translate it to an NSAttributedString but the first order of business is actually eliminating the duplicates and underscores. Any thoughts?
What is your actual purpose? If you want to show a man page, one option is to convert it to HTML and render it with a Web view.
Parsing man’s output can be tricky because it is processed by groff using a terminal processor by default. This means that the output is tailored to be shown on terminal devices.
One alternative solution is to determine the actual location of the man page source file, e.g.
$ man -w bash
/usr/share/man/man1/bash.1.gz
and manually invoke groff on it with -a (ASCII approximation) and -c (disable colour output), e.g.
$ gunzip -c /usr/share/man/man1/bash.1.gz | groff -c -a -Tascii -man
This will result in an ASCII file without most of the formatting. To generate HTML output,
$ gunzip -c /usr/share/man/man1/bash.1.gz | groff -Thtml -man
You can also specify these options in a custom configuration file for man, e.g. parseman.conf, and tell man to use that configuration file with the -C option instead of invoking man -w, gunzip, and groff. The default configuration file is /private/etc/man.conf.
Also, you can probably tailor the output of the terminal device processor by passing appropriate options to grotty.
Okay, here's the start of my solution, though I would be interested in any additional (easier?) ways to do this.
The output returned from the Terminal is UTF-8 encoding, but the NSUTF8StringEncoding doesn't interpret the string properly. The reason is the way NSTask output is formatted.
The letter N is 0x4e in UTF-8. But the NSData corresponding to that is 0x4e 0x08 0x4e. 0x08 corresponds to a Backspace. So for a bold letter, Terminal prints letter-backspace-letter.
For an italic c, it's 0x63 in UTF-8. The NSData contains 0x5f 0x08 0x63, with 0x5f corresponding to an underscore. So for italics, Terminal prints underscore-backspace-letter.
I really don't see any way around this at this point besides just scanning the raw NSData for these sequences. I'll probably post the source to my parser here once I finish it, unless anybody has any existing code. As the common programming phrase goes, never write yourself what you can copy. :)
Follow-Up:
I've got a good, fast parser together for taking man output and replacing the bold/underlined output with bold/underlined formatting in an NSMutableAttributedString. Here's the code if anybody else needs to solve the same problem:
NSMutableIndexSet *boldChars = [[NSMutableIndexSet alloc] init];
NSMutableIndexSet *underlineChars = [[NSMutableIndexSet alloc] init];
char* bBytes = malloc(1);
bBytes[0] = (char)0x08;
NSData *bData = [NSData dataWithBytes:bBytes length:1];
free(bBytes); bBytes = nil;
NSRange testRange = NSMakeRange(1, [inputData length] - 1);
NSRange bRange = NSMakeRange(0, 0);
do {
bRange = [inputData rangeOfData:bData options:(NSDataSearchOptions)NULL range:testRange];
if (bRange.location == NSNotFound || bRange.location > [inputData length] - 2) break;
const char * buff = [inputData bytes];
if (buff[bRange.location - 1] == 0x5f) {
// it's an underline
//NSLog(#"Undr %c\n", buff[bRange.location + 1]);
[inputData replaceBytesInRange:NSMakeRange(bRange.location - 1, 2) withBytes:NULL length:0];
[underlineChars addIndex:bRange.location - 1];
testRange = NSMakeRange(bRange.location, [inputData length] - (bRange.location));
} else if (buff[bRange.location - 1] == buff[bRange.location + 1]) {
// It's a bold
//NSLog(#"Bold %c\n", buff[bRange.location + 1]);
[inputData replaceBytesInRange:NSMakeRange(bRange.location - 1, 2) withBytes:NULL length:0];
[boldChars addIndex:bRange.location - 1];
testRange = NSMakeRange(bRange.location, [inputData length] - (bRange.location));
} else {
testRange.location = bRange.location + 1;
testRange.length = [inputData length] - testRange.location;
}
} while (testRange.location <= [inputData length] - 3);
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:[[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding]];
NSFont *font = [NSFont fontWithDescriptor:[NSFontDescriptor fontDescriptorWithName:#"Menlo" size:12] size:12];
NSFont *boldFont = [[NSFontManager sharedFontManager] convertFont:font toHaveTrait:NSBoldFontMask];
[str addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [str length])];
__block NSUInteger begin = [underlineChars firstIndex];
__block NSUInteger end = begin;
[underlineChars enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
if (idx - end < 2) {
// it's the next item to the previous one
end = idx;
} else {
// it's a split, so drop in the accumulated range and reset
[str addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:NSSingleUnderlineStyle] range:NSMakeRange(begin, (end-begin)+1)];
begin = idx;
end = begin;
}
if (idx == [underlineChars lastIndex]) {
[str addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:NSSingleUnderlineStyle] range:NSMakeRange(begin, (end-begin)+1)];
}
}];
begin = [boldChars firstIndex];
end = begin;
[boldChars enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
if (idx - end < 2) {
// it's the next item to the previous one
end = idx;
} else {
// it's a split, so drop in the accumulated range and reset
[str addAttribute:NSFontAttributeName value:boldFont range:NSMakeRange(begin, (end-begin)+1)];
begin = idx;
end = begin;
}
if (idx == [underlineChars lastIndex]) {
[str addAttribute:NSFontAttributeName value:boldFont range:NSMakeRange(begin, (end-begin)+1)];
}
}];
Another method would be to convert the man page to PostScript source code, run that through the PostScript-to-PDF converter, and put that into a PDFView.
The implementation would be similar to Bavarious's answer, just with different arguments to groff (-Tps instead of -Thtml).
This would be the slowest solution, but also probably the best for printing.
NSArray *test = [NSArray arrayWithObjects:#"22", #"3", #"22", #"5", #"1", #"0", #"2", nil];
NSArray *test2 = [NSArray arrayWithObjects:#"21", #"2", #"20", #"5", #"1", #"9", #"2", nil];
for(int i = 0; i < 7; i++) {
if ([test objectAtIndex:i] == [test2 objectAtIndex:i]); {
testVariable = testVariable + 1;
}
}
NSLog(#"%i", testVariable);
I tried the above code to test comparison of variables but it returns 7 when it should return 3. Do I need to somehow retrieve and store each array object in a local variable and compare thos against each other? Or can I do something more direct like what I tried above.
Arrays are very interesting. :)
UPDATE:
Got it to work with NSInteger.. :)
Guess I was comparing objects and not the actual integer numbers before..
You can use preprocesor
#define variable(name,number) {name##number}
and later in the loop
for (int a = 1; a <= 53; a++) {
if ((variable(taken,a) == 2) && (variable(hidden,a) == 2)) {
//Do something
}
}
The simple answer is not to use different variables. Use a collection or an array instead. Then you could have:
if (taken[a] == 2 && hidden[a] == 2) {
...
}
Think of using an array whenever you find yourself putting numeric suffixes on variables of the same type and prefix.