From 68e9ade4ac39a76e8b084a3f8c086e8f3c660b45 Mon Sep 17 00:00:00 2001 From: gedoor Date: Sun, 18 Jul 2021 15:26:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/data/entities/BookChapter.kt | 52 +++++----- .../app/model/analyzeRule/AnalyzeRule.kt | 98 ++++++++++--------- .../app/model/analyzeRule/AnalyzeUrl.kt | 19 ++-- .../app/model/analyzeRule/RuleAnalyzer.kt | 91 +++++++++++------ .../io/legado/app/service/help/ReadBook.kt | 7 +- .../read/page/provider/ChapterProvider.kt | 2 +- .../java/io/legado/app/utils/HtmlFormatter.kt | 16 ++- 7 files changed, 163 insertions(+), 122 deletions(-) diff --git a/app/src/main/java/io/legado/app/data/entities/BookChapter.kt b/app/src/main/java/io/legado/app/data/entities/BookChapter.kt index c6b4cb9fb..e42f9795c 100644 --- a/app/src/main/java/io/legado/app/data/entities/BookChapter.kt +++ b/app/src/main/java/io/legado/app/data/entities/BookChapter.kt @@ -14,30 +14,30 @@ import kotlinx.parcelize.Parcelize @Parcelize @Entity( - tableName = "chapters", - primaryKeys = ["url", "bookUrl"], - indices = [(Index(value = ["bookUrl"], unique = false)), - (Index(value = ["bookUrl", "index"], unique = true))], - foreignKeys = [(ForeignKey( - entity = Book::class, - parentColumns = ["bookUrl"], - childColumns = ["bookUrl"], - onDelete = ForeignKey.CASCADE - ))] + tableName = "chapters", + primaryKeys = ["url", "bookUrl"], + indices = [(Index(value = ["bookUrl"], unique = false)), + (Index(value = ["bookUrl", "index"], unique = true))], + foreignKeys = [(ForeignKey( + entity = Book::class, + parentColumns = ["bookUrl"], + childColumns = ["bookUrl"], + onDelete = ForeignKey.CASCADE + ))] ) // 删除书籍时自动删除章节 data class BookChapter( - var url: String = "", // 章节地址 - var title: String = "", // 章节标题 - var baseUrl: String = "", //用来拼接相对url - var bookUrl: String = "", // 书籍地址 - var index: Int = 0, // 章节序号 - var resourceUrl: String? = null, // 音频真实URL - var tag: String? = null, // - var start: Long? = null, // 章节起始位置 - var end: Long? = null, // 章节终止位置 - var startFragmentId: String? = null, //EPUB书籍当前章节的fragmentId - var endFragmentId: String? = null, //EPUB书籍下一章节的fragmentId - var variable: String? = null //变量 + var url: String = "", // 章节地址 + var title: String = "", // 章节标题 + var baseUrl: String = "", //用来拼接相对url + var bookUrl: String = "", // 书籍地址 + var index: Int = 0, // 章节序号 + var resourceUrl: String? = null, // 音频真实URL + var tag: String? = null, // + var start: Long? = null, // 章节起始位置 + var end: Long? = null, // 章节终止位置 + var startFragmentId: String? = null, //EPUB书籍当前章节的fragmentId + var endFragmentId: String? = null, //EPUB书籍下一章节的fragmentId + var variable: String? = null //变量 ) : Parcelable { @delegate:Transient @@ -61,10 +61,12 @@ data class BookChapter( return false } - fun getAbsoluteURL():String{ + fun getAbsoluteURL(): String { val urlBefore = url.substringBefore(',') - val urlAbsoluteBefore = NetworkUtils.getAbsoluteURL(baseUrl,urlBefore) - return if(urlBefore.length == url.length) urlAbsoluteBefore else urlAbsoluteBefore + url.substring(urlBefore.length) + val urlAbsoluteBefore = NetworkUtils.getAbsoluteURL(baseUrl, urlBefore) + return if (urlBefore.length == url.length) urlAbsoluteBefore else urlAbsoluteBefore + url.substring( + urlBefore.length + ) } fun getFileName(): String = String.format("%05d-%s.nb", index, MD5Utils.md5Encode16(title)) diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt index 1ad285563..6ea166857 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt @@ -121,7 +121,7 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions { @JvmOverloads fun getStringList(rule: String?, isUrl: Boolean = false): List? { if (rule.isNullOrEmpty()) return null - val ruleList = splitSourceRule(rule,true) + val ruleList = splitSourceRule(rule, true) return getStringList(ruleList, isUrl) } @@ -281,7 +281,7 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions { @Suppress("UNCHECKED_CAST") fun getElements(ruleStr: String): List { var result: Any? = null - val ruleList = splitSourceRule(ruleStr,true) + val ruleList = splitSourceRule(ruleStr, true) content?.let { o -> if (ruleList.isNotEmpty()) result = o for (sourceRule in ruleList) { @@ -356,61 +356,34 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions { /** * 分解规则生成规则列表 */ - fun splitSourceRule(ruleStr: String?,isList:Boolean = false): List { + fun splitSourceRule(ruleStr: String?, isList: Boolean = false): List { if (ruleStr.isNullOrEmpty()) return ArrayList() val ruleList = ArrayList() - //检测Mode var mMode: Mode = Mode.Default - fun mode(ruleStr0:String)=when { - ruleStr0.startsWith("@@") -> { - mMode = Mode.Default - ruleStr0.substring(2) - } - ruleStr0.startsWith("@XPath:", true) -> { - mMode = Mode.XPath - ruleStr0.substring(7) - } - ruleStr0.startsWith("/") -> {//XPath特征很明显,无需配置单独的识别标头 - mMode = Mode.XPath - ruleStr0 - } - ruleStr0.startsWith("@Json:", true) -> { - mMode = Mode.Json - ruleStr0.substring(6) - } - ( ruleStr0[1] == '.' || ruleStr0[1] == '[') && ruleStr0[0] == '$' || isJSON -> { - mMode = Mode.Json - ruleStr0 - } - else -> { - mMode = Mode.Default - ruleStr0 - } - } - - //拆分为规则列表 - var start = if(isList && ruleStr.startsWith(":")){ //仅首字符为:时为AllInOne,其实:与伪类选择器冲突,建议改成?更合理 + var start = 0 + //仅首字符为:时为AllInOne,其实:与伪类选择器冲突,建议改成?更合理 + if (isList && ruleStr.startsWith(":")) { mMode = Mode.Regex isRegex = true - 1 - }else 0 + start = 1 + } var tmp: String val jsMatcher = JS_PATTERN.matcher(ruleStr) - while (jsMatcher.find()){ + while (jsMatcher.find()) { if (jsMatcher.start() > start) { tmp = ruleStr.substring(start, jsMatcher.start()).trim { it <= ' ' } if (tmp.isNotEmpty()) { - ruleList.add(SourceRule(mode(tmp), mMode)) + ruleList.add(SourceRule(tmp, mMode)) } } - ruleList.add(SourceRule(jsMatcher.group(2)?:jsMatcher.group(1), Mode.Js)) + ruleList.add(SourceRule(jsMatcher.group(2) ?: jsMatcher.group(1), Mode.Js)) start = jsMatcher.end() } - if (ruleStr.length > start){ + if (ruleStr.length > start) { tmp = ruleStr.substring(start).trim { it <= ' ' } if (tmp.isNotEmpty()) { - ruleList.add(SourceRule(mode(tmp), mMode)) + ruleList.add(SourceRule(tmp, mMode)) } } @@ -436,22 +409,51 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions { init { this.mode = mainMode + rule = when { + ruleStr.startsWith("@CSS:", true) -> { + mode = Mode.Default + ruleStr + } + ruleStr.startsWith("@@") -> { + mode = Mode.Default + ruleStr.substring(2) + } + ruleStr.startsWith("@XPath:", true) -> { + mode = Mode.XPath + ruleStr.substring(7) + } + ruleStr.startsWith("/") -> {//XPath特征很明显,无需配置单独的识别标头 + mode = Mode.XPath + ruleStr + } + ruleStr.startsWith("@Json:", true) -> { + mode = Mode.Json + ruleStr.substring(6) + } + (ruleStr[1] == '.' || ruleStr[1] == '[') && ruleStr.startsWith("$.") || isJSON -> { + mode = Mode.Json + ruleStr + } + else -> { + ruleStr + } + } //分离put - rule = splitPutRule(ruleStr, putMap) + rule = splitPutRule(rule, putMap) //@get,{{ }}, 拆分 var start = 0 var tmp: String val evalMatcher = evalPattern.matcher(rule) - if(evalMatcher.find()){ + if (evalMatcher.find()) { var modeX = mode == Mode.Js || mode == Mode.Regex - if (evalMatcher.start() > 0 ) { + if (evalMatcher.start() > 0) { tmp = rule.substring(0, evalMatcher.start()) modeX = modeX || tmp.contains("##") splitRegex(tmp) } - if(!modeX)mode = Mode.Regex + if (!modeX) mode = Mode.Regex tmp = evalMatcher.group() when { @@ -470,7 +472,7 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions { start = evalMatcher.end() - while (evalMatcher.find()){ + while (evalMatcher.find()) { if (evalMatcher.start() > start) { tmp = rule.substring(start, evalMatcher.start()) splitRegex(tmp) @@ -508,11 +510,11 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions { val ruleStrArray = ruleStr.split("##") val regexMatcher = regexPattern.matcher(ruleStrArray[0]) - if(regexMatcher.find()) { + if (regexMatcher.find()) { if (mode != Mode.Js && mode != Mode.Regex) { mode = Mode.Regex } - do{ + do { if (regexMatcher.start() > start) { tmp = ruleStr.substring(start, regexMatcher.start()) ruleType.add(defaultRuleType) @@ -522,7 +524,7 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions { ruleType.add(tmp.substring(1).toInt()) ruleParam.add(tmp) start = regexMatcher.end() - }while (regexMatcher.find()) + } while (regexMatcher.find()) } if (ruleStr.length > start) { tmp = ruleStr.substring(start) diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index 90b4be570..78a1fd56d 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt @@ -82,7 +82,7 @@ class AnalyzeUrl( ruleUrl = tmp.replace("@result", ruleUrl) } } - ruleUrl = evalJS(jsMatcher.group(2)?:jsMatcher.group(1), ruleUrl) as String + ruleUrl = evalJS(jsMatcher.group(2) ?: jsMatcher.group(1), ruleUrl) as String start = jsMatcher.end() } if (ruleUrl.length > start) { @@ -114,14 +114,14 @@ class AnalyzeUrl( bindings["book"] = book //替换所有内嵌{{js}} - val url = analyze.innerRule("{{","}}"){ - when(val jsEval = SCRIPT_ENGINE.eval(it, bindings)){ + val url = analyze.innerRule("{{", "}}") { + when (val jsEval = SCRIPT_ENGINE.eval(it, bindings)) { is String -> jsEval jsEval is Double && jsEval % 1.0 == 0.0 -> String.format("%.0f", jsEval) else -> jsEval.toString() } } - if(url.isNotEmpty())ruleUrl = url + if (url.isNotEmpty()) ruleUrl = url } //page page?.let { @@ -143,13 +143,14 @@ class AnalyzeUrl( private fun initUrl() { //replaceKeyPageJs已经替换掉额外内容,此处url是基础形式,可以直接切首个‘,’之前字符串。 urlHasQuery = ruleUrl.substringBefore(',') - url = NetworkUtils.getAbsoluteURL(baseUrl,urlHasQuery ) + url = NetworkUtils.getAbsoluteURL(baseUrl, urlHasQuery) NetworkUtils.getBaseUrl(url)?.let { baseUrl = it } - if(urlHasQuery.length != ruleUrl.length ) { - GSON.fromJsonObject(ruleUrl.substring(urlHasQuery.length + 1).trim{ it < '!'})?.let { option -> + if (urlHasQuery.length != ruleUrl.length) { + GSON.fromJsonObject( + ruleUrl.substring(urlHasQuery.length + 1).trim { it < '!' })?.let { option -> option.method?.let { if (it.equals("POST", true)) method = RequestMethod.POST @@ -188,9 +189,9 @@ class AnalyzeUrl( RequestMethod.GET -> { if (!useWebView) { val pos = url.indexOf('?') - if(pos != -1) { + if (pos != -1) { analyzeFields(url.substring(pos + 1)) - url = url.substring(0,pos) + url = url.substring(0, pos) } } } diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/RuleAnalyzer.kt b/app/src/main/java/io/legado/app/model/analyzeRule/RuleAnalyzer.kt index e68df65b3..676ce5cef 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/RuleAnalyzer.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/RuleAnalyzer.kt @@ -1,8 +1,9 @@ package io.legado.app.model.analyzeRule import io.legado.app.utils.isJson -import java.util.ArrayList +import java.util.* import java.util.regex.Pattern +import kotlin.collections.HashMap //通用的规则切分处理 @@ -70,6 +71,7 @@ class RuleAnalyzer(data: String, code: Boolean = false) { } return false } + /** * 从剩余字串中拉出一个字符串,直到但不包括匹配序列(匹配参数列表中一项即为匹配),或剩余字串用完。 * @param seq 匹配字符串序列 @@ -369,8 +371,8 @@ class RuleAnalyzer(data: String, code: Boolean = false) { * * */ fun innerRule( - startStr:String, - endStr:String, + startStr: String, + endStr: String, fr: (String) -> String? ): String { @@ -380,13 +382,18 @@ class RuleAnalyzer(data: String, code: Boolean = false) { val posPre = pos //记录consumeTo匹配位置 if (consumeTo(endStr)) { val frv = fr(queue.substring(posPre, pos)) - st.append(queue.substring(startX, posPre - startStr.length) + frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串 - pos += endStr.length //跳过结束字符串 - startX = pos //记录下次规则起点 + st.append( + queue.substring( + startX, + posPre - startStr.length + ) + frv + ) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串 + pos += endStr.length //跳过结束字符串 + startX = pos //记录下次规则起点 } } - return if(startX == 0) queue else st.apply { + return if (startX == 0) queue else st.apply { append(queue.substring(startX)) }.toString() } @@ -394,8 +401,10 @@ class RuleAnalyzer(data: String, code: Boolean = false) { //-----------此处向下的函数和变量都未被使用,但以后要用-------- val ruleTypeList = ArrayList() + //设置平衡组函数,json或JavaScript时设置成chompCodeBalanced,否则为chompRuleBalanced val chompBalanced = if (code) ::chompCodeBalanced else ::chompRuleBalanced + enum class Mode { XPath, Json, Default, Js, Regex } @@ -450,11 +459,11 @@ class RuleAnalyzer(data: String, code: Boolean = false) { pos = st //位置回到筛选器处 val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符 - if (!chompBalanced(queue[pos], next)){ + if (!chompBalanced(queue[pos], next)) { ruleTypeList.clear() rule.clear() - consumeToAny("","@js:") - rule += queue.substring(0,pos) + consumeToAny("", "@js:") + rule += queue.substring(0, pos) ruleTypeList += queue.substring(pos, pos + 4) //设置组合类型 } @@ -472,7 +481,7 @@ class RuleAnalyzer(data: String, code: Boolean = false) { var isMulu = false var isreverse = false - var isAllInOne= false + var isAllInOne = false var isFind = false private val findName = ArrayList() @@ -488,15 +497,15 @@ class RuleAnalyzer(data: String, code: Boolean = false) { private val defaultRuleType = 0 @JvmOverloads - fun setContent(cont: String,type:String = ""): RuleAnalyzer { + fun setContent(cont: String, type: String = ""): RuleAnalyzer { queue = cont - when(type){ + when (type) { "mulu" -> { - if(queue[0] =='-'){ //目录反转 + if (queue[0] == '-') { //目录反转 isreverse = true startX++ pos++ - }else if(queue[0] =='?'){ //AllInOne + } else if (queue[0] == '?') { //AllInOne isAllInOne = true startX++ pos++ @@ -505,8 +514,8 @@ class RuleAnalyzer(data: String, code: Boolean = false) { } "find" -> { pos = queue.indexOf("::") - findName.add(queue.substring(startX,pos)) - pos+=2 + findName.add(queue.substring(startX, pos)) + pos += 2 isFind = true } "url" -> { @@ -538,14 +547,15 @@ class RuleAnalyzer(data: String, code: Boolean = false) { /** * 参数字符串 */ - private val STARTSTRURL = arrayOf(",{",) + private val STARTSTRURL = arrayOf(",{") private val regexPattern = Pattern.compile("\\$\\d{1,2}") private val putPattern = Pattern.compile("@put:(\\{[^}]+?\\})", Pattern.CASE_INSENSITIVE) private val getPattern = Pattern.compile("@get:\\{([^}]+?)\\}", Pattern.CASE_INSENSITIVE) - private val evalPattern = Pattern.compile("\\{\\{[\\w\\W]*?\\}\\}", Pattern.CASE_INSENSITIVE) + private val evalPattern = + Pattern.compile("\\{\\{[\\w\\W]*?\\}\\}", Pattern.CASE_INSENSITIVE) - val ENDSTR= mapOf( + val ENDSTR = mapOf( "" to "", "{{" to "}}", ) @@ -553,18 +563,35 @@ class RuleAnalyzer(data: String, code: Boolean = false) { /** * 规则起始字符串 */ - private val STARTSTR = arrayOf("@js:","","","##","@@","@" - ,"{{@", "{{","}}" - ,"}" - , "{@", "{/", "{$" - , "{class" - , "{id" - , "{tag" - , "{text" - , "{children" - ,"/","$","@xpath:","@json:","@css:" - ,"||", "&&", "%%" - ,"@get:{","@put:{" + private val STARTSTR = arrayOf( + "@js:", + "", + "", + "##", + "@@", + "@", + "{{@", + "{{", + "}}", + "}", + "{@", + "{/", + "{$", + "{class", + "{id", + "{tag", + "{text", + "{children", + "/", + "$", + "@xpath:", + "@json:", + "@css:", + "||", + "&&", + "%%", + "@get:{", + "@put:{" ) /** diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index f2b765b9b..c1d6fc2ca 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt @@ -415,10 +415,11 @@ object ReadBook { else -> chapter.title } val contents = contentProcessor!!.getContent(book, chapter.title, content) - val textChapter = ChapterProvider.getTextChapter(book, chapter,contents,chapterSize) + val textChapter = + ChapterProvider.getTextChapter(book, chapter, contents, chapterSize) val offset = chapter.index - durChapterIndex - if (upContent) callBack?.upContent(offset ,resetPageOffset) + if (upContent) callBack?.upContent(offset, resetPageOffset) when (offset) { 0 -> { curTextChapter = textChapter @@ -426,7 +427,7 @@ object ReadBook { curPageChanged() callBack?.contentLoadFinish() } - - 1 -> { + -1 -> { prevTextChapter = textChapter } 1 -> { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index c3bbf8b6e..2672849d2 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -113,7 +113,7 @@ object ChapterProvider { } else if (book.getImageStyle() != Book.imgStyleText) { content.replace(AppPattern.imgPattern.toRegex(), "\n\$0\n").split("\n") .forEach { text -> - if(text.isNotBlank()){ + if (text.isNotBlank()) { if (!text.startsWith("]*data-[^=]*= *\"([^\"])\"[^>]*>","]*src *= *\"([^\"{]+(?:\\{(?:[^{}]|\\{[^{}]*\\})*\\})?)\"[^>]*>")){ + for (pattern in listOf( + "]*data-[^=]*= *\"([^\"])\"[^>]*>", + "]*src *= *\"([^\"{]+(?:\\{(?:[^{}]|\\{[^{}]*\\})*\\})?)\"[^>]*>" + )) { var appendPos = 0 val matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).matcher(keepImgHtml) while (matcher.find()) { @@ -36,7 +39,10 @@ object HtmlFormatter { val urlBefore = url.substringBefore(',') val strBefore = keepImgHtml.substring(appendPos, matcher.start()) sb.append( - if(strBefore.isBlank()) strBefore else strBefore.replace("\n", "\n  ") //缩进图片之间的非空白段落 + if (strBefore.isBlank()) strBefore else strBefore.replace( + "\n", + "\n  " + ) //缩进图片之间的非空白段落 ) sb.append( "