Javascript replace regex to accept only numbers, including negative ones, two decimals, replace 0s in the beginning, except if number is 0 - javascript

The question became a bit long, but it explains the expected behaviour.
let regex = undefined;
const format = (string) => string.replace(regex, '');
format('0')
//0
format('00')
//0
format('02')
//2
format('-03')
//-3
format('023.2323')
//23.23
format('00023.2.3.2.3')
//23.23
In the above example you can see the expected results in comments.
To summarize. I'm looking for a regex not for test, for replace which formats a string:
removes 0s from the beginning if it's followed by any numbers
allows decimal digits, but just 2
allows negative numbers
allows decimal points, but just one (followed by min 1, max 2 decimal digits)
The last one is a bit difficult to handle as the user can't enter period at the same time, I'll have two formatter functions, one will be the input in the input field, and one for the closest valid value at the moment (for example '2.' will show '2.' in the input field, but the handler will receive the value '2').
If not big favour, I'd like to see explanation of the solution, why it works, and what's the purpose of which part.
Right now I'm having string.replace(/[^\d]+(\.\[^\d{1,2}])+|^0+(?!$)/g, ''), but it doesn't fulfill all the requirements.

You may use this code:
const arr = ['0', '00', '02', '-03', '023.2323', '00023.2.3.2.3', '-23.2.3.2.3']
var narr = []
// to remove leading zeroes
const re1 = /^([+-]?)0+?(?=\d)/
// to remove multiple decimals
const re2 = /^([+-]?\d*\.\d+)\.(\d+).*/
arr.forEach( el => {
el = el.replace(re1, '$1').replace(re2, '$1$2')
if (el.indexOf('.') >= 0)
el = Number(el).toFixed(2)
narr.push(el)
})
console.log(narr)
//=> ["0", "0", "2", "-3", "23.23", "23.23"]

If you aren't bound to the String#replace method, you can try this regex:
/^([+-])?0*(?=\d+$|\d+\.)(\d+)(?:\.(\d{1,2}))?$/
Inspect on regex101.com
It collects the parts of the number into capturing groups, as follows:
Sign: the sign of the number, +, - or undefined
Integer: the integer part of the number, without leading zeros
Decimal: the decimal part of the number, undefined if absent
This regex won't match if more then 2 decimal places present. To strip it instead, use this:
/^([+-])?0*(?=\d+$|\d+\.)(\d+)(?:\.(\d{1,2})\d*)?$/
Inspect on regex101.com
To format a number using one of the above, you can use something like:
let regex = /^([+-])?0*(?=\d+$|\d+\.)(\d+)(?:\.(\d{1,2}))?$/
const format = string => {
try{
const [, sign, integer, decimal = ''] = string.match(regex)
return `${(sign !== '-' ? '' : '-')}${integer}${(decimal && `.${decimal}`)}`
}catch(e){
//Invalid format, do something
return
}
}
console.log(format('0'))
//0
console.log(format('00'))
//0
console.log(format('02'))
//2
console.log(format('-03'))
//-3
console.log(format('023.23'))
//23.23
console.log(format('023.2323'))
//undefined (invalid format)
console.log(format('00023.2.3.2.3'))
//undefined (invalid format)
//Using the 2nd regex
regex = /^([+-])?0*(?=\d+$|\d+\.)(\d+)(?:\.(\d{1,2})\d*)?$/
console.log(format('0'))
//0
console.log(format('00'))
//0
console.log(format('02'))
//2
console.log(format('-03'))
//-3
console.log(format('023.23'))
//23.23
console.log(format('023.2323'))
//23.23
console.log(format('00023.2.3.2.3'))
//undefined (invalid format)

Another option is to use pattern with 3 capturing groups. In the replacement, use all 3 groups "$1$2$3"
If the string after the replacement is empty, return a single zero.
If the string is not empty, concat group 1, group 2 and group 3 where for group 3, remove all the dots except for the first one to keep it for the decimal and take the first 3 characters (which is the dot and 2 digits)
^([-+]?)0*([1-9]\d*)((?:\.\d+)*)|0+$
In parts
^ Start of string
( Capture group 1
[-+]? Match an optional + or -
) Close group
0* Match 0+ times a zero
( Capture group 2
[1-9]\d* Match a digit 1-9 followed by optional digits 0-9
) Close group
( Capture group 3
(?:\.\d+)* Repeat 0+ times matching a dot and a digit
) Close group
| Or
0+ Match 1+ times a zero
$ End of string
Regex demo
const strings = ['0', '00', '02', '-03', '023.2323', '00023.2.3.2.3', '-23.2.3.2.3', '00001234', '+0000100005.0001']
let pattern = /^([-+]?)0*([1-9]\d*)((?:\.\d+)*)|0+$/;
let format = s => {
s = s.replace(pattern, "$1$2$3");
return s === "" ? '0' : s.replace(pattern, (_, g1, g2, g3) =>
g1 + g2 + g3.replace(/(?!^)\./g, '').substring(0, 3)
);
};
strings.forEach(s => console.log(format(s)));

Related

Regex matches numbers in date but shouldn't

Why does my regex pattern match the date part of the string? It seems like I'm not accounting for the / (slash) correctly with [^\/] to avoid the pattern to match date strings?
const reg = new RegExp(
/(USD|\$|EUR|€|USDC|USDT)?\s?(\d+[^\/]|\d{1,3}(,\d{3})*)(\.\d+)?(k|K|m|M)?\b/,
"i"
);
const str = "02/22/2021 $50k";
console.log(reg.exec(str));
// result: ['02', undefined, '02', undefined, undefined, undefined, undefined, index: 0, input: '02/22/2021 $50k', groups: undefined]
// was expecting: [$50k,...]
You get those matches for the date part and the undefined ones, because you use a pattern with optional parts and alternations |
In your pattern there is this part (\d+[^\/]|\d{1,3}(,\d{3})*). That first part of the alternation \d+[^\/] matches 1+ digits followed by any char except a / (which can also match a digit) and the minimum amount of characters is 2. That part will match 20, 22 and 2021 in the date part.
If there is 1 digit, the second part of the alternation will match it.
If you want to match only numbers as well, you can assert not / to the left and the right, and make the whole part with the first alternatives like USD optional with the optional whitspace chars as well, to prevent matching that before only digits.
The last alternation can be shortened to a character class [km]? with a case insensitive flag.
See this page for the lookbehind support for Javascript.
(?:(?:USD|\$|EUR|€|USDC|USDT)\s?)?(?<!\/)\b(?:\d{1,3}(?:,\d{3})*(?:\.\d+)?|\d+)(?!\/)[KkMm]?\b
Regex demo
const reg = /(?:(?:USD|\$|EUR|€|USDC|USDT)\s?)?(?<!\/)\b(?:\d{1,3}(?:,\d{3})*(?:\.\d+)?|\d+)(?!\/)[KkMm]?\b/gi;
const str = "02/22/2021 $50k 1,213.3 11111111 $50,000 $50000"
const res = Array.from(str.matchAll(reg), m => m[0]);
console.log(res)
If the currency is not optional:
(?:USD|\$|EUR|€|USDC|USDT)\s?(?:\d{1,3}(?:,\d{3})*(?:\.\d+)?|\d+)[KkMm]?\b
Regex demo
I can't get your regex well. so i try to figure out what result you would expect. check this. in groups you have each part of your string.
const regex = /(\d{2})*\/?(\d{2})\/(\d{2,4})?\s*(USD|\$|EUR|€|USDC|USDT)?(\d*)(k|K|m|M)?\b/i
const regexNamed = /(?<day>\d{2})*\/?(?<month>\d{2})\/(?<year>\d{2,4})?\s*(?<currency>USD|\$|EUR|€|USDC|USDT)?(?<value>\d*)(?<unit>k|K|m|M)?\b/i
const str1 = '02/22/2021 $50k'
const str2 = '02/2021 €50m'
const m1 = str1.match(regex)
const m2 = str2.match(regexNamed)
console.log(m1)
console.log(m2.groups)
Blockquote

Javascript regex for money with max length

I want validate a money string with numbers max length 13 with 2 decimal. I have a comma as decimal separator and a period as a thousands separator.
I have this regex:
/^(\d{1}\.)?(\d+\.?)+(,\d{2})?$/
For sintax is valid but not for max length. What I need to add to this regex?
For example, these strings must be valid:
1.000.000.000.000
1.000.000.000.000,00
1
1,00
123,45
And these must be invalid:
10.000.000.000.000
10.000.000.000.000,00
10.000.000.000.000.000
10.000.000.000.000.000,00
Maybe try to assert position is not followed by 18 digits/dots using a negative lookahead:
^(?![\d.]{18})\d{1,3}(?:\.\d{3})*(?:,\d\d?)?$
See an online demo. Here is assumed you would also allow a single digit decimal.
^ - Open line anchor.
(?![\d.]{18}) - Negative lookahead to prevent 18 digits/dots ahead.
\d{1,3} - One-to-three digits.
(?:\.\d{3})* - A non-capture group of a literal dot followed by three digits with a 0+ multiplier.
(?:,\d\d?)? - Optional non-capture group of a comma followed by either 1 or two digits. Remove the question mark to make the 2nd decimal non-optional.
$ - End line anchor.
You may use this regex for validation:
^(?=[\d.]{1,17}(?:,\d{2})?$)\d{1,3}(?:\.\d{3})*(?:,\d{2})?$
RegEx Demo
RegEx Details:
^: Start
(?=[\d.]{1,17}(?:,\d{2})?$): Lookahead to match dot or digit 1 to 17 times followed by optional comma and 2 digits
\d{1,3}: Match 1 to 3 digits
(?:\.\d{3})*: Match . followed by 3 digits. Repeat this group 0 or more times
(?:,\d{2})?: Match optional , followed 2 decimal digits
$: End
90% of the time there is a better solution than using regex. It's probably best just to convert your strings into a real number then compare vs. your limit (ie 9999999999999.99).
// Pass a string
function convertStr(string) {
/*
Remove all '.'
Replace all ',' with '.'
*/
let numStr = string.split('.').join('').replaceAll(',', '.');
// Convert modified string into a real number
let realNum = parseFloat(numStr);
/*
if converted string is a real number...
round it to two decimal places and return it
Otherwise return false
*/
return !Number.isNaN(realNum) ? Math.round((realNum + Number.EPSILON) * 100) / 100 : false;
}
// Pass a string and the maxed number
function numLimit(string, limit) {
// Get the result of convertString()
let number = convertStr(string);
// if the result is equal to or less than limit...
if (number <= limit) {
return true;
} else {
return false;
}
}
const limit = 9999999999999.99;
const valid = ['1.000.000.000.000',
'1.000.000.000.000,00', '1', '1,00', '123,45'
];
const invalid = ['10.000.000.000.000', '10.000.000.000.000,00', '10.000.000.000.000.000', '10.000.000.000.000.000,00'];
let validResults = valid.map(str => numLimit(str, limit));
let invalidResults = invalid.map(str => numLimit(str, limit));
console.log('valid: ' + validResults);
console.log('invalid: ' + invalidResults);

Regular expression to isolate numbers from symbols and merge them as decimal numbers in JS

I am trying to isolate specific format groups within strings and convert them through JS or jQuery and regex, strings like these
#aba #abc #33-25
#02-20 #abe #abf
#abg #abe #00-50 #aba
#aja #255-45
to these
33.25€
2.20€
0.50€
255.45€
.
1. In regex level, my workaround so far for isolating #xx.xx groups within strings is
s = "#aba #abc #33-25"
s.match( /(#[W\d-]+)/ ) //["#33-25", "#33-25"]
It recognize the #33-25 substring but outputs it 2 times in an array which is obviously insufficient.
.
2. Also how it will work (JS or jQuery) to (solved by #Kosh's answer)
remove # symbols
replace hyphen symbol (-) to dot symbol (.)
when #01-xx or #0-xx to convert to 1.xx or 0.xx (where x is obviously decimal numbers, always 2)
Use match and replace:
const convert = (s) => s
.match(/#0*(0|[1-9]+)-(\d\d)\b/g)
.map(m => m.replace(/#0*(0|[1-9]+)-(\d\d)/g, '$1.$2€'));
console.log(convert(`#aba #abc #33-25
#02-20 #abe #abf
#abg #abe #00-50 #aba
#aja #255-45`))
You can use
const texts = ['#aba #abc #33-25','#02-20 #abe #abf','#abg #abe #00-50 #aba','#aja #255-45'];
texts.forEach( x => {
const m = x.match(/#(\d+)-(\d+)/);
if (m) {
console.log(x, '=>', `${m[1]}.${m[2]}€`);
}
});
Here, x.match(/#(\d+)-(\d+)/) finds the first match of a /#(\d+)-(\d+)/ regex (# matches a # char, (\d+) captures one or more digits into Group 1, - matches a hyphen and (\d+) captures one or more digits into Group 2) in a string, and then ${m[1]}.${m[2]}€ builds the final result where m[1] is Group 1 and m[2] is Group 2 value.

Regex for getting only the last N numbers in javascript

I've being trying to generate a regex for this string:
case1: test-123456789 should get 56789
case2: test-1234-123456789 should get 56789
case3: test-12345 should fail or not giving anything
what I need is a way to get only the last 5 numbers from only 9 numbers
so far I did this:
case.match(/\d{5}$/)
it works for the first 2 cases but not for the last one
You may use
/\b\d{4}(\d{5})$/
See the regex demo. Get Group 1 value.
Details
\b - word boundary (to make sure the digit chunks are 9 digit long) - if your digit chunks at the end of the string can contain more, remove \b
\d{4} - four digits
(\d{5}) - Group 1: five digits
$ - end of string.
JS demo:
var strs = ['test-123456789','test-1234-123456789','test-12345'];
var rx = /\b\d{4}(\d{5})$/;
for (var s of strs) {
var m = s.match(rx);
if (m) {
console.log(s, "=>", m[1]);
} else {
console.log("Fail for ", s);
}
}
You can try this:
var test="test-123456789";
console.log((test.match(/[^\d]\d{4}(\d{5})$/)||{1: null/*default value if not found*/})[1]);
This way supports default value for when not found any matching (look at inserted comment inline above code.).
You can use a positive lookbehind (?<= ) to assert that your group of 5 digits is preceeded by a group of 4 digits without including them in the result.
/(?<=\d{4})\d{5}$/
var inputs = [
"test-123456789", // 56789
"test-1234-123456789", // 56789
"test-12345", //fail or not giving anything
]
var rgx = /(?<=\d{4})\d{5}$/
inputs.forEach(str => {
console.log(rgx.exec(str))
})

Format a string in blocks of 3 digits

How would I go about reformating a string into blocks of 3 digits separated by dash (-) in JavaScript?
ex:
let myString = "00 64 33 3-44-23 982- 23-1-0"
produce desired output as:
myString = "006-433-344-239-822-310"
With regex:
("00 64 33 3-44-23 982- 23-1-0").replace(/\D/g,"").replace(/(\d{3})(?!$)/g,'$1-')
Explanation:
\D - remove all non numeric characters
(\d{3})(?!$)
(\d{3}) - match 3 numbers
(?!$) - but not the last 3
To turn 0 - 22 1985--324 into 022-198-53-24:
("0 - 22 1985--324")
.replace(/\D/g,"") // strip numbers
.replace( // do specific format
/^(\d{3})(\d{3})(\d{2})(\d{2})$/g,
'$1-$2-$3-$4'
)
From what you've said in the comments I believe you want groups of 3 where possible and groups of 2 at the end if there aren't enough numbers. I believe this will do it:
[
'1-2 3 45---67 890',
'12345678901',
'123456789012',
'1234567890123',
'12345678901234',
'1',
'12',
'123',
'1234',
'12345',
'123456',
'1234567'
].forEach(function(str) {
console.log(str.replace(/\D/g, '').replace(/(\d\d\d?)(?=\d\d)/g, '$1-'));
});
The /\D/g removes all the non-digits. The capture group (\d\d\d?) will attempt to grab 3 digits if it can (RegExps are greedy so they grab as much as possible) but if it can't grab 3 digits that ? makes the third digit optional. The lookahead (?=\d\d) requires there to be at least 2 more digits after the initial capture. The result of this is that if there are 5 or more digits remaining in the string this will result in 3 digits being included in the capture but if there are only 4 remaining it will apply the ? to grab just 2 digits.
When making multiple matches using the g flag the string is consumed from start to finish in one run. Each time it makes a match it doesn't go back to the start again, it just carries on. This means that matches can never overlap. Lookaheads provides a way to check what's coming up next without including it in the match, so that you can effectively rewind.
The key to understanding this RegExp is the observation that wherever we insert a - in the result there must always be at least 2 more digits after it. In English it translates as 'grab 3 digits if we can (or 2 if we can't) so long as there are 2 more digits yet to come'.
First you can use 'replace' to remove any non-digit characters with regex.
let myString = "00 64 33 3-44-23 982- 23-1-0"
myString = myString.replace(/\D/g, "")
// "006433344239822310"
Secondly, 'match' any digits (0-9) between 1 and 3 times. The match function conveniently returns an array with an item for each match.
myString = myString.match(/\d{1,3}/g)
// [006,433,344,239,822,310]
Lastly, join the array using "-" as the separator.
myString = myString.join("-");
// 006-433-344-239-822-310
And if you would like to chain each step together..
myString = myString.replace(/\D/g, "").match(/\d{1,3}/g).join("-");
Note that if the string has any leftover digits, they will be left in their own block on the end of the string. This is due to the 1-3 match.
For example..
"00 64 33 3-44-23 982- 23-1-0 24" // before - with 2 extra digits
"006-433-344-239-822-310-24" // after - the extra digits are maintained
Here's a non-regexp solution:
joinGroups(joinDigits(partition3(extract(string))))
joinGroups is just
// Join groups of digits with a hyphen.
const joinGroups = a => a.join('-');
joinDigits is easy enough too:
// Join elements of subarrays together into strings.
const joinDigits = groups = groups.map(group => group.join(''));
extract is easy too:
// Extract all digits from a string and return as array.
const extract = input => input.match(/\d/g);
partition3 is a little harder. There are many samples here on SO and elsewhere. See the implementation in the snippet for one idea.
const joinGroups = a => a.join('-');
const joinDigits = groups => groups.map(group => group.join(''));
const extract = input => input.match(/\d/g);
const partition = n =>
a => Array.from(
{length: Math.floor((a.length - 1) / n) + 1},
(_, i) => a.slice(i * n, (i + 1) * n));
const partition3 = partition(3);
const data = "00 64 33 3-44-23 982- 23-1-0";
console.log(joinGroups(joinDigits(partition3(extract(data)))));

Categories

Resources