@danielhaim/titlecaser 1.2.58 → 1.2.59

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.
package/README.md CHANGED
@@ -145,9 +145,10 @@ The `{options}` parameter is an object that contains the settings for the conver
145
145
 
146
146
  ## Methods
147
147
 
148
- - `setReplaceTerms(terms: object)`: Sets word replacement terms to be used during title casing. Multiple calls can be made to add or update multiple word replacements.
149
- - `removeReplaceTerm(term: string)`: Removes a replaced term from the `wordReplacementsList` array in the option object of the `TitleCaser` instance. Throws an error if the term is not found in the array, otherwise removes it from the array and updates the option object.
150
- - `addReplaceTerm(term: string, replacement: string)`: Adds a new term to the `wordReplacementsList` array in the options object of the TitleCaser instance. The method takes two string arguments: term specifies the word to be replaced, and replacement specifies the replacement for the word. If the term already exists in the array, the method updates its replacement value. Otherwise, it adds a new object with the term and replacement to the array. The method then updates the wordReplacementsList property in the object.
148
+ - `setReplaceTerms(terms)`: Updates the `wordReplacementsList` with new term-replacement pairs. It accepts an array of objects, each containing a single key-value pair representing the term and its replacement.
149
+ - `removeReplaceTerm(term)`: Removes a replaced term from the `wordReplacementsList` array in the option object of the `TitleCaser` instance. Throws an error if the term is not found in the array, otherwise removes it from the array and updates the option object.
150
+ - `addReplaceTerm(term, replacement)`: Adds a single term-replacement pair to the `wordReplacementsList`. If the term already exists, it updates the replacement value.
151
+ - `addExactPhraseReplacements(newPhrases)` - This method allows adding an array of exact phrase replacements to the `TitleCaser` class. Each item in the array should be an object with a single key-value pair, where the key is the phrase to be replaced and the value is the desired replacement.
151
152
  - `setStyle(style: string)`: Sets the style option in the object of the TitleCaser instance. The method takes a string argument style that specifies the style to use for the title casing. If the argument is not a string, the method throws a TypeError. Otherwise, it updates the style option in the object.
152
153
  - `smartQuotes(smartQuotes: boolean)`: Specifies whether to replace straight quotes with smart quotes during title casing. Provide a boolean argument smartQuotes to enable or disable this feature.
153
154
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielhaim/titlecaser",
3
- "version": "1.2.58",
3
+ "version": "1.2.59",
4
4
  "description": "Converts a string to title case with multiple style options, ability to ignore certain words, and handle acronyms",
5
5
  "keywords": [
6
6
  "title case",
package/src/TitleCaser.js CHANGED
@@ -11,6 +11,7 @@ export class TitleCaser {
11
11
  constructor(options = {}) {
12
12
  this.options = options;
13
13
  this.wordReplacementsList = wordReplacementsList;
14
+ this.correctPhraseCasingList = correctPhraseCasingList;
14
15
  }
15
16
 
16
17
  toTitleCase(str) {
@@ -49,6 +50,9 @@ export class TitleCaser {
49
50
  replaceTermList.map((term) => [Object.keys(term)[0].toLowerCase(), Object.values(term)[0]]),
50
51
  );
51
52
 
53
+ // console.log(replaceTermsArray);
54
+ // console.log(this.wordReplacementsList);
55
+
52
56
  const map = {
53
57
  "&": "&",
54
58
  "<": "&lt;",
@@ -89,7 +93,28 @@ export class TitleCaser {
89
93
  // If the word is in the correctTitleCasingList array, return the correct casing.
90
94
  return TitleCaserUtils.correctTerm(word, correctTitleCasingList);
91
95
  case TitleCaserUtils.hasHyphen(word):
92
- return TitleCaserUtils.correctTermHyphenated(word, style);
96
+ // Separate the base word from any trailing punctuation
97
+ const baseWord = word.replace(/[\W_]+$/, "");
98
+ const trailingPunctuation = word.slice(baseWord.length);
99
+
100
+ // Split the base word at the hyphen and process each part
101
+ const parts = baseWord.split("-");
102
+ const replacedParts = parts.map((part) => {
103
+ const lowerCasePart = part.toLowerCase();
104
+ if (replaceTermsArray.includes(lowerCasePart)) {
105
+ return replaceTermObj[lowerCasePart];
106
+ }
107
+ return part;
108
+ });
109
+
110
+ // Determine if any part was replaced
111
+ const isReplaced = !replacedParts.every((part, index) => part === parts[index]);
112
+
113
+ // Reassemble the word with the hyphen, reattach trailing punctuation, and return
114
+ return (
115
+ (isReplaced ? replacedParts.join("-") : TitleCaserUtils.correctTermHyphenated(word, style)) +
116
+ trailingPunctuation
117
+ );
93
118
  case TitleCaserUtils.hasSuffix(word, style):
94
119
  // If the word has a suffix, return the correct casing.
95
120
  return TitleCaserUtils.correctSuffix(word, correctTitleCasingList);
@@ -102,18 +127,29 @@ export class TitleCaser {
102
127
  ? word.charAt(0).toUpperCase() + word.slice(1)
103
128
  : word.toLowerCase();
104
129
  case TitleCaserUtils.endsWithSymbol(word):
130
+ // console.log("ends with symbol: ", word);
105
131
  // If the word ends with a symbol, return the correct casing.
106
- const splitWord = word.split(/([.,\/#!$%\^&\*;:{}=\-_`~()])/g);
107
- const processedWords = splitWord.map((splitWord, j) => {
108
- // If the word is in the correctTitleCasingList array, return the correct casing.
109
- if (TitleCaserUtils.isWordInArray(splitWord, correctTitleCasingList))
110
- return TitleCaserUtils.correctTerm(splitWord, correctTitleCasingList);
111
- // Else return the word with the correct casing.
112
- else
113
- return j > 0 && TitleCaserUtils.endsWithSymbol(splitWord)
114
- ? splitWord.charAt(0).toUpperCase() + splitWord.slice(1)
115
- : splitWord.charAt(0).toUpperCase() + splitWord.slice(1);
132
+ const splitWord = word.split(/([.,\/#!$%\^&\*;:{}=\-_`~()?])/g);
133
+ // console.log(splitWord);
134
+ // Process each part for correct casing
135
+ const processedWords = splitWord.map((part) => {
136
+ // Check if part is a symbol
137
+ if (TitleCaserUtils.endsWithSymbol(part)) {
138
+ // console.log(part);
139
+ return part;
140
+ } else {
141
+ // If it's a word, process it for correct casing
142
+ if (TitleCaserUtils.isWordInArray(part, correctTitleCasingList)) {
143
+ return TitleCaserUtils.correctTerm(part, correctTitleCasingList);
144
+ } else if (replaceTermsArray.includes(part)) {
145
+ return replaceTermObj[part];
146
+ } else {
147
+ // Apply the correct casing for words not in the list
148
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
149
+ }
150
+ }
116
151
  });
152
+
117
153
  // Join the processed words and return them.
118
154
  return processedWords.join("");
119
155
  case TitleCaserUtils.startsWithSymbol(word):
@@ -136,11 +172,12 @@ export class TitleCaser {
136
172
  // Join the words in the array into a string.
137
173
  inputString = wordsInTitleCase.join(" ");
138
174
 
139
- for (const phrase of correctPhraseCasingList) {
140
- // If the phrase is in the input string, replace it with the correct casing.
141
- if (inputString.toLowerCase().includes(phrase.toLowerCase())) {
142
- inputString = inputString.replace(new RegExp(phrase, "gi"), phrase);
143
- }
175
+ for (const [phrase, replacement] of Object.entries(this.correctPhraseCasingList)) {
176
+ // Create a regular expression for case-insensitive matching of the phrase
177
+ const regex = new RegExp(phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
178
+
179
+ // Replace the phrase in the input string with its corresponding replacement
180
+ inputString = inputString.replace(regex, replacement);
144
181
  }
145
182
 
146
183
  // Replace the nl2br placeholder with <br> tags.
@@ -197,27 +234,31 @@ export class TitleCaser {
197
234
  }
198
235
 
199
236
  setReplaceTerms(terms) {
200
- if (typeof terms !== "object") {
201
- throw new TypeError("Invalid argument: replace terms must be an object.");
237
+ if (!Array.isArray(terms)) {
238
+ throw new TypeError("Invalid argument: setReplaceTerms must be an array of objects.");
202
239
  }
203
-
204
- // Add the new replace terms to the wordReplacementsList array
205
- Object.entries(terms).forEach(([term, replacement]) => {
206
- const index = wordReplacementsList.findIndex((obj) => obj[term]);
207
- if (index !== -1) {
208
- // If the term already exists in the array, update the replacement value
209
- wordReplacementsList[index][term] = replacement;
240
+ // Iterate over each term-replacement object in the array
241
+ terms.forEach((termObject) => {
242
+ if (termObject && typeof termObject === "object") {
243
+ const [term, replacement] = Object.entries(termObject)[0];
244
+ const index = this.wordReplacementsList.findIndex((obj) => obj.hasOwnProperty(term));
245
+ if (index !== -1) {
246
+ // Update the existing term
247
+ this.wordReplacementsList[index][term] = replacement;
248
+ } else {
249
+ // Add the new term
250
+ this.wordReplacementsList.push({ [term]: replacement });
251
+ }
210
252
  } else {
211
- // If the term doesn't exist in the array, add a new object with the term and replacement
212
- wordReplacementsList.push({ [term]: replacement });
253
+ // Handle non-object entries in the array, if required
254
+ console.warn("Invalid entry in terms array:", termObject);
213
255
  }
214
256
  });
215
257
 
216
- // Log the updated wordReplacementsList array
217
- // console.log(wordReplacementsList);
258
+ this.options.wordReplacementsList = this.wordReplacementsList;
218
259
 
219
- // Update the replace terms option
220
- this.options.wordReplacementsList = wordReplacementsList;
260
+ // Log the updated wordReplacementsList array
261
+ // console.log(this.wordReplacementsList);
221
262
  }
222
263
 
223
264
  addReplaceTerm(term, replacement) {
@@ -225,8 +266,6 @@ export class TitleCaser {
225
266
  throw new TypeError("Invalid argument: term and replacement must be strings.");
226
267
  }
227
268
 
228
- const index = this.wordReplacementsList.findIndex((obj) => obj[term]);
229
-
230
269
  if (index !== -1) {
231
270
  // If the term already exists in the array, update the replacement value
232
271
  this.wordReplacementsList[index][term] = replacement;
@@ -255,11 +294,46 @@ export class TitleCaser {
255
294
  // Remove the term from the array
256
295
  this.wordReplacementsList.splice(index, 1);
257
296
 
297
+ // Update the replace terms option
298
+ this.options.wordReplacementsList = this.wordReplacementsList;
299
+
258
300
  // Log the updated wordReplacementsList array
259
301
  // console.log(this.wordReplacementsList);
302
+ }
260
303
 
261
- // Update the replace terms option
262
- this.options.wordReplacementsList = this.wordReplacementsList;
304
+ addExactPhraseReplacements(newPhrases) {
305
+ if (!Array.isArray(newPhrases)) {
306
+ throw new TypeError("Invalid argument: newPhrases must be an array.");
307
+ }
308
+
309
+ newPhrases.forEach((item) => {
310
+ // If the item is an object with a single key-value pair
311
+ if (typeof item === "object" && !Array.isArray(item) && Object.keys(item).length === 1) {
312
+ const key = Object.keys(item)[0];
313
+ const value = item[key];
314
+ if (typeof key === "string" && typeof value === "string") {
315
+ this.correctPhraseCasingList[key] = value;
316
+ } else {
317
+ throw new TypeError("Invalid argument: Each key-value pair must contain strings.");
318
+ }
319
+ }
320
+ // If the item is already a key-value pair
321
+ else if (typeof item === "object" && !Array.isArray(item)) {
322
+ Object.entries(item).forEach(([key, value]) => {
323
+ if (typeof key === "string" && typeof value === "string") {
324
+ this.correctPhraseCasingList[key] = value;
325
+ } else {
326
+ throw new TypeError("Invalid argument: Each key-value pair must contain strings.");
327
+ }
328
+ });
329
+ }
330
+ // Invalid format
331
+ else {
332
+ throw new TypeError("Invalid argument: Each item must be an object with a single key-value pair.");
333
+ }
334
+ });
335
+
336
+ // console.log(this.correctPhraseCasingList);
263
337
  }
264
338
 
265
339
  setStyle(style) {
@@ -145,4 +145,8 @@ export const titleCaseDefaultOptionsList = Object.freeze({
145
145
  });
146
146
 
147
147
  export const ignoredWordList = [];
148
- export const correctPhraseCasingList = ["The Cybersmile Foundation", "CO. by Colgate", "The Simpsons"];
148
+ export const correctPhraseCasingList = {
149
+ 'the cybersmile foundation': 'The Cybersmile Foundation',
150
+ 'co. by colgate': 'CO. by Colgate'
151
+ };
152
+