Skip to content

Commit

Permalink
feat(selector): ~= regex
Browse files Browse the repository at this point in the history
  • Loading branch information
lisonge committed Mar 12, 2024
1 parent a5d2040 commit a085efd
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 63 deletions.
52 changes: 27 additions & 25 deletions app/src/main/kotlin/li/songe/gkd/service/AbExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -131,33 +131,33 @@ val getChildren: (AccessibilityNodeInfo) -> Sequence<AccessibilityNodeInfo> = {

val allowPropertyNames by lazy {
mapOf(
"id" to PrimitiveValue.StringValue.type,
"vid" to PrimitiveValue.StringValue.type,
"id" to PrimitiveValue.StringValue.TYPE_NAME,
"vid" to PrimitiveValue.StringValue.TYPE_NAME,

"name" to PrimitiveValue.StringValue.type,
"text" to PrimitiveValue.StringValue.type,
"text.length" to PrimitiveValue.IntValue.type,
"desc" to PrimitiveValue.StringValue.type,
"desc.length" to PrimitiveValue.IntValue.type,
"name" to PrimitiveValue.StringValue.TYPE_NAME,
"text" to PrimitiveValue.StringValue.TYPE_NAME,
"text.length" to PrimitiveValue.IntValue.TYPE_NAME,
"desc" to PrimitiveValue.StringValue.TYPE_NAME,
"desc.length" to PrimitiveValue.IntValue.TYPE_NAME,

"clickable" to PrimitiveValue.BooleanValue.type,
"focusable" to PrimitiveValue.BooleanValue.type,
"checkable" to PrimitiveValue.BooleanValue.type,
"checked" to PrimitiveValue.BooleanValue.type,
"editable" to PrimitiveValue.BooleanValue.type,
"longClickable" to PrimitiveValue.BooleanValue.type,
"visibleToUser" to PrimitiveValue.BooleanValue.type,
"clickable" to PrimitiveValue.BooleanValue.TYPE_NAME,
"focusable" to PrimitiveValue.BooleanValue.TYPE_NAME,
"checkable" to PrimitiveValue.BooleanValue.TYPE_NAME,
"checked" to PrimitiveValue.BooleanValue.TYPE_NAME,
"editable" to PrimitiveValue.BooleanValue.TYPE_NAME,
"longClickable" to PrimitiveValue.BooleanValue.TYPE_NAME,
"visibleToUser" to PrimitiveValue.BooleanValue.TYPE_NAME,

"left" to PrimitiveValue.IntValue.type,
"top" to PrimitiveValue.IntValue.type,
"right" to PrimitiveValue.IntValue.type,
"bottom" to PrimitiveValue.IntValue.type,
"width" to PrimitiveValue.IntValue.type,
"height" to PrimitiveValue.IntValue.type,
"left" to PrimitiveValue.IntValue.TYPE_NAME,
"top" to PrimitiveValue.IntValue.TYPE_NAME,
"right" to PrimitiveValue.IntValue.TYPE_NAME,
"bottom" to PrimitiveValue.IntValue.TYPE_NAME,
"width" to PrimitiveValue.IntValue.TYPE_NAME,
"height" to PrimitiveValue.IntValue.TYPE_NAME,

"index" to PrimitiveValue.IntValue.type,
"depth" to PrimitiveValue.IntValue.type,
"childCount" to PrimitiveValue.IntValue.type,
"index" to PrimitiveValue.IntValue.TYPE_NAME,
"depth" to PrimitiveValue.IntValue.TYPE_NAME,
"childCount" to PrimitiveValue.IntValue.TYPE_NAME,
)
}

Expand Down Expand Up @@ -289,7 +289,8 @@ fun createCacheTransform(): CacheTransform {
getBeforeBrothers = { node, connectExpression ->
sequence {
val parentVal = node.parent ?: return@sequence
val index = indexCache[node] // 如果 node 由 quickFind 得到, 则第一次调用此方法可能得到 indexCache 是空
val index =
indexCache[node] // 如果 node 由 quickFind 得到, 则第一次调用此方法可能得到 indexCache 是空
if (index != null) {
var i = index - 1
var offset = 0
Expand All @@ -305,7 +306,8 @@ fun createCacheTransform(): CacheTransform {
offset++
}
} else {
val list = getChildrenCache(parentVal).takeWhile { it != node }.toMutableList()
val list =
getChildrenCache(parentVal).takeWhile { it != node }.toMutableList()
list.reverse()
yieldAll(list.filterIndexed { i, _ ->
connectExpression.checkOffset(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ data class GkdSyntaxError internal constructor(
val expectedValue: String,
val position: Int,
val source: String,
override val cause: Exception? = null
) : Exception(
"expected $expectedValue in selector at position $position, but got ${
source.getOrNull(
Expand All @@ -26,6 +27,11 @@ internal fun gkdAssert(
}
}

internal fun gkdError(source: String, offset: Int, expectedValue: String = ""): Nothing {
throw GkdSyntaxError(expectedValue, offset, source)
internal fun gkdError(
source: String,
offset: Int,
expectedValue: String = "",
cause: Exception? = null
): Nothing {
throw GkdSyntaxError(expectedValue, offset, source, cause)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ data class BinaryExpression(
val value: PrimitiveValue
) : Expression() {
override fun <T> match(node: T, transform: Transform<T>) =
operator.compare(transform.getAttr(node, name), value.value)
operator.compare(transform.getAttr(node, name), value)

override val binaryExpressions
get() = arrayOf(this)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package li.songe.selector.data

sealed class CompareOperator(val key: String) {
abstract fun compare(left: Any?, right: Any?): Boolean
abstract fun compare(left: Any?, right: PrimitiveValue): Boolean
abstract fun allowType(type: PrimitiveValue): Boolean

companion object {
Expand All @@ -10,7 +10,18 @@ sealed class CompareOperator(val key: String) {
listOf(
Equal,
NotEqual,
Start, NotStart, Include, NotInclude, End, NotEnd, Less, LessEqual, More, MoreEqual
Start,
NotStart,
Include,
NotInclude,
End,
NotEnd,
Less,
LessEqual,
More,
MoreEqual,
Matches,
NotMatches
).sortedBy { -it.key.length }.toTypedArray()
}

Expand All @@ -28,103 +39,156 @@ sealed class CompareOperator(val key: String) {
}

data object Equal : CompareOperator("=") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is CharSequence && right is CharSequence) {
left.contentReversedEquals(right)
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is CharSequence && right is PrimitiveValue.StringValue) {
left.contentReversedEquals(right.value)
} else {
left == right
left == right.value
}
}

override fun allowType(type: PrimitiveValue) = true
}

data object NotEqual : CompareOperator("!=") {
override fun compare(left: Any?, right: Any?) = !Equal.compare(left, right)
override fun compare(left: Any?, right: PrimitiveValue) = !Equal.compare(left, right)
override fun allowType(type: PrimitiveValue) = true
}

data object Start : CompareOperator("^=") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is CharSequence && right is CharSequence) left.startsWith(right) else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is CharSequence && right is PrimitiveValue.StringValue) {
left.startsWith(right.value)
} else {
false
}
}

override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.StringValue
}

data object NotStart : CompareOperator("!^=") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is CharSequence && right is CharSequence) !left.startsWith(right) else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is CharSequence && right is PrimitiveValue.StringValue) {
!left.startsWith(right.value)
} else {
false
}
}

override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.StringValue
}

data object Include : CompareOperator("*=") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is CharSequence && right is CharSequence) left.contains(right) else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is CharSequence && right is PrimitiveValue.StringValue) {
left.contains(right.value)
} else {
false
}
}

override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.StringValue
}

data object NotInclude : CompareOperator("!*=") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is CharSequence && right is CharSequence) !left.contains(right) else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is CharSequence && right is PrimitiveValue.StringValue) {
!left.contains(right.value)
} else {
false
}
}

override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.StringValue
}

data object End : CompareOperator("$=") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is CharSequence && right is CharSequence) left.endsWith(right) else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is CharSequence && right is PrimitiveValue.StringValue) {
left.endsWith(right.value)
} else {
false
}
}

override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.StringValue
}

data object NotEnd : CompareOperator("!$=") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is CharSequence && right is CharSequence) !left.endsWith(right) else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is CharSequence && right is PrimitiveValue.StringValue) {
!left.endsWith(
right.value
)
} else {
false
}
}

override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.StringValue
}

data object Less : CompareOperator("<") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is Int && right is Int) left < right else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is Int && right is PrimitiveValue.IntValue) left < right.value else false
}


override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.IntValue
}

data object LessEqual : CompareOperator("<=") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is Int && right is Int) left <= right else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is Int && right is PrimitiveValue.IntValue) left <= right.value else false
}

override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.IntValue
}

data object More : CompareOperator(">") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is Int && right is Int) left > right else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is Int && right is PrimitiveValue.IntValue) left > right.value else false
}


override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.IntValue
}

data object MoreEqual : CompareOperator(">=") {
override fun compare(left: Any?, right: Any?): Boolean {
return if (left is Int && right is Int) left >= right else false
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is Int && right is PrimitiveValue.IntValue) left >= right.value else false
}


override fun allowType(type: PrimitiveValue) = type is PrimitiveValue.IntValue
}

data object Matches : CompareOperator("~=") {
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is CharSequence && right is PrimitiveValue.StringValue) {
right.outMatches(left)
} else {
false
}
}

override fun allowType(type: PrimitiveValue): Boolean {
return type is PrimitiveValue.StringValue && type.matches != null
}
}

data object NotMatches : CompareOperator("!~=") {
override fun compare(left: Any?, right: PrimitiveValue): Boolean {
return if (left is CharSequence && right is PrimitiveValue.StringValue) {
!right.outMatches(left)
} else {
false
}
}

override fun allowType(type: PrimitiveValue): Boolean {
return Matches.allowType(type)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,55 @@ sealed class PrimitiveValue(open val value: Any?, open val type: String) {
override fun toString() = "null"
}

data class BooleanValue(override val value: Boolean) : PrimitiveValue(value, type) {
data class BooleanValue(override val value: Boolean) : PrimitiveValue(value, TYPE_NAME) {
override fun toString() = value.toString()

companion object {
const val type = "boolean"
const val TYPE_NAME = "boolean"
}
}

data class IntValue(override val value: Int) : PrimitiveValue(value, type) {
data class IntValue(override val value: Int) : PrimitiveValue(value, TYPE_NAME) {
override fun toString() = value.toString()

companion object {
const val type = "int"
const val TYPE_NAME = "int"
}
}

data class StringValue(override val value: String) : PrimitiveValue(value, type) {
data class StringValue(
override val value: String,
val matches: ((CharSequence) -> Boolean)? = null
) : PrimitiveValue(value, TYPE_NAME) {

val outMatches: (value: CharSequence) -> Boolean = run {
matches ?: return@run { false }
getMatchValue(value, "(?is)", ".*")?.let { startsWithValue ->
return@run { value -> value.startsWith(startsWithValue, ignoreCase = true) }
}
getMatchValue(value, "(?is).*", ".*")?.let { containsValue ->
return@run { value -> value.contains(containsValue, ignoreCase = true) }
}
getMatchValue(value, "(?is).*", "")?.let { endsWithValue ->
return@run { value -> value.endsWith(endsWithValue, ignoreCase = true) }
}
return@run matches
}

companion object {
const val type = "string"
const val TYPE_NAME = "string"
private const val REG_SPECIAL_STRING = "\\^$.?*|+()[]{}"
private fun getMatchValue(value: String, prefix: String, suffix: String): String? {
if (value.startsWith(prefix) && value.endsWith(suffix) && value.length >= (prefix.length + suffix.length)) {
for (i in prefix.length until value.length - suffix.length) {
if (value[i] in REG_SPECIAL_STRING) {
return null
}
}
return value.subSequence(prefix.length, value.length - suffix.length).toString()
}
return null
}
}

override fun toString(): String {
Expand Down
Loading

0 comments on commit a085efd

Please sign in to comment.