Mask a portion of a String using RegExp - javascript

I'm trying to mask a portion of a string using JavaScript.
e.g. Mask second and third segment of credit-card number like this using regex:
4567 6365 7987 3783 → 4567 **** **** 3783
3457 732837 82372 → 3457 ****** 82372
I just want to keep the first 4 numbers and the last 5 characters.
This is my first attempt: /(?!^.*)[^a-zA-Z\s](?=.{5})/g
https://regex101.com/r/ZBi54c/2

You can try this:
var cardnumber = '4567 6365 7987 3783';
var first4 = cardnumber.substring(0, 4);
var last5 = cardnumber.substring(cardnumber.length - 5);
mask = cardnumber.substring(4, cardnumber.length - 5).replace(/\d/g,"*");
console.log(first4 + mask + last5);

You could slice the first four digits and apply a replacement for the rest.
console.log(
['4567 6365 7987 3783', '3457 732837 82372'].map(
s => s.slice(0, 4) + s.slice(4).replace(/\d(?=.* )/g, '*')
)
);

The answer apparently satisfies the OP. Here is another solution using only Regexes:
function starry(match, gr1, gr2, gr3) {
var stars = gr2.replace(/\d/g, '*');
return gr1 + " " + stars + " " + gr3;
}
function ccStarry(str) {
var rex = /(\d{4})\s(\d{4}\s\d{4}|\d{6})\s(\d{4}|\d{5})/;
if (rex.test(str))
return str.replace(rex, starry);
else return "";
}
var s1 = "4567 6365 7987 3783";
var s2 = "3457 732837 82372";
var s3 = "dfdfdf";
console.log(ccStarry(s1));
console.log(ccStarry(s2));
console.log(ccStarry(s3));
This ensures that the pattern matches before trying any replacements. For example, in the third test case, it returns an empty string. The pattern can be updated to match other credit card patterns besides the ones given in the question.

I would like to elaborate more on the answer from #Nina Scholz, I use .slice() in the following sample code for masking the variable in 2 condition.
Just a simple variable var n = '12345567890'
Array object
// Single number
var n = '601115558888';
var singleNumber = n.slice(0, 4) + n.slice(4, n.length -4).replace(/\d/g,'*') + n.slice(n.length -4);
console.log(singleNumber);
// array of object
var obj = [{
contacts_name: 'Jason',
contacts_num : '651231239991'
},
{
contacts_name: 'King',
contacts_num : '60101233321'
}];
// Mask for the middle number, showing the first4 number and last4 number
// and replace the rest number with *
var num = obj.map((element, index) =>
element.contacts_num.slice(0,4)
+ element.contacts_num.slice(4, element.contacts_num.length-4).replace(/\d/g, '*')
+ element.contacts_num.slice(element.contacts_num.length -4)
);
console.log(num);

If it's JavaScript doing the regex masking, you've already failed because JS should never need to know the original card number, except when you've just received it from the user and are sending it to the server for the first time, in which case you shouldn't be masking it anyway so the user can check for typos.
I can't really help you there, you've already failed in the worst way.
Server-side, if the number is already broken into spaces*, then one option is: (in PHP but the same idea applies to all)
$parts = explode(" ",$fullnumber);
$first = array_shift($parts);
$last = array_pop($parts);
$middle = implode(" ",$parts);
$mask = preg_replace("/\d/","*",$middle);
$result = "$first $mask $last";
* it shouldn't be

Related

Javascript: Searching substring pattern and returning found string

I have a string composed by several fields. Two of which will always vary in length. I could simply use substring if all fields have fixed lengths.
Sample:
48001MCAbastillas2200800046300017100518110555130000123
The fields are divided like this:
480 | 01 | MCAbastillas | 2200800046300017 | 100518 | 110555 | 130000 | 123
The bolded fields are the fields that varies in length. They represent a name and a amount, respectively. I already posted this question but I mistakenly tagged it with Java. I tried to interpret the provided answers into Javascript but being no expert in it, I spent all day producing no results :(
You can use regular expression to capture the groups
const regex = /^(.{3})(.{2})(\D+)(.{16})(.{6})(.{6})(\d+)(.{3})$/
const str = '48001MCAbastillas2200800046300017100518110555130000123'
const values = str.match(regex)
console.log(values)
var input = '48001MCAbastillas2200800046300017100518110555130000123';
// match parts with regex
var match = input.match(/^(.{3})(.{2})([a-zA-Z]+)(.{16})(.{6})(.{6})(\d+)(.{3})$/);
// remove first element (full matching input)
match.shift();
// build output
var output = match.join(' | ');
console.log(output);
const test = [
'48001MCAbastillas2200800046300017100518110555130000123',
'48001MCAbasti2200800046300017100518110555130000123',
'48001MCAbastillasXYZ2200800046300017100518110555130000123',
'48001MCAbastillas2200800046300017100518110555130000999123',
'48001MCAbastillas2200800046300017100518110555130123',
'48001MCAbastillasXYZ2200800046300017100518110555130000999123',
'48001MCAbasti220080004630001710051811055513123'
]
const p = /(?<name>\D+)(\d{28})(?<amount>\d+)(\d{3}$)/
console.log (test.map (s => s.match (p).groups))
A more complex version that breaks all the parts based on known values. The regex answer is the way to go.
var r1 = "48001MCAbastillas2200800046300017100518110555130000123";
var r2 = "48001RANDOM220080004630001710051811055512345123";
function extract(str) {
var i = 5,max = str.length;
var parts = [], cut1, cut2 = (max - 3);
for(;i<max;i++) {
if (! isNaN(parseInt(str[i]))) {
cut1 = i;
break;
}
}
parts.push(str.substr(0,3));
parts.push(str.substr(3,2));
parts.push(str.substr(5,(cut1 - 5)));
parts.push(str.substr(cut1,16));
parts.push(str.substr((cut1 + 16),6));
parts.push(str.substr((cut1 + 16 + 6),6));
parts.push(str.substr((cut1 + 16 + 12),(cut2 - (cut1 + 16 + 12))));
parts.push(str.substr(cut2,3));
return parts;
}
console.log(extract(r1));
console.log(extract(r2));

Find two numbers in a string

This is a follow on from my previous question which can be found here
Link For Previous Question
I am posting a new question as the answer I got was correct, however my next question is how to take it a step further
Basically I have a string of data, within this data somewhere there will be the following;
Width = 70
Void = 40
The actual numbers there could be anything between 1-440.
From my previous question I found how to identify those two digits using regular expression and put them into separate fields, however, my issue now is that the string could contain for example
Part Number = 2353
Length = 3.3mm
Width = 70
Void = 35
Discount = 40%
My question is;
How do I identify only the Width + Void and put them into two separate fields, the answer in my previous question would not solve this issue as what would happen is in this example I would have an array of size 4 and I would simply select the 2nd and 3rd space.
This is not suitable for my issue as the length of array could vary from string to string therefore I need a way of identifying specifically
Width = ##
Void = ##
And from there be able to retrieve the digits individually to put into my separate fields
I am using JavaScript in CRM Dynamics
A simpler option is to convert the whole string into an object and get what you need from that object.
str = "Part Number = 2353\n" +
"Length = 3.3mm\n" +
"Width = 70\n" +
"Void = 35\n" +
"Discount = 40%\n";
data = {};
str.replace(/^(.+?)\s*=\s*(.+)$/gm, function(_, $1, $2) {
data[$1] = $2;
});
alert(data['Width']);
Width\s+=\s+(\d+)|Void\s+=\s+(\d+)
You can try this.Grab the capture.See demo.
http://regex101.com/r/oE6jJ1/31
var re = /Width\s+=\s+(\d+)|Void\s+=\s+(\d+)/igm;
var str = 'Part Number = 2353\n\nLength = 3.3mm\n\nWidth = 70\n\nVoid = 35\n\nDiscount = 40%';
var m;
while ((m = re.exec(str)) != null) {
if (m.index === re.lastIndex) {
re.lastIndex++;
}
// View your result using the m-variable.
// eg m[0] etc.
}
You can use this regex for matching input with Width and Void in any order:
/(\b(Width|Void) += *(\d+)\b)/
RegEx Demo
Your variable names and values are available in captured groups.

Parse semi-structured values

it's my first question here. I tried to find an answer but couldn't, honestly, figure out which terms should I use, so sorry if it has been asked before.
Here it goes:
I have thousands of records in a .txt file, in this format:
(1, 3, 2, 1, 'John (Finances)'),
(2, 7, 2, 1, 'Mary Jane'),
(3, 7, 3, 2, 'Gerald (Janitor), Broflowski'),
... and so on. The first value is the PK, the other 3 are Foreign Keys, the 5th is a string.
I need to parse them as JSON (or something) in Javascript, but I'm having troubles because some strings have parentheses+comma (on 3rd record, "Janitor", e.g.), so I can't use substring... maybe trimming the right part, but I was wondering if there is some smarter way to parse it.
Any help would be really appreciated.
Thanks!
You can't (read probably shouldn't) use a regular expression for this. What if the parentheses contain another pair or one is mismatched?
The good news is that you can easily construct a tokenizer/parser for this.
The idea is to keep track of your current state and act accordingly.
Here is a sketch for a parser I've just written here, the point is to show you the general idea. Let me know if you have any conceptual questions about it.
It works demo here but I beg you not to use it in production before understanding and patching it.
How it works
So, how do we build a parser:
var State = { // remember which state the parser is at.
BeforeRecord:0, // at the (
DuringInts:1, // at one of the integers
DuringString:2, // reading the name string
AfterRecord:3 // after the )
};
We'll need to keep track of the output, and the current working object since we'll parse these one at a time.
var records = []; // to contain the results
var state = State.BeforeRecord;
Now, we iterate the string, keep progressing in it and read the next character
for(var i = 0;i < input.length; i++){
if(state === State.BeforeRecord){
// handle logic when in (
}
...
if(state === State.AfterRecord){
// handle that state
}
}
Now, all that's left is to consume it into the object at each state:
If it's at ( we start parsing and skip any whitespaces
Read all the integers and ditch the ,
After four integers, read the string from ' to the next ' reaching the end of it
After the string, read until the ) , store the object, and start the cycle again.
The implementation is not very difficult too.
The parser
var State = { // keep track of the state
BeforeRecord:0,
DuringInts:1,
DuringString:2,
AfterRecord:3
};
var records = []; // to contain the results
var state = State.BeforeRecord;
var input = " (1, 3, 2, 1, 'John (Finances)'), (2, 7, 2, 1, 'Mary Jane'), (3, 7, 3, 2, 'Gerald (Janitor), Broflowski')," // sample input
var workingRecord = {}; // what we're reading into.
for(var i = 0;i < input.length; i++){
var token = input[i]; // read the current input
if(state === State.BeforeRecord){ // before reading a record
if(token === ' ') continue; // ignore whitespaces between records
if(token === '('){ state = State.DuringInts; continue; }
throw new Error("Expected ( before new record");
}
if(state === State.DuringInts){
if(token === ' ') continue; // ignore whitespace
for(var j = 0; j < 4; j++){
if(token === ' ') {token = input[++i]; j--; continue;} // ignore whitespace
var curNum = '';
while(token != ","){
if(!/[0-9]/.test(token)) throw new Error("Expected number, got " + token);
curNum += token;
token = input[++i]; // get the next token
}
workingRecord[j] = Number(curNum); // set the data on the record
token = input[++i]; // remove the comma
}
state = State.DuringString;
continue; // progress the loop
}
if(state === State.DuringString){
if(token === ' ') continue; // skip whitespace
if(token === "'"){
var str = "";
token = input[++i];
var lenGuard = 1000;
while(token !== "'"){
str+=token;
if(lenGuard-- === 0) throw new Error("Error, string length bounded by 1000");
token = input[++i];
}
workingRecord.str = str;
token = input[++i]; // remove )
state = State.AfterRecord;
continue;
}
}
if(state === State.AfterRecord){
if(token === ' ') continue; // ignore whitespace
if(token === ',') { // got the "," between records
state = State.BeforeRecord;
records.push(workingRecord);
workingRecord = {}; // new record;
continue;
}
throw new Error("Invalid token found " + token);
}
}
console.log(records); // logs [Object, Object, Object]
// each object has four numbers and a string, for example
// records[0][0] is 1, records[0][1] is 3 and so on,
// records[0].str is "John (Finances)"
I echo Ben's sentiments about regular expressions usually being bad for this, and I completely agree with him that tokenizers are the best tool here.
However, given a few caveats, you can use a regular expression here. This is because any ambiguities in your (, ), , and ' can be attributed (AFAIK) to your final column; as all of the other columns will always be integers.
So, given:
The input is perfectly formed (with no unexpected (, ), , or ').
Each record is on a new line, per your edit
The only new lines in your input will be to break to the next record
... the following should work (Note "new lines" here are \n. If they're \r\n, change them accordingly):
var input = /* Your input */;
var output = input.split(/\n/g).map(function (cols) {
cols = cols.match(/^\((\d+), (\d+), (\d+), (\d+), '(.*)'\)/).slice(1);
return cols.slice(0, 4).map(Number).concat(cols[4]);
});
The code splits on new lines, then goes through row by row and splits into cells using a regular expression, which greedily attributes as much as it can to the final cell. It then turns the first 4 elements into integers, and sticks the 5th element (the string) onto the end.
This gives you an array of records, where each record is itself an array. The first 4 elements are your PK's (as integers) and your 5th element is the string.
For example, given your input, use output[0][4] to get "Gerald (Janitor), Broflowski", and output[1][0] to get the first PK 2 for the second record (don't forget JavaScript arrays are zero-indexed).
You can see it working here: http://jsfiddle.net/56ThR/
Another option would be to convert it into something that looks like an Array and eval it. I know it is not recommended to use eval, but it's a cool solution :)
var lines = input.split("\n");
var output = [];
for(var v in lines){
// Remove opening (
lines[v] = lines[v].slice(1);
// Remove closing ) and what is after
lines[v] = lines[v].slice(0, lines[v].lastIndexOf(')'));
output[v] = eval("[" + lines[v] + "]");
}
So, the eval parameter would look like: [1, 3, 2, 1, 'John (Finances)'], which is indeed an Array.
Demo: http://jsfiddle.net/56ThR/3/
And, it can also be written shorter like this:
var lines = input.split("\n");
var output = lines.map( function(el) {
return eval("[" + el.slice(1).slice(0, el.lastIndexOf(')') - 1) + "]");
});
Demo: http://jsfiddle.net/56ThR/4/
You can always do it "manually" :)
var lines = input.split("\n");
var output = [];
for(var v in lines){
output[v] = [];
// Remove opening (
lines[v] = lines[v].slice(1);
// Get integers
for(var i = 0; i < 4; ++i){
var pos = lines[v].indexOf(',');
output[v][i] = parseInt(lines[v].slice(0, pos));
lines[v] = lines[v].slice(pos+1);
}
// Get string betwen apostrophes
lines[v] = lines[v].slice(lines[v].indexOf("'") + 1);
output[v][4] = lines[v].slice(0, lines[v].indexOf("'"));
}
Demo: http://jsfiddle.net/56ThR/2/
What you have here is basically a csv (comma separated value) file which you wish to parse.
The easiest way would be to use an wxternal library that will take care of most of the issues you have
Example: jquery csv library is a good one. https://code.google.com/p/jquery-csv/

How can I "count characters" in a regex?

I'm not sure if count is the right word to use because it doesn't really matter to me how many there are, but let me explain. My data will be formatted like this: (hi,(1,2),hey),(yo,(3,(rawr),4),howdy) and I have no control over how many dimensions there are. And I want to grab the lowest groups ["hi", Array[], "hey"] and ["yo", Array[], "howdy"] So if there was a way to "count" I could count the open parenthesis, and then count the closed ones and when it hits 0, that's when the regex ends. For example:
(hi,(1,2),hey),(yo,(3,(rawr),4),howdy)
1---2---1----0-1---2--3----2--1------0
Now with that being said, I don't believe counting is possible but what I want is a subsitute solution. This is what I have so far /\([^\(]*?\)/ but that only returns the highest level group from each of the low-level groups aka (1,2) and (rawr).
You can use a stack to track the (and).
Array.reduce(
'(hi,(1,2),hey),(yo,(3,(rawr),4),howdy)',
function(x,y){
if(y=='(')
return [x[0]+1, x[1]+(x[0]+1)]
else if(y==')')
return [x[0]-1, x[1]+(x[0]-1)]
else
return [x[0], x[1]+'-']
},
[0,'']
)[1]
Try it in firebug console.
This works for the original use-case and #Barmar's use-case - and it counts the parenthesis, if you really wanted that...
Also, I added arbitrary spaces all over the data strings - just in case (since you have no control over the incoming data)
var results = [];
var dataString = "(hi, (1,2) , hey), ( yo,( 3, ( rawr ), 4) , howdy )";
//var dataString = "(hi, (1 , 2 ), (3, 4), hey), (yo ,(3,(rawr ), 4), howdy)";
var dataSplit = dataString.split(",");
var trimRegex = /^\s+|\s+$/g;
var openParensRegex = /\(/;
var closeParensRegex = /\)/;
var parensRegex = /\(|\)/;
var parensCount = 0;
for (var x = 0, lenx = dataSplit.length; x < lenx; x++){
var cleanString = dataSplit[x].replace(trimRegex, "");
if (openParensRegex.test(cleanString)){ parensCount++; };
if (parensCount < 2){
results.push(cleanString.replace(parensRegex, "").replace(trimRegex, ""));
};
if (closeParensRegex.test(cleanString)){ parensCount--; };
};
console.log(results);
Hope that helps!
The following script might help, it will identify the parenthesis levels:
var string="(hi,(1,2),hey),(yo,(3,(rawr),4),howdy)",i=0;
while (string.indexOf("(")>=0) {
i++;
string=string.replace(/\(([^()]+)\)/g,"|l"+i+"|$1|l"+i+"|");
}
Result:
|l2|hi,|l1|1,2|l1|,hey|l2|,|l3|yo,|l2|3,|l1|rawr|l1|,4|l2|,howdy|l3|
Fiddle: http://jsfiddle.net/GfUZh/
If you want to get the highest levels you can probably do it with regex by finding the intersection points, in this case ),(.
var str = '(hi,(1,2),hey),(yo,(3,(rawr),4),howdy)';
var re = /(\(.+\)),(\(.+\))/;
var results = re.exec(str);
results.shift(); // remove first item which is useless
console.log(results); //=> ["(hi,(1,2),hey)", "(yo,(3,(rawr),4),howdy)"]
Demo: http://jsfiddle.net/elclanrs/fFmfE/

show only last 4 digits in bank account using javascript

I need help with Javascript. I need to replace however many characters there are previous to the last 4 digits of a text field that contains bank account number. I have searched through the net on this, but cannot find one code that works. I did find a code here on stackoverflow, which was regarding credit card,
new String('x', creditCard.Length - 4) + creditCard.Substring(creditCard.Length - 4);
I just replaced the creditCard with accounNumObject:
var accounNumObject = document.getElementById("bankAcctNum")
The input is pretty simple.
<cfinput type="text" name="bankAcctNum" id="bankAcctNum" maxlength="25" size="25" value="#value#" onblur="hideAccountNum();">
Can anyone help please?
To replace a string with x except for the last four characters in JavaScript, you could use (assuming str holds the string)...
var trailingCharsIntactCount = 4;
str = new Array(str.length - trailingCharsIntactCount + 1).join('x')
+ str.slice(-trailingCharsIntactCount);
jsFiddle.
You could also use a regular expression...
str = str.replace(/.(?=.{4})/g, 'x');
If you want to add the 4 from a variable, construct the regex with the RegExp constructor.
jsFiddle.
If you're fortunate enough to have the support, also...
const trailingCharsIntactCount = 4;
str = 'x'.repeat(str.length - trailingCharsIntactCount)
+ str.slice(-trailingCharsIntactCount);
Polyfill for String.prototype.repeat() is available.
Here is a fiddle showing what you're asking for:
http://jsfiddle.net/eGFqM/1/
<input id='account' value='abcdefghijklmnop'/>
<br/>
<input id='account_changed'/>
var account = document.getElementById('account');
var changed = document.getElementById('account_changed');
changed.value = new Array(account.value.length-3).join('x') +
account.value.substr(account.value.length-4, 4);
Edit: Updated fiddle to correct off by one problem pointed out by alex
Agreed with all above solutions.I just had one another approach.
const maskAccountId = (accountId) => {
if (accountId) { /** Condition will only executes if accountId is *not* undefined, null, empty, false or 0*/
const accountIdlength = accountId.length;
const maskedLength = accountIdlength - 4; /** Modify the length as per your wish */
let newString = accountId;
for (let i = 0; i < accountIdlength; i++) {
if (i < maskedLength) {
newString = newString.replace(accountId[i], '*');
}
}
return newString;
} else return /**Will handle if no string is passed */
}
console.log(maskAccountId('egrgrgry565yffvfdfdfdfdfgrtrt4t4'));

Categories