src_utils_string.js

/**
 * Capitalise the first letter of a string.
 *
 * @param {string} string The string to capitalise the first letter.
 * @returns {string} The new string.
 */
export function capitaliseFirstLetter(string) {
  let res = string;
  if (string) {
    res = string.charAt(0).toUpperCase() + string.slice(1);
  }
  return res;
}

/**
 * Check if a string starts with the input element.
 *
 * @param {string} str The input string.
 * @param {string} search The searched start.
 * @param {number} [rawPos] The position in this string at which to begin
 *  searching for searchString. Defaults to 0.
 * @returns {boolean} True if the input string starts with the searched string.
 */
export function startsWith(str, search, rawPos) {
  if (typeof str === 'undefined' || str === null ||
    typeof search === 'undefined' || search === null) {
    return false;
  }
  const pos = rawPos > 0 ? rawPos | 0 : 0;
  return str.substring(pos, pos + search.length) === search;
}

/**
 * Check if a string ends with the input element.
 *
 * @param {string} str The input string.
 * @param {string} search The searched ending.
 * @returns {boolean} True if the input string ends with the searched string.
 */
export function endsWith(str, search) {
  if (typeof str === 'undefined' || str === null ||
    typeof search === 'undefined' || search === null) {
    return false;
  }
  return str.substring(str.length - search.length) === search;
}

/**
 * Split key/value string: `key0=val00&key0=val01&key1=val10
 *   will return `{key0 : [val00, val01], key1 : val1}`.
 *
 * @param {string} inputStr The string to split.
 * @returns {object} The split string.
 */
export function splitKeyValueString(inputStr) {
  // result
  const result = {};
  // check input string
  if (inputStr) {
    // split key/value pairs
    const pairs = inputStr.split('&');
    for (let i = 0; i < pairs.length; ++i) {
      const pair = pairs[i].split('=');
      // if the key does not exist, create it
      if (!result[pair[0]]) {
        result[pair[0]] = pair[1];
      } else {
        // make it an array
        if (!(result[pair[0]] instanceof Array)) {
          result[pair[0]] = [result[pair[0]]];
        }
        result[pair[0]].push(pair[1]);
      }
    }
  }
  return result;
}

/**
 * Get flags from an input string. Flags are words surrounded with curly
 * braces.
 *
 * @param {string} inputStr The input string.
 * @returns {string[]} An array of found flags.
 */
export function getFlags(inputStr) {
  const flags = [];
  // check input string
  if (inputStr === null || typeof inputStr === 'undefined') {
    return flags;
  }

  // word surrounded by curly braces
  const regex = /{(\w+)}/g;

  let match = regex.exec(inputStr);
  while (match) {
    flags.push(match[1]); // first matching group
    match = regex.exec(inputStr);
  }
  return flags;
}

/**
 * Replace flags in a input string. Flags are keywords surrounded with curly
 * braces.
 *
 * @param {string} inputStr The input string.
 * @param {object} values A object of {value, unit}.
 * @returns {string} The result string.
 */
export function replaceFlags(inputStr, values) {
  let res = '';
  // check input string
  if (inputStr === null || typeof inputStr === 'undefined') {
    return res;
  }
  res = inputStr;
  // check values
  if (values === null || typeof values === 'undefined') {
    return res;
  }

  // loop through flags
  const keys = getFlags(inputStr);
  for (let i = 0; i < keys.length; ++i) {
    const valueObj = values[keys[i]];
    if (valueObj !== null && typeof valueObj !== 'undefined' &&
      valueObj.value !== null && typeof valueObj.value !== 'undefined') {
      // value string
      let valueStr = valueObj.value.toPrecision(4);
      // add unit if available
      // space or no space? Yes apart from degree...
      // check: https://en.wikipedia.org/wiki/Space_(punctuation)#Spaces_and_unit_symbols
      if (valueObj.unit !== null &&
        typeof valueObj.unit !== 'undefined' &&
        valueObj.unit.length !== 0) {
        if (valueObj.unit !== 'degree') {
          valueStr += ' ';
        }
        valueStr += valueObj.unit;
      }
      // flag to replace
      const flag = '{' + keys[i] + '}';
      // replace
      res = res.replace(flag, valueStr);
    }
  }
  // return
  return res;
}

/**
 * Get the root of an input path.
 * Splits using `/` as separator.
 *
 * @param {string} path The input path.
 * @returns {string} The input path without its last part.
 */
export function getRootPath(path) {
  return path.split('/').slice(0, -1).join('/');
}

/**
 * Get a file extension: anything after the last dot.
 * File name starting with a dot are discarded.
 * Extensions are expected to contain at least one letter.
 *
 * @param {string} filePath The file path containing the file name.
 * @returns {string} The lower case file extension or null for none.
 */
export function getFileExtension(filePath) {
  let ext = null;
  if (typeof filePath !== 'undefined' &&
    filePath !== null &&
    filePath[0] !== '.') {
    const pathSplit = filePath.toLowerCase().split('.');
    if (pathSplit.length !== 1) {
      ext = pathSplit.pop();
      // extension should contain at least one letter and no slash
      const regExp = /[a-z]/;
      if (!regExp.test(ext) || ext.includes('/')) {
        ext = null;
      }
    }
  }
  return ext;
}

/**
 * Convert a string to a Uint8Array.
 *
 * @param {string} str The string to convert.
 * @returns {Uint8Array} The Uint8Array.
 */
export function stringToUint8Array(str) {
  const arr = new Uint8Array(str.length);
  for (let i = 0, leni = str.length; i < leni; i++) {
    arr[i] = str.charCodeAt(i);
  }
  return arr;
}

/**
 * Round a float number to a given precision.
 *
 * Inspired from {@link https://stackoverflow.com/a/49729715/3639892}.
 *
 * Can be a solution to not have trailing zero as when
 *   using toFixed or toPrecision.
 * '+number.toFixed(precision)' does not pass all the tests...
 *
 * @param {number} number The number to round.
 * @param {number} precision The rounding precision.
 * @returns {number} The rounded number.
 */
export function precisionRound(number, precision) {
  const factor = Math.pow(10, precision);
  const delta = 0.01 / factor; // fixes precisionRound(1.005, 2)
  return Math.round(number * factor + delta) / factor;
}