优化链接分割规则,避免附加参数中存在 ,{ 时导致规则切错

修复<str0,str2,...{{js}}>这种页数列表写法中,js部分内含 < 或 > 会切割错误的问题

优化格式化图片标签操作和匹配图片标签操作,加快图片处理速度。
pull/1121/head
bushixuanqi 3 years ago
parent d2c064667a
commit 5bf2ad6a1e
  1. 4
      app/src/main/java/io/legado/app/constant/AppPattern.kt
  2. 184
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  3. 24
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  4. 350
      app/src/main/java/io/legado/app/model/analyzeRule/RuleAnalyzer.kt

@ -5,7 +5,9 @@ import java.util.regex.Pattern
@Suppress("RegExpRedundantEscape") @Suppress("RegExpRedundantEscape")
object AppPattern { object AppPattern {
val JS_PATTERN: Pattern = val JS_PATTERN: Pattern =
Pattern.compile("(<js>[\\w\\W]*?</js>|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE) Pattern.compile("(<js>[\\w\\W]+?</js>)", Pattern.CASE_INSENSITIVE)
val JS_PATTERN_END: Pattern =
Pattern.compile("(@js:[\\w\\W]+)", Pattern.CASE_INSENSITIVE)
val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}") val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}")
//匹配格式化后的图片格式 //匹配格式化后的图片格式

@ -3,6 +3,7 @@ package io.legado.app.model.analyzeRule
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.Keep import androidx.annotation.Keep
import io.legado.app.constant.AppConst.SCRIPT_ENGINE import io.legado.app.constant.AppConst.SCRIPT_ENGINE
import io.legado.app.constant.AppPattern
import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.BaseBook
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
@ -357,58 +358,81 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions {
/** /**
* 分解规则生成规则列表 * 分解规则生成规则列表
*/ */
fun splitSourceRule(ruleStr: String?, mode: Mode = Mode.Default): List<SourceRule> { fun splitSourceRule(ruleStr: String?): List<SourceRule> {
var vRuleStr = ruleStr if (ruleStr.isNullOrEmpty()) return ArrayList<SourceRule>()
val ruleList = ArrayList<SourceRule>() val ruleList = ArrayList<SourceRule>()
if (vRuleStr.isNullOrEmpty()) return ruleList
//检测Mode //检测Mode
var mMode: Mode = mode var mMode: Mode = Mode.Default
when { fun mode(ruleStr0:String)=when {
vRuleStr.startsWith("@@") -> { ruleStr0.startsWith("@@") -> {
vRuleStr = vRuleStr.substring(2) mMode = Mode.Default
ruleStr0.substring(2)
}
ruleStr0.startsWith("@XPath:", true) -> {
mMode = Mode.XPath
ruleStr0.substring(7)
} }
vRuleStr.startsWith("@XPath:", true) -> { ruleStr0.startsWith("/") -> {//XPath特征很明显,无需配置单独的识别标头
mMode = Mode.XPath mMode = Mode.XPath
vRuleStr = vRuleStr.substring(7) ruleStr0
} }
vRuleStr.startsWith("@Json:", true) -> { ruleStr0.startsWith("@Json:", true) -> {
mMode = Mode.Json mMode = Mode.Json
vRuleStr = vRuleStr.substring(6) ruleStr0.substring(6)
} }
vRuleStr.startsWith(":") -> { ruleStr0.startsWith("$.") || ruleStr0.startsWith("$[") -> {
mMode = Mode.Json
ruleStr0
}
ruleStr0.startsWith("?") -> {
mMode = Mode.Regex mMode = Mode.Regex
isRegex = true isRegex = true
vRuleStr = vRuleStr.substring(1) ruleStr0.substring(1)
}
else -> {
mMode = Mode.Default
ruleStr0
} }
isRegex -> mMode = Mode.Regex
isJSON -> mMode = Mode.Json
} }
//拆分为规则列表 //拆分为规则列表
var start = 0 var start = 0
var tmp: String var tmp: String
val jsMatcher = JS_PATTERN.matcher(vRuleStr) val jsMatcher = JS_PATTERN.matcher(ruleStr)
while (jsMatcher.find()) {
while (jsMatcher.find()){
if (jsMatcher.start() > start) { if (jsMatcher.start() > start) {
tmp = vRuleStr.substring(start, jsMatcher.start()).trim { it <= ' ' } tmp = ruleStr.substring(start, jsMatcher.start()).trim { it <= ' ' }
if (!TextUtils.isEmpty(tmp)) { if (tmp.isNotEmpty()) {
ruleList.add(SourceRule(tmp, mMode)) ruleList.add(SourceRule(mode(tmp), mMode))
} }
} }
ruleList.add(SourceRule(jsMatcher.group(), Mode.Js)) ruleList.add(SourceRule(jsMatcher.group().run{ substring(4, lastIndexOf("<")) }, Mode.Js))
start = jsMatcher.end() start = jsMatcher.end()
} }
if (vRuleStr.length > start) {
tmp = vRuleStr.substring(start).trim { it <= ' ' } if (ruleStr.length > start){
if (!TextUtils.isEmpty(tmp)) { val jsMatcherEnd = AppPattern.JS_PATTERN_END.matcher(ruleStr.substring(start))
ruleList.add(SourceRule(tmp, mMode)) if(jsMatcherEnd.find()){
tmp = ruleStr.substring(start, jsMatcher.start()).trim { it <= ' ' }
if (tmp.isNotEmpty()) {
ruleList.add(SourceRule(mode(tmp), mMode))
}
} }
} ruleList.add(SourceRule(jsMatcher.group().substring(4), Mode.Js))
}else{
tmp = ruleStr.substring(start).trim { it <= ' ' }
if (tmp.isNotEmpty()) {
ruleList.add(SourceRule(mode(tmp), mMode))
}
}
return ruleList return ruleList
} }
/** /**
* 规则类 * 规则类
*/ */
inner class SourceRule internal constructor(ruleStr: String, mainMode: Mode = Mode.Default) { inner class SourceRule internal constructor(ruleStr: String, mainMode: Mode = Mode.Default) {
internal var mode: Mode internal var mode: Mode
internal var rule: String internal var rule: String
@ -424,77 +448,42 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions {
init { init {
this.mode = mainMode this.mode = mainMode
if (mode == Mode.Js) {
rule = if (ruleStr.startsWith("<js>")) {
ruleStr.substring(4, ruleStr.lastIndexOf("<"))
} else {
ruleStr.substring(4)
}
} else {
when {
ruleStr.startsWith("@CSS:", true) -> {
mode = Mode.Default
rule = ruleStr
}
ruleStr.startsWith("@@") -> {
mode = Mode.Default
rule = ruleStr.substring(2)
}
ruleStr.startsWith("@XPath:", true) -> {
mode = Mode.XPath
rule = ruleStr.substring(7)
}
ruleStr.startsWith("//") -> {//XPath特征很明显,无需配置单独的识别标头
mode = Mode.XPath
rule = ruleStr
}
ruleStr.startsWith("@Json:", true) -> {
mode = Mode.Json
rule = ruleStr.substring(6)
}
ruleStr.startsWith("$.") -> {
mode = Mode.Json
rule = ruleStr
}
else -> rule = ruleStr
}
}
//分离put //分离put
rule = splitPutRule(rule, putMap) rule = splitPutRule(ruleStr, putMap)
//@get,{{ }}, 拆分 //@get,{{ }}, 拆分
var start = 0 var start = 0
var tmp: String var tmp: String
val evalMatcher = evalPattern.matcher(rule) val evalMatcher = evalPattern.matcher(rule)
while (evalMatcher.find()) {
if (evalMatcher.start() > start) { if(evalMatcher.find()){
var modeX = mode == Mode.Js || mode == Mode.Regex
if(evalMatcher.start() != 0){
tmp = rule.substring(start, evalMatcher.start()) tmp = rule.substring(start, evalMatcher.start())
if (mode != Mode.Js && mode != Mode.Regex modeX = modeX || tmp.contains("##")
&& start == 0 && !tmp.contains("##")
) {
mode = Mode.Regex
}
splitRegex(tmp) splitRegex(tmp)
} else if (mode != Mode.Js && mode != Mode.Regex
&& evalMatcher.start() == 0
) {
mode = Mode.Regex
} }
tmp = evalMatcher.group() if(!modeX)mode = Mode.Regex
when {
tmp.startsWith("@get:", true) -> { do{
ruleType.add(getRuleType) tmp = evalMatcher.group()
ruleParam.add(tmp.substring(6, tmp.lastIndex)) when {
} tmp.startsWith("@get:", true) -> {
tmp.startsWith("{{") -> { ruleType.add(getRuleType)
ruleType.add(jsRuleType) ruleParam.add(tmp.substring(6, tmp.lastIndex))
ruleParam.add(tmp.substring(2, tmp.length - 2)) }
} tmp.startsWith("{{") -> {
else -> { ruleType.add(jsRuleType)
splitRegex(tmp) ruleParam.add(tmp.substring(2, tmp.length - 2))
}
else -> {
splitRegex(tmp)
}
} }
} start = evalMatcher.end()
start = evalMatcher.end() }while (evalMatcher.find())
} }
if (rule.length > start) { if (rule.length > start) {
tmp = rule.substring(start) tmp = rule.substring(start)
splitRegex(tmp) splitRegex(tmp)
@ -509,19 +498,22 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions {
var tmp: String var tmp: String
val ruleStrArray = ruleStr.split("##") val ruleStrArray = ruleStr.split("##")
val regexMatcher = regexPattern.matcher(ruleStrArray[0]) val regexMatcher = regexPattern.matcher(ruleStrArray[0])
while (regexMatcher.find()) {
if(regexMatcher.find()) {
if (mode != Mode.Js && mode != Mode.Regex) { if (mode != Mode.Js && mode != Mode.Regex) {
mode = Mode.Regex mode = Mode.Regex
} }
if (regexMatcher.start() > start) { do{
tmp = ruleStr.substring(start, regexMatcher.start()) if (regexMatcher.start() > start) {
ruleType.add(defaultRuleType) tmp = ruleStr.substring(start, regexMatcher.start())
ruleType.add(defaultRuleType)
ruleParam.add(tmp)
}
tmp = regexMatcher.group()
ruleType.add(tmp.substring(1).toInt())
ruleParam.add(tmp) ruleParam.add(tmp)
} start = regexMatcher.end()
tmp = regexMatcher.group() }while (regexMatcher.find())
ruleType.add(tmp.substring(1).toInt())
ruleParam.add(tmp)
start = regexMatcher.end()
} }
if (ruleStr.length > start) { if (ruleStr.length > start) {
tmp = ruleStr.substring(start) tmp = ruleStr.substring(start)

@ -7,6 +7,7 @@ import com.bumptech.glide.load.model.LazyHeaders
import io.legado.app.constant.AppConst.SCRIPT_ENGINE import io.legado.app.constant.AppConst.SCRIPT_ENGINE
import io.legado.app.constant.AppConst.UA_NAME import io.legado.app.constant.AppConst.UA_NAME
import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.constant.AppPattern.JS_PATTERN_END
import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.BaseBook
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
@ -79,7 +80,7 @@ class AnalyzeUrl(
while (jsMatcher.find()) { while (jsMatcher.find()) {
if (jsMatcher.start() > start) { if (jsMatcher.start() > start) {
tmp = tmp =
ruleUrl.substring(start, jsMatcher.start()).replace("\n", "").trim { it <= ' ' } ruleUrl.substring(start, jsMatcher.start()).trim { it <= ' ' }
if (tmp.isNotEmpty()) { if (tmp.isNotEmpty()) {
ruleList.add(tmp) ruleList.add(tmp)
} }
@ -88,11 +89,24 @@ class AnalyzeUrl(
start = jsMatcher.end() start = jsMatcher.end()
} }
if (ruleUrl.length > start) { if (ruleUrl.length > start) {
tmp = ruleUrl.substring(start).replace("\n", "").trim { it <= ' ' } val jsMatcherEnd = JS_PATTERN_END.matcher(ruleUrl.substring(start))
if (tmp.isNotEmpty()) { if(jsMatcherEnd.find()){
ruleList.add(tmp) if (jsMatcherEnd.start() > start) {
tmp =
ruleUrl.substring(start, jsMatcherEnd.start()).trim { it <= ' ' }
if (tmp.isNotEmpty()) {
ruleList.add(tmp)
}
}
ruleList.add(jsMatcherEnd.group())
}else{
tmp = ruleUrl.substring(start).trim { it <= ' ' }
if (tmp.isNotEmpty()) {
ruleList.add(tmp)
}
} }
} }
for (rule in ruleList) { for (rule in ruleList) {
var ruleStr = rule var ruleStr = rule
when { when {
@ -130,7 +144,7 @@ class AnalyzeUrl(
bindings["book"] = book bindings["book"] = book
//替换所有内嵌{{js}} //替换所有内嵌{{js}}
val url = analyze.innerJsRule{ val url = analyze.innerRule("{{","}}"){
when(val jsEval = SCRIPT_ENGINE.eval(it, bindings)){ when(val jsEval = SCRIPT_ENGINE.eval(it, bindings)){
is String -> jsEval is String -> jsEval
jsEval is Double && jsEval % 1.0 == 0.0 -> String.format("%.0f", jsEval) jsEval is Double && jsEval % 1.0 == 0.0 -> String.format("%.0f", jsEval)

@ -1,5 +1,9 @@
package io.legado.app.model.analyzeRule package io.legado.app.model.analyzeRule
import io.legado.app.utils.isJson
import java.util.ArrayList
import java.util.regex.Pattern
//通用的规则切分处理 //通用的规则切分处理
class RuleAnalyzer(data: String, code: Boolean = false) { class RuleAnalyzer(data: String, code: Boolean = false) {
@ -9,14 +13,11 @@ class RuleAnalyzer(data: String, code: Boolean = false) {
private var start = 0 //当前处理字段的开始 private var start = 0 //当前处理字段的开始
private var startX = 0 //当前规则的开始 private var startX = 0 //当前规则的开始
private var rule = arrayOf<String>() //分割出的规则列表 private var rule = ArrayList<String>() //分割出的规则列表
private var step: Int = 0 //分割字符的长度 private var step: Int = 0 //分割字符的长度
var elementsType = "" //当前分割字符串 var elementsType = "" //当前分割字符串
var innerType = true //是否为内嵌{{}} var innerType = true //是否为内嵌{{}}
//设置平衡组函数,json或JavaScript时设置成chompCodeBalanced,否则为chompRuleBalanced
val chompBalanced = if (code) ::chompCodeBalanced else ::chompRuleBalanced
fun trim() { // 修剪当前规则之前的"@"或者空白符 fun trim() { // 修剪当前规则之前的"@"或者空白符
if(queue[pos] == '@' || queue[pos] < '!') { //在while里重复设置start和startX会拖慢执行速度,所以先来个判断是否存在需要修剪的字段,最后再一次性设置start和startX if(queue[pos] == '@' || queue[pos] < '!') { //在while里重复设置start和startX会拖慢执行速度,所以先来个判断是否存在需要修剪的字段,最后再一次性设置start和startX
pos++ pos++
@ -67,6 +68,31 @@ class RuleAnalyzer(data: String, code: Boolean = false) {
pos++ //逐个试探 pos++ //逐个试探
} }
return false
}
/**
* 从剩余字串中拉出一个字符串直到但不包括匹配序列匹配参数列表中一项即为匹配或剩余字串用完
* @param seq 匹配字符串序列
* @return 成功返回true并设置间隔失败则直接返回fasle
*/
fun chompToAny(vararg seq: String): Boolean {
var pos = pos //声明新变量记录匹配位置,不更改类本身的位置
while (pos != queue.length) {
for (s in seq) {
if (queue.regionMatches(pos, s, 0, s.length)) {
rule += queue.substring(this.pos, pos)
pos += s.length //跳过分隔符
ruleTypeList += s //追加类型到列表
start = this.pos
this.pos = pos //匹配成功, 同步处理位置到类
return true //匹配就返回 true
}
}
pos++ //逐个试探
}
return false return false
} }
@ -91,85 +117,6 @@ class RuleAnalyzer(data: String, code: Boolean = false) {
return -1 return -1
} }
//其中js只要符合语法,就不用避开任何阅读关键字,自由发挥
fun chompJsBalanced(innerType:Boolean = true,startPos:Int = pos): Boolean {
var pos = startPos //声明变量记录临时处理位置
var depth = 0 //{}嵌套深度
var depthX = 0 //[]嵌套深度
var balanced = false //平衡状态
var inSingleQuote = false //单引号
var inDoubleQuote = false //双引号
var inOtherQuote = false //js原始字串分隔字符
var inCommit = false //单行注释
var inCommits = false //多行注释
var commit= false //是否为注释,不分单行注释还是多行注释
val start:String
val end:String
val endChar:Char
if(innerType){
start = "{{"
endChar = '}'
end = "}"
}else{
start = "<js>"
endChar = '<'
end = "/js>"
}
pos += start.length //跳过起始字符串
while(true){
if (pos == queue.length) break
var c = queue[pos++]
if(c == ESC) {//跳过转义字符序列
pos++
continue
}
if(commit)when (c) {
'/' -> inCommit = true //匹配单行注释起点
'*' -> inCommits = true //匹配多行注释起点
else -> commit = false
}
if (c == '\'' && !commit && !inDoubleQuote && !inOtherQuote) inSingleQuote =
!inSingleQuote //匹配具有语法功能的单引号
else if (c == '"' && !commit && !inSingleQuote && !inOtherQuote) inDoubleQuote =
!inDoubleQuote //匹配具有语法功能的双引号
else if (c == '`' && !commit && !inSingleQuote && !inDoubleQuote) inOtherQuote =
!inOtherQuote //匹配具有语法功能的'`'
else if (c == '/' && !commit && !inSingleQuote && !inDoubleQuote && !inOtherQuote) commit = true //可能是注释
else if (inCommits) { //匹配多行注释终点
pos = queue.indexOf("*/", pos) //跳过多行注释
if(pos == -1)break //没有终点,语法出错,跳出
continue
}else if (c == '\n') inCommit = false //单行注释终点
if (commit || inSingleQuote || inDoubleQuote || inOtherQuote) continue //语法单元未匹配结束,直接进入下个循环
if(c == '[')depthX++
else if(c == ']')depthX--
else if(depth == 0){ //正则的[]中的{}不需要平衡,非正则的[]中的{}也无需考虑平衡
if(c == '{')depth++
else if(c == '}')depth--
}
balanced = depthX <1 && depth<1
if(!balanced)continue
if(c == endChar && queue.regionMatches(pos, end, 0, end.length)) {
this.pos = pos
return true
}
}
return false
}
/** /**
* 拉出一个非内嵌代码平衡组存在转义文本 * 拉出一个非内嵌代码平衡组存在转义文本
*/ */
@ -247,7 +194,7 @@ class RuleAnalyzer(data: String, code: Boolean = false) {
* 不用正则,不到最后不切片也不用中间变量存储,只在序列中标记当前查找字段的开头结尾,到返回时才切片,高效快速准确切割规则 * 不用正则,不到最后不切片也不用中间变量存储,只在序列中标记当前查找字段的开头结尾,到返回时才切片,高效快速准确切割规则
* 解决jsonPath自带的"&&""||"与阅读的规则冲突,以及规则正则或字符串中包含"&&""||""%%""@"导致的冲突 * 解决jsonPath自带的"&&""||"与阅读的规则冲突,以及规则正则或字符串中包含"&&""||""%%""@"导致的冲突
*/ */
tailrec fun splitRule(vararg split: String): Array<String> { //首段匹配,elementsType为空 tailrec fun splitRule(vararg split: String): ArrayList<String> { //首段匹配,elementsType为空
if (split.size == 1) { if (split.size == 1) {
elementsType = split[0] //设置分割字串 elementsType = split[0] //设置分割字串
@ -271,7 +218,7 @@ class RuleAnalyzer(data: String, code: Boolean = false) {
if (st == -1) { if (st == -1) {
rule = arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 rule = arrayListOf(queue.substring(startX, end)) //压入分隔的首段规则到数组
elementsType = queue.substring(end, end + step) //设置组合类型 elementsType = queue.substring(end, end + step) //设置组合类型
pos = end + step //跳过分隔符 pos = end + step //跳过分隔符
@ -288,7 +235,7 @@ class RuleAnalyzer(data: String, code: Boolean = false) {
if (st > end) { //先匹配到st1pos,表明分隔字串不在选择器中,将选择器前分隔字串分隔的字段依次压入数组 if (st > end) { //先匹配到st1pos,表明分隔字串不在选择器中,将选择器前分隔字串分隔的字段依次压入数组
rule = arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 rule = arrayListOf(queue.substring(startX, end)) //压入分隔的首段规则到数组
elementsType = queue.substring(end, end + step) //设置组合类型 elementsType = queue.substring(end, end + step) //设置组合类型
pos = end + step //跳过分隔符 pos = end + step //跳过分隔符
@ -322,7 +269,7 @@ class RuleAnalyzer(data: String, code: Boolean = false) {
} }
@JvmName("splitRuleNext") @JvmName("splitRuleNext")
private tailrec fun splitRule(): Array<String> { //二段匹配被调用,elementsType非空(已在首段赋值),直接按elementsType查找,比首段采用的方式更快 private tailrec fun splitRule(): ArrayList<String> { //二段匹配被调用,elementsType非空(已在首段赋值),直接按elementsType查找,比首段采用的方式更快
val end = pos //记录分隔位置 val end = pos //记录分隔位置
pos = start //重回开始,启动另一种查找 pos = start //重回开始,启动另一种查找
@ -347,7 +294,7 @@ class RuleAnalyzer(data: String, code: Boolean = false) {
if (st > end) { //先匹配到st1pos,表明分隔字串不在选择器中,将选择器前分隔字串分隔的字段依次压入数组 if (st > end) { //先匹配到st1pos,表明分隔字串不在选择器中,将选择器前分隔字串分隔的字段依次压入数组
rule += arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组 rule += arrayListOf(queue.substring(startX, end)) //压入分隔的首段规则到数组
pos = end + step //跳过分隔符 pos = end + step //跳过分隔符
while (consumeTo(elementsType) && pos < st) { //循环切分规则压入数组 while (consumeTo(elementsType) && pos < st) { //循环切分规则压入数组
@ -421,92 +368,201 @@ class RuleAnalyzer(data: String, code: Boolean = false) {
* @param fr 查找到内嵌规则时用于解析的函数 * @param fr 查找到内嵌规则时用于解析的函数
* *
* */ * */
fun innerJsRule( fun innerRule(
fr: (String) -> String? startStr:String,
endStr:String,
fr: (String) -> String?
): String { ): String {
val st = StringBuilder()
while (consumeTo("{{")) { //拉取成功返回true,ruleAnalyzes里的字符序列索引变量pos后移相应位置,否则返回false,且isEmpty为true val st = StringBuilder()
while (consumeTo(startStr)) { //拉取成功返回true,ruleAnalyzes里的字符序列索引变量pos后移相应位置,否则返回false,且isEmpty为true
pos += startStr.length //跳过开始字符串
val posPre = pos //记录consumeTo匹配位置 val posPre = pos //记录consumeTo匹配位置
if (chompJsBalanced()) { if (consumeTo(endStr)) {
val frv = fr(queue.substring(posPre + 2, pos - 2)) val frv = fr(queue.substring(posPre, pos))
if (!frv.isNullOrEmpty()) { st.append(queue.substring(startX, posPre - startStr.length) + frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串
st.append(queue.substring(startX, posPre) + frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串 pos += endStr.length //跳过结束字符串
startX = pos //记录下次规则起点 startX = pos //记录下次规则起点
continue //获取内容成功,继续选择下个内嵌规则
}
} }
pos += 2 //拉出字段不平衡,inner只是个普通字串,跳到此inner后继续匹配
} }
return if(startX == 0) "" else st.apply { return if(startX == 0) queue else st.apply {
append(queue.substring(startX)) append(queue.substring(startX))
}.toString() }.toString()
} }
companion object { val ruleTypeList = ArrayList<String>()
/** //设置平衡组函数,json或JavaScript时设置成chompCodeBalanced,否则为chompRuleBalanced
* 转义字符 val chompBalanced = if (code) ::chompCodeBalanced else ::chompRuleBalanced
*/ enum class Mode {
private const val ESC = '\\' XPath, Json, Default, Js, Regex
}
/**
* 不用正则,不到最后不切片也不用中间变量存储,只在序列中标记当前查找字段的开头结尾,到返回时才切片,高效快速准确切割规则
* 解决jsonPath自带的"&&""||"与阅读的规则冲突,以及规则正则或字符串中包含"&&""||""%%""@"导致的冲突
*/
tailrec fun splitAnyRule(): ArrayList<String> { //首段匹配,elementsType为空
/** if (!consumeToAny(* STARTSTR)) { //未找到分隔符
* "<js>" rule += queue.substring(startX)
*/ return rule
private const val JSSTART = "<js>" }
/** val end = pos //记录分隔位置
* 阅读共有分隔字串起始部分 pos = start //重回开始,启动另一种查找
* "##","@@","{{","{[","<js>", "@js:"
*/
val splitList2 = arrayOf("##", "@@", "{{", "{[")
/** do {
* 发现名称-链接分隔字串 val st = findToAny('[', '(') //查找筛选器位置
* "::"
*/
const val splitListFaXian = "::"
/** if (st == -1) {
* 目录专有起始字符
* "-"
*/
const val splitListMulu = "-"
/** rule += arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组
* 结果为元素列表的 all in one 模式起始字符
* "+"
*/
const val splitListTongYi = "+"
/** ruleTypeList += queue.substring(end, end + step) //追加类型到类型列表
* 结果为元素列表的项的同规则组合结构 pos = end + step //跳过分隔符
* "||","&&","%%"
*/
val splitListReSplit = arrayOf("||", "&&", "%%")
/** while (!chompToAny(elementsType)) { //循环切分规则压入数组
* js脚本结束字串 rule += queue.substring(pos) //将剩余字段压入数组末尾
* "</js>" return rule
*/ }
const val splitListEndJS = "</js>" }
if (st > end) { //先匹配到st1pos,表明分隔字串不在选择器中,将选择器前分隔字串分隔的字段依次压入数组
rule += arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组
ruleTypeList += queue.substring(end, end + step) //设置组合类型
pos = end + step //跳过分隔符
while (!chompToAny(elementsType) && pos >= st) { //循环切分规则压入数组
if (pos > st) {
startX = start
} else { //执行到此,证明后面再无分隔字符
rule += queue.substring(pos) //将剩余字段压入数组末尾
return rule
}
}
}
pos = st //位置回到筛选器处
val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符
if (!chompBalanced(queue[pos], next)){
ruleTypeList.clear()
rule.clear()
consumeToAny("<js>","@js:")
rule += queue.substring(0,pos)
ruleTypeList += queue.substring(pos, pos + 4) //设置组合类型
}
} while (end > pos)
start = pos //设置开始查找筛选器位置的起始位置
return splitAnyRule() //递归调用首段匹配
}
var isJSON = false
var isUrl = false
var isUrlList = false
var isMulu = false
var isreverse = false
var isAllInOne= false
var isFind = false
private val findName = ArrayList<String>()
var replaceRegex = ""
var replacement = ""
var replaceFirst = false
val putMap = HashMap<String, String>()
private val ruleParam = ArrayList<String>()
private val ruleType = ArrayList<Int>()
private val getRuleType = -2
private val jsRuleType = -1
private val defaultRuleType = 0
@JvmOverloads
fun setContent(cont: String,type:String = ""): RuleAnalyzer {
queue = cont
when(type){
"mulu" -> {
if(queue[0] =='-'){ //目录反转
isreverse = true
startX++
pos++
}else if(queue[0] =='?'){ //AllInOne
isAllInOne = true
startX++
pos++
}
isMulu = true
}
"find" -> {
pos = queue.indexOf("::")
findName.add(queue.substring(startX,pos))
pos+=2
isFind = true
}
"url" -> {
isUrl = true
}
"urlList" -> {
isUrlList = true
}
else -> {
isJSON = queue.toString().isJson()
}
}
return this
}
companion object {
/** /**
*内嵌js结束字串 * 转义字符
* "}}"
*/ */
const val splitListEndInnerJS = "}}" private const val ESC = '\\'
val validKeys = arrayOf("class", "id", "tag", "text", "children")
/** /**
* 内嵌规则结束字串 * 参数字符串
* "]}"
*/ */
const val splitListEndInnerRule = "]}" private val STARTSTRURL = arrayOf(",{",)
private val regexPattern = Pattern.compile("\\$\\d{1,2}")
private val putPattern = Pattern.compile("@put:(\\{[^}]+?\\})", Pattern.CASE_INSENSITIVE)
private val getPattern = Pattern.compile("@get:\\{([^}]+?)\\}", Pattern.CASE_INSENSITIVE)
private val evalPattern = Pattern.compile("\\{\\{[\\w\\W]*?\\}\\}", Pattern.CASE_INSENSITIVE)
val ENDSTR= mapOf(
"<js>" to "</js>",
"{{" to "}}",
)
/** /**
* '[', ']', '(', ')','{','}' * 规则起始字符串
*/ */
val splitListPublic = charArrayOf('[', ']', '(', ')', '{', '}') private val STARTSTR = arrayOf("@js:","<js>","</js>","##","@@","@"
,"{{@", "{{","}}"
,"}"
, "{@", "{/", "{$", "{?"
, "{class"
, "{id"
, "{tag"
, "{text"
, "{children"
,"/","$","@xpath:","@json:","@css:"
,"||", "&&", "%%"
,"@get:{","@put:{"
)
/** /**
* '*',"/","//",":","::","@","|","@xpath:" * '*',"/","//",":","::","@","|","@xpath:"

Loading…
Cancel
Save