mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-15 19:22:26 +08:00
feat(selector): add prev/getPrev
This commit is contained in:
parent
bbd52a2f35
commit
c3d7969ac9
|
@ -7,6 +7,7 @@ import android.view.accessibility.AccessibilityEvent
|
|||
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.MismatchExpressionTypeException
|
||||
import li.songe.selector.MismatchOperatorTypeException
|
||||
import li.songe.selector.MismatchParamTypeException
|
||||
|
@ -95,9 +96,8 @@ fun AccessibilityNodeInfo.querySelector(
|
|||
emptyList()
|
||||
})
|
||||
if (nodes.isNotEmpty()) {
|
||||
val trackNodes = ArrayList<AccessibilityNodeInfo>(selector.tracks.size)
|
||||
nodes.forEach { childNode ->
|
||||
val targetNode = selector.match(childNode, transform, trackNodes)
|
||||
val targetNode = selector.match(childNode, transform)
|
||||
if (targetNode != null) return targetNode
|
||||
}
|
||||
}
|
||||
|
@ -232,6 +232,14 @@ class NodeCache {
|
|||
indexMap.evictAll()
|
||||
}
|
||||
|
||||
fun getRoot(node: AccessibilityNodeInfo): AccessibilityNodeInfo? {
|
||||
if (rootNode == null) {
|
||||
rootNode = GkdAbService.service?.safeActiveWindow
|
||||
}
|
||||
if (node == rootNode) return null
|
||||
return rootNode
|
||||
}
|
||||
|
||||
val sizeList: List<Int>
|
||||
get() = listOf(childMap.size(), parentMap.size(), indexMap.size())
|
||||
|
||||
|
@ -310,13 +318,16 @@ fun createCacheTransform(): CacheTransform {
|
|||
}
|
||||
val getNodeAttr = createGetNodeAttr(cache)
|
||||
val transform = Transform(
|
||||
getAttr = { node, name ->
|
||||
when (node) {
|
||||
is AccessibilityNodeInfo -> getNodeAttr(node, name)
|
||||
is CharSequence -> getCharSequenceAttr(node, name)
|
||||
else -> {
|
||||
null
|
||||
getAttr = { target, name ->
|
||||
when (target) {
|
||||
is Context<*> -> when (name) {
|
||||
"prev" -> target.prev
|
||||
else -> getNodeAttr(target.current as AccessibilityNodeInfo, name)
|
||||
}
|
||||
|
||||
is AccessibilityNodeInfo -> getNodeAttr(target, name)
|
||||
is CharSequence -> getCharSequenceAttr(target, name)
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
getInvoke = { target, name, args ->
|
||||
|
@ -331,6 +342,20 @@ fun createCacheTransform(): CacheTransform {
|
|||
else -> null
|
||||
}
|
||||
|
||||
is Context<*> -> when (name) {
|
||||
"getPrev" -> {
|
||||
args.getIntOrNull()?.let { target.getPrev(it) }
|
||||
}
|
||||
|
||||
"getChild" -> {
|
||||
args.getIntOrNull()?.let { index ->
|
||||
cache.getChild(target.current as AccessibilityNodeInfo, index)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
is CharSequence -> getCharSequenceInvoke(target, name, args)
|
||||
is Int -> getIntInvoke(target, name, args)
|
||||
is Boolean -> getBooleanInvoke(target, name, args)
|
||||
|
@ -342,6 +367,7 @@ fun createCacheTransform(): CacheTransform {
|
|||
getName = { node -> node.className },
|
||||
getChildren = getChildrenCache,
|
||||
getParent = { cache.getParent(it) },
|
||||
getRoot = { cache.getRoot(it) },
|
||||
getDescendants = { node ->
|
||||
sequence {
|
||||
val stack = getChildrenCache(node).toMutableList()
|
||||
|
@ -349,7 +375,7 @@ fun createCacheTransform(): CacheTransform {
|
|||
stack.reverse()
|
||||
val tempNodes = mutableListOf<AccessibilityNodeInfo>()
|
||||
do {
|
||||
val top = stack.removeLast()
|
||||
val top = stack.removeAt(stack.lastIndex)
|
||||
yield(top)
|
||||
for (childNode in getChildrenCache(top)) {
|
||||
tempNodes.add(childNode)
|
||||
|
@ -363,7 +389,7 @@ fun createCacheTransform(): CacheTransform {
|
|||
} while (stack.isNotEmpty())
|
||||
}.take(MAX_DESCENDANTS_SIZE)
|
||||
},
|
||||
getChildrenX = { node, connectExpression ->
|
||||
traverseChildren = { node, connectExpression ->
|
||||
sequence {
|
||||
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { offset ->
|
||||
connectExpression.maxOffset?.let { maxOffset ->
|
||||
|
@ -376,7 +402,7 @@ fun createCacheTransform(): CacheTransform {
|
|||
}
|
||||
}
|
||||
},
|
||||
getBeforeBrothers = { node, connectExpression ->
|
||||
traverseBeforeBrothers = { node, connectExpression ->
|
||||
sequence {
|
||||
val parentVal = cache.getParent(node) ?: return@sequence
|
||||
// 如果 node 由 quickFind 得到, 则第一次调用此方法可能得到 cache.index 是空
|
||||
|
@ -407,7 +433,7 @@ fun createCacheTransform(): CacheTransform {
|
|||
}
|
||||
}
|
||||
},
|
||||
getAfterBrothers = { node, connectExpression ->
|
||||
traverseAfterBrothers = { node, connectExpression ->
|
||||
val parentVal = cache.getParent(node)
|
||||
if (parentVal != null) {
|
||||
val index = cache.indexMap[node]
|
||||
|
@ -447,7 +473,7 @@ fun createCacheTransform(): CacheTransform {
|
|||
emptySequence()
|
||||
}
|
||||
},
|
||||
getDescendantsX = { node, connectExpression ->
|
||||
traverseDescendants = { node, connectExpression ->
|
||||
sequence {
|
||||
val stack = getChildrenCache(node).toMutableList()
|
||||
if (stack.isEmpty()) return@sequence
|
||||
|
@ -455,7 +481,7 @@ fun createCacheTransform(): CacheTransform {
|
|||
val tempNodes = mutableListOf<AccessibilityNodeInfo>()
|
||||
var offset = 0
|
||||
do {
|
||||
val top = stack.removeLast()
|
||||
val top = stack.removeAt(stack.lastIndex)
|
||||
if (connectExpression.checkOffset(offset)) {
|
||||
yield(top)
|
||||
}
|
||||
|
@ -493,11 +519,14 @@ fun createNoCacheTransform(): CacheTransform {
|
|||
val transform = Transform(
|
||||
getAttr = { target, name ->
|
||||
when (target) {
|
||||
is Context<*> -> when (name) {
|
||||
"prev" -> target.prev
|
||||
else -> getNodeAttr(target.current as AccessibilityNodeInfo, name)
|
||||
}
|
||||
|
||||
is AccessibilityNodeInfo -> getNodeAttr(target, name)
|
||||
is CharSequence -> getCharSequenceAttr(target, name)
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
getInvoke = { target, name, args ->
|
||||
|
@ -512,6 +541,20 @@ fun createNoCacheTransform(): CacheTransform {
|
|||
else -> null
|
||||
}
|
||||
|
||||
is Context<*> -> when (name) {
|
||||
"getPrev" -> {
|
||||
args.getIntOrNull()?.let { target.getPrev(it) }
|
||||
}
|
||||
|
||||
"getChild" -> {
|
||||
args.getIntOrNull()?.let { index ->
|
||||
cache.getChild(target.current as AccessibilityNodeInfo, index)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
is CharSequence -> getCharSequenceInvoke(target, name, args)
|
||||
is Int -> getIntInvoke(target, name, args)
|
||||
is Boolean -> getBooleanInvoke(target, name, args)
|
||||
|
@ -529,7 +572,7 @@ fun createNoCacheTransform(): CacheTransform {
|
|||
val tempNodes = mutableListOf<AccessibilityNodeInfo>()
|
||||
var offset = 0
|
||||
do {
|
||||
val top = stack.removeLast()
|
||||
val top = stack.removeAt(stack.lastIndex)
|
||||
yield(top)
|
||||
offset++
|
||||
if (offset > MAX_DESCENDANTS_SIZE) {
|
||||
|
@ -547,7 +590,7 @@ fun createNoCacheTransform(): CacheTransform {
|
|||
} while (stack.isNotEmpty())
|
||||
}
|
||||
},
|
||||
getChildrenX = { node, connectExpression ->
|
||||
traverseChildren = { node, connectExpression ->
|
||||
sequence {
|
||||
repeat(node.childCount.coerceAtMost(MAX_CHILD_SIZE)) { offset ->
|
||||
connectExpression.maxOffset?.let { maxOffset ->
|
||||
|
|
|
@ -131,10 +131,10 @@ class AppConfigVm @Inject constructor(stateHandle: SavedStateHandle) : ViewModel
|
|||
|
||||
private val appConfigsFlow = subsFlow.map { subs ->
|
||||
DbSet.subsConfigDao.queryAppConfig(subs.map { it.first.id }, args.appId)
|
||||
}.flatMapLatest { it }
|
||||
}.flatMapLatest { it }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
|
||||
private val categoryConfigsFlow = subsFlow.map { subs ->
|
||||
DbSet.categoryConfigDao.queryBySubsIds(subs.map { it.first.id })
|
||||
}.flatMapLatest { it }
|
||||
}.flatMapLatest { it }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
|
||||
val appGroupsFlow = combine(
|
||||
sortedAppGroupsFlow,
|
||||
appConfigsFlow,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
|
@ -7,6 +8,7 @@ plugins {
|
|||
|
||||
kotlin {
|
||||
jvm {
|
||||
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_17)
|
||||
}
|
||||
|
|
|
@ -10,8 +10,11 @@ data class BinaryExpression(
|
|||
val operator: PositionImpl<CompareOperator>,
|
||||
val right: ValueExpression,
|
||||
) : Expression() {
|
||||
override fun <T> match(node: T, transform: Transform<T>): Boolean {
|
||||
return operator.value.compare(node, transform, left, right)
|
||||
override fun <T> match(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
): Boolean {
|
||||
return operator.value.compare(context, transform, left, right)
|
||||
}
|
||||
|
||||
override val binaryExpressions
|
||||
|
|
|
@ -7,10 +7,10 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
override fun stringify() = key
|
||||
|
||||
internal abstract fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
rightExp: ValueExpression,
|
||||
): Boolean
|
||||
|
||||
internal abstract fun allowType(left: ValueExpression, right: ValueExpression): Boolean
|
||||
|
@ -51,13 +51,13 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object Equal : CompareOperator("=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
rightExp: ValueExpression,
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is CharSequence && right is CharSequence) {
|
||||
left.contentReversedEquals(right)
|
||||
} else {
|
||||
|
@ -70,12 +70,12 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object NotEqual : CompareOperator("!=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
rightExp: ValueExpression,
|
||||
): Boolean {
|
||||
return !Equal.compare(node, transform, leftExp, rightExp)
|
||||
return !Equal.compare(context, transform, leftExp, rightExp)
|
||||
}
|
||||
|
||||
override fun allowType(left: ValueExpression, right: ValueExpression) = true
|
||||
|
@ -83,13 +83,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object Start : CompareOperator("^=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is CharSequence && right is CharSequence) {
|
||||
left.startsWith(right)
|
||||
} else {
|
||||
|
@ -104,13 +105,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object NotStart : CompareOperator("!^=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is CharSequence && right is CharSequence) {
|
||||
!left.startsWith(right)
|
||||
} else {
|
||||
|
@ -124,13 +126,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object Include : CompareOperator("*=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is CharSequence && right is CharSequence) {
|
||||
left.contains(right)
|
||||
} else {
|
||||
|
@ -144,13 +147,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object NotInclude : CompareOperator("!*=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is CharSequence && right is CharSequence) {
|
||||
!left.contains(right)
|
||||
} else {
|
||||
|
@ -164,13 +168,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object End : CompareOperator("$=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is CharSequence && right is CharSequence) {
|
||||
left.endsWith(right)
|
||||
} else {
|
||||
|
@ -184,13 +189,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object NotEnd : CompareOperator("!$=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is CharSequence && right is CharSequence) {
|
||||
!left.endsWith(
|
||||
right
|
||||
|
@ -206,13 +212,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object Less : CompareOperator("<") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is Int && right is Int) left < right else false
|
||||
}
|
||||
|
||||
|
@ -224,13 +231,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object LessEqual : CompareOperator("<=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is Int && right is Int) left <= right else false
|
||||
}
|
||||
|
||||
|
@ -240,13 +248,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object More : CompareOperator(">") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is Int && right is Int) left > right else false
|
||||
}
|
||||
|
||||
|
@ -256,13 +265,14 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object MoreEqual : CompareOperator(">=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
val right = rightExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
val right = rightExp.getAttr(context, transform)
|
||||
return if (left is Int && right is Int) left >= right else false
|
||||
}
|
||||
|
||||
|
@ -273,12 +283,13 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object Matches : CompareOperator("~=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
return if (left is CharSequence && rightExp is ValueExpression.StringLiteral) {
|
||||
rightExp.outMatches(left)
|
||||
} else {
|
||||
|
@ -293,12 +304,13 @@ sealed class CompareOperator(val key: String) : Stringify {
|
|||
|
||||
data object NotMatches : CompareOperator("!~=") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
leftExp: ValueExpression,
|
||||
rightExp: ValueExpression
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(node, transform)
|
||||
rightExp: ValueExpression,
|
||||
|
||||
): Boolean {
|
||||
val left = leftExp.getAttr(context, transform)
|
||||
return if (left is CharSequence && rightExp is ValueExpression.StringLiteral) {
|
||||
!rightExp.outMatches(left)
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package li.songe.selector
|
||||
|
||||
sealed class ConnectExpression {
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
sealed class ConnectExpression : Stringify {
|
||||
abstract val minOffset: Int
|
||||
abstract val maxOffset: Int?
|
||||
abstract fun checkOffset(offset: Int): Boolean
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
sealed class ConnectOperator(val key: String) : Stringify {
|
||||
override fun stringify() = key
|
||||
|
||||
|
@ -22,7 +25,7 @@ sealed class ConnectOperator(val key: String) : Stringify {
|
|||
data object BeforeBrother : ConnectOperator("+") {
|
||||
override fun <T> traversal(
|
||||
node: T, transform: Transform<T>, connectExpression: ConnectExpression
|
||||
) = transform.getBeforeBrothers(node, connectExpression)
|
||||
) = transform.traverseBeforeBrothers(node, connectExpression)
|
||||
|
||||
}
|
||||
|
||||
|
@ -32,7 +35,7 @@ sealed class ConnectOperator(val key: String) : Stringify {
|
|||
data object AfterBrother : ConnectOperator("-") {
|
||||
override fun <T> traversal(
|
||||
node: T, transform: Transform<T>, connectExpression: ConnectExpression
|
||||
) = transform.getAfterBrothers(node, connectExpression)
|
||||
) = transform.traverseAfterBrothers(node, connectExpression)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,7 +44,7 @@ sealed class ConnectOperator(val key: String) : Stringify {
|
|||
data object Ancestor : ConnectOperator(">") {
|
||||
override fun <T> traversal(
|
||||
node: T, transform: Transform<T>, connectExpression: ConnectExpression
|
||||
) = transform.getAncestors(node, connectExpression)
|
||||
) = transform.traverseAncestors(node, connectExpression)
|
||||
|
||||
}
|
||||
|
||||
|
@ -51,7 +54,7 @@ sealed class ConnectOperator(val key: String) : Stringify {
|
|||
data object Child : ConnectOperator("<") {
|
||||
override fun <T> traversal(
|
||||
node: T, transform: Transform<T>, connectExpression: ConnectExpression
|
||||
) = transform.getChildrenX(node, connectExpression)
|
||||
) = transform.traverseChildren(node, connectExpression)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,7 +63,7 @@ sealed class ConnectOperator(val key: String) : Stringify {
|
|||
data object Descendant : ConnectOperator("<<") {
|
||||
override fun <T> traversal(
|
||||
node: T, transform: Transform<T>, connectExpression: ConnectExpression
|
||||
) = transform.getDescendantsX(node, connectExpression)
|
||||
) = transform.traverseDescendants(node, connectExpression)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class ConnectSegment(
|
||||
val operator: ConnectOperator = ConnectOperator.Ancestor,
|
||||
val connectExpression: ConnectExpression = PolynomialExpression(),
|
||||
) {
|
||||
override fun toString(): String {
|
||||
if (operator == ConnectOperator.Ancestor && connectExpression is PolynomialExpression && connectExpression.a == 1 && connectExpression.b == 0) {
|
||||
) : Stringify {
|
||||
override fun stringify(): String {
|
||||
if (isMatchAnyAncestor) {
|
||||
return ""
|
||||
}
|
||||
return operator.stringify() + connectExpression.toString()
|
||||
return operator.stringify() + connectExpression.stringify()
|
||||
}
|
||||
|
||||
internal fun <T> traversal(node: T, transform: Transform<T>): Sequence<T?> {
|
||||
return operator.traversal(node, transform, connectExpression)
|
||||
}
|
||||
|
||||
val isMatchAnyAncestor = operator == ConnectOperator.Ancestor
|
||||
&& connectExpression is PolynomialExpression
|
||||
&& connectExpression.a == 1 && connectExpression.b == 0
|
||||
|
||||
val isMatchAnyDescendant = operator == ConnectOperator.Descendant
|
||||
&& connectExpression is PolynomialExpression
|
||||
&& connectExpression.a == 1 && connectExpression.b == 0
|
||||
}
|
||||
|
|
|
@ -1,22 +1,33 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class ConnectWrapper(
|
||||
val segment: ConnectSegment,
|
||||
val to: PropertyWrapper,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return (to.toString() + "\u0020" + segment.toString()).trim()
|
||||
) : Stringify {
|
||||
override fun stringify(): String {
|
||||
return (to.stringify() + "\u0020" + segment.stringify()).trim()
|
||||
}
|
||||
|
||||
internal fun <T> matchTracks(
|
||||
node: T, transform: Transform<T>,
|
||||
trackNodes: MutableList<T>,
|
||||
): List<T>? {
|
||||
segment.traversal(node, transform).forEach {
|
||||
fun <T> matchContext(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
): 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)
|
||||
}
|
||||
segment.traversal(context.current, transform).forEach {
|
||||
if (it == null) return@forEach
|
||||
val r = to.matchTracks(it, transform, trackNodes)
|
||||
val r = to.matchContext(context.next(it), transform)
|
||||
if (r != null) return r
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private val isMatchRoot = to.isMatchRoot && segment.isMatchAnyAncestor
|
||||
private val canQf = to.quickFindValue.canQf && segment.isMatchAnyDescendant
|
||||
}
|
42
selector/src/commonMain/kotlin/li/songe/selector/Context.kt
Normal file
42
selector/src/commonMain/kotlin/li/songe/selector/Context.kt
Normal file
|
@ -0,0 +1,42 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class Context<T>(
|
||||
val current: T,
|
||||
val prev: Context<T>? = null,
|
||||
) {
|
||||
fun getPrev(index: Int): Context<T>? {
|
||||
if (index < 0) return null
|
||||
var context = prev ?: return null
|
||||
repeat(index) {
|
||||
context = context.prev ?: return null
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
fun get(index: Int): Context<T> {
|
||||
if (index == 0) return this
|
||||
return getPrev(index - 1) ?: throw IndexOutOfBoundsException()
|
||||
}
|
||||
|
||||
fun toList(): List<T> {
|
||||
val list = mutableListOf(this.current)
|
||||
var context = prev
|
||||
while (context != null) {
|
||||
list.add(context.current)
|
||||
context = context.prev
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun toArray(): Array<T> {
|
||||
return (toList() as List<Any>).toTypedArray() as Array<T>
|
||||
}
|
||||
|
||||
fun next(value: T): Context<T> {
|
||||
return Context(value, this)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,13 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
sealed class Expression : Position {
|
||||
internal abstract fun <T> match(node: T, transform: Transform<T>): Boolean
|
||||
abstract fun <T> match(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
): Boolean
|
||||
|
||||
abstract val binaryExpressions: Array<BinaryExpression>
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class LogicalExpression(
|
||||
override val start: Int,
|
||||
override val end: Int,
|
||||
|
@ -8,8 +10,11 @@ data class LogicalExpression(
|
|||
val operator: PositionImpl<LogicalOperator>,
|
||||
val right: Expression,
|
||||
) : Expression() {
|
||||
override fun <T> match(node: T, transform: Transform<T>): Boolean {
|
||||
return operator.value.compare(node, transform, left, right)
|
||||
override fun <T> match(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
): Boolean {
|
||||
return operator.value.compare(context, transform, left, right)
|
||||
}
|
||||
|
||||
override val binaryExpressions
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
sealed class LogicalOperator(val key: String) : Stringify {
|
||||
override fun stringify() = key
|
||||
|
||||
|
@ -12,8 +15,8 @@ sealed class LogicalOperator(val key: String) : Stringify {
|
|||
}
|
||||
}
|
||||
|
||||
internal abstract fun <T> compare(
|
||||
node: T,
|
||||
abstract fun <T> compare(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
left: Expression,
|
||||
right: Expression,
|
||||
|
@ -21,23 +24,29 @@ sealed class LogicalOperator(val key: String) : Stringify {
|
|||
|
||||
data object AndOperator : LogicalOperator("&&") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
left: Expression,
|
||||
right: Expression,
|
||||
): Boolean {
|
||||
return left.match(node, transform) && right.match(node, transform)
|
||||
return left.match(context, transform) && right.match(
|
||||
context,
|
||||
transform
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data object OrOperator : LogicalOperator("||") {
|
||||
override fun <T> compare(
|
||||
node: T,
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
left: Expression,
|
||||
right: Expression,
|
||||
): Boolean {
|
||||
return left.match(node, transform) || right.match(node, transform)
|
||||
return left.match(context, transform) || right.match(
|
||||
context,
|
||||
transform
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
@Suppress("UNUSED", "UNCHECKED_CAST")
|
||||
class MultiplatformSelector private constructor(
|
||||
internal val selector: Selector,
|
||||
) {
|
||||
val tracks = selector.tracks
|
||||
val trackIndex = selector.trackIndex
|
||||
val connectKeys = selector.connectKeys
|
||||
|
||||
val isMatchRoot = selector.isMatchRoot
|
||||
val quickFindValue = selector.quickFindValue
|
||||
|
||||
val binaryExpressions = selector.binaryExpressions
|
||||
fun checkType(typeInfo: TypeInfo) = selector.checkType(typeInfo)
|
||||
|
||||
fun <T : Any> match(node: T, transform: MultiplatformTransform<T>): T? {
|
||||
return selector.match(node, transform.transform)
|
||||
}
|
||||
|
||||
fun <T : Any> matchTrack(node: T, transform: MultiplatformTransform<T>): Array<T>? {
|
||||
return selector.matchTracks(node, transform.transform)?.toTypedArray<Any?>() as Array<T>?
|
||||
}
|
||||
|
||||
override fun toString() = selector.toString()
|
||||
|
||||
companion object {
|
||||
fun parse(source: String) = MultiplatformSelector(Selector.parse(source))
|
||||
fun parseOrNull(source: String) = Selector.parseOrNull(source)?.let(::MultiplatformSelector)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
@Suppress("UNCHECKED_CAST", "UNUSED")
|
||||
class MultiplatformTransform<T : Any>(
|
||||
getAttr: (Any?, String) -> Any?,
|
||||
getInvoke: (Any?, String, List<Any>) -> Any?,
|
||||
getName: (T) -> String?,
|
||||
getChildren: (T) -> Array<T>,
|
||||
getParent: (T) -> T?,
|
||||
) {
|
||||
internal val transform = Transform(
|
||||
getAttr = getAttr,
|
||||
getInvoke = getInvoke,
|
||||
getName = getName,
|
||||
getChildren = { node -> getChildren(node).asSequence() },
|
||||
getParent = getParent,
|
||||
)
|
||||
|
||||
val querySelectorAll: (T, MultiplatformSelector) -> Array<T> = { node, selector ->
|
||||
val result =
|
||||
transform.querySelectorAll(node, selector.selector).toList().toTypedArray<Any?>()
|
||||
result as Array<T>
|
||||
}
|
||||
|
||||
val querySelector: (T, MultiplatformSelector) -> T? = { node, selector ->
|
||||
transform.querySelectorAll(node, selector.selector).firstOrNull()
|
||||
}
|
||||
|
||||
val querySelectorTrackAll: (T, MultiplatformSelector) -> Array<Array<T>> = { node, selector ->
|
||||
val result = transform.querySelectorTrackAll(node, selector.selector)
|
||||
.map { it.toTypedArray<Any?>() as Array<T> }.toList().toTypedArray<Any?>()
|
||||
result as Array<Array<T>>
|
||||
}
|
||||
|
||||
val querySelectorTrack: (T, MultiplatformSelector) -> Array<T>? = { node, selector ->
|
||||
transform.querySelectorTrackAll(node, selector.selector).firstOrNull()
|
||||
?.toTypedArray<Any?>() as Array<T>?
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class NotExpression(
|
||||
override val start: Int,
|
||||
val expression: Expression
|
||||
|
@ -7,8 +10,11 @@ data class NotExpression(
|
|||
override val end: Int
|
||||
get() = expression.end
|
||||
|
||||
override fun <T> match(node: T, transform: Transform<T>): Boolean {
|
||||
return !expression.match(node, transform)
|
||||
override fun <T> match(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
): Boolean {
|
||||
return !expression.match(context, transform)
|
||||
}
|
||||
|
||||
override val binaryExpressions: Array<BinaryExpression>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
/**
|
||||
* an+b
|
||||
*/
|
||||
@JsExport
|
||||
data class PolynomialExpression(val a: Int = 0, val b: Int = 1) : ConnectExpression() {
|
||||
|
||||
override fun toString(): String {
|
||||
override fun stringify(): String {
|
||||
if (a > 0 && b > 0) {
|
||||
if (a == 1) {
|
||||
return "(n+$b)"
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class PropertySegment(
|
||||
/**
|
||||
* 此属性选择器是否被 @ 标记
|
||||
*/
|
||||
val tracked: Boolean,
|
||||
val at: Boolean,
|
||||
val name: String,
|
||||
val expressions: List<Expression>,
|
||||
) {
|
||||
) : Stringify {
|
||||
private val matchAnyName = name.isBlank() || name == "*"
|
||||
|
||||
val binaryExpressions
|
||||
get() = expressions.flatMap { it.binaryExpressions.toList() }.toTypedArray()
|
||||
|
||||
override fun toString(): String {
|
||||
val matchTag = if (tracked) "@" else ""
|
||||
override fun stringify(): String {
|
||||
val matchTag = if (at) "@" else ""
|
||||
return matchTag + name + expressions.joinToString("") { "[${it.stringify()}]" }
|
||||
}
|
||||
|
||||
|
@ -30,8 +29,16 @@ data class PropertySegment(
|
|||
return false
|
||||
}
|
||||
|
||||
fun <T> match(node: T, transform: Transform<T>): Boolean {
|
||||
return matchName(node, transform) && expressions.all { ex -> ex.match(node, transform) }
|
||||
fun <T> match(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
): Boolean {
|
||||
return matchName(context.current, transform) && expressions.all { ex ->
|
||||
ex.match(
|
||||
context,
|
||||
transform
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,33 +1,40 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class PropertyWrapper(
|
||||
val segment: PropertySegment,
|
||||
val to: ConnectWrapper? = null,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
):Stringify {
|
||||
override fun stringify(): String {
|
||||
return (if (to != null) {
|
||||
to.toString() + "\u0020"
|
||||
to.stringify() + "\u0020"
|
||||
} else {
|
||||
""
|
||||
}) + segment.toString()
|
||||
}) + segment.stringify()
|
||||
}
|
||||
|
||||
fun <T> matchTracks(
|
||||
node: T,
|
||||
fun <T> matchContext(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
trackNodes: MutableList<T>,
|
||||
): List<T>? {
|
||||
if (!segment.match(node, transform)) {
|
||||
): Context<T>? {
|
||||
if (!segment.match(context, transform)) {
|
||||
return null
|
||||
}
|
||||
trackNodes.add(node)
|
||||
if (to == null) {
|
||||
return trackNodes
|
||||
return context
|
||||
}
|
||||
val r = to.matchTracks(node, transform, trackNodes)
|
||||
if (r == null) {
|
||||
trackNodes.removeLast()
|
||||
}
|
||||
return r
|
||||
return to.matchContext(context, transform)
|
||||
}
|
||||
|
||||
|
||||
val isMatchRoot = segment.expressions.any { e ->
|
||||
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 length: Int
|
||||
get() = if (to == null) 1 else to.to.length + 1
|
||||
}
|
||||
|
|
|
@ -8,8 +8,7 @@ data class QuickFindValue(
|
|||
val vid: String?,
|
||||
val text: String?,
|
||||
) {
|
||||
val canQf: Boolean
|
||||
get() = id != null || vid != null || text != null
|
||||
val canQf = id != null || vid != null || text != null
|
||||
}
|
||||
|
||||
internal fun getQuickFindValue(segment: PropertySegment): QuickFindValue {
|
||||
|
|
|
@ -1,50 +1,49 @@
|
|||
package li.songe.selector
|
||||
|
||||
import li.songe.selector.parser.selectorParser
|
||||
import kotlin.js.JsExport
|
||||
|
||||
class Selector internal constructor(
|
||||
@JsExport
|
||||
class Selector(
|
||||
val source: String,
|
||||
private val propertyWrapper: PropertyWrapper
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return propertyWrapper.toString()
|
||||
val propertyWrapper: PropertyWrapper
|
||||
) : Stringify {
|
||||
override fun stringify(): String {
|
||||
return propertyWrapper.stringify()
|
||||
}
|
||||
|
||||
val tracks = run {
|
||||
val list = mutableListOf(propertyWrapper)
|
||||
while (true) {
|
||||
list.add(list.last().to?.to ?: break)
|
||||
val targetIndex = run {
|
||||
val length = propertyWrapper.length
|
||||
var index = 0
|
||||
var c: PropertyWrapper? = propertyWrapper
|
||||
while (c != null) {
|
||||
if (c.segment.at) {
|
||||
return@run length - 1 - index
|
||||
}
|
||||
c = c.to?.to
|
||||
index++
|
||||
}
|
||||
list.map { p -> p.segment.tracked }.toTypedArray<Boolean>()
|
||||
length - 1
|
||||
}
|
||||
|
||||
val trackIndex = tracks.indexOfFirst { it }.let { i ->
|
||||
if (i < 0) 0 else i
|
||||
fun <T> matchContext(
|
||||
node: T,
|
||||
transform: Transform<T>,
|
||||
): Context<T>? {
|
||||
return propertyWrapper.matchContext(Context(node), transform)
|
||||
}
|
||||
|
||||
fun <T> match(
|
||||
node: T,
|
||||
transform: Transform<T>,
|
||||
trackNodes: MutableList<T> = ArrayList(tracks.size),
|
||||
): T? {
|
||||
val trackTempNodes = matchTracks(node, transform, trackNodes) ?: return null
|
||||
return trackTempNodes[trackIndex]
|
||||
val ctx = matchContext(node, transform) ?: return null
|
||||
return ctx.get(targetIndex).current
|
||||
}
|
||||
|
||||
fun <T> matchTracks(
|
||||
node: T,
|
||||
transform: Transform<T>,
|
||||
trackNodes: MutableList<T> = ArrayList(tracks.size),
|
||||
): List<T>? {
|
||||
return propertyWrapper.matchTracks(node, transform, trackNodes)
|
||||
}
|
||||
val quickFindValue = propertyWrapper.quickFindValue
|
||||
|
||||
val quickFindValue = getQuickFindValue(propertyWrapper.segment)
|
||||
|
||||
// 主动查询根节点
|
||||
val isMatchRoot = propertyWrapper.segment.expressions.any { e ->
|
||||
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 isMatchRoot = propertyWrapper.isMatchRoot
|
||||
|
||||
val connectKeys = run {
|
||||
var c = propertyWrapper.to
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
package li.songe.selector
|
||||
|
||||
@Suppress("UNUSED")
|
||||
class Transform<T>(
|
||||
val getAttr: (Any?, String) -> Any?,
|
||||
val getInvoke: (Any?, String, List<Any>) -> Any? = { _, _, _ -> null },
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
class Transform<T> @JsExport.Ignore constructor(
|
||||
val getAttr: (Any, String) -> Any?,
|
||||
val getInvoke: (Any, String, List<Any>) -> Any? = { _, _, _ -> null },
|
||||
val getName: (T) -> CharSequence?,
|
||||
val getChildren: (T) -> Sequence<T>,
|
||||
val getParent: (T) -> T?,
|
||||
|
||||
val getRoot: (T) -> T? = { node ->
|
||||
var parentVar: T? = getParent(node)
|
||||
while (parentVar != null) {
|
||||
parentVar = getParent(parentVar!!)
|
||||
}
|
||||
parentVar
|
||||
},
|
||||
val getDescendants: (T) -> Sequence<T> = { node ->
|
||||
sequence { // 深度优先 先序遍历
|
||||
// https://developer.mozilla.org/zh-CN/docs/Web/API/Document/querySelector
|
||||
|
@ -16,7 +25,7 @@ class Transform<T>(
|
|||
stack.reverse()
|
||||
val tempNodes = mutableListOf<T>()
|
||||
do {
|
||||
val top = stack.removeLast()
|
||||
val top = stack.removeAt(stack.lastIndex)
|
||||
yield(top)
|
||||
for (childNode in getChildren(top)) {
|
||||
// 可针对 sequence 优化
|
||||
|
@ -32,22 +41,20 @@ class Transform<T>(
|
|||
}
|
||||
},
|
||||
|
||||
val getChildrenX: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
getChildren(node)
|
||||
.let {
|
||||
if (connectExpression.maxOffset != null) {
|
||||
it.take(connectExpression.maxOffset!! + 1)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
.filterIndexed { i, _ ->
|
||||
connectExpression.checkOffset(
|
||||
i
|
||||
)
|
||||
val traverseChildren: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
getChildren(node).let {
|
||||
if (connectExpression.maxOffset != null) {
|
||||
it.take(connectExpression.maxOffset!! + 1)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.filterIndexed { i, _ ->
|
||||
connectExpression.checkOffset(
|
||||
i
|
||||
)
|
||||
}
|
||||
},
|
||||
val getAncestors: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
val traverseAncestors: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
sequence {
|
||||
var parentVar: T? = getParent(node) ?: return@sequence
|
||||
var offset = 0
|
||||
|
@ -67,7 +74,7 @@ class Transform<T>(
|
|||
}
|
||||
}
|
||||
},
|
||||
val getBeforeBrothers: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
val traverseBeforeBrothers: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
val parentVal = getParent(node)
|
||||
if (parentVal != null) {
|
||||
val list = getChildren(parentVal).takeWhile { it != node }.toMutableList()
|
||||
|
@ -81,28 +88,25 @@ class Transform<T>(
|
|||
emptySequence()
|
||||
}
|
||||
},
|
||||
val getAfterBrothers: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
val traverseAfterBrothers: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
val parentVal = getParent(node)
|
||||
if (parentVal != null) {
|
||||
getChildren(parentVal).dropWhile { it != node }
|
||||
.drop(1)
|
||||
.let {
|
||||
if (connectExpression.maxOffset != null) {
|
||||
it.take(connectExpression.maxOffset!! + 1)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.filterIndexed { i, _ ->
|
||||
connectExpression.checkOffset(
|
||||
i
|
||||
)
|
||||
getChildren(parentVal).dropWhile { it != node }.drop(1).let {
|
||||
if (connectExpression.maxOffset != null) {
|
||||
it.take(connectExpression.maxOffset!! + 1)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.filterIndexed { i, _ ->
|
||||
connectExpression.checkOffset(
|
||||
i
|
||||
)
|
||||
}
|
||||
} else {
|
||||
emptySequence()
|
||||
}
|
||||
},
|
||||
|
||||
val getDescendantsX: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
val traverseDescendants: (T, ConnectExpression) -> Sequence<T> = { node, connectExpression ->
|
||||
sequence {
|
||||
val stack = getChildren(node).toMutableList()
|
||||
if (stack.isEmpty()) return@sequence
|
||||
|
@ -110,7 +114,7 @@ class Transform<T>(
|
|||
val tempNodes = mutableListOf<T>()
|
||||
var offset = 0
|
||||
do {
|
||||
val top = stack.removeLast()
|
||||
val top = stack.removeAt(stack.lastIndex)
|
||||
if (connectExpression.checkOffset(offset)) {
|
||||
yield(top)
|
||||
}
|
||||
|
@ -132,40 +136,60 @@ class Transform<T>(
|
|||
} while (stack.isNotEmpty())
|
||||
}
|
||||
},
|
||||
|
||||
) {
|
||||
) {
|
||||
@JsExport.Ignore
|
||||
val querySelectorAll: (T, Selector) -> Sequence<T> = { node, selector ->
|
||||
sequence {
|
||||
// cache trackNodes
|
||||
val trackNodes = ArrayList<T>(selector.tracks.size)
|
||||
val r0 = selector.match(node, this@Transform, trackNodes)
|
||||
if (r0 != null) yield(r0)
|
||||
selector.match(node, this@Transform)?.let { yield(it) }
|
||||
getDescendants(node).forEach { childNode ->
|
||||
trackNodes.clear()
|
||||
val r = selector.match(childNode, this@Transform, trackNodes)
|
||||
if (r != null) yield(r)
|
||||
selector.match(childNode, this@Transform)?.let { yield(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
val querySelector: (T, Selector) -> T? = { node, selector ->
|
||||
querySelectorAll(
|
||||
node, selector
|
||||
).firstOrNull()
|
||||
querySelectorAll(node, selector).firstOrNull()
|
||||
}
|
||||
val querySelectorTrackAll: (T, Selector) -> Sequence<List<T>> = { node, selector ->
|
||||
|
||||
@JsExport.Ignore
|
||||
val querySelectorAllContext: (T, Selector) -> Sequence<Context<T>> = { node, selector ->
|
||||
sequence {
|
||||
val r0 = selector.matchTracks(node, this@Transform)
|
||||
if (r0 != null) yield(r0)
|
||||
selector.matchContext(node, this@Transform)?.let { yield(it) }
|
||||
getDescendants(node).forEach { childNode ->
|
||||
val r = selector.matchTracks(childNode, this@Transform)
|
||||
if (r != null) yield(r)
|
||||
selector.matchContext(childNode, this@Transform)?.let { yield(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val querySelectorTrack: (T, Selector) -> List<T>? = { node, selector ->
|
||||
querySelectorTrackAll(
|
||||
node, selector
|
||||
).firstOrNull()
|
||||
val querySelectorContext: (T, Selector) -> Context<T>? = { node, selector ->
|
||||
querySelectorAllContext(node, selector).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>
|
||||
}
|
||||
|
||||
val querySelectorAllContextArray: (T, Selector) -> Array<Context<T>> = { node, selector ->
|
||||
val result = querySelectorAllContext(node, selector)
|
||||
result.toList().toTypedArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T> multiplatformBuild(
|
||||
getAttr: (Any, String) -> Any?,
|
||||
getInvoke: (Any, String, List<Any>) -> Any?,
|
||||
getName: (T) -> String?,
|
||||
getChildren: (T) -> Array<T>,
|
||||
getParent: (T) -> T?,
|
||||
): Transform<T> {
|
||||
return Transform(
|
||||
getAttr = getAttr,
|
||||
getInvoke = { target, name, args -> getInvoke(target, name, args) },
|
||||
getName = getName,
|
||||
getChildren = { node -> getChildren(node).asSequence() },
|
||||
getParent = getParent,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
@JsExport
|
||||
data class TupleExpression(
|
||||
val numbers: List<Int>,
|
||||
) : ConnectExpression() {
|
||||
|
@ -15,7 +18,7 @@ data class TupleExpression(
|
|||
return numbers[i]
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
override fun stringify(): String {
|
||||
if (numbers.size == 1) {
|
||||
return if (numbers.first() == 1) {
|
||||
""
|
||||
|
|
|
@ -5,7 +5,11 @@ import kotlin.js.JsExport
|
|||
@JsExport
|
||||
sealed class ValueExpression(open val value: Any?, open val type: String) : Position {
|
||||
override fun stringify() = value.toString()
|
||||
internal abstract fun <T> getAttr(node: T, transform: Transform<T>): Any?
|
||||
internal abstract fun <T> getAttr(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
): Any?
|
||||
|
||||
abstract val properties: Array<String>
|
||||
abstract val methods: Array<String>
|
||||
|
||||
|
@ -18,8 +22,8 @@ sealed class ValueExpression(open val value: Any?, open val type: String) : Posi
|
|||
override val value: String,
|
||||
) : Variable(value) {
|
||||
override val end = start + value.length
|
||||
override fun <T> getAttr(node: T, transform: Transform<T>): Any? {
|
||||
return transform.getAttr(node, value)
|
||||
override fun <T> getAttr(context: Context<T>, transform: Transform<T>): Any? {
|
||||
return transform.getAttr(context, value)
|
||||
}
|
||||
|
||||
override val properties: Array<String>
|
||||
|
@ -34,8 +38,14 @@ sealed class ValueExpression(open val value: Any?, open val type: String) : Posi
|
|||
val object0: Variable,
|
||||
val property: String,
|
||||
) : Variable(value = "${object0.stringify()}.$property") {
|
||||
override fun <T> getAttr(node: T, transform: Transform<T>): Any? {
|
||||
return transform.getAttr(object0.getAttr(node, transform), property)
|
||||
override fun <T> getAttr(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
): Any? {
|
||||
return transform.getAttr(
|
||||
object0.getAttr(context, transform).whenNull { return null },
|
||||
property
|
||||
)
|
||||
}
|
||||
|
||||
override val properties: Array<String>
|
||||
|
@ -53,7 +63,10 @@ sealed class ValueExpression(open val value: Any?, open val type: String) : Posi
|
|||
value = "${callee.stringify()}(${arguments.joinToString(",") { it.stringify() }})",
|
||||
) {
|
||||
|
||||
override fun <T> getAttr(node: T, transform: Transform<T>): Any? {
|
||||
override fun <T> getAttr(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
): Any? {
|
||||
return when (callee) {
|
||||
is CallExpression -> {
|
||||
// not support
|
||||
|
@ -62,17 +75,21 @@ sealed class ValueExpression(open val value: Any?, open val type: String) : Posi
|
|||
|
||||
is Identifier -> {
|
||||
transform.getInvoke(
|
||||
node,
|
||||
context,
|
||||
callee.value,
|
||||
arguments.map { it.getAttr(node, transform).whenNull { return null } }
|
||||
arguments.map {
|
||||
it.getAttr(context, transform).whenNull { return null }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
is MemberExpression -> {
|
||||
transform.getInvoke(
|
||||
callee.object0.getAttr(node, transform).whenNull { return null },
|
||||
callee.object0.getAttr(context, transform).whenNull { return null },
|
||||
callee.property,
|
||||
arguments.map { it.getAttr(node, transform).whenNull { return null } }
|
||||
arguments.map {
|
||||
it.getAttr(context, transform).whenNull { return null }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +112,10 @@ sealed class ValueExpression(open val value: Any?, open val type: String) : Posi
|
|||
override val value: Any?,
|
||||
override val type: String,
|
||||
) : ValueExpression(value, type) {
|
||||
override fun <T> getAttr(node: T, transform: Transform<T>) = value
|
||||
override fun <T> getAttr(
|
||||
context: Context<T>,
|
||||
transform: Transform<T>,
|
||||
) = value
|
||||
|
||||
override val properties: Array<String>
|
||||
get() = emptyArray()
|
||||
|
|
|
@ -2,6 +2,7 @@ package li.songe.selector
|
|||
|
||||
import kotlin.js.JsExport
|
||||
|
||||
|
||||
expect fun String.toMatches(): (input: CharSequence) -> Boolean
|
||||
|
||||
expect fun setWasmToMatches(wasmToMatches: (String) -> (String) -> Boolean)
|
||||
|
|
|
@ -148,8 +148,14 @@ fun initDefaultTypeInfo(): DefaultTypeInfo {
|
|||
nodeType.methods = arrayOf(
|
||||
MethodInfo("getChild", nodeType, arrayOf(intType)),
|
||||
)
|
||||
contextType.methods = arrayOf(*nodeType.methods)
|
||||
contextType.props = arrayOf(*nodeType.props)
|
||||
contextType.methods = arrayOf(
|
||||
*nodeType.methods,
|
||||
MethodInfo("getPrev", contextType, arrayOf(intType))
|
||||
)
|
||||
contextType.props = arrayOf(
|
||||
*nodeType.props,
|
||||
PropInfo("prev", contextType),
|
||||
)
|
||||
return DefaultTypeInfo(
|
||||
booleanType = booleanType,
|
||||
intType = intType,
|
||||
|
@ -160,7 +166,7 @@ fun initDefaultTypeInfo(): DefaultTypeInfo {
|
|||
}
|
||||
|
||||
@JsExport
|
||||
fun getIntInvoke(target: Int, name: String, args: List<Any?>): Any? {
|
||||
fun getIntInvoke(target: Int, name: String, args: List<Any>): Any? {
|
||||
return when (name) {
|
||||
"plus" -> {
|
||||
target + (args.getIntOrNull() ?: return null)
|
||||
|
@ -187,26 +193,26 @@ fun getIntInvoke(target: Int, name: String, args: List<Any?>): Any? {
|
|||
}
|
||||
|
||||
|
||||
internal fun List<Any?>.getIntOrNull(i: Int = 0): Int? {
|
||||
internal fun List<Any>.getIntOrNull(i: Int = 0): Int? {
|
||||
val v = getOrNull(i)
|
||||
if (v is Int) return v
|
||||
return null
|
||||
}
|
||||
|
||||
@JsExport
|
||||
fun getStringInvoke(target: String, name: String, args: List<Any?>): Any? {
|
||||
fun getStringInvoke(target: String, name: String, args: List<Any>): Any? {
|
||||
return getCharSequenceInvoke(target, name, args)
|
||||
}
|
||||
|
||||
@JsExport
|
||||
fun getBooleanInvoke(target: Boolean, name: String, args: List<Any?>): Any? {
|
||||
fun getBooleanInvoke(target: Boolean, name: String, args: List<Any>): Any? {
|
||||
return when (name) {
|
||||
"toInt" -> if (target) 1 else 0
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun getCharSequenceInvoke(target: CharSequence, name: String, args: List<Any?>): Any? {
|
||||
fun getCharSequenceInvoke(target: CharSequence, name: String, args: List<Any>): Any? {
|
||||
return when (name) {
|
||||
"get" -> {
|
||||
target.getOrNull(args.getIntOrNull() ?: return null).toString()
|
||||
|
|
|
@ -34,7 +34,7 @@ class ParserTest {
|
|||
return value.intOrNull ?: value.booleanOrNull ?: value.content
|
||||
}
|
||||
|
||||
private fun getNodeInvoke(target: TestNode, name: String, args: List<Any?>): Any? {
|
||||
private fun getNodeInvoke(target: TestNode, name: String, args: List<Any>): Any? {
|
||||
when (name) {
|
||||
"getChild" -> {
|
||||
val arg = (args.getIntOrNull() ?: return null)
|
||||
|
@ -47,6 +47,11 @@ class ParserTest {
|
|||
private val transform = Transform<TestNode>(
|
||||
getAttr = { target, name ->
|
||||
when (target) {
|
||||
is Context<*> -> when (name) {
|
||||
"prev" -> target.prev
|
||||
else -> getNodeAttr(target.current as TestNode, name)
|
||||
}
|
||||
|
||||
is TestNode -> getNodeAttr(target, name)
|
||||
is String -> getCharSequenceAttr(target, name)
|
||||
|
||||
|
@ -59,6 +64,14 @@ class ParserTest {
|
|||
is Int -> getIntInvoke(target, name, args)
|
||||
is CharSequence -> getCharSequenceInvoke(target, name, args)
|
||||
is TestNode -> getNodeInvoke(target, name, args)
|
||||
is Context<*> -> when (name) {
|
||||
"getPrev" -> {
|
||||
args.getIntOrNull()?.let { target.getPrev(it) }
|
||||
}
|
||||
|
||||
else -> getNodeInvoke(target.current as TestNode, name, args)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
|
@ -121,7 +134,7 @@ class ParserTest {
|
|||
val text =
|
||||
"ImageView < @FrameLayout < LinearLayout < RelativeLayout <n LinearLayout < RelativeLayout + LinearLayout > RelativeLayout > TextView[text\$='广告']"
|
||||
val selector = Selector.parse(text)
|
||||
println("trackIndex: " + selector.trackIndex)
|
||||
println("trackIndex: " + selector.targetIndex)
|
||||
println("canCacheIndex: " + Selector.parse("A + B").useCache)
|
||||
println("canCacheIndex: " + Selector.parse("A > B - C").useCache)
|
||||
}
|
||||
|
@ -139,12 +152,10 @@ class ParserTest {
|
|||
assertTrue(targets.size == 1)
|
||||
println("id: " + targets.first().id)
|
||||
|
||||
val trackTargets = transform.querySelectorTrackAll(node, selector).toList()
|
||||
val trackTargets = transform.querySelectorAllContext(node, selector).toList()
|
||||
println("trackTargets_size: " + trackTargets.size)
|
||||
assertTrue(trackTargets.size == 1)
|
||||
println(trackTargets.first().mapIndexed { index, testNode ->
|
||||
testNode.id to selector.tracks[index]
|
||||
})
|
||||
println(trackTargets.first())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -157,11 +168,10 @@ class ParserTest {
|
|||
|
||||
@Test
|
||||
fun check_query() {
|
||||
val text = "@TextView - [text=\"签到提醒\"] <<n [vid=\"webViewContainer\"]"
|
||||
val text = "@TextView[getPrev(0).text=`签到提醒`] - [text=`签到提醒`] <<n [vid=`webViewContainer`]"
|
||||
val selector = Selector.parse(text)
|
||||
println("selector: $selector")
|
||||
println(selector.trackIndex)
|
||||
println(selector.tracks.toList())
|
||||
println(selector.targetIndex)
|
||||
|
||||
val node = getOrDownloadNode("https://i.gkd.li/i/14384152")
|
||||
val targets = transform.querySelectorAll(node, selector).toList()
|
||||
|
@ -249,7 +259,7 @@ class ParserTest {
|
|||
@Test
|
||||
fun check_type() {
|
||||
val source =
|
||||
"[visibleToUser=true][((parent.getChild(0,).getChild( (0), )=null) && (((`` >= 1)))) || (name=null && desc=null)]"
|
||||
"[prev!=null&&visibleToUser=true][((parent.getChild(0,).getChild( (0), )=null) && (((2 >= 1)))) || (name=null && desc=null)]"
|
||||
val selector = Selector.parse(source)
|
||||
val typeInfo = initDefaultTypeInfo().contextType
|
||||
val error = selector.checkType(typeInfo)
|
||||
|
|
Loading…
Reference in New Issue
Block a user