| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /** |
| * Contains a parser for ICU format plural/gender/select format for localized |
| * messages. See extract_to_arb.dart and make_hardcoded_translation.dart. |
| */ |
| library icu_parser; |
| |
| import 'package:intl/src/intl_message.dart'; |
| import 'package:petitparser/petitparser.dart'; |
| |
| /** |
| * This defines a grammar for ICU MessageFormat syntax. Usage is |
| * new ICUParser.message.parse(<string>).value; |
| * The "parse" method will return a Success or Failure object which responds |
| * to "value". |
| */ |
| class ICUParser { |
| get openCurly => char("{"); |
| |
| get closeCurly => char("}"); |
| get quotedCurly => (string("'{'") | string("'}'")).map((x) => x[1]); |
| |
| get icuEscapedText => quotedCurly | twoSingleQuotes; |
| get curly => (openCurly | closeCurly); |
| get notAllowedInIcuText => curly | char("<"); |
| get icuText => notAllowedInIcuText.neg(); |
| get notAllowedInNormalText => char("{"); |
| get normalText => notAllowedInNormalText.neg(); |
| get messageText => (icuEscapedText | icuText) |
| .plus().map((x) => x.join()); |
| get nonIcuMessageText => normalText.plus().map((x) => x.join()); |
| get twoSingleQuotes => string("''").map((x) => "'"); |
| get number => digit().plus().flatten().trim().map(int.parse); |
| get id => (letter() & (word() | char("_")).star()).flatten(); |
| get comma => char(",").trim(); |
| |
| /** |
| * Given a list of possible keywords, return a rule that accepts any of them. |
| * e.g., given ["male", "female", "other"], accept any of them. |
| */ |
| asKeywords(list) => list.map(string).reduce((a, b) => a | b).flatten().trim(); |
| |
| get pluralKeyword => asKeywords( |
| ["=0", "=1", "=2", "zero", "one", "two", "few", "many", "other"]); |
| get genderKeyword => asKeywords( |
| ["female", "male", "other"]); |
| |
| var interiorText = undefined(); |
| |
| get preface => (openCurly & id & comma).map((values) => values[1]); |
| |
| get pluralLiteral => string("plural"); |
| get pluralClause => (pluralKeyword & openCurly & interiorText & closeCurly) |
| .trim().map((result) => [result[0], result[2]]); |
| get plural => |
| preface & pluralLiteral & comma & pluralClause.plus() & closeCurly; |
| get intlPlural => |
| plural.map((values) => new Plural.from(values.first, values[3], null)); |
| |
| get selectLiteral => string("select"); |
| get genderClause => (genderKeyword & openCurly & interiorText & closeCurly) |
| .trim().map((result) => [result[0], result[2]]); |
| get gender => |
| preface & selectLiteral & comma & genderClause.plus() & closeCurly; |
| get intlGender => |
| gender.map((values) => new Gender.from(values.first, values[3], null)); |
| get selectClause => (id & openCurly & interiorText & closeCurly).map( |
| (x) => [x.first, x[2]]); |
| get generalSelect => preface & selectLiteral & comma & |
| selectClause.plus() & closeCurly; |
| get intlSelect => generalSelect.map( |
| (values) => new Select.from(values.first, values[3], null)); |
| |
| get pluralOrGenderOrSelect => intlPlural | intlGender | intlSelect; |
| |
| get contents => pluralOrGenderOrSelect | parameter | messageText; |
| get simpleText => (nonIcuMessageText | parameter | openCurly).plus(); |
| get empty => epsilon().map((_) => ''); |
| |
| get parameter => (openCurly & id & closeCurly).map( |
| (param) => new VariableSubstitution.named(param[1], null)); |
| |
| /** |
| * The primary entry point for parsing. Accepts a string and produces |
| * a parsed representation of it as a Message. |
| */ |
| get message => (pluralOrGenderOrSelect | empty).map((chunk) => |
| Message.from(chunk, null)); |
| |
| /** |
| * Represents an ordinary message, i.e. not a plural/gender/select, although |
| * it may have parameters. |
| */ |
| get nonIcuMessage => (simpleText | empty).map((chunk) => |
| Message.from(chunk, null)); |
| |
| get stuff => (pluralOrGenderOrSelect | empty).map( |
| (chunk) => Message.from(chunk, null)); |
| |
| |
| ICUParser() { |
| // There is a cycle here, so we need the explicit set to avoid |
| // infinite recursion. |
| interiorText.set(contents.plus() | empty); |
| } |
| } |