feat: remove android

This commit is contained in:
lisonge 2023-05-30 20:35:13 +08:00
parent b9cef851d7
commit 2778bc2cce
25 changed files with 0 additions and 1554 deletions

View File

@ -1 +0,0 @@
/build

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +0,0 @@
package li.songe.selector_android.parser
data class GkdParserResult<T>(val data: T, val length: Int = 0)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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