0. 新增专门用于切分各种规则的类,分解完所有规则后才切片,其余操作都只改变变量指向规则字符串中的位置。暂时只用于处理三大规则的“&&”、“||”,“%%”、“@”切分,以后将扩展到全部解析规则。

改进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🔚step,其中start为0可省略,end为-1可省略。
*
* 索引,区间两端及间隔都支持负数
*
* 例如 tag.div[-1, 3:-2:-10, 2]
*
* 特殊用法 tag.div[-1:0] 可在任意地方让列表反向
*
* */

 -------------------
 已测试过各种“&&”、“||”,“%%”切分的规则及两者索引写法,没发现大问题,坤飞大大可以再测试看看
pull/1114/head
bushixuanqi 3 years ago
parent 42b6c694da
commit 14d033f19f
  1. 28
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt
  2. 142
      app/src/main/java/io/legado/app/model/analyzeRule/RuleAnalyzer.kt

@ -213,7 +213,7 @@ class AnalyzeByJSoup(doc: Any) {
val curList = mutableListOf<Int?>() //当前数字区间
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
}

@ -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<String>{ //首段匹配,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 //跳过分隔符
}

Loading…
Cancel
Save