【scala中文源码系列】scala.util.matching.Regex类用法示例源码详解

scala Regex类用法示例源码详解


此包与字符串的正则表达式(regex)匹配有关,其主要目标是从这些匹配中提取信息或将其替换为其他内容。

Regex 是用户实例化进行正则表达式匹配的类。

Regex 相关联的伴生对象包含支持成员:

  • Match 提供了有关匹配的更多信息。
  • MatchIterator 用于迭代匹配的字符串。
  • MatchData 只是上述类的基本特征。
  • GroupsMatch 中提取组,而不重新计算匹配。

用法

这个类委托给Java平台的java.util.regex包。有关模式字符串的正则表达式语法的详细信息,请参阅java.util.regex.Pattern的文档。

Regex 的实例表示已编译的正则表达式模式。由于编译非常耗时,因此经常使用的Regex应该在循环之外构造一次,并且可能在伴生对象中。

创建Regex的规范方法是使用为字符串隐式提供的r方法:

val date = raw"(\d{4})-(\d{2})-(\d{2})".r

由于在多行字符串字面量中不会处理转义,因此使用三引号避免转义反斜杠字符,所以可以将"\\d"写成"""\d"""。使用某些插值器,例如raw"\d".r或自定义插值器r"\d"也可以实现相同的结果。

提取

要从匹配的 Regex 中提取捕获的组,可以在模式匹配中使用它作为提取器:

"2004-01-20" match {
  case date(year, month, day) => s"$year was a good year for PLs."
}

仅检查 Regex 是否匹配,忽略任何组,请使用序列通配符:

"2004-01-20" match {
  case date(_*) => "It's a date!"
}

这是因为 Regex 提取器会产生一个字符串序列。只从日期中提取年份也可以用序列通配符表示:

"2004-01-20" match {
  case date(year, _*) => s"$year was a good year for PLs."
}

在模式匹配中,Regex 通常匹配整个输入。但是,未锚定的 Regex 会在输入的任何地方找到模式。

val embeddedDate = date.unanchored
"Date: 2004-01-20 17:25:18 GMT (10 years, 28 weeks, 5 days, 17 hours and 51 minutes ago)" match {
  case embeddedDate("2004", "01", "20") => "A Scala is born."
}

查找匹配

要查找或替换模式的匹配项,请使用各种查找和替换方法。对于每种方法,都有一个用于处理匹配字符串的版本,以及一个用于处理 Match 对象的版本。

例如,使用未锚定的 Regex 进行模式匹配,如上面的例子中所示,也可以使用 findFirstMatchIn 完成。findFirst 方法返回一个 Option,如果找到匹配,则为非空,否则为 None

val dates = "Important dates in history: 2004-01-20, 1958-09-05, 2010-10-06, 2011-07-15"
val firstDate = date.findFirstIn(dates).getOrElse("No date found.")
val firstYear = for (m <- date.findFirstMatchIn(dates)) yield m.group(1)

要查找所有匹配项:

val allYears = for (m <- date.findAllMatchIn(dates)) yield m.group(1)

要迭代匹配的字符串,请使用 findAllIn,它返回一个特殊的迭代器,可以查询最后一个匹配的 MatchData

val mi = date.findAllIn(dates)
while (mi.hasNext) {
  val d = mi.next
  if (mi.group(1).toInt < 1960) println(s"$d: An oldie but goodie.")
}

尽管 findAllIn 返回的 MatchIterator 像任何 Iterator 一样使用,交替调用 hasNextnext,但是 hasNext 还具有将底层匹配器前进到下一个未使用的匹配的附加副作用。此效果在表示“当前匹配”的 MatchData 中可见。

val r = "(ab+c)".r
val s = "xxxabcyyyabbczzz"
r.findAllIn(s).start    // 3
val mi = r.findAllIn(s)
mi.hasNext              // true
mi.start                // 3
mi.next()               // "abc"
mi.start                // 3
mi.hasNext              // true
mi.start                // 9
mi.next()               // "abbc"

该示例显示了如果需要,MatchData 上的 start 等方法将前进到第一个匹配。它还显示了如果 next 已经返回当前匹配,hasNext 将前进到下一个未使用的匹配。

可以使用 matchData 方法捕获当前 MatchData。或者,findAllMatchIn 返回一个 Iterator[Match],其中迭代器与其已经生成的 Match 对象之间没有交互。

请注意,findAllIn 查找不重叠的匹配。 (有关更多示例,请参阅 findAllIn。)

val num = raw"(\d+)".r
val all = num.findAllIn("123").toList  // List("123"),而不是List("123", "23", "3")

替换文本

可以无条件地执行文本替换,也可以根据当前匹配来执行文本替换:

val redacted    = date.replaceAllIn(dates, "XXXX-XX-XX")
val yearsOnly   = date.replaceAllIn(dates, m => m.group(1))
val months      = (0 to 11).map { i => val c = Calendar.getInstance; c.set(2014, i, 1); f"${c}%tb" }
val reformatted = date.replaceAllIn(dates, _ match { case date(y,m,d) => f"${months(m.toInt - 1)} $d, $y" })

Match 与创建它的 Regex 进行模式匹配不会重新应用 Regex。在 reformatted 的表达式中,每个 date 匹配只计算一次。但是可以将 Regex 应用于来自不同模式的 Match

val docSpree = """2011(?:-\d{2}){2}""".r
val docView  = date.replaceAllIn(dates, _ match {
  case docSpree() => "Historic doc spree!"
  case _          => "Something else happened"
})

源码

/**
 * 此包与字符串的正则表达式(regex)匹配有关,
 * 其主要目标是从这些匹配中提取信息或将其替换为其他内容。
 *
 * [[scala.util.matching.Regex]] 是用户实例化进行正则表达式匹配的类。
 *
 * 与 [[scala.util.matching.Regex]] 相关联的伴生对象包含支持成员:
 * * [[scala.util.matching.Regex.Match]] 提供了有关匹配的更多信息。
 * * [[scala.util.matching.Regex.MatchIterator]] 用于迭代匹配的字符串。
 * * [[scala.util.matching.Regex.MatchData]] 只是上述类的基本特征。
 * * [[scala.util.matching.Regex.Groups]] 从 [[scala.util.matching.Regex.Match]] 中提取组,而不重新计算匹配。
 */
package scala.util.matching

import scala.collection.AbstractIterator
import java.util.regex.{ Pattern, Matcher }

/** 一个正则表达式用于确定字符串是否匹配某个模式,
 * 并在匹配成功后提取或转换匹配的部分。
 *
 * === 用法 ===
 * 这个类委托给Java平台的[[java.util.regex]]包。
 * 有关模式字符串的正则表达式语法的详细信息,请参阅[[java.util.regex.Pattern]]的文档。
 *
 * `Regex`的实例表示已编译的正则表达式模式。
 * 由于编译非常耗时,因此经常使用的`Regex`应该在循环之外构造一次,并且可能在伴生对象中。
 *
 * 创建`Regex`的规范方法是使用为字符串隐式提供的`r`方法:
 *
 * {{{
 * val date = raw"(\d{4})-(\d{2})-(\d{2})".r
 * }}}
 *
 * 由于在多行字符串字面量中不会处理转义,因此使用三引号避免转义反斜杠字符,所以可以将`"\\d"`写成`"""\d"""`。
 * 使用某些插值器,例如`raw"\d".r`或自定义插值器`r"\d"`也可以实现相同的结果。
 *
 * === 提取 ===
 * 要从匹配的`Regex`中提取捕获的组,可以在模式匹配中使用它作为提取器:
 *
 * {{{
 * "2004-01-20" match {
 *   case date(year, month, day) => s"\$year was a good year for PLs."
 * }
 * }}}
 *
 * 仅检查`Regex`是否匹配,忽略任何组,请使用序列通配符:
 *
 * {{{
 * "2004-01-20" match {
 *   case date(_*) => "It's a date!"
 * }
 * }}}
 *
 * 这是因为`Regex`提取器会产生一个字符串序列。
 * 只从日期中提取年份也可以用序列通配符表示:
 *
 * {{{
 * "2004-01-20" match {
 *   case date(year, _*) => s"\$year was a good year for PLs."
 * }
 * }}}
 *
 * 在模式匹配中,`Regex`通常匹配整个输入。
 * 但是,未锚定的`Regex`会在输入的任何地方找到模式。
 *
 * {{{
 * val embeddedDate = date.unanchored
 * "Date: 2004-01-20 17:25:18 GMT (10 years, 28 weeks, 5 days, 17 hours and 51 minutes ago)" match {
 *   case embeddedDate("2004", "01", "20") => "A Scala is born."
 * }
 * }}}
 *
 * === 查找匹配 ===
 * 要查找或替换模式的匹配项,请使用各种查找和替换方法。
 * 对于每种方法,都有一个用于处理匹配字符串的版本,以及一个用于处理`Match`对象的版本。
 *
 * 例如,使用未锚定的`Regex`进行模式匹配,如上面的例子中所示,也可以使用`findFirstMatchIn`完成。
 * `findFirst`方法返回一个`Option`,如果找到匹配,则为非空,否则为`None`:
 *
 * {{{
 * val dates = "Important dates in history: 2004-01-20, 1958-09-05, 2010-10-06, 2011-07-15"
 * val firstDate = date.findFirstIn(dates).getOrElse("No date found.")
 * val firstYear = for (m <- date.findFirstMatchIn(dates)) yield m.group(1)
 * }}}
 *
 * 要查找所有匹配项:
 *
 * {{{
 * val allYears = for (m <- date.findAllMatchIn(dates)) yield m.group(1)
 * }}}
 *
 * 要迭代匹配的字符串,请使用`findAllIn`,它返回一个特殊的迭代器,
 * 可以查询最后一个匹配的`MatchData`:
 *
 * {{{
 * val mi = date.findAllIn(dates)
 * while (mi.hasNext) {
 *   val d = mi.next
 *   if (mi.group(1).toInt < 1960) println(s"\$d: An oldie but goodie.")
 * }
 * }}}
 *
 * 尽管`findAllIn`返回的`MatchIterator`像任何`Iterator`一样使用,
 * 交替调用`hasNext`和`next`,但是`hasNext`还具有将底层匹配器前进到下一个未使用的匹配的附加副作用。
 * 此效果在表示“当前匹配”的`MatchData`中可见。
 *
 * {{{
 * val r = "(ab+c)".r
 * val s = "xxxabcyyyabbczzz"
 * r.findAllIn(s).start    // 3
 * val mi = r.findAllIn(s)
 * mi.hasNext              // true
 * mi.start                // 3
 * mi.next()               // "abc"
 * mi.start                // 3
 * mi.hasNext              // true
 * mi.start                // 9
 * mi.next()               // "abbc"
 * }}}
 *
 * 该示例显示了如果需要,`MatchData`上的`start`等方法将前进到第一个匹配。
 * 它还显示了如果`next`已经返回当前匹配,`hasNext`将前进到下一个未使用的匹配。
 *
 * 可以使用`matchData`方法捕获当前`MatchData`。
 * 或者,`findAllMatchIn`返回一个`Iterator[Match]`,其中迭代器与其已经生成的`Match`对象之间没有交互。
 *
 * 请注意,`findAllIn`查找不重叠的匹配。 (有关更多示例,请参阅[[findAllIn]]。)
 *
 * {{{
 * val num = raw"(\d+)".r
 * val all = num.findAllIn("123").toList  // List("123"),而不是List("123", "23", "3")
 * }}}
 *
 * === 替换文本 ===
 * 可以无条件地执行文本替换,也可以根据当前匹配来执行文本替换:
 *
 * {{{
 * val redacted    = date.replaceAllIn(dates, "XXXX-XX-XX")
 * val yearsOnly   = date.replaceAllIn(dates, m => m.group(1))
 * val months      = (0 to 11).map { i => val c = Calendar.getInstance; c.set(2014, i, 1); f"\$c%tb" }
 * val reformatted = date.replaceAllIn(dates, _ match { case date(y,m,d) => f"\${months(m.toInt - 1)} \$d, \$y" })
 * }}}
 *
 * 将`Match`与创建它的`Regex`进行模式匹配不会重新应用`Regex`。
 * 在`reformatted`的表达式中,每个`date`匹配只计算一次。 但是可以将`Regex`应用于来自不同模式的`Match`:
 *
 * {{{
 * val docSpree = """2011(?:-\d{2}){2}""".r
 * val docView  = date.replaceAllIn(dates, _ match {
 *   case docSpree() => "Historic doc spree!"
 *   case _          => "Something else happened"
 * })
 * }}}
 *
 * @see [[java.util.regex.Pattern]]
 *
 * @param pattern    编译的模式
 * @param groupNames 捕获组名称到索引的映射
 *
 * @define replacementString
 * 在替换字符串中,以美元符号(`$`)后跟一个数字将被解释为对匹配的模式中的组的引用,
 * 数字1到9对应于前九个组,数字0表示整个匹配。其他任何字符都是错误的。
 * 反斜杠(`\`)字符将被解释为转义字符,并且可以用于转义美元符号。
 * 使用`Regex.quoteReplacement`来转义这些字符。
 */
@SerialVersionUID(-2094783597747625537L)
class Regex private[matching](val pattern: Pattern, groupNames: String*) extends Serializable {
  outer =>

  import Regex._

  /** 编译正则表达式字符串成可匹配输入的模式。
   *
   * 如果提供了组名,则可以使用如下方式:
   *
   * {{{
   * val namedDate  = new Regex("""(\d\d\d\d)-(\d\d)-(\d\d)""", "year", "month", "day")
   * val namedYears = for (m <- namedDate findAllMatchIn dates) yield m group "year"
   * }}}
   *
   * 在提供了组名的情况下,优先使用构造函数中提供的组名来提取匹配的组。
   * 并非所有平台都支持内联名称。
   *
   * 此构造函数不支持选项作为标志,而必须以模式字符串中的内联标志形式提供:`(?idmsux-idmsux)`。
   *
   * @param regex      要编译的正则表达式。
   * @param groupNames 捕获组的名称。
   */
  def this(regex: String, groupNames: String*) = this(Pattern.compile(regex), groupNames: _*)

  /** 尝试匹配[[java.lang.CharSequence]]。
   *
   * 如果匹配成功,则结果是匹配组的列表(如果组没有匹配任何输入,则为`null`元素)。
   * 如果模式未指定组,则在成功匹配时结果将是一个空列表。
   *
   * 默认情况下,此方法尝试匹配整个输入;要查找下一个匹配的子序列,使用未锚定的`Regex`。
   *
   * 例如:
   *
   * {{{
   * val p1 = "ab*c".r
   * val p1Matches = "abbbc" match {
   *   case p1() => true               // 没有组
   *   case _    => false
   * }
   * val p2 = "a(b*)c".r
   * val p2Matches = "abbbc" match {
   *   case p2(_*) => true             // 任意组
   *   case _      => false
   * }
   * val numberOfB = "abbbc" match {
   *   case p2(b) => Some(b.length)    // 一个组
   *   case _     => None
   * }
   * val p3 = "b*".r.unanchored
   * val p3Matches = "abbbc" match {
   *   case p3() => true               // 查找b's
   *   case _    => false
   * }
   * val p4 = "a(b*)(c+)".r
   * val p4Matches = "abbbcc" match {
   *   case p4(_*) => true             // 多个组
   *   case _      => false
   * }
   * val allGroups = "abbbcc" match {
   *   case p4(all @ _*) => all mkString "/" // "bbb/cc"
   *   case _            => ""
   * }
   * val cGroup = "abbbcc" match {
   *   case p4(_, c) => c
   *   case _        => ""
   * }
   * }}}
   *
   * @param s 要匹配的字符串
   * @return 匹配项
   */
  def unapplySeq(s: CharSequence): Option[List[String]] = s match {
    case null => None
    case _    =>
      val m = pattern matcher s
      if (runMatcher(m)) Regex.extractGroupsFromMatcher(m)
      else None
  }

  /** 尝试匹配一个[[scala.Char]]的字符串表示形式。
   *
   * 如果匹配成功,则结果是第一个匹配组(如果定义了任何组),否则为空序列。
   *
   * 例如:
   *
   * {{{
   * val cat = "cat"
   * // 案例必须消耗组以进行匹配
   * val r = """(\p{Lower})""".r
   * cat(0) match { case r(x) => true }
   * cat(0) match { case r(_) => true }
   * cat(0) match { case r(_*) => true }
   * cat(0) match { case r() => true }     // 无匹配项
   *
   * // 没有要提取的组
   * val r = """\p{Lower}""".r
   * cat(0) match { case r(x) => true }    // 无匹配项
   * cat(0) match { case r(_) => true }    // 无匹配项
   * cat(0) match { case r(_*) => true }   // 匹配
   * cat(0) match { case r() => true }     // 匹配
   *
   * // 即使有多个组,只返回一个
   * val r = """((.))""".r
   * cat(0) match { case r(_) => true }    // 匹配
   * cat(0) match { case r(_,_) => true }  // 无匹配项
   * }}}
   *
   * @param c 要匹配的Char
   * @return 匹配项
   */
  def unapplySeq(c: Char): Option[List[Char]] = {
    val m = pattern matcher c.toString
    if (runMatcher(m)) {
      if (m.groupCount > 0) Some((m group 1).toList) else Some(Nil)
    } else None
  }

  /** 尝试在一个[[scala.util.matching.Regex.Match]]上进行匹配。
   *
   * 先前失败的匹配结果为`None`。
   *
   * 如果成功匹配了当前模式,则使用该结果。
   *
   * 否则,将此`Regex`应用于先前匹配的输入,并使用该匹配的结果。
   */
  def unapplySeq(m: Match): Option[List[String]] =
    if (m == null || m.matched == null) None
    else if (m.matcher.pattern == this.pattern) Regex.extractGroupsFromMatch(m)
    else unapplySeq(m.matched)

  /** 尝试匹配目标。
   * @param target 要匹配的字符串
   * @return 匹配项
   */
  @deprecated("extracting a match result from anything but a CharSequence or Match is deprecated", "2.11.0")
  def unapplySeq(target: Any): Option[List[String]] = target match {
    case s: CharSequence =>
      val m = pattern matcher s
      if (runMatcher(m)) Regex.extractGroupsFromMatcher(m)
      else None
    case m: Match => unapplySeq(m.matched)
    case _ => None
  }

  // @see UnanchoredRegex
  protected def runMatcher(m: Matcher) = m.matches()

  /** 将此`Regex`在给定字符序列中找到的所有非重叠匹配项作为[[scala.util.matching.Regex.MatchIterator]]返回,
   * 它是一个特殊的[[scala.collection.Iterator]],返回匹配的字符串,
   * 但也可以查询有关最后一次匹配的更多数据,例如捕获组和起始位置。
   *
   * `MatchIterator`也可以转换为返回类型为[[scala.util.matching.Regex.Match]]的迭代器,
   * 这样通常由`findAllMatchIn`返回。
   *
   * 在潜在的匹配重叠的情况下,首先返回第一个可能的匹配,然后返回紧接着第一个匹配所消耗的输入的下一个匹配:
   *
   * {{{
   * val hat  = "hat[^a]+".r
   * val hathaway = "hathatthattthatttt"
   * val hats = hat.findAllIn(hathaway).toList                     // List(hath, hattth)
   * val pos  = hat.findAllMatchIn(hathaway).map(_.start).toList   // List(0, 7)
   * }}}
   *
   * 要返回重叠的匹配项,可以使用具有前瞻(`?=`)的正向预测断言制定一个不消耗重叠区域的正则表达式。
   *
   * {{{
   * val madhatter = "(h)(?=(at[^a]+))".r
   * val madhats   = madhatter.findAllMatchIn(hathaway).map {
   *   case madhatter(x,y) => s"\$x\$y"
   * }.toList                                       // List(hath, hatth, hattth, hatttt)
   * }}}
   *
   * 在耗尽迭代器后尝试检索匹配信息会导致[[java.lang.IllegalStateException]]。
   * 有关详细信息,请参见[[scala.util.matching.Regex.MatchIterator]]。
   *
   * @param source 要匹配的文本。
   * @return 匹配子字符串的[[scala.util.matching.Regex.MatchIterator]]。
   * @example {{{for (words <- """\w+""".r findAllIn "A simple example.") yield words}}}
   */
  def findAllIn(source: CharSequence) = new Regex.MatchIterator(source, this, groupNames)

  /** 返回在给定字符序列中该正则表达式的所有非重叠匹配项作为
   * [[scala.collection.Iterator]],其元素类型为[[scala.util.matching.Regex.Match]]。
   *
   * @param source 要匹配的文本。
   * @return 所有匹配项的[[scala.collection.Iterator]],元素类型为[[scala.util.matching.Regex.Match]]。
   * @example {{{for (words <- """\w+""".r findAllMatchIn "A simple example.") yield words.start}}}
   */
  def findAllMatchIn(source: CharSequence): Iterator[Match] = {
    val matchIterator = findAllIn(source)
    new Iterator[Match] {
      def hasNext = matchIterator.hasNext
      def next: Match = {
        matchIterator.next()
        new Match(matchIterator.source, matchIterator.matcher, matchIterator.groupNames).force
      }
    }
  }
}
 
  /**
   * 返回给定字符序列中此`Regex`的第一个匹配字符串的可选值,如果没有匹配则返回None。
   *
   * @param source 要匹配的文本。
   * @return 包含文本中第一个匹配字符串的[[scala.Option]]。
   * @example {{{"""\w+""".r findFirstIn "A simple example." foreach println // 输出 "A"}}}
   */
  def findFirstIn(source: CharSequence): Option[String] = {
    val m = pattern.matcher(source)
    if (m.find) Some(m.group) else None
  }

  /**
   * 返回给定字符序列中此`Regex`的第一个匹配项,如果不存在则返回None。
   *
   * 如果匹配成功,则可以查询[[scala.util.matching.Regex.Match]]以获取更多数据。
   *
   * @param source 要匹配的文本。
   * @return 包含文本中第一个匹配字符串的[[scala.Option]]。
   * @example {{{("""[a-z]""".r findFirstMatchIn "A simple example.") map (_.start) // 返回 Some(2),表示文本中第一个匹配项的索引}}}
   */
  def findFirstMatchIn(source: CharSequence): Option[Match] = {
    val m = pattern.matcher(source)
    if (m.find) Some(new Match(source, m, groupNames)) else None
  }

  /**
   * 返回给定字符序列中此`Regex`在开头的可选匹配项,如果不匹配任何前缀则返回None。
   *
   * 与`findFirstIn`不同,此方法仅返回输入开头的匹配项。
   *
   * @param source 要匹配的文本。
   * @return 匹配前缀的[[scala.Option]]。
   * @example {{{"""\p{Lower}""".r findPrefixOf "A simple example." // 返回 None,因为文本不以小写字母开头}}}
   */
  def findPrefixOf(source: CharSequence): Option[String] = {
    val m = pattern.matcher(source)
    if (m.lookingAt) Some(m.group) else None
  }

  /**
   * 返回给定字符序列中此`Regex`在开头的可选匹配项,如果不匹配任何前缀则返回None。
   *
   * 与`findFirstMatchIn`不同,此方法仅返回输入开头的匹配项。
   *
   * @param source 要匹配的文本。
   * @return 匹配字符串的[[scala.Option]]。
   * @example {{{"""\w+""".r findPrefixMatchOf "A simple example." map (_.after) // 返回 Some(" simple example.")}}}
   */
  def findPrefixMatchOf(source: CharSequence): Option[Match] = {
    val m = pattern.matcher(source)
    if (m.lookingAt) Some(new Match(source, m, groupNames)) else None
  }

  /**
   * 使用一个字符串替换所有匹配项。
   *
   * $replacementString
   *
   * @param target      要匹配的字符串
   * @param replacement 将替换每个匹配项的字符串
   * @return            结果字符串
   * @example           {{{"""\d+""".r replaceAllIn ("July 15", "<NUMBER>") // 返回 "July <NUMBER>"}}}
   */
  def replaceAllIn(target: CharSequence, replacement: String): String = {
    val m = pattern.matcher(target)
    m.replaceAll(replacement)
  }

  /**
   * 使用替换函数替换所有匹配项。替换函数接受一个[[scala.util.matching.Regex.Match]],以便可以从匹配项中获取其他信息。
   *
   * @param target      要匹配的字符串。
   * @param replacer    将匹配项映射到另一个字符串的函数。
   * @return            替换后的目标字符串。
   */
  def replaceAllIn(target: CharSequence, replacer: Match => String): String = {
    val it = new Regex.MatchIterator(target, this, groupNames).replacementData
    it foreach (md => it replace replacer(md))
    it.replaced
  }

  /**
   * 使用返回[[scala.Option]]的替换函数替换部分匹配项。替换函数接受一个[[scala.util.matching.Regex.Match]],以便可以从匹配项中获取其他信息。
   *
   * @param target      要匹配的字符串。
   * @param replacer    可选地将匹配项映射到另一个字符串的函数。
   * @return            替换后的目标字符串。
   */
  def replaceSomeIn(target: CharSequence, replacer: Match => Option[String]): String = {
    val it = new Regex.MatchIterator(target, this, groupNames).replacementData
    for (matchdata <- it ; replacement <- replacer(matchdata))
      it replace replacement

    it.replaced
  }

  /**
   * 使用一个字符串替换第一个匹配项。
   *
   * $replacementString
   *
   * @param target      要匹配的字符串
   * @param replacement 将替换匹配项的字符串
   * @return            结果字符串
   */
  def replaceFirstIn(target: CharSequence, replacement: String): String = {
    val m = pattern.matcher(target)
    m.replaceFirst(replacement)
  }

  /**
   * 在此正则表达式的匹配项周围拆分提供的字符序列。
   *
   * @param toSplit 要拆分的字符序列
   * @return        根据此正则表达式的匹配项拆分的字符串数组
   */
  def split(toSplit: CharSequence): Array[String] =
    pattern.split(toSplit)

  /**
   * 创建一个具有相同模式但没有对提取器模式的整个字符串进行要求的新Regex。
   *
   * 通常,对`date`进行匹配的行为就好像模式被包含在锚定符中,`"^pattern\$"`。
   *
   * 未锚定的`Regex`的行为就好像这些锚定符已被移除。
   *
   * 注意,此方法实际上不会从模式中删除任何匹配器。
   *
   * 调用`anchored`返回原始的`Regex`。
   *
   * @return        新的未锚定的正则表达式
   */
  def unanchored: UnanchoredRegex = new Regex(pattern, groupNames: _*) with UnanchoredRegex { override def anchored = outer }
  
  /**
   * 返回正则表达式的字符串表示形式。
   */
  def regex: String = pattern.pattern

  /** 正则表达式的字符串表示形式。 */
  override def toString = regex
}

/** 在模式匹配中使用时,返回第一个匹配项的[[Match]]的[[Regex]]。
 *
 * @see [[Regex#unanchored]]
 */
trait UnanchoredRegex extends Regex {
  override protected def runMatcher(m: Matcher) = m.find()
  override def unanchored = this
}

/** 此对象定义了描述正则表达式匹配和辅助对象的内部类。 */
object Regex {

  /** 此类提供了访问匹配详细信息的方法。 */
  trait MatchData {

    /** 基本上包装了平台Matcher。 */
    protected def matcher: Matcher

    /** 匹配源 */
    val source: CharSequence

    /** 组的名称,如果没有定义则为空序列 */
    val groupNames: Seq[String]

    /** 模式中捕获组的数量。
     * (对于给定的成功匹配,其中一些组可能没有匹配任何输入。)
     */
    def groupCount: Int

    /** 第一个匹配字符的索引,如果没有匹配则为-1 */
    def start: Int

    /** 第`i`个组中第一个匹配字符的索引,如果该组没有匹配任何内容则为-1 */
    def start(i: Int): Int

    /** 最后一个匹配字符之后的索引,如果没有匹配则为-1。 */
    def end: Int

    /** 第`i`个组中最后一个匹配字符之后的索引,如果该组没有匹配任何内容则为-1 */
    def end(i: Int): Int

    /** 匹配的字符串,如果没有匹配则为`null` */
    def matched: String =
      if (start >= 0) source.subSequence(start, end).toString
      else null

    /** 第`i`个组中的匹配字符串,如果没有匹配则为`null` */
    def group(i: Int): String =
      if (start(i) >= 0) source.subSequence(start(i), end(i)).toString
      else null

    /** 所有捕获组,即不包括group(0) */
    def subgroups: List[String] = (1 to groupCount).toList map group

    /** 第一个匹配字符之前的字符序列,如果没有匹配则为`null` */
    def before: CharSequence =
      if (start >= 0) source.subSequence(0, start)
      else null

    /** 第`i`个组中第一个匹配字符之前的字符序列,如果该组没有匹配任何内容则为`null` */
    def before(i: Int): CharSequence =
      if (start(i) >= 0) source.subSequence(0, start(i))
      else null

    /** 最后一个匹配字符之后的字符序列,如果没有匹配则为`null` */
    def after: CharSequence =
      if (end >= 0) source.subSequence(end, source.length)
      else null

    /** 第`i`个组中最后一个匹配字符之后的字符序列,如果该组没有匹配任何内容则为`null` */
    def after(i: Int): CharSequence =
      if (end(i) >= 0) source.subSequence(end(i), source.length)
      else null

    private lazy val nameToIndex: Map[String, Int] = Map[String, Int]() ++ ("" :: groupNames.toList).zipWithIndex

    /** 返回具有给定名称的组。
     *
     * 如果提供了显式组名称,则使用显式组名称;否则,查询内部实现以获取内联命名组。
     * 并非所有平台都支持内联组名称。
     *
     * @param id 组名称
     * @return   请求的组
     * @throws   IllegalArgumentException 如果请求的组名称未定义
     */
    def group(id: String): String = (
      if (groupNames.isEmpty)
        matcher group id
      else
        nameToIndex.get(id) match {
          case Some(index) => group(index)
          case None        => matcher group id
        }
    )

    /** 匹配的字符串;等同于`matched.toString`。 */
    override def toString = matched
  }

  /** 提供有关成功匹配的信息。 */
  class Match(val source: CharSequence,
              protected[matching] val matcher: Matcher,
              val groupNames: Seq[String]) extends MatchData {

    /** 第一个匹配字符的索引。 */
    val start = matcher.start

    /** 最后一个匹配字符之后的索引。 */
    val end = matcher.end

    /** 子组的数量。 */
    def groupCount = matcher.groupCount

    private lazy val starts: Array[Int] =
      ((0 to groupCount) map matcher.start).toArray
    private lazy val ends: Array[Int] =
      ((0 to groupCount) map matcher.end).toArray

    /** 第`i`个组中第一个匹配字符的索引。 */
    def start(i: Int) = starts(i)

    /** 第`i`个组中最后一个匹配字符之后的索引。 */
    def end(i: Int) = ends(i)

    /** 匹配本身,强制使用依赖于匹配器的lazy val,以便一旦匹配器被推进,匹配仍然有效。 */
    def force: this.type = { starts; ends; this }
  }

  /** 匹配的提取对象,产生匹配的字符串。
   *
   * 当您对匹配数据不感兴趣时,可以使用此提取对象来帮助编写替换函数。例如:
   *
   * {{{
   * import scala.util.matching.Regex.Match
   * """\w+""".r replaceAllIn ("A simple example.", _ match { case Match(s) => s.toUpperCase })
   * }}}
   */
  object Match {
    def unapply(m: Match): Some[String] = Some(m.matched)
  }

  /** 提取对象,产生匹配中的组。使用此提取对象而不是原始`Regex`可确保不会重新计算匹配。
   *
   * {{{
   * import scala.util.matching.Regex.Groups
   *
   * val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r
   * val text = "The doc spree happened on 2011-07-15."
   * val day = date replaceAllIn(text, _ match { case Groups(_, month, day) => s"\$month/\$day" })
   * }}}
   */
  object Groups {
    def unapplySeq(m: Match): Option[Seq[String]] = {
      if (m.groupCount > 0) extractGroupsFromMatch(m) else None
    }
  }

  private def extractGroupsFromMatch(m: Match): Option[List[String]] = {
    var res = List.empty[String]
    var index = m.groupCount
    while (index > 0) {
      res ::= m.group(index)
      index -= 1
    }
    Some(res)
  }

  private def extractGroupsFromMatcher(m: Matcher): Option[List[String]] = {
    var res = List.empty[String]
    var index = m.groupCount
    while (index > 0) {
      res ::= m.group(index)
      index -= 1
    }
    Some(res)
  }
}
 
  /**
   * 用于遍历正则表达式匹配项的类。
   *
   * 这是一个迭代器,返回匹配的字符串。
   *
   * 关于匹配数据的查询与底层匹配器的当前状态有关,调用`hasNext`或`next`会推进底层匹配器。
   *
   * 当匹配项用尽时,关于匹配数据的查询将抛出[[java.lang.IllegalStateException]]。
   *
   * @see [[java.util.regex.Matcher]]
   */
  class MatchIterator(val source: CharSequence, val regex: Regex, val groupNames: Seq[String])
  extends AbstractIterator[String] with Iterator[String] with MatchData { self =>

    protected[Regex] val matcher = regex.pattern.matcher(source)

    // 0 = 尚未匹配, 1 = 已匹配, 2 = 进入下一个匹配, 3 = 没有更多的匹配
    private[this] var nextSeen = 0

    /** 如果`next`将找到一个匹配项,则返回true。
     * 并且必要时推进底层匹配器;
     * 对于当前匹配数据的查询与底层匹配器相关。
     */
    def hasNext: Boolean = {
      nextSeen match {
        case 0 => nextSeen = if (matcher.find()) 1 else 3
        case 1 => ()
        case 2 => nextSeen = 0 ; hasNext
        case 3 => ()
      }
      nextSeen == 1      // 否则,为3
    }

    /** 返回`source`的下一个匹配的子字符串。
     * 并且必要时推进底层匹配器。
     */
    def next(): String = {
      nextSeen match {
        case 0 => if (!hasNext) throw new NoSuchElementException ; next()
        case 1 => nextSeen = 2
        case 2 => nextSeen = 0 ; next()
        case 3 => throw new NoSuchElementException
      }
      matcher.group
    }

    /** 报告是否为空。*/
    override def toString = super[AbstractIterator].toString

    // 确保我们在匹配状态
    private[this] def ensure(): Unit = nextSeen match {
      case 0 => if (!hasNext) throw new IllegalStateException
      case 1 => ()
      case 2 => ()
      case 3 => throw new IllegalStateException
    }

    /** 第一个匹配字符的索引。*/
    def start: Int = { ensure() ; matcher.start }

    /** 第`i`个组中第一个匹配字符的索引。*/
    def start(i: Int): Int = { ensure() ; matcher.start(i) }

    /** 最后一个匹配字符的索引。*/
    def end: Int = { ensure() ; matcher.end }

    /** 第`i`个组中最后一个匹配字符之后的索引。*/
    def end(i: Int): Int = { ensure() ; matcher.end(i) }

    /** 子组的数量。*/
    def groupCount = { ensure() ; matcher.groupCount }

    /** 转换为返回MatchData元素而不是String的迭代器。*/
    def matchData: Iterator[Match] = new AbstractIterator[Match] {
      def hasNext = self.hasNext
      def next = { self.next(); new Match(source, matcher, groupNames).force }
    }

    /** 转换为返回MatchData元素而不是String的迭代器,并具有替换支持。*/
    private[matching] def replacementData = new AbstractIterator[Match] with Replacement {
      def matcher = self.matcher
      def hasNext = self.hasNext
      def next = { self.next(); new Match(source, matcher, groupNames).force }
    }
  }

  /**
   * 一个特质,能够构建包含替换内容的字符串,假设它有一个匹配器。
   * 可以与迭代器混合使用。
   */
  private[matching] trait Replacement {
    protected def matcher: Matcher

    private val sb = new java.lang.StringBuffer

    def replaced = {
      val newsb = new java.lang.StringBuffer(sb)
      matcher.appendTail(newsb)
      newsb.toString
    }

    def replace(rs: String) = matcher.appendReplacement(sb, rs)
  }

  /** 对正则表达式模式进行字面量引用。
   *
   * 输入中的所有正则表达式元字符在输出中以字面量形式匹配自身。
   *
   * @example {{{List("US\$", "CAN\$").map(Regex.quote).mkString("|").r}}}
   */
  def quote(text: String): String = Pattern quote text

  /** 引用替换字符串以在替换方法中使用。
   *
   * 替换方法对替换字符串中的反斜杠(`\`)和美元符号(`\$`)具有特殊含义,因此它们不会被视为字面量。
   * 此方法转义这些字符,以便可以将生成的字符串用作表示输入字符串的字面量替换。
   *
   * @param text 希望用作字面量替换的字符串。
   * @return 可以将匹配项替换为`text`的字符串。
   * @example {{{"CURRENCY".r.replaceAllIn(input, Regex quoteReplacement "US\$")}}}
   */
  def quoteReplacement(text: String): String = Matcher quoteReplacement text
}
  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Scala 中,@ 符号有多种含义,取决于上下文使用的位置。 1. 注解:在 Scala 中使用 @ 符号表示注解,类似于 Java 中的 @ 符号。注解可以用于类、方法、字段、参数等。例如: ``` class MyClass { @deprecated def myMethod(): Unit = { ... } } ``` 2. 模式匹配中的“绑定模式”:在模式匹配中,@ 符号可以用于绑定匹配成功的子模式的变量。例如: ``` val myList = List(1, 2, 3, 4, 5) myList match { case head :: tail @ List(2, 3, _*) => println(head) // 输出 1 println(tail) // 输出 List(4, 5) case _ => println("No match found") } ``` 3. 类型参数中的“类型投影”:在 Scala 中,@ 符号可以用于类型投影,表示从一个类型中排除另一个类型。例如: ``` trait A { def foo(): Int } trait B { def foo(): String } class C extends A with B { def foo(): Int = 42 def bar(): String = "hello" } def test(c: C): Unit = { val a: A = c val b: B = c val afoo: Int = a.foo() val bfoo: String = b.foo() val cfoo: Int = c.foo() val bbar: String = b.bar() val cbar: String = c.bar() // val abar: String = a.bar() // 编译错误,A 中没有 bar 方法 val b2: B { def foo(): Int } = b // 可以将 B 的 foo 方法的返回类型从 String 转换为 Int // val c2: C { def foo(): String } = c // 编译错误,C 中的 foo 方法的返回类型是 Int,无法转换为 String val c2: C { def foo(): Int } = c // 可以将 C 的 foo 方法的返回类型从 Int 转换为 String } ``` 总的来说,Scala 中的 @ 符号有多种用途,需要根据上下文进行具体理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值