Source

src/util/stringUtils.js

'use strict';

/**
 * String processing utilities for CSV parsing
 * @category Utilities
 */
class StringUtils {
    // Regular expressions as constants for better maintainability
    static PATTERNS = {
        INTEGER: /^-?\d+$/,
        FLOAT: /^-?\d*\.\d+$/,
        WHITESPACE: /\s/g
    };

    static BOOLEAN_VALUES = {
        TRUE: 'true',
        FALSE: 'false'
    };

    /**
     * Removes whitespace from property names based on configuration
     * @param {boolean} shouldTrimAll - If true, removes all whitespace, otherwise only trims edges
     * @param {string} propertyName - The property name to process
     * @returns {string} The processed property name
     */
    trimPropertyName(shouldTrimAll, propertyName) {
        if (!propertyName) {
            return '';
        }
        return shouldTrimAll ? 
            propertyName.replace(StringUtils.PATTERNS.WHITESPACE, '') : 
            propertyName.trim();
    }

    /**
     * Converts a string value to its appropriate type while preserving data integrity
     * @param {string} value - The input value to convert
     * @returns {string|number|boolean} The converted value
     */
    getValueFormatByType(value) {
        if (this.isEmpty(value)) {
            return String();
        }

        if (this.isBoolean(value)) {
            return this.convertToBoolean(value);
        }

        if (this.isInteger(value)) {
            return this.convertInteger(value);
        }

        if (this.isFloat(value)) {
            return this.convertFloat(value);
        }

        return String(value);
    }

    /**
     * Checks if a value array contains any non-empty values
     * @param {Array} values - Array to check for content
     * @returns {boolean} True if array has any non-empty values
     */
    hasContent(values = []) {
        return Array.isArray(values) && 
               values.some(value => Boolean(value));
    }

    // Private helper methods for type checking and conversion
    /**
     * Check if a value is empty (undefined or empty string)
     * @param {unknown} value - Value to check
     * @returns {boolean} True if value is undefined or empty string
     * @private
     */
    isEmpty(value) {
        return value === undefined || value === '';
    }

    /**
     * Check if a value is a boolean string ('true' or 'false', case-insensitive)
     * @param {string} value - Value to check
     * @returns {boolean} True if value is 'true' or 'false'
     * @private
     */
    isBoolean(value) {
        const normalizedValue = value.toLowerCase();
        return normalizedValue === StringUtils.BOOLEAN_VALUES.TRUE || 
               normalizedValue === StringUtils.BOOLEAN_VALUES.FALSE;
    }

    /**
     * Check if a value is an integer string (with optional leading minus sign)
     * @param {string} value - Value to check
     * @returns {boolean} True if value matches integer pattern
     * @private
     */
    isInteger(value) {
        return StringUtils.PATTERNS.INTEGER.test(value);
    }

    /**
     * Check if a value is a float string (decimal number with optional leading minus sign)
     * @param {string} value - Value to check
     * @returns {boolean} True if value matches float pattern
     * @private
     */
    isFloat(value) {
        return StringUtils.PATTERNS.FLOAT.test(value);
    }

    /**
     * Check if a numeric string has a leading zero (e.g., '01' or '-01')
     * Leading zeros indicate the value should be kept as a string to preserve formatting
     * @param {string} value - Numeric string value to check
     * @returns {boolean} True if value has a leading zero
     * @private
     */
    hasLeadingZero(value) {
        const isPositiveWithLeadingZero = value.length > 1 && value[0] === '0';
        const isNegativeWithLeadingZero = value.length > 2 && value[0] === '-' && value[1] === '0';
        return isPositiveWithLeadingZero || isNegativeWithLeadingZero;
    }

    /**
     * Convert a boolean string to native boolean value
     * Safely converts 'true' to true and 'false' to false
     * @param {string} value - Boolean string ('true' or 'false')
     * @returns {boolean} Native boolean value
     * @private
     */
    convertToBoolean(value) {
        return JSON.parse(value.toLowerCase());
    }

    /**
     * Convert an integer string to number or keep as string if it has leading zeros
     * Preserves leading zeros in strings (e.g., '007' stays as string)
     * @param {string} value - Integer string to convert
     * @returns {number|string} Number if safe, otherwise string value
     * @private
     */
    convertInteger(value) {
        if (this.hasLeadingZero(value)) {
            return String(value);
        }

        const num = Number(value);
        return Number.isSafeInteger(num) ? num : String(value);
    }

    /**
     * Convert a float string to number or keep as string if conversion is unsafe
     * @param {string} value - Float string to convert
     * @returns {number|string} Number if finite and valid, otherwise string value
     * @private
     */
    convertFloat(value) {
        const num = Number(value);
        return Number.isFinite(num) ? num : String(value);
    }
}

module.exports = new StringUtils();