Как отделить местозаполнители от текста, не выбрасывая меч, чтобы вы могли отбиться от мародеров абажуром - PullRequest
5 голосов
/ 30 июля 2010

Мне нужно было проанализировать заполнители из текста, например abc $$FOO$$ cba.Я взломал что-то вместе с комбинаторами парсера Scala, но я не очень доволен решением.

В частности, я прибегнул к сопоставителю нулевой ширины в регулярном выражении (?=(\$\$|\z)), чтобы прекратить синтаксический анализ текста.и начать разбор заполнителей.Это звучит опасно близко к махинациям, которые обсуждались и красочно игнорировались в списке рассылки scala (который вдохновил название этого вопроса).

Итак, проблема: исправить мой парсер, чтобы он работал без этогохак.Я хотел бы видеть четкое продвижение от проблемы к вашему решению, чтобы я мог заменить свою стратегию случайной сборки комбинаторов до тех пор, пока тесты не пройдут.

import scala.util.parsing.combinator.RegexParsers

object PlaceholderParser extends RegexParsers {
  sealed abstract class Element
  case class Text(text: String) extends Element
  case class Placeholder(key: String) extends Element

  override def skipWhitespace = false

  def parseElements(text: String): List[Element] = parseAll(elements, text) match {
    case Success(es, _) => es
    case NoSuccess(msg, _) => error("Could not parse: [%s]. Error: %s".format(text, msg))
  }

  def parseElementsOpt(text: String): ParseResult[List[Element]] = parseAll(elements, text)

  lazy val elements: Parser[List[Element]] = rep(element)
  lazy val element: Parser[Element] = placeholder ||| text
  lazy val text: Parser[Text] = """(?ims).+?(?=(\$\$|\z))""".r ^^ Text.apply
  lazy val placeholder: Parser[Placeholder] = delimiter ~> """[\w. ]+""".r <~ delimiter ^^ Placeholder.apply
  lazy val delimiter: Parser[String] = literal("$$")
}


import org.junit.{Assert, Test}

class PlaceholderParserTest {
  @Test
  def parse1 = check("a quick brown $$FOX$$ jumped over the lazy $$DOG$$")(Text("a quick brown "), Placeholder("FOX"), Text(" jumped over the lazy "), Placeholder("DOG"))

  @Test
  def parse2 = check("a quick brown $$FOX$$!")(Text("a quick brown "), Placeholder("FOX"), Text("!"))

  @Test
  def parse3 = check("a quick brown $$FOX$$!\n!")(Text("a quick brown "), Placeholder("FOX"), Text("!\n!"))

  @Test
  def parse4 = check("a quick brown $$F.O X$$")(Text("a quick brown "), Placeholder("F.O X"))

  def check(text: String)(expected: Element*) = Assert.assertEquals(expected.toList, parseElements(text))
}

1 Ответ

2 голосов
/ 30 июля 2010

Я нашел другой подход. Больше нет взлома регулярных выражений, но код немного длиннее. Он анализирует всю строку в виде списка отдельных символов или Placeholder объектов. Затем функция compact сжимает список (то есть преобразует последовательные строки в объекты Text и не касается объектов Placeholder):

object PlaceholderParser extends RegexParsers {
  sealed abstract class Element
  case class Text(text: String) extends Element
  case class Placeholder(key: String) extends Element

  override def skipWhitespace = false

  def parseElements(text: String): List[Element] = parseAll(elements, text) match {
    case Success(es, _) => es
    case NoSuccess(msg, _) => error("Could not parse: [%s]. Error: %s".format(text, msg))
  }

  def parseElementsOpt(text: String): ParseResult[List[Element]] = parseAll(elements, text)

  def compact(l: List[Any]): List[Element] = {
    val builder = new StringBuilder()
    val r = l.foldLeft(List.empty[Element])((l, e) => e match {
      case s: String =>
        builder.append(s)
        l
      case p: Placeholder =>
        val t = if (builder.size > 0) {
          val k = l ++ List(Text(builder.toString))
          builder.clear
          k
        } else {
          l
        }
        t ++ List(p)
    })
    if (builder.size > 0) r ++ List(Text(builder.toString)) else r
  }

  lazy val elements: Parser[List[Element]] = (placeholder ||| text).+ ^^ compact
  lazy val text: Parser[String] = """(?ims).""".r
  lazy val placeholder: Parser[Placeholder] = delimiter ~> """[\w. ]+""".r <~ delimiter ^^ Placeholder.apply
  lazy val delimiter: Parser[String] = literal("$$")
}

Это не идеальное решение, но, возможно, вы можете начать с него.

...