LoginSignup
25
25

More than 5 years have passed since last update.

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

Posted at

入力から空白を削りたいといった要件がたまにあるが、空白と一言で言っても半角スペース・全角スペースだけでなく、ユニコードには様々な空白文字がある。調べた範囲では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("| ", " | ", " |"))
      }

    }
  }
}

参考文献

25
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
25