1. 程式人生 > >【iOS解決方案】網路請求返回GB2312格式的xml資料轉成UTF-8後為空(適用於論壇bbs)

【iOS解決方案】網路請求返回GB2312格式的xml資料轉成UTF-8後為空(適用於論壇bbs)

一些高校的bbs由於歷史久遠,沒有適應新的資料結構,請求返回的資料還是xml格式的,而現在常用的返回是json,這是個很頭疼的地方,碰到的問題網上很難搜到資料,走了很多彎路。

現在具體講一下在解析返回xml過程中碰到的問題,希望能有些借鑑:

1.xml編碼問題:

對於編碼,我也僅僅只是瞭解。英文網頁返回的xml用UTF-8編碼滿足要求,而如果包含中文的話,用的就是GB2312編碼。問題是,iOS自帶的sdk中NSXMLParser(對於iOS自帶的解析方法,可以百度下,這裡不再詳述)解析UTF-8沒有問題,但是對於GB2312編碼,會直接觸發parser:parseErrorOccurred:導致解析失敗。所以轉碼是第一個問題,網上有很多GB2312轉UTF-8的方法。

2.NSData在轉碼過程中出資料錯誤的情況:

針對BBS論壇這類特殊情況,當標題超過某一長度時會自動進行截斷,所以這個比較坑,NSData轉碼不會報錯,但是轉換完就為nil,完全無從下手,不知道什麼問題。開始有的xml請求到了轉碼之後可以自己解析,但是有的就返回為nil,一直以為是資料請求失敗,但是返回的NSData是有資料的。直到無意中搜到cocoachina上的一個帖子,關於NSData轉碼問題(針對高校BBS回覆。這個帖子的回覆給了我方向,修復NSData中正好被截斷的位置,具體函式參考網上的一個處理方法(PS:他的封裝nsdata類我不太會用,所以自己修改了下,放到自己的類裡面去了= =)

+ (NSString *)GB18030String:(NSData *)sender {

    NSStringEncoding enc =CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);

    NSString *str = [[NSString alloc]initWithData:sender encoding:enc];

    if (!str) {

        str = [[NSString alloc]initWithData:[self dataByHealingGB18030Stream:sender]encoding:enc];

    }

    return str;

}



+ (NSData *)dataByHealingGB18030Stream:(NSData *)sender {

    NSUInteger length = [sender length];

    if (length == 0) {

        return sender;

    }

    

    static NSString * replacementCharacter = @"?";

    NSStringEncoding enc =CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);

    NSData *replacementCharacterData = [replacementCharacterdataUsingEncoding:enc];

    

    NSMutableData *resultData = [NSMutableData dataWithCapacity:sender.length];

    

    const Byte *bytes = [sender bytes];

    

    static const NSUInteger bufferMaxSize = 1024;

    Byte buffer[bufferMaxSize];

    NSUInteger bufferIndex = 0;

    

    NSUInteger byteIndex = 0;

    BOOL invalidByte = NO;

    

    

#define FlushBuffer() if (bufferIndex > 0) { \

[resultData appendBytes:buffer length:bufferIndex]; \

bufferIndex = 0; \

}

#define CheckBuffer() if ((bufferIndex+5) >= bufferMaxSize) { \

[resultData appendBytes:buffer length:bufferIndex]; \

bufferIndex = 0; \

}

    

    

    while (byteIndex < length) {

        Byte byte = bytes[byteIndex];

        

        //檢查第一位

        if (byte >= 0 && byte <= (Byte)0x7f) {

            //單位元組文字

            CheckBuffer();

            buffer[bufferIndex++] = byte;

        } else if (byte >= (Byte)0x81 && byte <= (Byte)0xfe){

            //可能是雙位元組,可能是四位元組

            if (byteIndex + 1 >= length) {

                //這是最後一個位元組了,但是這個位元組表明後面應該還有1或3個位元組,那麼這個位元組一定是錯誤位元組

                FlushBuffer();

                return resultData;

            }

            

            Byte byte2 = bytes[++byteIndex];

            if (byte2 >= (Byte)0x40 && byte <= (Byte)0xfe && byte != (Byte)0x7f) {

                //是雙位元組,並且可能合法

                Byte tuple[] = {byte, byte2};

                CFStringRef cfstr = CFStringCreateWithBytes(kCFAllocatorDefault, tuple, 2,kCFStringEncodingGB_18030_2000, false);

                if (cfstr) {

                    CFRelease(cfstr);

                    CheckBuffer();

                    buffer[bufferIndex++] = byte;

                    buffer[bufferIndex++] = byte2;

                } else {

                    //這個雙位元組字元不合法,但byte2可能是下一字元的第一位元組

                    byteIndex -= 1;

                    invalidByte = YES;

                }

            } else if (byte2 >= (Byte)0x30 && byte2 <= (Byte)0x39) {

                //可能是四位元組

                if (byteIndex + 2 >= length) {

                    FlushBuffer();

                    return resultData;

                }

                

                Byte byte3 = bytes[++byteIndex];

                

                if (byte3 >= (Byte)0x81 && byte3 <= (Byte)0xfe) {

                    // 第三位合法,判斷第四位

                    

                    Byte byte4 = bytes[++byteIndex];

                    

                    if (byte4 >= (Byte)0x30 && byte4 <= (Byte)0x39) {

                        //第四位可能合法

                        Byte tuple[] = {byte, byte2, byte3, byte4};

                        CFStringRef cfstr = CFStringCreateWithBytes(kCFAllocatorDefault, tuple,4, kCFStringEncodingGB_18030_2000, false);

                        if (cfstr) {

                            CFRelease(cfstr);

                            CheckBuffer();

                            buffer[bufferIndex++] = byte;

                            buffer[bufferIndex++] = byte2;

                            buffer[bufferIndex++] = byte3;

                            buffer[bufferIndex++] = byte4;

                        } else {

                            //這個四位元組字元不合法,但是byte2可能是下一個合法字元的第一位元組,回退3位

                            //並且將byte1,byte2用?替代

                            byteIndex -= 3;

                            invalidByte = YES;

                        }

                    } else {

                        //第四位元組不合法

                        byteIndex -= 3;

                        invalidByte = YES;

                    }

                } else {

                    // 第三位元組不合法

                    byteIndex -= 2;

                    invalidByte = YES;

                }

            } else {

                // 第二位元組不是合法的第二位,但可能是下一個合法的第一位,所以回退一個byte

                invalidByte = YES;

                byteIndex -= 1;

            }

            

            if (invalidByte) {

                invalidByte = NO;

                FlushBuffer();

                [resultData appendData:replacementCharacterData];

            }

        }

        byteIndex++;

    }

    FlushBuffer();

    return resultData;

}

3.關於xml中的特殊字元處理:

如果以為上面兩步處理完了解析xml就很順利了,那就錯了。在解析的過程中發現xml解析到中途就會失敗,返回的意思大概就是碰到PCDATA invalidchar 27什麼的,domain code = 9.這個只能怪我自己基礎不好,當時沒多想,網上也搜不到錯誤碼。後來無意中發現返回的xml nsstring裡面包含\x1b。這個我恰好搜到是ASCII碼裡面的第27個字元,和前面報錯對應起來了。就是這個符號除了問題,然後通過正則表示式/[\x00-\x08\x1b\x0b-\x0c\x0e-\x1f\x7f]/這裡iOS裡面可能要稍微修改下,把“\”改成“\\”。然後問題就解決了,NSXMLParser順利的解析出xml資料 其實我發現問題的順序是1-3-2 問題2是後來測試中偶然發現轉碼後資料為nil的情況 關於xml和編碼問題自己基礎確實薄弱,在這個問題上費了好大的裡,翻了好多帖子,感覺資料也不是很多。所以,在把自己的經驗貼出來提供參考,有不對的地方還請高手指正。微笑