I have a document consisting of five different worksheets. These sheets contain tables of five columns and in excess of 400 rows each. I am trying to replace specific text strings found in these tables with other text strings. To this end, I've created another sheet with another table ($replacement-table), containing the strings I want to replace in one column and the strings I want to replace them with in another column. The number of strings to be replaced in excess of 500, and I'm not certain that they all crop up somewhere in the other sheets.
While I could do it manually, I'd rather come up with a way to automate it. Therefore, I'm looking for a script that looks up whether the value in A2 of $replacement-table shows up anywhere else in the document, and if so, replaces it with the value found in B2 of $replacement-table. The same goes for A3 and B3, A4 and B4, and so on and so forth.
Sadly, I don't even have a starting point, as I'm not experienced in either the syntax or the functions required to tell Google Sheets what I want it to do. Any ideas (or even obvious/plug-and-play solutions?) on how to achieve my goal of replacing a wide selection of strings found in five different tables with the corresponding strings arranged in a certain table?
Here's a simple example that might help.
function replMyText(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('replacements');
var rgtxt=sh.getRange('A1:A7');//text to replace
var rgrep=sh.getRange('D1:E5');//replacement table
var txtA=rgtxt.getValues();
var repA=rgrep.getValues();
for(var i=0;i<txtA.length;i++){
for(var j=0;j<repA.length;j++){
if(txtA[i][0]==repA[j][0]){
txtA[i][0]=repA[j][1];
}
}
}
rgtxt.setValues(txtA);
}
Here's an image of my test sheet:
Related
I am attempting to create documentation from an export of data that gives me a jumbled mess all in one cell that I need to clean up and extract certain bits from.
Here is an example:
[{"label":"Native Invoice","value":"native_invoice","displayOrder":0,"hidden":false,"readOnly":false},{"label":"Data Sync","value":"data_sync","displayOrder":1,"hidden":false,"readOnly":false}]
All of this is in one cell, and I need to have only the following information in their own individual rows:
Native Invoice
Data Sync
This example only has 2 values, but some that I am working on have hundreds, and it is taking far too long to manually copy and paste the values I need into their own cells.
Note: I am working in Google Sheets exclusively.
If I'm understanding you correctly, you want to pull anything after "label": without quotes. If that's the case, and if you are open to a formula instead of a script, supposing that your raw-data block were in A1, place this in B1:
=ArrayFormula(IFERROR(QUERY(FLATTEN(REGEXREPLACE(IF(NOT(REGEXMATCH(SPLIT(REGEXREPLACE(A1,"label.:.([^"&CHAR(34)&"]+)","~|$1~"),"~"),"\|")),,SPLIT(REGEXREPLACE(A1,"label.:.([^"&CHAR(34)&"]+)","~|$1~"),"~")),"\|","")),"WHERE Col1 Is Not Null")))
Here is how a custom function can look like:
function parse(txt) {
var jsn = JSON.parse(txt);
return [jsn[0].label, jsn[1].label];
}
Here is how it works:
You put the data into cell A1, put the formula =parse(A1) into the cell B1, and get the results in cells B1 and B2.
Update
If you want to get labels from all objects of the data, here is another variant of the function:
function get_labels(txt) {
return JSON.parse(txt).map(x => x.label); // get 'label' from all the objects
}
It works about the same way:
I had a working Excel spreadsheet that used indirect() in the data validation and it worked fine. I uploaded it to sheets and converted it, now the indirect does not work.
I have found a link on the support forum that explains it does not work in Chrome but appears to work in Firefox, and the answers and workarounds seem to be for generating a secondary list... which is what I want, but in a data validation across a row.
I have knocked up a simple test sheet, hopefully public and the script editor is visible:
https://docs.google.com/spreadsheets/d/1KUgrdXKIKlk1DWvDOX9cY3B2VnRH_5h_vKuZJlqUlN8/edit?usp=sharing
Hopefully you can see what I'm after. I want the validation in C8 to be the list of items in the category based in B8; C9 based on B9 etc.
EDIT and Update
The question is about a replacement to indirect() in a data validation rule. While I did find a way round this by using indirect(), I preferred the version mentioned by Desire (to whom I have attributed the answer), but I thought I'd document my solution in case the sheet above becomes unavailable, or you cannot access it, or you just wanted a bit more detail.
So, for My Demo I have this:
In A1:C5 are my lists of data with the titles.
In the range B8:B12 I applied a data validation rule of value in range of A1:C1 - this gives the first dropdown.
In Cell E8 I put the formula =transpose(filter($A$2:$C$5, $A$1:$C$1 = B8)) and then copied this down to E12
Finally I put the following in a function and ran it in the script editor.
function runMeOnce() {
var dst = SpreadsheetApp.getActive().getSheetByName('Sheet1').getRange('C8:C12');
var rules = [];
for (var i = 8; i < 13; i++) {
var src = SpreadsheetApp.getActive().getSheetByName('Sheet1').getRange("E" + i + ":H" + i);
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(src).build();
rules.push(rule);
}
dst.setDataValidations(rules);
}
That's all there is, no more onEdit() triggering.
NOTE There is one downside I bumped into with this method though. I have this in place for 6000+ rows in my actual spreadsheet, and across multiple sheets, with some dropdowns having 50-100 items in. This solution seriously eats into the (current) 2 million cell limit.
Hope this helps someone.
Data Validation rule of the type "List of items" takes only a comma-separated list of values as its parameter, and does not evaluate any formulas you try to put there. It does not matter what the function returns, because it will not be called. If you put, say "=sqrt(A10)" in the field "List of items", that only means that the validation rule will require the string "=sqrt(A10)" to be entered in the cell.
Similarly with "List from a Range". Either what you enter parses as range notation, or it does not. The string "=getValidationRange(B8)" does not parse as range notation, hence the error. The function is never called.
The only type of validation that calls a function is "Custom formula". If you use it, then the validation can be performed as intended: for example,
=match(C8, filter(A2:C5, A1:C1 = B8), 0)
requires the content of C8 to be in the column of the table A2:C5 under the heading that matches the category in B8. However, with a custom formula you do not get a dropdown in a cell.
To get a dynamic dropdown, one can either
Use an auxiliary range
For example, enter filter(A2:C5, A1:C1 = B8) in cell F1, so that the F column is for the categories currently selected. The data validation would be "List from a Range", F1:F. This is a fine workaround for one validation rule, but takes more work when you have multiple ones.
Use a triggered script
Use a script that is triggered on edit and sets data validation rules accordingly; this is discussed in How do you do dynamic / dependent drop downs in Google Sheets? among other places.
Based on the sacrificing a goat issue, I did find a simple(ish) way around the problem that still uses indirect().
Set up the named ranges as previously using the titles in CamelCase. In my example I have CatA, CatB, and CatC - i.e. the white space needs removing.
At the end of a row (or in another sheet) transpose the chosen named range (in cell E8: =transpose(indirect(substitute(B8, " ", ""))) copy this down as far as you need.
At this point it's good to note that because we are unsing builtin functions, the speed is so much better, as can be seen by my example.
Now the painful bit. For each subcategory cell (C8, C9 etc in my example), you need to add the validation independently as a range of E8:ZZ8 (obviously ZZ8 needs reigning in a bit) and E9:ZZ9 etc. It doesn't seem to do referential so if you select all the cell in the column, they all only look at the data you specifically type in the box... I might just not have worked out R1C1 notation here, however. I tried.
This can be scripted on GAS to create the R1C1 validation function and then apply it to the range.
I'm trying to develop an interactive spreadsheet that creates a narrative for a budget document. There will be a variety of options. Once the user selects an item, it will help them calculate the total. I want to setup option boxes that they fill in. For example, four cells will be allowed for input B1:B4. I am going to name each of the four cells (i.e. A, B, C, D). In the reference document I want to write various formulas. In some cases I might need "(A+B)*C" in another I might need "A * B * C" or "(A+B+C)/D" ... The spreadsheet would lookup the text formula, and I want to then convert it. So in the case of the lookup finding "(A+B)*C" I want it to convert it to =(indirect(A)+indirect(B))*indirect(C) which would then get the values from A (which is B1), B (which is B2) and so on.
Essentially, I would like to use or create something that is exactly the opposite of Excel's FORMULATEXT() function. I would prefer to do this in Google Sheets but I am willing to use Excel if I need to. I have a very basic understanding of both Google's scripting and VBA, and am willing to create if necessary, but not even sure how to tackle it.
Suggestions?
I found a way to do it in Google Apps Script:
function reCalc() {
var sheet = SpreadsheetApp.getActiveSheet();
var src = sheet.getRange("J26"); // The cell which holds the formula
var str = src.getValue();
str = str.replace('A', 'indirect("OPTA")').replace('B', 'indirect("OPTB")').replace('C', 'indirect("OPTC")').replace('D', 'indirect("OPTD")').replace('ENR', 'indirect("ENR")');
var cell = sheet.getRange("J30"); // The cell where I want the results to be
cell.setFormula(str); // Setting the formula.
}
Thank you to SpiderPig for giving me the idea!
I've been working on a script (With some help from folk on SO). I have managed to create a script that imports a csv file and updates named ranges based on the imported data.
Here is an image of what I'm trying to do:
At this point I have imported data which has been populated into columns A:G. The cells in columns H:L are formula and are based on the data in A:G. Because I have imported data the data are now longer than the formula range. If not relying on script I would just double click the small blue square at the bottom right of the highlighted cells and the formulas would copy down. This is what I'd like to have the script do once I have imported the data.
I created a range called "formula_range" which automatically updates with the length of data. "formula_range" starts in cell H3 and ends in L:N where N is the length of rows in the sheet.
"formula_range" therefore contains some populated cells with formula and then blank rows all the way to the bottom of the sheet.
I saw this SO post. So in English, my line of thinking is:
Create a variable formula_range H3:L3 as a range
paste formula_range to every row in formula_range
Voila?
How do I get the variable formula_range if the first row in formula_range will always have the formula to be copied down. Put another way it should be fixed at H3:L3.
I tried this:
var copy_range = ss.getRangeByName("formula_range").getRange(1,5,1);
SpreadsheetApp.getUi().alert(copy_range);
My alert said "undefined" (I really wasn't sure what it would show.)
My line of thinking is not the most efficient since I am going to copy over existing formula. Perhaps there is a better way?
How do I get the first row in a named range formula_range[0]; ?
How can I use script to copy down formula in formula_range?
I think what you're trying to accomplish could be achieved more efficiently with a built-in arrayformula:
https://support.google.com/docs/answer/3093275
Place in H3:
=ARRAYFORMULA(DATE(A3:A,B3:B,1))
for each of your formulas in H through L in row 3 surround them with array formula and extend the arguments with open-ended ranges starting with row 3 as in the above example
We're migrating a lot of our business logic to scripts behind the scenes, but I'm worried that they'll be much more fragile when columns move.
On Sheet Updates Automagically
For example, If I have a formula on a spreadsheet like this:
=If(A1=5,"Yes","No")
And then I Insert 1 Column Left of A, the formula will be automatically updated like this:
=If(B1=5,"Yes","No")
Apps scripts doesn't update
For example, if I have the formula in the script section:
function myFunction() {
var value = SpreadsheetApp.getActiveSheet().getRange("A1").getValue();
var output = (value == 5) ? 'Yes' : 'No';
Logger.log(output);
}
It will not update when the sheet changes.
Q: How can I get stable references in the code behind for columns that could potentially move?
This is a general problem when hardcoding strings or numbers in code.
In general the javascript parser can't tell which strings might be used on a sheet function call. Its sometimes not trivial to solve.
Two approaches are:
If the columns/cells/ranges are known beforehand, use named ranges:
Define a named range and use NamedRange in code. Use the range to directly write to it or query its row/column position.
Another for column based ranges like yours is that your code does this naming manually by using the column header as the column names. Code uses those names and reads the header to build the mapping.