chromium/chrome/browser/resources/chromeos/accessibility/common/string_util.ts

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Utilities for strings.
 */

export class StringUtil {
  static toUpperCamelCase(str: string): string {
    const wordRegex = /(?:^\w|[A-Z]|(?:\b|_)\w)/g;
    const underscoreAndWhitespaceRegex = /(\s|_)+/g;
    return str.replace(wordRegex, word => word.toUpperCase())
        .replace(underscoreAndWhitespaceRegex, '');
  }

  /**
   * Returns the length of the longest common prefix of two strings.
   * TODO(b/319783585): This doesn't work well if there's a character
   * represented with a surrogate pair.
   * @param first The first string.
   * @param second The second string.
   * @return The length of the longest common prefix, which may be 0
   *     for an empty common prefix.
   */
  static longestCommonPrefixLength(first: string, second: string): number {
    const limit = Math.min(first.length, second.length);
    let i;
    for (i = 0; i < limit; ++i) {
      if (first.charAt(i) !== second.charAt(i)) {
        break;
      }
    }
    return i;
  }

  /**
   * Returns the length of the longest common suffix of two strings.
   * TODO(b/319783585): This doesn't work well if there's a character
   * represented with a surrogate pair.
   * @param first The first string.
   * @param second The second string.
   * @return The length of the longest common suffix, which may be 0
   *     for an empty common suffix.
   */
  static longestCommonSuffixLength(first: string, second: string): number {
    const limit = Math.min(first.length, second.length);
    let i;
    for (i = 0; i < limit; ++i) {
      if (first.charAt(first.length - i - 1) !==
          second.charAt(second.length - i - 1)) {
        break;
      }
    }
    return i;
  }

  /**
   * Returns the offset after the code point denoted by |offset|.
   * If |offset| points at a character that is not the first code unit of
   * a valid code point, then |offset + 1| is returned. If there are no
   * characters after the code point denoted by |offset|, then the length of
   * |str| is returned.
   * @param str String of characters.
   * @param offset A valid character index in |str|.
   * @return A valid index of |str| or |str.length|.
   */
  static nextCodePointOffset(str: string, offset: number): number {
    if (offset >= str.length) {
      return str.length;
    }
    // TODO(b/314203187): Not null asserted, check these to make sure this is
    // correct.
    if (str.codePointAt(offset)! > StringUtil.MAX_BMP_CODEPOINT) {
      return offset + 2;
    }
    return offset + 1;
  }

  /**
   * Returns the offset of the first code unit of the last code point before
   * |offset| in a string. If there is no valid code point right before
   * |offset| (including if offset is zero), |offset -1| is returned.
   * @param str String of characters.
   * @param offset A valid character offset into |str|.
   * @return A valid character index into |str| (or -1 in the case
   *     where |offset| is 0).
   */
  static previousCodePointOffset(str: string, offset: number): number {
    if (offset <= 0) {
      return -1;
    }
    // TODO(b/314203187): Not null asserted, check these to make sure this is
    // correct.
    if (offset > 1 &&
        str.codePointAt(offset - 2)! > StringUtil.MAX_BMP_CODEPOINT) {
      return offset - 2;
    }
    return offset - 1;
  }

  static toTitleCase(title: string): string {
    return title.replace(
        /\w\S*/g, word => word.charAt(0).toUpperCase() + word.substr(1));
  }

  /**
   * Converts a camel case string to snake case.
   * @param s A camel case string, e.g. 'brailleTable8'.
   * @return A snake case string, e.g. 'braille_table_8'.
   */
  static camelToSnake(s: string): string {
    return s.replace(/([A-Z0-9])/g, '_$1').toLowerCase();
  }

  /**
   * @param ch The character to test.
   * @return True if a character breaks a word, used to determine
   *     if the previous word should be spoken.
   */
  static isWordBreakChar(ch: string): boolean {
    return Boolean(ch.match(/^\W$/));
  }
}

export namespace StringUtil {
  /**
   * The last code point of the Unicode basic multilingual plane.
   * Code points larger than this value are represented in UTF-16 by a surrogate
   * pair, that is two code units.
   */
  export const MAX_BMP_CODEPOINT = 65535;
}