|
|
|
@ -1,6 +1,7 @@ |
|
|
|
|
package io.legado.app.help |
|
|
|
|
|
|
|
|
|
import java.util.* |
|
|
|
|
import java.util.regex.Pattern |
|
|
|
|
import kotlin.math.max |
|
|
|
|
import kotlin.math.min |
|
|
|
|
|
|
|
|
@ -30,6 +31,7 @@ object ContentHelp { |
|
|
|
|
} else { |
|
|
|
|
content1 |
|
|
|
|
} |
|
|
|
|
val dict = makeDict(content2) |
|
|
|
|
var p = content2 |
|
|
|
|
.replace(""".toRegex(), "“") |
|
|
|
|
.replace("[::]['\"‘”“]+".toRegex(), ":“") |
|
|
|
@ -68,9 +70,7 @@ object ContentHelp { |
|
|
|
|
buffer = StringBuffer((content1.length * 1.15).toInt()) |
|
|
|
|
for (s in p) { |
|
|
|
|
buffer.append("\n") |
|
|
|
|
buffer.append( |
|
|
|
|
findNewLines(s) |
|
|
|
|
) |
|
|
|
|
buffer.append(findNewLines(s, dict)) |
|
|
|
|
} |
|
|
|
|
buffer = reduceLength(buffer) |
|
|
|
|
content1 = ("$chapterName\n\n" + buffer.toString() // 处理章节头部空格和换行 |
|
|
|
@ -178,18 +178,18 @@ object ContentHelp { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 对内容重新划分段落.输入参数str已经使用换行符预分割 |
|
|
|
|
private fun findNewLines(str: String): String { |
|
|
|
|
private fun findNewLines(str: String, dict: List<String>): String { |
|
|
|
|
val string = StringBuffer(str) |
|
|
|
|
// 标记string中每个引号的位置.特别的,用引号进行列举时视为只有一对引号。 如:“锅”、“碗”视为“锅、碗”,从而避免误断句。 |
|
|
|
|
val arrayQuote: MutableList<Int> = ArrayList() |
|
|
|
|
// 标记插入换行符的位置,int为插入位置(str的char下标) |
|
|
|
|
var insN = ArrayList<Int>() |
|
|
|
|
|
|
|
|
|
// mod[i]标记str的每一段处于引号内还是引号外。范围: str.substring( array_quote.get(i), array_quote.get(i+1) )的状态。 |
|
|
|
|
// 长度:array_quote.size(),但是初始化时未预估占用的长度,用空间换时间 |
|
|
|
|
// 0未知,正数引号内,负数引号外。 |
|
|
|
|
// 如果相邻的两个标记都为+1,那么需要增加1个引号。 |
|
|
|
|
// 引号内不进行断句 |
|
|
|
|
//mod[i]标记str的每一段处于引号内还是引号外。范围: str.substring( array_quote.get(i), array_quote.get(i+1) )的状态。 |
|
|
|
|
//长度:array_quote.size(),但是初始化时未预估占用的长度,用空间换时间 |
|
|
|
|
//0未知,正数引号内,负数引号外。 |
|
|
|
|
//如果相邻的两个标记都为+1,那么需要增加1个引号。 |
|
|
|
|
//引号内不进行断句 |
|
|
|
|
val mod = IntArray(str.length) |
|
|
|
|
var waitClose = false |
|
|
|
|
for (i in str.indices) { |
|
|
|
@ -201,7 +201,16 @@ object ContentHelp { |
|
|
|
|
if (size > 0) { |
|
|
|
|
val quotePre = arrayQuote[size - 1] |
|
|
|
|
if (i - quotePre == 2) { |
|
|
|
|
if (match(",、,/", str[i - 1])) { |
|
|
|
|
var remove = false |
|
|
|
|
if (waitClose) { |
|
|
|
|
if (match(",,、/", str[i - 1])) { |
|
|
|
|
// 考虑出现“和”这种特殊情况 |
|
|
|
|
remove = true |
|
|
|
|
} |
|
|
|
|
} else if (match(",,、/和与或", str[i - 1])) { |
|
|
|
|
remove = true |
|
|
|
|
} |
|
|
|
|
if (remove) { |
|
|
|
|
string.setCharAt(i, '“') |
|
|
|
|
string.setCharAt(i - 2, '”') |
|
|
|
|
arrayQuote.removeAt(size - 1) |
|
|
|
@ -232,8 +241,10 @@ object ContentHelp { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// if(char_b2=='.' || char_b2=='。') |
|
|
|
|
if (match(MARK_SENTENCES_END_P, charB2)) insN.add(p - 1) else { |
|
|
|
|
//if(char_b2=='.' || char_b2=='。') |
|
|
|
|
if (match(MARK_SENTENCES_END_P, charB2)) { |
|
|
|
|
insN.add(p - 1) |
|
|
|
|
} else if (!match("的", charB2)) { |
|
|
|
|
val lastEnd = seekLast(str, MARK_SENTENCES_END, i, lastQuote) |
|
|
|
|
if (lastEnd > 0) insN.add(lastEnd) else insN.add(lastQuote) |
|
|
|
|
} |
|
|
|
@ -258,16 +269,15 @@ object ContentHelp { |
|
|
|
|
val size = arrayQuote.size |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 标记循环状态,此位置前的引号是否已经配对 |
|
|
|
|
//标记循环状态,此位置前的引号是否已经配对 |
|
|
|
|
var opend = false |
|
|
|
|
if (size > 0) { |
|
|
|
|
|
|
|
|
|
// 第1次遍历array_quote,令其元素的值不为0 |
|
|
|
|
//第1次遍历array_quote,令其元素的值不为0 |
|
|
|
|
for (i in 0 until size) { |
|
|
|
|
if (mod[i] > 0) { |
|
|
|
|
opend = true |
|
|
|
|
} else if (mod[i] < 0) { |
|
|
|
|
// 连续2个反引号表明存在冲突,强制把前一个设为正引号 |
|
|
|
|
//连续2个反引号表明存在冲突,强制把前一个设为正引号 |
|
|
|
|
if (!opend) { |
|
|
|
|
if (i > 0) mod[i] = 3 |
|
|
|
|
} |
|
|
|
@ -280,7 +290,7 @@ object ContentHelp { |
|
|
|
|
// 修正,断尾必须封闭引号 |
|
|
|
|
if (opend) { |
|
|
|
|
if (arrayQuote[size - 1] - string.length > -3) { |
|
|
|
|
// if((match(MARK_QUOTATION,string.charAt(string.length()-1)) || match(MARK_QUOTATION,string.charAt(string.length()-2)))){ |
|
|
|
|
//if((match(MARK_QUOTATION,string.charAt(string.length()-1)) || match(MARK_QUOTATION,string.charAt(string.length()-2)))){ |
|
|
|
|
if (size > 1) mod[size - 2] = 4 |
|
|
|
|
// 0<=i<size,故无需判断size>=1 |
|
|
|
|
mod[size - 1] = -4 |
|
|
|
@ -318,9 +328,39 @@ object ContentHelp { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 第3次循环,匹配并插入换行。 |
|
|
|
|
// "xxxx" xxxx。\n xxx“xxxx” |
|
|
|
|
// 未实现 |
|
|
|
|
//第3次循环,匹配并插入换行。 |
|
|
|
|
//"xxxx" xxxx。\n xxx“xxxx” |
|
|
|
|
//未实现 |
|
|
|
|
|
|
|
|
|
// 使用字典验证ins_n , 避免插入不必要的换行。 |
|
|
|
|
// 由于目前没有插入、的列表,无法解决 “xx”、“xx”“xx” 被插入换行的问题 |
|
|
|
|
val insN1 = ArrayList<Int>() |
|
|
|
|
for (i in insN) { |
|
|
|
|
if (match("\"'”“", string[i])) { |
|
|
|
|
val start: Int = seekLast( |
|
|
|
|
str, |
|
|
|
|
"\"'”“", |
|
|
|
|
i - 1, |
|
|
|
|
i - WORD_MAX_LENGTH |
|
|
|
|
) |
|
|
|
|
if (start > 0) { |
|
|
|
|
val word = str.substring(start + 1, i) |
|
|
|
|
if (dict.contains(word)) { |
|
|
|
|
//System.out.println("使用字典验证 跳过\tins_n=" + i + " word=" + word); |
|
|
|
|
//引号内如果是字典词条,后方不插入换行符(前方不需要优化) |
|
|
|
|
continue |
|
|
|
|
} else { |
|
|
|
|
//System.out.println("使用字典验证 插入\tins_n=" + i + " word=" + word); |
|
|
|
|
if (match("的地得", str[start])) { |
|
|
|
|
//xx的“xx”,后方不插入换行符(前方不需要优化) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
insN1.add(i) |
|
|
|
|
} |
|
|
|
|
insN = insN1 |
|
|
|
|
|
|
|
|
|
// 随机在句末插入换行符 |
|
|
|
|
insN = ArrayList(HashSet(insN)) |
|
|
|
@ -471,6 +511,34 @@ object ContentHelp { |
|
|
|
|
return buffer.toString() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 从字符串提取引号包围,且不止出现一次的内容为字典 |
|
|
|
|
* |
|
|
|
|
* @param str |
|
|
|
|
* @return 词条列表 |
|
|
|
|
*/ |
|
|
|
|
private fun makeDict(str: String): List<String> { |
|
|
|
|
|
|
|
|
|
// 引号中间不包含任何标点 |
|
|
|
|
val patten = Pattern.compile( |
|
|
|
|
""" |
|
|
|
|
(?<=["'”“])([^ |
|
|
|
|
\p{P}]{1,${WORD_MAX_LENGTH}})(?=["'”“]) |
|
|
|
|
""".trimIndent() |
|
|
|
|
) |
|
|
|
|
//Pattern patten = Pattern.compile("(?<=[\"'”“])([^\n\"'”“]{1,16})(?=[\"'”“])"); |
|
|
|
|
val matcher = patten.matcher(str) |
|
|
|
|
val cache: MutableList<String> = ArrayList() |
|
|
|
|
val dict: MutableList<String> = ArrayList() |
|
|
|
|
while (matcher.find()) { |
|
|
|
|
val word = matcher.group() |
|
|
|
|
if (cache.contains(word)) { |
|
|
|
|
if (!dict.contains(word)) dict.add(word) |
|
|
|
|
} else cache.add(word) |
|
|
|
|
} |
|
|
|
|
return dict |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 计算匹配到字典的每个字符的位置 |
|
|
|
|
* |
|
|
|
@ -510,14 +578,14 @@ object ContentHelp { |
|
|
|
|
* |
|
|
|
|
* @param str 数据字符串 |
|
|
|
|
* @param key 字典字符串 |
|
|
|
|
* @param from 从哪个字符开始匹配,默认0 |
|
|
|
|
* @param to 匹配到哪个字符(不包含此字符)默认匹配到最末位 |
|
|
|
|
* @param from 从哪个字符开始匹配,默认最末位 |
|
|
|
|
* @param to 匹配到哪个字符(不包含此字符)默认0 |
|
|
|
|
* @return 位置(正向计算) |
|
|
|
|
*/ |
|
|
|
|
private fun seekLast(str: String, key: String, from: Int, to: Int): Int { |
|
|
|
|
if (str.length - from < 1) return -1 |
|
|
|
|
var i = 0 |
|
|
|
|
if (from > i) i = from |
|
|
|
|
var i = str.lastIndex |
|
|
|
|
if (from < i && i > 0) i = from |
|
|
|
|
var t = 0 |
|
|
|
|
if (to > 0) t = to |
|
|
|
|
var c: Char |
|
|
|
@ -619,6 +687,9 @@ object ContentHelp { |
|
|
|
|
private const val MARK_QUOTATION = "\"“”" |
|
|
|
|
private val PARAGRAPH_DIAGLOG = "^[\"”“][^\"”“]+[\"”“]$".toRegex() |
|
|
|
|
|
|
|
|
|
// 限制字典的长度 |
|
|
|
|
private const val WORD_MAX_LENGTH = 16 |
|
|
|
|
|
|
|
|
|
private fun match(rule: String, chr: Char): Boolean { |
|
|
|
|
return rule.indexOf(chr) != -1 |
|
|
|
|
} |
|
|
|
|