diff --git a/app/src/main/java/io/legado/app/help/ContentHelp.kt b/app/src/main/java/io/legado/app/help/ContentHelp.kt index b0f0205bc..5ccf19b76 100644 --- a/app/src/main/java/io/legado/app/help/ContentHelp.kt +++ b/app/src/main/java/io/legado/app/help/ContentHelp.kt @@ -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,10 +178,12 @@ object ContentHelp { } // 对内容重新划分段落.输入参数str已经使用换行符预分割 - private fun findNewLines(str: String): String { + private fun findNewLines(str: String, dict: List): String { val string = StringBuffer(str) // 标记string中每个引号的位置.特别的,用引号进行列举时视为只有一对引号。 如:“锅”、“碗”视为“锅、碗”,从而避免误断句。 val arrayQuote: MutableList = ArrayList() + // 标记忽略的引号 + val array_ignore_quote: List = ArrayList() // 标记插入换行符的位置,int为插入位置(str的char下标) var insN = ArrayList() @@ -201,7 +203,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 +243,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 +271,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 +292,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=1 mod[size - 1] = -4 @@ -318,9 +330,39 @@ object ContentHelp { } } -// 第3次循环,匹配并插入换行。 -// "xxxx" xxxx。\n xxx“xxxx” -// 未实现 + //第3次循环,匹配并插入换行。 + //"xxxx" xxxx。\n xxx“xxxx” + //未实现 + + // 使用字典验证ins_n , 避免插入不必要的换行。 + // 由于目前没有插入、的列表,无法解决 “xx”、“xx”“xx” 被插入换行的问题 + val insN1 = ArrayList() + 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 +513,34 @@ object ContentHelp { return buffer.toString() } + /** + * 从字符串提取引号包围,且不止出现一次的内容为字典 + * + * @param str + * @return 词条列表 + */ + private fun makeDict(str: String): List { + + // 引号中间不包含任何标点 + 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 = ArrayList() + val dict: MutableList = 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 +580,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 +689,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 }