mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 11:42:22 +08:00
feat: remove android
This commit is contained in:
parent
b9cef851d7
commit
2778bc2cce
1
selector_android/.gitignore
vendored
1
selector_android/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
/build
|
|
@ -1,41 +0,0 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "li.songe.selector_android"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
buildToolsVersion = libs.versions.android.buildToolsVersion.get()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
testOptions {
|
||||
unitTests.isReturnDefaultValues = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso)
|
||||
testImplementation(libs.org.json)
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package li.songe.selector_android
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("li.songe.node_selector.test", appContext.packageName)
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="li.songe.selector_android">
|
||||
|
||||
</manifest>
|
|
@ -1,133 +0,0 @@
|
|||
package li.songe.selector_android
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import java.util.ArrayDeque
|
||||
|
||||
inline fun AccessibilityNodeInfo.forEach(action: (childNode: AccessibilityNodeInfo) -> Unit) {
|
||||
var index = 0
|
||||
while (index < childCount) {
|
||||
val child: AccessibilityNodeInfo? = getChild(index)
|
||||
if (child != null) {
|
||||
action(child)
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
inline fun AccessibilityNodeInfo.forEachIndexed(action: (index: Int, childNode: AccessibilityNodeInfo) -> Unit) {
|
||||
var index = 0
|
||||
while (index < childCount) {
|
||||
val child: AccessibilityNodeInfo? = getChild(index)
|
||||
if (child != null) {
|
||||
action(index, child)
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
inline fun AccessibilityNodeInfo?.forEachAncestorIndexed(action: (depth: Int, ancestorNode: AccessibilityNodeInfo) -> Unit) {
|
||||
var p = this
|
||||
var depth = 0
|
||||
while (true) {
|
||||
val p2 = p?.parent
|
||||
if (p2 != null) {
|
||||
p = p2
|
||||
depth++
|
||||
action(depth, p2)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun AccessibilityNodeInfo.forEachElderBrotherIndexed(action: (offset: Int, brotherNode: AccessibilityNodeInfo) -> Unit) {
|
||||
val parentNode = parent ?: return
|
||||
val index = getIndex(parentNode) ?: return
|
||||
if (index == 0) {
|
||||
return
|
||||
}
|
||||
repeat(index) {
|
||||
val brother: AccessibilityNodeInfo? = parentNode.getChild(index - it - 1)
|
||||
if (brother != null) {
|
||||
action(it + 1, brother)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun AccessibilityNodeInfo.forEachYoungerBrotherIndexed(action: (offset: Int, brotherNode: AccessibilityNodeInfo) -> Unit) {
|
||||
val parentNode = parent ?: return
|
||||
val index = getIndex(parentNode) ?: return
|
||||
if (index == parentNode.childCount - 1) {
|
||||
return
|
||||
}
|
||||
repeat(parentNode.childCount - index - 1) {
|
||||
val brother: AccessibilityNodeInfo? = parentNode.getChild(index + it + 1)
|
||||
if (brother != null) {
|
||||
action(it + 1, brother)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun AccessibilityNodeInfo.traverse() = sequence {
|
||||
val stack = ArrayDeque<AccessibilityNodeInfo>()
|
||||
stack.push(this@traverse)
|
||||
while (stack.isNotEmpty()) {
|
||||
val top = stack.pop()
|
||||
yield(top)
|
||||
top.forEach { childNode ->
|
||||
stack.push(childNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun AccessibilityNodeInfo.getIndex(parentNodeInfo: AccessibilityNodeInfo? = null): Int? {
|
||||
(parentNodeInfo ?: parent)?.forEachIndexed { index, accessibilityNodeInfo ->
|
||||
if (accessibilityNodeInfo == this) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun AccessibilityNodeInfo.getDepth(): Int {
|
||||
var p: AccessibilityNodeInfo? = this
|
||||
var depth = 0
|
||||
while (true) {
|
||||
val p2 = p?.parent
|
||||
if (p2 != null) {
|
||||
p = p2
|
||||
depth++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return depth
|
||||
}
|
||||
|
||||
fun AccessibilityNodeInfo.getAncestor(dep: Int): AccessibilityNodeInfo? {
|
||||
forEachAncestorIndexed { depth, ancestorNode ->
|
||||
if (depth == dep) {
|
||||
return ancestorNode
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun AccessibilityNodeInfo.getBrother(dep: Int, elder: Boolean = true): AccessibilityNodeInfo? {
|
||||
if (elder) {
|
||||
forEachElderBrotherIndexed { offset, brotherNode ->
|
||||
if (offset == dep) {
|
||||
return brotherNode
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forEachYoungerBrotherIndexed { offset, brotherNode ->
|
||||
if (offset == dep) {
|
||||
return brotherNode
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package li.songe.selector_android.expression
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.operator.Operator
|
||||
|
||||
data class BinaryExpression(val name: String, val operator: Operator, val value: Any?) {
|
||||
|
||||
override fun toString() = "[${name}${operator}${
|
||||
if (value is String) {
|
||||
"`${value.replace("`", "\\`")}`"
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}]"
|
||||
|
||||
val matchNodeInfo: (nodeInfo: AccessibilityNodeInfo) -> Boolean = operator.match(this)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
|
||||
|
||||
object End : Operator("$=") {
|
||||
override fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean {
|
||||
val value = expression.value
|
||||
return when (expression.name) {
|
||||
"id" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.viewIdResourceName?.endsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"text" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.text?.endsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"hint" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.hintText?.endsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"desc" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.contentDescription?.endsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"className" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.className?.endsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
else -> ({ false })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
import li.songe.selector_android.getDepth
|
||||
import li.songe.selector_android.getIndex
|
||||
|
||||
object Equal : Operator("=") {
|
||||
override fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean {
|
||||
val value = expression.value
|
||||
return when (expression.name) {
|
||||
"id" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.viewIdResourceName?.toString() == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"index" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.getIndex() == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"childCount" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.childCount == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"depth" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.getDepth() == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"text" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.text?.toString() == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"text_length" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.text?.length == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"hint" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.hintText?.toString() == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"hint_length" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.hintText?.length == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"desc" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.contentDescription?.toString() == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"desc_length" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.contentDescription?.length == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"isPassword" -> when (value) {
|
||||
is Boolean? -> ({ nodeInfo -> nodeInfo.isPassword == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"isChecked" -> when (value) {
|
||||
is Boolean? -> ({ nodeInfo -> nodeInfo.isChecked == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"className" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.className?.toString() == value })
|
||||
else -> ({ false })
|
||||
}
|
||||
else -> ({ false })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
|
||||
|
||||
object Include : Operator("*=") {
|
||||
override fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean {
|
||||
val value = expression.value
|
||||
return when (expression.name) {
|
||||
"id" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.viewIdResourceName?.contains(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"text" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.text?.contains(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"hint" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.hintText?.contains(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"desc" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.contentDescription?.contains(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"className" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.className?.contains(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
else -> ({ false })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
import li.songe.selector_android.getDepth
|
||||
import li.songe.selector_android.getIndex
|
||||
|
||||
object Less : Operator("<") {
|
||||
override fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean {
|
||||
val value = expression.value
|
||||
return when (expression.name) {
|
||||
"index" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.getIndex()?.let { it < value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"childCount" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.childCount < value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"depth" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.getDepth() < value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"text_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.text?.length?.let { it < value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"hint_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.hintText?.length?.let { it < value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"desc_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.contentDescription?.length?.let { it < value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"className_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.className?.length?.let { it < value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
else -> ({ false })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
import li.songe.selector_android.getDepth
|
||||
import li.songe.selector_android.getIndex
|
||||
|
||||
|
||||
object LessEqual : Operator("<=") {
|
||||
override fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean {
|
||||
val value = expression.value
|
||||
return when (expression.name) {
|
||||
"index" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.getIndex()?.let { it <= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"childCount" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.childCount <= value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"depth" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.getDepth() <= value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"text_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.text?.length?.let { it <= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"hint_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.hintText?.length?.let { it <= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"desc_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.contentDescription?.length?.let { it <= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"className_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.className?.length?.let { it <= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
else -> ({ false })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
import li.songe.selector_android.getDepth
|
||||
import li.songe.selector_android.getIndex
|
||||
|
||||
object More : Operator(">") {
|
||||
override fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean {
|
||||
val value = expression.value
|
||||
return when (expression.name) {
|
||||
"index" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.getIndex()?.let { it > value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"childCount" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.childCount > value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"depth" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.getDepth() > value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"text_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.text?.length?.let { it > value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"hint_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.hintText?.length?.let { it > value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"desc_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.contentDescription?.length?.let { it > value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"className_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.className?.length?.let { it > value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
else -> ({ false })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
import li.songe.selector_android.getDepth
|
||||
import li.songe.selector_android.getIndex
|
||||
|
||||
|
||||
object MoreEqual : Operator(">=") {
|
||||
override fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean {
|
||||
val value = expression.value
|
||||
return when (expression.name) {
|
||||
"index" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.getIndex()?.let { it >= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
"childCount" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.childCount >= value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"depth" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.getDepth() >= value })
|
||||
else -> ({ false })
|
||||
}
|
||||
"text_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.text?.length?.let { it >= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
"hint_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.hintText?.length?.let { it >= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
"desc_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.contentDescription?.length?.let { it >= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
"className_length" -> when (value) {
|
||||
is Int -> ({ nodeInfo -> nodeInfo.className?.length?.let { it >= value } == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
else -> ({ false })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
import li.songe.selector_android.getDepth
|
||||
import li.songe.selector_android.getIndex
|
||||
|
||||
|
||||
object NotEqual : Operator("!=") {
|
||||
override fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean {
|
||||
val value = expression.value
|
||||
return when (expression.name) {
|
||||
"id" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.viewIdResourceName?.toString() != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"index" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.getIndex() != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"childCount" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.childCount != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"depth" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.getDepth() != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"text" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.text?.toString() != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"text_length" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.text?.length != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"hint" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.hintText?.toString() != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"hint_length" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.hintText?.length != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"desc" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.contentDescription?.toString() != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"desc_length" -> when (value) {
|
||||
is Int? -> ({ nodeInfo -> nodeInfo.contentDescription?.length != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"isPassword" -> when (value) {
|
||||
is Boolean? -> ({ nodeInfo -> nodeInfo.isPassword != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"isChecked" -> when (value) {
|
||||
is Boolean? -> ({ nodeInfo -> nodeInfo.isChecked != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"className" -> when (value) {
|
||||
is String? -> ({ nodeInfo -> nodeInfo.className?.toString() != value })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
else -> ({ false })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
|
||||
sealed class Operator(private val key: String) {
|
||||
override fun toString() = key
|
||||
abstract fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package li.songe.selector_android.operator
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
|
||||
|
||||
object Start : Operator("^=") {
|
||||
override fun match(expression: BinaryExpression): (nodeInfo: AccessibilityNodeInfo) -> Boolean {
|
||||
val value = expression.value
|
||||
return when (expression.name) {
|
||||
"id" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.viewIdResourceName?.startsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"text" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.text?.startsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"hint" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.hintText?.startsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"desc" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.contentDescription?.startsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
"className" -> when (value) {
|
||||
is String -> ({ nodeInfo -> nodeInfo.className?.startsWith(value) == true })
|
||||
else -> ({ false })
|
||||
}
|
||||
|
||||
else -> ({ false })
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package li.songe.selector_android.parser
|
||||
|
||||
open class GkdParser<T>(
|
||||
val prefix: String = "",
|
||||
private val invokeTemp: (source: String, offset: Int, prefix: String) -> GkdParserResult<T>
|
||||
) : (String, Int) -> GkdParserResult<T> {
|
||||
override fun invoke(source: String, offset: Int) = invokeTemp(source, offset, prefix)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package li.songe.selector_android.parser
|
||||
|
||||
data class GkdParserResult<T>(val data: T, val length: Int = 0)
|
|
@ -1,22 +0,0 @@
|
|||
package li.songe.selector_android.parser
|
||||
|
||||
data class GkdSyntaxError(val expectedValue: String, val position: Int, val source: String) :
|
||||
Exception(
|
||||
"expected $expectedValue in selector at position $position, but got ${
|
||||
source.getOrNull(
|
||||
position
|
||||
)
|
||||
}"
|
||||
) {
|
||||
companion object {
|
||||
fun assert(source: String, offset: Int, value: String = "", expectedValue: String? = null) {
|
||||
if (offset >= source.length || (value.isNotEmpty() && !value.contains(source[offset]))) {
|
||||
throw GkdSyntaxError(expectedValue ?: value, offset, source)
|
||||
}
|
||||
}
|
||||
|
||||
fun throwError(source: String, offset: Int, expectedValue: String = ""):Nothing {
|
||||
throw GkdSyntaxError(expectedValue, offset, source)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,497 +0,0 @@
|
|||
package li.songe.selector_android.parser
|
||||
|
||||
import li.songe.selector_android.GkdSelector
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
import li.songe.selector_android.operator.End
|
||||
import li.songe.selector_android.operator.Equal
|
||||
import li.songe.selector_android.operator.Include
|
||||
import li.songe.selector_android.operator.Less
|
||||
import li.songe.selector_android.operator.LessEqual
|
||||
import li.songe.selector_android.operator.More
|
||||
import li.songe.selector_android.operator.MoreEqual
|
||||
import li.songe.selector_android.operator.NotEqual
|
||||
import li.songe.selector_android.operator.Start
|
||||
import li.songe.selector_android.selector.CombinatorSelector
|
||||
import li.songe.selector_android.selector.PropertySelector
|
||||
import li.songe.selector_android.wrapper.CombinatorSelectorWrapper
|
||||
import li.songe.selector_android.wrapper.PropertySelectorWrapper
|
||||
|
||||
internal object Transform {
|
||||
val whiteCharParser = GkdParser("\u0020\t\r\n") { source, offset, prefix ->
|
||||
var i = offset
|
||||
var data = ""
|
||||
while (i < source.length && prefix.contains(source[i])) {
|
||||
data += source[i]
|
||||
i++
|
||||
}
|
||||
GkdParserResult(data, i - offset)
|
||||
}
|
||||
|
||||
val whiteCharStrictParser = GkdParser("\u0020\t\r\n") { source, offset, prefix ->
|
||||
GkdSyntaxError.assert(source, offset, prefix, "whitespace")
|
||||
whiteCharParser(source, offset)
|
||||
}
|
||||
|
||||
val nameParser =
|
||||
GkdParser("*1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_") { source, offset, prefix ->
|
||||
var i = offset
|
||||
val s0 = source.getOrNull(i)
|
||||
if (s0 != null && !prefix.contains(s0)) {
|
||||
return@GkdParser GkdParserResult("", i - offset)
|
||||
}
|
||||
GkdSyntaxError.assert(source, i, prefix, "*0-9a-zA-Z_")
|
||||
var data = source[i].toString()
|
||||
i++
|
||||
if (data == "*") { // 范匹配
|
||||
return@GkdParser GkdParserResult(data, i - offset)
|
||||
}
|
||||
val center = "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_."
|
||||
while (i < source.length) {
|
||||
// . 不能在开头和结尾
|
||||
if (data[i - offset - 1] == '.') {
|
||||
GkdSyntaxError.assert(source, i, prefix, "[0-9a-zA-Z_]")
|
||||
}
|
||||
if (center.contains(source[i])) {
|
||||
data += source[i]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
GkdParserResult(data, i - offset)
|
||||
}
|
||||
|
||||
val combinatorOperatorParser = GkdParser("+-><") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix)
|
||||
return@GkdParser when (source[i]) {
|
||||
'+' -> GkdParserResult(CombinatorSelector.Operator.ElderBrother, ++i - offset)
|
||||
'-' -> GkdParserResult(CombinatorSelector.Operator.YoungerBrother, ++i - offset)
|
||||
'>' -> GkdParserResult(CombinatorSelector.Operator.Ancestor, ++i - offset)
|
||||
'<' -> GkdParserResult(CombinatorSelector.Operator.Child, ++i - offset)
|
||||
else -> GkdSyntaxError.throwError(source, i, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
val integerParser = GkdParser("1234567890") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix, "number")
|
||||
var s = ""
|
||||
while (prefix.contains(source[i]) && i < source.length) {
|
||||
s += source[i]
|
||||
i++
|
||||
}
|
||||
GkdParserResult(s.toInt(), i - offset)
|
||||
}
|
||||
|
||||
// [+-][a][n[^b]]
|
||||
val monomialParser = GkdParser("+-1234567890n") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix)
|
||||
/**
|
||||
* one of 1, -1
|
||||
*/
|
||||
val signal = when (source[i]) {
|
||||
'+' -> {
|
||||
i++
|
||||
1
|
||||
}
|
||||
'-' -> {
|
||||
i++
|
||||
-1
|
||||
}
|
||||
else -> 1
|
||||
}
|
||||
i += whiteCharParser(source, i).length
|
||||
// [a][n[^b]]
|
||||
GkdSyntaxError.assert(source, i, integerParser.prefix + "n")
|
||||
val coefficient =
|
||||
if (integerParser.prefix.contains(source[i])) {
|
||||
val coefficientResult = integerParser(source, i)
|
||||
i += coefficientResult.length
|
||||
coefficientResult.data
|
||||
} else {
|
||||
1
|
||||
} * signal
|
||||
// [n[^b]]
|
||||
if (i < source.length && source[i] == 'n') {
|
||||
i++
|
||||
if (i < source.length && source[i] == '^') {
|
||||
i++
|
||||
val powerResult = integerParser(source, i)
|
||||
i += powerResult.length
|
||||
return@GkdParser GkdParserResult(Pair(powerResult.data, coefficient), i - offset)
|
||||
} else {
|
||||
return@GkdParser GkdParserResult(Pair(1, coefficient), i - offset)
|
||||
}
|
||||
} else {
|
||||
return@GkdParser GkdParserResult(Pair(0, coefficient), i - offset)
|
||||
}
|
||||
}
|
||||
|
||||
// ([+-][a][n[^b]] [+-][a][n[^b]])
|
||||
val expressionParser = GkdParser("(0123456789n") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix)
|
||||
val monomialResultList = mutableListOf<GkdParserResult<Pair<Int, Int>>>()
|
||||
when (source[i]) {
|
||||
'(' -> {
|
||||
i++
|
||||
i += whiteCharParser(source, i).length
|
||||
GkdSyntaxError.assert(source, i, monomialParser.prefix)
|
||||
while (source[i] != ')') {
|
||||
if (monomialResultList.size > 0) {
|
||||
GkdSyntaxError.assert(source, i, "+-")
|
||||
}
|
||||
val monomialResult = monomialParser(source, i)
|
||||
monomialResultList.add(monomialResult)
|
||||
i += monomialResult.length
|
||||
i += whiteCharParser(source, i).length
|
||||
if (i >= source.length) {
|
||||
GkdSyntaxError.assert(source, i, ")")
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
else -> {
|
||||
val monomialResult = monomialParser(source, i)
|
||||
monomialResultList.add(monomialResult)
|
||||
i += monomialResult.length
|
||||
}
|
||||
}
|
||||
val map = mutableMapOf<Int, Int>()
|
||||
monomialResultList.forEach { monomialResult ->
|
||||
val (power, coefficient) = monomialResult.data
|
||||
map[power] = (map[power] ?: 0) + coefficient
|
||||
}
|
||||
GkdParserResult(CombinatorSelector.PolynomialExpression(map.filter { (_, coefficient) ->
|
||||
coefficient != 0
|
||||
}), i - offset)
|
||||
}
|
||||
|
||||
// [+-><](a*n^b)
|
||||
val combinatorParser = GkdParser(combinatorOperatorParser.prefix) { source, offset, _ ->
|
||||
var i = offset
|
||||
val operatorResult = combinatorOperatorParser(source, i)
|
||||
i += operatorResult.length
|
||||
var expressionResult: GkdParserResult<CombinatorSelector.PolynomialExpression>? = null
|
||||
if (i < source.length && expressionParser.prefix.contains(source[i])) {
|
||||
expressionResult = expressionParser(source, i)
|
||||
i += expressionResult.length
|
||||
}
|
||||
GkdParserResult(
|
||||
CombinatorSelector(
|
||||
operatorResult.data,
|
||||
expressionResult?.data ?: CombinatorSelector.PolynomialExpression()
|
||||
), i - offset
|
||||
)
|
||||
}
|
||||
|
||||
val attrOperatorParser = GkdParser("><!*$^=") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix)
|
||||
val attrOperator =
|
||||
when (source[i]) {
|
||||
'=' -> {
|
||||
i++
|
||||
when (source.getOrNull(i)) {
|
||||
'=' -> {
|
||||
i++
|
||||
Equal
|
||||
}
|
||||
else -> {
|
||||
Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
'>' -> {
|
||||
i++
|
||||
when (source.getOrNull(i)) {
|
||||
'=' -> {
|
||||
i++
|
||||
MoreEqual
|
||||
}
|
||||
else -> {
|
||||
More
|
||||
}
|
||||
}
|
||||
}
|
||||
'<' -> {
|
||||
i++
|
||||
when (source.getOrNull(i)) {
|
||||
'=' -> {
|
||||
i++
|
||||
LessEqual
|
||||
}
|
||||
else -> {
|
||||
Less
|
||||
}
|
||||
}
|
||||
}
|
||||
'!' -> {
|
||||
i++
|
||||
GkdSyntaxError.assert(source, i, "=")
|
||||
i++
|
||||
NotEqual
|
||||
}
|
||||
'*' -> {
|
||||
i++
|
||||
GkdSyntaxError.assert(source, i, "=")
|
||||
i++
|
||||
Include
|
||||
}
|
||||
'^' -> {
|
||||
i++
|
||||
GkdSyntaxError.assert(source, i, "=")
|
||||
i++
|
||||
Start
|
||||
}
|
||||
'$' -> {
|
||||
i++
|
||||
GkdSyntaxError.assert(source, i, "=")
|
||||
i++
|
||||
End
|
||||
}
|
||||
else -> {
|
||||
GkdSyntaxError.throwError(source, i, prefix)
|
||||
}
|
||||
}
|
||||
GkdParserResult(attrOperator, i - offset)
|
||||
}
|
||||
|
||||
val stringParser = GkdParser("`") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix)
|
||||
i++
|
||||
var data = ""
|
||||
while (source[i] != '`') {
|
||||
if (i == source.length - 1) {
|
||||
GkdSyntaxError.assert(source, i, "`")
|
||||
break
|
||||
}
|
||||
if (source[i] == '\\') {
|
||||
i++
|
||||
GkdSyntaxError.assert(source, i)
|
||||
if (source[i] == '`') {
|
||||
data += source[i]
|
||||
GkdSyntaxError.assert(source, i + 1)
|
||||
} else {
|
||||
data += '\\' + source[i].toString()
|
||||
}
|
||||
} else {
|
||||
data += source[i]
|
||||
}
|
||||
i++
|
||||
}
|
||||
i++
|
||||
GkdParserResult(data, i - offset)
|
||||
}
|
||||
|
||||
val numberParser = GkdParser("1234567890.") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix)
|
||||
var value = ""
|
||||
value = if (source[i] == '.') {
|
||||
value += source[i]
|
||||
i++
|
||||
GkdSyntaxError.assert(source, i, "1234567890")
|
||||
while ("1234567890".contains(source[i])) {
|
||||
value += source[i]
|
||||
i++
|
||||
}
|
||||
value
|
||||
} else {
|
||||
while ("1234567890.".contains(source[i])) {
|
||||
if (source[i] == '.') {
|
||||
value += source[i]
|
||||
i++
|
||||
// expectAssert(source, i, '1234567890')
|
||||
while ("1234567890".contains(source[i])) {
|
||||
value += source[i]
|
||||
i++
|
||||
}
|
||||
break
|
||||
} else {
|
||||
value += source[i]
|
||||
i++
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
val data = when {
|
||||
// value.endsWith('.') -> {
|
||||
// value = value.substring(0, value.length - 1)
|
||||
// value.toInt()
|
||||
// }
|
||||
value.contains('.') -> {
|
||||
value.toFloat()
|
||||
}
|
||||
else -> {
|
||||
value.toInt()
|
||||
}
|
||||
}
|
||||
GkdParserResult<Number>(data, i - offset)
|
||||
}
|
||||
|
||||
val propertyParser =
|
||||
GkdParser("1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix)
|
||||
var data = source[i].toString()
|
||||
i++
|
||||
while (i < source.length) {
|
||||
if (!prefix.contains(source[i])) {
|
||||
break
|
||||
}
|
||||
data += source[i]
|
||||
i++
|
||||
}
|
||||
|
||||
GkdParserResult(data, i - offset)
|
||||
}
|
||||
|
||||
val valueParser = GkdParser("tfn`1234567890.") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix)
|
||||
val value: Any? = when (source[i]) {
|
||||
't' -> {
|
||||
i++
|
||||
"rue".forEach { c ->
|
||||
GkdSyntaxError.assert(source, i, c.toString())
|
||||
i++
|
||||
}
|
||||
true
|
||||
}
|
||||
'f' -> {
|
||||
i++
|
||||
"alse".forEach { c ->
|
||||
GkdSyntaxError.assert(source, i, c.toString())
|
||||
i++
|
||||
}
|
||||
false
|
||||
}
|
||||
'n' -> {
|
||||
i++
|
||||
"ull".forEach { c ->
|
||||
GkdSyntaxError.assert(source, i, c.toString())
|
||||
i++
|
||||
}
|
||||
null
|
||||
}
|
||||
'`' -> {
|
||||
val s = stringParser(source, i)
|
||||
i += s.length
|
||||
s.data
|
||||
}
|
||||
in "1234567890." -> {
|
||||
val n = numberParser(source, i)
|
||||
i += n.length
|
||||
n.data
|
||||
}
|
||||
else -> {
|
||||
GkdSyntaxError.throwError(source, i, prefix)
|
||||
}
|
||||
}
|
||||
GkdParserResult(value, i - offset)
|
||||
}
|
||||
|
||||
val attrParser = GkdParser("[") { source, offset, prefix ->
|
||||
var i = offset
|
||||
GkdSyntaxError.assert(source, i, prefix)
|
||||
i++
|
||||
val parserResult = propertyParser(source, i)
|
||||
i += parserResult.length
|
||||
val operatorResult = attrOperatorParser(source, i)
|
||||
i += operatorResult.length
|
||||
val valueResult = valueParser(source, i)
|
||||
i += valueResult.length
|
||||
GkdSyntaxError.assert(source, i, "]")
|
||||
i++
|
||||
GkdParserResult(
|
||||
BinaryExpression(
|
||||
parserResult.data,
|
||||
operatorResult.data,
|
||||
valueResult.data
|
||||
), i - offset
|
||||
)
|
||||
}
|
||||
|
||||
val selectorParser = GkdParser { source, offset, _ ->
|
||||
var i = offset
|
||||
var match = false
|
||||
if (source.getOrNull(i) == '@') {
|
||||
match = true
|
||||
i++
|
||||
}
|
||||
val nameResult = nameParser(source, i)
|
||||
i += nameResult.length
|
||||
val attrList = mutableListOf<BinaryExpression>()
|
||||
while (i < source.length && source[i] == '[') {
|
||||
val attrResult = attrParser(source, i)
|
||||
i += attrResult.length
|
||||
attrList.add(attrResult.data)
|
||||
}
|
||||
if (nameResult.length == 0 && attrList.size == 0) {
|
||||
GkdSyntaxError.throwError(source, i, "[")
|
||||
}
|
||||
GkdParserResult(PropertySelector(match, nameResult.data, attrList), i - offset)
|
||||
}
|
||||
|
||||
val combinatorSelectorParser = GkdParser { source, offset, _ ->
|
||||
var i = offset
|
||||
i += whiteCharParser(source, i).length
|
||||
val topSelector = selectorParser(source, i)
|
||||
i += topSelector.length
|
||||
val selectorList = mutableListOf<Pair<CombinatorSelector, PropertySelector>>()
|
||||
while (i < source.length && whiteCharParser.prefix.contains(source[i])) {
|
||||
i += whiteCharStrictParser(source, i).length
|
||||
val combinator = if (combinatorParser.prefix.contains((source[i]))) {
|
||||
val combinatorResult = combinatorParser(source, i)
|
||||
i += combinatorResult.length
|
||||
i += whiteCharStrictParser(source, i).length
|
||||
combinatorResult.data
|
||||
} else {
|
||||
CombinatorSelector()
|
||||
}
|
||||
val selectorResult = selectorParser(source, i)
|
||||
i += selectorResult.length
|
||||
selectorList.add(combinator to selectorResult.data)
|
||||
}
|
||||
GkdParserResult(topSelector.data to selectorList, i - offset)
|
||||
}
|
||||
|
||||
val endParser = GkdParser { source, offset, _ ->
|
||||
if (offset != source.length) {
|
||||
GkdSyntaxError.throwError(source, offset, "end")
|
||||
}
|
||||
GkdParserResult(Unit, 0)
|
||||
}
|
||||
|
||||
val gkdSelectorParser: (String) -> GkdSelector = { source ->
|
||||
var i = 0
|
||||
i += whiteCharParser(source, i).length
|
||||
val combinatorSelectorResult = combinatorSelectorParser(source, i)
|
||||
i += combinatorSelectorResult.length
|
||||
|
||||
i += whiteCharParser(source, i).length
|
||||
i += endParser(source, i).length
|
||||
val data = combinatorSelectorResult.data
|
||||
val propertySelectorList = mutableListOf<PropertySelector>()
|
||||
val combinatorSelectorList = mutableListOf<CombinatorSelector>()
|
||||
propertySelectorList.add(data.first)
|
||||
data.second.forEach {
|
||||
propertySelectorList.add(it.second)
|
||||
combinatorSelectorList.add(it.first)
|
||||
}
|
||||
val wrapperList = mutableListOf(PropertySelectorWrapper(propertySelectorList.first()))
|
||||
combinatorSelectorList.forEachIndexed { index, combinatorSelector ->
|
||||
val combinatorSelectorWrapper =
|
||||
CombinatorSelectorWrapper(combinatorSelector, wrapperList.last())
|
||||
val propertySelectorWrapper =
|
||||
PropertySelectorWrapper(propertySelectorList[index + 1], combinatorSelectorWrapper)
|
||||
wrapperList.add(propertySelectorWrapper)
|
||||
}
|
||||
GkdSelector(wrapperList.last())
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
package li.songe.selector_android.selector
|
||||
|
||||
/**
|
||||
* 关系连接选择器
|
||||
*/
|
||||
data class CombinatorSelector(
|
||||
val operator: Operator = Operator.Ancestor,
|
||||
val polynomialExpression: PolynomialExpression = PolynomialExpression(mapOf(1 to 1))
|
||||
) {
|
||||
override fun toString()="${operator}(${polynomialExpression})"
|
||||
|
||||
sealed class Operator(private val key: String) {
|
||||
object ElderBrother : Operator("+")
|
||||
object YoungerBrother : Operator("-")
|
||||
object Ancestor : Operator(">")
|
||||
object Child : Operator("<")
|
||||
|
||||
override fun toString() = key
|
||||
}
|
||||
|
||||
data class PolynomialExpression(val power2coefficientMap: Map<Int, Int> = mapOf(0 to 1)) {
|
||||
fun calculate(n: Int = 1): Int {
|
||||
if (power2coefficientMap.isEmpty()) {
|
||||
return 1
|
||||
}
|
||||
var sum = 0
|
||||
power2coefficientMap.forEach { (power, coefficient) ->
|
||||
sum += coefficient * pow(n, power)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
private fun pow(x: Int, y: Int): Int {
|
||||
assert(x >= 1 && y >= 0)
|
||||
if (x == 1 || y == 0) {
|
||||
return 1
|
||||
}
|
||||
var product = x
|
||||
repeat(y - 1) {
|
||||
product *= x
|
||||
}
|
||||
return product
|
||||
}
|
||||
|
||||
override fun toString() = power2coefficientMap.map { (power, coefficient) ->
|
||||
if (coefficient == 0) {
|
||||
return@map "0"
|
||||
} else if (coefficient < 0) {
|
||||
""
|
||||
} else {
|
||||
"+"
|
||||
} + coefficient.toString() + when (power) {
|
||||
0 -> {
|
||||
""
|
||||
}
|
||||
1 -> {
|
||||
"n"
|
||||
}
|
||||
else -> {
|
||||
"n^$power"
|
||||
}
|
||||
}
|
||||
}.joinToString("")
|
||||
|
||||
val isConstant by lazy {
|
||||
power2coefficientMap.run {
|
||||
isEmpty() || (size == 1 && containsKey(0))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package li.songe.selector_android.selector
|
||||
|
||||
import li.songe.selector_android.expression.BinaryExpression
|
||||
|
||||
|
||||
data class PropertySelector(
|
||||
/**
|
||||
* 此属性选择器是否被 @ 标记
|
||||
*/
|
||||
val match: Boolean,
|
||||
val name: String,
|
||||
val expressionList: List<BinaryExpression>,
|
||||
) {
|
||||
override fun toString() = "${if (match) "@" else ""}${name}${expressionList.joinToString("")}"
|
||||
|
||||
val needMatchName = name != "*" && name.isNotEmpty()
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
package li.songe.selector_android.wrapper
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.forEachAncestorIndexed
|
||||
import li.songe.selector_android.forEachElderBrotherIndexed
|
||||
import li.songe.selector_android.forEachIndexed
|
||||
import li.songe.selector_android.forEachYoungerBrotherIndexed
|
||||
import li.songe.selector_android.getAncestor
|
||||
import li.songe.selector_android.getBrother
|
||||
import li.songe.selector_android.getDepth
|
||||
import li.songe.selector_android.getIndex
|
||||
import li.songe.selector_android.selector.CombinatorSelector
|
||||
|
||||
data class CombinatorSelectorWrapper(
|
||||
private val combinatorSelector: CombinatorSelector,
|
||||
val to: PropertySelectorWrapper,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return to.toString() + "\u0020" + combinatorSelector.toString()
|
||||
}
|
||||
|
||||
fun match(
|
||||
nodeInfo: AccessibilityNodeInfo,
|
||||
trackNodes: MutableList<AccessibilityNodeInfo?>,
|
||||
): List<AccessibilityNodeInfo?>? {
|
||||
val expression = combinatorSelector.polynomialExpression
|
||||
val isConstant = expression.isConstant
|
||||
when (combinatorSelector.operator) {
|
||||
CombinatorSelector.Operator.Ancestor -> {
|
||||
if (isConstant) {
|
||||
val constantValue = expression.calculate()
|
||||
if (constantValue > 0) {
|
||||
val ancestorNode = nodeInfo.getAncestor(constantValue)
|
||||
if (ancestorNode != null) {
|
||||
to.match(ancestorNode, trackNodes)?.let { return it }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val maxDepth = nodeInfo.getDepth()
|
||||
if (maxDepth <= 0) {
|
||||
return null
|
||||
}
|
||||
val set = mutableSetOf<Int>()
|
||||
repeat(maxDepth) {
|
||||
val v = expression.calculate(it + 1)
|
||||
if (v > 0) {
|
||||
set.add(v)
|
||||
}
|
||||
}
|
||||
nodeInfo.forEachAncestorIndexed { depth, ancestorNode ->
|
||||
if (set.contains(depth)) {
|
||||
set.remove(depth)
|
||||
to.match(ancestorNode, trackNodes)?.let { return it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CombinatorSelector.Operator.Child -> {
|
||||
if (isConstant) {
|
||||
val constantValue = expression.calculate()
|
||||
if (0 < constantValue && constantValue <= nodeInfo.childCount) {
|
||||
val childNode: AccessibilityNodeInfo? = nodeInfo.getChild(constantValue - 1)
|
||||
if (childNode != null) {
|
||||
return to.match(childNode, trackNodes)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (nodeInfo.childCount <= 0) {
|
||||
return null
|
||||
}
|
||||
val set = mutableSetOf<Int>()
|
||||
repeat(nodeInfo.childCount) {
|
||||
val v = expression.calculate(it + 1)
|
||||
if (v > 0) {
|
||||
set.add(v)
|
||||
}
|
||||
}
|
||||
nodeInfo.forEachIndexed { index, childNode ->
|
||||
if (set.contains(index)) {
|
||||
set.remove(index)
|
||||
to.match(childNode, trackNodes)?.let { return it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CombinatorSelector.Operator.ElderBrother -> {
|
||||
val i = nodeInfo.getIndex() ?: return null
|
||||
if (isConstant) {
|
||||
val constantValue = expression.calculate()
|
||||
if (constantValue in 1..i) {
|
||||
val brotherNode = nodeInfo.getBrother(constantValue)
|
||||
if (brotherNode != null) {
|
||||
to.match(brotherNode, trackNodes)?.let { return it }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (i <= 0) {
|
||||
return null
|
||||
}
|
||||
val set = mutableSetOf<Int>()
|
||||
repeat(i) {
|
||||
val v = expression.calculate(it + 1)
|
||||
if (v > 0) {
|
||||
set.add(v)
|
||||
}
|
||||
}
|
||||
nodeInfo.forEachElderBrotherIndexed { offset, brotherNode ->
|
||||
if (set.contains(offset)) {
|
||||
set.remove(offset)
|
||||
to.match(brotherNode, trackNodes)?.let { return it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CombinatorSelector.Operator.YoungerBrother -> {
|
||||
val i = nodeInfo.getIndex() ?: return null
|
||||
if (isConstant) {
|
||||
val constantValue = expression.calculate()
|
||||
if (0 < constantValue && i + constantValue < nodeInfo.parent.childCount) {
|
||||
val brotherNode = nodeInfo.getBrother(constantValue, false)
|
||||
if (brotherNode != null) {
|
||||
to.match(brotherNode, trackNodes)?.let { return it }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val parentNodeInfo = nodeInfo.parent ?: return null
|
||||
if (parentNodeInfo.childCount - i - 1 <= 0) {
|
||||
return null
|
||||
}
|
||||
val set = mutableSetOf<Int>()
|
||||
repeat(parentNodeInfo.childCount - i - 1) {
|
||||
val v = expression.calculate(it + 1)
|
||||
if (v > 0) {
|
||||
set.add(v)
|
||||
}
|
||||
}
|
||||
nodeInfo.forEachYoungerBrotherIndexed { offset, brotherNode ->
|
||||
if (set.contains(offset)) {
|
||||
set.remove(offset)
|
||||
to.match(brotherNode, trackNodes)?.let { return it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package li.songe.selector_android.wrapper
|
||||
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import li.songe.selector_android.selector.PropertySelector
|
||||
|
||||
data class PropertySelectorWrapper(
|
||||
private val propertySelector: PropertySelector,
|
||||
val to: CombinatorSelectorWrapper? = null,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return (if (to != null) {
|
||||
to.toString() + "\u0020"
|
||||
} else {
|
||||
""
|
||||
}) + propertySelector.toString()
|
||||
}
|
||||
|
||||
fun match(
|
||||
nodeInfo: AccessibilityNodeInfo,
|
||||
trackNodes: MutableList<AccessibilityNodeInfo?> = mutableListOf(),
|
||||
): List<AccessibilityNodeInfo?>? {
|
||||
|
||||
if (propertySelector.needMatchName) {
|
||||
val className = nodeInfo.className.toString()
|
||||
if (!((className.endsWith(propertySelector.name) &&
|
||||
className[className.length - propertySelector.name.length - 1] == '.')
|
||||
|| className == propertySelector.name
|
||||
)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 属性匹配单元 完全匹配
|
||||
propertySelector.expressionList.forEach { expression ->
|
||||
if (!expression.matchNodeInfo(nodeInfo)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (propertySelector.match || trackNodes.isEmpty()) {
|
||||
trackNodes.add(nodeInfo)
|
||||
} else {
|
||||
trackNodes.add(null)
|
||||
}
|
||||
if (to == null) {
|
||||
return trackNodes
|
||||
}
|
||||
return to.match(nodeInfo, trackNodes)
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package li.songe.selector_android
|
||||
|
||||
import li.songe.selector_android.parser.Transform.gkdSelectorParser
|
||||
import org.json.JSONObject
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun check_combinatorUnit() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun check_json() {
|
||||
// View > View[text='']
|
||||
val js = JSONObject("{\"key\":\"\\u6211\\u0020尼\\\\x20玛逼\\u8349\\u6ce5\\u9a6c\"}")
|
||||
|
||||
println(js.getString("key"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun check_property() {
|
||||
val source = "View[k^=.3] <2 @P + Z - G <2 P[k=1][k==2] T Sing V > V"
|
||||
println("source:$source")
|
||||
// val result = combinatorPositionSelectorParser(source, 0)
|
||||
// println("first:"+result.data.first.first)
|
||||
// result.data.first.second.forEach {
|
||||
// println("item:$it")
|
||||
// }
|
||||
// println("click_position:"+result.data.second)
|
||||
val gkdRule = gkdSelectorParser(source)
|
||||
println("result:$gkdRule")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user