How to replace exact values for .replace script using regex? - javascript

I have here a javascript replace script that is activated via onclick of a button.
I'm using this to easily replace numbers based on a long list of numbers.
What I'm doing is:
I have this list (please know that this is a sample, there's about a thousand more)
.replaceAll("10,", "80,")
.replaceAll("1,", "75,")
.replaceAll("0,", "65,")
.replaceAll("83,", "50,")
If I enter 10 in textarea, and if I click the "Update button", it should become 80 and not 865 or 7565 since I need an exact value. Right now this is what it's doing, changing 10 to 80 and then the 0 in the 80 into 65.
I'm looking for a quick or clean way of doing this using RegEx, to maybe add to the script that I only want to change the specified numbers in the quote marks and not those with preceding or following numbers.
Here's my sample code:
function fixTextarea(textarea) {
textarea.value = textarea.value.replaceAll("3d ed", "4th ed")
.replaceAll("437,", "511,")
.replaceAll("438,", "512, 517,")
.replaceAll("21,", "24,")
.replaceAll("309,", "357,")
.replaceAll("425,", "491,")
.replaceAll("427,", "493,")
.replaceAll("430,", "496-502,")
.replaceAll("10,", "80,")
.replaceAll("1,", "75,")
.replaceAll("0,", "65,")
.replaceAll("83,", "50,")
};
function fixtext() {
let textarea = document.getElementById("textarea1");
textarea.select();
fixTextarea(textarea);
}
window.addEventListener('DOMContentLoaded', function(e) {
var area = document.getElementById("textarea1");
var getCount = function(str, search) {
return str.split(search).length - 1;
};
var replace = function(search, replaceWith) {
if (typeof(search) == "object") {
area.value = area.value.replace(search, replaceWith);
return;
}
if (area.value.indexOf(search) >= 0) {
var start = area.selectionStart;
var end = area.selectionEnd;
var textBefore = area.value.substr(0, end);
var lengthDiff = (replaceWith.length - search.length) * getCount(textBefore, search);
area.value = area.value.replace(search, replaceWith);
area.selectionStart = start + lengthDiff;
area.selectionEnd = end + lengthDiff;
}
};
});
<textarea id="textarea1" rows="4" cols="50">Witkin & Epstein, Criminal Law (3d ed), Pretrial Proceedings §§ 10, 438,</textarea>
<button class="nbtngreen" onclick="fixtext()">Update</button>
I've tried using something like .replaceAll("/^10,\z/", "80,") but it doesn't seem to work.
I'd appreciate any help. I'm stuck with this and I really cannot solve it on my own anymore.

One option could be to create a Map where you would specify what to replace.
Then you could use a pattern to match 1 or more digits, preceded by a word boundary \b to prevent a partial match and assert a comma directly to the right (?=,) using a positive lookahead.
In the callback of replace, check if the string exists in the Map. Return the mapped value if it does, else return the original match to leave it unchanged.
Press the button once to do all the replacements.
\b\d+(?=,)
Regex demo
For example:
const mapper = new Map();
mapper.set("10", "80");
mapper.set("437", "511");
mapper.set("80", "81");
function fixtext() {
const elm = document.getElementById("textarea1");
if (elm) {
elm.value = elm.value
.replace(
/\b\d+(?=,)/g,
m => mapper.has(m) ? mapper.get(m) : m
);
}
}
<textarea id="textarea1" rows="4" cols="50">Witkin & Epstein, Criminal Law (3d ed), Pretrial Proceedings §§ 10,438, this should not change 437 and this should: 437,9999</textarea>
<button class="nbtngreen" onclick="fixtext()">Update</button>

Related

Get initials and full last name from a string containing names

Assume there are some strings containing names in different format (each line is a possible user input):
'Guilcher, G.M., Harvey, M. & Hand, J.P.'
'Ri Liesner, Peter Tom Collins, Michael Richards'
'Manco-Johnson M, Santagostino E, Ljung R.'
I need to transform those names to get the format Lastname ABC. So each surename should be transformed to its initial which are appended to the lastname.
The example should result in
Guilcher GM, Harvey M, Hand JP
Liesner R, Collins PT, Richards M
Manco-Johnson M, Santagostino E, Ljung R
The problem is the different (possible) input format. I think my attempts are not very smart, so I'm asking for
Some hints to optimize the transformation code
How do I put those in a single function at all? I think first of all I have to test which format the string has...??
So let me explain how far I tried to solve that:
First example string
In the first example there are initials followed by a dot. The dots should be removed and the comma between the name and the initals should be removed.
firstString
.replace('.', '')
.replace(' &', ', ')
I think I do need an regex to get the comma after the name and before the initials.
Second example string
In the second example the name should be splitted by space and the last element is handled as lastname:
const elm = secondString.split(/\s+/)
const lastname = elm[elm.length - 1]
const initials = elm.map((n,i) => {
if (i !== elm.length - 1) return capitalizeFirstLetter(n)
})
return lastname + ' ' + initals.join('')
...not very elegant
Third example string
The third example has the already the correct format - only the dot at the end has to be removed. So nothing else has to be done with that input.
It wouldn't be possible without calling multiple replace() methods. The steps in provided solution is as following:
Remove all dots in abbreviated names
Substitute lastname with firstname
Replace lastnames with their beginning letter
Remove unwanted characters
Demo:
var s = `Guilcher, G.M., Harvey, M. & Hand, J.P.
Ri Liesner, Peter Tom Collins, Michael Richards
Manco-Johnson M, Santagostino E, Ljung R.`
// Remove all dots in abbreviated names
var b = s.replace(/\b([A-Z])\./g, '$1')
// Substitute first names and lastnames
.replace(/([A-Z][\w-]+(?: +[A-Z][\w-]+)*) +([A-Z][\w-]+)\b/g, ($0, $1, $2) => {
// Replace full lastnames with their first letter
return $2 + " " + $1.replace(/\b([A-Z])\w+ */g, '$1');
})
// Remove unwanted preceding / following commas and ampersands
.replace(/(,) +([A-Z]+)\b *[,&]?/g, ' $2$1');
console.log(b);
Given your example data i would try to make guesses based on name part count = 2, since it is very hard to rely on any ,, & or \n - which means treat them all as ,.
Try this against your data and let me know of any use-cases where this fails because i am highly confident that this script will fail at some point with more data :)
let testString = "Guilcher, G.M., Harvey, M. & Hand, J.P.\nRi Liesner, Peter Tom Collins, Michael Richards\nManco-Johnson M, Santagostino E, Ljung R.";
const inputToArray = i => i
.replace(/\./g, "")
.replace(/[\n&]/g, ",")
.replace(/ ?, ?/g, ",")
.split(',');
const reducer = function(accumulator, value, index, array) {
let pos = accumulator.length - 1;
let names = value.split(' ');
if(names.length > 1) {
accumulator.push(names);
} else {
if(accumulator[pos].length > 1) accumulator[++pos] = [];
accumulator[pos].push(value);
}
return accumulator.filter(n => n.length > 0);
};
console.log(inputToArray(testString).reduce(reducer, [[]]));
Here's my approach. I tried to keep it short but complexity was surprisingly high to get the edge cases.
First I'm formatting the input, to replace & for ,, and removing ..
Then, I'm splitting the input by \n, then , and finally (spaces).
Next I'm processing the chunks. On each new segment (delimited by ,), I process the previous segment. I do this because I need to be sure that the current segment isn't an initial. If that's the case, I do my best to skip that inital-only segment and process the previous one. The previous one will have the correct initial and surname, as I have all the information I neeed.
I get the initial on the segment if there's one. This will be used on the start of the next segment to process the current one.
After finishing each line, I process again the last segment, as it wont be called otherwise.
I understand the complexity is high without using regexp, and probably would have been better to use a state machine to parse the input instead.
const isInitial = s => [...s].every(c => c === c.toUpperCase());
const generateInitial = arr => arr.reduce((a, c, i) => a + (i < arr.length - 1 ? c[0].toUpperCase() : ''), '');
const formatSegment = (words, initial) => {
if (!initial) {
initial = generateInitial(words);
}
const surname = words[words.length - 1];
return {initial, surname};
}
const doDisplay = x => x.map(x => x.surname + ' ' + x.initial).join(', ');
const doProcess = _ => {
const formatted = input.value.replace(/\./g, '').replace(/&/g, ',');
const chunks = formatted.split('\n').map(x => x.split(',').map(x => x.trim().split(' ')));
const peoples = [];
chunks.forEach(line => {
let lastSegment = null;
let lastInitial = null;
let lastInitialOnly = false;
line.forEach(segment => {
if (lastSegment) {
// if segment only contains an initial, it's the initial corresponding
// to the previous segment
const initialOnly = segment.length === 1 && isInitial(segment[0]);
if (initialOnly) {
lastInitial = segment[0];
}
// avoid processing last segments that were only initials
// this prevents adding a segment twice
if (!lastInitialOnly) {
// if segment isn't an initial, we need to generate an initial
// for the previous segment, if it doesn't already have one
const people = formatSegment(lastSegment, lastInitial);
peoples.push(people);
}
lastInitialOnly = initialOnly;
// Skip initial only segments
if (initialOnly) {
return;
}
}
lastInitial = null;
// Remove the initial from the words
// to avoid getting the initial calculated for the initial
segment = segment.filter(word => {
if (isInitial(word)) {
lastInitial = word;
return false;
}
return true;
});
lastSegment = segment;
});
// Process last segment
if (!lastInitialOnly) {
const people = formatSegment(lastSegment, lastInitial);
peoples.push(people);
}
});
return peoples;
}
process.addEventListener('click', _ => {
const peoples = doProcess();
const display = doDisplay(peoples);
output.value = display;
});
.row {
display: flex;
}
.row > * {
flex: 1 0;
}
<div class="row">
<h3>Input</h3>
<h3>Output</h3>
</div>
<div class="row">
<textarea id="input" rows="10">Guilcher, G.M., Harvey, M. & Hand, J.P.
Ri Liesner, Peter Tom Collins, Michael Richards
Manco-Johnson M, Santagostino E, Ljung R.
Jordan M, Michael Jackson & Willis B.</textarea>
<textarea id="output" rows="10"></textarea>
</div>
<button id="process" style="display: block;">Process</button>

character limit in a word inside a textarea dynamically

i am trying to put a limit on number of characters in a word. i am successful to find, if someone is entering more then 15 character.i am displaying a message right now. what i want to do is, if some one enter more then 15 characters ... the java script display a alert and then delete all letters leaving first 14 characters in that word. i tried to find some helpful functions but never found something useful.
i want to check for the character limit dynamically when the user is still entering.
my code is half complete but it has one flaw. it is displaying the message when a count is reaching more then 15 it displays a alert. now user can still save that string that has more than 15 char in a word. hope i explained it all.thanks to all,for everyone that is gonna put some effort in advance.
<textarea id="txt" name="area" onclick="checkcharactercount()"></textarea>
function checkcharactercount(){
document.body.addEventListener('keyup', function(e) {
var val = document.getElementById("txt").value;
var string = val.split(" ");
for(i=0;i<string.length; i++) {
len = string[i].length;
if (len >= 15) {
alert('you have exceeded the maximum number of charaters in a word!!!');
break;
}
}
});
}
Does this work like you want it to?
var textArea = document.getElementById('txt');
textArea.addEventListener('keyup', function () {
var val = textArea.value;
var words = val.split(' ');
for (var i = 0; i < words.length; i++) {
if (words[i].length > 14) {
// the word is longer than 14 characters, use only the first 14
words[i] = words[i].slice(0, 14);
}
}
// join the words together again and put them into the text area
textArea.value = words.join(' ');
});
<textarea id="txt" name="area"></textarea>

How to create ordered list inside text area using javascript?

I have a text area where I add the number of orderlist item on click of a button.This is my code,
var addListItem = function() {
if (!this.addListItem.num) {
this.addListItem.num = 0
}
++this.addListItem.num;
var text = document.getElementById('editor').value;
console.log('text', text);
var exp = '\n' + this.addListItem.num + '.\xa0';
text = text.concat(exp);
document.getElementById('editor').value = text;
}
<div>
<button onclick="addListItem()">NumberList</button>
<textarea id="editor" col=10 rows=10></textarea>
</div>
As I have used a static variable to increment it increments on every click and so if I delete the list and create a new list again it doesn't starts from '1' and also I couldn't figure out how to update the numbers when a item is added in between.Could anyone suggest me how to fix this?
If you want a more robust solution that handles all sorts of different cases, you can use regular expressions to detect what number you're at in your list.
This solution also allows users to type in their own numbers and the button click WILL STILL WORK!
That's because this solution uses the text area content as the source of truth and doesn't track state on the side.
var addListItem = function() {
var text = document.getElementById('editor').value;
// regex to match against any new line that has a number and a period
// and extracts the number. feel free to use regex101.com to understand
// this in more depth.
var listNumberRegex = /^[0-9]+(?=\.)/gm;
var existingNums = [];
var num;
// get all the matches
while ((num = listNumberRegex.exec(text)) !== null) {
existingNums.push(num);
}
// sort the values
existingNums.sort();
// use the existing biggest number + 1 or use 1.
var addListItemNum;
if (existingNums.length > 0) {
// get last number and add 1
addListItemNum = parseInt(existingNums[existingNums.length - 1], 10) + 1;
} else {
// otherwise if there's nothing, just use 1.
addListItemNum = 1;
}
var exp = '\n' + addListItemNum + '.\xa0';
text = text.concat(exp);
document.getElementById('editor').value = text;
}
<div>
<button onclick="addListItem()">NumberList</button>
<textarea id="editor" col=10 rows=10></textarea>
</div>
understanding regular expressions is tricky, feel free to view https://regex101.com/r/gyX7oO/1 to get a better understanding of what is going on.
You can try something like this:
Logic:
On every click, get the text in textarea and split it be new line.
Now that you have line items, you need to get last sentence that starts with a numeric value. But user can enter new lines on his own to format text.
For this, loop on every line and validate it it starts with number followed by ..
If yes, use substring to fetch this number and parse it to int. If no match is found, you can return 0.
This will ensure the numbering system and you do not need a variable to hold last value.
Note: This logic assumes that last value will be the maximum. If you wish to handle that, you can just compare n and parseInt and assign maximum value
Sample:
var addListItem = function() {
var text = document.getElementById('editor').value;
var exp = '\n' + (getLastNumber(text) + 1) + '.\xa0';
text = text.concat(exp);
document.getElementById('editor').value = text;
}
function getLastNumber(str){
var list = str.split(/[\r\n]/g);
var n = 0;
list.forEach(function(s){
if(/^\d+\./.test(s)){
n = parseInt(s.substring(0, s.indexOf(".")));
}
});
return n;
}
<div>
<button onclick="addListItem()">NumberList</button>
<textarea id="editor" col=10 rows=10></textarea>
</div>
If you delete the list and create a new list again it will start from '1'.
also, counts from whatever the last number is.
var addListItem = function() {
if (!this.addListItem.num) {
this.addListItem.num = 0
}
++this.addListItem.num;
var text = document.getElementById('editor').value;
//HERE start new counting if textarea is empty
if(text.trim() == ''){
this.addListItem.num = 1; //restart counting here
}
//else check if textarea has previous numbers to proceed counting
else {
var lastLine = text.substr(text.lastIndexOf("\n") + 1).trim();
this.addListItem.num = parseInt(lastLine.slice(0, -1)) + 1; //add 1 to last number
}
console.log('text', text);
var exp = '\n' + this.addListItem.num + '.\xa0';
text = text.concat(exp);
document.getElementById('editor').value = text;
}
<div>
<button onclick="addListItem()">NumberList</button>
<textarea id="editor" col=10 rows=10></textarea>
</div>

How can I change the direction of textarea when there is Persian character?

Here is my code:
$("body").on('input', 'textarea', function() {
var el = $(this);
var len = el.val().length;
if (len <= 1){
var x = new RegExp("[A-Za-z]"); // is ascii
var isAscii = x.test(el.val().substring(0, 1));
if(isAscii){
el.css("direction", "ltr");
} else {
el.css("direction", "rtl");
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea>dynamic direction</textarea>
My current code changes the direction of such a textarea. It is based on the first character:
If the first character is either a Persian character or a sign, it sets rlt direction to that textarea.
If the first character is a English character, it sets lrt direction to that textarea.
Well that's not what I want. I want this:
If the first letter (not signs) is a English letter, then set the textarea ltr direction. Otherwise set it rtl.
Here is some examples:
var test = "..!"; // rtl
var test = "te"; // ltr
var test = "!te"; // ltr
var test = "..ق"; // rtl
var test = "مب"; // rtl
var test = "eس"; // ltr
var test = "سe"; // rtl
var test = "^سe"; // rtl
var test = ".32eس"; // ltr
How can I do that?
If I read the question correctly, the goal is to have the text read left-to-right if the first non-symbol/sign/punctuation character is an ASCII character, otherwise read right-to-left.
I think all you need to do is change your regex to first match 0 or more symbols/signs/punctuation-marks, and then to test if the next character is an ASCII character.
The regex [-!$%^&*()_+|~=`{}\[\]:";'<>?,.\/] is a fairly complete regex for symbols/signs/punctuation-marks, found here: https://stackoverflow.com/a/8359631/4132627. You may need to add to it as you see fit.
Putting that together we'd get something like [-!$%^&*()_+|~=`{}\[\]:";'<>?,.\/]*[A-Za-z]. The * between the two character groups means "match 0 or more of the previous group".
I've updated your snippet with that regex and it appears to work as expected. Also removed the length check as this needs to run no matter how many characters there are.
This probably isn't perfect - there are many cases probably being left out. You may need to play with it a bit. For example, should that second character group also include numbers ([A-Za-z0-9])?
In any case, I hope this helps!
$("body").on('input', 'textarea', function() {
var el = $(this);
var len = el.val().length;
//if (len <= 1){
var x = /^[-!$%^&*()_+|~=`{}\[\]:\";'<>?,.\/]*[A-Za-z]/; // is ascii
var isAscii = x.test(el.val());
if(isAscii){
el.css("direction", "ltr");
} else {
el.css("direction", "rtl");
}
//}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea>dynamic direction</textarea>
You can have an array of persian, or any right to left, letters and check whether the first letter exists in the array using .inArray() function, something like this:
jsFiddle
var persianLetters = ['آ ', 'ا', 'ب', 'پ', 'ت', 'ث','ج', 'چ', 'ح', 'خ', 'د', 'ذ', 'ر', 'ز' , 'ژ', 'س', 'ش', 'ص', 'ض', 'ط', 'ظ', 'ع', 'غ', 'ف', 'ق', 'ک', 'گ' ,'ل', 'م', 'ن', 'و', 'ه', 'ى'];
$("#ta").on('input', function() {
var el = $(this);
var txt = el.val();
var len = txt.trim().length;
if (len <= 1){
var x = txt.substring(0, 1);
// if the letter is not in the array, the $.inArray() will return -1
console.log($.inArray(x, persianLetters));
if($.inArray(x, persianLetters) > -1){
el.css("direction", "rtl");
} else {
el.css("direction", "ltr");
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<textarea name="txt" id="ta" cols="30" rows="10"></textarea>

How do you divide a string of random letters into pairs of substrings and replace with new characters continuously in Javascript?

So I'm working on this project that involves translating a string of text such as "als;kdfja;lsjkdf" into regular charaters like "the big dog" by parsing for certain pairs of letters that translate. (i.e: "fj" = "D")
The catch is I cant simply use the .replace() function in javascript, because there are many occurences where it's given the text "fjkl", and needs to find "jk" and logically interprets the collision of "fj" and "kl" to say that it's found it. This wont work for me, because for me, it didnt find it, as i am only trying to look at found pairs within 2 characters at a time. (i.e: "fjkl" could only yeild "fj" and "kl".)
(In the end I intend to utilize just the 8 characters "asdfjkl;" and set pairs of characters to actual letters. (in this subsitution method, fyi, "fj" OR "jf" would actually be "_"(space). )
in trying to figure out this task in javascript, (I dont know if another language might handle it more efficiently,) I tried utilizing the "split" function in the following way. (Disclaimer, I'm not sure if this is formatted 100% perfectly)
<textarea id="textbox"></textarea>
<script>
var text = document.getElementById("textbox").value; //getting string from the textarea
var pairs = text.split(/(..)/).filter(String); //spliting string into pairs
if(pairs == "fj"){replace(pairs, " ")} //some sort of subsitution
</script>
Additionally, if possible, i would like the replaced characters to be fed directly into the textarea continuosly as the user types, so the translation happens almost simutaneously. (I'm assuming this will use some sort of setInterval() function?)
If any tips can be given on the correct formatting of which tools i should use in javascript, that would be very outstanding; Thanks in advance.
if your interested, here is full list of subsitutions im making in the end of this project:
syntax:(X OR Y == result)
AJ JA = F
AK KA = V
AL LA = B
A; ;A = Y
SJ JS = N
SK KS = M
SL LS = S
S; ;S = P
DJ JD = A
DK DK = U
DL LD = D
D; ;D = G
FJ JF = _
FK KF = I
FL LF = T
F; ;F = K
AS SA = C
SD DS = L
DF FD = E
JK KJ = O
KL LK = R
L; ;L = Z
AD DA = -
SF FS = ,
AF FA = .
JL LJ = !
K; ;K = :
J; ;J = ?
-Daniel Rehman
I have prepared a code for your requirement. You can bind a function on keydown to allow continuous changes as you type in the textarea.
I am using replacePair method to replace a pair of character by its equivalent uppercase representation. You can inject your own custom logic here.
var tb = document.getElementById('tb');
var processedLength = 0;
var pairEntered=false;
tb.onkeydown = function (e) {
pairEntered=!pairEntered;
if (pairEntered) {
var nextTwoChars = this.value.substr(this.value.length - 2, 2);
var prevPart=this.value.substr(0,this.value.length-2);
var finalText=prevPart+ replacePair(nextTwoChars);
this.value=finalText;
processedLength+=2;
}
}
function replacePair(str){
return str.toUpperCase();
}
jsfiddle:http://jsfiddle.net/218fq7t2/
updated fiddle as per your replacement logic: http://jsfiddle.net/218fq7t2/3/
If you can be assured that certain pairs always translate to the same character, then perhaps a dictionary object can help.
var dict = { as:"A", kl:"B", al:"C", fj:"D" ... };
And, if your 'decryption' algorithm is 'lazy' (evaluates the first pair it encounters), then you can just travel through the input string.
var outputString = "", c, cl;
for (c = 1, cl = inputString.length; c < cl; c += 2) {
outputString += dict[inputString[c-1] + inputString[c]] || "";
}
If your replacement algorithm is not any more complicated than simply looking up which letter the pair represents, then this should do alright for you. No real logic necessary.
Couldn't you do it as follows:
var text = document.getElementById("textbox").value;
for (i = 0; i <= text.length; i++) {
if (text[i] == "j") {
if (text[i+1] == "f") {
pair = "jf";
text = text.replace(pair, "_");
}
}
What this would do is it would always, when checking any letter, also check the letter after it during the same step in the procedure. When it finds both letter i and letter i+1 matching up with a pair you are looking for, then the letters will be replaced by a space (or whatever you want), meaning that when the for-loop reaches the next run after a pair was found, the size of the text string will have been reduced by one. Thus, when it increments i, it will automatically skip the letter that made up the second component of the found pair. Thus, "jfkl" will be identified as two different pairs and your algorithm will not be confused.
of course, you would also have to work in the other pairs/codewords into the for loop so that they are all checked in some way
I had hoped my previous answer was enough to get you started. I was merely providing an algorithm that you could then use to your liking (wrap it in a function and add your own event listeners, etc).
Here is the solution to your problem. I did not write the entire dictionary. You will need to complete that.
var dictionary = { "aj":"F", "ja":"F", "ak":"V", "ka":"V", "al":"B", "la":"B", "a;":"Y", ";a":"Y" }
var input, output;
function init() {
input = document.getElementById("input");
output = document.getElementById("output");
input.addEventListener("keyup", decrypt, false);
}
function decrypt () {
if (!input || !output) {
return;
}
var i = input.value, o = "", c, cl;
for (c = 1, cl = i.length; c < cl; c += 2) {
o += dictionary[ i[c-1] + i[c] ] || "";
}
while (output.hasChildNodes()) {
output.removeChild(output.firstChild);
}
output.appendChild(document.createTextNode(o));
}
window.addEventListener("load", init, false);
<textarea id="input"></textarea>
<div id="output"></div>

Categories

Resources