正規表現コンバータの文字クラスの入れ子の取り扱いを改善

This commit is contained in:
tateisu 2020-02-04 15:14:26 +09:00
parent 5658679180
commit 937b387f77
3 changed files with 105 additions and 75 deletions

View File

@ -2,8 +2,7 @@ package jp.juggler.subwaytooter
import androidx.test.runner.AndroidJUnit4
import jp.juggler.util.asciiPattern
import jp.juggler.util.asciiPatternInternal
import org.junit.Assert
import jp.juggler.util.asciiPatternString
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@ -40,22 +39,22 @@ class TestMisskeyMentionAndroid {
@Test
@Throws(Exception::class)
fun testAsciiPatternInternal() {
fun testasciiPatternString() {
// \w \d \W \D 以外の文字は素通しする
assertEquals("""ab\c\\""", """ab\c\\""".asciiPatternInternal())
assertEquals("""[A-Za-z0-9_]""", """\w""".asciiPatternInternal())
assertEquals("""[A-Za-z0-9_-]""", """[\w-]""".asciiPatternInternal())
assertEquals("""[^A-Za-z0-9_]""", """\W""".asciiPatternInternal())
assertEquals("""[0-9]""", """\d""".asciiPatternInternal())
assertEquals("""[0-9:-]""", """[\d:-]""".asciiPatternInternal())
assertEquals("""[^0-9]""", """\D""".asciiPatternInternal())
assertEquals("""ab\c\\""", """ab\c\\""".asciiPatternString())
assertEquals("""[A-Za-z0-9_]""", """\w""".asciiPatternString())
assertEquals("""[A-Za-z0-9_-]""", """[\w-]""".asciiPatternString())
assertEquals("""[^A-Za-z0-9_]""", """\W""".asciiPatternString())
assertEquals("""[0-9]""", """\d""".asciiPatternString())
assertEquals("""[0-9:-]""", """[\d:-]""".asciiPatternString())
assertEquals("""[^0-9]""", """\D""".asciiPatternString())
// 文字セットの中の \W \D は変換できないので素通しする
assertEquals("""[\W]""", """[\W]""".asciiPatternInternal())
assertEquals("""[\D]""", """[\D]""".asciiPatternInternal())
assertEquals("""[\W]""", """[\W]""".asciiPatternString())
assertEquals("""[\D]""", """[\D]""".asciiPatternString())
// エスケープ文字の後に何もない場合も素通しする
assertEquals("""\""", """\""".asciiPatternInternal())
assertEquals("""\""", """\""".asciiPatternString())
}
@Test
@ -81,4 +80,24 @@ class TestMisskeyMentionAndroid {
assertEquals(null, matchOrNull("\\d+", ""))
}
@Test fun test3(){
// [] 空の文字セットはパースエラーになる。
// val re1="""[]""".toRegex()
// 最低でも1文字を含む。
assertEquals(true,"""[]]""".toRegex().matches("]"))
// 1文字あけた次からは閉じ括弧として扱われる。
assertEquals(true,"""[ ]]""".toRegex().matches(" ]"))
// 閉じ括弧が単体で出たら文字クラスにならない。
assertEquals(true,"""]""".toRegex().matches("]"))
// 閉じ括弧が足りないのはエラーになる。
// val a="""[[ ]""".toRegex()
//
assertEquals(true,"""[[ ]]][ ]""".toRegex().matches(" ] "))
}
}

View File

@ -40,55 +40,48 @@ fun String.asciiPatternString() : String {
val dst = StringBuilder()
dst.ensureCapacity(this.length)
var escaped = false
var insideSet = false
for(c in this) {
if(escaped) {
escaped = false
when(c) {
'w' -> if(insideSet) {
dst.append("A-Za-z0-9_")
} else {
dst.append("[A-Za-z0-9_]")
}
'd' -> if(insideSet) {
dst.append("0-9")
} else {
dst.append("[0-9]")
}
'W' -> {
if(insideSet) {
// 対応できないのでそのまま通す
dst.append('\\')
dst.append(c)
} else {
dst.append("[^A-Za-z0-9_]")
}
}
'D' -> {
if(insideSet) {
// 対応できないのでそのまま通す
dst.append('\\')
dst.append(c)
} else {
dst.append("[^0-9]")
}
}
else -> {
dst.append('\\')
dst.append(c)
var insideSet = 0
var lastOpen =0
for( i in this.indices){
val c= this[i]
when {
escaped -> {
escaped = false
when(c) {
'w' -> dst.append(if(insideSet>0) "A-Za-z0-9_" else "[A-Za-z0-9_]")
'd' -> dst.append(if(insideSet>0) "0-9" else "[0-9]")
// W,Dは文字セット内では対応できないのでそのまま通す
'W' -> if(insideSet>0) dst.append('\\').append(c) else dst.append("[^A-Za-z0-9_]")
'D' -> if(insideSet>0) dst.append('\\').append(c) else dst.append("[^0-9]")
else -> dst.append('\\').append(c)
}
}
} else if(c == '\\') {
escaped = true
} else {
dst.append(c)
if(c == '[') {
insideSet = true
} else if(c == ']' && insideSet) {
insideSet = false
c == '\\' -> escaped = true
else -> {
dst.append(c)
if(c == '[') {
insideSet++
lastOpen=i
} else if(c == ']' && insideSet >0 && i > lastOpen+1) {
insideSet--
// [] のようなカラの文字クラスは正規表現のエラーになる。
// つまり文字クラスは最低でも1文字を含むので、
// []] のような記述は ] のみを示す文字クラスになる。
// [ ]] のような記述は 空白に続いて文字']'が出現する入力にマッチする。
// 1文字あけた次からは閉じ括弧として扱われて、
// 続いて開いてない時に登場した ] は普通の文字として扱われる。
// [[ABC][DEF]]は [ABCDEF]と同じ。入れ子になっている
// JVMのPatternでは[A-Z&&[^D-F]]のような記述は「A-ZのうちD-F以外」と解釈される
// ICUには [\p{Letter}&&\p{script=cyrillic}] や [\p{Letter}--\p{script=latin}]
// JVMと同じような記述ができるかどうかは分からない。
}
}
}
}

View File

@ -1,10 +1,7 @@
package jp.juggler.subwaytooter
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.util.asciiPattern
import jp.juggler.util.asciiPatternInternal
import jp.juggler.util.asciiPatternString
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.Test
class TestMisskeyMention {
@ -27,22 +24,43 @@ class TestMisskeyMention {
@Test
@Throws(Exception::class)
fun testAsciiPatternInternal() {
fun testasciiPatternString() {
// \w \d \W \D 以外の文字は素通しする
assertEquals("""ab\c\\""", """ab\c\\""".asciiPatternInternal())
assertEquals("""[A-Za-z0-9_]""", """\w""".asciiPatternInternal())
assertEquals("""[A-Za-z0-9_-]""", """[\w-]""".asciiPatternInternal())
assertEquals("""[^A-Za-z0-9_]""", """\W""".asciiPatternInternal())
assertEquals("""[0-9]""", """\d""".asciiPatternInternal())
assertEquals("""[0-9:-]""", """[\d:-]""".asciiPatternInternal())
assertEquals("""[^0-9]""", """\D""".asciiPatternInternal())
assertEquals("""ab\c\\""", """ab\c\\""".asciiPatternString())
assertEquals("""[A-Za-z0-9_]""", """\w""".asciiPatternString())
assertEquals("""[A-Za-z0-9_-]""", """[\w-]""".asciiPatternString())
assertEquals("""[^A-Za-z0-9_]""", """\W""".asciiPatternString())
assertEquals("""[0-9]""", """\d""".asciiPatternString())
assertEquals("""[0-9:-]""", """[\d:-]""".asciiPatternString())
assertEquals("""[^0-9]""", """\D""".asciiPatternString())
// 文字セットの中の \W \D は変換できないので素通しする
assertEquals("""[\W]""", """[\W]""".asciiPatternInternal())
assertEquals("""[\D]""", """[\D]""".asciiPatternInternal())
assertEquals("""[\W]""", """[\W]""".asciiPatternString())
assertEquals("""[\D]""", """[\D]""".asciiPatternString())
// エスケープ文字の後に何もない場合も素通しする
assertEquals("""\""", """\""".asciiPatternInternal())
assertEquals("""\""", """\""".asciiPatternString())
}
@Test fun test3(){
// [] 空の文字セットはパースエラーになる。
// val re1="""[]""".toRegex()
// 最低でも1文字を含む。
assertEquals(true,"""[]]""".toRegex().matches("]"))
// 1文字あけた次からは閉じ括弧として扱われる。
assertEquals(true,"""[ ]]""".toRegex().matches(" ]"))
// 閉じ括弧が単体で出たら文字クラスにならない。
assertEquals(true,"""]""".toRegex().matches("]"))
// 閉じ括弧が足りないのはエラーになる。
// val a="""[[ ]""".toRegex()
//
assertEquals(true,"""[[ ]]][ ]""".toRegex().matches(" ] "))
}
}