title-case
Advanced tools
Comparing version 4.2.0 to 4.3.0
export declare const WORD_SEPARATORS: Set<string>; | ||
export declare const SENTENCE_TERMINATORS: Set<string>; | ||
export declare const TITLE_TERMINATORS: Set<string>; | ||
export declare const SMALL_WORDS: Set<string>; | ||
export interface Options { | ||
locale?: string | string[]; | ||
sentenceCase?: boolean; | ||
sentenceTerminators?: Set<string>; | ||
smallWords?: Set<string>; | ||
sentenceTerminators?: Set<string>; | ||
titleTerminators?: Set<string>; | ||
wordSeparators?: Set<string>; | ||
locale?: string | string[]; | ||
} | ||
export declare function titleCase(input: string, options?: Options | string[] | string): string; |
const TOKENS = /(\S+)|(.)/g; | ||
const IS_SPECIAL_CASE = /[\.#]\p{L}/u; // #tag, example.com, etc. | ||
const IS_SPECIAL_CASE = /[\.#]\p{Alphabetic}/u; // #tag, example.com, etc. | ||
const IS_MANUAL_CASE = /\p{Ll}(?=[\p{Lu}])/u; // iPhone, iOS, etc. | ||
const ALPHANUMERIC_PATTERN = /[\p{L}\d]+/gu; | ||
const IS_ACRONYM = /(?:\p{Lu}\.){2,}$/u; | ||
const ALPHANUMERIC_PATTERN = /\p{Alphabetic}+/gu; | ||
const IS_ACRONYM = /^(\P{Alphabetic})*(?:\p{Alphabetic}\.){2,}(\P{Alphabetic})*$/u; | ||
export const WORD_SEPARATORS = new Set(["—", "–", "-", "―", "/"]); | ||
export const SENTENCE_TERMINATORS = new Set([ | ||
".", | ||
"!", | ||
"?", | ||
export const SENTENCE_TERMINATORS = new Set([".", "!", "?"]); | ||
export const TITLE_TERMINATORS = new Set([ | ||
...SENTENCE_TERMINATORS, | ||
":", | ||
@@ -55,11 +54,11 @@ '"', | ||
export function titleCase(input, options = {}) { | ||
const { locale = undefined, sentenceCase = false, sentenceTerminators = SENTENCE_TERMINATORS, titleTerminators = TITLE_TERMINATORS, smallWords = SMALL_WORDS, wordSeparators = WORD_SEPARATORS, } = typeof options === "string" || Array.isArray(options) | ||
? { locale: options } | ||
: options; | ||
const terminators = sentenceCase ? sentenceTerminators : titleTerminators; | ||
let result = ""; | ||
let m; | ||
let isNewSentence = true; | ||
const { smallWords = SMALL_WORDS, sentenceTerminators = SENTENCE_TERMINATORS, wordSeparators = WORD_SEPARATORS, locale, } = typeof options === "string" || Array.isArray(options) | ||
? { locale: options } | ||
: options; | ||
// tslint:disable-next-line | ||
while ((m = TOKENS.exec(input)) !== null) { | ||
const { 1: token, 2: whiteSpace, index } = m; | ||
for (const m of input.matchAll(TOKENS)) { | ||
const { 1: token, 2: whiteSpace, index = 0 } = m; | ||
if (whiteSpace) { | ||
@@ -71,8 +70,16 @@ result += whiteSpace; | ||
if (IS_SPECIAL_CASE.test(token)) { | ||
result += token; | ||
// The period at the end of an acronym is not a new sentence. | ||
if (IS_ACRONYM.test(token)) { | ||
isNewSentence = false; | ||
const acronym = token.match(IS_ACRONYM); | ||
// The period at the end of an acronym is not a new sentence, | ||
// but we should uppercase first for i.e., e.g., etc. | ||
if (acronym) { | ||
const [_, prefix = "", suffix = ""] = acronym; | ||
result += | ||
sentenceCase && !isNewSentence | ||
? token | ||
: upperAt(token, prefix.length, locale); | ||
isNewSentence = terminators.has(suffix.charAt(0)); | ||
continue; | ||
} | ||
result += token; | ||
isNewSentence = terminators.has(token.charAt(token.length - 1)); | ||
} | ||
@@ -82,36 +89,51 @@ else { | ||
let value = token; | ||
let isSentenceEnd = false; | ||
for (let i = 0; i < matches.length; i++) { | ||
const { 0: word, index: wordIndex = 0 } = matches[i]; | ||
// Reset "new sentence" when we find a word. | ||
const nextChar = token.charAt(wordIndex + word.length); | ||
isSentenceEnd = terminators.has(nextChar); | ||
// Always the capitalize first word and reset "new sentence". | ||
if (isNewSentence) { | ||
isNewSentence = false; | ||
} | ||
else { | ||
// Ignore small words except at beginning or end, | ||
// or previous token is a new sentence. | ||
if (smallWords.has(word) && | ||
// Not the final token and word. | ||
!(index + token.length === input.length && i === matches.length - 1)) { | ||
// Skip capitalizing all words if sentence case is enabled. | ||
else if (sentenceCase || IS_MANUAL_CASE.test(word)) { | ||
continue; | ||
} | ||
// Handle simple words. | ||
else if (matches.length === 1) { | ||
// Avoid capitalizing small words, except at the end of a sentence. | ||
if (smallWords.has(word)) { | ||
const isFinalToken = index + token.length === input.length; | ||
if (!isFinalToken && !isSentenceEnd) { | ||
continue; | ||
} | ||
} | ||
} | ||
// Multi-word tokens need to be parsed differently. | ||
else if (i > 0) { | ||
// Avoid capitalizing words without a valid word separator, | ||
// e.g. "apple's" or "test(ing)". | ||
if (!wordSeparators.has(token.charAt(wordIndex - 1))) { | ||
continue; | ||
} | ||
// Ignore small words in the middle of hyphenated words. | ||
if (smallWords.has(word) && wordSeparators.has(nextChar)) { | ||
continue; | ||
} | ||
} | ||
if (IS_MANUAL_CASE.test(word)) { | ||
continue; | ||
} | ||
// Only capitalize words after a valid word separator. | ||
if (i > 0 && !wordSeparators.has(token.charAt(wordIndex - 1))) { | ||
continue; | ||
} | ||
value = | ||
value.slice(0, wordIndex) + | ||
value.charAt(wordIndex).toLocaleUpperCase(locale) + | ||
value.slice(wordIndex + 1); | ||
value = upperAt(value, wordIndex, locale); | ||
} | ||
result += value; | ||
isNewSentence = | ||
isSentenceEnd || terminators.has(token.charAt(token.length - 1)); | ||
} | ||
const lastChar = token.charAt(token.length - 1); | ||
isNewSentence = sentenceTerminators.has(lastChar); | ||
} | ||
return result; | ||
} | ||
function upperAt(input, index, locale) { | ||
return (input.slice(0, index) + | ||
input.charAt(index).toLocaleUpperCase(locale) + | ||
input.slice(index + 1)); | ||
} | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "title-case", | ||
"version": "4.2.0", | ||
"version": "4.3.0", | ||
"description": "Transform a string into title case following English rules", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -20,2 +20,11 @@ # Title Case | ||
### Options | ||
- `locale?: string | string[]` Locale used for `toLocaleUpperCase` during case transformation (default: `undefined`) | ||
- `sentenceCase?: boolean` Only capitalize the first word of each sentence (default: `false`) | ||
- `sentenceTerminators?: Set<string>` Set of characters to consider a new sentence under sentence case behavior (e.g. `.`, default: `SENTENCE_TERMINATORS`) | ||
- `smallWords?: Set<string>` Set of words to keep lower-case when `sentenceCase === false` (default: `SMALL_WORDS`) | ||
- `titleTerminators?: Set<string>` Set of characters to consider a new sentence under title case behavior (e.g. `:`, default: `TITLE_TERMINATORS`) | ||
- `wordSeparators?: Set<string>` Set of characters to consider a new word for capitalization, such as hyphenation (default: `WORD_SEPARATORS`) | ||
## TypeScript and ESM | ||
@@ -22,0 +31,0 @@ |
Sorry, the diff of this file is not supported yet
16601
148
36