Google-Spreadsheet Scripts - javascript

I have a column of cells in a particular sheet of Google Spreadsheet document.
This column references multiple values in another sheet using the built-in JOIN command:
=JOIN(", ",Regular!B3,Regular!B9,Regular!B10,Regular!B11,Regular!B12,Regular!B13,Regular!B14)
typical output for each such cell is a list of integers that are comma-separated, f.ex:
2, 5, 10, 12, 13
Some cells use ranges like this:
=JOIN(", ",Regular!B3:B9)
I want to lock these cells in the formula as such: Regular!$B$3,Regular!$B:$9...
Right now I want each reference to lock both column and row, but a solution that lets me pick row, column or both is a better solution.
1) I haven't found a way to do this without using a custom script - have I missed something?
2) My custom script solution is unfinished:
function eachCellInRange(range, op) {
var numRows = range.getNumRows();
var numCols = range.getNumColumns();
for (var i = 1; i <= numRows; i++) {
for (var j = 1; j <= numCols; j++) {
op(range.getCell(i,j), i, j);
}
}
};
function lockCell(cell, row, col) {
var formula = cell.getFormula();
if(formula) {
var startIdx = formula.indexOf('(');
if(startIdx > 0) {
//!! REGEX HERE !! //
cell.setValue(formula);
}
}
}
function lockRows() {
var range = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getActiveRange();
eachCellInRange(range, lockCell);
};
I need to make a regex pattern that will identify the B3, B9... parts of the formula and change them to $B$3, $B$9... but also not break in the B1:B8 case
Currently all references are prefixed with SheetName! (e.g. Regular!B9:B20), in the future some may not be, so the most general solution is preferred.

I'm not sure whether this is what you're looking for but I would replace the little bit you currently have:
if(formula) {
var startIdx = formula.indexOf('(');
if(startIdx > 0) {
//!! REGEX HERE !! //
cell.setValue(formula);
}
}
by
if(formula.substring(0,6) == "=JOIN(") {
formula = formula.replace(/([A-Z]+(?=[0-9]))/g, function($1) {
return "$" +$1 + "$";
});
alert(formula);
// cell.setValue(formula);
}
Which ensures that the formula is a JOIN formula.
Also, I'm not that familiar with JS, but I put it in JSFiddle to see how it goes.
Warning: This will fail if your sheet names have alphanumeric characters (mix of letters and digits).

Using #Jerry's useful answer, I was able to suit it to my needs:
function eachCellInRange(range, op) {
var numRows = range.getNumRows();
var numCols = range.getNumColumns();
for (var i = 1; i <= numRows; i++) {
for (var j = 1; j <= numCols; j++) {
op(range.getCell(i,j), i, j);
}
}
};
var lockOn = 1, lockOff = -1, lockNop = 0,
lockChar = '$', lockEmpty = '';
function lock2char(newLock, curLock) {
if(newLock == lockNop) newLock = curLock;
return (newLock > lockNop) ? lockChar : lockEmpty;
}
function bool2lock(boolValue) {
return (boolValue) ? lockOn : lockOff;
}
function lockCell(lockCol, lockRow, cell, row, col) {
var formula = cell.getFormula();
if(formula) {
var startIdx = formula.indexOf('(');
if(startIdx > 0) {
var newFormula = formula.replace(/([A-Z|\$]+(?=[0-9]))/g, function(part) {
var prefix = lock2char(lockCol, (part.charAt(0) == lockChar));
var suffix = lock2char(lockRow, (part.charAt(part.length -1) == lockChar));
part = part.replace(/\$/g, '');
return prefix + part + suffix;
});
cell.setFormula(newFormula);
}
}
}
function lockRows() {
var range = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getActiveRange();
eachCellInRange(range, lockCell.bind(this, lockOff, lockOn));
};

Related

Pulling data from an API using Google App Script

I'm hoping someone could help as I've spent the last day trying to figure out where I'm going wrong.. to no avail.
I'm trying to pull some basic information from https://nvd.nist.gov/ using their API (https://services.nvd.nist.gov/rest/json/cve/1.0/cve-id). I need to pull the data for cve-id's. cve-id's are in column A.
Example cve-id: https://services.nvd.nist.gov/rest/json/cve/1.0/CVE-2019-9763
The only information I want is the description, cvssV3baseScore, cvss3vectorString. Very basic you might think but I'm lost after doing tons of research.
I've tried doing this using the following code in Google Apps Script:
function fetchData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("CVEs");
var sheetData = sheet.getDataRange().getValues();
var i, len = sheetData.length, row = [];
for (i = 1; i < len; i++) {
if (sheetData[i][0] == "" || sheetData[i][15] != "")
continue;
// sheetData[i][8] - here 8 represents column I as column A = 0. Column 23 = x, this is where sheet ends (at new Date())
let url = 'https://services.nvd.nist.gov/rest/json/cve/1.0/' + sheetData[i][0];
try {
var id = json.result.CVE_items.cve.CVE_data_meta.ID;
var idStr = '';
var response = UrlFetchApp.fetch(url).getContentText();
row.push([
idStr,
json.result.CVE_items.cve.CVE_data_meta.ID,
json.result.CVE_items.cve.description.description_data.value,
json.result.CVE_Items.impact.baseMetricV3.cvssV3.vectorString,
json.result.CVE_Items.impact.baseMetricV3.cvssV3.baseScore,
]);
//Here (middle number) 10 number denotes the exact column number from where we need to write data. 10 = J
sheet.getRange(i + 1, 2, 1, row[0].length).setValues(row);
}
catch (e) {
continue;
}
}
}
function lookupByNthValue(search_key, sourceColumn, targetColumn, n) {
if(arguments.length < 4){
throw new Error( "Only " + arguments.length + " arguments provided. Requires 4." );
}
var count = 1;
for(var i = 0; i < sourceColumn.length; i++){
if(sourceColumn[i] != search_key){
continue;
}
if(count == n){
return targetColumn[i];
}
count++;
}
}
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("Actions")
.addItem("Fetch Data", 'fetchData')
.addToUi();
}
I would much appreciate if someone could help, link to the Google sheet: https://docs.google.com/spreadsheets/d/18ilCE1udUM87c4YtxXAB52Fm3mEzDk8UggcH96S3JLA/edit?usp=sharing
Thank you in advance.
There are a few problems: len equals 1000 because you have data on row 1000! therefore the script will be too long. You write CVE_items instead of CVE_Items with a capital letter! CVE_Items is not a parent, it is an array, even with a single occurrence. You never parse the response of url fetch. Try this
function fetchData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("CVEs");
var sheetData = sheet.getDataRange().getValues();
var i, len = sheet.getLastRow(), row = [];
Logger.log(len)
for (i = 1; i < len; i++) {
try{
let url = 'https://services.nvd.nist.gov/rest/json/cve/1.0/' + sheetData[i][0];
var json = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var row=[]
row.push([
json.result.CVE_Items[0].cve.CVE_data_meta.ID,
json.result.CVE_Items[0].cve.CVE_data_meta.ID,
json.result.CVE_Items[0].cve.description.description_data.value,
json.result.CVE_Items[0].impact.baseMetricV3.cvssV3.vectorString,
json.result.CVE_Items[0].impact.baseMetricV3.cvssV3.baseScore,
]);
Logger.log(row)
sheet.getRange(i + 1, 2, 1, row[0].length).setValues(row);
}catch(e){}
}
}

Perform a merge on two strings

I'm trying to build a collaborative doc editor and implement operational transformation. Imagine we have a string that is manipulated simultaneously by 2 users. They can only add characters, not remove them. We want to incorporate both of their changes.
The original string is: catspider
The first user does this: cat<span id>spider</span>
The second user does this: c<span id>atspi</span>der
I'm trying to write a function that will produce: c<span id>at<span id>spi</span>der</span>
The function I've written is close, but it produces c<span id>at<span i</span>d>spider</span> codepen here
String.prototype.splice = function(start, newSubStr) {
return this.slice(0, start) + newSubStr + this.slice(start);
};
function merge(saved, working, requested) {
if (!saved || !working || !requested) {
return false;
}
var diffSavedWorking = createDiff(working, saved);
var diffRequestedWorking = createDiff(working, requested);
var newStr = working;
for (var i = 0; i < Math.max(diffRequestedWorking.length, diffSavedWorking.length); i++) {
//splice does an insert `before` -- we will assume that the saved document characters
//should always appear before the requested document characters in this merger operation
//so we first insert requested and then saved, which means that the final string will have the
//original characters first.
if (diffRequestedWorking[i]) {
newStr = newStr.splice(i, diffRequestedWorking[i]);
//we need to update the merge arrays by the number of
//inserted characters.
var length = diffRequestedWorking[i].length;
insertNatX(diffSavedWorking, length, i + 1);
insertNatX(diffRequestedWorking, length, i + 1);
}
if (diffSavedWorking[i]) {
newStr = newStr.splice(i, diffSavedWorking[i]);
//we need to update the merge arrays by the number of
//inserted characters.
var length = diffSavedWorking[i].length;
insertNatX(diffSavedWorking, length, i + 1);
insertNatX(diffRequestedWorking, length, i + 1);
}
}
return newStr;
}
//arr1 should be the shorter array.
//returns inserted characters at their
//insertion index.
function createDiff(arr1, arr2) {
var diff = [];
var j = 0;
for (var i = 0; i < arr1.length; i++) {
diff[i] = "";
while (arr2[j] !== arr1[i]) {
diff[i] += arr2[j];
j++;
}
j++;
}
var remainder = arr2.substr(j);
if (remainder) diff[i] = remainder;
return diff;
}
function insertNatX(arr, length, pos) {
for (var j = 0; j < length; j++) {
arr.splice(pos, 0, "");
}
}
var saved = 'cat<span id>spider</span>';
var working = 'catspider';
var requested = 'c<span id>atspi</span>der';
console.log(merge(saved, working, requested));
Would appreciate any thoughts on a better / simpler way to achieve this.

Define custom function and loop over range in Google Apps Script

I'm trying to create a custom function in Google Apps Script that takes on a range (in my case a single row e.g. Sheet1!A2:E2 containing strings), lower-case all strings and modifies them.
1 row of output should contain strings in lower case
2 row of output should contain strings with a suffix e.g. ".info" added
3 row of output should contain stings with a prefix and suffix e.g. "http://" + string + ".info"
…
My approach was the below, however doesn't work:
/**
* #customfunction
*/
function myFunction (range) {
var result = [];
for (var i = 0; i < range[0].length; i++) {
if ( range[i] == "") {
break;
} else {
for (var j = 0; j < 4; j++) {
result.push(range[i]);
}
}
}
return result;
};
Screenshot
I'm not quite understand English, but you may need to
function myFunction(range) {
if (range.length > 1) return 'One row required';
var result = [
[],
[],
[]
];
for (var i = 0; i < range[0].length; i++) {
if (range[0][i] && range[0][i].toLowerCase) {
var str = range[0][i].toLowerCase();
result[0][i] = str;
result[1][i] = str + '.info';
result[2][i] = 'https://' + str + '.info';
}
}
return result;
};

"Look and say sequence" in javascript

1
11
12
1121
122111
112213
122211
....
I was trying to solve this problem. It goes like this.
I need to check the former line and write: the number and how many time it was repeated.
ex. 1 -> 1(number)1(time)
var antsArr = [[1]];
var n = 10;
for (var row = 1; row < n; row++) {
var lastCheckedNumber = 0;
var count = 1;
antsArr[row] = [];
for (var col = 0; col < antsArr[row-1].length; col++) {
if (lastCheckedNumber == 0) {
lastCheckedNumber = 1;
antsArr[row].push(lastCheckedNumber);
} else {
if (antsArr[row-1][col] == lastCheckedNumber) {
count++;
} else {
lastCheckedNumber = antsArr[row-1][col];
}
}
}
antsArr[row].push(count);
antsArr[row].push(lastCheckedNumber);
}
for (var i = 0; i < antsArr.length; i++) {
console.log(antsArr[i]);
}
I have been on this since 2 days ago.
It it so hard to solve by myself. I know it is really basic code to you guys.
But still if someone who has a really warm heart help me out, I will be so happy! :>
Try this:
JSFiddle Sample
function lookAndSay(seq){
var prev = seq[0];
var freq = 0;
var output = [];
seq.forEach(function(s){
if (s==prev){
freq++;
}
else{
output.push(prev);
output.push(freq);
prev = s;
freq = 1;
}
});
output.push(prev);
output.push(freq);
console.log(output);
return output;
}
// Sample: try on the first 11 sequences
var seq = [1];
for (var n=0; n<11; n++){
seq = lookAndSay(seq);
}
Quick explanation
The input sequence is a simple array containing all numbers in the sequence. The function iterates through the element in the sequence, count the frequency of the current occurring number. When it encounters a new number, it pushes the previously occurring number along with the frequency to the output.
Keep the iteration goes until it reaches the end, make sure the last occurring number and the frequency are added to the output and that's it.
I am not sure if this is right,as i didnt know about this sequence before.Please check and let me know if it works.
var hh=0;
function ls(j,j1)
{
var l1=j.length;
var fer=j.split('');
var str='';
var counter=1;
for(var t=0;t<fer.length;t++)
{
if(fer[t]==fer[t+1])
{
counter++;
}
else
{
str=str+""+""+fer[t]+counter;
counter=1;
}
}
console.log(str);
while(hh<5) //REPLACE THE NUMBER HERE TO CHANGE NUMBER OF COUNTS!
{
hh++;
//console.log(hh);
ls(str);
}
}
ls("1");
You can check out the working solution for in this fiddle here
You can solve this by splitting your logic into different modules.
So primarily you have 2 tasks -
For a give sequence of numbers(say [1,1,2]), you need to find the frequency distribution - something like - [1,2,2,1] which is the main logic.
Keep generating new distribution lists until a given number(say n).
So split them into 2 different functions and test them independently.
For task 1, code would look something like this -
/*
This takes an input [1,1,2] and return is freq - [1,2,2,1]
*/
function find_num_freq(arr){
var freq_list = [];
var val = arr[0];
var freq = 1;
for(i=1; i<arr.length; i++){
var curr_val = arr[i];
if(curr_val === val){
freq += 1;
}else{
//Add the values to the freq_list
freq_list.push([val, freq]);
val = curr_val;
freq = 1;
}
}
freq_list.push([val, freq]);
return freq_list;
}
For task 2, it keeps calling the above function for each line of results.
It's code would look something like this -
function look_n_say(n){
//Starting number
var seed = 1;
var antsArr = [[seed]];
for(var i = 0; i < n; i++){
var content = antsArr[i];
var freq_list = find_num_freq(content);
//freq_list give an array of [[ele, freq],[ele,freq]...]
//Flatten so that it's of the form - [ele,freq,ele,freq]
var freq_list_flat = flatten_list(freq_list);
antsArr.push(freq_list_flat);
}
return antsArr;
}
/**
This is used for flattening a list.
Eg - [[1],[1,1],[1,2]] => [1,1,1,1,2]
basically removes only first level nesting
**/
function flatten_list(li){
var flat_li = [];
li.forEach(
function(val){
for(ind in val){
flat_li.push(val[ind]);
}
}
);
return flat_li;
}
The output of this for the first 10 n values -
OUTPUT
n = 1:
[[1],[1,1]]
n = 2:
[[1],[1,1],[1,2]]
n = 3:
[[1],[1,1],[1,2],[1,1,2,1]]
n = 4:
[[1],[1,1],[1,2],[1,1,2,1],[1,2,2,1,1,1]]
n = 5:
[[1],[1,1],[1,2],[1,1,2,1],[1,2,2,1,1,1],[1,1,2,2,1,3]]
n = 6:
[[1],[1,1],[1,2],[1,1,2,1],[1,2,2,1,1,1],[1,1,2,2,1,3],[1,2,2,2,1,1,3,1]]
n = 7:
[[1],[1,1],[1,2],[1,1,2,1],[1,2,2,1,1,1],[1,1,2,2,1,3],[1,2,2,2,1,1,3,1],[1,1,2,3,1,2,3,1,1,1]]
n = 8:
[[1],[1,1],[1,2],[1,1,2,1],[1,2,2,1,1,1],[1,1,2,2,1,3],[1,2,2,2,1,1,3,1],[1,1,2,3,1,2,3,1,1,1],[1,2,2,1,3,1,1,1,2,1,3,1,1,3]]
n = 9:
[[1],[1,1],[1,2],[1,1,2,1],[1,2,2,1,1,1],[1,1,2,2,1,3],[1,2,2,2,1,1,3,1],[1,1,2,3,1,2,3,1,1,1],[1,2,2,1,3,1,1,1,2,1,3,1,1,3],[1,1,2,2,1,1,3,1,1,3,2,1,1,1,3,1,1,2,3,1]]

Multiple regex match in javascript

document.querySelectorAll("table[id^='Table']");
This code selects all tables with the id of Table. But i want to select tables with id of Table2 OR Table7 (or any other numbers). How to do this with regex?
Edit: jQuery is not applicable in my case.
function getTables(tableNumbers) { // Usage: getTables([2, 7]) will return the tables with the ID 'Table2' and 'Table7'. (You can add more numbers; [2,7,3,6])
var allTables = document.querySelectorAll("table[id^='Table']");
var tablesWeWant = [];
for (var i = 0; i < allTables.length; i++) {
if (allTables[i].id.match(/Table[0-9]/)) {
tablesWeWant.push(allTables[i]);
}
}
for (var i = 0; i < tablesWeWant.length; i++) {
if (!tableNumbers.contains(tablesWeWant[i].id.substr(id.length - 1))) {
tableNumbers.splice(i, 1);
}
}
return tablesWeWant;
}
This should return all tables with an ID matching the regex /Table[0-9]/ and ending with a digit contained in the variable tableNumbders.
DISCLAIMER: I'm not a regex expert.
EDIT:
After editing a few times the code above became a bit too long, so I rewrote it like this:
function getTables(tableNumbers) {
var tablesWeWant = [];
for (var i = 0; i < tableNumbers.length; i++) {
tablesWeWant.push(document.querySelector("#Table" + tableNumbers[i]));
}
return tablesWeWant;
}
The second approach works: http://jsfiddle.net/qQ7VT/1/

Categories

Resources