From 8ce3ff401a2fe9194691bac4d369124fa2a5a344 Mon Sep 17 00:00:00 2001 From: gedoor Date: Thu, 5 Nov 2020 22:23:52 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=AD=A3=E5=88=99=E5=B8=AE=E5=8A=A9=E9=93=BE?= =?UTF-8?q?=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/replace/edit/ReplaceEditActivity.kt | 12 ++++-- .../ui/replace/edit/ReplaceEditViewModel.kt | 41 ++++++++++--------- .../main/res/layout/activity_replace_edit.xml | 23 +++++++++-- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt index 90e1fcaa0..68ec64649 100644 --- a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt @@ -17,9 +17,11 @@ import io.legado.app.constant.EventBus import io.legado.app.data.entities.ReplaceRule import io.legado.app.ui.widget.KeyboardToolPop import io.legado.app.utils.getViewModel +import io.legado.app.utils.openUrl import io.legado.app.utils.postEvent import kotlinx.android.synthetic.main.activity_replace_edit.* import org.jetbrains.anko.displayMetrics +import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.toast import kotlin.math.abs @@ -58,10 +60,12 @@ class ReplaceEditActivity : override fun onActivityCreated(savedInstanceState: Bundle?) { mSoftKeyboardTool = KeyboardToolPop(this, AppConst.keyboardToolChars, this) window.decorView.viewTreeObserver.addOnGlobalLayoutListener(this) - viewModel.replaceRuleData.observe(this, { + viewModel.initData(intent) { upReplaceView(it) - }) - viewModel.initData(intent) + } + iv_help.onClick { + openUrl("https://www.runoob.com/regexp/regexp-syntax.html") + } } override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { @@ -96,7 +100,7 @@ class ReplaceEditActivity : } private fun getReplaceRule(): ReplaceRule { - val replaceRule: ReplaceRule = viewModel.replaceRuleData.value ?: ReplaceRule() + val replaceRule: ReplaceRule = viewModel.replaceRule ?: ReplaceRule() replaceRule.name = et_name.text.toString() replaceRule.group = et_group.text.toString() replaceRule.pattern = et_replace_rule.text.toString() diff --git a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt index 12df23446..83dd7bf74 100644 --- a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt @@ -2,35 +2,36 @@ package io.legado.app.ui.replace.edit import android.app.Application import android.content.Intent -import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.ReplaceRule class ReplaceEditViewModel(application: Application) : BaseViewModel(application) { - val replaceRuleData = MutableLiveData() + var replaceRule: ReplaceRule? = null - fun initData(intent: Intent) { + fun initData(intent: Intent, finally: (replaceRule: ReplaceRule) -> Unit) { execute { - replaceRuleData.value ?: let { - val id = intent.getLongExtra("id", -1) - if (id > 0) { - App.db.replaceRuleDao().findById(id)?.let { - replaceRuleData.postValue(it) - } - } else { - val pattern = intent.getStringExtra("pattern") ?: "" - val isRegex = intent.getBooleanExtra("isRegex", false) - val scope = intent.getStringExtra("scope") - val rule = ReplaceRule( - name = pattern, - pattern = pattern, - isRegex = isRegex, - scope = scope - ) - replaceRuleData.postValue(rule) + val id = intent.getLongExtra("id", -1) + if (id > 0) { + App.db.replaceRuleDao().findById(id)?.let { + replaceRule = it } + } else { + val pattern = intent.getStringExtra("pattern") ?: "" + val isRegex = intent.getBooleanExtra("isRegex", false) + val scope = intent.getStringExtra("scope") + val rule = ReplaceRule( + name = pattern, + pattern = pattern, + isRegex = isRegex, + scope = scope + ) + replaceRule = rule + } + }.onFinally { + replaceRule?.let { + finally(it) } } } diff --git a/app/src/main/res/layout/activity_replace_edit.xml b/app/src/main/res/layout/activity_replace_edit.xml index c6b80619d..af4253705 100644 --- a/app/src/main/res/layout/activity_replace_edit.xml +++ b/app/src/main/res/layout/activity_replace_edit.xml @@ -62,11 +62,28 @@ android:layout_height="wrap_content" /> - + android:orientation="horizontal" + android:gravity="center_vertical"> + + + + + + Date: Thu, 5 Nov 2020 22:25:34 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt index 83dd7bf74..65fa3c430 100644 --- a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt @@ -14,20 +14,17 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application execute { val id = intent.getLongExtra("id", -1) if (id > 0) { - App.db.replaceRuleDao().findById(id)?.let { - replaceRule = it - } + replaceRule = App.db.replaceRuleDao().findById(id) } else { val pattern = intent.getStringExtra("pattern") ?: "" val isRegex = intent.getBooleanExtra("isRegex", false) val scope = intent.getStringExtra("scope") - val rule = ReplaceRule( + replaceRule = ReplaceRule( name = pattern, pattern = pattern, isRegex = isRegex, scope = scope ) - replaceRule = rule } }.onFinally { replaceRule?.let { From 9fa734ab4c15773f770311b8b9ba4d70c5af93f2 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 6 Nov 2020 08:40:10 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=88=86=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/help/ContentHelp.kt | 111 +++++++++++++++--- 1 file changed, 92 insertions(+), 19 deletions(-) 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 } From ee7026924cdce7272d0c496147b7fcd2d21601bc Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 6 Nov 2020 08:43:50 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=88=86=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/help/ContentHelp.kt | 2 -- 1 file changed, 2 deletions(-) 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 5ccf19b76..ace595ff5 100644 --- a/app/src/main/java/io/legado/app/help/ContentHelp.kt +++ b/app/src/main/java/io/legado/app/help/ContentHelp.kt @@ -182,8 +182,6 @@ object ContentHelp { val string = StringBuffer(str) // 标记string中每个引号的位置.特别的,用引号进行列举时视为只有一对引号。 如:“锅”、“碗”视为“锅、碗”,从而避免误断句。 val arrayQuote: MutableList = ArrayList() - // 标记忽略的引号 - val array_ignore_quote: List = ArrayList() // 标记插入换行符的位置,int为插入位置(str的char下标) var insN = ArrayList() From b0f6df4a2574db5dae19113343148ed36ae8b846 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 6 Nov 2020 08:44:34 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=88=86=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/help/ContentHelp.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 ace595ff5..9c2b612bb 100644 --- a/app/src/main/java/io/legado/app/help/ContentHelp.kt +++ b/app/src/main/java/io/legado/app/help/ContentHelp.kt @@ -185,11 +185,11 @@ object ContentHelp { // 标记插入换行符的位置,int为插入位置(str的char下标) var insN = ArrayList() -// 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) { From 68b172649178731f5397f12a8d4ec465ce91379d Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 6 Nov 2020 09:23:07 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/help/regex.md | 354 ++++++++++++++++++ .../legado/app/data/dao/SearchKeywordDao.kt | 3 + .../java/io/legado/app/help/storage/Backup.kt | 2 + .../io/legado/app/help/storage/Restore.kt | 3 + .../ui/replace/edit/ReplaceEditActivity.kt | 5 +- 5 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 app/src/main/assets/help/regex.md diff --git a/app/src/main/assets/help/regex.md b/app/src/main/assets/help/regex.md new file mode 100644 index 000000000..846814aab --- /dev/null +++ b/app/src/main/assets/help/regex.md @@ -0,0 +1,354 @@ +# 目录 + +- [基本匹配](#1-基本匹配) +- [元字符](#2-元字符) + - [英文句号](#21-英文句号) + - [字符集](#22-字符集) + - [否定字符集](#221-否定字符集) + - [重复](#23-重复) + - [星号](#231-星号) + - [加号](#232-加号) + - [问号](#233-问号) + - [花括号](#24-花括号) + - [字符组](#25-字符组) + - [分支结构](#26-分支结构) + - [转义特殊字符](#27-转义特殊字符) + - [定位符](#28-定位符) + - [插入符号](#281-插入符号) + - [美元符号](#282-美元符号) +- [简写字符集](#3-简写字符集) +- [断言](#4-断言) + - [正向先行断言](#41-正向先行断言) + - [负向先行断言](#42-负向先行断言) + - [正向后行断言](#43-正向后行断言) + - [负向后行断言](#44-负向后行断言) +- [标记](#5-标记) + - [不区分大小写](#51-不区分大小写) + - [全局搜索](#52-全局搜索) + - [多行匹配](#53-多行匹配) +- [常用正则表达式](#常用正则表达式) + +## 1. 基本匹配 + +正则表达式只是我们用于在文本中检索字母和数字的模式。例如正则表达式 `cat`,表示: 字母 `c` 后面跟着一个字母 `a`,再后面跟着一个字母 `t`。 + +
+"cat" => The cat sat on the mat
+
+ +正则表达式 `123` 会匹配字符串 "123"。通过将正则表达式中的每个字符逐个与要匹配的字符串中的每个字符进行比较,来完成正则匹配。 +正则表达式通常区分大小写,因此正则表达式 `Cat` 与字符串 "cat" 不匹配。 + +
+"Cat" => The cat sat on the Cat
+
+ +## 2. 元字符 + +元字符是正则表达式的基本组成元素。元字符在这里跟它通常表达的意思不一样,而是以某种特殊的含义去解释。有些元字符写在方括号内的时候有特殊含义。 +元字符如下: + +|元字符|描述| +|:----:|----| +|.|匹配除换行符以外的任意字符。| +|[ ]|字符类,匹配方括号中包含的任意字符。| +|[^ ]|否定字符类。匹配方括号中不包含的任意字符| +|*|匹配前面的子表达式零次或多次| +|+|匹配前面的子表达式一次或多次| +|?|匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。| +|{n,m}|花括号,匹配前面字符至少 n 次,但是不超过 m 次。| +|(xyz)|字符组,按照确切的顺序匹配字符xyz。| +|||分支结构,匹配符号之前的字符或后面的字符。| +|\|转义符,它可以还原元字符原来的含义,允许你匹配保留字符 [ ] ( ) { } . * + ? ^ $ \ || +|^|匹配行的开始| +|$|匹配行的结束| + +## 2.1 英文句号 + +英文句号 `.` 是元字符的最简单的例子。元字符 `.` 可以匹配任意单个字符。它不会匹配换行符和新行的字符。例如正则表达式 `.ar`,表示: 任意字符后面跟着一个字母 `a`, +再后面跟着一个字母 `r`。 + +
+".ar" => The car parked in the garage.
+
+ +## 2.2 字符集 + +字符集也称为字符类。方括号被用于指定字符集。使用字符集内的连字符来指定字符范围。方括号内的字符范围的顺序并不重要。 +例如正则表达式 `[Tt]he`,表示: 大写 `T` 或小写 `t` ,后跟字母 `h`,再后跟字母 `e`。 + +
+"[Tt]he" => The car parked in the garage.
+
+ +然而,字符集中的英文句号表示它字面的含义。正则表达式 `ar[.]`,表示小写字母 `a`,后面跟着一个字母 `r`,再后面跟着一个英文句号 `.` 字符。 + +
+"ar[.]" => A garage is a good place to park a car.
+
+ +### 2.2.1 否定字符集 + +一般来说插入字符 `^` 表示一个字符串的开始,但是当它在方括号内出现时,它会取消字符集。例如正则表达式 `[^c]ar`,表示: 除了字母 `c` 以外的任意字符,后面跟着字符 `a`, +再后面跟着一个字母 `r`。 + +
+"[^c]ar" => The car parked in the garage.
+
+ +## 2.3 重复 + +以下元字符 `+`,`*` 或 `?` 用于指定子模式可以出现多少次。这些元字符在不同情况下的作用不同。 + +### 2.3.1 星号 + +该符号 `*` 表示匹配上一个匹配规则的零次或多次。正则表达式 `a*` 表示小写字母 `a` 可以重复零次或者多次。但是它如果出现在字符集或者字符类之后,它表示整个字符集的重复。 +例如正则表达式 `[a-z]*`,表示: 一行中可以包含任意数量的小写字母。 + +
+"[a-z]*" => The car parked in the garage #21.
+
+ +该 `*` 符号可以与元符号 `.` 用在一起,用来匹配任意字符串 `.*`。该 `*` 符号可以与空格符 `\s` 一起使用,用来匹配一串空格字符。 +例如正则表达式 `\s*cat\s*`,表示: 零个或多个空格,后面跟小写字母 `c`,再后面跟小写字母 `a`,再再后面跟小写字母 `t`,后面再跟零个或多个空格。 + +
+"\s*cat\s*" => The fat cat sat on the cat.
+
+ +### 2.3.2 加号 + +该符号 `+` 匹配上一个字符的一次或多次。例如正则表达式 `c.+t`,表示: 一个小写字母 `c`,后跟任意数量的字符,后跟小写字母 `t`。 + +
+"c.+t" => The fat cat sat on the mat.
+
+ +### 2.3.3 问号 + +在正则表达式中,元字符 `?` 用来表示前一个字符是可选的。该符号匹配前一个字符的零次或一次。 +例如正则表达式 `[T]?he`,表示: 可选的大写字母 `T`,后面跟小写字母 `h`,后跟小写字母 `e`。 + +
+"[T]he" => The car is parked in the garage.
+
+
+"[T]?he" => The car is parked in the garage.
+
+ +## 2.4 花括号 + +在正则表达式中花括号(也被称为量词 ?)用于指定字符或一组字符可以重复的次数。例如正则表达式 `[0-9]{2,3}`,表示: 匹配至少2位数字但不超过3位(0到9范围内的字符)。 + +
+"[0-9]{2,3}" => The number was 9.9997 but we rounded it off to 10.0.
+
+ +我们可以省略第二个数字。例如正则表达式 `[0-9]{2,}`,表示: 匹配2个或更多个数字。如果我们也删除逗号,则正则表达式 `[0-9]{2}`,表示: 匹配正好为2位数的数字。 + +
+"[0-9]{2,}" => The number was 9.9997 but we rounded it off to 10.0.
+
+ +
+"[0-9]{2}" => The number was 9.9997 but we rounded it off to 10.0.
+
+ +## 2.5 字符组 + +字符组是一组写在圆括号内的子模式 `(...)`。正如我们在正则表达式中讨论的那样,如果我们把一个量词放在一个字符之后,它会重复前一个字符。 +但是,如果我们把量词放在一个字符组之后,它会重复整个字符组。 +例如正则表达式 `(ab)*` 表示匹配零个或多个的字符串 "ab"。我们还可以在字符组中使用元字符 `|`。例如正则表达式 `(c|g|p)ar`,表示: 小写字母 `c`、`g` 或 `p` 后面跟字母 `a`,后跟字母 `r`。 + +
+"(c|g|p)ar" => The car is parked in the garage.
+
+ +## 2.6 分支结构 + +在正则表达式中垂直条 `|` 用来定义分支结构,分支结构就像多个表达式之间的条件。现在你可能认为这个字符集和分支机构的工作方式一样。 +但是字符集和分支结构巨大的区别是字符集只在字符级别上有作用,然而分支结构在表达式级别上依然可以使用。 +例如正则表达式 `(T|t)he|car`,表示: 大写字母 `T` 或小写字母 `t`,后面跟小写字母 `h`,后跟小写字母 `e` 或小写字母 `c`,后跟小写字母 `a`,后跟小写字母 `r`。 + +
+"(T|t)he|car" => The car is parked in the garage.
+
+ +## 2.7 转义特殊字符 + +正则表达式中使用反斜杠 `\` 来转义下一个字符。这将允许你使用保留字符来作为匹配字符 `{ } [ ] / \ + * . $ ^ | ?`。在特殊字符前面加 `\`,就可以使用它来做匹配字符。 +例如正则表达式 `.` 是用来匹配除了换行符以外的任意字符。现在要在输入字符串中匹配 `.` 字符,正则表达式 `(f|c|m)at\.?`,表示: 小写字母 `f`、`c` 或者 `m` 后跟小写字母 `a`,后跟小写字母 `t`,后跟可选的 `.` 字符。 + +
+"(f|c|m)at\.?" => The fat cat sat on the mat.
+
+ +## 2.8 定位符 + +在正则表达式中,为了检查匹配符号是否是起始符号或结尾符号,我们使用定位符。 +定位符有两种类型: 第一种类型是 `^` 检查匹配字符是否是起始字符,第二种类型是 `$`,它检查匹配字符是否是输入字符串的最后一个字符。 + +### 2.8.1 插入符号 + +插入符号 `^` 符号用于检查匹配字符是否是输入字符串的第一个字符。如果我们使用正则表达式 `^a` (如果a是起始符号)匹配字符串 `abc`,它会匹配到 `a`。 +但是如果我们使用正则表达式 `^b`,它是匹配不到任何东西的,因为在字符串 `abc` 中 "b" 不是起始字符。 +让我们来看看另一个正则表达式 `^(T|t)he`,这表示: 大写字母 `T` 或小写字母 `t` 是输入字符串的起始符号,后面跟着小写字母 `h`,后跟小写字母 `e`。 + +
+"(T|t)he" => The car is parked in the garage.
+
+ +
+"^(T|t)he" => The car is parked in the garage.
+
+ +### 2.8.2 美元符号 + +美元 `$` 符号用于检查匹配字符是否是输入字符串的最后一个字符。例如正则表达式 `(at\.)$`,表示: 小写字母 `a`,后跟小写字母 `t`,后跟一个 `.` 字符,且这个匹配器必须是字符串的结尾。 + +
+"(at\.)" => The fat cat. sat. on the mat.
+
+ +
+"(at\.)$" => The fat cat sat on the mat.
+
+ +## 3. 简写字符集 + +正则表达式为常用的字符集和常用的正则表达式提供了简写。简写字符集如下: + +|简写|描述| +|:----:|----| +|.|匹配除换行符以外的任意字符| +|\w|匹配所有字母和数字的字符: `[a-zA-Z0-9_]`| +|\W|匹配非字母和数字的字符: `[^\w]`| +|\d|匹配数字: `[0-9]`| +|\D|匹配非数字: `[^\d]`| +|\s|匹配空格符: `[\t\n\f\r\p{Z}]`| +|\S|匹配非空格符: `[^\s]`| + +## 4. 断言 + +后行断言和先行断言有时候被称为断言,它们是特殊类型的 ***非捕获组*** (用于匹配模式,但不包括在匹配列表中)。当我们在一种特定模式之前或者之后有这种模式时,会优先使用断言。 +例如我们想获取输入字符串 `$4.44 and $10.88` 中带有前缀 `$` 的所有数字。我们可以使用这个正则表达式 `(?<=\$)[0-9\.]*`,表示: 获取包含 `.` 字符且前缀为 `$` 的所有数字。 +以下是正则表达式中使用的断言: + +|符号|描述| +|:----:|----| +|?=|正向先行断言| +|?!|负向先行断言| +|?<=|正向后行断言| +|? +"(T|t)he(?=\sfat)" => The fat cat sat on the mat. + + +### 4.2 负向先行断言 + +当我们需要从输入字符串中获取不匹配表达式的内容时,使用负向先行断言。负向先行断言的定义跟我们定义的正向先行断言一样, +唯一的区别是不是等号 `=`,我们使用否定符号 `!`,例如 `(?!...)`。 +我们来看看下面的正则表达式 `(T|t)he(?!\sfat)`,表示: 从输入字符串中获取全部 `The` 或者 `the` 且不匹配 `fat` 前面加上一个空格字符。 + +
+"(T|t)he(?!\sfat)" => The fat cat sat on the mat.
+
+ +### 4.3 正向后行断言 + +正向后行断言是用于获取在特定模式之前的所有匹配内容。正向后行断言表示为 `(?<=...)`。例如正则表达式 `(?<=(T|t)he\s)(fat|mat)`,表示: 从输入字符串中获取在单词 `The` 或 `the` 之后的所有 `fat` 和 `mat` 单词。 + +
+"(?<=(T|t)he\s)(fat|mat)" => The fat cat sat on the mat.
+
+ +### 4.4 负向后行断言 + +负向后行断言是用于获取不在特定模式之前的所有匹配的内容。负向后行断言表示为 `(? +"(?<!(T|t)he\s)(cat)" => The cat sat on cat. + + +## 5. 标记 + +标记也称为修饰符,因为它会修改正则表达式的输出。这些标志可以以任意顺序或组合使用,并且是正则表达式的一部分。 + +|标记|描述| +|:----:|----| +|i|不区分大小写: 将匹配设置为不区分大小写。| +|g|全局搜索: 搜索整个输入字符串中的所有匹配。| +|m|多行匹配: 会匹配输入字符串每一行。| + +### 5.1 不区分大小写 + +`i` 修饰符用于执行不区分大小写匹配。例如正则表达式 `/The/gi`,表示: 大写字母 `T`,后跟小写字母 `h`,后跟字母 `e`。 +但是在正则匹配结束时 `i` 标记会告诉正则表达式引擎忽略这种情况。正如你所看到的,我们还使用了 `g` 标记,因为我们要在整个输入字符串中搜索匹配。 + +
+"The" => The fat cat sat on the mat.
+
+ +
+"/The/gi" => The fat cat sat on the mat.
+
+ +### 5.2 全局搜索 + +`g` 修饰符用于执行全局匹配 (会查找所有匹配,不会在查找到第一个匹配时就停止)。 +例如正则表达式 `/.(at)/g`,表示: 除换行符之外的任意字符,后跟小写字母 `a`,后跟小写字母 `t`。 +因为我们在正则表达式的末尾使用了 `g` 标记,它会从整个输入字符串中找到每个匹配项。 + +
+".(at)" => The fat cat sat on the mat.
+
+ +
+"/.(at)/g" => The fat cat sat on the mat.
+
+ +### 5.3 多行匹配 + +`m` 修饰符被用来执行多行的匹配。正如我们前面讨论过的 `(^, $)`,使用定位符来检查匹配字符是输入字符串开始或者结束。但是我们希望每一行都使用定位符,所以我们就使用 `m` 修饰符。 +例如正则表达式 `/at(.)?$/gm`,表示: 小写字母 `a`,后跟小写字母 `t`,匹配除了换行符以外任意字符零次或一次。而且因为 `m` 标记,现在正则表达式引擎匹配字符串中每一行的末尾。 + +
+"/.at(.)?$/" => The fat
+                cat sat
+                on the mat.
+
+ +
+"/.at(.)?$/gm" => The fat
+                  cat sat
+                  on the mat.
+
+ +## 常用正则表达式 + +* **正整数**: `^\d+$` +* **负整数**: `^-\d+$` +* **电话号码**: `^+?[\d\s]{3,}$` +* **电话代码**: `^+?[\d\s]+(?[\d\s]{10,}$` +* **整数**: `^-?\d+$` +* **用户名**: `^[\w\d_.]{4,16}$` +* **字母数字字符**: `^[a-zA-Z0-9]*$` +* **带空格的字母数字字符**: `^[a-zA-Z0-9 ]*$` +* **密码**: `^(?=^.{6,}$)((?=.*[A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z]))^.*$` +* **电子邮件**: `^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})*$` +* **IPv4 地址**: `^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))*$` +* **小写字母**: `^([a-z])*$` +* **大写字母**: `^([A-Z])*$` +* **网址**: `^(((http|https|ftp):\/\/)?([[a-zA-Z0-9]\-\.])+(\.)([[a-zA-Z0-9]]){2,4}([[a-zA-Z0-9]\/+=%&_\.~?\-]*))*$` +* **VISA 信用卡号码**: `^(4[0-9]{12}(?:[0-9]{3})?)*$` +* **日期 (MM/DD/YYYY)**: `^(0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])[- /.](19|20)?[0-9]{2}$` +* **日期 (YYYY/MM/DD)**: `^(19|20)?[0-9]{2}[- /.](0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])$` +* **万事达信用卡号码**: `^(5[1-5][0-9]{14})*$` \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/SearchKeywordDao.kt b/app/src/main/java/io/legado/app/data/dao/SearchKeywordDao.kt index be7ad75c5..d3063759a 100644 --- a/app/src/main/java/io/legado/app/data/dao/SearchKeywordDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/SearchKeywordDao.kt @@ -8,6 +8,9 @@ import io.legado.app.data.entities.SearchKeyword @Dao interface SearchKeywordDao { + @get:Query("SELECT * FROM search_keywords") + val all: List + @Query("SELECT * FROM search_keywords ORDER BY usage DESC") fun liveDataByUsage(): LiveData> diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index 85686887d..d6ba35ff8 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -33,6 +33,7 @@ object Backup { "rssStar.json", "replaceRule.json", "readRecord.json", + "searchHistory.json", DefaultData.txtTocRuleFileName, DefaultData.httpTtsFileName, ReadBookConfig.configFileName, @@ -64,6 +65,7 @@ object Backup { writeListToJson(App.db.rssStarDao().all, "rssStar.json", backupPath) writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath) writeListToJson(App.db.readRecordDao().all, "readRecord.json", backupPath) + writeListToJson(App.db.searchKeywordDao().all, "searchHistory.json", backupPath) writeListToJson(App.db.txtTocRule().all, DefaultData.txtTocRuleFileName, backupPath) writeListToJson(App.db.httpTTSDao().all, DefaultData.httpTtsFileName, backupPath) GSON.toJson(ReadBookConfig.configList).let { diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index e5a4d002c..cd100503b 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -129,6 +129,9 @@ object Restore { fileToListT(path, "replaceRule.json")?.let { App.db.replaceRuleDao().insert(*it.toTypedArray()) } + fileToListT(path, "searchHistory.json")?.let { + App.db.searchKeywordDao().insert(*it.toTypedArray()) + } fileToListT(path, DefaultData.txtTocRuleFileName)?.let { App.db.txtTocRule().insert(*it.toTypedArray()) } diff --git a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt index 68ec64649..d690a0b74 100644 --- a/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt @@ -16,8 +16,8 @@ import io.legado.app.constant.AppConst import io.legado.app.constant.EventBus import io.legado.app.data.entities.ReplaceRule import io.legado.app.ui.widget.KeyboardToolPop +import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.utils.getViewModel -import io.legado.app.utils.openUrl import io.legado.app.utils.postEvent import kotlinx.android.synthetic.main.activity_replace_edit.* import org.jetbrains.anko.displayMetrics @@ -64,7 +64,8 @@ class ReplaceEditActivity : upReplaceView(it) } iv_help.onClick { - openUrl("https://www.runoob.com/regexp/regexp-syntax.html") + val mdText = String(assets.open("help/regex.md").readBytes()) + TextDialog.show(supportFragmentManager, mdText, TextDialog.MD) } } From 10d0b78e26dcdd7844aec090b7a618bacad10828 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 6 Nov 2020 09:24:33 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/help/regex.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/help/regex.md b/app/src/main/assets/help/regex.md index 846814aab..93888dcf2 100644 --- a/app/src/main/assets/help/regex.md +++ b/app/src/main/assets/help/regex.md @@ -1,4 +1,4 @@ -# 目录 +## 正则表达式学习 - [基本匹配](#1-基本匹配) - [元字符](#2-元字符) From 920e509d7cdb4642c4de1e44836394ac3f36d673 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 6 Nov 2020 09:27:13 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/updateLog.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 6457a9413..294ca5cc1 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -3,9 +3,12 @@ * 关注合作公众号 **[小说拾遗]()** 获取好看的小说。 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 -**2020/11/03** +**2020/11/06** * 详情页菜单添加拷贝URL * 解决一些书名太长缓存报错的bug +* 添加备份搜索记录 +* 替换编辑界面添加正则学习教程 +* 自动分段优化 by [tumuyan](https://github.com/tumuyan) **2020/11/01** * 导入本地添加智能扫描,菜单-智能扫描,扫描当前文件夹包括子文件夹下所有文件 From 4a916704ad556f01049aa4e20f9fed450c7300d2 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 6 Nov 2020 15:21:40 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 6d723ce40..75e7bb108 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -48,6 +48,8 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r viewModel.initData(intent) } + @Suppress("DEPRECATION") + @SuppressLint("SwitchIntDef") override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) when (newConfig.orientation) { From 9c72fdec08c3cda7517e3f06810ba47b1d6c29f2 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 6 Nov 2020 16:40:21 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/utils/NetworkUtils.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/legado/app/utils/NetworkUtils.kt b/app/src/main/java/io/legado/app/utils/NetworkUtils.kt index 40ec8edc6..8379c2dd3 100644 --- a/app/src/main/java/io/legado/app/utils/NetworkUtils.kt +++ b/app/src/main/java/io/legado/app/utils/NetworkUtils.kt @@ -99,12 +99,11 @@ object NetworkUtils { } fun getSubDomain(url: String?): String { - var baseUrl = getBaseUrl(url) - if (baseUrl == null) return "" - return if (baseUrl.indexOf(".") == baseUrl.lastIndexOf(".")) { - baseUrl.substring(baseUrl.lastIndexOf("/")+1) - } else baseUrl.substring(baseUrl.indexOf(".")+1) - } + val baseUrl = getBaseUrl(url) ?: return "" + return if (baseUrl.indexOf(".") == baseUrl.lastIndexOf(".")) { + baseUrl.substring(baseUrl.lastIndexOf("/") + 1) + } else baseUrl.substring(baseUrl.indexOf(".") + 1) + } /** * Get local Ip address. From b15fcd4b9873f268099f36e5e6498b82d6d9f6f9 Mon Sep 17 00:00:00 2001 From: gedoor Date: Fri, 6 Nov 2020 17:05:24 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/model/analyzeRule/AnalyzeRule.kt | 26 ++++++++++++---- .../main/java/io/legado/app/model/rss/Rss.kt | 6 ++-- .../legado/app/model/rss/RssParserByRule.kt | 2 +- .../app/model/webBook/BookChapterList.kt | 2 +- .../legado/app/model/webBook/BookContent.kt | 4 +-- .../io/legado/app/model/webBook/BookInfo.kt | 2 +- .../io/legado/app/model/webBook/BookList.kt | 2 +- .../java/io/legado/app/utils/NetworkUtils.kt | 30 ++++++++++++++----- 8 files changed, 52 insertions(+), 22 deletions(-) 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 11dcbab2c..0d13ee94d 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 @@ -10,6 +10,7 @@ import io.legado.app.help.JsExtensions import io.legado.app.utils.* import org.jsoup.nodes.Entities import org.mozilla.javascript.NativeObject +import java.net.URL import java.util.* import java.util.regex.Pattern import javax.script.SimpleBindings @@ -25,6 +26,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { var chapter: BookChapter? = null private var content: Any? = null private var baseUrl: String? = null + private var baseURL: URL? = null private var isJSON: Boolean = false private var isRegex: Boolean = false @@ -37,18 +39,28 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { private var objectChangedJP = false @Throws(Exception::class) - @JvmOverloads - fun setContent(content: Any?, baseUrl: String? = this.baseUrl): AnalyzeRule { + fun setContent(content: Any?): AnalyzeRule { if (content == null) throw AssertionError("Content cannot be null") isJSON = content.toString().isJson() this.content = content - this.baseUrl = baseUrl objectChangedXP = true objectChangedJS = true objectChangedJP = true return this } + fun setBaseUrl(baseUrl: String?): AnalyzeRule { + this.baseUrl = baseUrl + baseUrl?.let { + try { + baseURL = URL(baseUrl.substringBefore(",")) + } catch (e: Exception) { + e.printStackTrace() + } + } + return this + } + /** * 获取XPath解析类 */ @@ -157,7 +169,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { val urlList = ArrayList() if (result is List<*>) { for (url in result as List<*>) { - val absoluteURL = NetworkUtils.getAbsoluteURL(baseUrl, url.toString()) + val absoluteURL = NetworkUtils.getAbsoluteURL(baseURL, url.toString()) if (!absoluteURL.isNullOrEmpty() && !urlList.contains(absoluteURL)) { urlList.add(absoluteURL) } @@ -220,7 +232,11 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { result.toString() } if (isUrl) { - return NetworkUtils.getAbsoluteURL(baseUrl, str) ?: "" + return if (str.isBlank()) { + baseUrl ?: "" + } else { + NetworkUtils.getAbsoluteURL(baseURL, str) ?: "" + } } return str } diff --git a/app/src/main/java/io/legado/app/model/rss/Rss.kt b/app/src/main/java/io/legado/app/model/rss/Rss.kt index 830f72a80..6dee8a9d0 100644 --- a/app/src/main/java/io/legado/app/model/rss/Rss.kt +++ b/app/src/main/java/io/legado/app/model/rss/Rss.kt @@ -45,10 +45,8 @@ object Rss { ).getResponseAwait(rssArticle.origin) .body val analyzeRule = AnalyzeRule() - analyzeRule.setContent( - body, - NetworkUtils.getAbsoluteURL(rssArticle.origin, rssArticle.link) - ) + analyzeRule.setContent(body) + .setBaseUrl(NetworkUtils.getAbsoluteURL(rssArticle.origin, rssArticle.link)) analyzeRule.getString(ruleContent) } } diff --git a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt index 82ab063ae..955df439b 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt @@ -38,7 +38,7 @@ object RssParserByRule { } else { val articleList = mutableListOf() val analyzeRule = AnalyzeRule() - analyzeRule.setContent(body, sortUrl) + analyzeRule.setContent(body).setBaseUrl(sortUrl) var reverse = false if (ruleArticles.startsWith("-")) { reverse = true diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt index 78a139345..16ffa803a 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt @@ -210,7 +210,7 @@ object BookChapterList { log: Boolean = false ): ChapterData> { val analyzeRule = AnalyzeRule(book) - analyzeRule.setContent(body, baseUrl) + analyzeRule.setContent(body).setBaseUrl(baseUrl) val chapterList = arrayListOf() val nextUrlList = arrayListOf() val nextTocRule = tocRule.nextTocUrl diff --git a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt index c2ae45071..50b895ec7 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt @@ -97,7 +97,7 @@ object BookContent { val replaceRegex = bookSource.ruleContent?.replaceRegex if (!replaceRegex.isNullOrEmpty()) { val analyzeRule = AnalyzeRule(book) - analyzeRule.setContent(contentStr, baseUrl) + analyzeRule.setContent(contentStr).setBaseUrl(baseUrl) analyzeRule.chapter = bookChapter contentStr = analyzeRule.getString(replaceRegex) } @@ -119,7 +119,7 @@ object BookContent { printLog: Boolean = true ): ContentData> { val analyzeRule = AnalyzeRule(book) - analyzeRule.setContent(body, baseUrl) + analyzeRule.setContent(body).setBaseUrl(baseUrl) val nextUrlList = arrayListOf() analyzeRule.chapter = chapter val nextUrlRule = contentRule.nextContentUrl diff --git a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt index 7d6c3a94c..24dd869e6 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt @@ -27,7 +27,7 @@ object BookInfo { Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}") val infoRule = bookSource.getBookInfoRule() val analyzeRule = AnalyzeRule(book) - analyzeRule.setContent(body, baseUrl) + analyzeRule.setContent(body).setBaseUrl(baseUrl) infoRule.init?.let { if (it.isNotEmpty()) { Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则") diff --git a/app/src/main/java/io/legado/app/model/webBook/BookList.kt b/app/src/main/java/io/legado/app/model/webBook/BookList.kt index 05570b6ff..fd2697587 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookList.kt @@ -38,7 +38,7 @@ object BookList { Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}") if (!scope.isActive) throw CancellationException() val analyzeRule = AnalyzeRule(variableBook) - analyzeRule.setContent(body, baseUrl) + analyzeRule.setContent(body).setBaseUrl(baseUrl) bookSource.bookUrlPattern?.let { if (baseUrl.matches(it.toRegex())) { Debug.log(bookSource.bookSourceUrl, "≡链接为详情页") diff --git a/app/src/main/java/io/legado/app/utils/NetworkUtils.kt b/app/src/main/java/io/legado/app/utils/NetworkUtils.kt index 8379c2dd3..3b9ae2ed8 100644 --- a/app/src/main/java/io/legado/app/utils/NetworkUtils.kt +++ b/app/src/main/java/io/legado/app/utils/NetworkUtils.kt @@ -90,6 +90,22 @@ object NetworkUtils { return relativeUrl } + /** + * 获取绝对地址 + */ + fun getAbsoluteURL(baseURL: URL?, relativePath: String): String? { + if (baseURL == null) return relativePath + var relativeUrl = relativePath + try { + val parseUrl = URL(baseURL, relativePath) + relativeUrl = parseUrl.toString() + return relativeUrl + } catch (e: Exception) { + e.printStackTrace() + } + return relativeUrl + } + fun getBaseUrl(url: String?): String? { if (url == null || !url.startsWith("http")) return null val index = url.indexOf("/", 9) @@ -98,13 +114,13 @@ object NetworkUtils { } else url.substring(0, index) } - fun getSubDomain(url: String?): String { - val baseUrl = getBaseUrl(url) ?: return "" - return if (baseUrl.indexOf(".") == baseUrl.lastIndexOf(".")) { - baseUrl.substring(baseUrl.lastIndexOf("/") + 1) - } else baseUrl.substring(baseUrl.indexOf(".") + 1) - } - + fun getSubDomain(url: String?): String { + val baseUrl = getBaseUrl(url) ?: return "" + return if (baseUrl.indexOf(".") == baseUrl.lastIndexOf(".")) { + baseUrl.substring(baseUrl.lastIndexOf("/") + 1) + } else baseUrl.substring(baseUrl.indexOf(".") + 1) + } + /** * Get local Ip address. */