Add line break in string on last character before n characters - javascript

To start off, this is probably worded badly, as I am not sure how to put what I want into wordsLets say I have this canvas (I'm using node-canvas) and I want to make it display text from a user input. However, the way I am doing it limits the number of characters to 36-38 (not looking for a solution to this). So I made a script using the Regex textstr.match(/.{1,32}/g) that splits the string every 32 characters (just to be safe), calculates a new canvas height, and then does join("\n") when it comes time to print the string. However, when receiving feedback on this, I realized it would be better to split along the last space in the string and add a line break there, but I am confused how to do this.
My current code is this:
textStr = "123456789 01234567890 123456789012 34567890"
var splitStr
if(textstr.length > 32){
if(textstr.substring(1,32).includes(" ")){ //1,32 so it won't bug out if the first character is a space
//splitStr = textstr.something(test)
} else {
splitStr = textstr.match(/.{1,32}/g)
}
}
//canvas initialization blah blah blah
//load fonts yada yada yada
ctx.fillText(splitStr.join("\n"), 20, 55)
I was wondering if there was some sort of regex expression that I could use. Any help/feedback/common sense is appreciated

This solution is a bit complex and can due with some simplification. However it should get you mostly there.
const input = "123456789 01234567890 123456789012 34567890 11444444444 424124 1234124124121 4444444444444444444444444444444444444444444444444444444444444444444444";
const split = (value, width) => {
const stack = value.split(' ').reverse();
const results = [];
let builder = "";
while (stack.length > 0) {
const item = stack.pop();
if (item.length > width) { // is the current chunk already larger than our desired width?
if (builder !== "") { // we have to push our buffer too
results.push(builder);
builder = "";
}
results.push(item);
} else {
const line = builder === ""
? item
: `${builder} ${item}`;
if (line.length > width) { // is our new line greater than our width?
stack.push(item); // push the item back, since consuming it would make our line length too long. we let the next iteration consume it. results.push(builder); // push the buffer into our results.
builder = "";
} else if (stack.length === 0) { // is this the last element? just add it to the results.
results.push(line);
} else {
builder = line; // update our buffer to the current appended chunk.
}
}
}
return results;
};
split(input, 32).forEach((c) => console.log(c, c.length));
split(input, 32).join("\n");

Related

How to split a string and then split the returned value again connected to another value that has to be split, and join the remainder to the remaining

What I want is something like a string that is around 2000 to 3000 characters with over a hundred non-uniformly located \n in them, and I want to split them for every 1000 characters, and then in the returned strings in an array, for each of the values in the returned array, I want to end the string at the last \n (leaves it as it is if the string contains no \n) and the remainder of the string after the last \n should be appended to the beginning of the next value in the array, and then this should be carried out AFTER the previous string has been fixed to the last \n
I hope you understand what I mean, and this is my code
module.exports={
async split(text,length,max){
if (text.length > max){
return;
}
let regex = new RegExp(`.{1,${length}}`, "g");
let splitted = text.match(regex);
return splitted;
}
}
The place where its fetched and executed is here:
let splitted = await split(lyrics,1000,6000)
I managed to do the split for every 1000 words but the thing that I explained at the top is what I want to do and I am unable to manage it, so can anyone help out?
EDIT: Suppose I wanted to split the string for a max of 20 characters with the max string length of 1000, and if it bypasses that limit, then it means that nothing will be returned. It can do that second stage of splitting (as I mentioned in the question as \n) with a whitespace ( ).
Imagine that the string is: Hello, I love Stack Overflow, and it is super cool
In my current code, if we did
let string = `Hello, I love Stack Overflow, and it is super cool`
let splitted = await split(string, 10, 1000)
It would return
["Hello, I l", "ove Stack ", "Overflow, ", "and it is ", "super cool"]
What if another agument is added in split(), making it:
async split(text, length, max, splitAt)
splitAt can mean \n or depending on choice
The result that I want to return is: ["Hello, I", "love Stack", "Overflow,", "and it is", "super cool"]
The thing is, I can not understand how to do it
You certainly don't need this method to be async and it should just be a case of stepping through the string, splitting by len and finding the lastIndexOf your splitAt argument, and then taking that chunk into an array using substring
Something like this:
function split(text, len, max, splitAt) {
if (text.length > max) {
return;
}
let pos = 0;
const chunks = []
while (pos < text.length) {
if (pos + len >= text.length) {
chunks.push(text.substring(pos));
pos = text.length
} else {
const s = text.substring(pos, pos + len + 1).lastIndexOf(splitAt);
chunks.push(text.substring(pos, pos + s));
pos += s + 1;
}
}
return chunks;
}
let string = `Hello, I love Stack Overflow, and it is super cool`
let splitted = split(string, 10, 1000, " ")
console.log(splitted);
If I understand it correctly, you want to split the text into chunks that have a max size of 1000, and they should end with a newline character.
function split(str, chunkSize){
const chunks = [];
let current_chunk = ""
str.split("\n").forEach(part => {
if(current_chunk.length + part.length > chunkSize){
// Adds the chunk to chunks and resets current chunk.
chunks.push(current_chunk);
current_chunk = "";
}
// adds \n to the start of the part, if the current chunk isn't empty.
current_chunk.length
? current_chunk += `\n${part}`
: current_chunk += part
})
// Used to get the last one, if it isn't empty.
if(current_chunk.length) chunks.push(current_chunk);
return chunks;
}
Should look something like this. Haven't tested it though, since I've written it on my phone.

Java Script problem with initial display of str.replaceAll [duplicate]

This question already has an answer here:
How to make Java Script ignore or maintain spaces, not delete them
(1 answer)
Closed 1 year ago.
I was given this code for a moving window self-paced reading experiment where the initial display is a sentence string covered by dashes (separated by word) and the display of regions (multiple words separated by "," in the csv) is iterated as you click through the sentence. The amount of time the participant spends in a region is recorded in the output csv. Below is the code and desired display:
var _pj;
function replaceWithdash(Sentence, currentWordNumber) {
const sentenceList = Sentence.split(",")
const sigil = sentenceList.map(s => s.replaceAll(/[^\s]/g, "-"))
if (currentWordNumber !== undefined) {
sigil.splice(currentWordNumber, 1, sentenceList[currentWordNumber])
}
return sigil.join("")
}
function _pj_snippets(container) {
function in_es6(left, right) {
if (((right instanceof Array) || ((typeof right) === "string"))) {
return (right.indexOf(left) > (- 1));
} else {
if (((right instanceof Map) || (right instanceof Set) || (right instanceof WeakMap) || (right instanceof WeakSet))) {
return right.has(left);
} else {
return (left in right);
}
}
}
container["in_es6"] = in_es6;
return container;
}
_pj = {};
_pj_snippets(_pj);
Trials_Display.text = replaceWithdash(Sentence, wordNumber);
keypresses = psychoJS.eventManager.getKeys();
sentenceList = Sentence.split(",");
if ((keypresses.length > 0)) {
if (_pj.in_es6("space", keypresses)) {
thisResponseTime = t;
wordNumber = (wordNumber + 1);
if ((wordNumber < sentenceList.length)) {
if ((wordNumber === 0)) {
timeOfLastResponse = 0;
}
thisExp.addData(("IRI_" + wordNumber.toString()), (thisResponseTime - timeOfLastResponse));
timeOfLastResponse = thisResponseTime;
Trials_Display.text = replaceWithdash(Sentence, wordNumber);
} else {
continueRoutine = false;
}
} else {
if (_pj.in_es6("escape", keypresses)) {
core.quit();
}
}
}
Desired Display:
str = "The dog, ate, the food"
console.log(replaceWithdash(str))
//"--- --- --- --- ----"
console.log(replaceWithdash(str, 0))
//"The dog --- --- ----"
console.log(replaceWithdash(str, 1))
//"--- --- ate --- ----"
console.log(replaceWithdash(str, 2))
// "--- --- --- the food"
Although this code does successfully show spaces between dashes and displays region by region, my problem is with the initial display of the dashed sentence. It looks like it's only showing the first two dashed regions and then once you click to reveal the words in the first region, it'll adjust to show the dashes for the third region. Also, there seems to be an extra space between regions for some reason. Something like this:
str = "The dog, ate, the food"
//"--- --- ---"
//"The dog --- --- ----"
//"--- --- ate --- ----"
// "--- --- --- the food"
I assume there's something wrong with how it's interpreting the initial string without an index (console.log(replaceWithdash(str)), but I'm not sure how to fix this, or the extra spaces.
I'm relatively new to Java script besides what I've taught myself, so any help would be appreciated!!
Wow, TLDR the comments. Here's how I'd do it:
function replaceWithDash(input,partNumber) {
//First, lets check if partNumber was omitted, since we can use this case
//to replace a single part as well
if(typeof partNumber!="number")
//Just remove the commas and replace every letter with the dash
return input.replace(/,/g,"").replace(/[^\s]/g,"-");
//If partNumber was specified (the function didn't end on the previous return), lets split the sentence
parts=input.split(",");
//remove the partNumber-th element and replace it with its dashed version
parts.splice(partNumber,1,replaceWithdash(parts[partNumber]));
//then join everything again and fix repeating spaces (it's quicker to do this once here than trimming each element)
return parts.join(" ").replace(/[\s]+/g," ");
}
Note that I fixed the function name.

Filtering ranges of letters in Javascript

I have to create a program that takes the first letter of a prompt, and if that letter is between a and k, then it has to produce a certain output, if it's between l and p another and so on. Is there a way to do this without writing every letter of the alphabet down? (sorry I'm a new coder)
I think you should try to solve the problem, before asking - so you can show what you've already tried.
I think the snippet below points you in the right direction - but it takes any character, not just letters. You need to get everything filtered out that's not a lower case letter.
// UI elements
const input = document.getElementById('input1')
const result = document.getElementById('result')
// input event
// only the first character is taken into account
input.addEventListener('input', function(e) {
// adding the characters of the input value to an array, and
// picking the 0th element (or '', if there's no 0th element)
const a = [...this.value][0] || ''
let ret = ''
if (a !== '') {
// lowercaseing letters, so it's easier to categorize them
ret = categorizeAlphabet(a.toLowerCase().charCodeAt(0))
} else {
ret = 'The input is empty'
}
// displaying the result
result.textContent = ret
})
// you could use this function to filter and categorize
// according to the problem ahead of you - and return the result
// to be displayed.
// In this example this function is rather simple, but
// you can build a more complex return value.
const categorizeAlphabet = (chCode) => {
return `This is the character code: ${chCode}`
}
<label>
First character counts:
<input type="text" id='input1'>
</label>
<h3 id="result">The input is empty</h3>

Split a CSV string by line skipping newlines contained between quotes

If the following regex can split a csv string by line.
var lines = csv.split(/\r|\r?\n/g);
How could this be adapted to skip newline chars that are contained within a CSV value (Ie between quotes/double-quotes)?
Example:
2,"Evans & Sutherland","230-132-111AA",,"Visual","P
CB",,1,"Offsite",
If you don't see it, here's a version with the newlines visible:
2,"Evans & Sutherland","230-132-111AA",,"Visual","P\r\nCB",,1,"Offsite",\r\n
The part I'm trying to skip over is the newline contained in the middle of the "PCB" entry.
Update:
I probably should've mentioned this before but this is a part of a dedicated CSV parsing library called jquery-csv. To provide a better context I have added the current parser implementation below.
Here's the code for validating and parsing an entry (ie one line):
$.csvEntry2Array = function(csv, meta) {
var meta = (meta !== undefined ? meta : {});
var separator = 'separator' in meta ? meta.separator : $.csvDefaults.separator;
var delimiter = 'delimiter' in meta ? meta.delimiter : $.csvDefaults.delimiter;
// build the CSV validator regex
var reValid = /^\s*(?:D[^D\\]*(?:\\[\S\s][^D\\]*)*D|[^SD\s\\]*(?:\s+[^SD\s\\]+)*)\s*(?:S\s*(?:D[^D\\]*(?:\\[\S\s][^D\\]*)*D|[^SD\s\\]*(?:\s+[^SD\s\\]+)*)\s*)*$/;
reValid = RegExp(reValid.source.replace(/S/g, separator));
reValid = RegExp(reValid.source.replace(/D/g, delimiter));
// build the CSV line parser regex
var reValue = /(?!\s*$)\s*(?:D([^D\\]*(?:\\[\S\s][^D\\]*)*)D|([^SD\s\\]*(?:\s+[^SD\s\\]+)*))\s*(?:S|$)/g;
reValue = RegExp(reValue.source.replace(/S/g, separator), 'g');
reValue = RegExp(reValue.source.replace(/D/g, delimiter), 'g');
// Return NULL if input string is not well formed CSV string.
if (!reValid.test(csv)) {
return null;
}
// "Walk" the string using replace with callback.
var output = [];
csv.replace(reValue, function(m0, m1, m2) {
// Remove backslash from any delimiters in the value
if (m1 !== undefined) {
var reDelimiterUnescape = /\\D/g;
reDelimiterUnescape = RegExp(reDelimiterUnescape.source.replace(/D/, delimiter), 'g');
output.push(m1.replace(reDelimiterUnescape, delimiter));
} else if (m2 !== undefined) {
output.push(m2);
}
return '';
});
// Handle special case of empty last value.
var reEmptyLast = /S\s*$/;
reEmptyLast = RegExp(reEmptyLast.source.replace(/S/, separator));
if (reEmptyLast.test(csv)) {
output.push('');
}
return output;
};
Note: I haven't tested yet but I think I could probably incorporate the last match into the main split/callback.
This is the code that does the split-by-line part:
$.csv2Array = function(csv, meta) {
var meta = (meta !== undefined ? meta : {});
var separator = 'separator' in meta ? meta.separator : $.csvDefaults.separator;
var delimiter = 'delimiter' in meta ? meta.delimiter : $.csvDefaults.delimiter;
var skip = 'skip' in meta ? meta.skip : $.csvDefaults.skip;
// process by line
var lines = csv.split(/\r\n|\r|\n/g);
var output = [];
for(var i in lines) {
if(i < skip) {
continue;
}
// process each value
var line = $.csvEntry2Array(lines[i], {
delimiter: delimiter,
separator: separator
});
output.push(line);
}
return output;
};
For a breakdown on how that reges works take a look at this answer. Mine is a slightly adapted version. I consolidated the single and double quote matching to match just one text delimiter and made the delimiter/separators dynamic. It does a great job of validating entiries but the line-splitting solution I added on top is pretty frail and breaks on the edge case I described above.
I'm just looking for a solution that walks the string extracting valid entries (to pass on to the entry parser) or fails on bad data returning an error indicating the line the parsing failed on.
Update:
splitLines: function(csv, delimiter) {
var state = 0;
var value = "";
var line = "";
var lines = [];
function endOfRow() {
lines.push(value);
value = "";
state = 0;
};
csv.replace(/(\"|,|\n|\r|[^\",\r\n]+)/gm, function (m0){
switch (state) {
// the start of an entry
case 0:
if (m0 === "\"") {
state = 1;
} else if (m0 === "\n") {
endOfRow();
} else if (/^\r$/.test(m0)) {
// carriage returns are ignored
} else {
value += m0;
state = 3;
}
break;
// delimited input
case 1:
if (m0 === "\"") {
state = 2;
} else {
value += m0;
state = 1;
}
break;
// delimiter found in delimited input
case 2:
// is the delimiter escaped?
if (m0 === "\"" && value.substr(value.length - 1) === "\"") {
value += m0;
state = 1;
} else if (m0 === ",") {
value += m0;
state = 0;
} else if (m0 === "\n") {
endOfRow();
} else if (m0 === "\r") {
// Ignore
} else {
throw new Error("Illegal state");
}
break;
// un-delimited input
case 3:
if (m0 === ",") {
value += m0;
state = 0;
} else if (m0 === "\"") {
throw new Error("Unquoted delimiter found");
} else if (m0 === "\n") {
endOfRow();
} else if (m0 === "\r") {
// Ignore
} else {
throw new Error("Illegal data");
}
break;
default:
throw new Error("Unknown state");
}
return "";
});
if (state != 0) {
endOfRow();
}
return lines;
}
All it took is 4 states for a line splitter:
0: the start of an entry
1: the following is quoted
2: a second quote has been encountered
3: the following isn't quoted
It's almost a complete parser. For my use case, I just wanted a line splitter so I could provide a more granual approach to processing CSV data.
Note: Credit for this approach goes to another dev whom I won't name publicly without his permission. All I did was adapt it from a complete parser to a line-splitter.
Update:
Discovered a few broken edge cases in the previous lineSplitter implementation. The one provided should be fully RFC 4180 compliant.
As I have noted in a comment there is no complete solution just using single regex.
A novel method using several regexps by splitting on comma and joining back strings with embedded commas is described here:-
Personally I would use a simple finite state machine as described here
The state machine has more code, but the code is cleaner and its clear what each piece of code is doing. Longer term this will be much more reliable and maintainable.
It's not a good idea to use regex's to parse. Better to use it to detect the "bad" splits and then merge them back:
var lines = csv.split(/\r?\n/g);
var bad = [];
for(var i=lines.length-1; i> 0; i--) {
// find all the unescaped quotes on the line:
var m = lines[i].match(/[^\\]?\"/g);
// if there are an odd number of them, this line, and the line after it is bad:
if((m ? m.length : 0) % 2 == 1) { bad.push(i--); }
}
// starting at the bottom of the list, merge lines back, using \r\n
for(var b=0,len=bad.length; b < len; b++) {
lines.splice(bad[b]-1, 2, lines[bad[b]-1]+"\r\n"+lines[bad[b]]);
}
(This answer is licensed under both CC0 and WTFPL.)
Be careful- That newline is PART of that value. It's not PCB, it's P\nCB.
However, why can't you just use string.split(',')? If need be, you can run through the list and cast to ints or remove the padded quotation marks.

parse a textarea in substrings based on line breaks in Javascript

I have a text area that I need to parse. Each new line needs to be pulled out and an operation needs to be performed on it. After the operation is done the operation needs to be run on the next line. This is what I have at the moment. I know the indexOf search won't work because it's searching character by character.
function convertLines()
{
trueinput = document.getElementById(8).value; //get users input
length = trueinput.length; //getting the length of the user input
newinput=trueinput; //I know this looks silly but I'm using all of this later
userinput=newinput;
multiplelines=false; //this is a check to see if I should use the if statement later
for (var i = 0; i < length; i++) //loop threw each char in user input
{
teste=newinput.charAt(i); //gets the char at position i
if (teste.indexOf("<br />") != -1) //checks if the char is the same
{
//line break is found parse it out and run operation on it
userinput = newinput.substring(0,i+1);
submitinput(userinput);
newinput=newinput.substring(i+1);
multiplelines=true;
}
}
if (multiplelines==false)
submitinput(userinput);
}
So for the most part it is taking the userinput. If it has multiply lines it will run threw each line and seperatly and run submitinput. If you guys can help me I'd be eternally thankful. If you have any questions please ask
Line breaks within the value of a textarea are represented by line break characters (\r\n in most browsers, \n in IE and Opera) rather than an HTML <br> element, so you can get the individual lines by normalizing the line breaks to \n and then calling the split() method on the textarea's value. Here is a utility function that calls a function for every line of a textarea value:
function actOnEachLine(textarea, func) {
var lines = textarea.value.replace(/\r\n/g, "\n").split("\n");
var newLines, i;
// Use the map() method of Array where available
if (typeof lines.map != "undefined") {
newLines = lines.map(func);
} else {
newLines = [];
i = lines.length;
while (i--) {
newLines[i] = func(lines[i]);
}
}
textarea.value = newLines.join("\r\n");
}
var textarea = document.getElementById("your_textarea");
actOnEachLine(textarea, function(line) {
return "[START]" + line + "[END]";
});
If user is using enter key to go to next line in your text-area you can write,
var textAreaString = textarea.value;
textAreaString = textAreaString.replace(/\n\r/g,"<br />");
textAreaString = textAreaString.replace(/\n/g,"<br />");
textarea.value = textAreaString;
to simplify the answers, here is another approach..
var texta = document.getElementById('w3review');
function conv (el_id, dest_id){
var dest = document.getElementById(dest_id),
texta = document.getElementById(el_id),
val = texta.value.replace(/\n\r/g,"<br />").replace(/\n/g,"<br />");
dest.innerHTML = val;
}
<textarea id="targetted_textarea" rows="6" cols="50">
At https://www.a2z-eco-sys.com you will get more than what you need for your website, with less cost:
1) Advanced CMS (built on top of Wagtail-cms).
2) Multi-site management made easy.
3) Collectionized Media and file assets.
4) ...etc, to know more, visit: https://www.a2z-eco-sys.com
</textarea>
<button onclick="conv('targetted_textarea','destination')" id="convert">Convert</button>
<div id="destination">Had not been fetched yet click convert to fetch ..!</div>

Categories

Resources