footinch
Version:
A minimalistic parser for imperial lengths - feet and inch. Does metric too.
161 lines (143 loc) • 4.26 kB
JavaScript
// eslint-disable-next-line no-extra-semi
;(function() {
'use strict';
const root = this;
const previous_parse = root.parse;
const s_siPrefix = {
m : 1,
cm : 1e-2,
mm : 1e-3,
um : 1e-6,
nm : 1e-9,
pm : 1e-12,
km : 1e+3
};
const METERS_PER_FOOT = 0.3048;
// ---------------------------------------------------------
function _parse(strIn)
{
if (!strIn || typeof strIn !== 'string' ) {
return NaN;
}
let str = strIn.trim();
if (str.length == 0) {
return NaN;
}
// Just look up the sign now and be done, rather than bother with leading zeros
const sgn = str.startsWith('-') ? -1 : 1;
// Try +-: 1/2", 11/16"; trailing space OK, but nothing else
// Note: Trailing " is mandatory!
{
let lm = str.match(/^([+-]?\d+)\/(\d+)" *$/);
if (lm) {
return (parseFloat(lm[1]) / parseFloat(lm[2])) / 12.0 ;
}
}
// Try +-: 5, 1.2e7, .1e+2, 3e-1, 3.e1
let firstFloat = NaN;
{
let lm = str.match(/^[+-]? *(?:\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?/i);
if (!lm) {
return NaN;
}
firstFloat = parseFloat(lm[0].replace(/ */g, '')); // Clear spaces on the way
str = str.slice(lm[0].length); // Don't trim just yet!
}
if (str.length == 0 || isNaN(firstFloat)) {
return firstFloat;
}
// If inches, then end of story
if (str.match(/^("| *in) *$/i)) {
return firstFloat / 12.0;
}
{
let lm = str.match(/^ +(\d+)\/(\d+)" *$/i);
if (lm) {
// If original input was: 7 11/16"
return (firstFloat + sgn * parseFloat(lm[1]) / parseFloat(lm[2])) / 12.0;
}
}
// Try explicit units
{
let lm = str.match(/^ *(m|cm|mm|um|nm|pm|km) *$/i);
if (lm) {
// Return an array to signal that the output is in METERS:
return [firstFloat * s_siPrefix[lm[1]], true];
}
}
// Should be feet now
{
let lm = str.match(/^(?:'| *ft|-| +-?) */i); // Order matters here!
if (!lm) {
return NaN;
}
str = str.slice(lm[0].length).trim();
if (str.length == 0) {
if (lm[0].match(/-/)) {
return NaN; // Trailing dash - e.g. strIn was: 7-
}
return firstFloat;
}
}
// Now we can only have left: 2, 2.3, 7/8, 2 7/8, with an optional " at the end
{
let lm = str.match(/^(\d+(?:\.\d*)?)(?:"| *in)? *$/);
if (lm) {
return firstFloat + sgn * parseFloat(lm[1]) / 12.0 ;
}
lm = str.match(/^(\d+)\/(\d+)("| *in)? *$/);
if (lm) {
return firstFloat + sgn * (parseFloat(lm[1]) / parseFloat(lm[2])) / 12.0 ;
}
lm = str.match(/^(\d+) +(\d+)\/(\d+)("| *in)? *$/);
if (lm) {
return firstFloat + sgn * (parseFloat(lm[1]) + parseFloat(lm[2]) / parseFloat(lm[3])) / 12.0 ;
}
}
return NaN;
}
// We are trying to avoid unnecessary round-trip conversions like meters->feet->meters,
// in order to avoid machine epsilon noise, e.g. 1m parsing as 0.99999999999
// Return length in FEET:
function _parseF(strIn) {
// let [num, inMeters] = _parse(strIn); --- This line crashes for some reason ... WHY???
// --- TypeError: _parse is not a function
// if (inMeters === undefined) {
// return num;
// }
let num = _parse(strIn);
if (typeof num === 'number') {
return num;
}
return num[0] / METERS_PER_FOOT;
}
// Return length in METERS:
function _parseM(strIn) {
let num = _parse(strIn);
if (typeof num === 'number') {
return num * METERS_PER_FOOT;
}
return num[0];
}
const toExport = {
F : _parseF,
FEET : _parseF,
M : _parseM,
METERS : _parseM,
};
toExport.METERS_PER_FOOT = METERS_PER_FOOT;
// --- Node.js and Browser support shebang ---
toExport.noConflict = function() {
root.parse = previous_parse;
return toExport;
};
if( typeof exports !== 'undefined' ) {
if( typeof module !== 'undefined' && module.exports ) {
exports = module.exports = toExport;
}
exports.parse = toExport;
}
else {
root.parse = toExport;
}
}).call(this);