使用部分缓冲区将多字节unicode字节数组转换为NSString

时间:2014-08-28 15:32:32

标签: ios objective-c unicode utf-8 nsstream

在Objective C中有一种方法可以将多字节unicode字节数组转换为NSString,即使数组数据是部分缓冲区(不是在完整的字符边界上),它也允许转换成功吗? / p>

应用这是在流中接收字节缓冲区,并且您想要解析数据缓冲区的字符串版本(但是有更多数据要来,并且您的缓冲区数据没有完整的多字节unicode )。

NSString的initWithData:encoding:方法不能用于此目的,如此处所示......

测试代码:

    - (void)test {
        char myArray[] = {'f', 'o', 'o', (char) 0xc3, (char) 0x97, 'b', 'a', 'r'};
        size_t sizeOfMyArray = sizeof(myArray);
        [self dump:myArray sizeOfMyArray:sizeOfMyArray];
        [self dump:myArray sizeOfMyArray:sizeOfMyArray - 1];
        [self dump:myArray sizeOfMyArray:sizeOfMyArray - 2];
        [self dump:myArray sizeOfMyArray:sizeOfMyArray - 3];
        [self dump:myArray sizeOfMyArray:sizeOfMyArray - 4];
        [self dump:myArray sizeOfMyArray:sizeOfMyArray - 5];
    }

    - (void)dump:(char[])myArray sizeOfMyArray:(size_t)sourceLength {
        NSString *string = [[NSString alloc] initWithData:[NSData dataWithBytes:myArray length:sourceLength] encoding:NSUTF8StringEncoding];
        NSLog(@"sourceLength: %lu bytes, string.length: %i bytes, string :'%@'", sourceLength, string.length, string);
    }

输出:

sourceLength: 8 bytes, string.length: 7 bytes, string :'foo×bar'
sourceLength: 7 bytes, string.length: 6 bytes, string :'foo×ba'
sourceLength: 6 bytes, string.length: 5 bytes, string :'foo×b'
sourceLength: 5 bytes, string.length: 4 bytes, string :'foo×'
sourceLength: 4 bytes, string.length: 0 bytes, string :'(null)'
sourceLength: 3 bytes, string.length: 3 bytes, string :'foo'

可以看出,转换“sourceLength:4 bytes”字节数组失败,并返回(null)。这是因为UTF-8 unicode'×'字符(0xc3 0x97)仅部分包含在内。

理想情况下,我可以使用一个可以返回正确NString的函数,并告诉我“遗留”了多少字节。

3 个答案:

答案 0 :(得分:2)

你基本上有自己的答案。如果initWithData:dataWithBytes:encoding:方法返回nil,那么您知道缓冲区末尾有一个部分(无效)字符。

修改dump以返回int。然后尝试在循环中创建NSString。每次获得nil时,请缩短长度并重试。获得有效NSString后,返回已使用长度与传递长度之间的差异。

答案 1 :(得分:2)

之前我遇到过这个问题并暂时忘记了。这是一次机会。下面的代码是使用utf-8 page on wikipedia的信息完成的。它是NSData上的一个类别。

它从末尾检查数据,只检查最后四个字节,因为OP表示它可以是千兆字节的数据。否则使用utf-8从一开始就可以更简单地运行字节。

/* 
 Return the range of a valid utf-8 encoded text by
 removing partial trailing multi-byte char.
 It assumes that all the bytes are valid utf-8 encoded char,
 e.g. it don't raise a flag if a continuation byte is preceded
 by a single char byte.
 */
 - (NSRange)rangeOfUTF8WithoutPartialTrailingMultibytes
 {
    NSRange validRange = {0, 0};

    NSUInteger trailLength = MIN([self length], 4U);
    unsigned char trail[4];
    [self getBytes:&trail
             range:NSMakeRange([self length] - trailLength, trailLength)];

    unsigned multibyteCount = 0;

    for (NSInteger i = trailLength - 1; i >= 0; i--) {
        if (isUTF8SingleByte(trail[i])) {
            validRange = NSMakeRange(0, [self length] - trailLength + i + 1);
            break;
        }

        if (isUTF8ContinuationByte(trail[i])) {
            multibyteCount++;
            continue;
        }

        if (isUTF8StartByte(trail[i])) {
            multibyteCount++;
            if (multibyteCount == lengthForUTF8StartByte(trail[i])) {
                validRange = NSMakeRange(0, [self length] - trailLength + i + multibyteCount);
            }
            else {
                validRange = NSMakeRange(0, [self length] - trailLength + i);
            } 
            break;
        }
    }
    return validRange;
}

以下是方法中使用的静态函数:

static BOOL isUTF8SingleByte(const unsigned char c)
{
    return c <= 0x7f;
}

static BOOL isUTF8ContinuationByte(const unsigned char c)
{
    return (c >= 0x80) && (c <= 0xbf);
}

static BOOL isUTF8StartByte(const unsigned char c)
{
    return (c >= 0xc2) && (c <= 0xf4);
}

static BOOL isUTF8InvalidByte(const unsigned char c)
{
    return (c == 0xc0) || (c == 0xc1) || (c > 0xf4);
}

static unsigned lengthForUTF8StartByte(const unsigned char c)
{
    if ((c >= 0xc2) && (c <= 0xdf)) {
        return 2;
    }
    else if ((c >= 0xe0) && (c <= 0xef)) {
        return 3;
    }
    else if ((c >= 0xf0) && (c <= 0xf4)) {
        return 4;
    }
    return 1;
}

答案 2 :(得分:0)

这是我的低效实施,我不认为这是一个正确的答案。我会把它留在这里,以防其他人发现它有用(希望别人能给出比这更好的答案!)

它位于NSMutableData ...

的类别中
    /**
    * Removes the biggest string possible from this NSMutableData, leaving any remainder unicode half-characters behind.
    *
    * NOTE: This is a very inefficient implementation, it may require multiple parsing of the complete NSMutableData buffer,
    * it is especially inefficient when the data buffer does not contain a valid string encoding, as all lengths will be
    * attempted.
    */
    - (NSString *)removeMaximumStringUsingEncoding:(NSStringEncoding)encoding {
        if (self.length > 0) {
            // Quick test for the case where the whole buffer can be used (is common case, and doesn't require NSData manipulation).
            NSString *result = [[NSString alloc] initWithData:self encoding:encoding];
            if (result != Nil) {
                self.length = 0; // Simple case, we used the whole buffer.
                return result;
            }

            // Try to find the largest subData that is a valid string.
            for (NSUInteger subDataLength = self.length - 1; subDataLength > 0; subDataLength--) {
                NSRange subDataRange = NSMakeRange(0, subDataLength);
                result = [[NSString alloc] initWithData:[self subdataWithRange:subDataRange] encoding:encoding];
                if (result != Nil) {
                    // Delete the bytes we used from our buffer, leave the remainder.
                    [self replaceBytesInRange:subDataRange withBytes:Nil length:0];
                    return result;
                }
            }
        }
        return @"";
    }