Collator.js (12168B)
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 /* Portions Copyright Norbert Lindenberg 2011-2012. */ 6 7 /** 8 * Compute an internal properties object from |lazyCollatorData|. 9 */ 10 function resolveCollatorInternals(lazyCollatorData) { 11 assert(IsObject(lazyCollatorData), "lazy data not an object?"); 12 13 var internalProps = std_Object_create(null); 14 15 var Collator = collatorInternalProperties; 16 17 // Step 5. 18 internalProps.usage = lazyCollatorData.usage; 19 20 // Steps 6-7. 21 var collatorIsSorting = lazyCollatorData.usage === "sort"; 22 var localeData = collatorIsSorting 23 ? Collator.sortLocaleData 24 : Collator.searchLocaleData; 25 26 // Compute effective locale. 27 // Step 16. 28 var relevantExtensionKeys = Collator.relevantExtensionKeys; 29 30 // Step 17. 31 var r = ResolveLocale( 32 "Collator", 33 lazyCollatorData.requestedLocales, 34 lazyCollatorData.opt, 35 relevantExtensionKeys, 36 localeData 37 ); 38 39 // Step 18. 40 internalProps.locale = r.locale; 41 42 // Step 19. 43 var collation = r.co; 44 45 // Step 20. 46 if (collation === null) { 47 collation = "default"; 48 } 49 50 // Step 21. 51 internalProps.collation = collation; 52 53 // Step 22. 54 internalProps.numeric = r.kn === "true"; 55 56 // Step 23. 57 internalProps.caseFirst = r.kf; 58 59 // Compute remaining collation options. 60 // Step 25. 61 var s = lazyCollatorData.rawSensitivity; 62 if (s === undefined) { 63 // In theory the default sensitivity for the "search" collator is 64 // locale dependent; in reality the CLDR/ICU default strength is 65 // always tertiary. Therefore use "variant" as the default value for 66 // both collation modes. 67 s = "variant"; 68 } 69 70 // Step 26. 71 internalProps.sensitivity = s; 72 73 // Step 28. 74 var ignorePunctuation = lazyCollatorData.ignorePunctuation; 75 if (ignorePunctuation === undefined) { 76 var actualLocale = collatorActualLocale(r.dataLocale); 77 ignorePunctuation = intl_isIgnorePunctuation(actualLocale); 78 } 79 internalProps.ignorePunctuation = ignorePunctuation; 80 81 // The caller is responsible for associating |internalProps| with the right 82 // object using |setInternalProperties|. 83 return internalProps; 84 } 85 86 /** 87 * Returns an object containing the Collator internal properties of |obj|. 88 */ 89 function getCollatorInternals(obj) { 90 assert(IsObject(obj), "getCollatorInternals called with non-object"); 91 assert( 92 intl_GuardToCollator(obj) !== null, 93 "getCollatorInternals called with non-Collator" 94 ); 95 96 var internals = getIntlObjectInternals(obj); 97 assert( 98 internals.type === "Collator", 99 "bad type escaped getIntlObjectInternals" 100 ); 101 102 // If internal properties have already been computed, use them. 103 var internalProps = maybeInternalProperties(internals); 104 if (internalProps) { 105 return internalProps; 106 } 107 108 // Otherwise it's time to fully create them. 109 internalProps = resolveCollatorInternals(internals.lazyData); 110 setInternalProperties(internals, internalProps); 111 return internalProps; 112 } 113 114 /** 115 * Initializes an object as a Collator. 116 * 117 * This method is complicated a moderate bit by its implementing initialization 118 * as a *lazy* concept. Everything that must happen now, does -- but we defer 119 * all the work we can until the object is actually used as a Collator. This 120 * later work occurs in |resolveCollatorInternals|; steps not noted here occur 121 * there. 122 * 123 * Spec: ECMAScript Internationalization API Specification, 10.1.1. 124 */ 125 function InitializeCollator(collator, locales, options) { 126 assert(IsObject(collator), "InitializeCollator called with non-object"); 127 assert( 128 intl_GuardToCollator(collator) !== null, 129 "InitializeCollator called with non-Collator" 130 ); 131 132 // Lazy Collator data has the following structure: 133 // 134 // { 135 // requestedLocales: List of locales, 136 // usage: "sort" / "search", 137 // opt: // opt object computed in InitializeCollator 138 // { 139 // localeMatcher: "lookup" / "best fit", 140 // co: string matching a Unicode extension type / undefined 141 // kn: true / false / undefined, 142 // kf: "upper" / "lower" / "false" / undefined 143 // } 144 // rawSensitivity: "base" / "accent" / "case" / "variant" / undefined, 145 // ignorePunctuation: true / false / undefined 146 // } 147 // 148 // Note that lazy data is only installed as a final step of initialization, 149 // so every Collator lazy data object has *all* these properties, never a 150 // subset of them. 151 var lazyCollatorData = std_Object_create(null); 152 153 // Step 1. 154 var requestedLocales = CanonicalizeLocaleList(locales); 155 lazyCollatorData.requestedLocales = requestedLocales; 156 157 // Steps 2-3. 158 // 159 // If we ever need more speed here at startup, we should try to detect the 160 // case where |options === undefined| and then directly use the default 161 // value for each option. For now, just keep it simple. 162 if (options === undefined) { 163 options = std_Object_create(null); 164 } else { 165 options = ToObject(options); 166 } 167 168 // Compute options that impact interpretation of locale. 169 // Step 4. 170 var u = GetOption(options, "usage", "string", ["sort", "search"], "sort"); 171 lazyCollatorData.usage = u; 172 173 // Step 8. 174 var opt = NEW_RECORD(); 175 lazyCollatorData.opt = opt; 176 177 // Steps 9-10. 178 var matcher = GetOption( 179 options, 180 "localeMatcher", 181 "string", 182 ["lookup", "best fit"], 183 "best fit" 184 ); 185 opt.localeMatcher = matcher; 186 187 // https://github.com/tc39/ecma402/pull/459 188 var collation = GetOption( 189 options, 190 "collation", 191 "string", 192 undefined, 193 undefined 194 ); 195 if (collation !== undefined) { 196 collation = intl_ValidateAndCanonicalizeUnicodeExtensionType( 197 collation, 198 "collation", 199 "co" 200 ); 201 } 202 opt.co = collation; 203 204 // Steps 11-13. 205 var numericValue = GetOption( 206 options, 207 "numeric", 208 "boolean", 209 undefined, 210 undefined 211 ); 212 if (numericValue !== undefined) { 213 numericValue = numericValue ? "true" : "false"; 214 } 215 opt.kn = numericValue; 216 217 // Steps 14-15. 218 var caseFirstValue = GetOption( 219 options, 220 "caseFirst", 221 "string", 222 ["upper", "lower", "false"], 223 undefined 224 ); 225 opt.kf = caseFirstValue; 226 227 // Compute remaining collation options. 228 // Step 24. 229 var s = GetOption( 230 options, 231 "sensitivity", 232 "string", 233 ["base", "accent", "case", "variant"], 234 undefined 235 ); 236 lazyCollatorData.rawSensitivity = s; 237 238 // Step 27. 239 var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, undefined); 240 lazyCollatorData.ignorePunctuation = ip; 241 242 // Step 29. 243 // 244 // We've done everything that must be done now: mark the lazy data as fully 245 // computed and install it. 246 initializeIntlObject(collator, "Collator", lazyCollatorData); 247 } 248 249 /** 250 * Collator internal properties. 251 * 252 * Spec: ECMAScript Internationalization API Specification, 9.1 and 10.2.3. 253 */ 254 var collatorInternalProperties = { 255 sortLocaleData: collatorSortLocaleData, 256 searchLocaleData: collatorSearchLocaleData, 257 relevantExtensionKeys: ["co", "kf", "kn"], 258 }; 259 260 /** 261 * Returns the actual locale used when a collator for |locale| is constructed. 262 */ 263 function collatorActualLocale(locale) { 264 assert(typeof locale === "string", "locale should be string"); 265 266 // If |locale| is the default locale (e.g. da-DK), but only supported 267 // through a fallback (da), we need to get the actual locale before we 268 // can call intl_isUpperCaseFirst. Also see intl_BestAvailableLocale. 269 return BestAvailableLocaleIgnoringDefault("Collator", locale); 270 } 271 272 /** 273 * Returns the default caseFirst values for the given locale. The first 274 * element in the returned array denotes the default value per ES2017 Intl, 275 * 9.1 Internal slots of Service Constructors. 276 */ 277 function collatorSortCaseFirst(locale) { 278 var actualLocale = collatorActualLocale(locale); 279 if (intl_isUpperCaseFirst(actualLocale)) { 280 return ["upper", "false", "lower"]; 281 } 282 283 // Default caseFirst values for all other languages. 284 return ["false", "lower", "upper"]; 285 } 286 287 /** 288 * Returns the default caseFirst value for the given locale. 289 */ 290 function collatorSortCaseFirstDefault(locale) { 291 var actualLocale = collatorActualLocale(locale); 292 if (intl_isUpperCaseFirst(actualLocale)) { 293 return "upper"; 294 } 295 296 // Default caseFirst value for all other languages. 297 return "false"; 298 } 299 300 function collatorSortLocaleData() { 301 /* eslint-disable object-shorthand */ 302 return { 303 co: intl_availableCollations, 304 kn: function() { 305 return ["false", "true"]; 306 }, 307 kf: collatorSortCaseFirst, 308 default: { 309 co: function() { 310 // The first element of the collations array must be |null| 311 // per ES2017 Intl, 10.2.3 Internal Slots. 312 return null; 313 }, 314 kn: function() { 315 return "false"; 316 }, 317 kf: collatorSortCaseFirstDefault, 318 }, 319 }; 320 /* eslint-enable object-shorthand */ 321 } 322 323 function collatorSearchLocaleData() { 324 /* eslint-disable object-shorthand */ 325 return { 326 co: function() { 327 return [null]; 328 }, 329 kn: function() { 330 return ["false", "true"]; 331 }, 332 kf: function() { 333 return ["false", "lower", "upper"]; 334 }, 335 default: { 336 co: function() { 337 return null; 338 }, 339 kn: function() { 340 return "false"; 341 }, 342 kf: function() { 343 return "false"; 344 }, 345 }, 346 }; 347 /* eslint-enable object-shorthand */ 348 } 349 350 /** 351 * Create function to be cached and returned by Intl.Collator.prototype.compare. 352 * 353 * Spec: ECMAScript Internationalization API Specification, 10.3.3.1. 354 */ 355 function createCollatorCompare(collator) { 356 // This function is not inlined in $Intl_Collator_compare_get to avoid 357 // creating a call-object on each call to $Intl_Collator_compare_get. 358 return function(x, y) { 359 // Step 1 (implicit). 360 361 // Step 2. 362 assert(IsObject(collator), "collatorCompareToBind called with non-object"); 363 assert( 364 intl_GuardToCollator(collator) !== null, 365 "collatorCompareToBind called with non-Collator" 366 ); 367 368 // Steps 3-6 369 var X = ToString(x); 370 var Y = ToString(y); 371 372 // Step 7. 373 return intl_CompareStrings(collator, X, Y); 374 }; 375 } 376 377 /** 378 * Returns a function bound to this Collator that compares x (converted to a 379 * String value) and y (converted to a String value), 380 * and returns a number less than 0 if x < y, 0 if x = y, or a number greater 381 * than 0 if x > y according to the sort order for the locale and collation 382 * options of this Collator object. 383 * 384 * Spec: ECMAScript Internationalization API Specification, 10.3.3. 385 */ 386 // Uncloned functions with `$` prefix are allocated as extended function 387 // to store the original name in `SetCanonicalName`. 388 function $Intl_Collator_compare_get() { 389 // Step 1. 390 var collator = this; 391 392 // Steps 2-3. 393 if ( 394 !IsObject(collator) || 395 (collator = intl_GuardToCollator(collator)) === null 396 ) { 397 return callFunction( 398 intl_CallCollatorMethodIfWrapped, 399 this, 400 "$Intl_Collator_compare_get" 401 ); 402 } 403 404 var internals = getCollatorInternals(collator); 405 406 // Step 4. 407 if (internals.boundCompare === undefined) { 408 // Steps 4.a-c. 409 internals.boundCompare = createCollatorCompare(collator); 410 } 411 412 // Step 5. 413 return internals.boundCompare; 414 } 415 SetCanonicalName($Intl_Collator_compare_get, "get compare"); 416 417 /** 418 * Returns the resolved options for a Collator object. 419 * 420 * Spec: ECMAScript Internationalization API Specification, 10.3.4. 421 */ 422 function Intl_Collator_resolvedOptions() { 423 // Step 1. 424 var collator = this; 425 426 // Steps 2-3. 427 if ( 428 !IsObject(collator) || 429 (collator = intl_GuardToCollator(collator)) === null 430 ) { 431 return callFunction( 432 intl_CallCollatorMethodIfWrapped, 433 this, 434 "Intl_Collator_resolvedOptions" 435 ); 436 } 437 438 var internals = getCollatorInternals(collator); 439 440 // Steps 4-5. 441 var result = { 442 locale: internals.locale, 443 usage: internals.usage, 444 sensitivity: internals.sensitivity, 445 ignorePunctuation: internals.ignorePunctuation, 446 collation: internals.collation, 447 numeric: internals.numeric, 448 caseFirst: internals.caseFirst, 449 }; 450 451 // Step 6. 452 return result; 453 }