@danielhaim/titlecaser 1.2.45 → 1.2.48
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 +27 -43
- package/index.js +11 -12
- package/package.json +16 -14
- package/src/TitleCaser.js +220 -216
- package/src/TitleCaserConsts.js +172 -172
- package/src/TitleCaserUtils.js +565 -564
package/src/TitleCaser.js
CHANGED
|
@@ -1,223 +1,227 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
commonAbbreviationList,
|
|
3
|
+
correctTitleCasingList,
|
|
4
|
+
correctPhraseCasingList,
|
|
5
|
+
wordReplacementsList,
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
from "./TitleCaserConsts.js";
|
|
8
8
|
|
|
9
9
|
import { TitleCaserUtils } from "./TitleCaserUtils.js";
|
|
10
10
|
|
|
11
11
|
export class TitleCaser {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
12
|
+
constructor ( options = {} ) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
this.wordReplacementsList = wordReplacementsList;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
toTitleCase ( str ) {
|
|
18
|
+
try {
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
28
|
+
const {
|
|
29
|
+
style = "ap",
|
|
30
|
+
neverCapitalize = [],
|
|
31
|
+
replaceTermList = wordReplacementsList
|
|
32
|
+
} = this.options;
|
|
33
|
+
const ignoreList = [ "nl2br", ...neverCapitalize ];
|
|
34
|
+
const {
|
|
35
|
+
articlesList,
|
|
36
|
+
shortConjunctionsList,
|
|
37
|
+
shortPrepositionsList,
|
|
38
|
+
neverCapitalizedList,
|
|
39
|
+
replaceTerms
|
|
40
|
+
} = TitleCaserUtils.getTitleCaseOptions ( this.options, commonAbbreviationList, wordReplacementsList );
|
|
41
|
+
|
|
42
|
+
// Prerocess the replaceTerms array to make it easier to search for.
|
|
43
|
+
const replaceTermsArray = replaceTermList.map ( term => Object.keys ( term )[0].toLowerCase () );
|
|
44
|
+
// 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 map = {
|
|
50
|
+
'&': '&',
|
|
51
|
+
'<': '<',
|
|
52
|
+
'>': '>',
|
|
53
|
+
'"': '"',
|
|
54
|
+
"'": '''
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Remove extra spaces and replace <br> tags with a placeholder.
|
|
58
|
+
let inputString = str.trim ();
|
|
59
|
+
|
|
60
|
+
// Replace <br> and <br /> tags with a placeholder.
|
|
61
|
+
inputString = inputString.replace(/<\s*br\s*\/?\s*>/gi, " nl2br ");
|
|
62
|
+
|
|
63
|
+
// 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);
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
// Split the string into an array of words.
|
|
71
|
+
const words = inputString.split ( " " );
|
|
72
|
+
|
|
73
|
+
const wordsInTitleCase = words.map ( ( word, i ) => {
|
|
74
|
+
switch (true) {
|
|
75
|
+
case TitleCaserUtils.isWordAmpersand ( word ):
|
|
76
|
+
// if the word is an ampersand, return it as is.
|
|
77
|
+
return word;
|
|
78
|
+
case TitleCaserUtils.hasHtmlBreak ( word ):
|
|
79
|
+
// If the word is a <br> tag, return it as is.
|
|
80
|
+
return word;
|
|
81
|
+
case TitleCaserUtils.isWordIgnored ( word, ignoreList ):
|
|
82
|
+
// If the word is in the ignore list, return it as is.
|
|
83
|
+
return word;
|
|
84
|
+
case replaceTermsArray.includes ( word.toLowerCase () ):
|
|
85
|
+
// If the word is in the replaceTerms array, return the replacement.
|
|
86
|
+
return replaceTermObj[word.toLowerCase ()];
|
|
87
|
+
case TitleCaserUtils.isWordInArray ( word, correctTitleCasingList ):
|
|
88
|
+
// If the word is in the correctTitleCasingList array, return the correct casing.
|
|
89
|
+
return TitleCaserUtils.correctTerm ( word, correctTitleCasingList );
|
|
90
|
+
case TitleCaserUtils.hasSuffix ( word, style ):
|
|
91
|
+
// If the word has a suffix, return the correct casing.
|
|
92
|
+
return TitleCaserUtils.correctSuffix ( word, correctTitleCasingList );
|
|
93
|
+
case TitleCaserUtils.hasHyphen ( word ):
|
|
94
|
+
// If the word has a hyphen, return the correct casing.
|
|
95
|
+
return TitleCaserUtils.correctTermHyphenated ( word, style );
|
|
96
|
+
case TitleCaserUtils.hasUppercaseIntentional ( word ):
|
|
97
|
+
// If the word has an intentional uppercase letter, return the correct casing.
|
|
98
|
+
return word;
|
|
99
|
+
case TitleCaserUtils.isShortWord ( word, style ) && i !== 0:
|
|
100
|
+
// 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
|
+
// If the word ends with a symbol, return the correct casing.
|
|
105
|
+
const splitWord = word.split ( /([.,\/#!$%\^&\*;:{}=\-_`~()])/g );
|
|
106
|
+
const processedWords = splitWord.map ( ( splitWord, j ) => {
|
|
107
|
+
// If the word is in the correctTitleCasingList array, return the correct casing.
|
|
108
|
+
if ( TitleCaserUtils.isWordInArray ( splitWord, correctTitleCasingList ) ) return TitleCaserUtils.correctTerm ( splitWord, correctTitleCasingList );
|
|
109
|
+
// 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
|
+
} );
|
|
114
|
+
// Join the processed words and return them.
|
|
115
|
+
return processedWords.join ( "" );
|
|
116
|
+
case TitleCaserUtils.startsWithSymbol ( word ):
|
|
117
|
+
// 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 ):
|
|
120
|
+
// If the word has a roman numeral, return the correct casing.
|
|
121
|
+
return word.toUpperCase ();
|
|
122
|
+
case TitleCaserUtils.hasNumbers ( word ):
|
|
123
|
+
// If the word has numbers, return the correct casing.
|
|
124
|
+
return word;
|
|
125
|
+
default:
|
|
126
|
+
// Default to returning the word with the correct casing.
|
|
127
|
+
return word.charAt ( 0 )
|
|
128
|
+
.toUpperCase () + word.slice ( 1 )
|
|
129
|
+
.toLowerCase ();
|
|
130
|
+
}
|
|
131
|
+
} );
|
|
132
|
+
|
|
133
|
+
// Join the words in the array into a string.
|
|
134
|
+
inputString = wordsInTitleCase.join ( " " );
|
|
135
|
+
|
|
136
|
+
for ( const phrase of correctPhraseCasingList ) {
|
|
137
|
+
// 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
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Replace the nl2br placeholder with <br> tags.
|
|
145
|
+
inputString = inputString.replace(/nl2br/gi, "<br />");
|
|
146
|
+
|
|
147
|
+
// Return the string.
|
|
148
|
+
return inputString;
|
|
149
|
+
} catch ( error ) {
|
|
150
|
+
throw new Error ( error );
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
setReplaceTerms ( terms ) {
|
|
155
|
+
if ( typeof terms !== 'object' ) {
|
|
156
|
+
throw new TypeError ( 'Invalid argument: replace terms must be an object.' );
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Add the new replace terms to the wordReplacementsList array
|
|
160
|
+
Object.entries ( terms ).forEach ( ( [ term, replacement ] ) => {
|
|
161
|
+
const index = wordReplacementsList.findIndex ( obj => obj[term] );
|
|
162
|
+
if ( index !== -1 ) {
|
|
163
|
+
// If the term already exists in the array, update the replacement value
|
|
164
|
+
wordReplacementsList[index][term] = replacement;
|
|
165
|
+
} else {
|
|
166
|
+
// If the term doesn't exist in the array, add a new object with the term and replacement
|
|
167
|
+
wordReplacementsList.push ( { [term]: replacement } );
|
|
168
|
+
}
|
|
169
|
+
} );
|
|
170
|
+
|
|
171
|
+
// Log the updated wordReplacementsList array
|
|
172
|
+
// console.log(wordReplacementsList);
|
|
173
|
+
|
|
174
|
+
// Update the replace terms option
|
|
175
|
+
this.options.wordReplacementsList = wordReplacementsList;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
addReplaceTerm ( term, replacement ) {
|
|
179
|
+
if ( typeof term !== 'string' || typeof replacement !== 'string' ) {
|
|
180
|
+
throw new TypeError ( 'Invalid argument: term and replacement must be strings.' );
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const index = this.wordReplacementsList.findIndex ( obj => obj[term] );
|
|
184
|
+
|
|
185
|
+
if ( index !== -1 ) {
|
|
186
|
+
// If the term already exists in the array, update the replacement value
|
|
187
|
+
this.wordReplacementsList[index][term] = replacement;
|
|
188
|
+
} else {
|
|
189
|
+
// If the term doesn't exist in the array, add a new object with the term and replacement
|
|
190
|
+
this.wordReplacementsList.push ( { [term]: replacement } );
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Update the replace terms option
|
|
194
|
+
this.options.wordReplacementsList = this.wordReplacementsList;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
removeReplaceTerm ( term ) {
|
|
198
|
+
if ( typeof term !== 'string' ) {
|
|
199
|
+
throw new TypeError ( 'Invalid argument: term must be a string.' );
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Find the index of the term in the wordReplacementsList array
|
|
203
|
+
const index = this.wordReplacementsList.findIndex ( obj => Object.keys ( obj )[0] === term );
|
|
204
|
+
|
|
205
|
+
// If the term is not found in the array, throw an error
|
|
206
|
+
if ( index === -1 ) {
|
|
207
|
+
throw new Error ( `Term '${ term }' not found in word replacements list.` );
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Remove the term from the array
|
|
211
|
+
this.wordReplacementsList.splice ( index, 1 );
|
|
212
|
+
|
|
213
|
+
// Log the updated wordReplacementsList array
|
|
214
|
+
// console.log(this.wordReplacementsList);
|
|
215
|
+
|
|
216
|
+
// Update the replace terms option
|
|
217
|
+
this.options.wordReplacementsList = this.wordReplacementsList;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
setStyle ( style ) {
|
|
221
|
+
if ( typeof style !== 'string' ) {
|
|
222
|
+
throw new TypeError ( 'Invalid argument: style must be a string.' );
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.options.style = style;
|
|
226
|
+
}
|
|
223
227
|
}
|