mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 11:42:22 +08:00
feat: fastQuery
This commit is contained in:
parent
dd12a20f9c
commit
028ad89d3c
|
@ -8,13 +8,15 @@ import android.view.ViewConfiguration
|
|||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import com.blankj.utilcode.util.ScreenUtils
|
||||
import kotlinx.serialization.Serializable
|
||||
import li.songe.selector.FastQuery
|
||||
|
||||
@Serializable
|
||||
data class GkdAction(
|
||||
val selector: String,
|
||||
val quickFind: Boolean = false,
|
||||
val fastQuery: Boolean = false,
|
||||
val action: String? = null,
|
||||
val position: RawSubscription.Position? = null
|
||||
val position: RawSubscription.Position? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
|
@ -177,6 +177,7 @@ data class RawSubscription(
|
|||
val actionCd: Long?
|
||||
val actionDelay: Long?
|
||||
val quickFind: Boolean?
|
||||
val fastQuery: Boolean?
|
||||
val matchRoot: Boolean?
|
||||
val matchDelay: Long?
|
||||
val matchTime: Long?
|
||||
|
@ -213,6 +214,7 @@ data class RawSubscription(
|
|||
val valid: Boolean
|
||||
val errorDesc: String?
|
||||
val allExampleUrls: List<String>
|
||||
val cacheMap: MutableMap<String, Selector?>
|
||||
}
|
||||
|
||||
sealed interface RawAppRuleProps {
|
||||
|
@ -256,6 +258,7 @@ data class RawSubscription(
|
|||
override val actionCd: Long?,
|
||||
override val actionDelay: Long?,
|
||||
override val quickFind: Boolean?,
|
||||
override val fastQuery: Boolean?,
|
||||
override val matchRoot: Boolean?,
|
||||
override val matchDelay: Long?,
|
||||
override val matchTime: Long?,
|
||||
|
@ -273,15 +276,13 @@ data class RawSubscription(
|
|||
override val apps: List<RawGlobalApp>?,
|
||||
override val rules: List<RawGlobalRule>,
|
||||
) : RawGroupProps, RawGlobalRuleProps {
|
||||
|
||||
val appIdEnable by lazy {
|
||||
(apps ?: emptyList()).associate { a -> a.id to (a.enable ?: true) }
|
||||
}
|
||||
|
||||
override val cacheMap by lazy { HashMap<String, Selector?>() }
|
||||
override val errorDesc by lazy { getErrorDesc() }
|
||||
|
||||
override val valid by lazy { errorDesc == null }
|
||||
|
||||
override val allExampleUrls by lazy {
|
||||
((exampleUrls ?: emptyList()) + rules.flatMap { r ->
|
||||
r.exampleUrls ?: emptyList()
|
||||
|
@ -295,6 +296,7 @@ data class RawSubscription(
|
|||
override val actionCd: Long?,
|
||||
override val actionDelay: Long?,
|
||||
override val quickFind: Boolean?,
|
||||
override val fastQuery: Boolean?,
|
||||
override val matchRoot: Boolean?,
|
||||
override val matchDelay: Long?,
|
||||
override val matchTime: Long?,
|
||||
|
@ -332,6 +334,7 @@ data class RawSubscription(
|
|||
override val actionCd: Long?,
|
||||
override val actionDelay: Long?,
|
||||
override val quickFind: Boolean?,
|
||||
override val fastQuery: Boolean?,
|
||||
override val matchRoot: Boolean?,
|
||||
override val actionMaximum: Int?,
|
||||
override val order: Int?,
|
||||
|
@ -349,11 +352,9 @@ data class RawSubscription(
|
|||
override val versionCodes: List<Long>?,
|
||||
override val excludeVersionCodes: List<Long>?,
|
||||
) : RawGroupProps, RawAppRuleProps {
|
||||
|
||||
override val cacheMap by lazy { HashMap<String, Selector?>() }
|
||||
override val errorDesc by lazy { getErrorDesc() }
|
||||
|
||||
override val valid by lazy { errorDesc == null }
|
||||
|
||||
override val allExampleUrls by lazy {
|
||||
((exampleUrls ?: emptyList()) + rules.flatMap { r ->
|
||||
r.exampleUrls ?: emptyList()
|
||||
|
@ -377,6 +378,7 @@ data class RawSubscription(
|
|||
override val actionCd: Long?,
|
||||
override val actionDelay: Long?,
|
||||
override val quickFind: Boolean?,
|
||||
override val fastQuery: Boolean?,
|
||||
override val matchRoot: Boolean?,
|
||||
override val actionMaximum: Int?,
|
||||
override val order: Int?,
|
||||
|
@ -405,9 +407,12 @@ data class RawSubscription(
|
|||
|
||||
val allSelector = allSelectorStrings.map { s ->
|
||||
try {
|
||||
Selector.parse(s)
|
||||
Selector.parse(s).apply {
|
||||
cacheMap[s] = this
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d("非法选择器", e.toString())
|
||||
cacheMap[s] = null
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -591,6 +596,7 @@ data class RawSubscription(
|
|||
preKeys = getIntIArray(jsonObject, "preKeys"),
|
||||
action = getString(jsonObject, "action"),
|
||||
quickFind = getBoolean(jsonObject, "quickFind"),
|
||||
fastQuery = getBoolean(jsonObject, "fastQuery"),
|
||||
matchRoot = getBoolean(jsonObject, "matchRoot"),
|
||||
actionMaximum = getInt(jsonObject, "actionMaximum"),
|
||||
matchDelay = getLong(jsonObject, "matchDelay"),
|
||||
|
@ -634,6 +640,7 @@ data class RawSubscription(
|
|||
jsonToRuleRaw(it)
|
||||
},
|
||||
quickFind = getBoolean(jsonObject, "quickFind"),
|
||||
fastQuery = getBoolean(jsonObject, "fastQuery"),
|
||||
matchRoot = getBoolean(jsonObject, "matchRoot"),
|
||||
actionMaximum = getInt(jsonObject, "actionMaximum"),
|
||||
matchDelay = getLong(jsonObject, "matchDelay"),
|
||||
|
@ -688,6 +695,7 @@ data class RawSubscription(
|
|||
actionCd = getLong(jsonObject, "actionCd"),
|
||||
actionDelay = getLong(jsonObject, "actionDelay"),
|
||||
quickFind = getBoolean(jsonObject, "quickFind"),
|
||||
fastQuery = getBoolean(jsonObject, "fastQuery"),
|
||||
matchRoot = getBoolean(jsonObject, "matchRoot"),
|
||||
actionMaximum = getInt(jsonObject, "actionMaximum"),
|
||||
matchDelay = getLong(jsonObject, "matchDelay"),
|
||||
|
@ -725,6 +733,7 @@ data class RawSubscription(
|
|||
actionCd = getLong(jsonObject, "actionCd"),
|
||||
actionDelay = getLong(jsonObject, "actionDelay"),
|
||||
quickFind = getBoolean(jsonObject, "quickFind"),
|
||||
fastQuery = getBoolean(jsonObject, "fastQuery"),
|
||||
matchRoot = getBoolean(jsonObject, "matchRoot"),
|
||||
actionMaximum = getInt(jsonObject, "actionMaximum"),
|
||||
matchDelay = getLong(jsonObject, "matchDelay"),
|
||||
|
|
|
@ -13,6 +13,8 @@ import li.songe.gkd.service.lastTriggerTime
|
|||
import li.songe.gkd.service.querySelector
|
||||
import li.songe.gkd.service.safeActiveWindow
|
||||
import li.songe.gkd.util.ResolvedGroup
|
||||
import li.songe.selector.ConnectOperator
|
||||
import li.songe.selector.MatchOption
|
||||
import li.songe.selector.Selector
|
||||
|
||||
sealed class ResolvedRule(
|
||||
|
@ -26,16 +28,22 @@ sealed class ResolvedRule(
|
|||
val key = rule.key
|
||||
val index = group.rules.indexOfFirst { r -> r === rule }
|
||||
private val preKeys = (rule.preKeys ?: emptyList()).toSet()
|
||||
private val matches = (rule.matches ?: emptyList()).map { s -> Selector.parse(s) }
|
||||
private val excludeMatches = (rule.excludeMatches ?: emptyList()).map { s -> Selector.parse(s) }
|
||||
private val anyMatches = (rule.anyMatches ?: emptyList()).map { s -> Selector.parse(s) }
|
||||
private val matches =
|
||||
(rule.matches ?: emptyList()).map { s -> group.cacheMap[s] ?: Selector.parse(s) }
|
||||
private val excludeMatches =
|
||||
(rule.excludeMatches ?: emptyList()).map { s -> group.cacheMap[s] ?: Selector.parse(s) }
|
||||
private val anyMatches =
|
||||
(rule.anyMatches ?: emptyList()).map { s -> group.cacheMap[s] ?: Selector.parse(s) }
|
||||
|
||||
private val resetMatch = rule.resetMatch ?: group.resetMatch
|
||||
val matchDelay = rule.matchDelay ?: group.matchDelay ?: 0L
|
||||
val actionDelay = rule.actionDelay ?: group.actionDelay ?: 0L
|
||||
private val matchTime = rule.matchTime ?: group.matchTime
|
||||
private val forcedTime = rule.forcedTime ?: group.forcedTime ?: 0L
|
||||
private val quickFind = rule.quickFind ?: group.quickFind ?: false
|
||||
private val matchOption = MatchOption(
|
||||
quickFind = rule.quickFind ?: group.quickFind ?: false,
|
||||
fastQuery = rule.fastQuery ?: group.fastQuery ?: false
|
||||
)
|
||||
private val matchRoot = rule.matchRoot ?: group.matchRoot ?: false
|
||||
val order = rule.order ?: group.order ?: 0
|
||||
|
||||
|
@ -55,9 +63,8 @@ sealed class ResolvedRule(
|
|||
|
||||
private val slowSelectors by lazy {
|
||||
(matches + excludeMatches + anyMatches).filterNot { s ->
|
||||
((quickFind && s.quickFindValue.canQf) || s.isMatchRoot) && !s.connectKeys.contains(
|
||||
"<<"
|
||||
)
|
||||
(!s.connectKeys.contains(ConnectOperator.Descendant)) &&
|
||||
((matchOption.quickFind && s.quickFindValue != null) || (matchOption.fastQuery && s.fastQueryList.isNotEmpty()) || s.isMatchRoot)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,7 +171,7 @@ sealed class ResolvedRule(
|
|||
for (selector in anyMatches) {
|
||||
target = nodeInfo.querySelector(
|
||||
selector,
|
||||
quickFind,
|
||||
matchOption,
|
||||
transform.transform,
|
||||
isRootNode || matchRoot
|
||||
) ?: break
|
||||
|
@ -174,7 +181,7 @@ sealed class ResolvedRule(
|
|||
for (selector in matches) {
|
||||
target = nodeInfo.querySelector(
|
||||
selector,
|
||||
quickFind,
|
||||
matchOption,
|
||||
transform.transform,
|
||||
isRootNode || matchRoot
|
||||
) ?: return null
|
||||
|
@ -182,7 +189,7 @@ sealed class ResolvedRule(
|
|||
for (selector in excludeMatches) {
|
||||
nodeInfo.querySelector(
|
||||
selector,
|
||||
quickFind,
|
||||
matchOption,
|
||||
transform.transform,
|
||||
isRootNode || matchRoot
|
||||
)?.let { return null }
|
||||
|
|
|
@ -8,6 +8,8 @@ import android.view.accessibility.AccessibilityNodeInfo
|
|||
import com.blankj.utilcode.util.LogUtils
|
||||
import li.songe.gkd.BuildConfig
|
||||
import li.songe.selector.Context
|
||||
import li.songe.selector.FastQuery
|
||||
import li.songe.selector.MatchOption
|
||||
import li.songe.selector.MismatchExpressionTypeException
|
||||
import li.songe.selector.MismatchOperatorTypeException
|
||||
import li.songe.selector.MismatchParamTypeException
|
||||
|
@ -70,7 +72,7 @@ fun AccessibilityNodeInfo.getVid(): CharSequence? {
|
|||
|
||||
fun AccessibilityNodeInfo.querySelector(
|
||||
selector: Selector,
|
||||
quickFind: Boolean,
|
||||
option: MatchOption,
|
||||
transform: Transform<AccessibilityNodeInfo>,
|
||||
isRootNode: Boolean,
|
||||
): AccessibilityNodeInfo? {
|
||||
|
@ -80,31 +82,51 @@ fun AccessibilityNodeInfo.querySelector(
|
|||
} else {
|
||||
GkdAbService.service?.safeActiveWindow ?: return null
|
||||
}
|
||||
return selector.match(root, transform)
|
||||
return selector.match(root, transform, option)
|
||||
}
|
||||
if (quickFind && selector.quickFindValue.canQf) {
|
||||
val idValue = selector.quickFindValue.id
|
||||
val vidValue = selector.quickFindValue.vid
|
||||
val textValue = selector.quickFindValue.text
|
||||
val nodes = (if (idValue != null) {
|
||||
findAccessibilityNodeInfosByViewId(idValue)
|
||||
} else if (vidValue != null) {
|
||||
findAccessibilityNodeInfosByViewId("$packageName:id/$vidValue")
|
||||
} else if (textValue != null) {
|
||||
findAccessibilityNodeInfosByText(textValue)
|
||||
} else {
|
||||
emptyList()
|
||||
})
|
||||
if (nodes.isNotEmpty()) {
|
||||
nodes.forEach { childNode ->
|
||||
val targetNode = selector.match(childNode, transform)
|
||||
if (targetNode != null) return targetNode
|
||||
}
|
||||
if (option.fastQuery && selector.fastQueryList.isNotEmpty()) {
|
||||
val nodes = transform.traverseFastQueryDescendants(this, selector.fastQueryList)
|
||||
nodes.forEach { childNode ->
|
||||
val targetNode = selector.match(childNode, transform, option)
|
||||
if (targetNode != null) return targetNode
|
||||
}
|
||||
return null
|
||||
}
|
||||
if (option.quickFind && selector.quickFindValue != null) {
|
||||
val nodes = getFastQueryNodes(this, selector.quickFindValue!!)
|
||||
nodes.forEach { childNode ->
|
||||
val targetNode = selector.match(childNode, transform, option)
|
||||
if (targetNode != null) return targetNode
|
||||
}
|
||||
return null
|
||||
}
|
||||
// 在一些开屏广告的界面会造成1-2s的阻塞
|
||||
return transform.querySelector(this, selector)
|
||||
return transform.querySelector(this, selector, option)
|
||||
}
|
||||
|
||||
private fun getFastQueryNodes(
|
||||
node: AccessibilityNodeInfo,
|
||||
fastQuery: FastQuery
|
||||
): List<AccessibilityNodeInfo> {
|
||||
return when (fastQuery) {
|
||||
is FastQuery.Id -> node.findAccessibilityNodeInfosByViewId(fastQuery.value)
|
||||
is FastQuery.Text -> node.findAccessibilityNodeInfosByText(fastQuery.value)
|
||||
is FastQuery.Vid -> node.findAccessibilityNodeInfosByViewId("${node.packageName}:id/${fastQuery.value}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun traverseFastQueryDescendants(
|
||||
node: AccessibilityNodeInfo,
|
||||
fastQueryList: List<FastQuery>
|
||||
): Sequence<AccessibilityNodeInfo> {
|
||||
return sequence {
|
||||
for (fastQuery in fastQueryList) {
|
||||
val nodes = getFastQueryNodes(node, fastQuery)
|
||||
nodes.forEach { childNode ->
|
||||
yield(childNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -504,6 +526,7 @@ fun createCacheTransform(): CacheTransform {
|
|||
} while (stack.isNotEmpty())
|
||||
}
|
||||
},
|
||||
traverseFastQueryDescendants = ::traverseFastQueryDescendants
|
||||
)
|
||||
|
||||
return CacheTransform(transform, cache)
|
||||
|
@ -603,6 +626,7 @@ fun createNoCacheTransform(): CacheTransform {
|
|||
}
|
||||
}
|
||||
},
|
||||
traverseFastQueryDescendants = ::traverseFastQueryDescendants
|
||||
)
|
||||
return CacheTransform(transform, cache)
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ import li.songe.gkd.util.launchTry
|
|||
import li.songe.gkd.util.map
|
||||
import li.songe.gkd.util.storeFlow
|
||||
import li.songe.gkd.util.toast
|
||||
import li.songe.selector.MatchOption
|
||||
import li.songe.selector.Selector
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
@ -535,9 +536,12 @@ class GkdAbService : CompositionAbService({
|
|||
}
|
||||
val targetNode = serviceVal.safeActiveWindow?.querySelector(
|
||||
selector,
|
||||
gkdAction.quickFind,
|
||||
MatchOption(
|
||||
quickFind = gkdAction.quickFind,
|
||||
fastQuery = gkdAction.fastQuery,
|
||||
),
|
||||
createCacheTransform().transform,
|
||||
true
|
||||
isRootNode = true
|
||||
) ?: throw RpcError("没有查询到节点")
|
||||
|
||||
if (gkdAction.action == null) {
|
||||
|
|
|
@ -230,12 +230,12 @@ val ruleSummaryFlow by lazy {
|
|||
val appGroupConfigs = subGroupSubsConfigs.filter { c -> c.appId == appRaw.id }
|
||||
val subAppGroupToRules = mutableMapOf<RawSubscription.RawAppGroup, List<AppRule>>()
|
||||
val groupAndEnables = appRaw.groups.map { group ->
|
||||
val enable = group.valid && getGroupRawEnable(
|
||||
val enable = getGroupRawEnable(
|
||||
group,
|
||||
appGroupConfigs.find { c -> c.groupKey == group.key },
|
||||
rawSubs.groupToCategoryMap[group],
|
||||
subCategoryConfigs.find { c -> c.categoryKey == rawSubs.groupToCategoryMap[group]?.key }
|
||||
)
|
||||
) && group.valid
|
||||
ResolvedAppGroup(
|
||||
group = group,
|
||||
subscription = rawSubs,
|
||||
|
|
|
@ -14,20 +14,29 @@ data class ConnectWrapper(
|
|||
fun <T> matchContext(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
option: MatchOption
|
||||
): Context<T>? {
|
||||
if (isMatchRoot) {
|
||||
// C <<n [parent=null] >n A
|
||||
val root = transform.getRoot(context.current) ?: return null
|
||||
return to.matchContext(context.next(root), transform)
|
||||
return to.matchContext(context.next(root), transform, option)
|
||||
}
|
||||
segment.traversal(context.current, transform).forEach {
|
||||
if (it == null) return@forEach
|
||||
val r = to.matchContext(context.next(it), transform)
|
||||
if (r != null) return r
|
||||
if (canFq && option.fastQuery) {
|
||||
// C[name='a'||vid='b'] <<n A
|
||||
transform.traverseFastQueryDescendants(context.current, to.fastQueryList).forEach {
|
||||
val r = to.matchContext(context.next(it), transform, option)
|
||||
if (r != null) return r
|
||||
}
|
||||
} else {
|
||||
segment.traversal(context.current, transform).forEach {
|
||||
if (it == null) return@forEach
|
||||
val r = to.matchContext(context.next(it), transform, option)
|
||||
if (r != null) return r
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private val isMatchRoot = to.isMatchRoot && segment.isMatchAnyAncestor
|
||||
private val canQf = to.quickFindValue.canQf && segment.isMatchAnyDescendant
|
||||
private val canFq = segment.isMatchAnyDescendant && to.fastQueryList.isNotEmpty()
|
||||
}
|
|
@ -8,7 +8,7 @@ sealed class SelectorCheckException(override val message: String) : Exception(me
|
|||
@JsExport
|
||||
data class UnknownIdentifierException(
|
||||
val value: ValueExpression.Identifier,
|
||||
) : SelectorCheckException("Unknown Identifier: ${value.value}")
|
||||
) : SelectorCheckException("Unknown Identifier: ${value.name}")
|
||||
|
||||
@JsExport
|
||||
data class UnknownMemberException(
|
||||
|
@ -18,7 +18,7 @@ data class UnknownMemberException(
|
|||
@JsExport
|
||||
data class UnknownIdentifierMethodException(
|
||||
val value: ValueExpression.Identifier,
|
||||
) : SelectorCheckException("Unknown Identifier Method: ${value.value}")
|
||||
) : SelectorCheckException("Unknown Identifier Method: ${value.name}")
|
||||
|
||||
@JsExport
|
||||
data class UnknownMemberMethodException(
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
sealed class FastQuery(open val value: String) : Stringify {
|
||||
override fun stringify() = value
|
||||
data class Id(override val value: String) : FastQuery(value)
|
||||
data class Vid(override val value: String) : FastQuery(value)
|
||||
data class Text(override val value: String) : FastQuery(value)
|
||||
}
|
||||
|
||||
internal fun getFastQueryList(segment: PropertySegment): List<FastQuery>? {
|
||||
val exp = segment.expressions.firstOrNull() ?: return null
|
||||
if (exp is LogicalExpression) {
|
||||
if (exp.operator.value != LogicalOperator.OrOperator) return null
|
||||
val expArray = exp.getSameExpressionArray() ?: return null
|
||||
val list = mutableListOf<FastQuery>()
|
||||
expArray.forEach { e ->
|
||||
val fq = expToFastQuery(e) ?: return null
|
||||
list.add(fq)
|
||||
}
|
||||
return list
|
||||
}
|
||||
if (exp is BinaryExpression) {
|
||||
val fq = expToFastQuery(exp) ?: return null
|
||||
return listOf(fq)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun expToFastQuery(e: BinaryExpression): FastQuery? {
|
||||
if (e.left !is ValueExpression.Identifier) return null
|
||||
if (e.right !is ValueExpression.StringLiteral) return null
|
||||
if (e.right.value.isEmpty()) return null
|
||||
if (e.left.value == "id" && e.operator.value == CompareOperator.Equal) {
|
||||
return FastQuery.Id(e.right.value)
|
||||
} else if (e.left.value == "vid" && e.operator.value == CompareOperator.Equal) {
|
||||
return FastQuery.Vid(e.right.value)
|
||||
} else if (e.left.value == "text" && (e.operator.value == CompareOperator.Equal || e.operator.value == CompareOperator.Start || e.operator.value == CompareOperator.Include || e.operator.value == CompareOperator.End)) {
|
||||
return FastQuery.Text(e.right.value)
|
||||
}
|
||||
return null
|
||||
}
|
|
@ -33,4 +33,37 @@ data class LogicalExpression(
|
|||
}
|
||||
return "$leftStr\u0020${operator.stringify()}\u0020$rightStr"
|
||||
}
|
||||
|
||||
fun getSameExpressionArray(): Array<BinaryExpression>? {
|
||||
if (left is LogicalExpression && left.operator.value != operator.value) {
|
||||
return null
|
||||
}
|
||||
if (right is LogicalExpression && right.operator.value != operator.value) {
|
||||
return null
|
||||
}
|
||||
return when (left) {
|
||||
is BinaryExpression -> when (right) {
|
||||
is BinaryExpression -> arrayOf(left, right)
|
||||
is LogicalExpression -> {
|
||||
arrayOf(left) + (right.getSameExpressionArray() ?: return null)
|
||||
}
|
||||
|
||||
is NotExpression -> null
|
||||
}
|
||||
|
||||
is LogicalExpression -> {
|
||||
val leftArray = left.getSameExpressionArray() ?: return null
|
||||
when (right) {
|
||||
is BinaryExpression -> leftArray + right
|
||||
is LogicalExpression -> {
|
||||
return leftArray + (right.getSameExpressionArray() ?: return null)
|
||||
}
|
||||
|
||||
is NotExpression -> null
|
||||
}
|
||||
}
|
||||
|
||||
is NotExpression -> null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class MatchOption(
|
||||
val quickFind: Boolean = false,
|
||||
val fastQuery: Boolean = false,
|
||||
)
|
||||
|
||||
val defaultMatchOption = MatchOption()
|
|
@ -6,7 +6,7 @@ import kotlin.js.JsExport
|
|||
data class PropertyWrapper(
|
||||
val segment: PropertySegment,
|
||||
val to: ConnectWrapper? = null,
|
||||
):Stringify {
|
||||
) : Stringify {
|
||||
override fun stringify(): String {
|
||||
return (if (to != null) {
|
||||
to.stringify() + "\u0020"
|
||||
|
@ -18,6 +18,7 @@ data class PropertyWrapper(
|
|||
fun <T> matchContext(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
option: MatchOption,
|
||||
): Context<T>? {
|
||||
if (!segment.match(context, transform)) {
|
||||
return null
|
||||
|
@ -25,7 +26,7 @@ data class PropertyWrapper(
|
|||
if (to == null) {
|
||||
return context
|
||||
}
|
||||
return to.matchContext(context, transform)
|
||||
return to.matchContext(context, transform, option)
|
||||
}
|
||||
|
||||
|
||||
|
@ -33,7 +34,7 @@ data class PropertyWrapper(
|
|||
e is BinaryExpression && e.operator.value == CompareOperator.Equal && ((e.left.value == "depth" && e.right.value == 0) || (e.left.value == "parent" && e.right.value == "null"))
|
||||
}
|
||||
|
||||
val quickFindValue = getQuickFindValue(segment)
|
||||
val fastQueryList = getFastQueryList(segment) ?: emptyList()
|
||||
|
||||
val length: Int
|
||||
get() = if (to == null) 1 else to.to.length + 1
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class QuickFindValue(
|
||||
val id: String?,
|
||||
val vid: String?,
|
||||
val text: String?,
|
||||
) {
|
||||
val canQf = id != null || vid != null || text != null
|
||||
}
|
||||
|
||||
internal fun getQuickFindValue(segment: PropertySegment): QuickFindValue {
|
||||
val id = segment.expressions.firstOrNull().let { e ->
|
||||
if (e is BinaryExpression && e.left.value == "id" && e.operator.value == CompareOperator.Equal && e.right is ValueExpression.StringLiteral) {
|
||||
e.right.value
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val vid = segment.expressions.firstOrNull().let { e ->
|
||||
if (e is BinaryExpression && e.left.value == "vid" && e.operator.value == CompareOperator.Equal && e.right is ValueExpression.StringLiteral) {
|
||||
e.right.value
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val text = segment.expressions.firstOrNull().let { e ->
|
||||
if (e is BinaryExpression && e.left.value == "text" && (e.operator.value == CompareOperator.Equal || e.operator.value == CompareOperator.Start || e.operator.value == CompareOperator.Include || e.operator.value == CompareOperator.End) && e.right is ValueExpression.StringLiteral) {
|
||||
e.right.value
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
return QuickFindValue(id, vid, text)
|
||||
}
|
|
@ -29,28 +29,31 @@ class Selector(
|
|||
fun <T> matchContext(
|
||||
node: T,
|
||||
transform: Transform<T>,
|
||||
option: MatchOption,
|
||||
): Context<T>? {
|
||||
return propertyWrapper.matchContext(Context(node), transform)
|
||||
return propertyWrapper.matchContext(Context(node), transform, option)
|
||||
}
|
||||
|
||||
fun <T> match(
|
||||
node: T,
|
||||
transform: Transform<T>,
|
||||
option: MatchOption,
|
||||
): T? {
|
||||
val ctx = matchContext(node, transform) ?: return null
|
||||
val ctx = matchContext(node, transform, option) ?: return null
|
||||
return ctx.get(targetIndex).current
|
||||
}
|
||||
|
||||
val quickFindValue = propertyWrapper.quickFindValue
|
||||
val fastQueryList = propertyWrapper.fastQueryList
|
||||
val quickFindValue = if (fastQueryList.size == 1) fastQueryList.first() else null
|
||||
|
||||
val isMatchRoot = propertyWrapper.isMatchRoot
|
||||
|
||||
val connectKeys = run {
|
||||
var c = propertyWrapper.to
|
||||
val keys = mutableListOf<String>()
|
||||
val keys = mutableListOf<ConnectOperator>()
|
||||
while (c != null) {
|
||||
c.apply {
|
||||
keys.add(segment.operator.key)
|
||||
keys.add(segment.operator)
|
||||
}
|
||||
c = c.to.to
|
||||
}
|
||||
|
|
|
@ -136,43 +136,70 @@ class Transform<T> @JsExport.Ignore constructor(
|
|||
} while (stack.isNotEmpty())
|
||||
}
|
||||
},
|
||||
|
||||
val traverseFastQueryDescendants: (T, List<FastQuery>) -> Sequence<T> = { _, _ -> emptySequence() }
|
||||
) {
|
||||
@JsExport.Ignore
|
||||
val querySelectorAll: (T, Selector) -> Sequence<T> = { node, selector ->
|
||||
sequence {
|
||||
selector.match(node, this@Transform)?.let { yield(it) }
|
||||
getDescendants(node).forEach { childNode ->
|
||||
selector.match(childNode, this@Transform)?.let { yield(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
val querySelector: (T, Selector) -> T? = { node, selector ->
|
||||
querySelectorAll(node, selector).firstOrNull()
|
||||
}
|
||||
|
||||
@JsExport.Ignore
|
||||
val querySelectorAllContext: (T, Selector) -> Sequence<Context<T>> = { node, selector ->
|
||||
sequence {
|
||||
selector.matchContext(node, this@Transform)?.let { yield(it) }
|
||||
fun querySelectorAll(
|
||||
node: T,
|
||||
selector: Selector,
|
||||
option: MatchOption = defaultMatchOption,
|
||||
): Sequence<T> {
|
||||
return sequence {
|
||||
selector.match(node, this@Transform, option)?.let { yield(it) }
|
||||
getDescendants(node).forEach { childNode ->
|
||||
selector.matchContext(childNode, this@Transform)?.let { yield(it) }
|
||||
selector.match(childNode, this@Transform, option)?.let { yield(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val querySelectorContext: (T, Selector) -> Context<T>? = { node, selector ->
|
||||
querySelectorAllContext(node, selector).firstOrNull()
|
||||
fun querySelector(
|
||||
node: T,
|
||||
selector: Selector,
|
||||
option: MatchOption = defaultMatchOption,
|
||||
): T? {
|
||||
return querySelectorAll(node, selector, option).firstOrNull()
|
||||
}
|
||||
|
||||
@JsExport.Ignore
|
||||
fun querySelectorAllContext(
|
||||
node: T,
|
||||
selector: Selector,
|
||||
option: MatchOption = defaultMatchOption,
|
||||
): Sequence<Context<T>> {
|
||||
return sequence {
|
||||
selector.matchContext(node, this@Transform, option)?.let { yield(it) }
|
||||
getDescendants(node).forEach { childNode ->
|
||||
selector.matchContext(childNode, this@Transform, option)?.let { yield(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun querySelectorContext(
|
||||
node: T,
|
||||
selector: Selector,
|
||||
option: MatchOption = defaultMatchOption,
|
||||
): Context<T>? {
|
||||
return querySelectorAllContext(node, selector, option).firstOrNull()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val querySelectorAllArray: (T, Selector) -> Array<T> = { node, selector ->
|
||||
val result = querySelectorAll(node, selector).toList()
|
||||
(result as List<Any>).toTypedArray() as Array<T>
|
||||
fun querySelectorAllArray(
|
||||
node: T,
|
||||
selector: Selector,
|
||||
option: MatchOption = defaultMatchOption,
|
||||
): Array<T> {
|
||||
val result = querySelectorAll(node, selector, option).toList()
|
||||
return (result as List<Any>).toTypedArray() as Array<T>
|
||||
}
|
||||
|
||||
val querySelectorAllContextArray: (T, Selector) -> Array<Context<T>> = { node, selector ->
|
||||
val result = querySelectorAllContext(node, selector)
|
||||
result.toList().toTypedArray()
|
||||
fun querySelectorAllContextArray(
|
||||
node: T,
|
||||
selector: Selector,
|
||||
option: MatchOption = defaultMatchOption,
|
||||
): Array<Context<T>> {
|
||||
return querySelectorAllContext(node, selector, option).toList().toTypedArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -19,8 +19,8 @@ sealed class ValueExpression(open val value: Any?, open val type: String) : Posi
|
|||
|
||||
data class Identifier internal constructor(
|
||||
override val start: Int,
|
||||
override val value: String,
|
||||
) : Variable(value) {
|
||||
val name: String,
|
||||
) : Variable(name) {
|
||||
override val end = start + value.length
|
||||
override fun <T> getAttr(context: Context<T>, transform: Transform<T>): Any? {
|
||||
return transform.getAttr(context, value)
|
||||
|
@ -76,7 +76,7 @@ sealed class ValueExpression(open val value: Any?, open val type: String) : Posi
|
|||
is Identifier -> {
|
||||
transform.getInvoke(
|
||||
context,
|
||||
callee.value,
|
||||
callee.name,
|
||||
arguments.map {
|
||||
it.getAttr(context, transform).whenNull { return null }
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ sealed class ValueExpression(open val value: Any?, open val type: String) : Posi
|
|||
override val methods: Array<String>
|
||||
get() = when (callee) {
|
||||
is CallExpression -> callee.methods
|
||||
is Identifier -> arrayOf(callee.value)
|
||||
is Identifier -> arrayOf(callee.name)
|
||||
is MemberExpression -> arrayOf(*callee.object0.methods, callee.property)
|
||||
}.toMutableList().plus(arguments.flatMap { it.methods.toList() })
|
||||
.toTypedArray()
|
||||
|
|
|
@ -267,4 +267,12 @@ class ParserTest {
|
|||
println("error: $error")
|
||||
println("check_type: $selector")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun check_qf(){
|
||||
val source = "@UIView[clickable=true] -3 FlattenUIText[text=`a`||text=`b`||vid=`233`]"
|
||||
val selector = Selector.parse(source)
|
||||
println("fastQuery: ${selector.fastQueryList}")
|
||||
println("quickFind: ${selector.quickFindValue}")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user