最近做了一个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
第二种算法先不发了,,,,