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 a2b78b251..0de7342da 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,11 +12,13 @@ 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 = "" //当前平衡字段 fun currBalancedString( stepStart:Int = 1 , stepEnd:Int = 1): String { //stepStart平衡字符的起始分隔字串长度,stepEnd平衡字符的结束分隔字串长度 - return queue.substring(start+stepStart,pos-stepEnd) //当前平衡字段 + return queue.substring(startX+stepStart,pos-stepEnd) //当前平衡字段 } @@ -48,14 +50,6 @@ class RuleAnalyzer(data: String) { val isEmpty: Boolean get() = queue.length - pos == 0 //是否处理到最后 - /** - * 检索并返回首字符,但pos不变 - * @return 首字符:若为空则为 0 号字符 - */ - fun peek(): Char { //检索首字符 - return if (isEmpty) 0.toChar() else queue[pos] - } - /** * 消耗剩余字串中一个字符。 * @return 返回剩余字串中的下个字符。 @@ -191,15 +185,7 @@ class RuleAnalyzer(data: String) { if (fn) depth++ else depth-- //嵌套或者闭合 }else pos++ -// { //转义字符 -// var next = queue[pos++] //拉出被转义字符 -// if(commit && next == 'n') commit = false //匹配单行注释终点。当前为\,下个为n,表示换行 -// else if (!commits && !commit && next == '\\') { -// queue[pos++] //当前为\,下个为\,双重转义中"\\"表示转义字符本身,根据if条件"\\"字串不在注释中,则只能在字串或正则中 -// next = queue[pos++] //拉出下个字符,因为在双重转义的字串或正则中,类似于 \\/ 这样的结构才是转义结构 -// if(next == '\\')queue[pos++] //若为转义字符则继续拉出,因为双重转义中转义字符成对存在,即 \\\\ -// } -// } + } while (depth > 0 || bracketsDepth >0) //拉出全部符合js语法的字段 return if(depth > 0 || bracketsDepth > 0) false else { @@ -209,9 +195,9 @@ class RuleAnalyzer(data: String) { } /** - * 在双重转义字串中拉出一个规则平衡组 + * 拉出一个代码平衡组,存在转义文本,没有实体字符,通常以{}作为模块 */ - fun chompRuleBalanced(open: Char = '[', close: Char = ']',f: ((Char) ->Boolean?)? = null ): Boolean { + fun chompCodeBalanced(open: Char, close: Char): Boolean { var pos = pos //声明临时变量记录匹配位置,匹配成功后才同步到类的pos @@ -230,220 +216,241 @@ class RuleAnalyzer(data: String) { if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环 - if ( c == open )depth++ //开始嵌套一层 - else if ( c== close) depth-- //闭合一层嵌套 - else if(depth == 0 && f != null) { //处于默认嵌套中的非默认字符不需要平衡,仅depth为0时默认嵌套全部闭合,此字符才进行嵌套 - val fn = f(c) ?: continue - if (fn) otherDepth++ else otherDepth-- + if ( c == '[' )depth++ //开始嵌套一层 + else if ( c== ']') depth-- //闭合一层嵌套 + else if(depth == 0 ) { + //处于默认嵌套中的非默认字符不需要平衡,仅depth为0时默认嵌套全部闭合,此字符才进行嵌套 + if(c == open)otherDepth++ + else if(c == close)otherDepth-- } }else pos++ -// { //转义字符 -// var next = queue[pos++] //拉出被转义字符,匹配\/、\"、\'等 -// if (next == ESC) { -// queue[pos++] //当前为\,下个为\,双重转义中"\\"表示转义字符本身,根据语法特征当前字段在字串或正则中 -// next = queue[pos++] //拉出下个字符,因为在双重转义的字串或正则中,类似于 \\/ 这样的结构才是转义结构 -// if(next == ESC)queue[pos++] //若为转义字符则继续拉出,因为双重转义中转义字符成对存在,即 \\\\ -// } -// } + } while (depth > 0 || otherDepth > 0) //拉出一个平衡字串 return if(depth > 0 || otherDepth > 0) false else { + startX = this.pos //内嵌规则起始 this.pos = pos //同步位置 true } } /** - * 不用正则,不到最后不切片也不用中间变量存储,只在序列中标记当前查找字段的开头结尾,到返回时才切片,高效快速准确切割规则 - * 解决jsonPath自带的"&&"和"||"与阅读的规则冲突,以及规则正则或字符串中包含"&&"、"||"、"%%"、"@"导致的冲突 + * 拉出一个规则平衡组,经过仔细测试xpath和jsoup中,引号内转义字符无效。 */ - tailrec fun splitRule(vararg split: String): Array{ //首段匹配,elementsType为空 + fun chompRuleBalanced(open: Char, close: Char): Boolean { - if(split.size == 1) { - elementsType = split[0] //设置分割字串 - return if(!consumeTo(elementsType)) { - rule += queue.substring(startX) - rule - }else { - step = elementsType.length //设置分隔符长度 - splitRule() - } //递归匹配 - }else if (!consumeToAny(* split)) { //未找到分隔符 - rule += queue.substring(startX) - return rule + var pos = pos //声明临时变量记录匹配位置,匹配成功后才同步到类的pos + var depth = 0 //嵌套深度 + var inSingleQuote = false //单引号 + var inDoubleQuote = false //双引号 + + 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 } - val end = pos //记录分隔位置 - pos = start //重回开始,启动另一种查找 + if ( c == open )depth++ //开始嵌套一层 + else if ( c== close) depth-- //闭合一层嵌套 - do{ - val st = findToAny('[', '(') //查找筛选器位置 + } while (depth > 0 ) //拉出一个平衡字串 - if (st == -1) { + return if(depth > 0) false else { + this.pos = pos //同步位置 + true + } +} - rule = arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 +/** + * 不用正则,不到最后不切片也不用中间变量存储,只在序列中标记当前查找字段的开头结尾,到返回时才切片,高效快速准确切割规则 + * 解决jsonPath自带的"&&"和"||"与阅读的规则冲突,以及规则正则或字符串中包含"&&"、"||"、"%%"、"@"导致的冲突 + */ +tailrec fun splitRule(vararg split: String): Array{ //首段匹配,elementsType为空 - elementsType = queue.substring(end, end + step) //设置组合类型 - pos = end + step //跳过分隔符 + if(split.size == 1) { + elementsType = split[0] //设置分割字串 + return if(!consumeTo(elementsType)) { + rule += queue.substring(startX) + rule + }else { + step = elementsType.length //设置分隔符长度 + splitRule() + } //递归匹配 + }else if (!consumeToAny(* split)) { //未找到分隔符 + rule += queue.substring(startX) + return rule + } - while (consumeTo(elementsType)) { //循环切分规则压入数组 - rule += queue.substring(start, pos) - pos += step //跳过分隔符 - } + val end = pos //记录分隔位置 + pos = start //重回开始,启动另一种查找 - rule += queue.substring(pos) //将剩余字段压入数组末尾 + do{ + val st = findToAny('[', '(') //查找筛选器位置 + + if (st == -1) { - return rule + rule = arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 + + elementsType = queue.substring(end, end + step) //设置组合类型 + pos = end + step //跳过分隔符 + + while (consumeTo(elementsType)) { //循环切分规则压入数组 + rule += queue.substring(start, pos) + pos += step //跳过分隔符 } - if (st > end) { //先匹配到st1pos,表明分隔字串不在选择器中,将选择器前分隔字串分隔的字段依次压入数组 + rule += queue.substring(pos) //将剩余字段压入数组末尾 - rule = arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 + return rule + } - elementsType = queue.substring(end, end + step) //设置组合类型 - pos = end + step //跳过分隔符 + if (st > end) { //先匹配到st1pos,表明分隔字串不在选择器中,将选择器前分隔字串分隔的字段依次压入数组 - while (consumeTo(elementsType) && pos < st) { //循环切分规则压入数组 - rule += queue.substring(start, pos) - pos += step //跳过分隔符 - } + rule = arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 - return if(pos > st) { - startX = start - splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配 - } - else { //执行到此,证明后面再无分隔字符 - rule += queue.substring(pos) //将剩余字段压入数组末尾 - rule - } - } + elementsType = queue.substring(end, end + step) //设置组合类型 + pos = end + step //跳过分隔符 - pos = st //位置推移到筛选器处 - val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符 + while (consumeTo(elementsType) && pos < st) { //循环切分规则压入数组 + rule += queue.substring(start, pos) + pos += step //跳过分隔符 + } - if (!chompRuleBalanced(queue[pos], next)) throw Error( - queue.substring( - 0, - start - ) + "后未平衡" - ) //拉出一个筛选器,不平衡则报错 + return if(pos > st) { + startX = start + splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配 + } + else { //执行到此,证明后面再无分隔字符 + rule += queue.substring(pos) //将剩余字段压入数组末尾 + rule + } + } - }while( end > pos ) + pos = st //位置推移到筛选器处 + val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符 - start = pos //设置开始查找筛选器位置的起始位置 + if (!chompBalanced(queue[pos], next)) throw Error( + queue.substring( + 0, + start + ) + "后未平衡" + ) //拉出一个筛选器,不平衡则报错 - return splitRule(* split) //递归调用首段匹配 - } + }while( end > pos ) - @JvmName("splitRuleNext") - private tailrec fun splitRule(): Array{ //二段匹配被调用,elementsType非空(已在首段赋值),直接按elementsType查找,比首段采用的方式更快 + start = pos //设置开始查找筛选器位置的起始位置 - val end = pos //记录分隔位置 - pos = start //重回开始,启动另一种查找 + return splitRule(* split) //递归调用首段匹配 +} - do{ - val st = findToAny('[', '(') //查找筛选器位置 +@JvmName("splitRuleNext") +private tailrec fun splitRule(): Array{ //二段匹配被调用,elementsType非空(已在首段赋值),直接按elementsType查找,比首段采用的方式更快 - if (st == -1) { + val end = pos //记录分隔位置 + pos = start //重回开始,启动另一种查找 - rule += arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 - pos = end + step //跳过分隔符 + do{ + val st = findToAny('[', '(') //查找筛选器位置 - while (consumeTo(elementsType)) { //循环切分规则压入数组 - rule += queue.substring(start, pos) - pos += step //跳过分隔符 - } + if (st == -1) { - rule += queue.substring(pos) //将剩余字段压入数组末尾 + rule += arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 + pos = end + step //跳过分隔符 - return rule + while (consumeTo(elementsType)) { //循环切分规则压入数组 + rule += queue.substring(start, pos) + pos += step //跳过分隔符 } - if (st > end) { //先匹配到st1pos,表明分隔字串不在选择器中,将选择器前分隔字串分隔的字段依次压入数组 + rule += queue.substring(pos) //将剩余字段压入数组末尾 - rule += arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 - pos = end + step //跳过分隔符 + return rule + } - while (consumeTo(elementsType) && pos < st) { //循环切分规则压入数组 - rule += queue.substring(start, pos) - pos += step //跳过分隔符 - } + if (st > end) { //先匹配到st1pos,表明分隔字串不在选择器中,将选择器前分隔字串分隔的字段依次压入数组 - return if(pos > st) { - startX = start - splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配 - } - else { //执行到此,证明后面再无分隔字符 - rule += queue.substring(pos) //将剩余字段压入数组末尾 - rule - } + rule += arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 + pos = end + step //跳过分隔符 + + while (consumeTo(elementsType) && pos < st) { //循环切分规则压入数组 + rule += queue.substring(start, pos) + pos += step //跳过分隔符 + } + + return if(pos > st) { + startX = start + splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配 + } + else { //执行到此,证明后面再无分隔字符 + rule += queue.substring(pos) //将剩余字段压入数组末尾 + rule } + } - pos = st //位置推移到筛选器处 - val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符 + pos = st //位置推移到筛选器处 + val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符 - if (!chompRuleBalanced(queue[pos], next)) throw Error( - queue.substring( - 0, - start - ) + "后未平衡" - ) //拉出一个筛选器,不平衡则报错 + if (!chompBalanced(queue[pos], next)) throw Error( + queue.substring( + 0, + start + ) + "后未平衡" + ) //拉出一个筛选器,不平衡则报错 - }while( end > pos ) + }while( end > pos ) - start = pos //设置开始查找筛选器位置的起始位置 + start = pos //设置开始查找筛选器位置的起始位置 - return if(!consumeTo(elementsType)) { - rule += queue.substring(startX) - rule - }else splitRule() //递归匹配 + return if(!consumeTo(elementsType)) { + rule += queue.substring(startX) + rule + }else splitRule() //递归匹配 - } +} - /** - * 替换内嵌规则 - * @param inner 起始标志,如{$. 或 {{ - * @param startStep 不属于规则部分的前置字符长度,如{$.中{不属于规则的组成部分,故startStep为1 - * @param endStep 不属于规则部分的后置字符长度,如}}长度为2 - * @param fr 查找到内嵌规则时,用于解析的函数 - * - * */ - fun innerRule( inner:String,startStep:Int = 1,endStep:Int = 1,fr:(String)->String?): String { - - val start0 = pos //规则匹配前起点 - - val st = StringBuilder() - - while (!isEmpty && consumeTo(inner)) { //拉取成功返回true,ruleAnalyzes里的字符序列索引变量pos后移相应位置,否则返回false,且isEmpty为true - val posPre = pos //记录上次结束位置 - if (chompRuleBalanced {//拉出一个以[]为默认嵌套、以{}为补充嵌套的平衡字段 - when (it) { - '{' -> true - '}' -> false - else -> null - } - }) { - val frv= fr(currBalancedString(startStep,endStep)) - if(frv != null) { +/** + * 替换内嵌规则 + * @param inner 起始标志,如{$. 或 {{ + * @param startStep 不属于规则部分的前置字符长度,如{$.中{不属于规则的组成部分,故startStep为1 + * @param endStep 不属于规则部分的后置字符长度,如}}长度为2 + * @param fr 查找到内嵌规则时,用于解析的函数 + * + * */ +fun innerRule( inner:String,startStep:Int = 1,endStep:Int = 1,fr:(String)->String?): String { - st.append(queue.substring(start,posPre)+frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串 - continue //获取内容成功,继续选择下个内嵌规则 + val start0 = pos //规则匹配前起点 - } - } + val st = StringBuilder() - pos += inner.length //拉出字段不平衡,inner只是个普通字串,跳到此inner后继续匹配 + while (!isEmpty && consumeTo(inner)) { //拉取成功返回true,ruleAnalyzes里的字符序列索引变量pos后移相应位置,否则返回false,且isEmpty为true + val posPre = pos //记录上次结束位置 + if (chompBalanced('{', '}')) { + val frv= fr(currBalancedString(startStep,endStep)) + if(frv != null) { + st.append(queue.substring(start,posPre)+frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串 + continue //获取内容成功,继续选择下个内嵌规则 + } } - //匹配前起点与当前规则起点相同,证明无替换成功的内嵌规则,返回空字符串。否则返回替换后的字符串 - return if(start0 == start) "" else { - st.append(remainingString()) //压入剩余字符串 - st.toString() - } + pos += inner.length //拉出字段不平衡,inner只是个普通字串,跳到此inner后继续匹配 + + } + + //匹配前起点与当前规则起点相同,证明无替换成功的内嵌规则,返回空字符串。否则返回替换后的字符串 + return if(start0 == start) "" else { + st.append(remainingString()) //压入剩余字符串 + st.toString() } +} // /** // * 匹配并返回标签中的属性键字串(字母、数字、-、_、:) @@ -478,84 +485,83 @@ class RuleAnalyzer(data: String) { // return query // } - companion object { - /** - * 转义字符 - */ - private const val ESC = '\\' - - /** - * 阅读共有分隔字串起始部分 - * "##","@@","{{","{[","", "@js:" - */ - val splitList =arrayOf("##","@@","{{","{[","", "@js:") - - /** - * 发现‘名称-链接’分隔字串 - * "::" - */ - const val splitListFaXian = "::" - - /** - * 目录专有起始字符 - * "-" - */ - const val splitListMulu = "-" - - /** - * 结果为元素列表的 all in one 模式起始字符 - * "+" - */ - const val splitListTongYi = "+" - - /** - * 结果为元素列表的项的同规则组合结构 - * "||","&&","%%" - */ - val splitListReSplit = arrayOf("||","&&","%%") - - /** - * js脚本结束字串 - * "" - */ - const val splitListEndJS = "" - - /** - *内嵌js结束字串 - * "}}" - */ - const val splitListEndInnerJS = "}}" - - /** - * 内嵌规则结束字串 - * "]}" - */ - const val splitListEndInnerRule = "]}" - - /** - * '[', ']', '(', ')','{','}' - */ - val splitListPublic = charArrayOf('[', ']', '(', ')','{','}') - - /** - * '*',"/","//",":","::","@","|","@xpath:" - */ - val splitListXpath = arrayOf("*","/","//",":","::","@","|","@xpath:") - - /** - * '*','$',".","..", "@json:" - */ - val splitListJson = arrayOf('*','$',".","..", "@json:") - - /** - * '*',"+","~",".",",","|","@","@css:",":" - */ - val splitListCss = arrayOf('*',"+","~",".",",","|","@","@css:",":") - - /** - * "-",".","!","@","@@" - */ - val splitListDefault = arrayOf("-",".","!","@","@@") +companion object { + /** + * 转义字符 + */ + private const val ESC = '\\' - } -} + /** + * 阅读共有分隔字串起始部分 + * "##","@@","{{","{[","", "@js:" + */ + val splitList =arrayOf("##","@@","{{","{[","", "@js:") + + /** + * 发现‘名称-链接’分隔字串 + * "::" + */ + const val splitListFaXian = "::" + + /** + * 目录专有起始字符 + * "-" + */ + const val splitListMulu = "-" + + /** + * 结果为元素列表的 all in one 模式起始字符 + * "+" + */ + const val splitListTongYi = "+" + + /** + * 结果为元素列表的项的同规则组合结构 + * "||","&&","%%" + */ + val splitListReSplit = arrayOf("||","&&","%%") + + /** + * js脚本结束字串 + * "" + */ + const val splitListEndJS = "" + + /** + *内嵌js结束字串 + * "}}" + */ + const val splitListEndInnerJS = "}}" + + /** + * 内嵌规则结束字串 + * "]}" + */ + const val splitListEndInnerRule = "]}" + + /** + * '[', ']', '(', ')','{','}' + */ + val splitListPublic = charArrayOf('[', ']', '(', ')','{','}') + + /** + * '*',"/","//",":","::","@","|","@xpath:" + */ + val splitListXpath = arrayOf("*","/","//",":","::","@","|","@xpath:") + + /** + * '*','$',".","..", "@json:" + */ + val splitListJson = arrayOf('*','$',".","..", "@json:") + + /** + * '*',"+","~",".",",","|","@","@css:",":" + */ + val splitListCss = arrayOf('*',"+","~",".",",","|","@","@css:",":") + + /** + * "-",".","!","@","@@" + */ + val splitListDefault = arrayOf("-",".","!","@","@@") + +}} \ No newline at end of file