prevent regex errors with unpredictable values - google-apps-script

In a mail merge application I use the .replace() method to replace field identifiers by custom values and also in a reverse process to get the identifiers back.
The first way works every time since the replace first argument is a pretty normal string that I have chosen on purpose... but when I reverse the process it happens sometimes that the string contains incorrect regular expression characters.
This happens mainly on phone numbers in the form +32 2 345 345 or even with some accentuated characters.
Given I can't prevent this from happening and that I have little hope that my endusers won't use this phone number format I was wondering if someone could suggest a workaround to escape illegal characters when they come up ? note : it can be at any place in the string.
below is the code for both functions.
... (partial code)
var newField = ChampSpecial(curData,realIdx,fctSpe);// returns the value from the database
if(newField!=''){replacements.push(newField+'∏'+'#ch'+(n+1)+'#')};
//Logger.log('value in '+n+'='+realIdx+' >> '+Headers[realIdx]+' = '+ChampSpecial(curData,realIdx,fctSpe))
app.getElementById('textField'+(n+1)).setHTML(ChampSpecial(curData,realIdx,fctSpe));
if(e.parameter.source=='insertInText'){
body.replaceText('#ch'+(n+1)+'#',newField);
}
}
UserProperties.setProperty('replacements',replacements.join('|'));
cloakOn();
colorize('#ffff44');
return app;
}
function fieldsInDoc(e){
cloakOff();// remet d'abord les champs vides
var replacements = UserProperties.getProperty('replacements').split('|');
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
for(var n=0;n<replacements.length;++n){
var field = replacements[n].split('∏')[1];
var testVal = replacements[n].split('∏')[0];
body.replaceText(testVal,field);
}
colorize('#ffff44');
}

In the reverse process you are using the fieldvalues provided that can include regex special characters. you have to escape them before replacing:
body.replaceText(field.replace(/[[\]{}()*-+?.,\\^$|#\s]/, '\\$&'), '#ch'+(n+1)+'#');
This said, the "replace back the markers" a bad idea. What happens if two fields of the mail merge have the same value or the replacement text is already present in the document template...

One possible solution was to prevent the example fields in the doc from containing regex special characters so the replace had to occur in the forward process, not in the reverse (as suggested in the other answer).
Escaping these character in the fields values didn't work* so I ended up with a simple replacement by a hyphen (which make sense in most cases to replace a slash or a '+').
(*) the reverse process uses the value kept in memory so the escape sign was disturbing the replace in that function, preventing it to work properly.
the final working code goes simply like this :
//(in the first function)
var newField = ChampSpecial(curData,realIdx,fctSpe).replace(/([*+?^=!:${}()|\[\]\/\\])/g, "-");// replace every occurrence of *+?^... by '-' (global search)
About the comment stating that this approach is a bad idea I can only say that I'm afraid there is not really other ways to get that behavior and that the probability to get errors if finally quite low since the main usage of mail merge is to insert proper names, adresses, emails and phone numbers that are rarely in the template itself.
As for the field indicators they will never have the same name since they are numerically indexed (#chXX#).
EDIT : following Taras's comment I'll try another solution, will update later if it works as expected.
EDIT June 19 , Yesssss... found it.
I finally found a far better solution that doesn't use regular expression so I'm not forced to escape special characters ... the .find() method accepts any string.
The code is a bit more complex but the results is worth the pain :-))
here is the full code in 2 functions if ever someone looks for something similar.
function valuesInDoc(e){
var lock = LockService.getPrivateLock(); // just in case one clicks the second button before this one ends
var success = lock.tryLock(5000);
if (!success) {
Logger.log('tryLock failed to get the lock');
return
}
colorize('#ffffff');// this function removes the color tags on the field marlers
var app = UiApp.getActiveApplication();
var listVal = UserProperties.getProperty('listSel').split(',');
var replacements = [];
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
var find = body.findText('#ch');
if(find == null){return app };
var curData = UserProperties.getProperty('selItem').split('|');
var Headers = [];
var OriHeaders = UserProperties.getProperty('Headers').split('|');
for(n=0;n<OriHeaders.length;++n){
Headers.push('#'+OriHeaders[n]+'#');
}
var fctSpe = 0 ;
for(var i in Headers){if(Headers[i].indexOf('SS')>-1){fctSpe = i}}
for(var n=0;n<listVal.length;++n){
var realIdx = Number(listVal[n]);
Logger.log(n);
var newField = ChampSpecial(curData,realIdx,fctSpe);
//Logger.log(newField);
app.getElementById('textField'+(n+1)).setHTML(ChampSpecial(curData,realIdx,fctSpe));
if(e.parameter.source=='insertInText'){
var found = body.findText('#ch'+(n+1)+'#');// look for every field markers in the whole doc
while(found!=null){
var elemTxt = found.getElement().asText();
var startOffset = found.getStartOffset();
var len = ('#ch'+(n+1)+'#').length;
elemTxt.deleteText(startOffset, found.getEndOffsetInclusive())
elemTxt.insertText(startOffset,newField);// remove the marker and write the sample value in place
Logger.log('n='+n+' newField = '+newField+' for '+'#ch'+(n+1)+'#'+' at position '+startOffset)
replacements.push(newField+'∏'+'#ch'+(n+1)+'#'+'∏'+startOffset);// memorize the change that just occured
found = body.findText('#ch'+(n+1)+'#',found); //loop until all markers are replaced
}
}
}
UserProperties.setProperty('replacements',replacements.join('|'));
cloakOn();
colorize('#ffff44');// colorize the markers if ever one is left but it shouldn't happen
lock.releaseLock();
return app;
}
function fieldsInDoc(e){
var lock = LockService.getPrivateLock();
var success = lock.tryLock(5000);
if (!success) {
Logger.log('tryLock failed to get the lock');
return
}
cloakOff();// remet d'abord les champs vides > shows the hidden fields (markers that had no sample velue in the first function
var replacements = UserProperties.getProperty('replacements').split('|');// recover replacement data as an array
Logger.log(replacements)
var doc = DocumentApp.getActiveDocument();
var body = doc.getBody();
for(var n=replacements.length-1;n>=0;n--){ // for each replacement find the data in doc and write a field marker in place
var testVal = replacements[n].split('∏')[0]; // [0] is the sample value
if(body.findText(testVal)==null){break};// this is only to handle the case one click on the wrong button trying to place markers again when they are already there ;-)
var field = replacements[n].split('∏')[1];
var testValLength = testVal.length;
var found = body.findText(testVal);
var startOffset = found.getStartOffset();
Logger.log(testVal+' = '+field+' / start: '+startOffset+' / Length: '+ testValLength)
var elemTxt = found.getElement().asText();
elemTxt.deleteText(startOffset, startOffset+testValLength-1);// remove the text
// elemTxt.deleteText(startOffset, found.getEndOffsetInclusive() )
elemTxt.insertText(startOffset,field);// and write the marker
}
colorize('#ffff44'); // colorize the marker
lock.releaseLock();// and release the lock
}

Related

Counting word matches

This has been driving me crazy - it seems it should be simple, but I can't find how to get this to work.
In Apps Script, I have a string (taken from a cell formula) and want to count how many times a certain word or phrase appears. I thought match should do it, but I can't get it to work with a variable. Only with typing it directly. Is there a way to do this using a variable?
I have had a look at regexp, but there is not much on the Google pages. That may be my best option though I don't understand it.
function myFunction() {
var str = "'=IFERROR(IF('Train Station'!D154 ='A','A: Good - Performing well',
IF('Train Station'!D154='B','B: OK',
IF('Train Station'!D154='C','C: Do Better',
IF('Train Station'!D154='D','D: Give Up')))),'')'";
// var searchfor = '/'+'Train'+'/g';
// var regex = new RegExp(searchfor)
// var answer = RegExp(searchfor,str). //str.match(/+searchfor+/g).length;
var answer = str.match(/Train/g).length
Logger.log(answer)
}
EDIT / UPDATE
Getting closer. RegExp seems to be what I need (without all the /s\g*!!? stuff I was worried about). Now I just need to manage what happens withe the error : Cannot read property 'length' of null.
var searchfor = 'Train';
var regex = str.match(new RegExp(searchfor, "g")).length
Logger.log(regex)
I was going to progress this project using If regex == 4 then but the nulls are winning that battle. For now.
SUCCESS!!
var regex = (str.match(new RegExp(searchfor, "g"))||[]).length
I'm not going to even think how many hours I spent on this. Hopefully someone will find this helpful one day.
str is the string to look at, searchfor is the text to find, the "g" flag (global) and ||[] returns 0 if the result is null.
Thanks to all who looked at this for me.
Try it this way:
function myFunction() {
var str = "'=IFERROR(IF('Train Station'!D154 ='A','A: Good - Performing well'IF('Train Station'!D154='B','B: OK',IF('Train Station'!D154='C','C: Do Better',IF('Train Station'!D154='D','D: Give Up'),'')'";
// var searchfor = '/'+'Train'+'/g';
// var regex = new RegExp(searchfor)
// var answer = RegExp(searchfor,str). //str.match(/+searchfor+/g).length;
var answer = str.match(/Train/g).length
Logger.log(answer)
}
function myFunction() {
var str = "'=IFERROR(IF('Train Station'!D154 ='A','A: Good - Performing well',
IF('Train Station'!D154='B','B: OK',
IF('Train Station'!D154='C','C: Do Better',
IF('Train Station'!D154='D','D: Give Up')))),'')'";
var searchfor = 'Train';
var regex = (str.match(new RegExp(searchfor, "g"))||[]).length;
Logger.log(regex);
}

Finding and Deleting All Emojis in a Google Spreadsheet

I have a Google Spreadsheet with thousands of cells with each cell being populated with strings with many different emojis.
Example of entries:
"Lol 😊","Haha 😊","Fire 🔥","👏👏👏Awesome!","Nice👍 See you tomorrow!😀",
"こんにちは😊", "你好😀"
But I want to delete all of the emojis, is there a search function I can run/piece of Spreadsheet code I can run to make the document devoid of emojis?
Cleaning Up with Regular Expressions
I don't have the time to do the whole thing but this will give you a start. I cleaned everything in one cell with this.
var sht = SpreadsheetApp.getActiveSheet();
var text = sht.getActiveCell().getValue();
var cleantext = text.replace(/[^\s\w]/g,'');//replace everything that's not whitespace or word characters with null
sht.getActiveCell().setValue(cleantext);
I used the line you provided as test data. Admittedly it needs a little tweaking because it's getting rid of some punctuation.
This is a little better.
function test()
{
var sht = SpreadsheetApp.getActiveSheet();
var text = sht.getActiveCell().getValue();
var cleantext = text.replace(/[^\s\w"!,]/g,'');//added "!,
sht.getActiveCell().setValue(cleantext);
}
So as you run it you may want to add a few more characters to don't replace list. That's it.
I have an expense report that I use to collect my expenses in different categories and I like to produce pie charts to help me get a big picture view of where my money is going. I use this Array Formula to help me gather the information into useful categories for me.
=ArrayFormula(IF(Row(C:C)=1,"Title",IF(LEN(C:C),IF(REGEXMATCH(C:C,"(?i)(string1|string2|string3|string4)"),D:D,""),)))
The regular expression provides an or function for adding additional matching for unexpected item appearing on my expense lists that I want to gather into these categories. If you need another matching term you just go into that formula and add another term as shown below
(string1|string2|string3|string4||string5)
The strings are replaced with real terms with no quotes unless they have quotes around them in the search target.
Here is some code that goes through one column of data and removes emojis from each cell.
You must replace Your Sheet Tab Name with the sheet tab name that the code should work on. This code currently only processes one column of data. The entire column of values is written back to the sheet in one write operation. Any character codes that are 5 characters or more are assumed to be emojis.
Test it on a few rows of data first.
function killEmojies() {
var arrayThisRow,columnOfValues,columnToRemoveEmojiesFrom,firstTwoChar,
i,innerArray,j,L,newCellContent,outerArray,
ss,sh,
targetSheet,thisCell,thisCellChar,thisCellVal,thisCharCode,thisCharCodeLength;
columnToRemoveEmojiesFrom = 1;
outerArray = [];
ss = SpreadsheetApp.getActiveSpreadsheet()
sh = ss.getSheetByName("Your Sheet Tab Name Here");
targetSheet = ss.getSheetByName("Your Sheet Tab Name Here");
columnOfValues = sh.getRange(1, columnToRemoveEmojiesFrom,sh.getLastRow(),1).getValues();
L = columnOfValues.length;
Logger.log('L: ' + L);
for (i=0;i<L;i++) {
thisCell = columnOfValues[i];//Get inner array
thisCellVal = thisCell[0];//Get first element of inner array
Logger.log(thisCellVal)
Logger.log('typeof thisCellVal: ' + typeof thisCellVal)
newCellContent = "";//Reset for every cell
innerArray = [];//Reset for every row loop
if (typeof thisCellVal !== 'string') {//This spreadsheet cell contains something
//other than text
innerArray.push(thisCellVal);
} else {
for (j=0;j<thisCellVal.length;j++) {//Loop through every character in the cell
thisCellChar = thisCellVal[j];
thisCharCode = thisCellChar.charCodeAt(0);//Character code of this character
thisCharCodeLength = thisCharCode.toString().length;
Logger.log('typeof thisCharCodeLength: ' + typeof thisCharCodeLength);
Logger.log('this val: ' + thisCharCode);
Logger.log('thisCharCodeLength: ' + thisCharCodeLength);
Logger.log(thisCharCodeLength < 5);
if (thisCharCodeLength === 5) {
firstTwoChar = thisCharCode.toString().slice(0,2);
Logger.log('firstTwoChar: ' + firstTwoChar)
}
if (thisCharCodeLength > 4 && (firstTwoChar === "54" || firstTwoChar === "55" || firstTwoChar === "56")) {
continue;//exclude character codes that are 5 or more characters long
//and start with 54 or 55
}
newCellContent = newCellContent + thisCellChar;
}
innerArray.push(newCellContent);
}
outerArray.push(innerArray);
}
targetSheet.getRange(1, columnToRemoveEmojiesFrom,outerArray.length,1).setValues(outerArray);
}
Replace emojis from text
I've found, you may use a REGEXREPLACE for that.
To replace all emojis from [A1] please try:
=REGEXREPLACE($A$1,"[🏻🏼🏽🏾🏿©®‼⁉™ℹ↔-↙↩-↪⌚-⌛⌨⏏⏩-⏳⏸-⏺Ⓜ▪-▫▶◀◻-◾☀-☄☎☑☔-☕☘☝☠☢-☣☦☪☮-☯☸-☺♀♂♈-♓♟-♠♣♥-♦♨♻♾-♿⚒-⚗⚙⚛-⚜⚠-⚡⚧⚪-⚫⚰-⚱⚽-⚾⛄-⛅⛈⛎-⛏⛑⛓-⛔⛩-⛪⛰-⛵⛷-⛺⛽✂✅✈-✍✏✒✔✖✝✡✨✳-✴❄❇❌❎❓-❕❗❣-❤➕-➗➡➰➿⤴-⤵⬅-⬇⬛-⬜⭐⭕〰〽㊗㊙🀄🃏🅰-🅱🅾-🅿🆎🆑-🆚🈁-🈂🈚🈯🈲-🈺🉐-🉑🌀-🌡🌤-🎓🎖-🎗🎙-🎛🎞-🏰🏳-🏵🏷-🏺🐀-📽📿-🔽🕉-🕎🕐-🕧🕯-🕰🕳-🕺🖇🖊-🖍🖐🖕-🖖🖤-🖥🖨🖱-🖲🖼🗂-🗄🗑-🗓🗜-🗞🗡🗣🗨🗯🗳🗺-🙏🚀-🛅🛋-🛒🛕-🛗🛝-🛥🛩🛫-🛬🛰🛳-🛼🟠-🟫🟰🤌-🤺🤼-🥅🥇-🧿🩰-🩴🩸-🩼🪀-🪆🪐-🪬🪰-🪺🫀-🫅🫐-🫙🫠-🫧🫰-🫶🇦-🇿#️⃣*️⃣0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣]","")
I believe this regex will find all current emojis from your text.
Notes:
some emojis are compound for instance, an astronaut is 🧑🏼‍🚀. Regex needs to find only solid chars, so all compound emojis will be included.
I've tried to shorten the solution, and used actual emojis in RegEx. You may also see more "computer-like" solutions: [\u1F60-\u1F64]|[\u2702-\u27B0].... Those solutions use codes of emojis instead.
Another interesting option is given here. Remove all not printable chars: =REGEXREPLACE(A1,"[[:print:]]","")
skins are included:
please see my study here: Emojis-Lab.gsheet
Assuming all your text strings are single words followed by a space and then an Emoji, you can use the formula
=LEFT(A1,(FIND(" ",A1,1)-1))
This will return the textual contents of a cell only (A1 in this example).
If all your data is in a single column, you can just pull down and this will apply to all your data.

Google html service to sheets

I'm not a big fan of google forms so I made the form for my user input in the html service. I found a way to push the data out of the form and into google sheets using all of my variables in the html file like this:
<textarea type="text" name="Special Instructions" id="instructions"></textarea>
...
var instructions = document.getElementById("instructions").value;
...
google.script.run
.formsubmit (instructions,...)
google.script.host.close()}
in combination with the following in the code file:
function formsubmit(instructions,...)
var ss = SpreadsheetApp.getActive().getSheetByName("Sheet1");
ss.getRange(ss.getLastRow(),7,1,1).setValue(instructions);
...
The problem is, not only is the code very slow to output results, but if I have more than 37 or so variables defined, it glitches out and rather than closing the dialog box and recording the values in a spreadsheet, it opens a blank web page.
I know there has to be better (and more efficient) way, but I'm afraid I don't know it.
On the "client side", put all of your variables into a JSON object or an array, the stringify it, and send that string to the server.
var objectOfData;
variableOne = "one";
variable2 = "two";
objectOfData = {};
objectOfData['varOne'] = variableOne;//Create a new element in the object
objectOfData['var2'] = variable2;//key name is in the brackets
objectOfData = JSON.stringify(objectOfData);//Convert object to string
google.script.run
.formsubmit(objectOfData);
And then convert the object as a string back to a real object:
function formsubmit(o) {
var arrayOfValues,k,myData,outerArray;
myData = JSON.parse(o);//Convert string back to object
var ss = SpreadsheetApp.getActive().getSheetByName("Sheet1");
arrayOfValues = [];
for (k in myData) {//Loop through every property in the object
thisValue = myData[k];//
Logger.log('thisValue: ' + thisValue);//VIEW the LOGS to see print out
arrayOfValues.push(thisValue);
}
outerArray = [];
outerArray.push(arrayOfValues);
ss.getRange(ss.getLastRow() + 1,7,1,arrayOfValues.length).setValue(outerArray);
...
Note that the last parameter of getRange('start row', start column, number of rows, number of columns) uses the length of the inner array named arrayOfValues. This insures that the parameter value will always be correct regardless of how the array is constructed.

Apps Script Utilities.parseCsv assumes new row on line breaks within double quotes

When using Utilities.parseCsv() linebreaks encased inside double quotes are assumed to be new rows entirely. The output array from this function will have several incorrect rows.
How can I fix this, or work around it?
Edit: Specifically, can I escape line breaks that exist only within double quotes? ie.
/r/n "I have some stuff to do:/r/n Go home/r/n Take a Nap"/r/n
Would be escaped to:
/r/n "I have some stuff to do://r//n Go home//r//n Take a Nap"/r/n
Edit2: Bug report from 2012: https://code.google.com/p/google-apps-script-issues/issues/detail?id=1871
So I had a somewhat large csv file about 10MB 50k rows, which contained a field at the end of each row with comments that users enter with all sorts of characters inside. I found the proposed regex solution was working when I tested a small set of the rows, but when I threw the big file to it, there was an error again and after trying a few things with the regex I even got to crash the whole runtime.
BTW I'm running my code on the V8 runtime.
After scratching my head for about an hour and with not really helpful error messages from AppsSript runtime. I had an idea, what if some weird users where deciding to use back-slashes in some weird ways making some escapes go wrong.
So I tried replacing all back-slashes in my data with something else for a while until I had the array that parseCsv() returns.
It worked!
My hypothesis is that having a \ at the end of lines was breaking the replacement.
So my final solution is:
function testParse() {
let csv =
'"title1","title2","title3"\r\n' +
'1,"person1","A ""comment"" with a \\ and \\\r\n a second line"\r\n' +
'2,"person2","Another comment"';
let sanitizedString =
csv.replace(/\\/g, '::back-slash::')
.replace(/(?=["'])(?:"[^"\\]*(?:\\[\s\S][^"\\]*)*"|'[^'\\]\r?\n(?:\\[\s\S][^'\\]\r?\n)*')/g,
match => match.replace(/\r?\n/g, "::newline::"));
let arr = Utilities.parseCsv(sanitizedString);
for (let i = 0, rows = arr.length; i < rows; i++) {
for (let j = 0, cols = arr[i].length; j < cols; j++) {
arr[i][j] =
arr[i][j].replace(/::back-slash::/g,'\\')
.replace(/::newline::/g,'\r\n');
}
}
Logger.log(arr)
}
Output:
[20-02-18 11:29:03:980 CST] [[title1, title2, title3], [1, person1, A "comment" with a \ and \
a second line], [2, person2, Another comment]]
It may be helpful for you to use Sheets API.
In my case, it works fine without replacing the CSV text that contains double-quoted multi-line text.
First, you need to make sure of bellow:
Enabling advanced services
To use an advanced Google service, follow these instructions:
In the script editor, select Resources > Advanced Google services....
In the Advanced Google Service dialog that appears,
click the on/off switch next to the service you want to use.
Click OK in the dialog.
If it is ok, you can import a CSV text data into a sheet with:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('some_name');
const resource = {
requests: [
{
pasteData: {
data: csvText, // Your CSV data string
coordinate: {sheetId: sheet.getSheetId()},
delimiter: ",",
}
}
]
};
Sheets.Spreadsheets.batchUpdate(resource, ss.getId());
or for TypeScript, which can be used by clasp:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('some_name');
const resource: GoogleAppsScript.Sheets.Schema.BatchUpdateSpreadsheetRequest = {
requests: [
{
pasteData: {
data: csvText, // Your CSV data string
coordinate: {sheetId: sheet.getSheetId()},
delimiter: ",",
}
}
]
};
Sheets.Spreadsheets.batchUpdate(resource, ss.getId());
I had this same problem and have finally figured it out. Thanks Douglas for the Regex/code (a bit over my head I must say) it matches up nicely to the field in question. Unfortunately, that is only half the battle. The replace shown will simply replaces the entire field with \r\n. So that only works when whatever is between the "" in the CSV file is only \r\n. If it is embedded in the field with other data it silently destroys that data. To solve the other half of the problem, you need to use a function as your replace. The replace takes the matching field as a parameter so so you can execute a simple replace call in the function to address just that field. Example...
Data:
"Student","Officer
RD
Special Member","Member",705,"2016-07-25 22:40:04 EDT"
Code to process:
var dataString = myBlob().getDataAsString();
var escapedString = dataString.replace(/(?=["'])(?:"[^"\](?:\[\s\S][^"\])"|'[^'\]\r\n(?:\[\s\S][^'\]\r\n)')/g, function(match) { return match.replace(/\r\n/g,"\r\n")} );
var csvData = Utilities.parseCsv(escapedString);
Now the "Officer\r\nRD\r\nSpecial Member" field gets evaluated individually so the match.replace call in the replace function can be very straight forward and simple.
To avoid trying to understand regular expressions, I found a workaround below, not using Utilities.parseCsv(). I'm copying the data line by line.
Here is how it goes:
If you can find a way to add an extra column to the end of your CSV, that contains the exact same value all the time, then you can force a specific "line break separator" according to that value.
Then, you copy the whole line into column A and use google app script' dedicated splitTextToColumns() method...
In the example below, I'm getting the CSV from an HTML form. This works because I also have admin access to the database the user takes the CSV from, so I could force that last column on all CSV files...
function updateSheet(form) {
var fileData = form.myFile;
// gets value from form
blob = fileData.getBlob();
var name = String(form.folderId);
// gets value from form
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.setActiveSheet(ss.getSheetByName(name), true);
sheet.clearContents().clearFormats();
var values = [];
// below, the "Dronix" value is the value that I could force at the end of each row
var rows = blob.contents.split('"Dronix",\n');
if (rows.length > 1) {
for (var r = 2, max_r = rows.length; r < max_r; ++r) {
sheet.getRange(r + 6, 1, 1, 1).setValue(String(rows[r]));
}
}
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange("A:A").activate();
spreadsheet.getRange("A:A").splitTextToColumns();
}
Retrieved and slightly modified a regex from another reply on another post: https://stackoverflow.com/a/29452781/3547347
Regex: (?=["'])(?:"[^"\\]*(?:\\[\s\S][^"\\]*)*"|'[^'\\]\r\n(?:\\[\s\S][^'\\]\r\n)*')
Code:
var dataString = myBlob.getDataAsString();
var escapedString = dataString.replace(/(?=["'])(?:"[^"\\]*(?:\\[\s\S][^"\\]*)*"|'[^'\\]\r\n(?:\\[\s\S][^'\\]\r\n)*')/g, '\\r\\n');

How do you change formatting within a google doc for multiple occurrences using findText()?

I am trying to find text within a google doc and replace with a subscript notation - replace "a3" with a3 but with the 3 now formatted as a subscript.
based on the answer here
I wrote some code that is working but only replaces the 1st instance of any occurrence (some are repeated).
I wrote the following:
for (var k=0; k<subscriptsReplace.length; k++) {
subscript = ' a'+subscriptsReplace[k];
find = ' a'+subscriptsReplace[k]+' ';
Logger.log(find)
var element = body.findText(find);
if(element){ // if found a match
var start = element.getStartOffset();
var text = element.getElement().asText();
text.replaceText(find, subscript);
text.setTextAlignment(start+2, start+2, DocumentApp.TextAlignment.SUBSCRIPT);
Logger.log("found one");
} // else do nothing
}
note that subscriptsReplace is an array that contains all the numbers of the subscripts throughout the document.
I cannot figure out why it's not getting the repeats, by looking at the logs, I know that it's not running the conditional on the repeats - so it's not re-replacing the same subscript it already replaced.
can someone see what's going on?
THank you!
Ultimately the issue was that using replaceText() was replacing all the occurences of the text throughout the document and therefor, it wasn't available to find and replace the formatting after the 1st iteration.
Here's the code that replaced all occurences:
for (var k=0; k<subscriptsReplace.length; k++) {
find = 'a'+subscriptsReplace[k]+'_';
var element = body.findText(find);
if(element){ // if found a match
var start = element.getStartOffset();
var text = element.getElement().asText();
text.setTextAlignment(start+1, start+1, DocumentApp.TextAlignment.SUBSCRIPT);
text.deleteText(start+2, start+2);
} // else do nothing
}
you'll see that rather than replacing, I added a special character "_" as a marker to find and then used deleteText() to get rid of them 1 at a time as I reformatted into subscripts
You can replace everything in the entire body with this:
function testReplace() {
var docBody = DocumentApp.getActiveDocument().getBody();
docBody.replaceText(searchPattern, replacement);
};
Google Documentation - Replace Text