1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5"use strict"; 6 7const EXPORTED_SYMBOLS = ["UnitConverterSimple"]; 8 9// NOTE: This units table need to be localized upon supporting multi locales 10// since it supports en-US only. 11// e.g. Should support plugada or funty as well for pound. 12const UNITS_GROUPS = [ 13 { 14 // Angle 15 degree: 1, 16 deg: "degree", 17 d: "degree", 18 radian: Math.PI / 180.0, 19 rad: "radian", 20 r: "radian", 21 gradian: 1 / 0.9, 22 grad: "gradian", 23 g: "gradian", 24 minute: 60, 25 min: "minute", 26 m: "minute", 27 second: 3600, 28 sec: "second", 29 s: "second", 30 sign: 1 / 30.0, 31 mil: 1 / 0.05625, 32 revolution: 1 / 360.0, 33 circle: 1 / 360.0, 34 turn: 1 / 360.0, 35 quadrant: 1 / 90.0, 36 rightangle: 1 / 90.0, 37 sextant: 1 / 60.0, 38 }, 39 { 40 // Force 41 newton: 1, 42 n: "newton", 43 kilonewton: 0.001, 44 kn: "kilonewton", 45 "gram-force": 101.9716213, 46 gf: "gram-force", 47 "kilogram-force": 0.1019716213, 48 kgf: "kilogram-force", 49 "ton-force": 0.0001019716213, 50 tf: "ton-force", 51 exanewton: 1.0e-18, 52 en: "exanewton", 53 petanewton: 1.0e-15, 54 PN: "petanewton", 55 Pn: "petanewton", 56 teranewton: 1.0e-12, 57 tn: "teranewton", 58 giganewton: 1.0e-9, 59 gn: "giganewton", 60 meganewton: 0.000001, 61 MN: "meganewton", 62 Mn: "meganewton", 63 hectonewton: 0.01, 64 hn: "hectonewton", 65 dekanewton: 0.1, 66 dan: "dekanewton", 67 decinewton: 10, 68 dn: "decinewton", 69 centinewton: 100, 70 cn: "centinewton", 71 millinewton: 1000, 72 mn: "millinewton", 73 micronewton: 1000000, 74 µn: "micronewton", 75 nanonewton: 1000000000, 76 nn: "nanonewton", 77 piconewton: 1000000000000, 78 pn: "piconewton", 79 femtonewton: 1000000000000000, 80 fn: "femtonewton", 81 attonewton: 1000000000000000000, 82 an: "attonewton", 83 dyne: 100000, 84 dyn: "dyne", 85 "joule/meter": 1, 86 "j/m": "joule/meter", 87 "joule/centimeter": 100, 88 "j/cm": "joule/centimeter", 89 "ton-force-short": 0.0001124045, 90 short: "ton-force-short", 91 "ton-force-long": 0.0001003611, 92 tonf: "ton-force-long", 93 "kip-force": 0.0002248089, 94 kipf: "kip-force", 95 "pound-force": 0.2248089431, 96 lbf: "pound-force", 97 "ounce-force": 3.5969430896, 98 ozf: "ounce-force", 99 poundal: 7.2330138512, 100 pdl: "poundal", 101 pond: 101.9716213, 102 p: "pond", 103 kilopond: 0.1019716213, 104 kp: "kilopond", 105 }, 106 { 107 // Length 108 meter: 1, 109 m: "meter", 110 nanometer: 1000000000, 111 micrometer: 1000000, 112 millimeter: 1000, 113 mm: "millimeter", 114 centimeter: 100, 115 cm: "centimeter", 116 kilometer: 0.001, 117 km: "kilometer", 118 mile: 0.0006213689, 119 mi: "mile", 120 yard: 1.0936132983, 121 yd: "yard", 122 foot: 3.280839895, 123 feet: "foot", 124 ft: "foot", 125 inch: 39.37007874, 126 inches: "inch", 127 in: "inch", 128 }, 129 { 130 // Mass 131 kilogram: 1, 132 kg: "kilogram", 133 gram: 1000, 134 g: "gram", 135 milligram: 1000000, 136 mg: "milligram", 137 ton: 0.001, 138 t: "ton", 139 longton: 0.0009842073, 140 "l.t.": "longton", 141 "l/t": "longton", 142 shortton: 0.0011023122, 143 "s.t.": "shortton", 144 "s/t": "shortton", 145 pound: 2.2046244202, 146 lbs: "pound", 147 lb: "pound", 148 ounce: 35.273990723, 149 oz: "ounce", 150 carat: 5000, 151 ffd: 5000, 152 }, 153]; 154 155// There are some units that will be same in lower case in same unit group. 156// e.g. Mn: meganewton and mn: millinewton on force group. 157// Handle them as case-sensitive. 158const CASE_SENSITIVE_UNITS = ["PN", "Pn", "MN", "Mn"]; 159 160const NUMBER_REGEX = "\\d+(?:\\.\\d+)?\\s*"; 161const UNIT_REGEX = "[A-Za-zµ0-9_./-]+"; 162 163// NOTE: This regex need to be localized upon supporting multi locales 164// since it supports en-US input format only. 165const QUERY_REGEX = new RegExp( 166 `^(${NUMBER_REGEX})(${UNIT_REGEX})(?:\\s+in\\s+|\\s+to\\s+|\\s*=\\s*)(${UNIT_REGEX})`, 167 "i" 168); 169 170const DECIMAL_PRECISION = 10; 171 172/** 173 * This module converts simple unit such as angle and length. 174 */ 175class UnitConverterSimple { 176 /** 177 * Convert the given search string. 178 * 179 * @param {string} searchString 180 * @returns {string} conversion result. 181 */ 182 convert(searchString) { 183 const regexResult = QUERY_REGEX.exec(searchString); 184 if (!regexResult) { 185 return null; 186 } 187 188 const target = findUnitGroup(regexResult[2], regexResult[3]); 189 190 if (!target) { 191 return null; 192 } 193 194 const { group, inputUnit, outputUnit } = target; 195 const inputNumber = Number(regexResult[1]); 196 const outputNumber = parseFloat( 197 ((inputNumber / group[inputUnit]) * group[outputUnit]).toPrecision( 198 DECIMAL_PRECISION 199 ) 200 ); 201 202 try { 203 return new Intl.NumberFormat("en-US", { 204 style: "unit", 205 unit: outputUnit, 206 maximumFractionDigits: DECIMAL_PRECISION, 207 }).format(outputNumber); 208 } catch (e) {} 209 210 return `${outputNumber} ${outputUnit}`; 211 } 212} 213 214/** 215 * Returns the suitable unit group and unit for the given two values. 216 * If could not found suitable unit group, returns null. 217 * 218 * @param {string} inputUnit 219 * @param {string} outputUnit 220 * @returns {object} 221 * { 222 * group: one in UNITS_GROUPS, 223 * inputUnit: input unit, 224 * outputUnit: output unit, 225 * } 226 */ 227function findUnitGroup(inputUnit, outputUnit) { 228 inputUnit = toSuitableUnit(inputUnit); 229 outputUnit = toSuitableUnit(outputUnit); 230 231 const group = UNITS_GROUPS.find(ug => ug[inputUnit] && ug[outputUnit]); 232 233 if (!group) { 234 return null; 235 } 236 237 const inputValue = group[inputUnit]; 238 const outputValue = group[outputUnit]; 239 240 return { 241 group, 242 inputUnit: typeof inputValue === "string" ? inputValue : inputUnit, 243 outputUnit: typeof outputValue === "string" ? outputValue : outputUnit, 244 }; 245} 246 247function toSuitableUnit(unit) { 248 return CASE_SENSITIVE_UNITS.includes(unit) ? unit : unit.toLowerCase(); 249} 250