Scala
正規表現

Scalaで正規表現: ユニコードの空白文字29種類にマッチするか試してみた

More than 3 years have passed since last update.

入力から空白を削りたいといった要件がたまにあるが、空白と一言で言っても半角スペース・全角スペースだけでなく、ユニコードには様々な空白文字がある。調べた範囲では29種類あった。これらの空白文字を正規表現で一網打尽する方法はないか検証してみた。検証のために用いたコードは、この記事の最後に載せてある。


検証結果

検証結果は下の表にまとめた。各行が空白文字に、各列が正規表現になっている。その文字がその正規表現でマッチしたものには「o」を付けた。

code
name
\p{javaWhitespace}
\p{javaSpaceChar}
\p{Zs}
\s
\p{Blank}
見た目

\u0009
HORIZONTAL TABULATION
o

o
o

foo\u0009bar

\u000A
LINE FEED
o

o

foo\u000Abar

\u000B
VERTICAL TABULATION
o

o

foo\u000Bbar

\u000C
FORM FEED
o

o

foo\u000Cbar

\u000D
CARRIAGE RETURN
o

o

foo\u000Dbar

\u001C
FILE SEPARATOR
o

foo\u001Cbar

\u001D
GROUP SEPARATOR
o

foo\u001Dbar

\u001E
RECORD SEPARATOR
o

foo\u001Ebar

\u001F
UNIT SEPARATOR
o

foo\u001Fbar

\u0020
SPACE
o
o
o
o
o

foo bar

\u00A0
NO-BREAK SPACE

o
o

foo bar

\u1680
OGHAM SPACE MARK
o
o
o

foobar

\u180E
MONGOLIAN VOWEL SEPARATOR
o
o
o

foobar

\u2000
EN QUAD
o
o
o

foobar

\u2001
EM QUAD
o
o
o

foobar

\u2002
EN SPACE
o
o
o

foobar

\u2003
EM SPACE
o
o
o

foobar

\u2004
THREE-PER-EM SPACE
o
o
o

foobar

\u2005
FOUR-PER-EM SPACE
o
o
o

foobar

\u2006
SIX-PER-EM SPACE
o
o
o

foobar

\u2007
FIGURE SPACE

o
o

foobar

\u2008
PUNCTUATION SPACE
o
o
o

foobar

\u2009
THIN SPACE
o
o
o

foobar

\u200A
HAIR SPACE
o
o
o

foobar

\u200B
ZERO WIDTH SPACE

foobar

\u202F
NARROW NO-BREAK SPACE

o
o

foobar

\u205F
MEDIUM MATHEMATICAL SPACE
o
o
o

foobar

\u3000
IDEOGRAPHIC SPACE
o
o
o

foo bar

\uFEFF
ZERO WIDTH NO-BREAK SPACE

foobar


  • 全角スペースと言われるものはこの表では \u3000 になる。

  • \u001F 以下の文字はMarkdownで表示しきれないものがあるので見た目には反映していない。

\p{javaWhitespace}java.lang.CharacterisWhitespace と同じものらしいが、一番多く空白文字にマッチした。それでも、マッチしない文字もあったので、\p{javaWhitespace}とユニコードエスケープを組み合わせた正規表現を作る必要がありそう。

全角スペースと半角スペースとタブくらいにマッチすればいい要件なら、\p{javaSpaceChar}\p{Zs} で十分。


検証用コード

import org.scalatest._

class CompilationSpec extends FeatureSpec with GivenWhenThen with BeforeAndAfter with ParallelTestExecution with Matchers {
feature("Playground") {
scenario("test \\p{javaSpaceChar}") {
def isS(char: String) = """\s""".r.findFirstIn(char).nonEmpty
def isZs(char: String) = """\p{Zs}""".r.findFirstIn(char).nonEmpty
def isJavaSpaceChar(char: String) = """\p{javaSpaceChar}""".r.findFirstIn(char).nonEmpty
def isJavaWhitespace(char: String) = """\p{javaWhitespace}""".r.findFirstIn(char).nonEmpty
def isBlank(char: String) = """\p{Blank}""".r.findFirstIn(char).nonEmpty

implicit class MyBoolean(bool: Boolean) {
def o: String = {
bool match {
case true => "o"
case false => ""
}
}
}

val unicodeSpaces = Seq(
Seq("\u0009", "HORIZONTAL TABULATION"),
Seq("\u000A", "LINE FEED"),
Seq("\u000B", "VERTICAL TABULATION"),
Seq("\u000C", "FORM FEED"),
Seq("\u000D", "CARRIAGE RETURN"),
Seq("\u001C", "FILE SEPARATOR"),
Seq("\u001D", "GROUP SEPARATOR"),
Seq("\u001E", "RECORD SEPARATOR"),
Seq("\u001F", "UNIT SEPARATOR"),
Seq("\u0020", "SPACE"),
Seq("\u00A0", "NO-BREAK SPACE"),
Seq("\u1680", "OGHAM SPACE MARK"),
Seq("\u180E", "MONGOLIAN VOWEL SEPARATOR"),
Seq("\u2000", "EN QUAD"),
Seq("\u2001", "EM QUAD"),
Seq("\u2002", "EN SPACE"),
Seq("\u2003", "EM SPACE"),
Seq("\u2004", "THREE-PER-EM SPACE"),
Seq("\u2005", "FOUR-PER-EM SPACE"),
Seq("\u2006", "SIX-PER-EM SPACE"),
Seq("\u2007", "FIGURE SPACE"),
Seq("\u2008", "PUNCTUATION SPACE"),
Seq("\u2009", "THIN SPACE"),
Seq("\u200A", "HAIR SPACE"),
Seq("\u200B", "ZERO WIDTH SPACE"),
Seq("\u202F", "NARROW NO-BREAK SPACE"),
Seq("\u205F", "MEDIUM MATHEMATICAL SPACE"),
Seq("\u3000", "IDEOGRAPHIC SPACE"), // 全角スペース
Seq("\uFEFF", "ZERO WIDTH NO-BREAK SPACE")
)

unicodeSpaces.foreach {
case Seq(space, name) =>
val code = space.charAt(0).toInt.formatted("\\u%04X")
println(List(code, name, isJavaWhitespace(space).o, isJavaSpaceChar(space).o, isZs(space).o, isS(space).o, isBlank(space).o, "`foo`" + ({ space.charAt(0).toInt match { case a if a <= 31 => code; case a => space; } }) + "`bar`").mkString("| ", " | ", " |"))
}

}
}
}


参考文献