最近做了一個WIFI傳書本地閱讀功能,有所收獲在這里記錄下吧。
用戶下載的書籍分為兩種,一種是有章節格式的,比如 第一章,001章、等,這種可以用正則來直接分章節,還有絕大多數書籍是沒有這種格式的,這種如果整本書來直接解析的話,對CPU要求比較大,可能會卡死閃退,所有手動分章節還是很有必要的,這種情況下我們采用按照兩千字來分。
話不多說,開始吧。
1、WIFI傳書把書傳到APP沙盒里,這里我們采用的是 GCDWebServer ,很方便,這里就不做陳述了。
2、將沙盒里面的 .txt 文件轉成 文本 ,這里的坑點也不少,我們專門寫了一個 NSStringEncoding 解碼的算法來轉文字,可以解析多種編碼方式的文本,這種算法只能適配iOS11及以上系統,其他系統只能采用系統UTF-8方法來解析,限制較多。
//轉成文字 - (void)encodeWithURL:(NSString *)url result:(void (^)(NSString *content))result; - (void)encodeWithURL:(NSString *)url result:(void (^)(NSString *content))result { if (url.length == 0) { result(@""); return; } NSData *data = [NSData dataWithContentsOfFile:url options:NSDataReadingMappedIfSafe error:nil]; if (@available(iOS 11.0, *)) { NSString *content = data.mc_autoString; if (content.length == 0) { NSString *txt = data.utf8String; txt = [txt stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"]; txt = [txt stringByReplacingOccurrencesOfString:@"\r" withString:@"\n"]; result(txt); return; } result(content); return; } NSString *txt = data.utf8String; txt = [txt stringByReplacingOccurrencesOfString:@"\r\n" withString:@"\n"]; txt = [txt stringByReplacingOccurrencesOfString:@"\r" withString:@"\n"]; result(txt); }
3、文本拿到之后開始分章節吧
//分章節 - (void)separateChapterContent:(NSString *)content result:(void (^)(NSArray *chapterArr))result; - (void)separateChapterContent:(NSString *)content result:(void (^)(NSArray *chapterArr))result { NSMutableArray *chapters = [[NSMutableArray alloc] init]; NSString *parten = @"第[0-9一二三四五六七八九十百千]*[章回].*"; NSError* error = NULL; NSRegularExpression *reg = [NSRegularExpression regularExpressionWithPattern:parten options:NSRegularExpressionCaseInsensitive error:&error]; NSArray* match = [reg matchesInString:content options:NSMatchingReportCompletion range:NSMakeRange(0, [content length])]; if (match.count >= 100) { __block NSRange lastRange = NSMakeRange(0, 0); [match enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSRange range = [obj range]; NSInteger local = range.location; if (idx == 0) { NSDictionary *dict = @{@"title":@"序章", @"content":[content substringWithRange:NSMakeRange(0, local)] }; [chapters addObject:dict]; } if (idx > 0 ) { NSUInteger len = local-lastRange.location; NSDictionary *dict = @{@"title":[content substringWithRange:lastRange], @"content":[content substringWithRange:NSMakeRange(lastRange.location, len)] }; [chapters addObject:dict]; } if (idx == match.count-1) { NSDictionary *dict = @{@"title":[content substringWithRange:range], @"content":[content substringWithRange:NSMakeRange(local, content.length-local)] }; [chapters addObject:dict]; } lastRange = range; }]; } else { //不能分章節的書籍按照2000字來手動分章節 NSArray *lineAry = [content componentsSeparatedByString:@"\n"]; NSMutableArray *bodyTextAry = [[NSMutableArray alloc] init]; NSInteger outLength = 0; //末尾長度 NSInteger startLength = 0; //起始長度 NSInteger textLeng = content.length; //總長度 NSLog(@"總長度:%ld",textLeng); //先把文字按2000字分出來 for (int i = 0; i < lineAry.count ; i ++) { NSString *textLine = lineAry[i]; outLength += textLine.length; if (i+1 != lineAry.count) { ++outLength; } if (outLength >= 2000) { NSRange lastRange = NSMakeRange(startLength, outLength); [bodyTextAry addObject:[content substringWithRange:lastRange]]; startLength += outLength; outLength = 0; } else if (i == lineAry.count - 1) { NSRange lastRange = NSMakeRange(startLength, outLength); [bodyTextAry addObject:[content substringWithRange:lastRange]]; } } //再構造數據傳出去 [bodyTextAry enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) { NSDictionary *dict = @{@"title":[NSString stringWithFormat:@"第%lu章",(unsigned long)idx+1], @"content":obj }; [chapters addObject:dict]; }]; } result(chapters); }
4、章節分好之后就存入本地數據庫,然后傳入閱讀器解析閱讀吧。
注:補充下文本轉文字的算法吧
第一種,如果文本是以utf-8來編碼的
NSData+Addition.h #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface NSData (Addition) #pragma mark - Encode and Decode /** UTF8編碼 */ - (nullable NSString *)utf8String; /** Base64編碼 */ - (nullable NSString *)base64EncodedString; /** Base64解碼 @param base64EncodedString The encoded string. */ + (nullable NSData *)dataWithBase64EncodedString:(NSString *)base64EncodedString; /** Json解析 如果失敗,返回 nil */ - (nullable id)jsonValueDecodedWithError:(NSError **)error; #pragma mark - Hash /** MD5編碼 */ - (NSString *)md5String; @end NSData+Addition.m #import "NSData+Addition.h" #include <CommonCrypto/CommonCrypto.h> @implementation NSData (Addition) #pragma mark - Encode and Decode static const char base64EncodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const short base64DecodingTable[256] = { -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -1, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2, -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2 }; - (NSString *)utf8String { if (self.length > 0) { return [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding]; } return @""; } - (NSString *)base64EncodedString { NSUInteger length = self.length; if (length == 0) return @""; NSUInteger out_length = ((length + 2) / 3) * 4; uint8_t *output = malloc(((out_length + 2) / 3) * 4); if (output == NULL) return nil; const char *input = self.bytes; NSInteger i, value; for (i = 0; i < length; i += 3) { 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] = base64EncodingTable[(value >> 18) & 0x3F]; output[index + 1] = base64EncodingTable[(value >> 12) & 0x3F]; output[index + 2] = ((i + 1) < length) ? base64EncodingTable[(value >> 6) & 0x3F] : '='; output[index + 3] = ((i + 2) < length) ? base64EncodingTable[(value >> 0) & 0x3F] : '='; } NSString *base64 = [[NSString alloc] initWithBytes:output length:out_length encoding:NSASCIIStringEncoding]; free(output); return base64; } + (NSData *)dataWithBase64EncodedString:(NSString *)base64EncodedString { NSInteger length = base64EncodedString.length; const char *string = [base64EncodedString cStringUsingEncoding:NSASCIIStringEncoding]; if (string == NULL) return nil; while (length > 0 && string[length - 1] == '=') length--; NSInteger outputLength = length * 3 / 4; NSMutableData *data = [NSMutableData dataWithLength:outputLength]; if (data == nil) return nil; if (length == 0) return data; uint8_t *output = data.mutableBytes; NSInteger inputPoint = 0; NSInteger outputPoint = 0; while (inputPoint < length) { char i0 = string[inputPoint++]; char i1 = string[inputPoint++]; char i2 = inputPoint < length ? string[inputPoint++] : 'A'; char i3 = inputPoint < length ? string[inputPoint++] : 'A'; output[outputPoint++] = (base64DecodingTable[i0] << 2) | (base64DecodingTable[i1] >> 4); if (outputPoint < outputLength) { output[outputPoint++] = ((base64DecodingTable[i1] & 0xf) << 4) | (base64DecodingTable[i2] >> 2); } if (outputPoint < outputLength) { output[outputPoint++] = ((base64DecodingTable[i2] & 0x3) << 6) | base64DecodingTable[i3]; } } return data; } - (nullable id)jsonValueDecodedWithError:(NSError **)error { id value = [NSJSONSerialization JSONObjectWithData:self options:kNilOptions error:error]; return value; } #pragma mark - Hash - (NSString *)md5String { unsigned char result[CC_MD5_DIGEST_LENGTH]; CC_MD5(self.bytes, (CC_LONG)self.length, result); return [NSString stringWithFormat: @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], result[8], result[9], result[10], result[11], result[12], result[13], result[14], result[15] ]; } @end
第二種算法先不發了,,,,