From 028ad89d3cca331be698dbcb1e1a121f64fc16c4 Mon Sep 17 00:00:00 2001 From: lisonge Date: Sat, 13 Jul 2024 17:06:38 +0800 Subject: [PATCH] feat: fastQuery --- .../kotlin/li/songe/gkd/data/GkdAction.kt | 4 +- .../li/songe/gkd/data/RawSubscription.kt | 23 ++++-- .../kotlin/li/songe/gkd/data/ResolvedRule.kt | 27 ++++--- .../main/kotlin/li/songe/gkd/service/AbExt.kt | 66 ++++++++++------ .../li/songe/gkd/service/GkdAbService.kt | 8 +- .../kotlin/li/songe/gkd/util/SubsState.kt | 4 +- .../li/songe/selector/ConnectWrapper.kt | 21 ++++-- .../kotlin/li/songe/selector/Exception.kt | 4 +- .../kotlin/li/songe/selector/FastQuery.kt | 44 +++++++++++ .../li/songe/selector/LogicalExpression.kt | 33 ++++++++ .../kotlin/li/songe/selector/MatchOption.kt | 11 +++ .../li/songe/selector/PropertyWrapper.kt | 7 +- .../li/songe/selector/QuickFindValue.kt | 37 --------- .../kotlin/li/songe/selector/Selector.kt | 13 ++-- .../kotlin/li/songe/selector/Transform.kt | 75 +++++++++++++------ .../li/songe/selector/ValueExpression.kt | 8 +- .../kotlin/li/songe/selector/ParserTest.kt | 8 ++ 17 files changed, 269 insertions(+), 124 deletions(-) create mode 100644 selector/src/commonMain/kotlin/li/songe/selector/FastQuery.kt create mode 100644 selector/src/commonMain/kotlin/li/songe/selector/MatchOption.kt delete mode 100644 selector/src/commonMain/kotlin/li/songe/selector/QuickFindValue.kt diff --git a/app/src/main/kotlin/li/songe/gkd/data/GkdAction.kt b/app/src/main/kotlin/li/songe/gkd/data/GkdAction.kt index be847b9..1ce1fe5 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/GkdAction.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/GkdAction.kt @@ -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 diff --git a/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt b/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt index 9f0ed98..e8ea2f3 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt @@ -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 + val cacheMap: MutableMap } 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?, override val rules: List, ) : RawGroupProps, RawGlobalRuleProps { - val appIdEnable by lazy { (apps ?: emptyList()).associate { a -> a.id to (a.enable ?: true) } } + override val cacheMap by lazy { HashMap() } 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?, override val excludeVersionCodes: List?, ) : RawGroupProps, RawAppRuleProps { - + override val cacheMap by lazy { HashMap() } 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"), diff --git a/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt b/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt index 45c6aca..a4b245c 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/ResolvedRule.kt @@ -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 } diff --git a/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt b/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt index 2a8e1e4..5a80979 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt @@ -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, 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 { + 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 +): Sequence { + 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) } diff --git a/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt b/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt index 00cf0aa..258b8de 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt @@ -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) { diff --git a/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt b/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt index 6e18ee0..af2d612 100644 --- a/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt +++ b/app/src/main/kotlin/li/songe/gkd/util/SubsState.kt @@ -230,12 +230,12 @@ val ruleSummaryFlow by lazy { val appGroupConfigs = subGroupSubsConfigs.filter { c -> c.appId == appRaw.id } val subAppGroupToRules = mutableMapOf>() 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, diff --git a/selector/src/commonMain/kotlin/li/songe/selector/ConnectWrapper.kt b/selector/src/commonMain/kotlin/li/songe/selector/ConnectWrapper.kt index b88eca3..fbb6a4d 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/ConnectWrapper.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/ConnectWrapper.kt @@ -14,20 +14,29 @@ data class ConnectWrapper( fun matchContext( context: Context, transform: Transform, + option: MatchOption ): Context? { if (isMatchRoot) { // C <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'] <? { + 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() + 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 +} \ No newline at end of file diff --git a/selector/src/commonMain/kotlin/li/songe/selector/LogicalExpression.kt b/selector/src/commonMain/kotlin/li/songe/selector/LogicalExpression.kt index ff63bcf..313ff85 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/LogicalExpression.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/LogicalExpression.kt @@ -33,4 +33,37 @@ data class LogicalExpression( } return "$leftStr\u0020${operator.stringify()}\u0020$rightStr" } + + fun getSameExpressionArray(): Array? { + 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 + } + } } \ No newline at end of file diff --git a/selector/src/commonMain/kotlin/li/songe/selector/MatchOption.kt b/selector/src/commonMain/kotlin/li/songe/selector/MatchOption.kt new file mode 100644 index 0000000..a1464c8 --- /dev/null +++ b/selector/src/commonMain/kotlin/li/songe/selector/MatchOption.kt @@ -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() diff --git a/selector/src/commonMain/kotlin/li/songe/selector/PropertyWrapper.kt b/selector/src/commonMain/kotlin/li/songe/selector/PropertyWrapper.kt index 1c9a01a..0d16994 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/PropertyWrapper.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/PropertyWrapper.kt @@ -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 matchContext( context: Context, transform: Transform, + option: MatchOption, ): Context? { 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 diff --git a/selector/src/commonMain/kotlin/li/songe/selector/QuickFindValue.kt b/selector/src/commonMain/kotlin/li/songe/selector/QuickFindValue.kt deleted file mode 100644 index 49a55ae..0000000 --- a/selector/src/commonMain/kotlin/li/songe/selector/QuickFindValue.kt +++ /dev/null @@ -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) -} \ No newline at end of file diff --git a/selector/src/commonMain/kotlin/li/songe/selector/Selector.kt b/selector/src/commonMain/kotlin/li/songe/selector/Selector.kt index 19779d3..8c746c1 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/Selector.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/Selector.kt @@ -29,28 +29,31 @@ class Selector( fun matchContext( node: T, transform: Transform, + option: MatchOption, ): Context? { - return propertyWrapper.matchContext(Context(node), transform) + return propertyWrapper.matchContext(Context(node), transform, option) } fun match( node: T, transform: Transform, + 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() + val keys = mutableListOf() while (c != null) { c.apply { - keys.add(segment.operator.key) + keys.add(segment.operator) } c = c.to.to } diff --git a/selector/src/commonMain/kotlin/li/songe/selector/Transform.kt b/selector/src/commonMain/kotlin/li/songe/selector/Transform.kt index ab4bdc6..1a33e88 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/Transform.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/Transform.kt @@ -136,43 +136,70 @@ class Transform @JsExport.Ignore constructor( } while (stack.isNotEmpty()) } }, + + val traverseFastQueryDescendants: (T, List) -> Sequence = { _, _ -> emptySequence() } ) { - @JsExport.Ignore - val querySelectorAll: (T, Selector) -> Sequence = { 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> = { node, selector -> - sequence { - selector.matchContext(node, this@Transform)?.let { yield(it) } + fun querySelectorAll( + node: T, + selector: Selector, + option: MatchOption = defaultMatchOption, + ): Sequence { + 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? = { 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> { + 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? { + return querySelectorAllContext(node, selector, option).firstOrNull() } @Suppress("UNCHECKED_CAST") - val querySelectorAllArray: (T, Selector) -> Array = { node, selector -> - val result = querySelectorAll(node, selector).toList() - (result as List).toTypedArray() as Array + fun querySelectorAllArray( + node: T, + selector: Selector, + option: MatchOption = defaultMatchOption, + ): Array { + val result = querySelectorAll(node, selector, option).toList() + return (result as List).toTypedArray() as Array } - val querySelectorAllContextArray: (T, Selector) -> Array> = { node, selector -> - val result = querySelectorAllContext(node, selector) - result.toList().toTypedArray() + fun querySelectorAllContextArray( + node: T, + selector: Selector, + option: MatchOption = defaultMatchOption, + ): Array> { + return querySelectorAllContext(node, selector, option).toList().toTypedArray() } companion object { diff --git a/selector/src/commonMain/kotlin/li/songe/selector/ValueExpression.kt b/selector/src/commonMain/kotlin/li/songe/selector/ValueExpression.kt index f9724c4..95eec8a 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/ValueExpression.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/ValueExpression.kt @@ -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 getAttr(context: Context, transform: Transform): 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 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() diff --git a/selector/src/jvmTest/kotlin/li/songe/selector/ParserTest.kt b/selector/src/jvmTest/kotlin/li/songe/selector/ParserTest.kt index b449c6f..bed1cff 100644 --- a/selector/src/jvmTest/kotlin/li/songe/selector/ParserTest.kt +++ b/selector/src/jvmTest/kotlin/li/songe/selector/ParserTest.kt @@ -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}") + } }