diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt index b2704513a..36c5c2840 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt @@ -30,7 +30,7 @@ class AnalyzeByJSonPath(json: Any) { fun getString(rule: String): String? { if (rule.isEmpty()) return null var result: String - val ruleAnalyzes = RuleAnalyzer(rule) + val ruleAnalyzes = RuleAnalyzer(rule,true) //设置平衡组为代码平衡 val rules = ruleAnalyzes.splitRule("&&","||") if (rules.size == 1) { @@ -83,7 +83,7 @@ class AnalyzeByJSonPath(json: Any) { internal fun getStringList(rule: String): List { val result = ArrayList() if (rule.isEmpty()) return result - val ruleAnalyzes = RuleAnalyzer(rule) + val ruleAnalyzes = RuleAnalyzer(rule,true) //设置平衡组为代码平衡 val rules = ruleAnalyzes.splitRule("&&","||","%%") if (rules.size == 1) { @@ -148,7 +148,7 @@ class AnalyzeByJSonPath(json: Any) { internal fun getList(rule: String): ArrayList? { val result = ArrayList() if (rule.isEmpty()) return result - val ruleAnalyzes = RuleAnalyzer(rule) + val ruleAnalyzes = RuleAnalyzer(rule,true) //设置平衡组为代码平衡 val rules = ruleAnalyzes.splitRule("&&","||","%%") if (rules.size == 1) { ctx.let { diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt index 6d59f8133..0d5438348 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt @@ -1,6 +1,5 @@ package io.legado.app.model.analyzeRule -import android.text.TextUtils.join import androidx.annotation.Keep import org.jsoup.Jsoup import org.jsoup.nodes.Element @@ -230,7 +229,7 @@ class AnalyzeByJSoup(doc: Any) { tn.add(temp) } } - textS.add(join("\n", tn)) + textS.add(tn.joinToString("\n")) } "ownText" -> for (element in elements) { textS.add(element.ownText()) @@ -258,35 +257,104 @@ class AnalyzeByJSoup(doc: Any) { return textS } + /** + * 1.支持阅读原有写法,':'分隔索引,!或.表示筛选方式,索引可为负数 + * + * 例如 tag.div.-1:10:2 或 tag.div!0:3 + * + * 2. 支持与jsonPath类似的[]索引写法 + * + * 格式形如 [it,it,。。。] 或 [!it,it,。。。] 其中[!开头表示筛选方式为排除,it为单个索引或区间。 + * + * 区间格式为 start:end 或 start:end:step,其中start为0可省略,end为-1可省略。 + * + * 索引,区间两端及间隔都支持负数 + * + * 例如 tag.div[-1, 3:-2:-10, 2] + * + * 特殊用法 tag.div[-1:0] 可在任意地方让列表反向 + * + * */ data class ElementsSingle(var split:Char = '.', var beforeRule:String = "", val indexDefault:MutableList = mutableListOf(), val indexs:MutableList = mutableListOf()){ - /** * 获取Elements按照一个规则 */ fun getElementsSingle(temp: Element, rule: String): Elements { - var elements = Elements() - findIndexSet(rule) //执行索引列表处理器 - val rules = beforeRule.split(".") - - elements.addAll( - if(beforeRule.isEmpty()) temp.children() //允许索引直接作为根元素,此时前置规则为空,效果与children相同 - else when (rules[0]) { + /** + * 获取所有元素 + * */ + var elements = if (beforeRule.isEmpty()) temp.children() //允许索引直接作为根元素,此时前置规则为空,效果与children相同 + else { + val rules = beforeRule.split(".") + when (rules[0]) { "children" -> temp.children() //允许索引直接作为根元素,此时前置规则为空,效果与children相同 "class" -> temp.getElementsByClass(rules[1]) "tag" -> temp.getElementsByTag(rules[1]) "id" -> Collector.collect(Evaluator.Id(rules[1]), temp) "text" -> temp.getElementsContainingOwnText(rules[1]) else -> temp.select(beforeRule) - } ) + } + } + + val len = elements.size + val lastIndexs = (indexDefault.size - 1).takeIf { it !=-1 } ?: indexs.size -1 + val indexSet = mutableSetOf() + + /** + * 获取无重且不越界的索引集合 + * */ + if(indexs.isEmpty())for (ix in lastIndexs downTo 0 ){ //indexs为空,表明是非[]式索引,集合是逆向遍历插入的,所以这里也逆向遍历,好还原顺序 - val indexSet = getIndexs(elements.size) //传入元素数量,处理负数索引及索引越界问题,生成可用索引集合。 + val it = indexDefault[ix] + if(it in 0 until len) indexSet.add(it) //将正数不越界的索引添加到集合 + else if(it < 0 && len >= -it) indexSet.add(it + len) //将负数不越界的索引添加到集合 + }else for (ix in lastIndexs downTo 0 ){ //indexs不空,表明是[]式索引,集合是逆向遍历插入的,所以这里也逆向遍历,好还原顺序 + + if(indexs[ix] is Triple<*, *, *>){ //区间 + + val (startx, endx, stepx) = indexs[ix] as Triple //还原储存时的类型 + + val start = if (startx == null) 0 //左端省略表示0 + else if (startx >= 0) if (startx < len) startx else len - 1 //右端越界,设置为最大索引 + else if (-startx <= len) len + startx /* 将负索引转正 */ else 0 //左端越界,设置为最小索引 + + val end = if (endx == null) len - 1 //右端省略表示 len - 1 + else if (endx >= 0) if (endx < len) endx else len - 1 //右端越界,设置为最大索引 + else if (-endx <= len) len + endx /* 将负索引转正 */ else 0 //左端越界,设置为最小索引 + + if (start == end || stepx >= len) { //两端相同,区间里只有一个数。或间隔过大,区间实际上仅有首位 + + indexSet.add(start) + continue + + } + + val step = if (stepx > 0) stepx else if (-stepx < len) stepx + len else 1 //最小正数间隔为1 + + //将区间展开到集合中,允许列表反向。 + indexSet.addAll(if (end > start) start..end step step else start downTo end step step) + + }else{//单个索引 + + val it = indexs[ix] as Int //还原储存时的类型 + + if(it in 0 until len) indexSet.add(it) //将正数不越界的索引添加到集合 + else if(it < 0 && len >= -it) indexSet.add(it + len) //将负数不越界的索引添加到集合 + + } + + } + + /** + * 根据索引集合筛选元素 + * */ if(split == '!'){ //排除 for (pcInt in indexSet) elements[pcInt] = null @@ -303,29 +371,11 @@ class AnalyzeByJSoup(doc: Any) { } - return elements + return elements //返回筛选结果 } - /** - * 1.支持阅读原有写法,':'分隔索引,!或.表示筛选方式,索引可为负数 - * - * 例如 tag.div.-1:10:2 或 tag.div!0:3 - * - * 2. 支持与jsonPath类似的[]索引写法 - * - * 格式形如 [it,it,。。。] 或 [!it,it,。。。] 其中[!开头表示筛选方式为排除,it为单个索引或区间。 - * - * 区间格式为 start:end 或 start:end:step,其中start为0可省略,end为-1可省略。 - * - * 索引,区间两端及间隔都支持负数 - * - * 例如 tag.div[-1, 3:-2:-10, 2] - * - * 特殊用法 tag.div[-1:0] 可在任意地方让列表反向 - * - * */ - fun findIndexSet( rule:String ): ElementsSingle { + private fun findIndexSet( rule:String ){ val rus = rule.trim{ it <= ' '} @@ -381,7 +431,7 @@ class AnalyzeByJSoup(doc: Any) { if(rl == '[') { beforeRule = rus.substring(0, len) //遇到索引边界,返回结果 - return this + return } if(rl != ',') break //非索引结构,跳出 @@ -409,7 +459,7 @@ class AnalyzeByJSoup(doc: Any) { if (rl != ':'){ //rl == '!' || rl == '.' split = rl beforeRule = rus.substring(0, len) - return this + return } }else break //非索引结构,跳出循环 @@ -422,63 +472,7 @@ class AnalyzeByJSoup(doc: Any) { split = ' ' beforeRule = rus - return this //非索引格式 } - - private fun getIndexs(len:Int): MutableSet { - - val indexSet = mutableSetOf() - - val lastIndexs = (indexDefault.size - 1).takeIf { it !=-1 } ?: indexs.size -1 - - - if(indexs.isEmpty())for (ix in lastIndexs downTo 0 ){ //indexs为空,表明是非[]式索引,集合是逆向遍历插入的,所以这里也逆向遍历,好还原顺序 - - val it = indexDefault[ix] - if(it in 0 until len) indexSet.add(it) //将正数不越界的索引添加到集合 - else if(it < 0 && len >= -it) indexSet.add(it + len) //将负数不越界的索引添加到集合 - - }else for (ix in lastIndexs downTo 0 ){ //indexs不空,表明是[]式索引,集合是逆向遍历插入的,所以这里也逆向遍历,好还原顺序 - - if(indexs[ix] is Triple<*, *, *>){ //区间 - - val (startx, endx, stepx) = indexs[ix] as Triple //还原储存时的类型 - - val start = if (startx == null) 0 //左端省略表示0 - else if (startx >= 0) if (startx < len) startx else len - 1 //右端越界,设置为最大索引 - else if (-startx <= len) len + startx /* 将负索引转正 */ else 0 //左端越界,设置为最小索引 - - val end = if (endx == null) len - 1 //右端省略表示 len - 1 - else if (endx >= 0) if (endx < len) endx else len - 1 //右端越界,设置为最大索引 - else if (-endx <= len) len + endx /* 将负索引转正 */ else 0 //左端越界,设置为最小索引 - - if (start == end || stepx >= len) { //两端相同,区间里只有一个数。或间隔过大,区间实际上仅有首位 - - indexSet.add(start) - continue - - } - - val step = if (stepx > 0) stepx else if (-stepx < len) stepx + len else 1 //最小正数间隔为1 - - //将区间展开到集合中,允许列表反向。 - indexSet.addAll(if (end > start) start..end step step else start downTo end step step) - - }else{//单个索引 - - val it = indexs[ix] as Int //还原储存时的类型 - - if(it in 0 until len) indexSet.add(it) //将正数不越界的索引添加到集合 - else if(it < 0 && len >= -it) indexSet.add(it + len) //将负数不越界的索引添加到集合 - - } - - } - - return indexSet - - } - } 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 5167b526f..6a5c674ec 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 @@ -2,7 +2,7 @@ package io.legado.app.model.analyzeRule //通用的规则切分处理 -class RuleAnalyzer(data: String) { +class RuleAnalyzer(data: String, code:Boolean = false) { private var queue: String = data //被处理字符串 private var pos = 0 //处理到的位置 @@ -12,6 +12,8 @@ class RuleAnalyzer(data: String) { private var startX = 0 //规则的开始 private var step:Int = 0 //分割字符的长度 + val chompBalanced = if(code) ::chompCodeBalanced else ::chompRuleBalanced //设置平衡组函数,json或JavaScript时设置成chompCodeBalanced,否则为chompRuleBalanced + var elementsType = "" //当前平衡字段 @@ -195,7 +197,7 @@ class RuleAnalyzer(data: String) { /** * 拉出一个代码平衡组,存在转义文本,没有实体字符,通常以{}作为模块 */ - fun chompCodeBalanced(open: Char = '{', close: Char = '}'): Boolean { + fun chompCodeBalanced(open: Char, close: Char): Boolean { var pos = pos //声明临时变量记录匹配位置,匹配成功后才同步到类的pos @@ -234,9 +236,9 @@ class RuleAnalyzer(data: String) { } /** - * 拉出一个规则平衡组,没有转义文本,有实体字符,通常以[]作为选择器 + * 拉出一个规则平衡组,经过仔细测试xpath和jsoup中,引号内转义字符无效。 */ - fun chompRuleBalanced(open: Char = '[', close: Char = ']'): Boolean { + fun chompRuleBalanced(open: Char, close: Char): Boolean { var pos = pos //声明临时变量记录匹配位置,匹配成功后才同步到类的pos var depth = 0 //嵌套深度 @@ -246,11 +248,14 @@ class RuleAnalyzer(data: String) { do { if (pos == queue.length) break val c = queue[pos++] - if (c == '\'' && !inDoubleQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号 else if (c == '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号 if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环 + else if( c=='\\' ){ //不在引号中的转义字符才将下个字符转义 + pos++ + continue + } if ( c == open )depth++ //开始嵌套一层 else if ( c== close) depth-- //闭合一层嵌套 @@ -331,7 +336,7 @@ class RuleAnalyzer(data: String) { pos = st //位置推移到筛选器处 val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符 - if (!chompRuleBalanced(queue[pos], next)) throw Error( + if (!chompBalanced(queue[pos], next)) throw Error( queue.substring( 0, start @@ -392,7 +397,7 @@ class RuleAnalyzer(data: String) { pos = st //位置推移到筛选器处 val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符 - if (!chompRuleBalanced(queue[pos], next)) throw Error( + if (!chompBalanced(queue[pos], next)) throw Error( queue.substring( 0, start @@ -427,7 +432,7 @@ class RuleAnalyzer(data: String) { while (!isEmpty && consumeTo(inner)) { //拉取成功返回true,ruleAnalyzes里的字符序列索引变量pos后移相应位置,否则返回false,且isEmpty为true val posPre = pos //记录上次结束位置 - if (chompCodeBalanced()) { + if (chompBalanced('{', '}')) { val frv= fr(currBalancedString(startStep,endStep)) if(frv != null) { st.append(queue.substring(start,posPre)+frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串