I have a kebabize function which converts camelCase to kebab-case. I am sharing my code. Can it be more optimized? I know this problem can be solved using regex. But, I want to do it without using regex.
const kebabize = str => {
let subs = []
let char = ''
let j = 0
for( let i = 0; i < str.length; i++ ) {
char = str[i]
if(str[i] === char.toUpperCase()) {
subs.push(str.slice(j, i))
j = i
}
if(i == str.length - 1) {
subs.push(str.slice(j, str.length))
}
}
return subs.map(el => (el.charAt(0).toLowerCase() + el.substr(1, el.length))).join('-')
}
kebabize('myNameIsStack')
const kebabize = str => {
return str.split('').map((letter, idx) => {
return letter.toUpperCase() === letter
? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}`
: letter;
}).join('');
}
console.log(kebabize('myNameIsStack'));
console.log(kebabize('MyNameIsStack'));
You can just check every letter is if upperCase or not and replace it.
I have a one-liner similar to Marc's but with a simpler Regular Expression and ~20% faster according my benchmark (Chrome 89).
const kebabize = (str) => str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? "-" : "") + $.toLowerCase())
const words = ['StackOverflow', 'camelCase', 'alllowercase', 'ALLCAPITALLETTERS', 'CustomXMLParser', 'APIFinder', 'JSONResponseData', 'Person20Address', 'UserAPI20Endpoint'];
console.log(words.map(kebabize));
[A-Z]+(?![a-z]) matches any consecutive capital letters, excluding any capitals followed by a lowercase (signifying the next word). Adding |[A-Z] then includes any single capital letters. It must be after the consecutive capital expression, otherwise the expression will match all capital letters individually and never match consecutives.
String.prototype.replace can take a replacer function. Here, it returns the lowercased matched capital(s) for each word, after prefixing a hyphen when the match offset is truthy (not zero - not the first character of the string).
I suspect Marc's solution is less performant than mine because by using replace to insert hyphens and lowercasing the whole string afterwards, it must iterate over the string more than once, and its expression also has more complex look aheads/behind constructs.
Benchmark
RegEx is faster!
Unlike what you might think, the RegEx way of doing this is actually significantly faster! See benchmark.
The function below supports converting both camelCase and PascalCase into kebab-case:
function toKebabCase(str) {
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}
Here is my solution:
Works with camelCase and PascalCase:
let words = ['StackOverflow', 'camelCase', 'alllowercase', 'ALLCAPITALLETTERS', 'CustomXMLParser', 'APIFinder', 'JSONResponseData', 'Person20Address', 'UserAPI20Endpoint'];
let result = words.map(w => w.replace(/((?<=[a-z\d])[A-Z]|(?<=[A-Z\d])[A-Z](?=[a-z]))/g, '-$1').toLowerCase());
console.log(result);
/*
Returns:
[
"stack-overflow",
"camel-case",
"alllowercase",
"allcapitalletters",
"custom-xml-parser",
"api-finder",
"json-response-data",
"person20-address",
"user-api20-endpoint"
]
*/
Explanation:
Match any of the following regular expressions:
Find any capital letter, that is immediately preceeded by a small letter or a number, or
Find any capital letter, that is immediately preceeded by a capital letter or a number, that is immediately followed by a small letter
Replace the captured position with a dash ('-') followed by the captured capital letter
Finally, convert the whole string to lowercase.
I would use something like this.
function kebabize(string) {
// uppercase after a non-uppercase or uppercase before non-uppercase
const upper = /(?<!\p{Uppercase_Letter})\p{Uppercase_Letter}|\p{Uppercase_Letter}(?!\p{Uppercase_Letter})/gu;
return string.replace(upper, "-$&").replace(/^-/, "").toLowerCase();
}
const strings = ["myNameIsStack", "HTTPRequestData", "DataX", "Foo6HelloWorld9Bar", "Áb"];
const result = strings.map(kebabize);
console.log(result);
This snippet replaces all uppercase characters before or after a non-uppercase character with - followed by the uppercase. It then removes the - at the start of the string (if there is any) and downcases the whole string.
Simple solution for older browsers:
var str = 'someExampleString'
var i
function camelToKebab() {
var __str = arguments[0]
var __result = ''
for (i = 0; i < __str.length; i++) {
var x = __str[i]
if(x === x.toUpperCase()) {
__result += '-' + x.toLowerCase()
} else {
__result += x
}
}
return __result
}
console.log(str, '->', camelToKebab(str))
Here is the solution I came up with:
let resultDiv = document.querySelector(".result");
let camelCase = "thisIsCamelCase";
let kebabCase;
kebabCase = camelCase.split('').map(el=> {
const charCode = el.charCodeAt(0);
if(charCode>=65 && charCode<=90){
return "-" + el.toLowerCase()
}else{
return el;
}
})
return(kebabCase.join(''))
Related
I would like to know how to capitalize the first letter after hypen in a string using javascript. If no hypen str should in lowercase
var result = capitalize("js-script");
function capitalize(str){
return str.split("-")[1].charAt(0).toUpperCase()+ str.slice(1);
}
Expected Output:
js-script => js-Script
tom => tom
Consider using a regular expression instead - match a - and an alphabetical character, and replace with a - and that word character, capitalized:
const capitalize = (str) => str.replace(/-([a-z])/g, (_, char) => '-' + char.toUpperCase());
console.log(capitalize("js-script"));
console.log(capitalize("foo-bar-baz"));
To fix your original code, if there's only going to be one - in the input, you need to save the rest of the characters in the part after the - (not just the charAt(0)):
function capitalize(str) {
if (!str.includes('-')) {
return str;
}
const [before, after] = str.split("-");
return before + '-' + after.charAt(0).toUpperCase() + after.slice(1);
}
console.log(capitalize('foo-bar'));
console.log(capitalize('foo'));
You can use regex and look behind to do this:
console.log(capitalize("js-script"));
function capitalize(str){
return str.replace(/(?<=-)\w/g, (text) => text.toUpperCase());
}
You can simply use regex and replace method
-[a-z]
- - match character -
[a-z] - match any character from a to z
function capitalize(str){
return typeof str === 'string' ? str.replace(/-([a-z])/gi,(m,g1)=> `-${g1.toUpperCase()}`) : str
}
console.log(capitalize("js-script"))
console.log( capitalize("tom"))
You can do this,
function capitalize(str){
let arrSplit = str.split("-")
let joinArray = [];
for(var i=0;i<arrSplit.length;i++){
if(i==0){
joinArray.push(arrSplit[i]);
}else{
joinArray.push(arrSplit[i].charAt(0).toUpperCase()+arrSplit[i].slice(1));
}
}
return joinArray.join("-",)
}
console.log(capitalize("js-script"))
console.log(capitalize("js-script-again"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Everything I can find by searching is people wanting to convert to sentence/title case from lower/upper/random case. That's the opposite of my problem.
What I have is already correct, I want to convert it to uppercase except for the "c" or "ac" etc. So McDonald becomes McDONALD, MacDonald becomes MacDONALD, etc.
Probably the best way is separating out the lower-case letters that occur between two upper-case letters, either before or after running toUpperCase(), but my brain is fried at the moment so I'm not sure how to go about it.
It's for an After Effects expression, controlling the display so I can have sentence case in one composition and upper case in another, from the same source layer. So I know input will be perfect.
You can try something like this:
const input = "MacDonald";
const matches = input.match(/([A-Z][a-z]*)/g);
const output = matches.length > 1 ?
matches.reduce((acc, match, index) => {
if (index === 0) {
return match;
}
return acc + match.toUpperCase();
}) :
input.toUpperCase();
First we take the input apart by matching it against a simple regular expression. The match method in the example will return ["Mac","Donald"].
Then, if there is only one match, we return it in uppercase.
In case of multiple matches, we construct the result by concatenating uppercase parts except for the first part.
Here's a version for a whole sentence:
const input = "Old MacDonald is a fine man.";
const output = input
.split(/\s/)
.map(word => {
const matches = word.match(/([A-Z][a-z]*)/g);
if (!matches || matches.length === 1) {
return word.toUpperCase();
}
return matches.reduce((acc, match, index) => {
return index === 0 ? match : acc + match.toUpperCase();
});
})
.join(' ');
// output == "OLD MacDONALD IS A FINE MAN."
Sami Hult's answer covers most of the bases, but unfortunately refuses to work in After Effects due to syntax issues and map() and reduce() not being supported, and I wanted to make one small tweak, all-capsing only the last portion rather than all but the first (to account for a possible double prefix).
So based on that code, I came up with this:
function str_uppercase(str) {
str = str.split(/\s/);
var output = [];
for (i = 0; i < str.length; i++) {
var word = str[i];
var matches = word.match(/([A-Z][a-z]*)/g);
if (!matches || matches.length === 1) {
word = word.toUpperCase();
} else {
var x = matches.length - 1;
matches[x] = matches[x].toUpperCase();
word = matches.join('');
}
output.push(word);
}
return output.join(' ');
}
console.log(str_uppercase('Old MacMcDonald Had a farm'));
// => OLD MacMcDONALD HAD A FARM
The code below assumes a string prefix to be one capital letter character followed by one or more small letter characters followed by one capital letter character and always at the beginning of the whole word.
The prefix will be retained as it is and the rest will be capitalized.
const input = [
"McDonald",
"MacDonald",
"Mcdonald",
"mcDonald",
"mcdonald"
];
// Function for converting to special uppercase
const specialUpperCase = function(item) {
// Find prefix (one or more lower case characters between upper case character - at the beginning)
const match = item.match(/^[A-Z][a-z]+[A-Z]/);
if (match) {
// If prefix, capitalize only the remaining
return match[0] + item.substr(match[0].length).toLocaleUpperCase();
}
// If no prefix, capitalize the whole string
return item.toLocaleUpperCase();
};
const output = input.map(specialUpperCase);
console.log(output);
The easiest solution would probably be to keep a list of prefixes and test if the word starts with one of these:
//Prefixes to look for
var prefixToKeep = [
"Mac",
"Mc"
];
//Selective uppercase function
function selectiveUpperCase(text) {
//Find words by wordBoundaries
return text.replace(/\b\w+\b/gim, function (word) {
//Test for prefixes
for (var prefixToKeepIndex = 0; prefixToKeepIndex < prefixToKeep.length; prefixToKeepIndex++) {
var prefix = prefixToKeep[prefixToKeepIndex];
if (word.indexOf(prefix) === 0) {
//prefix matches. Return prefix as is + rest of the word in uppercase
return word.slice(0, prefix.length) + word.slice(prefix.length).toUpperCase();
}
}
//No prefix found, return word as uppercase
return word.toUpperCase();
});
}
//TEST
var text = "Old MacDonald had a farm\nE-I-E-I-O\nAnd on this farm he had a cow\nE-I-E-I-O\nWith a moo-moo here\nAnd a moo-moo there\nHere a moo, there a moo\nEverywhere a moo-moo\nOld MacDonald had a farm\nE-I-E-I-O ";
console.log(selectiveUpperCase(text));
EDIT 1 - Upper-Lower-Upper Test
In response to the comments, this newer version tests for Upper-Lower-Upper cases and uses its findings to determine which parts to uppercase.
//Selective uppercase function
function selectiveUpperCase(text) {
//Find words by wordBoundaries
return text.replace(/\b\w+\b/gim, function (word) {
var reg = /[A-Z]+[a-z]+[A-Z]\w+/gm;
//Test for Upper-Lower-Upper combo
if (reg.test(word) || reg.test(word)) {
//start at index 1
var l = 0;
while (l++ < word.length) {
//move up the word and test for an uppercase letter
if (word[l] === word[l].toUpperCase()) {
break;
}
l++;
//return the first slice (the prefix) as is and uppercase the rest
return word.slice(0, l) + word.slice(l).toUpperCase();
}
}
//No prefix found, return word as uppercase
return word.toUpperCase();
});
}
//TEST
var text = "Old MacDonald had a farm\nE-I-E-I-O\nAnd on this farm he had a cow\nE-I-E-I-O\nWith a moo-moo here\nAnd a moo-moo there\nHere a moo, there a moo\nEverywhere a moo-moo\nOld McDonald had a farm\nE-I-E-I-O ";
console.log(selectiveUpperCase(text));
ES6 version with RegEx, you can try below function replaceStr()
const replaceStr = str => str.replace(/(^[A-Z])([a-z]{1,2})(.+)/,
(_, p1, p2, p3) => p1.toUpperCase() + p2 + p3.toUpperCase());
Im trying to replace a character at a specific indexOf to uppercase.
My string is a surname plus the first letter in the last name,
looking like this: "lovisa t".
I check the position with this and it gives me the right place in the string. So the second gives me 8(in this case).
first = texten.indexOf(" ");
second = texten.indexOf(" ", first + 1);
And with this I replace the first letter to uppercase.
var name = texten.substring(0, second);
name=name.replace(/^./, name[0].toUpperCase());
But how do I replace the character at "second" to uppercase?
I tested with
name=name.replace(/.$/, name[second].toUpperCase());
But it did´t work, so any input really appreciated, thanks.
Your error is the second letter isn't in position 8, but 7.
Also this second = texten.indexOf(" ", first + 1); gives -1, not 8, because you do not have a two spaces in your string.
If you know that the string is always in the format surname space oneLetter and you want to capitalize the first letter and the last letter you can simply do this:
var name = 'something s';
name = name[0].toUpperCase() + name.substring(1, name.length - 1) + name[name.length -1].toUpperCase();
console.log(name)
Here's a version that does exactly what your question title asks for: It uppercases a specific index in a string.
function upperCaseAt(str, i) {
return str.substr(0, i) + str.charAt(i).toUpperCase() + str.substr(i + 1);
}
var str = 'lovisa t';
var i = str.indexOf(' ');
console.log(upperCaseAt(str, i + 1));
However, if you want to look for specific patterns in the string, you don't need to deal with indices.
var str = 'lovisa t';
console.log(str.replace(/.$/, function (m0) { return m0.toUpperCase(); }));
This version uses a regex to find the last character in a string and a replacement function to uppercase the match.
var str = 'lovisa t';
console.log(str.replace(/ [a-z]/, function (m0) { return m0.toUpperCase(); }));
This version is similar but instead of looking for the last character, it looks for a space followed by a lowercase letter.
var str = 'lovisa t';
console.log(str.replace(/(?:^|\s)\S/g, function (m0) { return m0.toUpperCase(); }));
Finally, here we're looking for (and uppercasing) all non-space characters that are preceded by the beginning of the string or a space character; i.e. we're uppercasing the start of each (space-separated) word.
All can be done by regex replace.
"lovisa t".replace(/(^|\s)\w/g, s=>s.toUpperCase());
Try this one (if it will be helpfull, better move constants to other place, due performance issues(yes, regexp creation is not fast)):
function normalize(str){
var LOW_DASH = /\_/g;
var NORMAL_TEXT_REGEXP = /([a-z])([A-Z])/g;
if(!str)str = '';
if(str.indexOf('_') > -1) {
str = str.replace(LOW_DASH, ' ');
}
if(str.match(NORMAL_TEXT_REGEXP)) {
str = str.replace(NORMAL_TEXT_REGEXP, '$1 $2');
}
if(str.indexOf(' ') > -1) {
var p = str.split(' ');
var out = '';
for (var i = 0; i < p.length; i++) {
if (!p[i])continue;
out += p[i].charAt(0).toUpperCase() + p[i].substring(1) + (i !== p.length - 1 ? ' ' : '');
}
return out;
} else {
return str.charAt(0).toUpperCase() + str.substring(1);
}
}
console.log(normalize('firstLast'));//First Last
console.log(normalize('first last'));//First Last
console.log(normalize('first_last'));//First Last
I have this string :
var a='abc123#xyz123';
I want to build 2 regexes replace functions which :
1) Replace all characters that do have a future '#' - with '*' (not including '#')
so the result should look like :
'******#xyz123'
2) Replace all characters that do not have a future '#' - with '*' (not including '#')
so the result should look like :
'abc123#******'
What have I tried :
For the positive lookahead :
var a='abc123#xyz123';
alert(a.replace(/(.+(?=#))+/ig,'*')); //*#xyz123 --wrong result since it is greedy...
Question :
How can I make my regexes work as expected ?
First part using lookahead:
repl = a.replace(/.(?=[^#]*#)/g, "*");
//=> "******#xyz123"
Explanation:
This regex finds any character that is followed by # using lookahead and replaced that with *.
Second part using replace callback:
repla = a.replace(/#(.*)$/, function(m, t) { return m[0] + t.replace(/./g, '*'); } );
//=> abc123#******
Explanation:
This code finds text after #. Inside the callback function is replaces every character with asterisk.
You can use indexOf and substr for this instead:
function maskBeforeAfter(before, str, character, maskCharacter) {
character = character || '#';
maskCharacter = maskCharacter || '*';
var characterPosition = str.indexOf(character);
if (characterPosition > -1) {
var mask = '';
if (before) {
for (var i = 0; i < characterPosition; i++) {
mask += maskCharacter;
}
return mask + str.substr(characterPosition);
} else {
for (var i = 0; i < str.length - characterPosition - 1; i++) {
mask += maskCharacter;
}
return str.substr(0, characterPosition + 1) + mask;
}
}
return str;
}
function maskBefore(str, character, maskCharacter) {
return maskBeforeAfter(true, str, character, maskCharacter);
}
function maskAfter(str, character, maskCharacter) {
return maskBeforeAfter(false, str, character, maskCharacter);
}
> var a = 'abc12345#xyz123';
> maskBefore(a);
"********#xyz123"
> maskAfter(a);
"abc12345#******"
If you insist on a simple regex:
The first one is already answered. The second can be written similarly:
a.replace(/[^#](?![^#]*#)/g, '*')
(?![^#]*#) is a negative lookahead that checks that there isn't a pound after the current character.
[^#] also checks that the current character isn't a pound. (we could have also used /(?![^#]*#)./g, but it is less pretty.
A positive option is:
a.replace(/[^#](?=[^#]*$)/g, '*');
this is very similar to the first one: (?=[^#]*$) checks that we have only non-pounds ahead, until the end of the string.
In both of this options, all characters in strings with no pounds will be replaces: "abcd" -> "****"
I’ve been trying to get a JavaScript regex command to turn something like "thisString" into "This String" but the closest I’ve gotten is replacing a letter, resulting in something like "Thi String" or "This tring". Any ideas?
To clarify I can handle the simplicity of capitalizing a letter, I’m just not as strong with RegEx, and splitting "somethingLikeThis" into "something Like This" is where I’m having trouble.
"thisStringIsGood"
// insert a space before all caps
.replace(/([A-Z])/g, ' $1')
// uppercase the first character
.replace(/^./, function(str){ return str.toUpperCase(); })
displays
This String Is Good
(function() {
const textbox = document.querySelector('#textbox')
const result = document.querySelector('#result')
function split() {
result.innerText = textbox.value
// insert a space before all caps
.replace(/([A-Z])/g, ' $1')
// uppercase the first character
.replace(/^./, (str) => str.toUpperCase())
};
textbox.addEventListener('input', split);
split();
}());
#result {
margin-top: 1em;
padding: .5em;
background: #eee;
white-space: pre;
}
<div>
Text to split
<input id="textbox" value="thisStringIsGood" />
</div>
<div id="result"></div>
I had an idle interest in this, particularly in handling sequences of capitals, such as in xmlHTTPRequest. The listed functions would produce "Xml H T T P Request" or "Xml HTTPRequest", mine produces "Xml HTTP Request".
function unCamelCase (str){
return str
// insert a space between lower & upper
.replace(/([a-z])([A-Z])/g, '$1 $2')
// space before last upper in a sequence followed by lower
.replace(/\b([A-Z]+)([A-Z])([a-z])/, '$1 $2$3')
// uppercase the first character
.replace(/^./, function(str){ return str.toUpperCase(); })
}
There's also a String.prototype version in a gist.
This can be concisely done with regex lookahead (live demo):
function splitCamelCaseToString(s) {
return s.split(/(?=[A-Z])/).join(' ');
}
(I thought that the g (global) flag was necessary, but oddly enough, it isn't in this particular case.)
Using lookahead with split ensures that the matched capital letter is not consumed and avoids dealing with a leading space if UpperCamelCase is something you need to deal with. To capitalize the first letter of each, you can use:
function splitCamelCaseToString(s) {
return s.split(/(?=[A-Z])/).map(function(p) {
return p.charAt(0).toUpperCase() + p.slice(1);
}).join(' ');
}
The map array method is an ES5 feature, but you can still use it in older browsers with some code from MDC. Alternatively, you can iterate over the array elements using a for loop.
I think this should be able to handle consecutive uppercase characters as well as simple camelCase.
For example: someVariable => someVariable, but ABCCode != A B C Code.
The below regex works on your example but also the common example of representing abbreviations in camcelCase.
"somethingLikeThis"
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/([A-Z])([a-z])/g, ' $1$2')
.replace(/\ +/g, ' ') => "something Like This"
"someVariableWithABCCode"
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/([A-Z])([a-z])/g, ' $1$2')
.replace(/\ +/g, ' ') => "some Variable With ABC Code"
You could also adjust as above to capitalize the first character.
Lodash handles this nicely with _.startCase()
function spacecamel(s){
return s.replace(/([a-z])([A-Z])/g, '$1 $2');
}
spacecamel('somethingLikeThis')
// returned value: something Like This
A solution that handles numbers as well:
function capSplit(str){
return str.replace(
/(^[a-z]+)|[0-9]+|[A-Z][a-z]+|[A-Z]+(?=[A-Z][a-z]|[0-9])/g,
function(match, first){
if (first) match = match[0].toUpperCase() + match.substr(1);
return match + ' ';
}
)
}
Tested here [JSFiddle, no library. Not tried IE]; should be pretty stable.
Try this solution here -
var value = "myCamelCaseText";
var newStr = '';
for (var i = 0; i < value.length; i++) {
if (value.charAt(i) === value.charAt(i).toUpperCase()) {
newStr = newStr + ' ' + value.charAt(i)
} else {
(i == 0) ? (newStr += value.charAt(i).toUpperCase()) : (newStr += value.charAt(i));
}
}
return newStr;
If you don't care about older browsers (or don't mind using a fallback reduce function for them), this can split even strings like 'xmlHTTPRequest' (but certainly the likes of 'XMLHTTPRequest' cannot).
function splitCamelCase(str) {
return str.split(/(?=[A-Z])/)
.reduce(function(p, c, i) {
if (c.length === 1) {
if (i === 0) {
p.push(c);
} else {
var last = p.pop(), ending = last.slice(-1);
if (ending === ending.toLowerCase()) {
p.push(last);
p.push(c);
} else {
p.push(last + c);
}
}
} else {
p.push(c.charAt(0).toUpperCase() + c.slice(1));
}
return p;
}, [])
.join(' ');
}
My version
function camelToSpace (txt) {
return txt
.replace(/([^A-Z]*)([A-Z]*)([A-Z])([^A-Z]*)/g, '$1 $2 $3$4')
.replace(/ +/g, ' ')
}
camelToSpace("camelToSpaceWithTLAStuff") //=> "camel To Space With TLA Stuff"
const value = 'camelCase';
const map = {};
let index = 0;
map[index] = [];
for (let i = 0; i < value.length; i++) {
if (i !== 0 && value[i] === value[i].toUpperCase()) {
index = i;
map[index] = [];
}
if (i === 0) {
map[index].push(value[i].toUpperCase());
} else {
map[index].push(value[i]);
}
}
let resultArray = [];
Object.keys(map).map(function (key, index) {
resultArray = [...resultArray, ' ', ...map[key]];
return resultArray;
});
console.log(resultArray.join(''));
Not regex, but useful to know plain and old techniques like this:
var origString = "thisString";
var newString = origString.charAt(0).toUpperCase() + origString.substring(1);