@danielhaim/titlecaser 1.2.53 → 1.2.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,205 +7,206 @@ import {
7
7
  } from "./TitleCaserConsts.js";
8
8
 
9
9
  export class TitleCaserUtils {
10
-
10
+
11
11
  // Validate the option key
12
- static validateOption ( key, value ) {
12
+ static validateOption(key, value) {
13
13
  // Check if value is an array
14
- if ( !Array.isArray ( value ) ) {
15
- throw new TypeError ( `Invalid option: ${ key } must be an array` );
14
+ if (!Array.isArray(value)) {
15
+ throw new TypeError(`Invalid option: ${key} must be an array`);
16
16
  }
17
-
17
+
18
18
  // Check if all values in the array are strings
19
- if ( !value.every ( ( word ) => typeof word === "string" ) ) {
20
- throw new TypeError ( `Invalid option: ${ key } must be an array of strings` );
19
+ if (!value.every((word) => typeof word === "string")) {
20
+ throw new TypeError(`Invalid option: ${key} must be an array of strings`);
21
21
  }
22
22
  }
23
-
23
+
24
24
  // Validate the option object
25
25
  static TitleCaseValidator;
26
-
27
- static validateOptions ( options ) {
28
- for ( const key of Object.keys ( options ) ) {
29
-
30
- if ( key === 'style' ) {
31
- if ( typeof options.style !== 'string' ) {
32
- throw new TypeError ( `Invalid option: ${ key } must be a string` );
33
- } else if ( !allowedTitleCaseStylesList.includes ( options.style ) ) {
34
- throw new TypeError ( `Invalid option: ${ key } must be a string` );
26
+
27
+ static validateOptions(options) {
28
+ for (const key of Object.keys(options)) {
29
+
30
+ if (key === 'style') {
31
+ if (typeof options.style !== 'string') {
32
+ throw new TypeError(`Invalid option: ${key} must be a string`);
33
+ } else if (!allowedTitleCaseStylesList.includes(options.style)) {
34
+ throw new TypeError(`Invalid option: ${key} must be a string`);
35
35
  }
36
36
  continue;
37
37
  }
38
-
39
- if ( key === 'wordReplacementsList' ) {
40
- if ( !Array.isArray ( options.wordReplacementsList ) ) {
41
- throw new TypeError ( `Invalid option: ${ key } must be an array` );
38
+
39
+ if (key === 'wordReplacementsList') {
40
+ if (!Array.isArray(options.wordReplacementsList)) {
41
+ throw new TypeError(`Invalid option: ${key} must be an array`);
42
42
  } else {
43
- for ( const term of options.wordReplacementsList ) {
44
- if ( typeof term !== 'string' ) {
45
- throw new TypeError ( `Invalid option: ${ key } must contain only strings` );
43
+ for (const term of options.wordReplacementsList) {
44
+ if (typeof term !== 'string') {
45
+ throw new TypeError(`Invalid option: ${key} must contain only strings`);
46
46
  }
47
47
  }
48
48
  }
49
49
  continue;
50
50
  }
51
-
52
- if ( !titleCaseDefaultOptionsList.hasOwnProperty ( key ) ) {
53
- throw new TypeError ( `Invalid option: ${ key }` );
51
+
52
+ if (!titleCaseDefaultOptionsList.hasOwnProperty(key)) {
53
+ throw new TypeError(`Invalid option: ${key}`);
54
54
  }
55
-
56
- this.TitleCaseValidator.validateOption ( key, options[key] );
55
+
56
+ this.TitleCaseValidator.validateOption(key, options[key]);
57
57
  }
58
58
  }
59
-
60
- static titleCaseOptionsCache = new Map ();
61
-
62
- static getTitleCaseOptions ( options = {}, lowercaseWords = [] ) {
59
+
60
+ static titleCaseOptionsCache = new Map();
61
+
62
+ static getTitleCaseOptions(options = {}, lowercaseWords = []) {
63
63
  // Create a unique key for the cache that combines the options and the lowercase words
64
- const cacheKey = JSON.stringify ( {
64
+ const cacheKey = JSON.stringify({
65
65
  options,
66
66
  lowercaseWords
67
- } );
68
-
67
+ });
68
+
69
69
  // If the cache already has an entry for this key, return the cached options
70
- if ( TitleCaserUtils.titleCaseOptionsCache.has ( cacheKey ) ) {
71
- return TitleCaserUtils.titleCaseOptionsCache.get ( cacheKey );
70
+ if (TitleCaserUtils.titleCaseOptionsCache.has(cacheKey)) {
71
+ return TitleCaserUtils.titleCaseOptionsCache.get(cacheKey);
72
72
  }
73
-
74
- // Merge the default options with the user-provided options
73
+
75
74
  const mergedOptions = {
76
75
  ...titleCaseDefaultOptionsList[options.style || "ap"],
77
- ...options
76
+ ...options,
77
+ smartQuotes: options.hasOwnProperty('smartQuotes') ? options.smartQuotes : false
78
78
  };
79
-
79
+
80
80
  // Merge the default articles with user-provided articles and lowercase words
81
- const mergedArticles = mergedOptions.articlesList.concat ( lowercaseWords )
82
- .filter ( ( word, index, array ) => array.indexOf ( word ) === index );
83
-
81
+ const mergedArticles = mergedOptions.articlesList.concat(lowercaseWords)
82
+ .filter((word, index, array) => array.indexOf(word) === index);
83
+
84
84
  // Merge the default short conjunctions with user-provided conjunctions and lowercase words
85
- const mergedShortConjunctions = mergedOptions.shortConjunctionsList.concat ( lowercaseWords )
86
- .filter ( ( word, index, array ) => array.indexOf ( word ) === index );
87
-
85
+ const mergedShortConjunctions = mergedOptions.shortConjunctionsList.concat(lowercaseWords)
86
+ .filter((word, index, array) => array.indexOf(word) === index);
87
+
88
88
  // Merge the default short prepositions with user-provided prepositions and lowercase words
89
- const mergedShortPrepositions = mergedOptions.shortPrepositionsList.concat ( lowercaseWords )
90
- .filter ( ( word, index, array ) => array.indexOf ( word ) === index );
91
-
89
+ const mergedShortPrepositions = mergedOptions.shortPrepositionsList.concat(lowercaseWords)
90
+ .filter((word, index, array) => array.indexOf(word) === index);
91
+
92
92
  // Merge the default word replacements with the user-provided replacements
93
93
  const mergedReplaceTerms = [
94
94
  ...(mergedOptions.replaceTerms || [])
95
- .map ( ( [ key, value ] ) => [ key.toLowerCase (), value ] ),
95
+ .map(([key, value]) => [key.toLowerCase(), value]),
96
96
  ...wordReplacementsList,
97
97
  ];
98
-
98
+
99
99
  // Return the merged options
100
100
  const result = {
101
101
  articlesList: mergedArticles,
102
102
  shortConjunctionsList: mergedShortConjunctions,
103
103
  shortPrepositionsList: mergedShortPrepositions,
104
- neverCapitalizedList: [ ...mergedOptions.neverCapitalizedList ],
104
+ neverCapitalizedList: [...mergedOptions.neverCapitalizedList],
105
105
  replaceTerms: mergedReplaceTerms,
106
+ smartQuotes: mergedOptions.smartQuotes // Add smartQuotes option to result
106
107
  };
107
-
108
+
108
109
  // Add the merged options to the cache and return them
109
- TitleCaserUtils.titleCaseOptionsCache.set ( cacheKey, result );
110
+ TitleCaserUtils.titleCaseOptionsCache.set(cacheKey, result);
110
111
  return result;
111
112
  }
112
-
113
- static isNeverCapitalizedCache = new Map ();
114
-
113
+
114
+ static isNeverCapitalizedCache = new Map();
115
+
115
116
  // Check if the word is a short conjunction
116
- static isShortConjunction ( word, style ) {
117
+ static isShortConjunction(word, style) {
117
118
  // Get the list of short conjunctions from the TitleCaseHelper
118
- const shortConjunctionsList = [ ...TitleCaserUtils.getTitleCaseOptions ( {
119
+ const shortConjunctionsList = [...TitleCaserUtils.getTitleCaseOptions({
119
120
  style: style
120
- } )
121
+ })
121
122
  .shortConjunctionsList
122
123
  ];
123
-
124
+
124
125
  // Convert the word to lowercase
125
- const wordLowerCase = word.toLowerCase ();
126
-
126
+ const wordLowerCase = word.toLowerCase();
127
+
127
128
  // Return true if the word is in the list of short conjunctions
128
- return shortConjunctionsList.includes ( wordLowerCase );
129
+ return shortConjunctionsList.includes(wordLowerCase);
129
130
  }
130
-
131
+
131
132
  // Check if the word is an article
132
- static isArticle ( word, style ) {
133
+ static isArticle(word, style) {
133
134
  // Get the list of articles for the language
134
- const articlesList = TitleCaserUtils.getTitleCaseOptions ( {
135
+ const articlesList = TitleCaserUtils.getTitleCaseOptions({
135
136
  style: style
136
- } )
137
+ })
137
138
  .articlesList;
138
139
  // Return true if the word matches an article
139
- return articlesList.includes ( word.toLowerCase () );
140
+ return articlesList.includes(word.toLowerCase());
140
141
  }
141
-
142
+
142
143
  // Check if the word is a short preposition
143
- static isShortPreposition ( word, style ) {
144
+ static isShortPreposition(word, style) {
144
145
  // Get the list of short prepositions from the Title Case Helper.
145
146
  const {
146
147
  shortPrepositionsList
147
- } = TitleCaserUtils.getTitleCaseOptions ( {
148
+ } = TitleCaserUtils.getTitleCaseOptions({
148
149
  style: style
149
- } );
150
+ });
150
151
  // Check if the word is in the list of short prepositions.
151
- return shortPrepositionsList.includes ( word.toLowerCase () );
152
+ return shortPrepositionsList.includes(word.toLowerCase());
152
153
  }
153
-
154
+
154
155
  // This function is only ever called once per word per style, since the result is cached.
155
156
  // The cache key is a combination of the style and the lowercase word.
156
- static isNeverCapitalized ( word, style ) {
157
+ static isNeverCapitalized(word, style) {
157
158
  // Check if the word is in the cache. If it is, return it.
158
- const cacheKey = `${ style }_${ word.toLowerCase () }`;
159
- if ( TitleCaserUtils.isNeverCapitalizedCache.has ( cacheKey ) ) {
160
- return TitleCaserUtils.isNeverCapitalizedCache.get ( cacheKey );
159
+ const cacheKey = `${style}_${word.toLowerCase()}`;
160
+ if (TitleCaserUtils.isNeverCapitalizedCache.has(cacheKey)) {
161
+ return TitleCaserUtils.isNeverCapitalizedCache.get(cacheKey);
161
162
  }
162
-
163
+
163
164
  // If the word is not in the cache, then check if it is in the word list for the given style.
164
165
  const {
165
166
  neverCapitalizedList
166
- } = TitleCaserUtils.getTitleCaseOptions ( {
167
+ } = TitleCaserUtils.getTitleCaseOptions({
167
168
  style
168
- } );
169
-
170
- const result = neverCapitalizedList.includes ( word.toLowerCase () );
169
+ });
170
+
171
+ const result = neverCapitalizedList.includes(word.toLowerCase());
171
172
  // Store the result in the cache so it can be used again
172
- TitleCaserUtils.isNeverCapitalizedCache.set ( cacheKey, result );
173
-
173
+ TitleCaserUtils.isNeverCapitalizedCache.set(cacheKey, result);
174
+
174
175
  return result;
175
176
  }
176
-
177
- static isShortWord ( word, style ) {
177
+
178
+ static isShortWord(word, style) {
178
179
  // If the word is not a string, throw a TypeError.
179
- if ( typeof word !== "string" ) {
180
- throw new TypeError ( `Invalid input: word must be a string. Received ${ typeof word }.` );
180
+ if (typeof word !== "string") {
181
+ throw new TypeError(`Invalid input: word must be a string. Received ${typeof word}.`);
181
182
  }
182
-
183
+
183
184
  // If the style is not one of the allowed styles, throw an Error.
184
- if ( !allowedTitleCaseStylesList.includes ( style ) ) {
185
- throw new Error ( `Invalid option: style must be one of ${ allowedTitleCaseStylesList.join ( ", " ) }.` );
185
+ if (!allowedTitleCaseStylesList.includes(style)) {
186
+ throw new Error(`Invalid option: style must be one of ${allowedTitleCaseStylesList.join(", ")}.`);
186
187
  }
187
-
188
+
188
189
  // If the word is a short conjunction, article, preposition, or is in the never-capitalized list, return true.
189
190
  // Otherwise, return false.
190
- return TitleCaserUtils.isShortConjunction ( word, style ) ||
191
- TitleCaserUtils.isArticle ( word, style ) ||
192
- TitleCaserUtils.isShortPreposition ( word, style ) ||
193
- TitleCaserUtils.isNeverCapitalized ( word, style );
191
+ return TitleCaserUtils.isShortConjunction(word, style) ||
192
+ TitleCaserUtils.isArticle(word, style) ||
193
+ TitleCaserUtils.isShortPreposition(word, style) ||
194
+ TitleCaserUtils.isNeverCapitalized(word, style);
194
195
  }
195
-
196
+
196
197
  // Check if a word has a number
197
- static hasNumbers ( word ) {
198
- return /\d/.test ( word );
198
+ static hasNumbers(word) {
199
+ return /\d/.test(word);
199
200
  }
200
-
201
+
201
202
  // Check if a word has multiple uppercase letters
202
- static hasUppercaseMultiple ( word ) {
203
+ static hasUppercaseMultiple(word) {
203
204
  // initialize count to 0
204
205
  let count = 0;
205
206
  // loop through each character of the word
206
- for ( let i = 0; i < word.length && count < 2; i++ ) {
207
+ for (let i = 0; i < word.length && count < 2; i++) {
207
208
  // if the character is an uppercase letter
208
- if ( /[A-Z]/.test ( word[i] ) ) {
209
+ if (/[A-Z]/.test(word[i])) {
209
210
  // increment count by 1
210
211
  count++;
211
212
  }
@@ -213,104 +214,104 @@ export class TitleCaserUtils {
213
214
  // return true if count is greater or equal to 2, false otherwise
214
215
  return count >= 2;
215
216
  }
216
-
217
+
217
218
  // Check if a word has an intentional uppercase letter
218
219
  // (i.e. not the first letter of the word)
219
- static hasUppercaseIntentional ( word ) {
220
+ static hasUppercaseIntentional(word) {
220
221
  // Only check for uppercase letters after the first letter
221
222
  // and only check for lowercase letters before the last letter
222
- return /[A-Z]/.test ( word.slice ( 1 ) ) && /[a-z]/.test ( word.slice ( 0, -1 ) );
223
+ return /[A-Z]/.test(word.slice(1)) && /[a-z]/.test(word.slice(0, -1));
223
224
  }
224
-
225
+
225
226
  // Check if a word has a suffix
226
- static hasSuffix ( word ) {
227
+ static hasSuffix(word) {
227
228
  // Test if word is longer than suffix
228
229
  const suffix = "'s";
229
230
  // Test if word ends with suffix
230
- return word.length > suffix.length && word.endsWith ( suffix );
231
+ return word.length > suffix.length && word.endsWith(suffix);
231
232
  }
232
-
233
+
233
234
  // Check if a word has an apostrophe
234
- static hasApostrophe ( word ) {
235
- return word.indexOf ( "'" ) !== -1;
235
+ static hasApostrophe(word) {
236
+ return word.indexOf("'") !== -1;
236
237
  }
237
-
238
+
238
239
  // Check if a word has a hyphen
239
- static hasHyphen ( word ) {
240
- return word.indexOf ( '-' ) !== -1 || word.indexOf ( '–' ) !== -1 || word.indexOf ( '—' ) !== -1;
240
+ static hasHyphen(word) {
241
+ return word.indexOf('-') !== -1 || word.indexOf('–') !== -1 || word.indexOf('—') !== -1;
241
242
  }
242
-
243
+
243
244
  // Check if a word is a Roman numeral
244
- static hasRomanNumeral ( word ) {
245
+ static hasRomanNumeral(word) {
245
246
  // Check if the input is a string
246
- if ( typeof word !== 'string' || word === '' ) {
247
+ if (typeof word !== 'string' || word === '') {
247
248
  // Throw an error if the input is not a string
248
- throw new TypeError ( 'Invalid input: word must be a non-empty string.' );
249
+ throw new TypeError('Invalid input: word must be a non-empty string.');
249
250
  }
250
-
251
+
251
252
  // Define a regular expression that matches a roman numeral
252
253
  const romanNumeralRegex = /^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/i;
253
254
  // Check if the input word matches the regular expression
254
- return romanNumeralRegex.test ( word );
255
+ return romanNumeralRegex.test(word);
255
256
  }
256
-
257
+
257
258
  // Check if a word is a hyphenated Roman numeral
258
- static hasHyphenRomanNumeral ( word ) {
259
- if ( typeof word !== "string" || word === "" ) {
260
- throw new TypeError ( "Invalid input: word must be a non-empty string." );
259
+ static hasHyphenRomanNumeral(word) {
260
+ if (typeof word !== "string" || word === "") {
261
+ throw new TypeError("Invalid input: word must be a non-empty string.");
261
262
  }
262
-
263
- const parts = word.split ( "-" );
264
- for ( let i = 0; i < parts.length; i++ ) {
265
- if ( !TitleCaserUtils.hasRomanNumeral ( parts[i] ) ) {
263
+
264
+ const parts = word.split("-");
265
+ for (let i = 0; i < parts.length; i++) {
266
+ if (!TitleCaserUtils.hasRomanNumeral(parts[i])) {
266
267
  return false;
267
268
  }
268
269
  }
269
270
  return true;
270
271
  }
271
-
272
+
272
273
  // Check if a word has `nl2br` in it
273
- static hasHtmlBreak ( word ) {
274
+ static hasHtmlBreak(word) {
274
275
  return word === "nl2br";
275
276
  }
276
-
277
+
277
278
  // Check if a string has Unicode symbols.
278
- static hasUnicodeSymbols ( str ) {
279
- return /[^\x00-\x7F\u00A0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u02B0-\u02FF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0800-\u083F\u0840-\u085F\u0860-\u087F\u0880-\u08AF\u08B0-\u08FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF]/.test ( str );
279
+ static hasUnicodeSymbols(str) {
280
+ return /[^\x00-\x7F\u00A0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u02B0-\u02FF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0800-\u083F\u0840-\u085F\u0860-\u087F\u0880-\u08AF\u08B0-\u08FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF]/.test(str);
280
281
  }
281
-
282
+
282
283
  // Checks whether a string contains any currency symbols
283
- static hasCurrencySymbols ( str ) {
284
- return /[^\x00-\x7F\u00A0-\u00FF\u20AC\u20A0-\u20B9\u20BD\u20A1-\u20A2\u00A3-\u00A5\u058F\u060B\u09F2-\u09F3\u0AF1\u0BF9\u0E3F\u17DB\u20A6\u20A8\u20B1\u2113\u20AA-\u20AB\u20AA\u20AC-\u20AD\u20B9]/.test ( str );
284
+ static hasCurrencySymbols(str) {
285
+ return /[^\x00-\x7F\u00A0-\u00FF\u20AC\u20A0-\u20B9\u20BD\u20A1-\u20A2\u00A3-\u00A5\u058F\u060B\u09F2-\u09F3\u0AF1\u0BF9\u0E3F\u17DB\u20A6\u20A8\u20B1\u2113\u20AA-\u20AB\u20AA\u20AC-\u20AD\u20B9]/.test(str);
285
286
  }
286
-
287
+
287
288
  // Check if a word is ampersand
288
- static isWordAmpersand ( str ) {
289
- return /&amp;|&/.test ( str );
289
+ static isWordAmpersand(str) {
290
+ return /&amp;|&/.test(str);
290
291
  }
291
-
292
-
292
+
293
+
293
294
  // Check if a word starts with a symbol
294
- static startsWithSymbol ( word ) {
295
- if ( typeof word !== 'string' ) {
296
- throw new Error ( `Parameter 'word' must be a string. Received '${ typeof word }' instead.` );
295
+ static startsWithSymbol(word) {
296
+ if (typeof word !== 'string') {
297
+ throw new Error(`Parameter 'word' must be a string. Received '${typeof word}' instead.`);
297
298
  }
298
-
299
- if ( word.length === 0 ) {
299
+
300
+ if (word.length === 0) {
300
301
  return false;
301
302
  }
302
-
303
- const firstChar = word.charAt ( 0 );
304
-
303
+
304
+ const firstChar = word.charAt(0);
305
+
305
306
  return (
306
307
  firstChar === '#' ||
307
308
  firstChar === '@' ||
308
309
  firstChar === '.'
309
310
  );
310
311
  }
311
-
312
- static escapeSpecialCharacters ( str ) {
313
- return str.replace ( /[&<>"']/g, function ( match ) {
312
+
313
+ static escapeSpecialCharacters(str) {
314
+ return str.replace(/[&<>"']/g, function (match) {
314
315
  switch (match) {
315
316
  case "&":
316
317
  return "&amp;";
@@ -325,11 +326,11 @@ export class TitleCaserUtils {
325
326
  default:
326
327
  return match;
327
328
  }
328
- } );
329
+ });
329
330
  }
330
-
331
- static unescapeSpecialCharacters ( str ) {
332
- return str.replace ( /&amp;|&lt;|&gt;|&quot;|&#x27;/g, function ( match ) {
331
+
332
+ static unescapeSpecialCharacters(str) {
333
+ return str.replace(/&amp;|&lt;|&gt;|&quot;|&#x27;/g, function (match) {
333
334
  switch (match) {
334
335
  case "&amp;":
335
336
  return "&";
@@ -344,227 +345,262 @@ export class TitleCaserUtils {
344
345
  default:
345
346
  return match;
346
347
  }
347
- } );
348
+ });
348
349
  }
349
-
350
-
350
+
351
+
351
352
  // Check if a word ends with a symbol
352
- static endsWithSymbol ( word, symbols = [ ".", ",", ";", ":", "?", "!" ] ) {
353
+ static endsWithSymbol(word, symbols = [".", ",", ";", ":", "?", "!"]) {
353
354
  // Check if the word is a string and the symbols is an array
354
- if ( typeof word !== "string" || !Array.isArray ( symbols ) )
355
- throw new Error ( "Invalid arguments" );
355
+ if (typeof word !== "string" || !Array.isArray(symbols))
356
+ throw new Error("Invalid arguments");
356
357
  // Check if the word ends with a symbol or two symbols
357
- return symbols.some ( symbol => word.endsWith ( symbol ) ) || symbols.includes ( word.slice ( -2 ) );
358
+ return symbols.some(symbol => word.endsWith(symbol)) || symbols.includes(word.slice(-2));
358
359
  }
359
-
360
+
360
361
  // This function accepts two arguments: a word and an array of ignored words.
361
- static isWordIgnored ( word, ignoredWords = ignoredWordList ) {
362
+ static isWordIgnored(word, ignoredWords = ignoredWordList) {
362
363
  // If the ignoredWords argument is not an array, throw an error.
363
- if ( !Array.isArray ( ignoredWords ) ) {
364
- throw new TypeError ( "Invalid input: ignoredWords must be an array." );
364
+ if (!Array.isArray(ignoredWords)) {
365
+ throw new TypeError("Invalid input: ignoredWords must be an array.");
365
366
  }
366
-
367
+
367
368
  // If the word argument is not a non-empty string, throw an error.
368
- if ( typeof word !== "string" || word.trim () === "" ) {
369
- throw new TypeError ( "Invalid input: word must be a non-empty string." );
369
+ if (typeof word !== "string" || word.trim() === "") {
370
+ throw new TypeError("Invalid input: word must be a non-empty string.");
370
371
  }
371
-
372
+
372
373
  // Convert the word to lowercase and trim any space.
373
374
  let lowercasedWord;
374
- lowercasedWord = word.toLowerCase ()
375
- .trim ();
376
-
375
+ lowercasedWord = word.toLowerCase()
376
+ .trim();
377
+
377
378
  // If the word is in the ignoredWords array, return true. Otherwise, return false.
378
- return ignoredWords.includes ( lowercasedWord );
379
+ return ignoredWords.includes(lowercasedWord);
379
380
  }
380
-
381
+
381
382
  // Check if the wordList is a valid array
382
- static isWordInArray ( targetWord, wordList ) {
383
- if ( !Array.isArray ( wordList ) ) {
383
+ static isWordInArray(targetWord, wordList) {
384
+ if (!Array.isArray(wordList)) {
384
385
  return false;
385
386
  }
386
-
387
+
387
388
  // Check if the targetWord is in the wordList
388
- return wordList.some ( ( word ) => word.toLowerCase () === targetWord.toLowerCase () );
389
+ return wordList.some((word) => word.toLowerCase() === targetWord.toLowerCase());
390
+ }
391
+
392
+ static convertQuotesToCurly(input) {
393
+ const curlyQuotes = {
394
+ "'": ['\u2018', '\u2019'],
395
+ '"': ['\u201C', '\u201D'],
396
+ };
397
+
398
+ let replacedText = '';
399
+
400
+ for (let i = 0; i < input.length; i++) {
401
+ const char = input[i];
402
+ const curlyQuotePair = curlyQuotes[char];
403
+
404
+ if (curlyQuotePair) {
405
+ const prevChar = input[i - 1];
406
+ const nextChar = input[i + 1];
407
+
408
+ // Determine whether to use left or right curly quote
409
+ const isLeftAligned = (!prevChar || prevChar === ' ' || prevChar === '\n');
410
+ const curlyQuote = isLeftAligned ? curlyQuotePair[0] : curlyQuotePair[1];
411
+ replacedText += curlyQuote;
412
+
413
+ // Handle cases where right curly quote is followed by punctuation or space
414
+ if (curlyQuote === curlyQuotePair[1] && /[.,;!?()\[\]{}:]/.test(nextChar)) {
415
+ replacedText += nextChar;
416
+ i++; // Skip the next character
417
+ }
418
+ } else {
419
+ replacedText += char;
420
+ }
421
+ }
422
+
423
+ return replacedText;
389
424
  }
390
-
425
+
426
+
391
427
  // This function is used to replace a word with a term in the replaceTerms object
392
- static replaceTerm ( word, replaceTermObj ) {
428
+ static replaceTerm(word, replaceTermObj) {
393
429
  // Validate input
394
- if ( typeof word !== "string" || word === "" ) {
395
- throw new TypeError ( "Invalid input: word must be a non-empty string." );
430
+ if (typeof word !== "string" || word === "") {
431
+ throw new TypeError("Invalid input: word must be a non-empty string.");
396
432
  }
397
-
398
- if ( !replaceTermObj || typeof replaceTermObj !== "object" ) {
399
- throw new TypeError ( "Invalid input: replaceTermObj must be a non-null object." );
433
+
434
+ if (!replaceTermObj || typeof replaceTermObj !== "object") {
435
+ throw new TypeError("Invalid input: replaceTermObj must be a non-null object.");
400
436
  }
401
-
437
+
402
438
  // Convert the word to lowercase
403
439
  let lowercasedWord;
404
- lowercasedWord = word.toLowerCase ();
405
-
440
+ lowercasedWord = word.toLowerCase();
441
+
406
442
  // Check if the word is in the object with lowercase key
407
- if ( replaceTermObj.hasOwnProperty ( lowercasedWord ) ) {
443
+ if (replaceTermObj.hasOwnProperty(lowercasedWord)) {
408
444
  return replaceTermObj[lowercasedWord];
409
445
  }
410
-
446
+
411
447
  // Check if the word is in the object with original case key
412
- if ( replaceTermObj.hasOwnProperty ( word ) ) {
448
+ if (replaceTermObj.hasOwnProperty(word)) {
413
449
  return replaceTermObj[word];
414
450
  }
415
-
451
+
416
452
  // Check if the word is in the object with uppercase key
417
- const uppercasedWord = word.toUpperCase ();
418
- if ( replaceTermObj.hasOwnProperty ( uppercasedWord ) ) {
453
+ const uppercasedWord = word.toUpperCase();
454
+ if (replaceTermObj.hasOwnProperty(uppercasedWord)) {
419
455
  return replaceTermObj[uppercasedWord];
420
456
  }
421
-
457
+
422
458
  // If the word is not in the object, return the original word
423
459
  return word;
424
460
  }
425
-
461
+
426
462
  // This function is used to check if a suffix is present in a word that is in the correct terms list
427
- static correctSuffix ( word, correctTerms ) {
463
+ static correctSuffix(word, correctTerms) {
428
464
  // Validate input
429
- if ( typeof word !== "string" || word === "" ) {
430
- throw new TypeError ( "Invalid input: word must be a non-empty string." );
465
+ if (typeof word !== "string" || word === "") {
466
+ throw new TypeError("Invalid input: word must be a non-empty string.");
431
467
  }
432
-
433
- if ( !correctTerms || !Array.isArray ( correctTerms ) || correctTerms.some ( ( term ) => typeof term !== "string" ) ) {
434
- throw new TypeError ( "Invalid input: correctTerms must be an array of strings." );
468
+
469
+ if (!correctTerms || !Array.isArray(correctTerms) || correctTerms.some((term) => typeof term !== "string")) {
470
+ throw new TypeError("Invalid input: correctTerms must be an array of strings.");
435
471
  }
436
-
472
+
437
473
  // Define the regular expression for the suffix
438
474
  const suffixRegex = /'s$/i;
439
-
475
+
440
476
  // If the word ends with the suffix
441
- if ( suffixRegex.test ( word ) ) {
477
+ if (suffixRegex.test(word)) {
442
478
  // Remove the suffix from the word
443
- const wordWithoutSuffix = word.slice ( 0, -2 );
479
+ const wordWithoutSuffix = word.slice(0, -2);
444
480
  // Check if the word without the suffix matches any of the correct terms
445
- const matchingIndex = correctTerms.findIndex ( ( term ) => term.toLowerCase () === wordWithoutSuffix.toLowerCase () );
446
-
447
- if ( matchingIndex >= 0 ) {
481
+ const matchingIndex = correctTerms.findIndex((term) => term.toLowerCase() === wordWithoutSuffix.toLowerCase());
482
+
483
+ if (matchingIndex >= 0) {
448
484
  // If it does, return the correct term with the suffix
449
485
  const correctCase = correctTerms[matchingIndex];
450
- return `${ correctCase }'s`;
486
+ return `${correctCase}'s`;
451
487
  } else {
452
488
  // If not, capitalize the first letter and append the suffix
453
- const capitalizedWord = wordWithoutSuffix.charAt ( 0 ).toUpperCase () + wordWithoutSuffix.slice ( 1 );
454
- return `${ capitalizedWord }'s`;
489
+ const capitalizedWord = wordWithoutSuffix.charAt(0).toUpperCase() + wordWithoutSuffix.slice(1);
490
+ return `${capitalizedWord}'s`;
455
491
  }
456
492
  }
457
-
493
+
458
494
  // If the word doesn't end with the suffix, return the word as-is
459
495
  return word;
460
496
  }
461
-
497
+
462
498
  // This function is used to check if a word is in the correct terms list
463
- static correctTerm ( word, correctTerms, delimiters = /[-']/ ) {
499
+ static correctTerm(word, correctTerms, delimiters = /[-']/) {
464
500
  // Validate input
465
- if ( typeof word !== "string" || word === "" ) {
466
- throw new TypeError ( "Invalid input: word must be a non-empty string." );
501
+ if (typeof word !== "string" || word === "") {
502
+ throw new TypeError("Invalid input: word must be a non-empty string.");
467
503
  }
468
-
469
- if ( !correctTerms || !Array.isArray ( correctTerms ) ) {
470
- throw new TypeError ( "Invalid input: correctTerms must be an array." );
504
+
505
+ if (!correctTerms || !Array.isArray(correctTerms)) {
506
+ throw new TypeError("Invalid input: correctTerms must be an array.");
471
507
  }
472
-
473
- if ( typeof delimiters !== "string" && !Array.isArray ( delimiters ) && !(delimiters instanceof RegExp) ) {
474
- throw new TypeError ( "Invalid input: delimiters must be a string, an array of strings, or a regular expression." );
508
+
509
+ if (typeof delimiters !== "string" && !Array.isArray(delimiters) && !(delimiters instanceof RegExp)) {
510
+ throw new TypeError("Invalid input: delimiters must be a string, an array of strings, or a regular expression.");
475
511
  }
476
-
512
+
477
513
  // Convert delimiters to a regular expression if it is a string or an array
478
- if ( typeof delimiters === "string" ) {
479
- delimiters = new RegExp ( `[${delimiters}]` );
480
- } else if ( Array.isArray ( delimiters ) ) {
481
- delimiters = new RegExp ( `[${delimiters.join("")}]` );
514
+ if (typeof delimiters === "string") {
515
+ delimiters = new RegExp(`[${delimiters}]`);
516
+ } else if (Array.isArray(delimiters)) {
517
+ delimiters = new RegExp(`[${delimiters.join("")}]`);
482
518
  }
483
-
519
+
484
520
  // Split the word into parts delimited by the specified delimiters
485
- const parts = word.split ( delimiters );
521
+ const parts = word.split(delimiters);
486
522
  // Count the number of parts
487
523
  const numParts = parts.length;
488
-
524
+
489
525
  // For each part
490
- for ( let i = 0; i < numParts; i++ ) {
526
+ for (let i = 0; i < numParts; i++) {
491
527
  // Lowercase the part
492
- const lowercasedPart = parts[i].toLowerCase ();
528
+ const lowercasedPart = parts[i].toLowerCase();
493
529
  // Search for the part in the list of correct terms
494
- const index = correctTerms.findIndex ( ( t ) => t.toLowerCase () === lowercasedPart );
530
+ const index = correctTerms.findIndex((t) => t.toLowerCase() === lowercasedPart);
495
531
  // If the part is found in the list of correct terms
496
- if ( index >= 0 ) {
532
+ if (index >= 0) {
497
533
  // Replace the part with the correct term
498
534
  parts[i] = correctTerms[index];
499
535
  }
500
536
  }
501
-
537
+
502
538
  // Join the parts back together using the first delimiter as the default delimiter
503
- return parts.join ( delimiters.source.charAt ( 0 ) );
539
+ return parts.join(delimiters.source.charAt(0));
504
540
  }
505
-
541
+
506
542
  // This function is used to check if a word is in the correct terms list
507
- static correctTermHyphenated ( word, style ) {
543
+ static correctTermHyphenated(word, style) {
508
544
  // Split the word into an array of words
509
- const hyphenatedWords = word.split ( "-" );
510
-
545
+ const hyphenatedWords = word.split("-");
546
+
511
547
  // Define functions to process words
512
- const capitalizeFirst = ( word ) => word.charAt ( 0 )
513
- .toUpperCase () + word.slice ( 1 );
514
- const lowercaseRest = ( word ) => word.charAt ( 0 ) + word.slice ( 1 )
515
- .toLowerCase ();
516
-
548
+ const capitalizeFirst = (word) => word.charAt(0)
549
+ .toUpperCase() + word.slice(1);
550
+ const lowercaseRest = (word) => word.charAt(0) + word.slice(1)
551
+ .toLowerCase();
552
+
517
553
  // Define the style-specific processing functions
518
554
  const styleFunctions = {
519
- ap: ( word, index ) => (index === 0 ? capitalizeFirst ( word ) : lowercaseRest ( word )),
555
+ ap: (word, index) => (index === 0 ? capitalizeFirst(word) : lowercaseRest(word)),
520
556
  chicago: capitalizeFirst,
521
- apa: ( word, index, length ) => {
522
- if ( TitleCaserUtils.isShortWord ( word, style ) && index > 0 && index < length - 1 ) {
523
- return word.toLowerCase ();
557
+ apa: (word, index, length) => {
558
+ if (TitleCaserUtils.isShortWord(word, style) && index > 0 && index < length - 1) {
559
+ return word.toLowerCase();
524
560
  } else {
525
- return capitalizeFirst ( word );
561
+ return capitalizeFirst(word);
526
562
  }
527
563
  },
528
- nyt: ( word, index ) => (index === 0 ? capitalizeFirst ( word ) : lowercaseRest ( word )),
529
- wikipedia: ( word, index ) => (index === 0 ? capitalizeFirst ( word ) : lowercaseRest ( word )),
564
+ nyt: (word, index) => (index === 0 ? capitalizeFirst(word) : lowercaseRest(word)),
565
+ wikipedia: (word, index) => (index === 0 ? capitalizeFirst(word) : lowercaseRest(word)),
530
566
  };
531
-
567
+
532
568
  // Get the style-specific processing function
533
569
  const processWord = styleFunctions[style] || lowercaseRest;
534
-
570
+
535
571
  // Process each word
536
- const processedWords = hyphenatedWords.map ( ( word, i ) => {
537
-
572
+ const processedWords = hyphenatedWords.map((word, i) => {
573
+
538
574
  // Check if the word is a Roman numeral
539
575
  const romanNumeralRegex = /^(M{0,3})(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/i;
540
- if ( romanNumeralRegex.test ( word ) ) {
541
- return word.toUpperCase ();
576
+ if (romanNumeralRegex.test(word)) {
577
+ return word.toUpperCase();
542
578
  }
543
-
579
+
544
580
  // Preserve the original word
545
581
  let correctedWord = word;
546
-
582
+
547
583
  // Check if the word is in the list of words to preserve
548
- const lowerCaseWord = word.toLowerCase ();
549
- const uniqueTermsIndex = correctTitleCasingList.findIndex ( ( w ) => w.toLowerCase () === lowerCaseWord );
550
- if ( uniqueTermsIndex >= 0 ) {
584
+ const lowerCaseWord = word.toLowerCase();
585
+ const uniqueTermsIndex = correctTitleCasingList.findIndex((w) => w.toLowerCase() === lowerCaseWord);
586
+ if (uniqueTermsIndex >= 0) {
551
587
  correctedWord = correctTitleCasingList[uniqueTermsIndex];
552
588
  }
553
589
  // Check if the word is a possessive form
554
- else if ( lowerCaseWord.endsWith ( "'s" ) ) {
555
- const rootWord = lowerCaseWord.substring ( 0, lowerCaseWord.length - 2 );
556
- const rootWordIndex = correctTitleCasingList.findIndex ( ( w ) => w.toLowerCase () === rootWord );
557
- if ( rootWordIndex >= 0 ) {
558
- correctedWord = `${ correctTitleCasingList[rootWordIndex] }'s`;
590
+ else if (lowerCaseWord.endsWith("'s")) {
591
+ const rootWord = lowerCaseWord.substring(0, lowerCaseWord.length - 2);
592
+ const rootWordIndex = correctTitleCasingList.findIndex((w) => w.toLowerCase() === rootWord);
593
+ if (rootWordIndex >= 0) {
594
+ correctedWord = `${correctTitleCasingList[rootWordIndex]}'s`;
559
595
  }
560
596
  }
561
-
597
+
562
598
  // Process the word
563
- return processWord ( correctedWord, i, hyphenatedWords.length );
564
- } );
565
-
599
+ return processWord(correctedWord, i, hyphenatedWords.length);
600
+ });
601
+
566
602
  // Rejoin the words
567
- return processedWords.join ( "-" );
603
+ return processedWords.join("-");
568
604
  }
569
-
605
+
570
606
  }