feat: fastQuery

This commit is contained in:
lisonge 2024-07-13 17:06:38 +08:00
parent dd12a20f9c
commit 028ad89d3c
17 changed files with 269 additions and 124 deletions

View File

@ -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

View File

@ -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"),

View File

@ -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 }

View File

@ -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)
}

View File

@ -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) {

View File

@ -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,

View File

@ -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()
}

View File

@ -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(

View File

@ -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
}

View File

@ -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
}
}
}

View File

@ -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()

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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()

View File

@ -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}")
}
}