Files
BeWoPlaner/BeWoPlanerMobil/node_modules/cldr/lib/cldr.js
Lyndon Jetten 88fd24f3a9 MoK:
- Bug beim Übernehmen eines Termins in die Zeiterfassung, wenn man Stunden als Zeiteinheit ausgewählt hatte, behoben.
- Berichte und Mitarbeiterstundenkonto eingebaut
- Node-Module optimiert (sehr viele gelöscht)
2023-05-15 11:54:13 +02:00

2135 lines
67 KiB
JavaScript

const Path = require('path');
const fs = require('fs');
const passError = require('passerror');
const memoizeAsync = require('memoizeasync');
const DOMParser = require('@xmldom/xmldom').DOMParser;
const xpath = require('xpath');
const seq = require('seq');
const normalizeLocaleId = require('./normalizeLocaleId');
const normalizeProperty = require('./normalizeProperty');
const convertObjectsWithIntegerKeysToArrays = require('./convertObjectsWithIntegerKeysToArrays');
const CldrPluralRuleSet = require('./CldrPluralRuleSet');
const CldrRbnfRuleSet = require('./CldrRbnfRuleSet');
const escodegen = require('escodegen');
const unicoderegexp = require('unicoderegexp');
function normalizeXPathQuery(xpathQuery) {
const xpathQueryFragments = xpathQuery.split('/');
for (let i = 0; i < xpathQueryFragments.length; i += 1) {
if (
i > 0 &&
xpathQueryFragments[i] === '..' &&
xpathQueryFragments[i - 1] !== '..'
) {
xpathQueryFragments.splice(i - 1, 2);
i -= 2;
}
}
return xpathQueryFragments.join('/');
}
function Cldr(cldrPath) {
// Support instantiation without the 'new' operator:
if (!(this instanceof Cldr)) {
return new Cldr(cldrPath);
}
this.cldrPath = cldrPath;
this.documentByFileName = {};
this.memoizerByFileName = {};
}
Cldr.prototype = {
fileNamesByTypeAndNormalizedLocaleId(type = 'main') {
if (!this._fileNamesByTypeAndNormalizedLocaleId) {
this._fileNamesByTypeAndNormalizedLocaleId = {};
}
if (
typeof this._fileNamesByTypeAndNormalizedLocaleId[type] === 'undefined'
) {
this._fileNamesByTypeAndNormalizedLocaleId[type] = {};
let fileNames;
try {
fileNames = fs.readdirSync(Path.resolve(this.cldrPath, 'common', type));
} catch (e) {
if (e.code === 'ENOENT') {
// Directory doesn't exist, just pretend it's empty.
return;
}
}
fileNames.forEach(function (fileName) {
const matchFileName = fileName.match(/^(.*)\.xml$/);
if (matchFileName) {
this._fileNamesByTypeAndNormalizedLocaleId[type][
normalizeLocaleId(matchFileName[1])
] = Path.resolve(this.cldrPath, 'common', type, fileName);
}
}, this);
}
return this._fileNamesByTypeAndNormalizedLocaleId[type];
},
get localeIds() {
if (!this._localeIds) {
this._localeIds = Object.keys(
this.fileNamesByTypeAndNormalizedLocaleId('main')
);
}
return this._localeIds;
},
get localeIdsSet() {
if (!this._localeIdsSet) {
this._localeIdsSet = new Set(this.localeIds);
}
return this._localeIdsSet;
},
checkValidLocaleId(localeId) {
if (!this.localeIdsSet.has(normalizeLocaleId(localeId))) {
throw new Error(`No data for locale id: ${localeId}`);
}
},
get calendarIds() {
if (!this._calendarIds) {
this._calendarIds = [];
xpath
.select(
'/ldmlBCP47/keyword/key[@name="ca"]/type',
this.getDocument(
Path.resolve(this.cldrPath, 'common', 'bcp47', 'calendar.xml')
)
)
.forEach(function (keyNode) {
let calendarId = keyNode.getAttribute('name');
if (calendarId === 'gregory') {
calendarId = 'gregorian';
}
this._calendarIds.push(calendarId);
}, this);
}
return this._calendarIds;
},
get numberSystemIds() {
if (!this._numberSystemIds) {
this._numberSystemIds = [];
xpath
.select(
'/ldmlBCP47/keyword/key[@name="nu"]/type',
this.getDocument(
Path.resolve(this.cldrPath, 'common', 'bcp47', 'number.xml')
)
)
.forEach(function (keyNode) {
this._numberSystemIds.push(keyNode.getAttribute('name'));
}, this);
}
return this._numberSystemIds;
},
get localesByParentLocale() {
if (!this._localesByParentLocale) {
this._localesByParentLocale = {};
xpath
.select(
'/supplementalData/parentLocales/parentLocale',
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'supplementalData.xml'
)
)
)
.forEach(function (parentLocaleNode) {
this._localesByParentLocale[
normalizeLocaleId(parentLocaleNode.getAttribute('parent'))
] = parentLocaleNode
.getAttribute('locales')
.split(' ')
.map((localeId) => normalizeLocaleId(localeId));
}, this);
}
return this._localesByParentLocale;
},
get windowsZonesByMapZone() {
if (!this._windowsZonesByMapZone) {
this._windowsZonesByMapZone = [];
xpath
.select(
'/supplementalData/windowsZones/mapTimezones/mapZone',
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'windowsZones.xml'
)
)
)
.forEach(function (keyNode) {
keyNode
.getAttribute('type')
.split(/\s+/)
.forEach(function (typeEntry) {
this._windowsZonesByMapZone.push({
name: typeEntry,
territory: keyNode.getAttribute('territory'),
timeZone: keyNode.getAttribute('other'),
});
}, this);
}, this);
}
return this._windowsZonesByMapZone;
},
// Works both async and sync (omit cb):
getDocument(fileName, cb) {
const that = this;
if (that.documentByFileName[fileName]) {
if (cb) {
process.nextTick(() => {
cb(null, that.documentByFileName[fileName]);
});
} else {
return that.documentByFileName[fileName];
}
} else {
if (cb) {
// Make sure not to load file more than once if it's being loaded when getDocument is called for the second time:
that.memoizerByFileName[fileName] =
that.memoizerByFileName[fileName] ||
memoizeAsync((cb) => {
fs.readFile(
fileName,
'utf-8',
passError(cb, (xmlString) => {
const document = new DOMParser().parseFromString(xmlString);
that.documentByFileName[fileName] = document;
cb(null, document);
})
);
});
that.memoizerByFileName[fileName](cb);
} else {
return (that.documentByFileName[fileName] =
new DOMParser().parseFromString(fs.readFileSync(fileName, 'utf-8')));
}
}
},
resolveParentLocaleId(localeId) {
localeId = normalizeLocaleId(localeId);
if (!localeId) {
return;
}
let parentLocaleId;
Object.keys(this.localesByParentLocale).forEach(function (_parentLocaleId) {
if (this.localesByParentLocale[_parentLocaleId].indexOf(localeId) > -1) {
parentLocaleId = _parentLocaleId;
}
}, this);
if (!parentLocaleId && /_[^_]+$/.test(localeId)) {
parentLocaleId = localeId.replace(/_[^_]+$/, '');
}
return parentLocaleId;
},
extractWindowsZonesByTimeZone(timeZone) {
if (!timeZone) {
return;
}
return this.windowsZonesByMapZone.filter(
(element) => element.timeZone.toLowerCase() === timeZone.toLowerCase()
);
},
extractWindowsZonesByName(name) {
if (!name) {
return;
}
return this.windowsZonesByMapZone.filter(
(element) => element.name.toLowerCase() === name.toLowerCase()
);
},
expandLocaleIdToPrioritizedList(localeId) {
localeId = normalizeLocaleId(localeId);
if (!localeId) {
return [];
}
const localeIds = [localeId];
let parentLocaleId = this.resolveParentLocaleId(localeId);
while (parentLocaleId) {
localeIds.push(parentLocaleId);
parentLocaleId = this.resolveParentLocaleId(parentLocaleId);
}
return localeIds;
},
getPrioritizedDocumentsForLocale(localeId, type) {
this.checkValidLocaleId(localeId);
const that = this;
return that
.expandLocaleIdToPrioritizedList(localeId)
.concat('root')
.map(
(subLocaleId) =>
that.fileNamesByTypeAndNormalizedLocaleId(type)[
normalizeLocaleId(subLocaleId)
]
)
.filter((fileName) => !!fileName)
.map((fileName) => that.getDocument(fileName));
},
preload(localeIds, cb) {
const that = this;
if (typeof localeIds === 'function') {
cb = localeIds;
localeIds = that.localeIds;
}
localeIds = (Array.isArray(localeIds) ? localeIds : [localeIds]).map(
normalizeLocaleId
);
const neededLocaleById = { root: true };
localeIds.forEach((localeId) => {
that.expandLocaleIdToPrioritizedList(localeId).forEach((subLocaleId) => {
neededLocaleById[subLocaleId] = true;
});
});
const fileNames = [
Path.resolve(that.cldrPath, 'common', 'supplemental', 'plurals.xml'),
Path.resolve(
that.cldrPath,
'common',
'supplemental',
'numberingSystems.xml'
),
];
Object.keys(neededLocaleById).forEach((localeId) => {
['main', 'rbnf'].forEach((type) => {
const fileName =
that.fileNamesByTypeAndNormalizedLocaleId(type)[localeId];
if (fileName) {
fileNames.push(fileName);
}
});
});
seq(fileNames)
.parEach(20, function (fileName) {
that.getDocument(fileName, this);
})
.seq(() => {
cb();
})
.catch(cb);
},
createFinder(prioritizedDocuments) {
return function finder(xpathQuery) {
const prioritizedResults = [];
prioritizedDocuments.forEach((document, i) => {
const resultsForLocaleDocument = xpath.select(xpathQuery, document);
if (
resultsForLocaleDocument.length === 0 &&
i === prioritizedDocuments.length - 1
) {
// We're in root and there were no results, look for alias elements in path:
const queryFragments = xpathQuery.split('/');
const poppedQueryFragments = [];
while (queryFragments.length > 1) {
const aliasNodes = xpath.select(
queryFragments.join('/') + '/alias',
document
);
if (aliasNodes.length > 0) {
const aliasSpecifiedQuery = normalizeXPathQuery(
queryFragments.join('/') +
'/' +
aliasNodes[0].getAttribute('path') +
'/' +
poppedQueryFragments.join('/')
);
Array.prototype.push.apply(
prioritizedResults,
finder(aliasSpecifiedQuery)
);
break;
}
poppedQueryFragments.unshift(queryFragments.pop());
}
} else {
Array.prototype.push.apply(
prioritizedResults,
resultsForLocaleDocument
);
}
});
return prioritizedResults;
};
},
extractLocaleDisplayPattern(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const localeDisplayPattern = {};
finder('/ldml/localeDisplayNames/localeDisplayPattern/*').forEach(
(node) => {
localeDisplayPattern[node.nodeName] = node.textContent;
}
);
return localeDisplayPattern;
},
extractLanguageDisplayNames(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const languageDisplayNames = {};
finder('/ldml/localeDisplayNames/languages/language').forEach((node) => {
const id = normalizeLocaleId(node.getAttribute('type'));
languageDisplayNames[id] = languageDisplayNames[id] || node.textContent;
});
return languageDisplayNames;
},
extractTimeZoneDisplayNames(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const timeZoneDisplayNames = {};
finder('/ldml/dates/timeZoneNames/zone').forEach((zoneNode) => {
const timeZoneId = zoneNode.getAttribute('type');
const exemplarCityNodes = xpath.select('exemplarCity', zoneNode);
let tzNameLocale;
if (exemplarCityNodes.length > 0) {
tzNameLocale = exemplarCityNodes[0].textContent;
} else {
const genericDisplayNameNodes = xpath.select('long/generic', zoneNode);
if (genericDisplayNameNodes.length > 0) {
tzNameLocale = genericDisplayNameNodes[0].textContent;
} else {
const longDisplayNameNodes = xpath.select('long/standard', zoneNode);
if (longDisplayNameNodes.length > 0) {
tzNameLocale = longDisplayNameNodes[0].textContent;
}
}
}
if (tzNameLocale) {
timeZoneDisplayNames[timeZoneId] =
timeZoneDisplayNames[timeZoneId] || tzNameLocale;
}
});
return timeZoneDisplayNames;
},
extractTimeZoneFormats(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const timeZoneFormats = {};
[
'hourFormat',
'gmtFormat',
'gmtZeroFormat',
'regionFormat',
'fallbackFormat',
'fallbackRegionFormat',
].forEach((tagName) => {
finder('/ldml/dates/timeZoneNames/' + tagName).forEach((node) => {
const formatName = node.nodeName.replace(/Format$/, '');
let value = node.textContent;
if (formatName === 'hour') {
value = value.split(';');
}
timeZoneFormats[formatName] = timeZoneFormats[formatName] || value;
});
});
finder('/ldml/dates/timeZoneNames/regionFormat[@type]').forEach((node) => {
const type = node.getAttribute('type');
timeZoneFormats.regions = timeZoneFormats.regions || {};
timeZoneFormats.regions[type] =
timeZoneFormats.regions[type] || node.textContent;
});
return timeZoneFormats;
},
extractTerritoryDisplayNames(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const territoryDisplayNames = {};
finder('/ldml/localeDisplayNames/territories/territory').forEach(
(territoryNode) => {
let territoryId = territoryNode.getAttribute('type');
const alternative = territoryNode.getAttribute('alt');
if (alternative) {
// Use the same format for alternative names as cldr-json (e.g. BA-alt-short)
territoryId = `${territoryId}-alt-${alternative}`;
}
territoryDisplayNames[territoryId] =
territoryDisplayNames[territoryId] || territoryNode.textContent;
}
);
return territoryDisplayNames;
},
/**
* Subdivisions are usually subnational administrative entities.
* Codes follow the BCP47 standard, e.g. `usca` for California, USA.
* Note that these codes are similar but not identical to ISO 3166-2 codes.
* Unlike ISO 3166-2, CLDR never reuses a code.
*
* @param {string} localeId Locale or subdivision names
* @returns {Object} A dictionary of subdivision codes and their names.
*
* @example
* // returns 'Zealand'
* cldr.extractSubdivisionDisplayNames('en').dk85
*/
extractSubdivisionDisplayNames(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'subdivisions')
);
const subdivisionDisplayNames = {};
finder('/ldml/localeDisplayNames/subdivisions/subdivision').forEach(
(node) => {
const subdivisionId = node.getAttribute('type');
subdivisionDisplayNames[subdivisionId] =
subdivisionDisplayNames[subdivisionId] || node.textContent;
}
);
return subdivisionDisplayNames;
},
/**
* Subdivision aliases contain deprecated or alternative subdivision
* codes. Note that the returned code may be either
* a territory code, (such as 'cn71' => 'TW'), or a subdivision code.
*
* @returns {Object} A dictionary with replacement code and reason
*
* @example
* // returns {replacement: 'cz71', reason: 'deprecated'}
* cldr.extractSubdivisionAliases().czol
*
* // returns {replacement: 'GU', reason: 'overlong'}
* cldr.extractSubdivisionAliases().usgu
*/
extractSubdivisionAliases() {
const finder = this.createFinder([
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'supplementalMetadata.xml'
)
),
]);
const aliasData = {};
finder('/supplementalData/metadata/alias/subdivisionAlias').forEach(
(subdivisionAliasNode) => {
const type = subdivisionAliasNode.getAttribute('type');
aliasData[type] = {
replacement: subdivisionAliasNode.getAttribute('replacement'),
reason: subdivisionAliasNode.getAttribute('reason'),
};
}
);
return aliasData;
},
/**
* Territory aliases contain deprecated or alternative nation/territory
* codes.
*
* @returns {Object} A dictionary with replacement code and reason
*
* @example
* // returns {replacement: 'MM', reason: 'deprecated'}
* cldr.extractTerritorpAliases().BU
*/
extractTerritoryAliases() {
const finder = this.createFinder([
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'supplementalMetadata.xml'
)
),
]);
const aliasData = {};
finder('/supplementalData/metadata/alias/territoryAlias').forEach(
(territoryAliasNode) => {
const type = territoryAliasNode.getAttribute('type');
aliasData[type] = {
replacement: territoryAliasNode.getAttribute('replacement'),
reason: territoryAliasNode.getAttribute('reason'),
};
}
);
return aliasData;
},
extractCurrencyInfoById(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const currencyDisplayNameByCurrencyId = {};
const currencyDisplayNameByCurrencyIdAndCount = {};
const currencySymbolByCurrencyId = {};
finder('/ldml/numbers/currencies/currency/displayName').forEach(
(displayNameNode) => {
const currencyId = displayNameNode.parentNode.getAttribute('type');
const countAttribute = displayNameNode.getAttribute('count');
if (countAttribute) {
currencyDisplayNameByCurrencyIdAndCount[currencyId] =
currencyDisplayNameByCurrencyIdAndCount[currencyId] || {};
currencyDisplayNameByCurrencyIdAndCount[currencyId][countAttribute] =
displayNameNode.textContent;
} else {
currencyDisplayNameByCurrencyId[currencyId] =
currencyDisplayNameByCurrencyId[currencyId] ||
displayNameNode.textContent;
}
}
);
finder('/ldml/numbers/currencies/currency/symbol').forEach((symbolNode) => {
const currencyId = symbolNode.parentNode.getAttribute('type');
currencySymbolByCurrencyId[currencyId] =
currencySymbolByCurrencyId[currencyId] || symbolNode.textContent;
});
const currencyInfoById = {};
Object.keys(currencyDisplayNameByCurrencyId).forEach((currencyId) => {
currencyInfoById[currencyId] = Object.assign(
{
displayName: currencyDisplayNameByCurrencyId[currencyId],
symbol: currencySymbolByCurrencyId[currencyId],
},
currencyDisplayNameByCurrencyIdAndCount[currencyId]
);
});
return currencyInfoById;
},
extractScriptDisplayNames(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const scriptDisplayNames = {};
finder('/ldml/localeDisplayNames/scripts/script').forEach((scriptNode) => {
const id = scriptNode.getAttribute('type');
scriptDisplayNames[id] = scriptDisplayNames[id] || scriptNode.textContent;
});
return scriptDisplayNames;
},
extractVariantDisplayNames(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const variantDisplayNames = {};
finder('/ldml/localeDisplayNames/variants/variant').forEach(
(variantNode) => {
const id = variantNode.getAttribute('type');
variantDisplayNames[id] =
variantDisplayNames[id] || variantNode.textContent;
}
);
return variantDisplayNames;
},
extractKeyTypes(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const keyTypes = {};
finder('/ldml/localeDisplayNames/keys/key').forEach((keyNode) => {
const type = keyNode.getAttribute('type');
keyTypes[type] = { displayName: keyNode.textContent };
});
finder('/ldml/localeDisplayNames/types/type').forEach((typeNode) => {
const key = typeNode.getAttribute('key');
const type = normalizeProperty(typeNode.getAttribute('type'));
keyTypes[key] = keyTypes[key] || {};
keyTypes[key].types = keyTypes[key].types || {};
keyTypes[key].types[type] = typeNode.textContent;
});
return keyTypes;
},
extractMeasurementSystemNames(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const measurementSystemNames = {};
finder(
'/ldml/localeDisplayNames/measurementSystemNames/measurementSystemName'
).forEach((measurementSystemNameNode) => {
const id = measurementSystemNameNode.getAttribute('type');
measurementSystemNames[id] =
measurementSystemNames[id] || measurementSystemNameNode.textContent;
});
return measurementSystemNames;
},
extractCodePatterns(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const codePatterns = {};
finder('/ldml/localeDisplayNames/codePatterns/codePattern').forEach(
(codePatternNode) => {
const id = codePatternNode.getAttribute('type');
codePatterns[id] = codePatterns[id] || codePatternNode.textContent;
}
);
return codePatterns;
},
// Calendar extraction methods:
extractEraNames(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let eraNames;
['eraNames', 'eraAbbr'].forEach((eraType) => {
const typeInOutput = { eraNames: 'wide', eraAbbr: 'abbreviated' }[
eraType
];
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/eras/" +
eraType +
'/era'
).forEach((eraNode) => {
const type = parseInt(eraNode.getAttribute('type'), 10);
eraNames = eraNames || {};
eraNames[typeInOutput] = eraNames[typeInOutput] || {};
eraNames[typeInOutput][type] =
eraNames[typeInOutput][type] || eraNode.textContent;
});
});
return convertObjectsWithIntegerKeysToArrays(eraNames);
},
extractQuarterNames(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let quarterNames;
['format', 'stand-alone'].forEach((quarterContext) => {
const quarterContextCamelCase = normalizeProperty(quarterContext); // stand-alone => standAlone
['abbreviated', 'narrow', 'wide'].forEach((quarterWidth) => {
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/quarters/quarterContext[@type='" +
quarterContext +
"']/quarterWidth[@type='" +
quarterWidth +
"']/quarter"
).forEach((quarterNode) => {
const quarterNo = parseInt(quarterNode.getAttribute('type'), 10) - 1;
quarterNames = quarterNames || {};
quarterNames[quarterContextCamelCase] =
quarterNames[quarterContextCamelCase] || {};
quarterNames[quarterContextCamelCase][quarterWidth] =
quarterNames[quarterContextCamelCase][quarterWidth] || {};
quarterNames[quarterContextCamelCase][quarterWidth][quarterNo] =
quarterNames[quarterContextCamelCase][quarterWidth][quarterNo] ||
quarterNode.textContent;
});
});
});
return convertObjectsWithIntegerKeysToArrays(quarterNames);
},
extractDayPeriods(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let dayPeriods;
['format', 'stand-alone'].forEach((dayPeriodContext) => {
const dayPeriodContextCamelCase = normalizeProperty(dayPeriodContext); // stand-alone => standAlone
['abbreviated', 'narrow', 'wide', 'short'].forEach((dayPeriodWidth) => {
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/dayPeriods/dayPeriodContext[@type='" +
dayPeriodContext +
"']/dayPeriodWidth[@type='" +
dayPeriodWidth +
"']/dayPeriod"
).forEach((dayPeriodNode) => {
const type = dayPeriodNode.getAttribute('type');
dayPeriods = dayPeriods || {};
dayPeriods[dayPeriodContextCamelCase] =
dayPeriods[dayPeriodContextCamelCase] || {};
dayPeriods[dayPeriodContextCamelCase][dayPeriodWidth] =
dayPeriods[dayPeriodContextCamelCase][dayPeriodWidth] || {};
dayPeriods[dayPeriodContextCamelCase][dayPeriodWidth][type] =
dayPeriods[dayPeriodContextCamelCase][dayPeriodWidth][type] ||
dayPeriodNode.textContent;
});
});
});
return dayPeriods;
},
extractCyclicNames(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let cyclicNames;
['dayParts', 'days', 'months', 'years', 'zodiacs'].forEach(
(cyclicNameSet) => {
['format'].forEach((cyclicNameContext) => {
['abbreviated', 'narrow', 'wide'].forEach((cyclicNameWidth) => {
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/cyclicNameSets/cyclicNameSet[@type='" +
cyclicNameSet +
"']/cyclicNameContext[@type='" +
cyclicNameContext +
"']/cyclicNameWidth[@type='" +
cyclicNameWidth +
"']/cyclicName"
).forEach((cyclicNameNode) => {
const type = cyclicNameNode.getAttribute('type');
cyclicNames = cyclicNames || {};
cyclicNames[cyclicNameSet] = cyclicNames[cyclicNameSet] || {};
cyclicNames[cyclicNameSet][cyclicNameContext] =
cyclicNames[cyclicNameSet][cyclicNameContext] || {};
cyclicNames[cyclicNameSet][cyclicNameContext][cyclicNameWidth] =
cyclicNames[cyclicNameSet][cyclicNameContext][
cyclicNameWidth
] || {};
cyclicNames[cyclicNameSet][cyclicNameContext][cyclicNameWidth][
type
] =
cyclicNames[cyclicNameSet][cyclicNameContext][cyclicNameWidth][
type
] || cyclicNameNode.textContent;
});
});
});
}
);
return convertObjectsWithIntegerKeysToArrays(cyclicNames);
},
extractMonthNames(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let monthNames;
['format', 'stand-alone'].forEach((monthContext) => {
const monthContextCamelCase = normalizeProperty(monthContext); // stand-alone => standAlone
['abbreviated', 'narrow', 'wide'].forEach((monthWidth) => {
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/months/monthContext[@type='" +
monthContext +
"']/monthWidth[@type='" +
monthWidth +
"']/month"
).forEach((monthNode) => {
const monthNo = parseInt(monthNode.getAttribute('type'), 10) - 1;
monthNames = monthNames || {};
monthNames[monthContextCamelCase] =
monthNames[monthContextCamelCase] || {};
monthNames[monthContextCamelCase][monthWidth] =
monthNames[monthContextCamelCase][monthWidth] || {};
monthNames[monthContextCamelCase][monthWidth][monthNo] =
monthNames[monthContextCamelCase][monthWidth][monthNo] ||
monthNode.textContent;
});
});
});
return convertObjectsWithIntegerKeysToArrays(monthNames);
},
extractMonthPatterns(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let monthPatterns;
['format', 'numeric', 'stand-alone'].forEach((monthPatternContext) => {
const monthPatternContextCamelCase =
normalizeProperty(monthPatternContext); // stand-alone => standAlone
['abbreviated', 'narrow', 'wide', 'all'].forEach((monthPatternWidth) => {
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/monthPatterns/monthPatternContext[@type='" +
monthPatternContext +
"']/monthPatternWidth[@type='" +
monthPatternWidth +
"']/monthPattern"
).forEach((monthPatternNode) => {
const type = monthPatternNode.getAttribute('type');
monthPatterns = monthPatterns || {};
monthPatterns[monthPatternContextCamelCase] =
monthPatterns[monthPatternContextCamelCase] || {};
monthPatterns[monthPatternContextCamelCase][monthPatternWidth] =
monthPatterns[monthPatternContextCamelCase][monthPatternWidth] ||
{};
monthPatterns[monthPatternContextCamelCase][monthPatternWidth][type] =
monthPatterns[monthPatternContextCamelCase][monthPatternWidth][
type
] || monthPatternNode.textContent;
});
});
});
return monthPatterns;
},
extractDayNames(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const dayNoByCldrId = {
sun: 0,
mon: 1,
tue: 2,
wed: 3,
thu: 4,
fri: 5,
sat: 6,
};
let dayNames;
['format', 'numeric', 'stand-alone'].forEach((dayContext) => {
const dayContextCamelCase = normalizeProperty(dayContext); // stand-alone => standAlone
['abbreviated', 'narrow', 'wide', 'short'].forEach((dayWidth) => {
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/days/dayContext[@type='" +
dayContext +
"']/dayWidth[@type='" +
dayWidth +
"']/day"
).forEach((dayNode) => {
const dayNo = dayNoByCldrId[dayNode.getAttribute('type')];
dayNames = dayNames || {};
dayNames[dayContextCamelCase] = dayNames[dayContextCamelCase] || {};
dayNames[dayContextCamelCase][dayWidth] =
dayNames[dayContextCamelCase][dayWidth] || {};
dayNames[dayContextCamelCase][dayWidth][dayNo] =
dayNames[dayContextCamelCase][dayWidth][dayNo] ||
dayNode.textContent;
});
});
});
return convertObjectsWithIntegerKeysToArrays(dayNames);
},
extractFields(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let fields;
finder('/ldml/dates/fields/field/displayName').forEach(
(fieldDisplayNameNode) => {
const fieldName = fieldDisplayNameNode.parentNode.getAttribute('type');
fields = fields || {};
fields[fieldName] = fields[fieldName] || {};
fields[fieldName].displayName =
fields[fieldName].displayName || fieldDisplayNameNode.textContent;
}
);
finder('/ldml/dates/fields/field/relative').forEach((fieldRelativeNode) => {
const fieldName = fieldRelativeNode.parentNode.getAttribute('type');
const type = fieldRelativeNode.getAttribute('type');
fields = fields || {};
fields[fieldName] = fields[fieldName] || {};
fields[fieldName].relative = fields[fieldName].relative || {};
fields[fieldName].relative[type] =
fields[fieldName].relative[type] || fieldRelativeNode.textContent;
});
finder('/ldml/dates/fields/field/relativeTime/relativeTimePattern').forEach(
(relativeTimePatternNode) => {
const relativeTimeNode = relativeTimePatternNode.parentNode;
const fieldName = relativeTimeNode.parentNode.getAttribute('type');
const type = relativeTimeNode.getAttribute('type');
const count = relativeTimePatternNode.getAttribute('count');
fields = fields || {};
fields[fieldName] = fields[fieldName] || {};
fields[fieldName].relativeTime = fields[fieldName].relativeTime || {};
fields[fieldName].relativeTime[type] =
fields[fieldName].relativeTime[type] || {};
fields[fieldName].relativeTime[type][count] =
fields[fieldName].relativeTime[type][count] ||
relativeTimePatternNode.textContent;
}
);
return fields;
},
extractDateTimePatterns(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let dateTimePatterns;
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/dateTimeFormats/dateTimeFormatLength/dateTimeFormat"
).forEach((dateTimeFormatNode) => {
const dateTimeFormatLengthType =
dateTimeFormatNode.parentNode.getAttribute('type');
const patternNodes = xpath.select('pattern', dateTimeFormatNode);
if (patternNodes.length !== 1) {
throw new Error('Expected exactly one pattern in dateTimeFormatNode');
}
dateTimePatterns = dateTimePatterns || {};
dateTimePatterns[dateTimeFormatLengthType] =
dateTimePatterns[dateTimeFormatLengthType] ||
patternNodes[0].textContent;
});
return dateTimePatterns;
},
extractDateOrTimeFormats(localeId, calendarId, dateOrTime) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let formats;
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/" +
dateOrTime +
'Formats/' +
dateOrTime +
'FormatLength/' +
dateOrTime +
'Format/*'
).forEach((patternNode) => {
const type = patternNode.parentNode.parentNode.getAttribute('type');
formats = formats || {};
formats[type] = formats[type] || patternNode.textContent;
});
return formats;
},
extractDateFormats(localeId, calendarId) {
this.checkValidLocaleId(localeId);
return this.extractDateOrTimeFormats(localeId, calendarId, 'date');
},
extractTimeFormats(localeId, calendarId) {
this.checkValidLocaleId(localeId);
return this.extractDateOrTimeFormats(localeId, calendarId, 'time');
},
extractDateFormatItems(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let dateFormatItems;
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/dateTimeFormats/availableFormats/dateFormatItem"
).forEach((dateFormatItemNode) => {
const id = dateFormatItemNode.getAttribute('id');
dateFormatItems = dateFormatItems || {};
dateFormatItems[id] =
dateFormatItems[id] || dateFormatItemNode.textContent;
});
return dateFormatItems;
},
extractDateIntervalFormats(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let dateIntervalFormats;
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/dateTimeFormats/intervalFormats/intervalFormatItem"
).forEach((intervalFormatItemNode) => {
const dateIntervalFormat = {};
for (let i = 0; i < intervalFormatItemNode.childNodes.length; i += 1) {
const greatestDifferenceNode = intervalFormatItemNode.childNodes[i];
if (greatestDifferenceNode.nodeType !== 1) {
// Skip whitespace node
continue;
}
const greatestDifferenceIdAttribute =
greatestDifferenceNode.getAttribute('id');
const greatestDifferenceId = greatestDifferenceIdAttribute;
dateIntervalFormat[greatestDifferenceId] =
dateIntervalFormat[greatestDifferenceId] ||
greatestDifferenceNode.textContent;
}
const id = intervalFormatItemNode.getAttribute('id');
dateIntervalFormats = dateIntervalFormats || {};
dateIntervalFormats[id] = dateIntervalFormats[id] || dateIntervalFormat;
});
return dateIntervalFormats;
},
extractDateIntervalFallbackFormat(localeId, calendarId) {
this.checkValidLocaleId(localeId);
calendarId = calendarId || 'gregorian';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let dateIntervalFallbackFormat;
finder(
"/ldml/dates/calendars/calendar[@type='" +
calendarId +
"']/dateTimeFormats/intervalFormats/intervalFormatFallback"
).forEach((intervalFormatFallbackNode) => {
dateIntervalFallbackFormat =
dateIntervalFallbackFormat || intervalFormatFallbackNode.textContent;
});
return dateIntervalFallbackFormat;
},
// Number extraction code:
extractNumberSymbols(localeId, numberSystemId) {
this.checkValidLocaleId(localeId);
numberSystemId = numberSystemId || 'latn';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let numberSymbols;
finder(
"/ldml/numbers/symbols[@numberSystem = '" +
numberSystemId +
"']/*[name() != 'alias']"
)
.concat(finder("/ldml/numbers/symbols/*[name() != 'alias']"))
.forEach((numberSymbolNode) => {
const symbolId = numberSymbolNode.nodeName;
numberSymbols = numberSymbols || {};
numberSymbols[symbolId] =
numberSymbols[symbolId] || numberSymbolNode.textContent;
});
return numberSymbols;
},
extractNumberFormats(localeId, numberSystemId) {
this.checkValidLocaleId(localeId);
numberSystemId = numberSystemId || 'latn';
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let numberFormats;
['scientific', 'decimal', 'currency', 'percent'].forEach((formatType) => {
['full', 'long', 'medium', 'short'].forEach((length) => {
finder(
'/ldml/numbers/' +
formatType +
"Formats[@numberSystem = '" +
numberSystemId +
"']/" +
formatType +
"FormatLength[@type='" +
length +
"']/" +
formatType +
'Format/pattern'
).forEach((patternNode) => {
const type = patternNode.getAttribute('type');
const count = patternNode.getAttribute('count');
numberFormats = numberFormats || {};
numberFormats[formatType] = numberFormats[formatType] || {};
numberFormats[formatType][length] =
numberFormats[formatType][length] || {};
numberFormats[formatType][length][type] =
numberFormats[formatType][length][type] || {};
numberFormats[formatType][length][type][count] =
numberFormats[formatType][length][type][count] ||
patternNode.textContent;
});
});
finder(
'/ldml/numbers/' +
formatType +
"Formats[@numberSystem = '" +
numberSystemId +
"']/" +
formatType +
'FormatLength[not(@type)]/' +
formatType +
'Format/pattern'
).forEach((patternNode) => {
numberFormats = numberFormats || {};
numberFormats[formatType] = numberFormats[formatType] || {};
numberFormats[formatType].default =
numberFormats[formatType].default || patternNode.textContent;
});
finder(
'/ldml/numbers/' +
formatType +
"Formats[@numberSystem = '" +
numberSystemId +
"']/unitPattern"
).forEach((unitPatternNode) => {
const count = unitPatternNode.getAttribute('count');
numberFormats = numberFormats || {};
numberFormats[formatType] = numberFormats[formatType] || {};
numberFormats[formatType][count] =
numberFormats[formatType][count] || unitPatternNode.textContent;
});
});
finder(
"/ldml/numbers/currencyFormats[@numberSystem = '" +
numberSystemId +
"']/currencySpacing"
).forEach((currencySpacingNode) => {
numberFormats = numberFormats || {};
numberFormats.currency = numberFormats.currency || {};
numberFormats.currency.currencySpacing =
numberFormats.currency.currencySpacing || {};
['before', 'after'].forEach((place) => {
const placeData = (numberFormats.currency.currencySpacing[
place + 'Currency'
] = numberFormats.currency.currencySpacing[place + 'Currency'] || {});
['currencyMatch', 'surroundingMatch', 'insertBetween'].forEach(
(spacingPropertyName) => {
const match = xpath.select(
place + 'Currency/' + spacingPropertyName,
currencySpacingNode
);
if (match.length > 0) {
numberFormats.currency.currencySpacing[place + 'Currency'][
spacingPropertyName
] = match[0].textContent;
}
}
);
['currencyMatch', 'surroundingMatch'].forEach((spacingPropertyName) => {
if (placeData[spacingPropertyName]) {
placeData[spacingPropertyName] =
unicoderegexp.expandCldrUnicodeSetIdToCharacterClass(
placeData[spacingPropertyName]
);
}
});
});
});
return numberFormats;
},
extractDefaultNumberSystemId(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
let defaultNumberSystemId;
finder('/ldml/numbers/defaultNumberingSystem').forEach(
(defaultNumberingSystemNode) => {
defaultNumberSystemId =
defaultNumberSystemId || defaultNumberingSystemNode.textContent;
}
);
return defaultNumberSystemId;
},
extractUnitPatterns(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const unitPatterns = {};
finder('/ldml/units/unitLength/unit/unitPattern').forEach(
(unitPatternNode) => {
const unitNode = unitPatternNode.parentNode;
const unitLength = unitNode.parentNode.getAttribute('type');
const unitId = normalizeProperty(unitNode.getAttribute('type'));
unitPatterns[unitLength] = unitPatterns[unitLength] || {};
unitPatterns[unitLength].unit = unitPatterns[unitLength].unit || {};
unitPatterns[unitLength].unit[unitId] =
unitPatterns[unitLength].unit[unitId] || {};
const count = unitPatternNode.getAttribute('count');
unitPatterns[unitLength].unit[unitId][count] =
unitPatterns[unitLength].unit[unitId][count] ||
unitPatternNode.textContent;
}
);
finder('/ldml/units/unitLength/compoundUnit/compoundUnitPattern').forEach(
(compoundUnitPatternNode) => {
const compoundUnitNode = compoundUnitPatternNode.parentNode;
const unitLength = compoundUnitNode.parentNode.getAttribute('type');
const compoundUnitId = compoundUnitNode.getAttribute('type');
unitPatterns[unitLength].compoundUnit =
unitPatterns[unitLength].compoundUnit || {};
unitPatterns[unitLength].compoundUnit[compoundUnitId] =
compoundUnitPatternNode.textContent;
}
);
return unitPatterns;
},
extractDelimiters(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const delimiters = {};
finder('/ldml/delimiters/*').forEach((delimiterNode) => {
const type = delimiterNode.nodeName;
delimiters[type] = delimiters[type] || delimiterNode.textContent;
});
return delimiters;
},
extractListPatterns(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const listPatterns = {};
finder('/ldml/listPatterns/listPattern/listPatternPart').forEach(
(listPatternPartNode) => {
const listPatternTypeAttribute =
listPatternPartNode.parentNode.getAttribute('type');
const type = listPatternTypeAttribute
? normalizeProperty(listPatternTypeAttribute)
: 'default';
const part = listPatternPartNode.getAttribute('type');
listPatterns[type] = listPatterns[type] || {};
listPatterns[type][part] =
listPatterns[type][part] || listPatternPartNode.textContent;
}
);
return listPatterns;
},
extractCharacters(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const characters = {
exemplar: {},
ellipsis: {},
};
finder('/ldml/characters/exemplarCharacters').forEach(
(exemplarCharactersNode) => {
const typeAttr = exemplarCharactersNode.getAttribute('type');
const type = typeAttr || 'default';
characters.exemplar[type] =
characters.exemplar[type] ||
exemplarCharactersNode.textContent.replace(/^\[|\]$/g, '').split(' ');
}
);
finder('/ldml/characters/ellipsis').forEach((ellipsisNode) => {
const type = ellipsisNode.getAttribute('type');
characters.ellipsis[type] =
characters.ellipsis[type] || ellipsisNode.textContent;
});
finder('/ldml/characters/moreInformation').forEach(
(moreInformationNode) => {
characters.moreInformation =
characters.moreInformation || moreInformationNode.textContent;
}
);
return characters;
},
/**
* @param {string} localeId
* @param {string} cardinalOrOrdinal - 'cardinal' or 'ordinal'
* @returns {CldrPluralRuleSet} A set of plural rules
*/
_extractPluralRules(localeId, cardinalOrOrdinal) {
this.checkValidLocaleId(localeId);
const that = this;
const document = that.getDocument(
Path.resolve(
that.cldrPath,
'common',
'supplemental',
cardinalOrOrdinal === 'ordinal' ? 'ordinals.xml' : 'plurals.xml'
)
);
const subLocaleIds = that.expandLocaleIdToPrioritizedList(localeId);
let pluralRules = new CldrPluralRuleSet();
for (let i = 0; i < subLocaleIds.length; i += 1) {
const subLocaleId = subLocaleIds[i];
const matchLocalesXPathExpr =
"@locales = '" +
subLocaleId +
"' or " +
"starts-with(@locales, '" +
subLocaleId +
"') or " +
"contains(@locales, ' " +
subLocaleId +
" ') or " +
"substring(@locales, string-length(@locales) - string-length(' " +
subLocaleId +
"') + 1) = ' " +
subLocaleId +
"'";
const pluralRulesNodes = xpath.select(
'/supplementalData/plurals/pluralRules[' + matchLocalesXPathExpr + ']',
document
);
const cldrPluralRuleSet = new CldrPluralRuleSet();
if (pluralRulesNodes.length > 0) {
xpath
.select('pluralRule', pluralRulesNodes[0])
.forEach((pluralRuleNode) => {
cldrPluralRuleSet.addRule(
pluralRuleNode.textContent,
pluralRuleNode.getAttribute('count')
);
});
pluralRules = cldrPluralRuleSet;
break;
}
}
return pluralRules;
},
_extractPluralRuleAst(localeId, cardinalOrOrdinal) {
this.checkValidLocaleId(localeId);
return this._extractPluralRules(
localeId,
cardinalOrOrdinal
).toJavaScriptFunctionBodyAst();
},
extractPluralClasses(localeId, cardinalOrOrdinal) {
this.checkValidLocaleId(localeId);
return this._extractPluralRules(
localeId,
cardinalOrOrdinal
).availablePluralClasses();
},
extractPluralRuleFunction(localeId, cardinalOrOrdinal) {
this.checkValidLocaleId(localeId);
// eslint-disable-next-line no-new-func
return new Function(
'val',
escodegen.generate({
type: 'Program',
body: this._extractPluralRuleAst(localeId, cardinalOrOrdinal),
})
);
},
// 'types' is optional, defaults to all available
extractRbnfFunctionByType(localeId, types) {
this.checkValidLocaleId(localeId);
const cardinalPluralRuleAst = this._extractPluralRuleAst(
localeId,
'cardinal'
);
const ordinalPluralRuleAst = this._extractPluralRuleAst(
localeId,
'ordinal'
);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'rbnf')
);
const cldrRbnfRuleSetByType = {};
for (const rbnfRuleNode of finder(
'/ldml/rbnf/rulesetGrouping/ruleset/rbnfrule'
)) {
const type = CldrRbnfRuleSet.getSafeRendererName(
rbnfRuleNode.parentNode.getAttribute('type')
);
const value = rbnfRuleNode.getAttribute('value');
cldrRbnfRuleSetByType[type] =
cldrRbnfRuleSetByType[type] ||
new CldrRbnfRuleSet({
type,
cardinalPluralRuleAst,
ordinalPluralRuleAst,
});
if (!cldrRbnfRuleSetByType[type].ruleByValue[value]) {
const radixAttribute = rbnfRuleNode.getAttribute('radix');
cldrRbnfRuleSetByType[type].ruleByValue[value] = {
value,
rbnf: rbnfRuleNode.textContent
.replace(/;$/, '')
.replace(/←/g, '<')
.replace(/→/g, '>'),
radix: radixAttribute,
};
}
}
const isAddedByType = {};
const typesToAdd = types
? [].concat(types)
: Object.keys(cldrRbnfRuleSetByType);
const defaultNumberingSystem = this.extractDefaultNumberSystemId(localeId);
const numberingSystem = this.extractNumberingSystem(defaultNumberingSystem);
if (numberingSystem.type === 'algorithmic') {
typesToAdd.push(numberingSystem.rules);
}
const zeroCharCode = '0'.charCodeAt(0);
const rbnfFunctionByType = {
// Provide a (bad) default number rendering implementation to avoid #13
renderNumber(number) {
if (numberingSystem.type === 'numeric') {
return String(number)
.split(/(?:)/)
.map((ch) => {
return (
numberingSystem.digits[ch.charCodeAt(0) - zeroCharCode] || ch
);
})
.join('');
} else {
// numberingSystem.type === 'algorithmic'
return this[numberingSystem.rules](number);
}
},
};
while (typesToAdd.length > 0) {
const type = typesToAdd.shift();
if (!(type in isAddedByType)) {
isAddedByType[type] = true;
const cldrRbnfRuleSet = cldrRbnfRuleSetByType[type];
// Some rules aren't available in some locales (such as spellout-cardinal-financial).
// The easiest thing is just to skip the missing ones here, even though it can produce
// some broken function sets:
if (cldrRbnfRuleSet) {
const result = cldrRbnfRuleSet.toFunctionAst();
// eslint-disable-next-line no-new-func
rbnfFunctionByType[type] = new Function(
'n',
escodegen.generate({
type: 'Program',
body: result.functionAst.body.body,
})
);
Array.prototype.push.apply(typesToAdd, result.dependencies);
}
}
}
return rbnfFunctionByType;
},
extractNumberingSystem(numberingSystemId) {
const document = this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'numberingSystems.xml'
)
);
const numberingSystemNode = xpath.select1(
`/supplementalData/numberingSystems/numberingSystem[@id="${numberingSystemId}"]`,
document
);
if (!numberingSystemNode) {
throw new Error(`Unknown numbering system: ${numberingSystemId}`);
}
const type = numberingSystemNode.getAttribute('type');
const numberingSystem = {
type,
};
if (type === 'numeric') {
numberingSystem.digits = numberingSystemNode
.getAttribute('digits')
.split(/(?:)/u);
} else {
// type='algorithmic'
const rulesAttributeFragments = numberingSystemNode
.getAttribute('rules')
.split('/');
numberingSystem.rules = CldrRbnfRuleSet.getSafeRendererName(
rulesAttributeFragments[rulesAttributeFragments.length - 1]
);
if (rulesAttributeFragments.length > 1) {
// Eg. [ "zh", "SpelloutRules", "spellout-numbering-days" ]
numberingSystem.locale = rulesAttributeFragments[0];
}
}
return numberingSystem;
},
// Deprecated
extractDigitsByNumberSystemId() {
const document = this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'numberingSystems.xml'
)
);
const digitsByNumberSystemId = {};
xpath
.select('/supplementalData/numberingSystems/numberingSystem', document)
.forEach((numberingSystemNode) => {
const numberSystemId = numberingSystemNode.getAttribute('id');
if (numberingSystemNode.getAttribute('type') === 'numeric') {
digitsByNumberSystemId[numberSystemId] = numberingSystemNode
.getAttribute('digits')
.split(/(?:)/u);
} else {
// type='algorithmic'
const rulesAttributeFragments = numberingSystemNode
.getAttribute('rules')
.split('/');
const ruleType = CldrRbnfRuleSet.getSafeRendererName(
rulesAttributeFragments[rulesAttributeFragments.length - 1]
);
digitsByNumberSystemId[numberSystemId] = ruleType; // A string value means "use this rbnf renderer for the digits"
}
}, this);
return digitsByNumberSystemId;
},
extractLayout(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'main')
);
const layout = {};
for (const leafNode of finder('/ldml/layout/*/*')) {
const type = leafNode.nodeName;
const parentType = leafNode.parentNode.nodeName;
layout[parentType] = layout[parentType] || {};
layout[parentType][type] =
layout[parentType][type] || leafNode.textContent;
}
return layout;
},
extractTerritories() {
const finder = this.createFinder([
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'supplementalData.xml'
)
),
]);
const territoryInfoByTerritoryId = {};
for (const territoryCodeNode of finder(
'/supplementalData/codeMappings/territoryCodes'
)) {
const type = territoryCodeNode.getAttribute('type');
const numericCode = territoryCodeNode.getAttribute('numeric');
const alpha3Code = territoryCodeNode.getAttribute('alpha3');
const countryInfo = {
alpha2Code: type, // ISO 3166-1 alpha-2
};
if (alpha3Code) {
// ISO 3166-1 alpha-3
countryInfo.alpha3Code = alpha3Code;
}
if (numericCode) {
// UN M.49 / ISO-3166-1 numeric-3
// A numeric code may be reused by another country, e.g. Zaire and the DRC both have numeric code 180, but only the DRC currently exists
// Entries without a numeric code are few, which are usually exceptional reservations in ISO 3166-1 alpha-2. Example: IC, Canary Islands
countryInfo.numericCode = numericCode;
}
territoryInfoByTerritoryId[type] = countryInfo;
}
return territoryInfoByTerritoryId;
},
extractTerritoryInfo() {
const finder = this.createFinder([
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'supplementalData.xml'
)
),
]);
const territoryInfoByTerritoryId = {};
for (const territoryNode of finder(
'/supplementalData/territoryInfo/territory'
)) {
const territoryId = territoryNode.getAttribute('type');
territoryInfoByTerritoryId[territoryId] = {
id: territoryId,
gdp: parseInt(territoryNode.getAttribute('gdp'), 10),
literacyPercent: parseFloat(
territoryNode.getAttribute('literacyPercent')
),
population: parseInt(territoryNode.getAttribute('population'), 10),
languages: [],
};
}
for (const languagePopulationNode of finder(
'/supplementalData/territoryInfo/territory/languagePopulation'
)) {
const territoryId =
languagePopulationNode.parentNode.getAttribute('type');
const languageInfo = {
id: languagePopulationNode.getAttribute('type'),
populationPercent: parseFloat(
languagePopulationNode.getAttribute('populationPercent')
),
};
const officialStatus =
languagePopulationNode.getAttribute('officialStatus');
if (officialStatus) {
languageInfo.officialStatus = officialStatus;
}
const writingPercent =
languagePopulationNode.getAttribute('writingPercent');
if (writingPercent) {
languageInfo.writingPercent = parseFloat(writingPercent);
}
territoryInfoByTerritoryId[territoryId].languages.push(languageInfo);
}
return territoryInfoByTerritoryId;
},
extractTerritoryContainmentGroups() {
const finder = this.createFinder([
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'supplementalData.xml'
)
),
]);
const territoryContainmentGroups = {};
const isSeenByType = {};
const parentRegionIdByType = {};
for (const groupNode of finder(
'/supplementalData/territoryContainment/group'
)) {
const type = groupNode.getAttribute('type');
const contains = groupNode.getAttribute('contains').split(' ');
if (!isSeenByType[type]) {
// Only look at the first occurence of a 'type', the first one overrides any items after it with the same type
isSeenByType[type] = true;
territoryContainmentGroups[type] = {
type,
contains,
};
contains.forEach((id) => {
parentRegionIdByType[id] = type;
});
}
}
for (const type of Object.keys(territoryContainmentGroups)) {
// Territory containment groups that are not themselves somehow linked to the root world group '001', are not exposed because they're not part of the tree structure
if (!(type in parentRegionIdByType)) {
if (type !== '001') {
delete territoryContainmentGroups[type];
}
} else {
territoryContainmentGroups[type].parent = parentRegionIdByType[type];
}
}
return territoryContainmentGroups;
},
extractSubdivisionContainmentGroups() {
const finder = this.createFinder([
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'subdivisions.xml'
)
),
]);
const subdivisionContainmentGroups = {};
const parentRegionIdByType = {};
for (const groupNode of finder(
'/supplementalData/subdivisionContainment/subgroup'
)) {
const type = groupNode.getAttribute('type');
const contains = groupNode.getAttribute('contains').split(' ');
subdivisionContainmentGroups[type] = {
type,
contains,
};
for (const id of contains) {
parentRegionIdByType[id] = type;
}
}
for (const type of Object.keys(subdivisionContainmentGroups)) {
if (typeof parentRegionIdByType[type] !== 'undefined') {
subdivisionContainmentGroups[type].parent = parentRegionIdByType[type];
}
}
return subdivisionContainmentGroups;
},
extractLanguageSupplementalData() {
const finder = this.createFinder([
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'supplementalData.xml'
)
),
]);
const languageData = {};
for (const languageNode of finder(
'/supplementalData/languageData/language'
)) {
const type = languageNode.getAttribute('type');
const isSecondary = languageNode.getAttribute('alt') === 'secondary';
let record;
if (!languageData[type]) {
languageData[type] = record = {};
}
if (isSecondary) {
languageData[type].secondary = record = {};
}
const scriptsAttr = languageNode.getAttribute('scripts');
if (scriptsAttr) {
record.scripts = scriptsAttr.split(' ');
}
const territoriesAttr = languageNode.getAttribute('territories');
if (territoriesAttr) {
record.territories = territoriesAttr.split(' ');
}
}
return languageData;
},
extractLanguageSupplementalMetadata() {
const finder = this.createFinder([
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'supplementalMetadata.xml'
)
),
]);
const languageData = {};
for (const languageAliasNode of finder(
'/supplementalData/metadata/alias/languageAlias'
)) {
const type = languageAliasNode.getAttribute('type');
languageData[type] = {
replacement: languageAliasNode.getAttribute('replacement'),
reason: languageAliasNode.getAttribute('reason'),
};
}
return languageData;
},
/**
* Weekdata contains various week related info, grouped by territory or locale
* Defaults are indicated by territory="001" or locale="und"
*
* https://www.unicode.org/reports/tr35/tr35-55/tr35-dates.html#Week_Data
*/
extractWeekData() {
const finder = this.createFinder([
this.getDocument(
Path.resolve(
this.cldrPath,
'common',
'supplemental',
'supplementalData.xml'
)
),
]);
const weekData = {
minDays: [],
firstDay: [],
weekendStart: [],
weekendEnd: [],
weekOfPreference: [],
};
for (const minDaysNode of finder('/supplementalData/weekData/minDays')) {
const territories = minDaysNode
.getAttribute('territories')
.split(/\s+/)
.filter((x) => x);
const count = minDaysNode.getAttribute('count');
weekData.minDays.push({
territories,
count,
});
}
for (const firstDayNode of finder('/supplementalData/weekData/firstDay')) {
const territories = firstDayNode
.getAttribute('territories')
.split(/\s+/)
.filter((x) => x);
const day = firstDayNode.getAttribute('day');
const alt = firstDayNode.getAttribute('alt');
weekData.firstDay.push({
territories,
day,
variant: alt === 'variant',
});
}
for (const weekendStartNode of finder(
'/supplementalData/weekData/weekendStart'
)) {
const territories = weekendStartNode
.getAttribute('territories')
.split(/\s+/)
.filter((x) => x);
const day = weekendStartNode.getAttribute('day');
weekData.weekendStart.push({
territories,
day,
});
}
for (const weekendEndNode of finder(
'/supplementalData/weekData/weekendEnd'
)) {
const territories = weekendEndNode
.getAttribute('territories')
.split(/\s+/)
.filter((x) => x);
const day = weekendEndNode.getAttribute('day');
weekData.weekendEnd.push({
territories,
day,
});
}
for (const weekOfPreferenceNode of finder(
'/supplementalData/weekData/weekOfPreference'
)) {
const locales = weekOfPreferenceNode.getAttribute('locales').split(/\s+/);
const ordering = weekOfPreferenceNode.getAttribute('ordering');
weekData.weekOfPreference.push({
locales,
ordering,
});
}
return weekData;
},
extractTextToSpeechCharacterLabels(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'annotations')
);
const characterLabels = {};
for (const node of finder('/ldml/annotations/annotation[@type="tts"]')) {
characterLabels[node.getAttribute('cp')] =
characterLabels[node.getAttribute('cp')] || node.textContent;
}
return characterLabels;
},
extractDerivedTextToSpeechCharacterLabels(localeId) {
this.checkValidLocaleId(localeId);
const finder = this.createFinder(
this.getPrioritizedDocumentsForLocale(localeId, 'annotationsDerived')
);
const characterLabels = {};
for (const node of finder('/ldml/annotations/annotation[@type="tts"]')) {
characterLabels[node.getAttribute('cp')] =
characterLabels[node.getAttribute('cp')] || node.textContent;
}
return characterLabels;
},
extractAllTextToSpeechCharacterLabels(localeId) {
return {
...this.extractDerivedTextToSpeechCharacterLabels(localeId),
...this.extractTextToSpeechCharacterLabels(localeId),
};
},
};
module.exports = new Cldr(Path.resolve(__dirname, '../3rdparty/cldr/'));
module.exports.load = (cldrPath) => new Cldr(cldrPath);