@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.
package/README.md CHANGED
@@ -28,8 +28,8 @@ Transform any text to proper title case format using popular style guides such a
28
28
  + [Basic Usage](#basic-usage)
29
29
  + [Customizing Word Replacements Method](#customizing-word-replacements-method)
30
30
  + [Customizing TitleCaser](#customizing-titlecaser)
31
- + [TitleCaser With Default Word Replacement](#titlecaser-with-default-word-replacement)
32
- + [TitleCaser With Possessive Noun and a Colon](#titlecaser-with-possessive-noun-and-a-colon)
31
+ + [TitleCaser with Default Word Replacement](#titlecaser-with-default-word-replacement)
32
+ + [TitleCaser with Possessive Noun and a Colon](#titlecaser-with-possessive-noun-and-a-colon)
33
33
  * [Build Process](#build-process)
34
34
  * [Test](#test)
35
35
  * [Resources](#resources)
@@ -130,6 +130,7 @@ function applyTitleCaseToH2Elements(options = { style: "apa" }) {
130
130
 
131
131
  applyTitleCaseToH2Elements();
132
132
  ```
133
+
133
134
  ## Options
134
135
 
135
136
  The `{options}` parameter is an object that contains the settings for the conversion process.
@@ -140,6 +141,7 @@ The `{options}` parameter is an object that contains the settings for the conver
140
141
  - `shortPrepositionsList` relates to the words that should be treated as short prepositions in title case.
141
142
  - `neverCapitalizedList` contains the words that should never be capitalized in title case.
142
143
  - `wordReplacementsList` is a map of terms that will be replaced during the title case conversion process.
144
+ - `smartQuotes` boolean value that determines whether quotes should be replaced with smart quotes.
143
145
 
144
146
  ## Methods
145
147
 
@@ -147,6 +149,7 @@ The `{options}` parameter is an object that contains the settings for the conver
147
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.
148
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.
149
151
  - `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
+ - `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.
150
153
 
151
154
  ## Examples
152
155
 
@@ -200,7 +203,7 @@ const options = {
200
203
  // Instantiate a new TitleCaser object with the options
201
204
  const titleCaser = new TitleCaser(options);
202
205
 
203
- // Set the input string to test
206
+ // Set the input string to be tested
204
207
  const input = "the basics of nodejs development with mongodb";
205
208
 
206
209
  // Set the expected output
@@ -210,7 +213,7 @@ const expectedOutput = "The Basics of Node.js Development with MongoDB";
210
213
  const actualOutput = titleCaser.toTitleCase(input);
211
214
  ```
212
215
 
213
- ### TitleCaser With Default Word Replacement
216
+ ### TitleCaser with Default Word Replacement
214
217
 
215
218
  The example below demonstrates how to use the TitleCaser class to convert a string to a title case with AP style formatting, including hyphenated words and word/brand replacement.
216
219
 
@@ -220,7 +223,7 @@ import "./path/to/@danielhaim/titlecaser";
220
223
  // Instantiate a new TitleCaser object with AP style formatting
221
224
  const titleCaser = new TitleCaser({ style: 'ap' });
222
225
 
223
- // Set the input string to test
226
+ // Set the input string to be tested
224
227
  const input = 'nodejs development on aws: an in-depth tutorial on server-side javascript deployment';
225
228
 
226
229
  // Set the expected output
@@ -230,7 +233,7 @@ const expectedOutput = 'Node.js Development on AWS: An In-depth Tutorial on Serv
230
233
  const actualOutput = titleCaser.toTitleCase(input);
231
234
  ```
232
235
 
233
- ### TitleCaser With Possessive Noun and a Colon
236
+ ### TitleCaser with Possessive Noun and a Colon
234
237
 
235
238
  The example below demonstrates how to use the TitleCaser class to convert a string to title case with AP style formatting, including a possessive noun and a colon.
236
239
 
@@ -240,7 +243,7 @@ import "./path/to/@danielhaim/titlecaser";
240
243
  // Instantiate a new TitleCaser object with AP style formatting
241
244
  const titleCaser = new TitleCaser({ style: "ap" });
242
245
 
243
- // Set the input string to test
246
+ // Set the input string to be tested
244
247
  const input = "the iphone's impact on modern communication: a sociolinguistic analysis";
245
248
 
246
249
  // Set the expected output
@@ -250,6 +253,29 @@ const expectedOutput = "The iPhone's Impact on Modern Communication: A Socioling
250
253
  const actualOutput = titleCaser.toTitleCase(input);
251
254
  ```
252
255
 
256
+ ### TitleCaser with Smart Quotes
257
+
258
+ The example below demonstrates how to use the TitleCaser with smart quotes.
259
+
260
+ ```js
261
+ import "./path/to/@danielhaim/titlecaser";
262
+
263
+ // Instantiate a new TitleCaser object with AP style formatting and smart quotes enabled
264
+ const titleCaser = new TitleCaser({
265
+ style: 'ap',
266
+ smartQuotes: true
267
+ });
268
+
269
+ // Set the input string to be tested
270
+ const input = '"Never underestimate the power O\' persistence,"';
271
+
272
+ // Set the expected output
273
+ const expectedOutput = '“Never Underestimate the Power O’ Persistence,”';
274
+
275
+ // Call the toTitleCase method and store the result in actualOutput
276
+ const actualOutput = titleCaser.toTitleCase(input);
277
+ ```
278
+
253
279
  ## Build Process
254
280
 
255
281
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielhaim/titlecaser",
3
- "version": "1.2.53",
3
+ "version": "1.2.56",
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
@@ -9,222 +9,228 @@ import {
9
9
  import { TitleCaserUtils } from "./TitleCaserUtils.js";
10
10
 
11
11
  export class TitleCaser {
12
- constructor ( options = {} ) {
12
+ constructor(options = {}) {
13
13
  this.options = options;
14
14
  this.wordReplacementsList = wordReplacementsList;
15
15
  }
16
-
17
- toTitleCase ( str ) {
16
+
17
+ toTitleCase(str) {
18
18
  try {
19
19
  // If input is empty, throw an error.
20
- if ( str.trim ().length === 0 ) throw new TypeError ( "Invalid input: input must not be empty." );
21
-
20
+ if (str.trim().length === 0) throw new TypeError("Invalid input: input must not be empty.");
21
+
22
22
  // If input is not a string, throw an error.
23
- if ( typeof str !== 'string' ) throw new TypeError ( "Invalid input: input must be a string." );
24
-
23
+ if (typeof str !== 'string') throw new TypeError("Invalid input: input must be a string.");
24
+
25
25
  // If options is not an object, throw an error.
26
- if ( typeof this.options !== "undefined" && typeof this.options !== "object" ) throw new TypeError ( "Invalid options: options must be an object." );
27
-
26
+ if (typeof this.options !== "undefined" && typeof this.options !== "object") throw new TypeError("Invalid options: options must be an object.");
27
+
28
28
  const {
29
29
  style = "ap",
30
30
  neverCapitalize = [],
31
- replaceTermList = wordReplacementsList
31
+ replaceTermList = wordReplacementsList,
32
+ smartQuotes = false // Set to false by default
32
33
  } = this.options;
33
- const ignoreList = [ "nl2br", ...neverCapitalize ];
34
+
35
+
36
+ const ignoreList = ["nl2br", ...neverCapitalize];
34
37
  const {
35
38
  articlesList,
36
39
  shortConjunctionsList,
37
40
  shortPrepositionsList,
38
41
  neverCapitalizedList,
39
- replaceTerms
40
- } = TitleCaserUtils.getTitleCaseOptions ( this.options, commonAbbreviationList, wordReplacementsList );
41
-
42
+ replaceTerms,
43
+ smartQuotes: mergedSmartQuotes // Rename for clarity
44
+ } = TitleCaserUtils.getTitleCaseOptions(this.options, commonAbbreviationList, wordReplacementsList);
45
+
42
46
  // Prerocess the replaceTerms array to make it easier to search for.
43
- const replaceTermsArray = replaceTermList.map ( term => Object.keys ( term )[0].toLowerCase () );
47
+ const replaceTermsArray = replaceTermList.map(term => Object.keys(term)[0].toLowerCase());
44
48
  // Create an object from the replaceTerms array to make it easier to search for.
45
- const replaceTermObj = Object.fromEntries ( replaceTermList.map (
46
- term => [ Object.keys ( term )[0].toLowerCase (), Object.values ( term )[0] ]
47
- ) );
48
-
49
+ const replaceTermObj = Object.fromEntries(replaceTermList.map(
50
+ term => [Object.keys(term)[0].toLowerCase(), Object.values(term)[0]]
51
+ ));
52
+
49
53
  const map = {
50
54
  '&': '&',
51
55
  '<': '&lt;',
52
56
  '>': '&gt;',
57
+ // '\u2018': '\u2019', // Smart single quote
58
+ // '\u201C': '\u201D', // Smart double quote
53
59
  '"': '&quot;',
54
60
  "'": '&#039;'
55
61
  };
56
-
62
+
57
63
  // Remove extra spaces and replace <br> tags with a placeholder.
58
- let inputString = str.trim ();
59
-
64
+ let inputString = str.trim();
65
+
60
66
  // Replace <br> and <br /> tags with a placeholder.
61
67
  inputString = inputString.replace(/<\s*br\s*\/?\s*>/gi, " nl2br ");
62
68
 
63
69
  // Remove extra spaces and replace <br> tags with a placeholder.
64
- inputString = inputString.replace ( / {2,}/g, ( match ) => match.slice ( 0, 1 ) );
65
-
66
-
67
- // console.log(inputString);
70
+ inputString = inputString.replace(/ {2,}/g, (match) => match.slice(0, 1));
68
71
 
69
72
 
70
73
  // Split the string into an array of words.
71
- const words = inputString.split ( " " );
72
-
73
- const wordsInTitleCase = words.map ( ( word, i ) => {
74
+ const words = inputString.split(" ");
75
+
76
+ const wordsInTitleCase = words.map((word, i) => {
74
77
  switch (true) {
75
- case TitleCaserUtils.isWordAmpersand ( word ):
78
+ case TitleCaserUtils.isWordAmpersand(word):
76
79
  // if the word is an ampersand, return it as is.
77
80
  return word;
78
- case TitleCaserUtils.hasHtmlBreak ( word ):
81
+ case TitleCaserUtils.hasHtmlBreak(word):
79
82
  // If the word is a <br> tag, return it as is.
80
83
  return word;
81
- case TitleCaserUtils.isWordIgnored ( word, ignoreList ):
84
+ case TitleCaserUtils.isWordIgnored(word, ignoreList):
82
85
  // If the word is in the ignore list, return it as is.
83
86
  return word;
84
- case replaceTermsArray.includes ( word.toLowerCase () ):
87
+ case replaceTermsArray.includes(word.toLowerCase()):
85
88
  // If the word is in the replaceTerms array, return the replacement.
86
- return replaceTermObj[word.toLowerCase ()];
87
- case TitleCaserUtils.isWordInArray ( word, correctTitleCasingList ):
89
+ return replaceTermObj[word.toLowerCase()];
90
+ case TitleCaserUtils.isWordInArray(word, correctTitleCasingList):
88
91
  // If the word is in the correctTitleCasingList array, return the correct casing.
89
- return TitleCaserUtils.correctTerm ( word, correctTitleCasingList );
90
- case TitleCaserUtils.hasSuffix ( word, style ):
92
+ return TitleCaserUtils.correctTerm(word, correctTitleCasingList);
93
+ case TitleCaserUtils.hasSuffix(word, style):
91
94
  // If the word has a suffix, return the correct casing.
92
- return TitleCaserUtils.correctSuffix ( word, correctTitleCasingList );
93
- case TitleCaserUtils.hasHyphen ( word ):
95
+ return TitleCaserUtils.correctSuffix(word, correctTitleCasingList);
96
+ case TitleCaserUtils.hasHyphen(word):
94
97
  // If the word has a hyphen, return the correct casing.
95
- return TitleCaserUtils.correctTermHyphenated ( word, style );
96
- case TitleCaserUtils.hasUppercaseIntentional ( word ):
98
+ return TitleCaserUtils.correctTermHyphenated(word, style);
99
+ case TitleCaserUtils.hasUppercaseIntentional(word):
97
100
  // If the word has an intentional uppercase letter, return the correct casing.
98
101
  return word;
99
- case TitleCaserUtils.isShortWord ( word, style ) && i !== 0:
102
+ case TitleCaserUtils.isShortWord(word, style) && i !== 0:
100
103
  // If the word is a short word, return the correct casing.
101
- return (i > 0 && TitleCaserUtils.endsWithSymbol ( words[i - 1],
102
- [ ':', '?', '!', '.' ] )) ? word.charAt ( 0 ).toUpperCase () + word.slice ( 1 ) : word.toLowerCase ();
103
- case TitleCaserUtils.endsWithSymbol ( word ):
104
+ return (i > 0 && TitleCaserUtils.endsWithSymbol(words[i - 1],
105
+ [':', '?', '!', '.'])) ? word.charAt(0).toUpperCase() + word.slice(1) : word.toLowerCase();
106
+ case TitleCaserUtils.endsWithSymbol(word):
104
107
  // If the word ends with a symbol, return the correct casing.
105
- const splitWord = word.split ( /([.,\/#!$%\^&\*;:{}=\-_`~()])/g );
106
- const processedWords = splitWord.map ( ( splitWord, j ) => {
108
+ const splitWord = word.split(/([.,\/#!$%\^&\*;:{}=\-_`~()])/g);
109
+ const processedWords = splitWord.map((splitWord, j) => {
107
110
  // If the word is in the correctTitleCasingList array, return the correct casing.
108
- if ( TitleCaserUtils.isWordInArray ( splitWord, correctTitleCasingList ) ) return TitleCaserUtils.correctTerm ( splitWord, correctTitleCasingList );
111
+ if (TitleCaserUtils.isWordInArray(splitWord, correctTitleCasingList)) return TitleCaserUtils.correctTerm(splitWord, correctTitleCasingList);
109
112
  // Else return the word with the correct casing.
110
- else return (j > 0 && TitleCaserUtils.endsWithSymbol ( splitWord )) ? splitWord.charAt ( 0 )
111
- .toUpperCase () + splitWord.slice ( 1 ) : splitWord.charAt ( 0 )
112
- .toUpperCase () + splitWord.slice ( 1 );
113
- } );
113
+ else return (j > 0 && TitleCaserUtils.endsWithSymbol(splitWord)) ? splitWord.charAt(0)
114
+ .toUpperCase() + splitWord.slice(1) : splitWord.charAt(0)
115
+ .toUpperCase() + splitWord.slice(1);
116
+ });
114
117
  // Join the processed words and return them.
115
- return processedWords.join ( "" );
116
- case TitleCaserUtils.startsWithSymbol ( word ):
118
+ return processedWords.join("");
119
+ case TitleCaserUtils.startsWithSymbol(word):
117
120
  // If the word starts with a symbol, return the correct casing.
118
- return !TitleCaserUtils.isWordInArray ( word, correctTitleCasingList ) ? word : TitleCaserUtils.correctTerm ( word );
119
- case TitleCaserUtils.hasRomanNumeral ( word ):
121
+ return !TitleCaserUtils.isWordInArray(word, correctTitleCasingList) ? word : TitleCaserUtils.correctTerm(word);
122
+ case TitleCaserUtils.hasRomanNumeral(word):
120
123
  // If the word has a roman numeral, return the correct casing.
121
- return word.toUpperCase ();
122
- case TitleCaserUtils.hasNumbers ( word ):
124
+ return word.toUpperCase();
125
+ case TitleCaserUtils.hasNumbers(word):
123
126
  // If the word has numbers, return the correct casing.
124
127
  return word;
125
- default:
128
+ default:
126
129
  // Default to returning the word with the correct casing.
127
- return word.charAt ( 0 )
128
- .toUpperCase () + word.slice ( 1 )
129
- .toLowerCase ();
130
+ return word.charAt(0)
131
+ .toUpperCase() + word.slice(1)
132
+ .toLowerCase();
130
133
  }
131
- } );
132
-
134
+ });
135
+
133
136
  // Join the words in the array into a string.
134
- inputString = wordsInTitleCase.join ( " " );
135
-
136
- for ( const phrase of correctPhraseCasingList ) {
137
+ inputString = wordsInTitleCase.join(" ");
138
+
139
+ for (const phrase of correctPhraseCasingList) {
137
140
  // If the phrase is in the input string, replace it with the correct casing.
138
- if ( inputString.toLowerCase ()
139
- .includes ( phrase.toLowerCase () ) ) {
140
- inputString = inputString.replace ( new RegExp ( phrase, 'gi' ), phrase );
141
+ if (inputString.toLowerCase()
142
+ .includes(phrase.toLowerCase())) {
143
+ inputString = inputString.replace(new RegExp(phrase, 'gi'), phrase);
141
144
  }
142
145
  }
143
-
146
+
144
147
  // Replace the nl2br placeholder with <br> tags.
145
148
  inputString = inputString.replace(/nl2br/gi, "<br />");
146
-
147
- // BEFORE WE RETURN THE STRING
148
- // CHECK THE LAST WORD AND IF IT IS INTENTIONALLY UPPERCASED, IF IT IS, RETURN THE STRING.
149
+
150
+ // Convert quotation marks to smart quotes if enabled
151
+ // Refer to: https://github.com/danielhaim1/TitleCaser/issues/4
152
+ if (smartQuotes) {
153
+ inputString = TitleCaserUtils.convertQuotesToCurly(inputString);
154
+ }
149
155
 
150
156
  // Return the string.
151
157
  return inputString;
152
- } catch ( error ) {
153
- throw new Error ( error );
158
+ } catch (error) {
159
+ throw new Error(error);
154
160
  }
155
161
  }
156
-
157
- setReplaceTerms ( terms ) {
158
- if ( typeof terms !== 'object' ) {
159
- throw new TypeError ( 'Invalid argument: replace terms must be an object.' );
162
+
163
+ setReplaceTerms(terms) {
164
+ if (typeof terms !== 'object') {
165
+ throw new TypeError('Invalid argument: replace terms must be an object.');
160
166
  }
161
-
167
+
162
168
  // Add the new replace terms to the wordReplacementsList array
163
- Object.entries ( terms ).forEach ( ( [ term, replacement ] ) => {
164
- const index = wordReplacementsList.findIndex ( obj => obj[term] );
165
- if ( index !== -1 ) {
169
+ Object.entries(terms).forEach(([term, replacement]) => {
170
+ const index = wordReplacementsList.findIndex(obj => obj[term]);
171
+ if (index !== -1) {
166
172
  // If the term already exists in the array, update the replacement value
167
173
  wordReplacementsList[index][term] = replacement;
168
174
  } else {
169
175
  // If the term doesn't exist in the array, add a new object with the term and replacement
170
- wordReplacementsList.push ( { [term]: replacement } );
176
+ wordReplacementsList.push({ [term]: replacement });
171
177
  }
172
- } );
173
-
178
+ });
179
+
174
180
  // Log the updated wordReplacementsList array
175
181
  // console.log(wordReplacementsList);
176
-
182
+
177
183
  // Update the replace terms option
178
184
  this.options.wordReplacementsList = wordReplacementsList;
179
185
  }
180
-
181
- addReplaceTerm ( term, replacement ) {
182
- if ( typeof term !== 'string' || typeof replacement !== 'string' ) {
183
- throw new TypeError ( 'Invalid argument: term and replacement must be strings.' );
186
+
187
+ addReplaceTerm(term, replacement) {
188
+ if (typeof term !== 'string' || typeof replacement !== 'string') {
189
+ throw new TypeError('Invalid argument: term and replacement must be strings.');
184
190
  }
185
-
186
- const index = this.wordReplacementsList.findIndex ( obj => obj[term] );
187
-
188
- if ( index !== -1 ) {
191
+
192
+ const index = this.wordReplacementsList.findIndex(obj => obj[term]);
193
+
194
+ if (index !== -1) {
189
195
  // If the term already exists in the array, update the replacement value
190
196
  this.wordReplacementsList[index][term] = replacement;
191
197
  } else {
192
198
  // If the term doesn't exist in the array, add a new object with the term and replacement
193
- this.wordReplacementsList.push ( { [term]: replacement } );
199
+ this.wordReplacementsList.push({ [term]: replacement });
194
200
  }
195
-
201
+
196
202
  // Update the replace terms option
197
203
  this.options.wordReplacementsList = this.wordReplacementsList;
198
204
  }
199
-
200
- removeReplaceTerm ( term ) {
201
- if ( typeof term !== 'string' ) {
202
- throw new TypeError ( 'Invalid argument: term must be a string.' );
205
+
206
+ removeReplaceTerm(term) {
207
+ if (typeof term !== 'string') {
208
+ throw new TypeError('Invalid argument: term must be a string.');
203
209
  }
204
-
210
+
205
211
  // Find the index of the term in the wordReplacementsList array
206
- const index = this.wordReplacementsList.findIndex ( obj => Object.keys ( obj )[0] === term );
207
-
212
+ const index = this.wordReplacementsList.findIndex(obj => Object.keys(obj)[0] === term);
213
+
208
214
  // If the term is not found in the array, throw an error
209
- if ( index === -1 ) {
210
- throw new Error ( `Term '${ term }' not found in word replacements list.` );
215
+ if (index === -1) {
216
+ throw new Error(`Term '${term}' not found in word replacements list.`);
211
217
  }
212
-
218
+
213
219
  // Remove the term from the array
214
- this.wordReplacementsList.splice ( index, 1 );
215
-
220
+ this.wordReplacementsList.splice(index, 1);
221
+
216
222
  // Log the updated wordReplacementsList array
217
223
  // console.log(this.wordReplacementsList);
218
-
224
+
219
225
  // Update the replace terms option
220
226
  this.options.wordReplacementsList = this.wordReplacementsList;
221
227
  }
222
-
223
- setStyle ( style ) {
224
- if ( typeof style !== 'string' ) {
225
- throw new TypeError ( 'Invalid argument: style must be a string.' );
228
+
229
+ setStyle(style) {
230
+ if (typeof style !== 'string') {
231
+ throw new TypeError('Invalid argument: style must be a string.');
226
232
  }
227
-
233
+
228
234
  this.options.style = style;
229
235
  }
230
236
  }