// Copyright 2008 Cyrus Najmabadi // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import "TextFormat.h" #import "Utilities.h" @implementation PBTextFormat BOOL allZeroes(NSString* string); BOOL allZeroes(NSString* string) { for (int i = 0; i < (int32_t)string.length; i++) { if ([string characterAtIndex:(NSUInteger)i] != '0') { return NO; } } return YES; } /** Is this an octal digit? */ BOOL isOctal(unichar c); BOOL isOctal(unichar c) { return '0' <= c && c <= '7'; } /** Is this an octal digit? */ BOOL isDecimal(unichar c); BOOL isDecimal(unichar c) { return '0' <= c && c <= '9'; } /** Is this a hex digit? */ BOOL isHex(unichar c); BOOL isHex(unichar c) { return isDecimal(c) || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'); } + (int64_t) parseInteger:(NSString*) text isSigned:(BOOL) isSigned isLong:(BOOL) isLong { if (text.length == 0) { @throw [NSException exceptionWithName:@"NumberFormat" reason:@"Number was blank" userInfo:nil]; } if (isblank([text characterAtIndex:0])) { @throw [NSException exceptionWithName:@"NumberFormat" reason:@"Invalid character" userInfo:nil]; } if ([text hasPrefix:@"-"]) { if (!isSigned) { @throw [NSException exceptionWithName:@"NumberFormat" reason:@"Number must be positive" userInfo:nil]; } } // now call into the appropriate conversion utilities. int64_t result; const char* in_string = text.UTF8String; char* out_string = NULL; errno = 0; if (isLong) { if (isSigned) { result = strtoll(in_string, &out_string, 0); } else { result = convertUInt64ToInt64(strtoull(in_string, &out_string, 0)); } } else { if (isSigned) { result = strtol(in_string, &out_string, 0); } else { result = convertUInt32ToInt32((unsigned int)strtoul(in_string, &out_string, 0)); } } // from the man pages: // (Thus, i* tr is not `\0' but **endptr is `\0' on return, the entire // string was valid.) if (*in_string == 0 || *out_string != 0) { @throw [NSException exceptionWithName:@"NumberFormat" reason:@"IllegalNumber" userInfo:nil]; } if (errno == ERANGE) { @throw [NSException exceptionWithName:@"NumberFormat" reason:@"Number out of range" userInfo:nil]; } return result; } /** * Parse a 32-bit signed integer from the text. This function recognizes * the prefixes "0x" and "0" to signify hexidecimal and octal numbers, * respectively. */ + (int32_t) parseInt32:(NSString*) text { return (int32_t)[self parseInteger:text isSigned:YES isLong:NO]; } /** * Parse a 32-bit unsigned integer from the text. This function recognizes * the prefixes "0x" and "0" to signify hexidecimal and octal numbers, * respectively. The result is coerced to a (signed) {@code int} when returned. */ + (int32_t) parseUInt32:(NSString*) text { return (int32_t)[self parseInteger:text isSigned:NO isLong:NO]; } /** * Parse a 64-bit signed integer from the text. This function recognizes * the prefixes "0x" and "0" to signify hexidecimal and octal numbers, * respectively. */ + (int64_t) parseInt64:(NSString*) text { return [self parseInteger:text isSigned:YES isLong:YES]; } /** * Parse a 64-bit unsigned integer from the text. This function recognizes * the prefixes "0x" and "0" to signify hexidecimal and octal numbers, * respectively. The result is coerced to a (signed) {@code long} when * returned. */ + (int64_t) parseUInt64:(NSString*) text { return [self parseInteger:text isSigned:NO isLong:YES]; } /** * Interpret a character as a digit (in any base up to 36) and return the * numeric value. This is like {@code Character.digit()} but we don't accept * non-ASCII digits. */ int32_t digitValue(unichar c); int32_t digitValue(unichar c) { if ('0' <= c && c <= '9') { return c - '0'; } else if ('a' <= c && c <= 'z') { return c - 'a' + 10; } else { return c - 'A' + 10; } } /** * Un-escape a byte sequence as escaped using * {@link #escapeBytes(ByteString)}. Two-digit hex escapes (starting with * "\x") are also recognized. */ + (NSData*) unescapeBytes:(NSString*) input { NSMutableData* result = [NSMutableData dataWithLength:input.length]; int32_t pos = 0; for (int32_t i = 0; i < (int32_t)input.length; i++) { unichar c = [input characterAtIndex:(NSUInteger)i]; if (c == '\\') { if (i + 1 < (int32_t)input.length) { ++i; c = [input characterAtIndex:(NSUInteger)i]; if (isOctal(c)) { // Octal escape. int32_t code = digitValue(c); if (i + 1 < (int32_t)input.length && isOctal([input characterAtIndex:(NSUInteger)(i + 1)])) { ++i; code = code * 8 + digitValue([input characterAtIndex:(NSUInteger)i]); } if (i + 1 < (int32_t)input.length && isOctal([input characterAtIndex:(NSUInteger)(i + 1)])) { ++i; code = code * 8 + digitValue([input characterAtIndex:(NSUInteger)i]); } ((int8_t*)result.mutableBytes)[pos++] = (int8_t)code; } else { switch (c) { case 'a' : ((int8_t*)result.mutableBytes)[pos++] = 0x07; break; case 'b' : ((int8_t*)result.mutableBytes)[pos++] = '\b'; break; case 'f' : ((int8_t*)result.mutableBytes)[pos++] = '\f'; break; case 'n' : ((int8_t*)result.mutableBytes)[pos++] = '\n'; break; case 'r' : ((int8_t*)result.mutableBytes)[pos++] = '\r'; break; case 't' : ((int8_t*)result.mutableBytes)[pos++] = '\t'; break; case 'v' : ((int8_t*)result.mutableBytes)[pos++] = 0x0b; break; case '\\': ((int8_t*)result.mutableBytes)[pos++] = '\\'; break; case '\'': ((int8_t*)result.mutableBytes)[pos++] = '\''; break; case '"' : ((int8_t*)result.mutableBytes)[pos++] = '\"'; break; case 'x': // hex escape { int32_t code = 0; if (i + 1 < (int32_t)input.length && isHex([input characterAtIndex:(NSUInteger)(i + 1)])) { ++i; code = digitValue([input characterAtIndex:(NSUInteger)i]); } else { @throw [NSException exceptionWithName:@"InvalidEscape" reason:@"Invalid escape sequence: '\\x' with no digits" userInfo:nil]; } if (i + 1 < (int32_t)input.length && isHex([input characterAtIndex:(NSUInteger)(i + 1)])) { ++i; code = code * 16 + digitValue([input characterAtIndex:(NSUInteger)i]); } ((int8_t*)result.mutableBytes)[pos++] = (int8_t)code; break; } default: @throw [NSException exceptionWithName:@"InvalidEscape" reason:@"Invalid escape sequence" userInfo:nil]; } } } else { @throw [NSException exceptionWithName:@"InvalidEscape" reason:@"Invalid escape sequence: '\\' at end of string" userInfo:nil]; } } else { ((int8_t*)result.mutableBytes)[pos++] = (int8_t)c; } } [result setLength:(NSUInteger)pos]; return result; } @end