/
githubmirror
/
oppia
Обзор
Документация
Войти
/
githubmirror
/
oppia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
core/templates/domain/objects/units.model.ts
390 строк
11 KB
Himkar_Singh
Fix the part #10700 of Refactor all migrated object factories to model.ts files in angular. (UnitsObjectFactory) (#22507)
03 май 2025, 14:19
Не верифицирован
03 май 2025, 14:19
b20abd3
Код
Авторство
О чём код?
// Copyright 2019 The Oppia Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview Model class for creating instances of Units domain objects. */ import {createUnit, unit} from 'mathjs'; import {ObjectsDomainConstants} from 'domain/objects/objects-domain.constants'; import {Unit} from 'interactions/answer-defs'; interface UnitsBackendDict { units: Unit[]; } interface UnitsDict { [unit: string]: number; } type CurrencyUnitsKeys = (keyof typeof ObjectsDomainConstants.CURRENCY_UNITS)[]; export class Units { units: Unit[]; constructor(unitsList: Unit[]) { this.units = unitsList; } toDict(): UnitsBackendDict { return { units: this.units, }; } toString(): string { var unit = ''; for (var i = 0; i < this.units.length; i++) { var d = this.units[i]; if (d.exponent === 1) { unit += d.unit + ' '; } else { unit += d.unit + '^' + d.exponent.toString() + ' '; } } return unit.trim(); } static isunit(unit: string): boolean { return !'/*() '.includes(unit); } static isLastElementUnit(unitList: string[]): boolean { return ( unitList.length > 0 && this.isunit(unitList.slice(-1).pop() as string) ); } static stringToLexical(units: string): string[] { units += '#'; var unitList = []; var unit = ''; for (var i = 0; i < units.length; i++) { if ('*/()# '.includes(units[i]) && unit !== 'per') { if (unit.length > 0) { if (this.isLastElementUnit(unitList)) { unitList.push('*'); } unitList.push(unit); unit = ''; } if (!'# '.includes(units[i])) { unitList.push(units[i]); } } else if (units[i] === ' ' && unit === 'per') { unitList.push('/'); unit = ''; } else { unit += units[i]; } } return unitList; } static unitWithMultiplier(unitList: string[]): [string, number][] { var multiplier = 1; var unitsWithMultiplier: [string, number][] = []; var parenthesisStack = []; for (var ind = 0; ind < unitList.length; ind++) { if (unitList[ind] === '/') { multiplier = -multiplier; } else if (unitList[ind] === '(') { if (unitList[ind - 1] === '/') { // If previous element was division then we need to inverse // multiplier when we find its corresponsing closing parenthesis. // Second element of pushed element is used for this purpose. parenthesisStack.push(['(', -1]); } else { // If previous element was not division then we don't need to // invert the multiplier. parenthesisStack.push(['(', 1]); } } else if (unitList[ind] === ')') { var elem = parenthesisStack.pop(); if (elem) { multiplier = parseInt(elem[1] as string) * multiplier; } else { throw new Error('Close parenthesis with no open parenthesis'); } } else if (this.isunit(unitList[ind])) { unitsWithMultiplier.push([unitList[ind], multiplier]); // If previous element was division then we need to invert // multiplier. if (unitList[ind - 1] === '/') { multiplier = -multiplier; } } } return unitsWithMultiplier; } static convertUnitDictToList(unitDict: UnitsDict): Unit[] { var unitList: Unit[] = []; for (var key in unitDict) { unitList.push({unit: key, exponent: unitDict[key]}); } return unitList; } static unitToList(unitsWithMultiplier: [string, number][]): Unit[] { var unitDict: UnitsDict = {}; for (var i = 0; i < unitsWithMultiplier.length; i++) { var unit = unitsWithMultiplier[i][0]; var multiplier = unitsWithMultiplier[i][1]; var ind = unit.indexOf('^'); var s = null; var power = null; if (ind > -1) { s = unit.substr(0, ind); power = parseInt(unit.substr(ind + 1)); } else { s = unit; power = 1; } if (!(s in unitDict)) { unitDict[s] = 0; } unitDict[s] += multiplier * power; } return this.convertUnitDictToList(unitDict); } static fromList(unitsList: Unit[]): Units { return new Units(unitsList); } static fromStringToList(unitsString: string): Unit[] { return this.unitToList( this.unitWithMultiplier(this.stringToLexical(unitsString)) ); } static createCurrencyUnits(): void { var keys = Object.keys( ObjectsDomainConstants.CURRENCY_UNITS ) as CurrencyUnitsKeys; for (var i = 0; i < keys.length; i++) { let baseUnitValue = ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].base_unit; if (baseUnitValue !== null) { // Sub unit (like: paise, cents etc.). createUnit(ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].name, { definition: baseUnitValue, aliases: Object.values( ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].aliases ), }); } else { // Base unit (like: rupees, dollar etc.). createUnit(ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].name, { aliases: Object.values( ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].aliases ), }); } } } static toMathjsCompatibleString(units: string): string { // Makes the units compatible with the math.js allowed format. units = units.replace(/per/g, '/'); // Special symbols need to be replaced as math.js doesn't support custom // units starting with special symbols. Also, it doesn't allow units // followed by a number as in the case of currency units. var keys = Object.keys( ObjectsDomainConstants.CURRENCY_UNITS ) as CurrencyUnitsKeys; for (var i = 0; i < keys.length; i++) { for ( var j = 0; j < ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].front_units.length; j++ ) { if ( units.includes( ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].front_units[j] ) ) { units = units.replace( ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].front_units[j], '' ); units = ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].name + units; } } for ( var j = 0; j < ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].aliases.length; j++ ) { if ( units.includes( ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].aliases[j] ) ) { units = units.replace( ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].aliases[j], ObjectsDomainConstants.CURRENCY_UNITS[keys[i]].name ); } } } return units.trim(); } static fromRawInputString(units: string): Units { try { this.createCurrencyUnits(); } catch (parsingError) {} var compatibleUnits = this.toMathjsCompatibleString(units); if (compatibleUnits !== '') { unit(compatibleUnits); } return new Units(this.fromStringToList(units)); } static getAllInstancesOfUnits(units: string[]): string[] { let unitInstances: string[] = []; units.forEach(unitString => { if (unitString.includes('^')) { this.getAllInstancesOfUnits(unitString.split('^')).forEach(baseUnit => { if (/^[a-zA-Z]+$/.test(baseUnit)) { unitInstances.push(baseUnit); } }); } else if (this.isunit(unitString) && unitString.length > 0) { let sanitizedUnit = unitString.replace(/[0-9]/g, ''); if (/^[a-zA-Z]+$/.test(sanitizedUnit)) { unitInstances.push(sanitizedUnit); } } }); return unitInstances; } static getDuplicatedUnit(units: string): string { let unitList = this.getAllInstancesOfUnits(this.stringToLexical(units)); let unitNormalizationMap: {[originalUnit: string]: string} = {}; unitList.forEach(unit => { unitNormalizationMap[unit] = ObjectsDomainConstants.UNIT_TO_NORMALIZED_UNIT_MAPPING[unit] || unit; }); let normalizedUnits = unitList.map(unit => unitNormalizationMap[unit]); let seenUnits = new Set<string>(); for (let i = 0; i < normalizedUnits.length; i++) { let normalizedUnit = normalizedUnits[i]; if (seenUnits.has(normalizedUnit)) { return ( unitList.find( (originalUnit, index) => unitNormalizationMap[originalUnit] === normalizedUnit && index < i ) || '' ); } seenUnits.add(normalizedUnit); } return ''; } static hasMultipleSlashes(units: string): boolean { let slashCount = (units.match(/\//g) || []).length; return slashCount > 1; } static getCorrectedFormat(units: { units: {unit: string; exponent: number}[]; }): string { let unitDetails = units.units; let unitNormalizationMap: {[unit: string]: string} = {}; unitDetails.forEach(({unit}) => { unitNormalizationMap[unit] = ObjectsDomainConstants.UNIT_TO_NORMALIZED_UNIT_MAPPING[unit] || unit; }); let unitToFirstInstanceMap: {[normalizedUnit: string]: string} = {}; unitDetails.forEach(({unit}) => { let normalizedUnit = unitNormalizationMap[unit]; if (!unitToFirstInstanceMap[normalizedUnit]) { unitToFirstInstanceMap[normalizedUnit] = unit; } }); let aggregatedExponents: {[finalUnit: string]: number} = {}; unitDetails.forEach(({unit, exponent}) => { let normalizedUnit = unitNormalizationMap[unit]; let originalUnit = unitToFirstInstanceMap[normalizedUnit]; if (aggregatedExponents[originalUnit]) { aggregatedExponents[originalUnit] += exponent; } else { aggregatedExponents[originalUnit] = exponent; } }); let numeratorUnits: string[] = []; let denominatorUnits: string[] = []; for (let unit in aggregatedExponents) { let exponent = aggregatedExponents[unit]; if (exponent > 0) { numeratorUnits.push(exponent === 1 ? `${unit}` : `${unit}^${exponent}`); } else if (exponent < 0) { denominatorUnits.push( exponent === -1 ? `${unit}` : `${unit}^${-exponent}` ); } } let numeratorPart = numeratorUnits.join(' '); let denominatorPart = denominatorUnits.join(' '); let correctedFormat = numeratorPart.length === 0 ? denominatorPart.length === 0 ? '' : denominatorUnits.length === 1 ? `1/${denominatorPart}` : `1/(${denominatorPart})` : denominatorPart.length === 0 ? numeratorPart : denominatorUnits.length === 1 ? `${numeratorPart}/${denominatorPart}` : `${numeratorPart}/(${denominatorPart})`; return correctedFormat; } }