Issue #2259 - process Unicode langtags and locale identifiers according to BCP 47

Major spec change: text references are to BCP47 (not the implementing RFCs) and
the single source of truth is now Unicode CLDR.

- Switch from IANA to CLDR for make_unicode
- Update grandfathered tag handling directly in the parser
- Don't support extlang, irregular, privateuse or 4-letter subtags
- Adjust comments to refer to Unicode BCP 47 locale identifiers, remove RFC 5646
- Canonicalize/order langtags correctly
- Tokenize BCP47 in reusable class

Based-on: m-c 1407674(partial), 1451082, 1530320, 1522070, 1531091
This commit is contained in:
Martok 2023-06-29 23:07:20 +02:00 committed by roytam1
commit 3ee2c9dcf1
12 changed files with 3331 additions and 1321 deletions

View file

@ -974,8 +974,7 @@ IsTrailSurrogateWithLeadSurrogate(JSContext* cx, HandleLinearString input, int32
*/
static RegExpRunStatus
ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
int32_t lastIndex,
MatchPairs* matches, size_t* endIndex, RegExpStaticsUpdate staticsUpdate)
int32_t lastIndex, MatchPairs* matches, size_t* endIndex)
{
/*
* WARNING: Despite the presence of spec step comment numbers, this
@ -990,14 +989,9 @@ ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
if (!RegExpObject::getShared(cx, reobj, &re))
return RegExpRunStatus_Error;
RegExpStatics* res;
if (staticsUpdate == UpdateRegExpStatics) {
res = GlobalObject::getRegExpStatics(cx, cx->global());
if (!res)
return RegExpRunStatus_Error;
} else {
res = nullptr;
}
RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global());
if (!res)
return RegExpRunStatus_Error;
RootedLinearString input(cx, string->ensureLinear(cx));
if (!input)
@ -1051,15 +1045,14 @@ ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string,
* steps 3, 9-25, except 12.a.i, 12.c.i.1, 15.
*/
static bool
RegExpMatcherImpl(JSContext* cx, HandleObject regexp, HandleString string,
int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, MutableHandleValue rval)
RegExpMatcherImpl(JSContext* cx, HandleObject regexp, HandleString string, int32_t lastIndex,
MutableHandleValue rval)
{
/* Execute regular expression and gather matches. */
ScopedMatchPairs matches(&cx->tempLifoAlloc());
/* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
&matches, nullptr, staticsUpdate);
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex, &matches, nullptr);
if (status == RegExpRunStatus_Error)
return false;
@ -1099,8 +1092,7 @@ js::RegExpMatcher(JSContext* cx, unsigned argc, Value* vp)
return false;
/* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */
return RegExpMatcherImpl(cx, regexp, string, lastIndex,
UpdateRegExpStatics, args.rval());
return RegExpMatcherImpl(cx, regexp, string, lastIndex, args.rval());
}
/*
@ -1123,8 +1115,7 @@ js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
return false;
return CreateRegExpMatchResult(cx, *shared, input, *maybeMatches, output);
}
return RegExpMatcherImpl(cx, regexp, input, lastIndex,
UpdateRegExpStatics, output);
return RegExpMatcherImpl(cx, regexp, input, lastIndex, output);
}
/*
@ -1135,14 +1126,13 @@ js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
*/
static bool
RegExpSearcherImpl(JSContext* cx, HandleObject regexp, HandleString string,
int32_t lastIndex, RegExpStaticsUpdate staticsUpdate, int32_t* result)
int32_t lastIndex, int32_t* result)
{
/* Execute regular expression and gather matches. */
ScopedMatchPairs matches(&cx->tempLifoAlloc());
/* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
&matches, nullptr, staticsUpdate);
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex, &matches, nullptr);
if (status == RegExpRunStatus_Error)
return false;
@ -1180,7 +1170,7 @@ js::RegExpSearcher(JSContext* cx, unsigned argc, Value* vp)
/* Steps 3, 9-25, except 12.a.i, 12.c.i.1, 15. */
int32_t result = 0;
if (!RegExpSearcherImpl(cx, regexp, string, lastIndex, UpdateRegExpStatics, &result))
if (!RegExpSearcherImpl(cx, regexp, string, lastIndex, &result))
return false;
args.rval().setInt32(result);
@ -1203,23 +1193,7 @@ js::RegExpSearcherRaw(JSContext* cx, HandleObject regexp, HandleString input,
*result = CreateRegExpSearchResult(cx, *maybeMatches);
return true;
}
return RegExpSearcherImpl(cx, regexp, input, lastIndex,
UpdateRegExpStatics, result);
}
bool
js::regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(IsRegExpObject(args[0]));
MOZ_ASSERT(args[1].isString());
RootedObject regexp(cx, &args[0].toObject());
RootedString string(cx, args[1].toString());
return RegExpMatcherImpl(cx, regexp, string, 0,
DontUpdateRegExpStatics, args.rval());
return RegExpSearcherImpl(cx, regexp, input, lastIndex, result);
}
/*
@ -1245,8 +1219,7 @@ js::RegExpTester(JSContext* cx, unsigned argc, Value* vp)
/* Steps 3, 9-14, except 12.a.i, 12.c.i.1. */
size_t endIndex = 0;
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex,
nullptr, &endIndex, UpdateRegExpStatics);
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, lastIndex, nullptr, &endIndex);
if (status == RegExpRunStatus_Error)
return false;
@ -1271,8 +1244,7 @@ js::RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input,
MOZ_ASSERT(lastIndex >= 0);
size_t endIndexTmp = 0;
RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, lastIndex,
nullptr, &endIndexTmp, UpdateRegExpStatics);
RegExpRunStatus status = ExecuteRegExp(cx, regexp, input, lastIndex, nullptr, &endIndexTmp);
if (status == RegExpRunStatus_Success) {
MOZ_ASSERT(endIndexTmp <= INT32_MAX);
@ -1287,24 +1259,6 @@ js::RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input,
return false;
}
bool
js::regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(IsRegExpObject(args[0]));
MOZ_ASSERT(args[1].isString());
RootedObject regexp(cx, &args[0].toObject());
RootedString string(cx, args[1].toString());
size_t ignored = 0;
RegExpRunStatus status = ExecuteRegExp(cx, regexp, string, 0,
nullptr, &ignored, DontUpdateRegExpStatics);
args.rval().setBoolean(status == RegExpRunStatus_Success);
return status != RegExpRunStatus_Error;
}
static void
GetParen(JSLinearString* matched, const JS::Value& capture, JSSubString* out)
{

View file

@ -18,10 +18,6 @@ namespace js {
JSObject*
InitRegExpClass(JSContext* cx, HandleObject obj);
// Whether RegExp statics should be updated with the input and results of a
// regular expression execution.
enum RegExpStaticsUpdate { UpdateRegExpStatics, DontUpdateRegExpStatics };
/*
* Legacy behavior of ExecuteRegExp(), which is baked into the JSAPI.
*
@ -71,22 +67,6 @@ intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp);
* The following functions are for use by self-hosted code.
*/
/*
* Behaves like regexp.exec(string), but doesn't set RegExp statics.
*
* Usage: match = regexp_exec_no_statics(regexp, string)
*/
extern MOZ_MUST_USE bool
regexp_exec_no_statics(JSContext* cx, unsigned argc, Value* vp);
/*
* Behaves like regexp.test(string), but doesn't set RegExp statics.
*
* Usage: does_match = regexp_test_no_statics(regexp, string)
*/
extern MOZ_MUST_USE bool
regexp_test_no_statics(JSContext* cx, unsigned argc, Value* vp);
/*
* Behaves like RegExp(pattern, flags).
* |pattern| should be a RegExp object, |flags| should be a raw integer value.

View file

@ -80,12 +80,6 @@ MakeConstructible(Record, {});
/********** Abstract operations defined in ECMAScript Language Specification **********/
/* Spec: ECMAScript Language Specification, 5.1 edition, 8.12.6 and 11.8.7 */
function HasProperty(o, p) {
return p in o;
}
/* Spec: ECMAScript Language Specification, 5.1 edition, 9.2 and 11.4.9 */
function ToBoolean(v) {
return !!v;

View file

@ -5,18 +5,6 @@
/********** Intl.Collator **********/
/**
* Mapping from Unicode extension keys for collation to options properties,
* their types and permissible values.
*
* Spec: ECMAScript Internationalization API Specification, 10.1.1.
*/
var collatorKeyMappings = {
kn: {property: "numeric", type: "boolean"},
kf: {property: "caseFirst", type: "string", values: ["upper", "lower", "false"]}
};
/**
* Compute an internal properties object from |lazyCollatorData|.
*/
@ -26,60 +14,49 @@ function resolveCollatorInternals(lazyCollatorData)
var internalProps = std_Object_create(null);
// Step 7.
internalProps.usage = lazyCollatorData.usage;
// Step 8.
var Collator = collatorInternalProperties;
// Step 9.
// Step 5.
internalProps.usage = lazyCollatorData.usage;
// Steps 6-7.
var collatorIsSorting = lazyCollatorData.usage === "sort";
var localeData = collatorIsSorting
? Collator.sortLocaleData
: Collator.searchLocaleData;
// Compute effective locale.
// Step 14.
// Step 16.
var relevantExtensionKeys = Collator.relevantExtensionKeys;
// Step 15.
// Step 17.
var r = ResolveLocale(callFunction(Collator.availableLocales, Collator),
lazyCollatorData.requestedLocales,
lazyCollatorData.opt,
relevantExtensionKeys,
localeData);
// Step 16.
// Step 18.
internalProps.locale = r.locale;
// Steps 17-19.
var key, property, value, mapping;
var i = 0, len = relevantExtensionKeys.length;
while (i < len) {
// Step 19.a.
key = relevantExtensionKeys[i];
if (key === "co") {
// Step 19.b.
property = "collation";
value = r.co === null ? "default" : r.co;
} else {
// Step 19.c.
mapping = collatorKeyMappings[key];
property = mapping.property;
value = r[key];
if (mapping.type === "boolean")
value = value === "true";
}
// Step 19.
var collation = r.co;
// Step 19.d.
internalProps[property] = value;
// Step 20.
if (collation === null)
collation = "default";
// Step 19.e.
i++;
}
// Step 21.
internalProps.collation = collation;
// Step 22.
internalProps.numeric = r.kn === "true";
// Step 23.
internalProps.caseFirst = r.kf;
// Compute remaining collation options.
// Steps 21-22.
// Step 25.
var s = lazyCollatorData.rawSensitivity;
if (s === undefined) {
// In theory the default sensitivity for the "search" collator is
@ -88,14 +65,13 @@ function resolveCollatorInternals(lazyCollatorData)
// both collation modes.
s = "variant";
}
// Step 26.
internalProps.sensitivity = s;
// Step 24.
// Step 28.
internalProps.ignorePunctuation = lazyCollatorData.ignorePunctuation;
// Step 25.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
@ -139,9 +115,6 @@ function InitializeCollator(collator, locales, options) {
assert(IsObject(collator), "InitializeCollator called with non-object");
assert(IsCollator(collator), "InitializeCollator called with non-Collator");
// Steps 1-2 (These steps are no longer required and should be removed
// from the spec; https://github.com/tc39/ecma402/issues/115).;
// Lazy Collator data has the following structure:
//
// {
@ -162,11 +135,11 @@ function InitializeCollator(collator, locales, options) {
// subset of them.
var lazyCollatorData = std_Object_create(null);
// Step 3.
// Step 1.
var requestedLocales = CanonicalizeLocaleList(locales);
lazyCollatorData.requestedLocales = requestedLocales;
// Steps 4-5.
// Steps 2-3.
//
// If we ever need more speed here at startup, we should try to detect the
// case where |options === undefined| and Object.prototype hasn't been
@ -179,38 +152,39 @@ function InitializeCollator(collator, locales, options) {
options = ToObject(options);
// Compute options that impact interpretation of locale.
// Step 6.
// Step 4.
var u = GetOption(options, "usage", "string", ["sort", "search"], "sort");
lazyCollatorData.usage = u;
// Step 10.
// Step 8.
var opt = new Record();
lazyCollatorData.opt = opt;
// Steps 11-12.
// Steps 9-10.
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Step 13, unrolled.
// Steps 11-13.
var numericValue = GetOption(options, "numeric", "boolean", undefined, undefined);
if (numericValue !== undefined)
numericValue = numericValue ? 'true' : 'false';
opt.kn = numericValue;
// Steps 14-15.
var caseFirstValue = GetOption(options, "caseFirst", "string", ["upper", "lower", "false"], undefined);
opt.kf = caseFirstValue;
// Compute remaining collation options.
// Step 20.
// Step 24.
var s = GetOption(options, "sensitivity", "string",
["base", "accent", "case", "variant"], undefined);
lazyCollatorData.rawSensitivity = s;
// Step 23.
// Step 27.
var ip = GetOption(options, "ignorePunctuation", "boolean", undefined, false);
lazyCollatorData.ignorePunctuation = ip;
// Step 26.
// Step 29.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
@ -228,9 +202,14 @@ function InitializeCollator(collator, locales, options) {
function Intl_Collator_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(collatorInternalProperties.availableLocales,
collatorInternalProperties);
// Step 2.
var requestedLocales = CanonicalizeLocaleList(locales);
// Step 3.
return SupportedLocales(availableLocales, requestedLocales, options);
}
@ -353,9 +332,9 @@ function collatorSearchLocaleData() {
/**
* Function to be bound and returned by Intl.Collator.prototype.format.
* Function to be bound and returned by Intl.Collator.prototype.compare.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
* Spec: ECMAScript Internationalization API Specification, 10.3.3.1.
*/
function collatorCompareToBind(x, y) {
// Steps 1.a.i-ii implemented by ECMAScript declaration binding instantiation,
@ -375,26 +354,28 @@ function collatorCompareToBind(x, y) {
* than 0 if x > y according to the sort order for the locale and collation
* options of this Collator object.
*
* Spec: ECMAScript Internationalization API Specification, 10.3.2.
* Spec: ECMAScript Internationalization API Specification, 10.3.3.
*/
function Intl_Collator_compare_get() {
// Check "this Collator object" per introduction of section 10.3.
if (!IsObject(this) || !IsCollator(this))
// Step 1.
var collator = this;
// Steps 2-3.
if (!IsObject(collator) || !IsCollator(collator))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "Collator", "compare", "Collator");
var internals = getCollatorInternals(this);
var internals = getCollatorInternals(collator);
// Step 1.
// Step 4.
if (internals.boundCompare === undefined) {
// Step 1.a.
var F = collatorCompareToBind;
// Steps 4.a-b.
var F = callFunction(FunctionBind, collatorCompareToBind, collator);
// Steps 1.b-d.
var bc = callFunction(FunctionBind, F, this);
internals.boundCompare = bc;
// Step 4.c.
internals.boundCompare = F;
}
// Step 2.
// Step 5.
return internals.boundCompare;
}
_SetCanonicalName(Intl_Collator_compare_get, "get compare");
@ -403,28 +384,30 @@ _SetCanonicalName(Intl_Collator_compare_get, "get compare");
/**
* Returns the resolved options for a Collator object.
*
* Spec: ECMAScript Internationalization API Specification, 10.3.3 and 10.4.
* Spec: ECMAScript Internationalization API Specification, 10.3.4.
*/
function Intl_Collator_resolvedOptions() {
// Check "this Collator object" per introduction of section 10.3.
if (!IsObject(this) || !IsCollator(this))
// Step 1.
var collator = this;
// Steps 2-3.
if (!IsObject(collator) || !IsCollator(collator))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "Collator", "resolvedOptions", "Collator");
var internals = getCollatorInternals(this);
var internals = getCollatorInternals(collator);
// Steps 4-5.
var result = {
locale: internals.locale,
usage: internals.usage,
sensitivity: internals.sensitivity,
ignorePunctuation: internals.ignorePunctuation
ignorePunctuation: internals.ignorePunctuation,
collation: internals.collation,
numeric: internals.numeric,
caseFirst: internals.caseFirst,
};
var relevantExtensionKeys = collatorInternalProperties.relevantExtensionKeys;
for (var i = 0; i < relevantExtensionKeys.length; i++) {
var key = relevantExtensionKeys[i];
var property = (key === "co") ? "collation" : collatorKeyMappings[key].property;
_DefineDataProperty(result, property, internals[property]);
}
// Step 6.
return result;
}

File diff suppressed because it is too large Load diff

View file

@ -53,9 +53,10 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
// never a subset of them.
var internalProps = std_Object_create(null);
var DateTimeFormat = dateTimeFormatInternalProperties;
// Compute effective locale.
var DateTimeFormat = dateTimeFormatInternalProperties;
// Step 10.
var localeData = DateTimeFormat.localeData;
@ -73,7 +74,7 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
internalProps.numberingSystem = r.nu;
// Compute formatting options.
// Step 16.
// Step 14.
var dataLocale = r.dataLocale;
// Steps 20.
@ -119,8 +120,6 @@ function resolveDateTimeFormatInternals(lazyDateTimeFormatData) {
// Step 31.
internalProps.pattern = pattern;
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
@ -297,23 +296,25 @@ function DefaultTimeZone() {
/**
* UnwrapDateTimeFormat(dtf)
* 12.1.10 UnwrapDateTimeFormat( dtf )
*/
function UnwrapDateTimeFormat(dtf, methodName) {
// Step 1.
// Step 1 (not applicable in our implementation).
// Step 2.
if ((!IsObject(dtf) || !IsDateTimeFormat(dtf)) &&
dtf instanceof GetDateTimeFormatConstructor())
{
dtf = dtf[intlFallbackSymbol()];
}
// Step 2.
// Step 3.
if (!IsObject(dtf) || !IsDateTimeFormat(dtf)) {
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "DateTimeFormat", methodName,
"DateTimeFormat");
}
// Step 3.
// Step 4.
return dtf;
}
@ -334,9 +335,6 @@ function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options, m
assert(IsDateTimeFormat(dateTimeFormat),
"InitializeDateTimeFormat called with non-DateTimeFormat");
// Steps 1-2 (These steps are no longer required and should be removed
// from the spec; https://github.com/tc39/ecma402/issues/115).
// Lazy DateTimeFormat data has the following structure:
//
// {
@ -471,6 +469,8 @@ function InitializeDateTimeFormat(dateTimeFormat, thisValue, locales, options, m
initializeIntlObject(dateTimeFormat, "DateTimeFormat", lazyDateTimeFormatData);
// 12.2.1, steps 4-5.
// TODO: spec issue - The current spec doesn't have the IsObject check,
// which means |Intl.DateTimeFormat.call(null)| is supposed to throw here.
if (dateTimeFormat !== thisValue && thisValue instanceof GetDateTimeFormatConstructor()) {
if (!IsObject(thisValue))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue);
@ -687,17 +687,19 @@ function ToDateTimeOptions(options, required, defaults) {
assert(typeof required === "string", "ToDateTimeOptions");
assert(typeof defaults === "string", "ToDateTimeOptions");
// Steps 1-3.
// Steps 1-2.
if (options === undefined)
options = null;
else
options = ToObject(options);
options = std_Object_create(options);
// Step 4.
// Step 3.
var needDefaults = true;
// Step 5.
// Step 4.
// TODO: spec issue - The spec requires to retrieve all options, so using
// the ||-operator with its lazy evaluation semantics is incorrect.
if ((required === "date" || required === "any") &&
(options.weekday !== undefined || options.year !== undefined ||
options.month !== undefined || options.day !== undefined))
@ -705,7 +707,9 @@ function ToDateTimeOptions(options, required, defaults) {
needDefaults = false;
}
// Step 6.
// Step 5.
// TODO: spec issue - The spec requires to retrieve all options, so using
// the ||-operator with its lazy evaluation semantics is incorrect.
if ((required === "time" || required === "any") &&
(options.hour !== undefined || options.minute !== undefined ||
options.second !== undefined))
@ -713,7 +717,7 @@ function ToDateTimeOptions(options, required, defaults) {
needDefaults = false;
}
// Step 7.
// Step 6.
if (needDefaults && (defaults === "date" || defaults === "all")) {
// The specification says to call [[DefineOwnProperty]] with false for
// the Throw parameter, while Object.defineProperty uses true. For the
@ -724,7 +728,7 @@ function ToDateTimeOptions(options, required, defaults) {
_DefineDataProperty(options, "day", "numeric");
}
// Step 8.
// Step 7.
if (needDefaults && (defaults === "time" || defaults === "all")) {
// See comment for step 7.
_DefineDataProperty(options, "hour", "numeric");
@ -732,7 +736,7 @@ function ToDateTimeOptions(options, required, defaults) {
_DefineDataProperty(options, "second", "numeric");
}
// Step 9.
// Step 8.
return options;
}
@ -842,14 +846,19 @@ function BestFitFormatMatcher(options, formats) {
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript Internationalization API Specification, 12.2.2.
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
*/
function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(dateTimeFormatInternalProperties.availableLocales,
dateTimeFormatInternalProperties);
// Step 2.
var requestedLocales = CanonicalizeLocaleList(locales);
// Step 3.
return SupportedLocales(availableLocales, requestedLocales, options);
}
@ -857,7 +866,7 @@ function Intl_DateTimeFormat_supportedLocalesOf(locales /*, options*/) {
/**
* DateTimeFormat internal properties.
*
* Spec: ECMAScript Internationalization API Specification, 9.1 and 12.2.3.
* Spec: ECMAScript Internationalization API Specification, 9.1 and 12.3.3.
*/
var dateTimeFormatInternalProperties = {
localeData: dateTimeFormatLocaleData,
@ -897,7 +906,7 @@ function dateTimeFormatLocaleData() {
/**
* Function to be bound and returned by Intl.DateTimeFormat.prototype.format.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
* Spec: ECMAScript Internationalization API Specification, 12.1.5.
*/
function dateTimeFormatFormatToBind() {
// Steps 1.a.i-ii
@ -913,7 +922,7 @@ function dateTimeFormatFormatToBind() {
* representing the result of calling ToNumber(date) according to the
* effective locale and the formatting options of this DateTimeFormat.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.2.
* Spec: ECMAScript Internationalization API Specification, 12.4.3.
*/
function Intl_DateTimeFormat_format_get() {
// Steps 1-3.
@ -923,12 +932,11 @@ function Intl_DateTimeFormat_format_get() {
// Step 4.
if (internals.boundFormat === undefined) {
// Step 4.a.
var F = dateTimeFormatFormatToBind;
// Steps 4.a-b.
var F = callFunction(FunctionBind, dateTimeFormatFormatToBind, dtf);
// Steps 4.b-d.
var bf = callFunction(FunctionBind, F, dtf);
internals.boundFormat = bf;
// Step 4.c.
internals.boundFormat = F;
}
// Step 5.
@ -937,6 +945,11 @@ function Intl_DateTimeFormat_format_get() {
_SetCanonicalName(Intl_DateTimeFormat_format_get, "get format");
/**
* Intl.DateTimeFormat.prototype.formatToParts ( date )
*
* Spec: ECMAScript Internationalization API Specification, 12.4.4.
*/
function Intl_DateTimeFormat_formatToParts() {
// Steps 1-3.
var dtf = UnwrapDateTimeFormat(this, "formatToParts");
@ -956,14 +969,15 @@ function Intl_DateTimeFormat_formatToParts() {
/**
* Returns the resolved options for a DateTimeFormat object.
*
* Spec: ECMAScript Internationalization API Specification, 12.3.3 and 12.4.
* Spec: ECMAScript Internationalization API Specification, 12.4.5.
*/
function Intl_DateTimeFormat_resolvedOptions() {
// Invoke |UnwrapDateTimeFormat| per introduction of section 12.3.
// Steps 1-3.
var dtf = UnwrapDateTimeFormat(this, "resolvedOptions");
var internals = getDateTimeFormatInternals(dtf);
// Steps 4-5.
var result = {
locale: internals.locale,
calendar: internals.calendar,
@ -981,6 +995,8 @@ function Intl_DateTimeFormat_resolvedOptions() {
}
resolveICUPattern(internals.pattern, result);
// Step 6.
return result;
}

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@
/**
* NumberFormat internal properties.
*
* Spec: ECMAScript Internationalization API Specification, 9.1 and 11.2.3.
* Spec: ECMAScript Internationalization API Specification, 9.1 and 11.3.3.
*/
var numberFormatInternalProperties = {
localeData: numberFormatLocaleData,
@ -35,44 +35,38 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
var internalProps = std_Object_create(null);
// Step 3.
var requestedLocales = lazyNumberFormatData.requestedLocales;
// Compute options that impact interpretation of locale.
// Step 6.
var opt = lazyNumberFormatData.opt;
var NumberFormat = numberFormatInternalProperties;
// Step 9.
// Compute effective locale.
// Step 7.
var localeData = NumberFormat.localeData;
// Step 10.
// Step 8.
var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat),
lazyNumberFormatData.requestedLocales,
lazyNumberFormatData.opt,
NumberFormat.relevantExtensionKeys,
localeData);
// Steps 11-12. (Step 13 is not relevant to our implementation.)
// Steps 9-10. (Step 11 is not relevant to our implementation.)
internalProps.locale = r.locale;
internalProps.numberingSystem = r.nu;
// Compute formatting options.
// Step 15.
// Step 13.
var s = lazyNumberFormatData.style;
internalProps.style = s;
// Steps 19, 21.
// Steps 17, 19.
if (s === "currency") {
internalProps.currency = lazyNumberFormatData.currency;
internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay;
}
// Step 22.
internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits;
internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
if ("minimumSignificantDigits" in lazyNumberFormatData) {
@ -83,12 +77,9 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits;
}
// Step 27.
// Step 24.
internalProps.useGrouping = lazyNumberFormatData.useGrouping;
// Step 34.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
// object using |setInternalProperties|.
return internalProps;
@ -118,19 +109,21 @@ function getNumberFormatInternals(obj) {
/**
* UnwrapNumberFormat(nf)
* 11.1.11 UnwrapNumberFormat( nf )
*/
function UnwrapNumberFormat(nf, methodName) {
// Step 1.
// Step 1 (not applicable in our implementation).
// Step 2.
if ((!IsObject(nf) || !IsNumberFormat(nf)) && nf instanceof GetNumberFormatConstructor()) {
nf = nf[intlFallbackSymbol()];
}
// Step 2.
// Step 3.
if (!IsObject(nf) || !IsNumberFormat(nf))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "NumberFormat", methodName, "NumberFormat");
// Step 3.
// Step 4.
return nf;
}
@ -141,18 +134,18 @@ function UnwrapNumberFormat(nf, methodName) {
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
*/
function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault) {
// We skip Step 1 because we set the properties on a lazyData object.
// We skip step 1 because we set the properties on a lazyData object.
// Step 2-3.
// Steps 2-4.
assert(IsObject(options), "SetNumberFormatDigitOptions");
assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions");
// Steps 4-6.
// Steps 5-8.
const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
const mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
const mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20);
// Steps 7-8.
// Steps 9-10.
let mnsd = options.minimumSignificantDigits;
let mxsd = options.maximumSignificantDigits;
@ -196,17 +189,9 @@ function toASCIIUpperCase(s) {
*
* Spec: ECMAScript Internationalization API Specification, 6.3.1.
*/
function getIsWellFormedCurrencyCodeRE() {
return internalIntlRegExps.isWellFormedCurrencyCodeRE ||
(internalIntlRegExps.isWellFormedCurrencyCodeRE = RegExpCreate("[^A-Z]"));
}
function IsWellFormedCurrencyCode(currency) {
var c = ToString(currency);
var normalized = toASCIIUpperCase(c);
if (normalized.length !== 3)
return false;
return !regexp_test_no_statics(getIsWellFormedCurrencyCodeRE(), normalized);
assert(typeof currency === "string", "currency is a string value");
return currency.length === 3 && IsASCIIAlphaString(currency);
}
/**
@ -218,15 +203,12 @@ function IsWellFormedCurrencyCode(currency) {
* This later work occurs in |resolveNumberFormatInternals|; steps not noted
* here occur there.
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
* Spec: ECMAScript Internationalization API Specification, 11.1.2.
*/
function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
assert(IsObject(numberFormat), "InitializeNumberFormat called with non-object");
assert(IsNumberFormat(numberFormat), "InitializeNumberFormat called with non-NumberFormat");
// Steps 1-2 (These steps are no longer required and should be removed
// from the spec; https://github.com/tc39/ecma402/issues/115).
// Lazy NumberFormat data has the following structure:
//
// {
@ -258,11 +240,11 @@ function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
// subset of them.
var lazyNumberFormatData = std_Object_create(null);
// Step 3.
// Step 1.
var requestedLocales = CanonicalizeLocaleList(locales);
lazyNumberFormatData.requestedLocales = requestedLocales;
// Steps 4-5.
// Steps 2-3.
//
// If we ever need more speed here at startup, we should try to detect the
// case where |options === undefined| and Object.prototype hasn't been
@ -275,20 +257,20 @@ function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
options = ToObject(options);
// Compute options that impact interpretation of locale.
// Step 6.
// Step 4.
var opt = new Record();
lazyNumberFormatData.opt = opt;
// Steps 7-8.
// Steps 5-6.
var matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Compute formatting options.
// Step 14.
// Step 12.
var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
lazyNumberFormatData.style = s;
// Steps 16-19.
// Steps 14-17.
var c = GetOption(options, "currency", "string", undefined, undefined);
if (c !== undefined && !IsWellFormedCurrencyCode(c))
ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, c);
@ -303,12 +285,12 @@ function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
cDigits = CurrencyDigits(c);
}
// Step 20.
// Step 18.
var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol");
if (s === "currency")
lazyNumberFormatData.currencyDisplay = cd;
// Steps 22-24.
// Steps 20-22.
SetNumberFormatDigitOptions(lazyNumberFormatData, options, s === "currency" ? cDigits: 0);
// Step 25.
@ -322,16 +304,19 @@ function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
std_Math_max(lazyNumberFormatData.minimumFractionDigits, mxfdDefault);
}
// Step 26.
// Steps 23.
var g = GetOption(options, "useGrouping", "boolean", undefined, true);
lazyNumberFormatData.useGrouping = g;
// Steps 35-36.
// Step 31.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
initializeIntlObject(numberFormat, "NumberFormat", lazyNumberFormatData);
// 11.2.1, steps 4-5.
// TODO: spec issue - The current spec doesn't have the IsObject check,
// which means |Intl.NumberFormat.call(null)| is supposed to throw here.
if (numberFormat !== thisValue && thisValue instanceof GetNumberFormatConstructor()) {
if (!IsObject(thisValue))
ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, typeof thisValue);
@ -342,6 +327,7 @@ function InitializeNumberFormat(numberFormat, thisValue, locales, options) {
return thisValue;
}
// 11.2.1, step 6.
return numberFormat;
}
@ -386,15 +372,12 @@ var currencyDigits = {
/**
* Returns the number of decimal digits to be used for the given currency.
*
* Spec: ECMAScript Internationalization API Specification, 11.1.1.
* Spec: ECMAScript Internationalization API Specification, 11.1.3.
*/
function getCurrencyDigitsRE() {
return internalIntlRegExps.currencyDigitsRE ||
(internalIntlRegExps.currencyDigitsRE = RegExpCreate("^[A-Z]{3}$"));
}
function CurrencyDigits(currency) {
assert(typeof currency === "string", "CurrencyDigits");
assert(regexp_test_no_statics(getCurrencyDigitsRE(), currency), "CurrencyDigits");
assert(typeof currency === "string", "currency is a string value");
assert(IsWellFormedCurrencyCode(currency), "currency is well-formed");
assert(currency == toASCIIUpperCase(currency), "currency is all upper-case");
if (hasOwn(currency, currencyDigits))
return currencyDigits[currency];
@ -407,14 +390,19 @@ function CurrencyDigits(currency) {
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript Internationalization API Specification, 11.2.2.
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
*/
function Intl_NumberFormat_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 1.
var availableLocales = callFunction(numberFormatInternalProperties.availableLocales,
numberFormatInternalProperties);
// Step 2.
var requestedLocales = CanonicalizeLocaleList(locales);
// Step 3.
return SupportedLocales(availableLocales, requestedLocales, options);
}
@ -427,8 +415,8 @@ function getNumberingSystems(locale) {
// Algorithmic numbering systems are typically tied to one locale, so for
// lack of information we don't offer them. To increase chances that
// other software will process output correctly, we further restrict to
// those decimal numbering systems explicitly listed in table 2 of
// the ECMAScript Internationalization API Specification, 11.3.2, which
// those decimal numbering systems explicitly listed in table 3 of
// the ECMAScript Internationalization API Specification, 11.1.6, which
// in turn are those with full specifications in version 21 of Unicode
// Technical Standard #35 using digits that were defined in Unicode 5.0,
// the Unicode version supported in Windows Vista.
@ -459,7 +447,7 @@ function numberFormatLocaleData() {
/**
* Function to be bound and returned by Intl.NumberFormat.prototype.format.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
* Spec: ECMAScript Internationalization API Specification, 11.1.4.
*/
function numberFormatFormatToBind(value) {
// Steps 1.a.i implemented by ECMAScript declaration binding instantiation,
@ -476,7 +464,7 @@ function numberFormatFormatToBind(value) {
* representing the result of calling ToNumber(value) according to the
* effective locale and the formatting options of this NumberFormat.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.2.
* Spec: ECMAScript Internationalization API Specification, 11.4.3.
*/
function Intl_NumberFormat_format_get() {
// Steps 1-3.
@ -486,12 +474,11 @@ function Intl_NumberFormat_format_get() {
// Step 4.
if (internals.boundFormat === undefined) {
// Step 4.a.
var F = numberFormatFormatToBind;
// Steps 4.a-b.
var F = callFunction(FunctionBind, numberFormatFormatToBind, nf);
// Steps 4.b-d.
var bf = callFunction(FunctionBind, F, nf);
internals.boundFormat = bf;
// Step 4.c.
internals.boundFormat = F;
}
// Step 5.
@ -499,6 +486,9 @@ function Intl_NumberFormat_format_get() {
}
_SetCanonicalName(Intl_NumberFormat_format_get, "get format");
/**
* 11.4.4 Intl.NumberFormat.prototype.formatToParts ( value )
*/
function Intl_NumberFormat_formatToParts(value) {
// Steps 1-3.
var nf = UnwrapNumberFormat(this, "formatToParts");
@ -516,14 +506,15 @@ function Intl_NumberFormat_formatToParts(value) {
/**
* Returns the resolved options for a NumberFormat object.
*
* Spec: ECMAScript Internationalization API Specification, 11.3.3 and 11.4.
* Spec: ECMAScript Internationalization API Specification, 11.4.5.
*/
function Intl_NumberFormat_resolvedOptions() {
// Invoke |UnwrapNumberFormat| per introduction of section 11.3.
// Steps 1-3.
var nf = UnwrapNumberFormat(this, "resolvedOptions");
var internals = getNumberFormatInternals(nf);
// Steps 4-5.
var result = {
locale: internals.locale,
numberingSystem: internals.numberingSystem,
@ -533,17 +524,31 @@ function Intl_NumberFormat_resolvedOptions() {
maximumFractionDigits: internals.maximumFractionDigits,
useGrouping: internals.useGrouping
};
var optionalProperties = [
"currency",
"currencyDisplay",
"minimumSignificantDigits",
"maximumSignificantDigits"
];
for (var i = 0; i < optionalProperties.length; i++) {
var p = optionalProperties[i];
if (hasOwn(p, internals))
_DefineDataProperty(result, p, internals[p]);
// currency and currencyDisplay are only present for currency formatters.
assert(hasOwn("currency", internals) === (internals.style === "currency"),
"currency is present iff style is 'currency'");
assert(hasOwn("currencyDisplay", internals) === (internals.style === "currency"),
"currencyDisplay is present iff style is 'currency'");
if (hasOwn("currency", internals)) {
_DefineDataProperty(result, "currency", internals.currency);
_DefineDataProperty(result, "currencyDisplay", internals.currencyDisplay);
}
// Min/Max significant digits are either both present or not at all.
assert(hasOwn("minimumSignificantDigits", internals) ===
hasOwn("maximumSignificantDigits", internals),
"minimumSignificantDigits is present iff maximumSignificantDigits is present");
if (hasOwn("minimumSignificantDigits", internals)) {
_DefineDataProperty(result, "minimumSignificantDigits",
internals.minimumSignificantDigits);
_DefineDataProperty(result, "maximumSignificantDigits",
internals.maximumSignificantDigits);
}
// Step 6.
return result;
}

View file

@ -79,7 +79,7 @@ static const JSFunctionSpec pluralRules_methods[] = {
/**
* PluralRules constructor.
* Spec: ECMAScript 402 API, PluralRules, 1.1
* Spec: ECMAScript 402 API, PluralRules, 13.2.1
*/
static bool
PluralRules(JSContext* cx, const CallArgs& args, bool construct)

View file

@ -7,7 +7,7 @@
/**
* PluralRules internal properties.
*
* Spec: ECMAScript 402 API, PluralRules, 1.3.3.
* Spec: ECMAScript 402 API, PluralRules, 13.3.3.
*/
var pluralRulesInternalProperties = {
localeData: pluralRulesLocaleData,
@ -44,20 +44,25 @@ function resolvePluralRulesInternals(lazyPluralRulesData) {
var PluralRules = pluralRulesInternalProperties;
// Step 13.
const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules),
lazyPluralRulesData.requestedLocales,
lazyPluralRulesData.opt,
PluralRules.relevantExtensionKeys, PluralRules.localeData);
// Compute effective locale.
// Step 14.
// Step 10.
var localeData = PluralRules.localeData;
// Step 11.
const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules),
lazyPluralRulesData.requestedLocales,
lazyPluralRulesData.opt,
PluralRules.relevantExtensionKeys,
localeData);
// Step 12.
internalProps.locale = r.locale;
// Step 8.
internalProps.type = lazyPluralRulesData.type;
internalProps.pluralCategories = intl_GetPluralCategories(
internalProps.locale,
internalProps.type);
// Step 9.
internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
@ -68,6 +73,9 @@ function resolvePluralRulesInternals(lazyPluralRulesData) {
internalProps.maximumSignificantDigits = lazyPluralRulesData.maximumSignificantDigits;
}
// Step 13 (lazily computed on first access).
internalProps.pluralCategories = null;
return internalProps;
}
@ -99,15 +107,12 @@ function getPluralRulesInternals(obj) {
* This later work occurs in |resolvePluralRulesInternals|; steps not noted
* here occur there.
*
* Spec: ECMAScript 402 API, PluralRules, 1.1.1.
* Spec: ECMAScript 402 API, PluralRules, 13.1.1.
*/
function InitializePluralRules(pluralRules, locales, options) {
assert(IsObject(pluralRules), "InitializePluralRules called with non-object");
assert(IsPluralRules(pluralRules), "InitializePluralRules called with non-PluralRules");
// Steps 1-2 (These steps are no longer required and should be removed
// from the spec; https://github.com/tc39/ecma402/issues/115).
// Lazy PluralRules data has the following structure:
//
// {
@ -133,30 +138,29 @@ function InitializePluralRules(pluralRules, locales, options) {
// subset of them.
const lazyPluralRulesData = std_Object_create(null);
// Step 3.
// Step 1.
let requestedLocales = CanonicalizeLocaleList(locales);
lazyPluralRulesData.requestedLocales = requestedLocales;
// Steps 4-5.
// Steps 2-3.
if (options === undefined)
options = {};
else
options = ToObject(options);
// Step 6.
const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
lazyPluralRulesData.type = type;
// Step 8.
// Step 4.
let opt = new Record();
lazyPluralRulesData.opt = opt;
// Steps 9-10.
// Steps 5-6.
let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
opt.localeMatcher = matcher;
// Step 7.
const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
lazyPluralRulesData.type = type;
// Step 11.
// Step 9.
SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0);
// Step 12.
@ -165,6 +169,10 @@ function InitializePluralRules(pluralRules, locales, options) {
std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3);
}
// Step 15.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
initializeIntlObject(pluralRules, "PluralRules", lazyPluralRulesData)
}
@ -173,7 +181,7 @@ function InitializePluralRules(pluralRules, locales, options) {
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript 402 API, PluralRules, 1.3.2.
* Spec: ECMAScript 402 API, PluralRules, 13.3.2.
*/
function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
var options = arguments.length > 1 ? arguments[1] : undefined;
@ -193,20 +201,20 @@ function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
* the number passed as value according to the
* effective locale and the formatting options of this PluralRules.
*
* Spec: ECMAScript 402 API, PluralRules, 1.4.3.
* Spec: ECMAScript 402 API, PluralRules, 13.4.3.
*/
function Intl_PluralRules_select(value) {
// Step 1.
let pluralRules = this;
// Step 2.
// Steps 2-3.
if (!IsObject(pluralRules) || !IsPluralRules(pluralRules))
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "PluralRules", "select", "PluralRules");
// Ensure the PluralRules internals are resolved.
getPluralRulesInternals(pluralRules);
// Steps 3-4.
// Step 4.
let n = ToNumber(value);
// Step 5.
@ -216,17 +224,34 @@ function Intl_PluralRules_select(value) {
/**
* Returns the resolved options for a PluralRules object.
*
* Spec: ECMAScript 402 API, PluralRules, 1.4.4.
* Spec: ECMAScript 402 API, PluralRules, 13.4.4.
*/
function Intl_PluralRules_resolvedOptions() {
// Check "this PluralRules object" per introduction of section 1.4.
if (!IsObject(this) || !IsPluralRules(this)) {
// Step 1.
var pluralRules = this;
// Steps 2-3.
if (!IsObject(pluralRules) || !IsPluralRules(pluralRules)) {
ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "PluralRules", "resolvedOptions",
"PluralRules");
}
var internals = getPluralRulesInternals(this);
var internals = getPluralRulesInternals(pluralRules);
var internalsPluralCategories = internals.pluralCategories;
if (internalsPluralCategories === null) {
internalsPluralCategories = intl_GetPluralCategories(internals.locale, internals.type);
internals.pluralCategories = internalsPluralCategories;
}
// TODO: The current spec actually requires to return the internal array
// object and not a copy of it.
// <https://github.com/tc39/proposal-intl-plural-rules/issues/28#issuecomment-341557030>
var pluralCategories = [];
for (var i = 0; i < internalsPluralCategories.length; i++)
_DefineDataProperty(pluralCategories, i, internalsPluralCategories[i]);
// Steps 4-5.
var result = {
locale: internals.locale,
type: internals.type,
@ -236,16 +261,19 @@ function Intl_PluralRules_resolvedOptions() {
maximumFractionDigits: internals.maximumFractionDigits,
};
var optionalProperties = [
"minimumSignificantDigits",
"maximumSignificantDigits"
];
// Min/Max significant digits are either both present or not at all.
assert(hasOwn("minimumSignificantDigits", internals) ===
hasOwn("maximumSignificantDigits", internals),
"minimumSignificantDigits is present iff maximumSignificantDigits is present");
for (var i = 0; i < optionalProperties.length; i++) {
var p = optionalProperties[i];
if (hasOwn(p, internals))
_DefineDataProperty(result, p, internals[p]);
if (hasOwn("minimumSignificantDigits", internals)) {
_DefineDataProperty(result, "minimumSignificantDigits",
internals.minimumSignificantDigits);
_DefineDataProperty(result, "maximumSignificantDigits",
internals.maximumSignificantDigits);
}
// Step 6.
return result;
}

View file

@ -6,19 +6,14 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
""" Usage:
make_intl_data.py langtags [language-subtag-registry.txt]
make_intl_data.py langtags [ldmlSupplemental.dtd supplementalMetadata.xml likelySubtags.xml]
make_intl_data.py tzdata
Target "langtags":
This script extracts information about mappings between deprecated and
current BCP 47 language tags from the IANA Language Subtag Registry and
converts it to JavaScript object definitions in
LangTagMappingsGenerated.js. The definitions are used in Intl.js.
The IANA Language Subtag Registry is imported from
https://www.iana.org/assignments/language-subtag-registry
and uses the syntax specified in
https://tools.ietf.org/html/rfc5646#section-3
current Unicode BCP 47 locale identifiers from CLDR and converts it to
JavaScript object definitions in LangTagMappingsGenerated.js. The
definitions are used in Intl.js.
Target "tzdata":
@ -32,202 +27,714 @@ import os
import re
import io
import codecs
import shutil
import subprocess
import sys
import tarfile
import tempfile
import urllib2
import urlparse
from contextlib import closing
from contextlib import closing, contextmanager
from functools import partial
from itertools import chain, ifilter, ifilterfalse, imap, tee
from operator import attrgetter, itemgetter
from urlparse import urlsplit, urlunsplit
def readRegistryRecord(registry):
""" Yields the records of the IANA Language Subtag Registry as dictionaries. """
record = {}
for line in registry:
line = line.strip()
if line == "":
continue
if line == "%%":
yield record
record = {}
else:
if ":" in line:
key, value = line.split(":", 1)
key, value = key.strip(), value.strip()
record[key] = value
def writeMappingHeader(println, description, source, url):
if type(description) is not list:
description = [description]
for desc in description:
println(u"// {0}".format(desc))
println(u"// Derived from {0}.".format(source))
println(u"// {0}".format(url))
def writeMappingsVar(println, mapping, name, description, source, url):
""" Writes a variable definition with a mapping table.
Writes the contents of dictionary |mapping| through the |println|
function with the given variable name and a comment with description,
source, and URL.
"""
println(u"")
writeMappingHeader(println, description, source, url)
println(u"var {0} = {{".format(name))
for key in sorted(mapping):
if not isinstance(mapping[key], dict):
value = mapping[key]
if isinstance(value, bool):
value = "true" if value else "false"
else:
# continuation line
record[key] += " " + line
if record:
yield record
return
value = '"{0}"'.format(value)
else:
preferred = mapping[key]["preferred"]
prefix = mapping[key]["prefix"]
if key != preferred:
raise Exception("Expected '{0}' matches preferred locale '{1}'".format(key, preferred))
value = '"{0}"'.format(prefix)
println(u' "{0}": {1},'.format(key, value))
println(u"};")
def writeUpdateLocaleIdMappingsFunction(println,
complex_language_mappings,
complex_region_mappings,
description, source, url):
""" Writes a function definition that performs language tag mapping. """
println(u"")
writeMappingHeader(println, description, source, url)
println(u"""\
/* eslint-disable complexity */
function updateLocaleIdMappings(tag) {
assert(IsObject(tag), "tag is an object");
// Replace deprecated language tags with their preferred values.
var language = tag.language;
if (hasOwn(language, languageMappings)) {
tag.language = languageMappings[language];
} else if (hasOwn(language, complexLanguageMappings)) {
switch (language) {""")
# Merge duplicate language entries.
language_aliases = {}
for (deprecated_language, (language, script, region)) in (
sorted(complex_language_mappings.items(), key=itemgetter(0))
):
key = (language, script, region)
if key not in language_aliases:
language_aliases[key] = []
else:
language_aliases[key].append(deprecated_language)
for (deprecated_language, (language, script, region)) in (
sorted(complex_language_mappings.items(), key=itemgetter(0))
):
key = (language, script, region)
if deprecated_language in language_aliases[key]:
continue
for lang in [deprecated_language] + language_aliases[key]:
println(u"""
case "{}":
""".format(lang).rstrip().strip("\n"))
println(u"""
tag.language = "{}";
""".format(language).rstrip().strip("\n"))
if script is not None:
println(u"""
if (tag.script === undefined)
tag.script = "{}";
""".format(script).rstrip().strip("\n"))
if region is not None:
println(u"""
if (tag.region === undefined)
tag.region = "{}";
""".format(region).rstrip().strip("\n"))
println(u"""
break;
""".rstrip().strip("\n"))
println(u"""
default:
assert(false, "language not handled: " + language);
}
}
// No script replacements are currently present.
// Replace deprecated subtags with their preferred values.
var region = tag.region;
if (region !== undefined) {
if (hasOwn(region, regionMappings)) {
tag.region = regionMappings[region];
} else if (hasOwn(region, complexRegionMappings)) {
switch (region) {""".lstrip("\n"))
# |non_default_replacements| is a list and hence not hashable. Convert it
# to a string to get a proper hashable value.
def hash_key(default, non_default_replacements):
return (default, str(sorted(str(v) for v in non_default_replacements)))
# Merge duplicate region entries.
region_aliases = {}
for (deprecated_region, (default, non_default_replacements)) in (
sorted(complex_region_mappings.items(), key=itemgetter(0))
):
key = hash_key(default, non_default_replacements)
if key not in region_aliases:
region_aliases[key] = []
else:
region_aliases[key].append(deprecated_region)
for (deprecated_region, (default, non_default_replacements)) in (
sorted(complex_region_mappings.items(), key=itemgetter(0))
):
key = hash_key(default, non_default_replacements)
if deprecated_region in region_aliases[key]:
continue
for region in [deprecated_region] + region_aliases[key]:
println(u"""
case "{}":
""".format(region).rstrip().strip("\n"))
for (language, script, region) in sorted(non_default_replacements, key=itemgetter(0)):
if script is None:
println(u"""
if (tag.language === "{}") {{
""".format(language).rstrip().strip("\n"))
else:
println(u"""
if (tag.language === "{}" && tag.script === "{}") {{
""".format(language, script).rstrip().strip("\n"))
println(u"""
tag.region = "{}";
break;
}}
""".format(region).rstrip().strip("\n"))
println(u"""
tag.region = "{}";
break;
""".format(default).rstrip().strip("\n"))
println(u"""
default:
assert(false, "region not handled: " + region);
}
}
// No variant replacements are currently present.
// No extension replacements are currently present.
// Private use sequences are left as is.
}
}
/* eslint-enable complexity */
""".strip("\n"))
def readRegistry(registry):
""" Reads IANA Language Subtag Registry and extracts information for Intl.js.
def writeGrandfatheredMappingsFunction(println,
grandfathered_mappings,
description, source, url):
""" Writes a function definition that maps grandfathered language tags. """
println(u"")
writeMappingHeader(println, description, source, url)
println(u"""\
function updateGrandfatheredMappings(tag) {
assert(IsObject(tag), "tag is an object");
// We're mapping regular grandfathered tags to non-grandfathered form here.
// Other tags remain unchanged.
//
// regular = "art-lojban"
// / "cel-gaulish"
// / "no-bok"
// / "no-nyn"
// / "zh-guoyu"
// / "zh-hakka"
// / "zh-min"
// / "zh-min-nan"
// / "zh-xiang"
//
// Therefore we can quickly exclude most tags by checking every
// |unicode_locale_id| subcomponent for characteristics not shared by any of
// the regular grandfathered (RG) tags:
//
// * Real-world |unicode_language_subtag|s are all two or three letters,
// so don't waste time running a useless |language.length > 3| fast-path.
// * No RG tag has a "script"-looking component.
// * No RG tag has a "region"-looking component.
// * The RG tags that match |unicode_locale_id| (art-lojban, cel-gaulish,
// zh-guoyu, zh-hakka, zh-xiang) have exactly one "variant". (no-bok,
// no-nyn, zh-min, and zh-min-nan require BCP47's extlang subtag
// that |unicode_locale_id| doesn't support.)
// * No RG tag contains |extensions| or |pu_extensions|.
if (tag.script !== undefined ||
tag.region !== undefined ||
tag.variants.length !== 1 ||
tag.extensions.length !== 0 ||
tag.privateuse !== undefined)
{
return;
}""")
# From Unicode BCP 47 locale identifier <https://unicode.org/reports/tr35/>.
#
# Doesn't allow any 'extensions' subtags.
re_unicode_locale_id = re.compile(
r"""
^
# unicode_language_id = unicode_language_subtag
# unicode_language_subtag = alpha{2,3} | alpha{5,8}
(?P<language>[a-z]{2,3}|[a-z]{5,8})
# (sep unicode_script_subtag)?
# unicode_script_subtag = alpha{4}
(?:-(?P<script>[a-z]{4}))?
# (sep unicode_region_subtag)?
# unicode_region_subtag = (alpha{2} | digit{3})
(?:-(?P<region>([a-z]{2}|[0-9]{3})))?
# (sep unicode_variant_subtag)*
# unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3})
(?P<variants>(-([a-z0-9]{5,8}|[0-9][a-z0-9]{3}))+)?
# pu_extensions?
# pu_extensions = sep [xX] (sep alphanum{1,8})+
(?:-(?P<privateuse>x(-[a-z0-9]{1,8})+))?
$
""", re.IGNORECASE | re.VERBOSE)
is_first = True
for (tag, modern) in sorted(grandfathered_mappings.items(), key=itemgetter(0)):
tag_match = re_unicode_locale_id.match(tag)
assert tag_match is not None
tag_language = tag_match.group("language")
assert tag_match.group("script") is None, (
"{} does not contain a script subtag".format(tag))
assert tag_match.group("region") is None, (
"{} does not contain a region subtag".format(tag))
tag_variants = tag_match.group("variants")
assert tag_variants is not None, (
"{} contains a variant subtag".format(tag))
assert tag_match.group("privateuse") is None, (
"{} does not contain a privateuse subtag".format(tag))
tag_variant = tag_variants[1:]
assert "-" not in tag_variant, (
"{} contains only a single variant".format(tag))
modern_match = re_unicode_locale_id.match(modern)
assert modern_match is not None
modern_language = modern_match.group("language")
modern_script = modern_match.group("script")
modern_region = modern_match.group("region")
modern_variants = modern_match.group("variants")
modern_privateuse = modern_match.group("privateuse")
println(u"""
// {} -> {}
""".format(tag, modern).rstrip())
println(u"""
{}if (tag.language === "{}" && tag.variants[0] === "{}") {{
""".format("" if is_first else "else ", tag_language, tag_variant).rstrip().strip("\n"))
is_first = False
println(u"""
tag.language = "{}";
""".format(modern_language).rstrip().strip("\n"))
if modern_script is not None:
println(u"""
tag.script = "{}";
""".format(modern_script).rstrip().strip("\n"))
if modern_region is not None:
println(u"""
tag.region = "{}";
""".format(modern_region).rstrip().strip("\n"))
if modern_variants is not None:
println(u"""
tag.variants = {};
""".format(sorted(modern_variants[1:].split("-"))).rstrip().strip("\n"))
else:
println(u"""
tag.variants.length = 0;
""".rstrip().strip("\n"))
if modern_privateuse is not None:
println(u"""
tag.privateuse = "{}";
""".format(modern_privateuse).rstrip().strip("\n"))
println(u"""
}""".rstrip().strip("\n"))
println(u"""
}""".lstrip("\n"))
@contextmanager
def TemporaryDirectory():
tmpDir = tempfile.mkdtemp()
try:
yield tmpDir
finally:
shutil.rmtree(tmpDir)
def readSupplementalData(supplemental_dtd_file, supplemental_metadata_file, likely_subtags_file):
""" Reads CLDR Supplemental Data and extracts information for Intl.js.
Information extracted:
- langTagMappings: mappings from complete language tags to preferred
- grandfatheredMappings: mappings from grandfathered tags to preferred
complete language tags
- langSubtagMappings: mappings from subtags to preferred subtags
- extlangMappings: mappings from extlang subtags to preferred subtags,
with prefix to be removed
Returns these three mappings as dictionaries, along with the registry's
file date.
We also check that mappings for language subtags don't affect extlang
subtags and vice versa, so that CanonicalizeLanguageTag doesn't have
to separate them for processing. Region codes are separated by case,
and script codes by length, so they're unproblematic.
- languageMappings: mappings from language subtags to preferred subtags
- complexLanguageMappings: mappings from language subtags with complex rules
- regionMappings: mappings from region subtags to preferred subtags
- complexRegionMappings: mappings from region subtags with complex rules
Returns these five mappings as dictionaries.
"""
langTagMappings = {}
langSubtagMappings = {}
extlangMappings = {}
languageSubtags = set()
extlangSubtags = set()
import xml.etree.ElementTree as ET
for record in readRegistryRecord(registry):
if "File-Date" in record:
fileDate = record["File-Date"]
# <!ATTLIST version cldrVersion CDATA #FIXED "36" >
re_cldr_version = re.compile(
r"""<!ATTLIST version cldrVersion CDATA #FIXED "(?P<version>[\d|\.]+)" >""")
with io.open(supplemental_dtd_file, mode="r", encoding="utf-8") as f:
version_match = re_cldr_version.search(f.read())
assert version_match is not None, "CLDR version string not found"
cldr_version = version_match.group("version")
# From Unicode BCP 47 locale identifier <https://unicode.org/reports/tr35/>.
re_unicode_language_id = re.compile(
r"""
^
# unicode_language_id = unicode_language_subtag
# unicode_language_subtag = alpha{2,3} | alpha{5,8}
(?P<language>[a-z]{2,3}|[a-z]{5,8})
# (sep unicode_script_subtag)?
# unicode_script_subtag = alpha{4}
(?:-(?P<script>[a-z]{4}))?
# (sep unicode_region_subtag)?
# unicode_region_subtag = (alpha{2} | digit{3})
(?:-(?P<region>([a-z]{2}|[0-9]{3})))?
# (sep unicode_variant_subtag)*
# unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3})
(?P<variants>(-([a-z0-9]{5,8}|[0-9][a-z0-9]{3}))+)?
$
""", re.IGNORECASE | re.VERBOSE)
re_unicode_language_subtag = re.compile(
r"""
^
# unicode_language_subtag = alpha{2,3} | alpha{5,8}
([a-z]{2,3}|[a-z]{5,8})
$
""", re.IGNORECASE | re.VERBOSE)
re_unicode_region_subtag = re.compile(
r"""
^
# unicode_region_subtag = (alpha{2} | digit{3})
([a-z]{2}|[0-9]{3})
$
""", re.IGNORECASE | re.VERBOSE)
# The fixed list of BCP 47 grandfathered language tags.
grandfathered_tags = (
"art-lojban",
"cel-gaulish",
"en-GB-oed",
"i-ami",
"i-bnn",
"i-default",
"i-enochian",
"i-hak",
"i-klingon",
"i-lux",
"i-mingo",
"i-navajo",
"i-pwn",
"i-tao",
"i-tay",
"i-tsu",
"no-bok",
"no-nyn",
"sgn-BE-FR",
"sgn-BE-NL",
"sgn-CH-DE",
"zh-guoyu",
"zh-hakka",
"zh-min",
"zh-min-nan",
"zh-xiang",
)
# The list of grandfathered tags which are valid Unicode BCP 47 locale identifiers.
unicode_bcp47_grandfathered_tags = {tag for tag in grandfathered_tags
if re_unicode_language_id.match(tag)}
# Dictionary of simple language subtag mappings, e.g. "in" -> "id".
language_mappings = {}
# Dictionary of complex language subtag mappings, modifying more than one
# subtag, e.g. "sh" -> ("sr", "Latn", None) and "cnr" -> ("sr", None, "ME").
complex_language_mappings = {}
# Dictionary of simple region subtag mappings, e.g. "DD" -> "DE".
region_mappings = {}
# Dictionary of complex region subtag mappings, containing more than one
# replacement, e.g. "SU" -> ("RU", ["AM",complex_region_mappings[type] = replacements "AZ", "BY", ...]).
complex_region_mappings = {}
# Dictionary of grandfathered mappings to preferred values.
grandfathered_mappings = {}
# CLDR uses "_" as the separator for some elements. Replace it with "-".
def bcp47_id(cldr_id):
return cldr_id.replace("_", "-")
# CLDR uses the canonical case for most entries, but there are some
# exceptions, like:
# <languageAlias type="drw" replacement="fa_af" reason="deprecated"/>
# Therefore canonicalize all tags to be on the safe side.
def bcp47_canonical(language, script, region):
# Canonical case for language subtags is lower case.
# Canonical case for script subtags is title case.
# Canonical case for region subtags is upper case.
return (language.lower() if language else None,
script.title() if script else None,
region.upper() if region else None)
tree = ET.parse(supplemental_metadata_file)
for language_alias in tree.iterfind(".//languageAlias"):
type = bcp47_id(language_alias.get("type"))
replacement = bcp47_id(language_alias.get("replacement"))
# Handle grandfathered mappings first.
if type in unicode_bcp47_grandfathered_tags:
grandfathered_mappings[type] = replacement
continue
if record["Type"] == "grandfathered":
# Grandfathered tags don't use standard syntax, so
# CanonicalizeLanguageTag expects the mapping table to provide
# the final form for all.
# For langTagMappings, keys must be in lower case; values in
# the case used in the registry.
tag = record["Tag"]
if "Preferred-Value" in record:
langTagMappings[tag.lower()] = record["Preferred-Value"]
else:
langTagMappings[tag.lower()] = tag
elif record["Type"] == "redundant":
# For langTagMappings, keys must be in lower case; values in
# the case used in the registry.
if "Preferred-Value" in record:
langTagMappings[record["Tag"].lower()] = record["Preferred-Value"]
elif record["Type"] in ("language", "script", "region", "variant"):
# For langSubtagMappings, keys and values must be in the case used
# in the registry.
subtag = record["Subtag"]
if record["Type"] == "language":
languageSubtags.add(subtag)
if "Preferred-Value" in record:
if subtag == "heploc":
# The entry for heploc is unique in its complexity; handle
# it as special case below.
continue
if "Prefix" in record:
# This might indicate another heploc-like complex case.
raise Exception("Please evaluate: subtag mapping with prefix value.")
langSubtagMappings[subtag] = record["Preferred-Value"]
elif record["Type"] == "extlang":
# For extlangMappings, keys must be in the case used in the
# registry; values are records with the preferred value and the
# prefix to be removed.
subtag = record["Subtag"]
extlangSubtags.add(subtag)
if "Preferred-Value" in record:
preferred = record["Preferred-Value"]
prefix = record["Prefix"]
extlangMappings[subtag] = {"preferred": preferred, "prefix": prefix}
# We're only interested in language subtag matches, so ignore any
# entries which have additional subtags.
if re_unicode_language_subtag.match(type) is None:
continue
if re_unicode_language_subtag.match(replacement) is not None:
# Canonical case for language subtags is lower-case.
language_mappings[type] = replacement.lower()
else:
# No other types are allowed by
# https://tools.ietf.org/html/rfc5646#section-3.1.3
assert False, "Unrecognized Type: {0}".format(record["Type"])
replacement_match = re_unicode_language_id.match(replacement)
assert replacement_match is not None, (
"{} invalid Unicode BCP 47 locale identifier".format(replacement))
assert replacement_match.group("variants") is None, (
"{}: unexpected variant subtags in {}".format(type, replacement))
# Check that mappings for language subtags and extlang subtags don't affect
# each other.
for lang in languageSubtags:
if lang in extlangMappings and extlangMappings[lang]["preferred"] != lang:
raise Exception("Conflict: lang with extlang mapping: " + lang)
for extlang in extlangSubtags:
if extlang in langSubtagMappings:
raise Exception("Conflict: extlang with lang mapping: " + extlang)
complex_language_mappings[type] = bcp47_canonical(replacement_match.group("language"),
replacement_match.group("script"),
replacement_match.group("region"))
# Special case for heploc.
langTagMappings["ja-latn-hepburn-heploc"] = "ja-Latn-alalc97"
for territory_alias in tree.iterfind(".//territoryAlias"):
type = territory_alias.get("type")
replacement = territory_alias.get("replacement")
# ValidateAndCanonicalizeLanguageTag in Intl.js expects langTagMappings
# contains no 2*3ALPHA.
assert all(len(lang) > 3 for lang in langTagMappings.iterkeys())
# We're only interested in region subtag matches, so ignore any entries
# which contain legacy formats, e.g. three letter region codes.
if re_unicode_region_subtag.match(type) is None:
continue
return {"fileDate": fileDate,
"langTagMappings": langTagMappings,
"langSubtagMappings": langSubtagMappings,
"extlangMappings": extlangMappings}
def writeMappingsVar(intlData, dict, name, description, fileDate, url):
""" Writes a variable definition with a mapping table to file intlData.
Writes the contents of dictionary dict to file intlData with the given
variable name and a comment with description, fileDate, and URL.
"""
intlData.write("\n")
intlData.write("// {0}.\n".format(description))
intlData.write("// Derived from IANA Language Subtag Registry, file date {0}.\n".format(fileDate))
intlData.write("// {0}\n".format(url))
intlData.write("var {0} = {{\n".format(name))
keys = sorted(dict)
for key in keys:
if isinstance(dict[key], basestring):
value = '"{0}"'.format(dict[key])
if re_unicode_region_subtag.match(replacement) is not None:
# Canonical case for region subtags is upper-case.
region_mappings[type] = replacement.upper()
else:
preferred = dict[key]["preferred"]
prefix = dict[key]["prefix"]
value = '{{preferred: "{0}", prefix: "{1}"}}'.format(preferred, prefix)
intlData.write(' "{0}": {1},\n'.format(key, value))
intlData.write("};\n")
# Canonical case for region subtags is upper-case.
replacements = [r.upper() for r in replacement.split(" ")]
assert all(
re_unicode_region_subtag.match(loc) is not None for loc in replacements
), "{} invalid region subtags".format(replacement)
complex_region_mappings[type] = replacements
tree = ET.parse(likely_subtags_file)
def writeLanguageTagData(intlData, fileDate, url, langTagMappings, langSubtagMappings, extlangMappings):
likely_subtags = {}
for likely_subtag in tree.iterfind(".//likelySubtag"):
from_tag = bcp47_id(likely_subtag.get("from"))
from_match = re_unicode_language_id.match(from_tag)
assert from_match is not None, (
"{} invalid Unicode BCP 47 locale identifier".format(from_tag))
assert from_match.group("variants") is None, (
"unexpected variant subtags in {}".format(from_tag))
to_tag = bcp47_id(likely_subtag.get("to"))
to_match = re_unicode_language_id.match(to_tag)
assert to_match is not None, (
"{} invalid Unicode BCP 47 locale identifier".format(to_tag))
assert to_match.group("variants") is None, (
"unexpected variant subtags in {}".format(to_tag))
from_canonical = bcp47_canonical(from_match.group("language"),
from_match.group("script"),
from_match.group("region"))
to_canonical = bcp47_canonical(to_match.group("language"),
to_match.group("script"),
to_match.group("region"))
likely_subtags[from_canonical] = to_canonical
complex_region_mappings_final = {}
for (deprecated_region, replacements) in complex_region_mappings.items():
# Find all likely subtag entries which don't already contain a region
# subtag and whose target region is in the list of replacement regions.
region_likely_subtags = [(from_language, from_script, to_region)
for ((from_language, from_script, from_region),
(_, _, to_region)) in likely_subtags.items()
if from_region is None and to_region in replacements]
# The first replacement entry is the default region.
default = replacements[0]
# Find all likely subtag entries whose region matches the default region.
default_replacements = {(language, script)
for (language, script, region) in region_likely_subtags
if region == default}
# And finally find those entries which don't use the default region.
# These are the entries we're actually interested in, because those need
# to be handled specially when selecting the correct preferred region.
non_default_replacements = [(language, script, region)
for (language, script, region) in region_likely_subtags
if (language, script) not in default_replacements]
# If there are no non-default replacements, we can handle the region as
# part of the simple region mapping.
if non_default_replacements:
complex_region_mappings_final[deprecated_region] = (default, non_default_replacements)
else:
region_mappings[deprecated_region] = default
return {"version": cldr_version,
"grandfatheredMappings": grandfathered_mappings,
"languageMappings": language_mappings,
"complexLanguageMappings": complex_language_mappings,
"regionMappings": region_mappings,
"complexRegionMappings": complex_region_mappings_final,
}
def writeCLDRLanguageTagData(println, data, url):
""" Writes the language tag data to the Intl data file. """
writeMappingsVar(intlData, langTagMappings, "langTagMappings",
"Mappings from complete tags to preferred values", fileDate, url)
writeMappingsVar(intlData, langSubtagMappings, "langSubtagMappings",
"Mappings from non-extlang subtags to preferred values", fileDate, url)
writeMappingsVar(intlData, extlangMappings, "extlangMappings",
"Mappings from extlang subtags to preferred values", fileDate, url)
def updateLangTags(args):
""" Update the LangTagMappingsGenerated.js file. """
source = u"CLDR Supplemental Data, version {}".format(data["version"])
grandfathered_mappings = data["grandfatheredMappings"]
language_mappings = data["languageMappings"]
complex_language_mappings = data["complexLanguageMappings"]
region_mappings = data["regionMappings"]
complex_region_mappings = data["complexRegionMappings"]
writeMappingsVar(println, grandfathered_mappings, "grandfatheredMappings",
"Mappings from grandfathered tags to preferred values.", source, url)
writeMappingsVar(println, language_mappings, "languageMappings",
"Mappings from language subtags to preferred values.", source, url)
writeMappingsVar(println, {key: True for key in complex_language_mappings},
"complexLanguageMappings",
"Language subtags with complex mappings.", source, url)
writeMappingsVar(println, region_mappings, "regionMappings",
"Mappings from region subtags to preferred values.", source, url)
writeMappingsVar(println, {key: True for key in complex_region_mappings},
"complexRegionMappings",
"Region subtags with complex mappings.", source, url)
writeUpdateLocaleIdMappingsFunction(println, complex_language_mappings,
complex_region_mappings,
"Canonicalize Unicode BCP 47 locale identifiers.",
source, url)
writeGrandfatheredMappingsFunction(println, grandfathered_mappings,
"Canonicalize grandfathered locale identifiers.",
source, url)
def updateCLDRLangTags(args):
""" Update the LangTagMappingsCLDRGenerated.js file. """
url = args.url
branch = args.branch
revision = args.revision
out = args.out
filename = args.file
files = args.files
print("Arguments:")
print("\tDownload url: %s" % url)
print("\tLocal registry: %s" % filename)
print("\tBranch: %s" % branch)
print("\tRevision: %s" % revision)
print("\tLocal supplemental data and likely subtags: %s" % files)
print("\tOutput file: %s" % out)
print("")
if filename is not None:
print("Always make sure you have the newest language-subtag-registry.txt!")
registry = codecs.open(filename, "r", encoding="utf-8")
else:
print("Downloading IANA Language Subtag Registry...")
with closing(urllib2.urlopen(url)) as reader:
text = reader.read().decode("utf-8")
registry = codecs.open("language-subtag-registry.txt", "w+", encoding="utf-8")
registry.write(text)
registry.seek(0)
if files:
if len(files) != 3:
raise Exception("Expected three files, but got: {}".format(files))
print("Processing IANA Language Subtag Registry...")
with closing(registry) as reg:
data = readRegistry(reg)
fileDate = data["fileDate"]
langTagMappings = data["langTagMappings"]
langSubtagMappings = data["langSubtagMappings"]
extlangMappings = data["extlangMappings"]
print(("Always make sure you have the newest ldmlSupplemental.dtd, "
"supplementalMetadata.xml, and likelySubtags.xml!"))
supplemental_dtd_file = files[0]
supplemental_metadata_file = files[1]
likely_subtags_file = files[2]
else:
print("Downloading CLDR supplemental data...")
supplemental_dtd_filename = "ldmlSupplemental.dtd"
supplemental_dtd_path = "common/dtd/{}".format(supplemental_dtd_filename)
supplemental_dtd_file = os.path.join(os.getcwd(), supplemental_dtd_filename)
supplemental_metadata_filename = "supplementalMetadata.xml"
supplemental_metadata_path = "common/supplemental/{}".format(
supplemental_metadata_filename)
supplemental_metadata_file = os.path.join(os.getcwd(), supplemental_metadata_filename)
likely_subtags_filename = "likelySubtags.xml"
likely_subtags_path = "common/supplemental/{}".format(likely_subtags_filename)
likely_subtags_file = os.path.join(os.getcwd(), likely_subtags_filename)
# Try to download the raw file directly from GitHub if possible.
split = urlsplit(url)
if split.netloc == "github.com" and split.path.endswith(".git") and revision == "HEAD":
def download(path, file):
urlpath = "{}/raw/{}/{}".format(urlsplit(url).path[:-4], branch, path)
raw_url = urlunsplit((split.scheme, split.netloc, urlpath, split.query,
split.fragment))
with closing(urllib2.urlopen(raw_url)) as reader:
text = reader.read().decode("utf-8")
with io.open(file, "w", encoding="utf-8") as saved_file:
saved_file.write(text)
download(supplemental_dtd_path, supplemental_dtd_file)
download(supplemental_metadata_path, supplemental_metadata_file)
download(likely_subtags_path, likely_subtags_file)
else:
# Download the requested branch in a temporary directory.
with TemporaryDirectory() as inDir:
if revision == "HEAD":
subprocess.check_call(["git", "clone", "--depth=1",
"--branch=%s" % branch, url, inDir])
else:
subprocess.check_call(["git", "clone", "--single-branch",
"--branch=%s" % branch, url, inDir])
subprocess.check_call(["git", "-C", inDir, "reset", "--hard", revision])
shutil.copyfile(os.path.join(inDir, supplemental_dtd_path),
supplemental_dtd_file)
shutil.copyfile(os.path.join(inDir, supplemental_metadata_path),
supplemental_metadata_file)
shutil.copyfile(os.path.join(inDir, likely_subtags_path), likely_subtags_file)
print("Processing CLDR supplemental data...")
data = readSupplementalData(supplemental_dtd_file,
supplemental_metadata_file,
likely_subtags_file)
print("Writing Intl data...")
with codecs.open(out, "w", encoding="utf-8") as intlData:
intlData.write("// Generated by make_intl_data.py. DO NOT EDIT.\n")
writeLanguageTagData(intlData, fileDate, url, langTagMappings, langSubtagMappings, extlangMappings)
with io.open(out, mode="w", encoding="utf-8", newline="") as f:
println = partial(print, file=f)
println(u"// Generated by make_intl_data.py. DO NOT EDIT.")
writeCLDRLanguageTagData(println, data, url)
def flines(filepath, encoding="utf-8"):
""" Open filepath and iterate over its content. """
@ -707,11 +1214,11 @@ def processTimeZones(tzdataDir, icuDir, icuTzDir, version, ignoreBackzone, ignor
println(u"// Format:")
println(u'// "LinkName", "Target" // ICU-Target [time zone file]')
println(u"struct LinkAndTarget");
println(u"{");
println(u" const char* const link;");
println(u" const char* const target;");
println(u"};");
println(u"struct LinkAndTarget")
println(u"{")
println(u" const char* const link;")
println(u" const char* const target;")
println(u"};")
println(u"")
println(u"const LinkAndTarget ianaLinksCanonicalizedDifferentlyByICU[] = {")
for (zone, target, icuTarget) in incorrectLinks:
@ -932,7 +1439,7 @@ def updateTzdata(topsrcdir, args):
if tzDir is None:
print("Downloading tzdata file...")
with closing(urllib2.urlopen(url)) as tzfile:
fname = urlparse.urlsplit(tzfile.geturl()).path.split("/")[-1]
fname = urlsplit(tzfile.geturl()).path.split("/")[-1]
with tempfile.NamedTemporaryFile(suffix=fname) as tztmpfile:
print("File stored in %s" % tztmpfile.name)
tztmpfile.write(tzfile.read())
@ -959,20 +1466,24 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Update intl data.")
subparsers = parser.add_subparsers(help="Select update mode")
parser_tags = subparsers.add_parser("langtags",
help="Update language-subtag-registry")
parser_tags.add_argument("--url",
metavar="URL",
default="https://www.iana.org/assignments/language-subtag-registry",
type=EnsureHttps,
help="Download url for language-subtag-registry.txt (default: %(default)s)")
parser_tags.add_argument("--out",
default="LangTagMappingsGenerated.js",
help="Output file (default: %(default)s)")
parser_tags.add_argument("file",
nargs="?",
help="Local language-subtag-registry.txt file, if omitted uses <URL>")
parser_tags.set_defaults(func=updateLangTags)
parser_cldr_tags = subparsers.add_parser("langtags",
help="Update CLDR language tags data")
parser_cldr_tags.add_argument("--url",
metavar="URL",
default="https://github.com/unicode-org/cldr.git",
help="URL to git repository (default: %(default)s)")
parser_cldr_tags.add_argument("--branch", default="latest",
help="Git branch (default: %(default)s)")
parser_cldr_tags.add_argument("--revision", default="HEAD",
help="Git revision (default: %(default)s)")
parser_cldr_tags.add_argument("--out",
default="LangTagMappingsGenerated.js",
help="Output file (default: %(default)s)")
parser_cldr_tags.add_argument("files",
nargs="*",
help="Local ldmlSupplemental.dtd, supplementalMetadata.xml, "
"and likelySubtags.xml files, if omitted uses <URL>")
parser_cldr_tags.set_defaults(func=updateCLDRLangTags)
parser_tz = subparsers.add_parser("tzdata", help="Update tzdata")
parser_tz.add_argument("--tz",

View file

@ -2545,8 +2545,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("StringSplitStringLimit", intrinsic_StringSplitStringLimit, 3, 0),
// See builtin/RegExp.h for descriptions of the regexp_* functions.
JS_FN("regexp_exec_no_statics", regexp_exec_no_statics, 2,0),
JS_FN("regexp_test_no_statics", regexp_test_no_statics, 2,0),
JS_FN("regexp_construct_raw_flags", regexp_construct_raw_flags, 2,0),
JS_FN("regexp_clone", regexp_clone, 1,0),