From 14d033f19fbf31959d76646c0afd078b9bbc5543 Mon Sep 17 00:00:00 2001 From: bushixuanqi <2208877960@qq.com> Date: Sun, 4 Jul 2021 14:24:48 +0800 Subject: [PATCH] =?UTF-8?q?0.=20=E6=96=B0=E5=A2=9E=E4=B8=93=E9=97=A8?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E5=88=87=E5=88=86=E5=90=84=E7=A7=8D=E8=A7=84?= =?UTF-8?q?=E5=88=99=E7=9A=84=E7=B1=BB=EF=BC=8C=E5=88=86=E8=A7=A3=E5=AE=8C?= =?UTF-8?q?=E6=89=80=E6=9C=89=E8=A7=84=E5=88=99=E5=90=8E=E6=89=8D=E5=88=87?= =?UTF-8?q?=E7=89=87=EF=BC=8C=E5=85=B6=E4=BD=99=E6=93=8D=E4=BD=9C=E9=83=BD?= =?UTF-8?q?=E5=8F=AA=E6=94=B9=E5=8F=98=E5=8F=98=E9=87=8F=E6=8C=87=E5=90=91?= =?UTF-8?q?=E8=A7=84=E5=88=99=E5=AD=97=E7=AC=A6=E4=B8=B2=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E3=80=82=E6=9A=82=E6=97=B6=E5=8F=AA=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E5=A4=84=E7=90=86=E4=B8=89=E5=A4=A7=E8=A7=84=E5=88=99?= =?UTF-8?q?=E7=9A=84=E2=80=9C&&=E2=80=9D=E3=80=81=E2=80=9C||=E2=80=9D?= =?UTF-8?q?=EF=BC=8C=E2=80=9C%%=E2=80=9D=E3=80=81=E2=80=9C@=E2=80=9D?= =?UTF-8?q?=E5=88=87=E5=88=86=EF=BC=8C=E4=BB=A5=E5=90=8E=E5=B0=86=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E5=88=B0=E5=85=A8=E9=83=A8=E8=A7=A3=E6=9E=90=E8=A7=84?= =?UTF-8?q?=E5=88=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 改进jsonPath,jsoup,xpath中的规则切分方法,允许查询规则的正则或字符串中存在“&&”,“||”,“%%”,“@”而不切错。 /** * 改进解析方法 * 解决阅读”&&“、”||“与jsonPath支持的”&&“、”||“之间的冲突 * 解决{$.rule}形式规则可能匹配错误的问题,旧规则用正则解析内容含‘}’的json文本时,用规则中的字段去匹配这种内容时,会匹配错误.现改用平衡嵌套方法解决这个问题 * */ 1. 加强AnalyzeByJSoup中的索引写法 /** * 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] 可在任意地方让列表反向 * * */ ------------------- 已测试过各种“&&”、“||”,“%%”切分的规则及两者索引写法,没发现大问题,坤飞大大可以再测试看看 --- .../app/model/analyzeRule/AnalyzeByJSoup.kt | 28 ++-- .../app/model/analyzeRule/RuleAnalyzer.kt | 142 +++++++++--------- 2 files changed, 90 insertions(+), 80 deletions(-) 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 6f45f9f21..be1615a46 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 @@ -213,7 +213,7 @@ class AnalyzeByJSoup(doc: Any) { val curList = mutableListOf() //当前数字区间 var l = "" //暂存数字字符串 - val head = rus[rus.length-1] == ']' //是否为常规索引写法 + val head = rus.last() == ']' //是否为常规索引写法 if(head){ //常规索引写法[index...] @@ -236,13 +236,16 @@ class AnalyzeByJSoup(doc: Any) { else -> { - indexSet.indexs.add( //压入以下值,为保证查找顺序,区间和单个索引都添加到同一集合 + //为保证查找顺序,区间和单个索引都添加到同一集合 + if(curList.isEmpty())indexSet.indexs.add(curInt!!) + else{ - if(curList.isEmpty()) curInt!! //区间为空,表明当前是单个索引,单个索引不能空 + //列表最后压入的是区间右端,若列表有两位则最先压入的是间隔 + indexSet.indexs.add( Triple(curInt, curList.last(), if(curList.size == 2) curList.first() else 1) ) - else Triple(curInt, curList.last(), if(curList.size == 2) curList.first() else 1) //否则为区间,列表最后压入的是区间右端,若列表有两位则最先压入了间隔 + curList.clear() //重置临时列表,避免影响到下个区间的处理 - ) + } if(rl == '!'){ indexSet.split='!' @@ -262,7 +265,7 @@ class AnalyzeByJSoup(doc: Any) { curMinus = false //重置 } } - } else while (len --> 1) { //阅读原本写法,逆向遍历,至少两位前置字符,如 p. + } else while (len --> 0) { //阅读原本写法,逆向遍历,至少两位前置字符,如 . val rl = rus[len] if (rl == ' ') continue //跳过空格 @@ -311,8 +314,9 @@ class AnalyzeByJSoup(doc: Any) { val rules = ruleStr.split(".") elements.addAll( - when (rules[0]) { - "children" -> temp.children() + if(ruleStr.isEmpty()) temp.children() //允许索引直接作为根元素,此时前置规则为空,效果与children相同 + else 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) @@ -431,9 +435,9 @@ class AnalyzeByJSoup(doc: Any) { 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) //将负数不越界的索引添加到集合 + 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不空,表明是[]式索引,集合是逆向遍历插入的,所以这里也逆向遍历,好还原顺序 @@ -451,7 +455,7 @@ class AnalyzeByJSoup(doc: Any) { if (start == end || stepx >= len) { //两端相同,区间里只有一个数。或间隔过大,区间实际上仅有首位 - indexSet.add(stepx) + indexSet.add(start) continue } 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 73a861acf..023f03629 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 @@ -8,7 +8,6 @@ class RuleAnalyzer(data: String) { private var pos = 0 //处理到的位置 private var start = 0 //每次处理字段的开始 - private var end:Int = queue.length //每次处理字段的终点 private var step:Int = 0 //分割字符的长度 var elementsType = "" @@ -29,7 +28,7 @@ class RuleAnalyzer(data: String) { } //剩余字串 - fun remainingString(): String { + private fun remainingString(): String { start = pos pos = queue.length return queue.substring(start) @@ -82,69 +81,73 @@ class RuleAnalyzer(data: String) { return queue.regionMatches(pos, seq, 0, seq.length, ignoreCase = true) } - /** - * 测试下个字符是否与序列中相应位置的字符相等。 - * @param seq :被检查的字符列表 - * @return 相等就为 true ,不相等则为 false - */ - fun matchesAny(vararg seq: Char): Boolean { - if (isEmpty) return false - for (c in seq) { - if (queue[pos] == c) { - return true - } - } - return false - } - - /** - * 测试下个字符(串)是否与参数列表里的序列存在匹配。 不区分大小写。 - * @param seq :被不区分大小写检查的字符串列表 - * @return 只要匹配就为 true ,没有匹配则为 false - */ - fun matchesAny(vararg seq: String): Boolean { - for (s in seq) { - if (matches(s)) { - step = s.length - return true - } - } - return false - } - /** * 从剩余字串中拉出一个字符串,直到但不包括匹配序列,或剩余字串用完。 * @param seq :分隔字符 **区分大小写** * @return 是否找到相应字段。 */ fun consumeTo(seq: String,setStartPos:Boolean = true): Boolean { + if(setStartPos)start = pos //将处理到的位置设置为规则起点 val offset = queue.indexOf(seq, pos) return if (offset != -1) { pos = offset true } else false + + } + + /** + * 从剩余字串中拉出一个字符串,直到且包括匹配序列(匹配参数列表中一项即为匹配),或剩余字串用完。 + * @param seq 匹配字符串序列 + * @return 成功返回true并推移pos,失败返回fasle而不推移pos + */ + fun consumeToAny(vararg seq:String): Boolean { + + start = pos + + while (!isEmpty) { + + for (s in seq) { + if (matches(s)) { + step = s.length //间隔数 + return true //匹配就返回 true + } + } + + pos++ //逐个试探 + } + + pos = start //匹配失败,位置回退 + + return false } /** * 从剩余字串中拉出一个字符串,直到但不包括匹配序列(匹配参数列表中一项即为匹配),或剩余字串用完。 - * @param f 消费函数,返回true表示消费,fasle表示不消费 - * @param setStartPos 设置开始消费位置 - * @return 消耗的字符串 + * @param seq 匹配字符序列 + * @return 匹配到的位置 */ - fun consumeToAny(setStartPos:Boolean = true, f:()->Boolean,): Boolean { - if(setStartPos)start = pos //将处理到的位置设置为规则起点 - while (!isEmpty && !f()) { - pos++ + private fun findToAny(vararg seq:Char): Int { + + for (s in seq){ + + val offset = queue.indexOf(s, pos) + + if (offset != -1) return offset //只要匹配到,就返回相应位置 + } - return !isEmpty + + return -1 } //其中js只要符合语法,就不用避开任何阅读关键字,自由发挥 fun chompJsBalanced(f: ((Char) -> Boolean?) = { - if ( it == '{' )true //开始嵌套一层 - else if ( it == '}') false //闭合一层嵌套 - else null + when (it) { + '{' -> true //开始嵌套一层 + '}' -> false //闭合一层嵌套 + else -> null + } } ): Boolean { start = pos var depth = 0 //嵌套深度 @@ -178,20 +181,20 @@ class RuleAnalyzer(data: String) { } else if(regex && c == '/') { //正则的终点或[]平衡 - if(c == '/')regex = false//匹配正则终点 - - //为了保证当open为( 且 close 为 )时,正则中[(]或[)]的合法性。故对[]这对在任何规则中都平衡的成对符号做匹配。 - // 注:正则里[(]、[)]、[{]、[}]都是合法的,所以只有[]必须平衡。 + when (c) { + '/' -> regex = false//匹配正则终点 - else if ( c == '[' )bracketsDepth++ //开始嵌套一层[] - else if ( c== ']') bracketsDepth-- //闭合一层嵌套[] + //为了保证当open为( 且 close 为 )时,正则中[(]或[)]的合法性。故对[]这对在任何规则中都平衡的成对符号做匹配。 + // 注:正则里[(]、[)]、[{]、[}]都是合法的,所以只有[]必须平衡。 + '[' -> bracketsDepth++ //开始嵌套一层[] + ']' -> bracketsDepth-- //闭合一层嵌套[] + } } if (commits || commit || regex || inSingleQuote || inDoubleQuote || inOtherQuote) continue //语法单元未匹配结束,直接进入下个循环 - val fn = f(c) - if (fn == null) continue + val fn = f(c) ?: continue if (fn) depth++ else depth-- //嵌套或者闭合 }else { //转义字符 @@ -233,8 +236,7 @@ class RuleAnalyzer(data: String) { if ( c == open )depth++ //开始嵌套一层 else if ( c== close) depth-- //闭合一层嵌套 else if(depth == 0 && f != null) { //处于默认嵌套中的非默认字符不需要平衡,仅depth为0时默认嵌套全部闭合,此字符才进行嵌套 - val fn = f(c) - if (fn == null) continue + val fn = f(c) ?: continue if (fn) otherDepth++ else otherDepth-- } @@ -257,24 +259,28 @@ class RuleAnalyzer(data: String) { */ tailrec fun splitRule(vararg split: String): Array{ //首段匹配,elementsType为空 - if (!consumeToAny { matchesAny(* split) }) return arrayOf(queue) //未找到分隔符 + if(split.size == 1) { + elementsType = split[0] //设置分割字串 + step = elementsType.length //设置分隔符长度 + return splitRule(arrayOf()) //仅一个分隔字串时,直接二段解析更快 + }else if (!consumeToAny(* split)) return arrayOf(queue) //未找到分隔符 - end = pos - val st = if( consumeToAny(false){ matchesAny( '(','[' ) } )pos else -1 //查找筛选器 - pos = end + val st = findToAny( '(','[' ) //查找筛选器 if(st == -1) { var rule = arrayOf(queue.substring(0, pos)) //压入分隔的首段规则到数组 + elementsType = queue.substring(pos, pos + step) //设置组合类型 pos += step //跳过分隔符 - elementsType = queue.substring(pos - step, pos) //设置组合类型 - while (consumeToAny { matchesAny(* split) }) { //循环切分规则压入数组 + while (consumeToAny(* split)) { //循环切分规则压入数组 rule += queue.substring(start, pos) pos += step //跳过分隔符 } - rule+= queue.substring(start) //将剩余字段压入数组末尾 + + rule += queue.substring(pos) //将剩余字段压入数组末尾 + return rule } @@ -282,14 +288,16 @@ class RuleAnalyzer(data: String) { var rule = arrayOf(queue.substring(0, pos)) //压入分隔的首段规则到数组 + elementsType = queue.substring(pos, pos + step) //设置组合类型 pos += step //跳过分隔符 - elementsType = queue.substring(pos - step, pos) //设置组合类型 - while (pos < st && consumeToAny { matchesAny( * split ) }) { - rule += queue.substring(start, pos) //循环切分规则压入数组 + while (consumeToAny( * split ) && pos < st ) { //循环切分规则压入数组 + rule += queue.substring(start, pos) pos += step //跳过分隔符 } + rule + }else null pos = st //位置推移到筛选器处 @@ -316,9 +324,7 @@ class RuleAnalyzer(data: String) { if (!consumeTo(elementsType,false)) return rules + queue.substring(start) //此处consumeTo(...)开始位置不是规则的开始位置,start沿用上次设置 - end = pos - val st = if( consumeToAny(false){ matchesAny( '(','[' ) } )pos else -1 //查找筛选器 - pos = end + val st = findToAny( '(','[' ) //查找筛选器 if(st == -1) { var rule = rules + queue.substring(start, pos) //压入本次分隔的首段规则到数组 @@ -327,14 +333,14 @@ class RuleAnalyzer(data: String) { rule += queue.substring(start, pos) pos += step //跳过分隔符 } - rule += queue.substring(start) //将剩余字段压入数组末尾 + rule += queue.substring(pos) //将剩余字段压入数组末尾 return rule } val rule = if(st > pos ){//先匹配到st1pos,表明"&&","||"不在选择器中,将选择器前"&&","||"分隔的字段依次压入数组 var rule = rules + queue.substring(start, pos) //压入本次分隔的首段规则到数组 pos += step //跳过分隔符 - while (pos < st && consumeTo(elementsType)) { //循环切分规则压入数组 + while (consumeTo(elementsType) && pos < st) { //循环切分规则压入数组 rule += queue.substring(start, pos) pos += step //跳过分隔符 }