All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scaposer.Parser.scala Maven / Gradle / Ivy

The newest version!
package scaposer

import java.io.Reader
import scala.util.parsing.combinator.JavaTokenParsers

/**  Parses .po file to [[Translation]]s. */
object Parser {
  private val parser = new Parser

  //----------------------------------------------------------------------------
  // We only want to expose parsePo methods, instead of exposing all methods
  // inherited from JavaTokenParsers, in class Parser below.

  def parse(po: CharSequence) = parser.parsePo(po)
  def parse(po: Reader) = parser.parsePo(po)
}

/** See http://www.gnu.org/software/hello/manual/gettext/PO-Files.html */
private class Parser extends JavaTokenParsers {
  /**
   * Removes the first and last quote (") character of strings and concat them.
   */
  private def mergeStrs(quoteds: List[String]): String = {
    val unquoted = quoteds.map { quoted =>
      quoted.substring(1, quoted.length - 1)
    }.mkString

    // Unescape
    unquoted.
      replace("""\n""", "\n").
      replace("""\r""", "\r").
      replace("""\t""", "\t").
      replace("""\\""", "\\").
      replace("""\"""", "\"")
  }

  /**
   * Double quotes (`"`) enclosing a sequence of:
   *
   * - Any character except double quotes, control characters or backslash (`\`)
   * - A backslash followed by a slash, another backslash, or one of the letters
   *    `b`, `f`, `n`, `r` or `t`.
   * - `\` followed by `u` followed by four hexadecimal digits
   */
  private val reStringLiteral: Parser[String] =
    ("\""+"""((\\\")|\p{Space}|\\u[a-fA-F0-9]{4}|[^"\p{Cntrl}\\]|\\[\\/bfnrt])*"""+"\"").r

  // Scala regex is single line by default
  private def comment = rep(regex("^#.*".r))

  private def msgctxt = "msgctxt" ~ rep(reStringLiteral) ^^ {
    case _ ~ quoteds => mergeStrs(quoteds)
  }

  private def msgid = "msgid" ~ rep(reStringLiteral) ^^ {
    case _ ~ quoteds => mergeStrs(quoteds)
  }

  private def msgidPlural = "msgid_plural" ~ rep(reStringLiteral) ^^ {
    case _ ~ quoteds => mergeStrs(quoteds)
  }

  private def msgstr = "msgstr" ~ rep(reStringLiteral) ^^ {
    case _ ~ quoteds => mergeStrs(quoteds)
  }

  private def msgstrN = "msgstr[" ~ wholeNumber ~ "]" ~ rep(reStringLiteral) ^^ {
    case _ ~ number ~ _ ~ quoteds => (number.toInt, mergeStrs(quoteds))
  }

  private def singular =
    (opt(comment) ~ opt(msgctxt) ~
     opt(comment) ~ msgid ~
     opt(comment) ~ msgstr ~ opt(comment)) ^^ {
    case ctxCommentso ~ ctxo ~ singularCommentso ~ singular ~ strCommentso ~ str ~ otherCommentso =>
      SingularTranslation(
        ctxCommentso.getOrElse(Seq.empty),
        ctxo.getOrElse(""),
        singularCommentso.getOrElse(Seq.empty),
        singular,
        strCommentso.getOrElse(Seq.empty),
        str,
        otherCommentso.getOrElse(Seq.empty)
      )
  }

  private def plural =
    (opt(comment) ~ opt(msgctxt) ~
     opt(comment) ~ msgid ~
     opt(comment) ~ msgidPlural ~
     opt(comment) ~ rep(msgstrN) ~ opt(comment)) ^^ {
    case ctxCommentso ~ ctxo ~ singularCommentso ~ singular ~ pluralCommentso ~ plural ~ strsCommentso ~ n_strs ~ otherCommentso =>
      val strs = n_strs.sorted.map { case (n, str) => str }
      PluralTranslation(
        ctxCommentso.getOrElse(Seq.empty),
        ctxo.getOrElse(""),
        singularCommentso.getOrElse(Seq.empty),
        singular,
        pluralCommentso.getOrElse(Seq.empty),
        plural,
        strsCommentso.getOrElse(Seq.empty),
        strs,
        otherCommentso.getOrElse(Seq.empty)
      )
  }

  private def exp = rep(singular | plural)

  //----------------------------------------------------------------------------

  def parsePo(po: CharSequence): Either[ParseFailure, Seq[Translation]] = {
    val parseResult = parseAll(exp, po)
    translationsFromParseResult(parseResult)
  }

  def parsePo(po: Reader): Either[ParseFailure, Seq[Translation]] = {
    val parseResult = parseAll(exp, po)
    translationsFromParseResult(parseResult)
  }

  private def translationsFromParseResult(
    parseResult: ParseResult[List[Translation]]
  ): Either[ParseFailure, Seq[Translation]] = parseResult match {
    case NoSuccess(msg, next) =>
      val errorPos = next.pos
      Left(new ParseFailure(msg, errorPos))

    case Success(translations, _) =>
      Right(translations)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy