1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "mozilla/intl/PluralRules.h"
6 
7 #include "mozilla/intl/ICU4CGlue.h"
8 #include "mozilla/intl/NumberFormat.h"
9 #include "mozilla/intl/NumberRangeFormat.h"
10 #include "mozilla/Utf8.h"
11 #include "mozilla/PodOperations.h"
12 #include "mozilla/Span.h"
13 #include "ScopedICUObject.h"
14 
15 #include "unicode/unum.h"
16 #include "unicode/upluralrules.h"
17 #include "unicode/ustring.h"
18 
19 namespace mozilla::intl {
20 
PluralRules(UPluralRules * & aPluralRules,UniquePtr<NumberFormat> && aNumberFormat,UniquePtr<NumberRangeFormat> && aNumberRangeFormat)21 PluralRules::PluralRules(UPluralRules*& aPluralRules,
22                          UniquePtr<NumberFormat>&& aNumberFormat,
23                          UniquePtr<NumberRangeFormat>&& aNumberRangeFormat)
24     : mPluralRules(aPluralRules),
25       mNumberFormat(std::move(aNumberFormat)),
26       mNumberRangeFormat(std::move(aNumberRangeFormat)) {
27   MOZ_ASSERT(aPluralRules);
28   aPluralRules = nullptr;
29 }
30 
TryCreate(const std::string_view aLocale,const PluralRulesOptions & aOptions)31 Result<UniquePtr<PluralRules>, ICUError> PluralRules::TryCreate(
32     const std::string_view aLocale, const PluralRulesOptions& aOptions) {
33   auto numberFormat =
34       NumberFormat::TryCreate(aLocale, aOptions.ToNumberFormatOptions());
35 
36   if (numberFormat.isErr()) {
37     return Err(numberFormat.unwrapErr());
38   }
39 
40   auto numberRangeFormat = NumberRangeFormat::TryCreate(
41       aLocale, aOptions.ToNumberRangeFormatOptions());
42 
43   if (numberRangeFormat.isErr()) {
44     return Err(numberRangeFormat.unwrapErr());
45   }
46 
47   UErrorCode status = U_ZERO_ERROR;
48   auto pluralType = aOptions.mPluralType == PluralRules::Type::Cardinal
49                         ? UPLURAL_TYPE_CARDINAL
50                         : UPLURAL_TYPE_ORDINAL;
51   UPluralRules* pluralRules = uplrules_openForType(
52       AssertNullTerminatedString(aLocale), pluralType, &status);
53 
54   if (U_FAILURE(status)) {
55     return Err(ToICUError(status));
56   }
57 
58   return UniquePtr<PluralRules>(new PluralRules(
59       pluralRules, numberFormat.unwrap(), numberRangeFormat.unwrap()));
60 }
61 
Select(const double aNumber) const62 Result<PluralRules::Keyword, ICUError> PluralRules::Select(
63     const double aNumber) const {
64   char16_t keyword[MAX_KEYWORD_LENGTH];
65 
66   auto lengthResult = mNumberFormat->selectFormatted(
67       aNumber, keyword, MAX_KEYWORD_LENGTH, mPluralRules);
68 
69   if (lengthResult.isErr()) {
70     return Err(lengthResult.unwrapErr());
71   }
72 
73   return KeywordFromUtf16(Span(keyword, lengthResult.unwrap()));
74 }
75 
SelectRange(double aStart,double aEnd) const76 Result<PluralRules::Keyword, ICUError> PluralRules::SelectRange(
77     double aStart, double aEnd) const {
78   char16_t keyword[MAX_KEYWORD_LENGTH];
79 
80   auto lengthResult = mNumberRangeFormat->selectForRange(
81       aStart, aEnd, keyword, MAX_KEYWORD_LENGTH, mPluralRules);
82 
83   if (lengthResult.isErr()) {
84     return Err(lengthResult.unwrapErr());
85   }
86 
87   return KeywordFromUtf16(Span(keyword, lengthResult.unwrap()));
88 }
89 
Categories() const90 Result<EnumSet<PluralRules::Keyword>, ICUError> PluralRules::Categories()
91     const {
92   UErrorCode status = U_ZERO_ERROR;
93   UEnumeration* enumeration = uplrules_getKeywords(mPluralRules, &status);
94   if (U_FAILURE(status)) {
95     return Err(ToICUError(status));
96   }
97 
98   ScopedICUObject<UEnumeration, uenum_close> closeEnum(enumeration);
99   EnumSet<PluralRules::Keyword> set;
100 
101   while (true) {
102     int32_t keywordLength;
103     const char* keyword = uenum_next(enumeration, &keywordLength, &status);
104     if (U_FAILURE(status)) {
105       return Err(ToICUError(status));
106     }
107 
108     if (!keyword) {
109       break;
110     }
111 
112     set += KeywordFromAscii(Span(keyword, keywordLength));
113   }
114 
115   return set;
116 }
117 
KeywordFromUtf16(Span<const char16_t> aKeyword)118 PluralRules::Keyword PluralRules::KeywordFromUtf16(
119     Span<const char16_t> aKeyword) {
120   static constexpr auto kZero = MakeStringSpan(u"zero");
121   static constexpr auto kOne = MakeStringSpan(u"one");
122   static constexpr auto kTwo = MakeStringSpan(u"two");
123   static constexpr auto kFew = MakeStringSpan(u"few");
124   static constexpr auto kMany = MakeStringSpan(u"many");
125 
126   if (aKeyword == kZero) {
127     return PluralRules::Keyword::Zero;
128   }
129   if (aKeyword == kOne) {
130     return PluralRules::Keyword::One;
131   }
132   if (aKeyword == kTwo) {
133     return PluralRules::Keyword::Two;
134   }
135   if (aKeyword == kFew) {
136     return PluralRules::Keyword::Few;
137   }
138   if (aKeyword == kMany) {
139     return PluralRules::Keyword::Many;
140   }
141 
142   MOZ_ASSERT(aKeyword == MakeStringSpan(u"other"));
143   return PluralRules::Keyword::Other;
144 }
145 
KeywordFromAscii(Span<const char> aKeyword)146 PluralRules::Keyword PluralRules::KeywordFromAscii(Span<const char> aKeyword) {
147   static constexpr auto kZero = MakeStringSpan("zero");
148   static constexpr auto kOne = MakeStringSpan("one");
149   static constexpr auto kTwo = MakeStringSpan("two");
150   static constexpr auto kFew = MakeStringSpan("few");
151   static constexpr auto kMany = MakeStringSpan("many");
152 
153   if (aKeyword == kZero) {
154     return PluralRules::Keyword::Zero;
155   }
156   if (aKeyword == kOne) {
157     return PluralRules::Keyword::One;
158   }
159   if (aKeyword == kTwo) {
160     return PluralRules::Keyword::Two;
161   }
162   if (aKeyword == kFew) {
163     return PluralRules::Keyword::Few;
164   }
165   if (aKeyword == kMany) {
166     return PluralRules::Keyword::Many;
167   }
168 
169   MOZ_ASSERT(aKeyword == MakeStringSpan("other"));
170   return PluralRules::Keyword::Other;
171 }
172 
~PluralRules()173 PluralRules::~PluralRules() {
174   if (mPluralRules) {
175     uplrules_close(mPluralRules);
176     mPluralRules = nullptr;
177   }
178 }
179 
180 }  // namespace mozilla::intl
181