This is a Google Apps Script library including useful scripts for supporting to development of applications by Google Apps Script. In the current stage, the 3 categories "For array processing", "For binary processing", and "For string processing" are included in this library.
When I create applications using Google Apps Script, there are useful scripts for often use. At that time, I thought that when those scripts can be simply used, they will be useful not only to me but also to other users. From this motivation, I created a Google Apps Script library including those scripts. But, I have been using these useful scripts only in my development before.
I sometimes answer questions related to Google Apps Script at Stackoverflow. From my experience that I have answered those questions, I also added useful scripts, that I believe, in the library. Some of these scripts will be also the direct answer to the questions. If this was useful for your situation, I'm glad.
The policy of this library is not to use any scopes.
1idMI9-WtPMbYvbK5D7KH2_GWh62Dny9RG8NzjwjHI5whGIAPXEtTJmeC
In order to use this library, please install the library as follows.
-
Create a GAS project.
- You can use this library for the GAS project of both the standalone type and the container-bound script type.
-
- Library's project key is
1idMI9-WtPMbYvbK5D7KH2_GWh62Dny9RG8NzjwjHI5whGIAPXEtTJmeC
.
- Library's project key is
Methods | Description |
---|---|
is2DimensionalArray | When the inputted array is 2 dimensional array, true is returned. |
isUniform2DArray | When the inputted 2 dimensional array is the uniformed array, true is returned. |
uniform2DArray | Make all array in 2 dimensional array uniforming the same length. |
transpose | Transpose 2 dimensional array. |
splitArray | Split array every n length. |
getSpecificColumns | Retrieve the specific columns from 2 dimensional array. |
deleteSpecificColumns | Delete specific columns from 2 dimensional array. |
insertColumns | Insert columns to 2 dimensional array. |
removeDuplicatedValues | Remove duplicated values from 1 dimensional array. |
get1stEmptyRow | Retrieve empty row index. |
get1stEmptyColumn | Retrieve empty column index. |
sum | Sum numbers in an array. |
compilingNumbers | Compiling Continuous Numbers using Google Apps Script. |
convArrayToObject | Converting 2 dimensional array to JSON object. |
unpivot | Converting 2-dimensional array as unpivot (reverse pivot). |
reverseUnpivot | Reversing 2-dimensional array with unpivot. |
dotProduct | Calculate dot product from 2 arrays. |
cosineSimilarity | Calculate cosine similarity from 2 arrays. |
Methods | Description |
---|---|
convInt8ArrayToHexAr | Convert Int8Array to hex string array. |
convStrToHex | Convert string to hex. |
convHexToInt8Ar | Convert string to hex. |
convInt8ArToStr | Convert Int8Array to string value. |
convInt8ArToUint8Ar | Convert Int8Array to Uint8Array. |
convUint8ArToInt8Ar | Convert Uint8Array to Int8Array. |
searchIndexFromDataByData | Search index from base data using a search data. |
splitByteArrayBySearchData | Split byteArray by a search data. |
Methods | Description |
---|---|
ConvText | Converting text as unicode. |
columnLetterToIndex | Converting colum letter to column index. Start of column index is 0. |
columnIndexToLetter | Converting colum index to column letter. Start of column index is 0. |
convA1NotationToGridRange | Converting a1Notation to gridrange. This will be useful for using Sheets API. |
convGridRangeToA1Notation | Converting gridrange to a1Notation. This will be useful for using Sheets API. |
addQueryParameters | This method is used for adding the query parameters to the URL. |
parseQueryParameters | This method is used for parsing the URL including the query parameters. |
expandA1Notations | This method is used for expanding A1Notations. |
consolidateA1Notations | This method is used for consolidating the scattered A1Notations. |
blobToDataUrl | This method is used for converting Blob to the data URL. |
snake_caseToCamelCase | This method is used for converting a string of the snake case to the camel case. |
camelCaseTosnake_case | This method is used for converting a string of the camel case to the snake case. |
createFormDataObject | This method is used for creating the form data to HTTP request from an object. |
When you use this library with the script editor of Google Apps Script, you can see the document of each method by the autocompletion of the script editor. You can see the following demonstration.
When the inputted array is 2 dimensional array, true is returned.
/**
* ### Description
* When the inputted array is 2 dimensional array, true is returned.
*
* ### Sample script
* ```
* const array1 = ["", [1, 2, 3], [1, 2, 3], [1, 2, 3]];
* const res1 = UtlApp.is2DimensionalArray(array1); // false
*
* const array2 = [[1, 2, 3], [1, 2], [1]];
* const res2 = UtlApp.is2DimensionalArray(array2); // true
* ```
*
* @param {Array} array 2 dimensional array.
* @return {Boolean} When the inputted array is 2 dimensional array, true is returned.
*/
function is2DimensionalArray(array) {
return array.every((r) => Array.isArray(r));
}
When the inputted 2 dimensional array is the uniformed array, true is returned.
/**
* ### Description
* When the inputted 2 dimensional array is the uniformed array, true is returned.
*
* ### Sample script
* ```
* const array1 = [[1, 2, 3], [1, 2, 3], [1, 2, 3]];
* const res1 = UtlApp.isUniform2DArray(array1); // true
*
* const array2 = [[1, 2, 3], [1, 2], [1]];
* const res2 = UtlApp.isUniform2DArray(array2); // false
* ```
*
* @param {Array} array 2 dimensional array.
* @param {Boolean} check Check whether the inputted array is 2 dimensional array. Default is true.
* @return {Boolean} When the inputted 2 dimensional array is the uniformed array, true is returned.
*/
function isUniform2DArray(array, check = true) {
if (check && !is2DimensionalArray(array)) {
throw new Error("Please use 2 dimensional array.");
}
return new Set(array.map((r) => r.length)).size == 1 ? true : false;
}
Make all array in 2 dimensional array uniforming the same length.
/**
* ### Description
* Make all array in 2 dimensional array uniforming the same length.
*
* ### Sample script
* ```
* const array1 = [[1, 2, 3], [1, 2, 3], [1, 2, 3]];
* const res1 = UtlApp.uniform2DArray(array1);
* console.log(res1); // [ [ 1, 2, 3 ], [ 1, 2, 3 ], [ 1, 2, 3 ] ]
*
* const array2 = [[1, 2, 3], [1, 2], [1]];
* const res2 = UtlApp.uniform2DArray(array2);
* console.log(res2); // [ [ 1, 2, 3 ], [ 1, 2, null ], [ 1, null, null ] ]
* ```
*
* @param {Array} array 2 dimensional array.
* @param {*} empty Value used in the added element. Default is null.
* @param {Boolean} check Check whether the inputted array is 2 dimensional array. Default is true.
* @return {Array} Uniformed array.
*/
function uniform2DArray(array, empty = null, check = true) {
if (check && !is2DimensionalArray(array)) {
throw new Error("Please use 2 dimensional array.");
}
const maxLen = Math.max(...array.map((r) => r.length));
return array.map((r) => [...r, ...Array(maxLen - r.length).fill(empty)]);
}
Transpose 2 dimensional array.
/**
* ### Description
* Transpose 2 dimensional array.
*
* ### Sample script
* ```
* const array1 = [[1, 2, 3], [1, 2, 3], [1, 2, 3]];
* const res1 = UtlApp.transpose(array1);
* console.log(res1); // [ [ 1, 1, 1 ], [ 2, 2, 2 ], [ 3, 3, 3 ] ]
*
* const array2 = [[1, 2, 3], [1, 2], [1]];
* const res2 = UtlApp.transpose(array2);
* console.log(res2); // [ [ 1, 1, 1 ], [ 2, 2, null ], [ 3, null, null ] ]
* ```
*
* @param {Array} array 2 dimensional array.
* @param {Boolean} check Check whether the inputted array is 2 dimensional array. Default is true.
* @return {Array} Transposed array.
*/
function transpose(array, check = true) {
if (check && !is2DimensionalArray(array)) {
throw new Error("Please use 2 dimensional array.");
}
return array[0].map((_, col) => array.map((row) => row[col] || null));
}
Split array every n length. Ref
/**
* ### Description
* Split array every n length.
* Ref: https://tanaikech.github.io/2022/05/21/splitting-and-processing-an-array-every-n-length-using-google-apps-script/
*
* ### Sample script
* ```
* const size = 3;
* const array1 = ["a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", "i1", "j1"];
* const res = UtlApp.splitArray(array1, size);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* [
* [ 'a1', 'b1', 'c1' ],
* [ 'd1', 'e1', 'f1' ],
* [ 'g1', 'h1', 'i1' ],
* [ 'j1' ]
* ]
* ```
*
* @param {Array} array 2 dimensional array.
* @param {Boolean} check Check whether the inputted array is 2 dimensional array. Default is true.
* @return {Array} Transposed array.
*/
function splitArray(array, size) {
if (!array || !size || !Array.isArray(array)) {
throw new Error("Please give an array and split size.");
}
return [...Array(Math.ceil(array.length / size))].map((_) =>
array.splice(0, size)
);
}
Retrieve the specific columns from 2 dimensional array.
/**
* ### Description
* Retrieve the specific columns from 2 dimensional array.
*
* ### Sample script
* ```
* const array1 = [[1, 2, 3], [1, 2, 3], [1, 2, 3]];
* const columns = [2];
* const res = UtlApp.getSpecificColumns(array1, columns);
* console.log(res); // [ [ 2 ], [ 2 ], [ 2 ] ]
* ```
*
* @param {Array} array 2 dimensional array.
* @param {Array} columns 1 dimensional array. Give the specific column numbers you want to get. The 1st number is 1.
* @param {Boolean} check Check whether the inputted array is 2 dimensional array. Default is true.
* @return {Array} Retrieved specific columns.
*/
function getSpecificColumns(array, columns, check = true) {
if (check && !is2DimensionalArray(array)) {
throw new Error("Please use 2 dimensional array.");
}
if (!columns || !Array.isArray(columns) || columns.length == 0) {
throw new Error("Please set column numbers you want to retrieve.");
}
return array.map((r) => columns.map((e) => r[e - 1] || null));
}
Delete specific columns from 2 dimensional array.
/**
* ### Description
* Delete specific columns from 2 dimensional array.
*
* ### Sample script
* ```
* const array1 = [[1, 2, 3], [1, 2, 3], [1, 2, 3]];
* const columns = [2];
* const res = UtlApp.deleteSpecificColumns(array1, columns);
* console.log(res); // [ [ 1, 3 ], [ 1, 3 ], [ 1, 3 ] ]
* ```
*
* @param {Array} array 2 dimensional array.
* @param {Array} columns 1 dimensional array. Give the specific column numbers you want to delete. The 1st number is 1.
* @param {Boolean} check Check whether the inputted array is 2 dimensional array. Default is true.
* @return {Array} Array deleted the specific columns.
*/
function deleteSpecificColumns(array, columns, check = true) {
if (check && !is2DimensionalArray(array)) {
throw new Error("Please use 2 dimensional array.");
}
if (!columns || !Array.isArray(columns) || columns.length == 0) {
throw new Error("Please set column numbers you want to retrieve.");
}
return array.map((r) => r.filter((_, j) => !columns.includes(j + 1)));
}
Insert columns to 2 dimensional array.
/**
* ### Description
* Insert columns to 2 dimensional array.
*
* ### Sample script
* ```
* const array1 = [[1, 2, 3], [1, 2, 3], [1, 2, 3]];
* const array2 = [[4, 5, 6], [7, 8, 9]];
* const column = 2;
* const res1 = UtlApp.insertColumns(array1, array2, column);
* console.log(res1); // [[1,4,5,6,2,3],[1,7,8,9,2,3],[1,null,null,null,2,3]]
*
* const res2 = UtlApp.insertColumns(array1, array2, column, true);
* console.log(res1); // [[1,4,5,6,2,3],[1,7,8,9,2,3],[1,null,null,null,2,3]]
* ```
*
* @param {Array} array 2 dimensional array.
* @param {Array} insertArray 2 dimensional array you want to insert.
* @param {Number} column Column number you want to insert the array. The 1st number is 1. Default is 1.
* @param {Boolean} rep When you want to replace the column, please use true. Default is false. In this case, the columns are inserted.
* @param {Boolean} check Check whether the inputted array is 2 dimensional array. Default is true.
* @return {Array} Array inserted the columns.
*/
function insertColumns(
array,
insertArray,
column = 1,
rep = false,
check = true
) {
if (check && !is2DimensionalArray(array)) {
throw new Error("Please use 2 dimensional array.");
}
if (
!insertArray ||
!Array.isArray(insertArray) ||
insertArray.length == 0 ||
!is2DimensionalArray(insertArray)
) {
throw new Error("Please set columns you want to insert.");
}
const t = transpose(array, false);
t.splice(column - 1, rep ? 1 : 0, ...transpose(insertArray, false));
return transpose(t, false);
}
Remove duplicated values from 1 dimensional array.
/**
* ### Description
* Remove duplicated values from 1 dimensional array.
*
* ### Sample script
* ```
* const array1 = ["a1", "b1", "c1", "b1", "c1"];
* const res = UtlApp.removeDuplicatedValues(array1);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* {
* "removeDuplicatedValues":["a1","b1","c1"],
* "duplicatedValues":["b1","c1"],
* "numberOfDuplicate":{"a1":1,"b1":2,"c1":2}
* }
* ```
*
* @param {Array} array 1 dimensional array.
* @return {Object} Object including removeDuplicatedValues, duplicatedValues and numberOfDuplicate.
*/
function removeDuplicatedValues(array) {
if (!Array.isArray(array)) {
throw new Error("Please use 1 dimensional array.");
}
const obj = array.reduce(
(m, e) => m.set(e, m.has(e) ? m.get(e) + 1 : 1),
new Map()
);
const e = [...obj.entries()];
return {
removeDuplicatedValues: [...obj.keys()],
duplicatedValues: e.reduce((ar, [k, v]) => {
if (v != 1) ar.push(k);
return ar;
}, []),
numberOfDuplicate: Object.fromEntries(e),
};
}
Retrieve empty row index.
/**
* ### Description
* Retrieve empty row index.
*
* ### Sample script
* ```
* const array1 = [["a1"], [2], [3], [4], [], [5], [], ["z"], [""], [7], [8]];
* const res = UtlApp.get1stEmptyRow(array1);
* console.log(res); // { topEmptyRow: 4, lastEmptyRow: 8 }
* ```
*
* @param {Array} array 2 dimensional array.
* @param {Boolean} check Check whether the inputted array is 2 dimensional array. Default is true.
* @return {Object} Object including the top empty row index and the last empty row index.
*/
function get1stEmptyRow(array, check = true) {
if (check && !is2DimensionalArray(array)) {
throw new Error("Please use 2 dimensional array.");
}
return {
topEmptyRow: array.findIndex((r) => r.join("") == ""),
lastEmptyRow:
array.length - 1 - array.reverse().findIndex((r) => r.join("") == ""),
};
}
Retrieve empty column index.
/**
* ### Description
* Retrieve empty column index.
*
* ### Sample script
* ```
* const array1 = [["a1", "b1", "f", "d1", "aa", "h", "aa"], [2, 3, 4, 5], [3, 1, 1, , , 3], [4, 1, 1, 1], [1, 2, 3], [5, 1, 1], [1, 1, 1], ["z", 1, 1], [1, "b", 1, 1, ""], [7, 1, 1, 1], [8, 6, 5, 4, 3]];
* const res = UtlApp.get1stEmptyColumn(array1);
* console.log(res); // { topEmptyColumn: 3, lastEmptyColumn: 7 }
* ```
*
* @param {Array} array 2 dimensional array.
* @param {Boolean} check Check whether the inputted array is 2 dimensional array. Default is true.
* @return {Object} Object including the top empty column index and the last empty column index.
*/
function get1stEmptyColumn(array, check = true) {
if (check && !is2DimensionalArray(array)) {
throw new Error("Please use 2 dimensional array.");
}
return array.reduce(
(o, r) => {
let top = r.findIndex(
(c) => typeof c === "undefined" || c.toString() == ""
);
let last =
r.length -
1 -
r
.reverse()
.findIndex((c) => typeof c === "undefined" || c.toString() == "");
top = top == -1 ? r.length : top;
last = last == -1 ? 0 : last;
o.topEmptyColumn = o.topEmptyColumn < top ? o.topEmptyColumn : top;
o.lastEmptyColumn = o.lastEmptyColumn > last ? o.lastEmptyColumn : last;
return o;
},
{ topEmptyColumn: Infinity, lastEmptyColumn: 0 }
);
}
Sum numbers in an array.
/**
* ### Description
* Sum numbers in an array.
*
* ### Sample script
* ```
* const array1 = [1, 2, 3, 4, 5];
* const res = UtlApp.sum(array1);
* console.log(res); // 15
* ```
*
* @param {Array} array Input 1 dimensional array including number values.
* @param {Boolean} check Check whether the values of the inputted array is number. Default is true.
* @return {String} int8array.
*/
function sum(array, check = true) {
if (check && !(Array.isArray(array) && array.every((e) => !isNaN(e)))) {
throw new Error("Please give an array including numbers.");
}
return array.reduce((n, e) => (n += e), 0);
}
Compiling Continuous Numbers using Google Apps Script. Ref
/**
* ### Description
* Compiling Continuous Numbers using Google Apps Script.
* Ref: https://tanaikech.github.io/2021/10/08/compiling-continuous-numbers-using-google-apps-script/
*
* ### Sample script
* ```
* const ar = [4, 5, 9, 3, 10, 5, 11, 7, 7, 13, 1];
* const res = UtlApp.compilingNumbers(ar);
* console.log(res)
* ```
*
* Result is as follows.
*
* ```
* [
* { start: 1, end: 1 },
* { start: 3, end: 5 },
* { start: 7, end: 7 },
* { start: 9, end: 11 },
* { start: 13, end: 13 }
* ]
* ```
*
* @param {Array} array Input array.
* @return {Array} Array including object like [{"start":1,"end":1},{"start":3,"end":5},{"start":7,"end":7},{"start":9,"end":11},{"start":13,"end":13}].
*/
function compilingNumbers(array) {
if (!(Array.isArray(array) && array.every((e) => !isNaN(e)))) {
throw new Error("Please give an array including numbers.");
}
const { values } = [...new Set(array.sort((a, b) => a - b))].reduce(
(o, e, i, a) => {
if (
o.temp.length == 0 ||
(o.temp.length > 0 && e == o.temp[o.temp.length - 1] + 1)
) {
o.temp.push(e);
} else {
if (o.temp.length > 0) {
o.values.push({ start: o.temp[0], end: o.temp[o.temp.length - 1] });
}
o.temp = [e];
}
if (i == a.length - 1) {
o.values.push(
o.temp.length > 1
? { start: o.temp[0], end: o.temp[o.temp.length - 1] }
: { start: e, end: e }
);
}
return o;
},
{ temp: [], values: [] }
);
return values;
}
Converting 2 dimensional array to JSON object. Ref
/**
* ### Description
* Converting 2 dimensional array to JSON object.
* Ref: https://tanaikech.github.io/2021/10/24/converting-values-of-google-spreadsheet-to-object-using-google-apps-script/
*
* ### Sample script
* ```
* const headers = ["header1", "header2", "header3"];
* const rows = [["a2", "b2", "c2"], ["a3", "b3", "c3"]];
* const res = UtlApp.convArrayToObject(headers, rows);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* [
* { header1: 'a2', header2: 'b2', header3: 'c2' },
* { header1: 'a3', header2: 'b3', header3: 'c3' }
* ]
* ```
*
* @param {Array} headers Header array (1 dimensional array).
* @param {Array} rows Row array (2 dimensional array).
* @return {Object} JSON object.
*/
function convArrayToObject(headers, rows) {
if (
!Array.isArray(headers) ||
!Array.isArray(rows) ||
!is2DimensionalArray(rows)
) {
throw new Error("Please give an array of header and values.");
}
return rows.map((r) => headers.reduce((o, h, j) => ((o[h] = r[j]), o), {}));
}
Converting 2-dimensional array as unpivot (reverse pivot). Ref
/**
* ### Description
* Converting 2-dimensional array as unpivot (reverse pivot).
*
* ### Sample script
* ```
* const values = [
* ["", "b1", "c1", "d1"],
* ["a2", 1, 2, 3],
* ["a3", 4, 5, 6],
* ["a4", 7, 8, 9],
* ["a5", 10, 11, 12]
* ];
* const res = UtlApp.unpivot(values);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* [
* ['b1', 'a2', 1],
* ['b1', 'a3', 4],
* ['b1', 'a4', 7],
* ['b1', 'a5', 10],
* ['c1', 'a2', 2],
* ['c1', 'a3', 5],
* ['c1', 'a4', 8],
* ['c1', 'a5', 11],
* ['d1', 'a2', 3],
* ['d1', 'a3', 6],
* ['d1', 'a4', 9],
* ['d1', 'a5', 12]
* ]
* ```
*
* @param {Array} values 2 dimensional array.
* @return {Array} 2 dimensional array converted as unpivot (reverse pivot).
*/
function unpivot(values) {
if (!Array.isArray(values) || !is2DimensionalArray(values)) {
throw new Error("Please give an array of values.");
}
const [[, ...h], ...v] = values;
return h.flatMap((hh, i) => v.map((t) => [hh, t[0], t[i + 1]]));
}
Reversing 2-dimensional array with unpivot. Ref
/**
* ### Description
* Reversing 2-dimensional array with unpivot.
* Ref: https://tanaikech.github.io/2023/05/11/unpivot-on-google-spreadsheet-using-google-apps-script/
*
* ### Sample script
* ```
* const values = [
* ["b1", "a2", 1],
* ["b1", "a3", 4],
* ["b1", "a4", 7],
* ["b1", "a5", 10],
* ["c1", "a2", 2],
* ["c1", "a3", 5],
* ["c1", "a4", 8],
* ["c1", "a5", 11],
* ["d1", "a2", 3],
* ["d1", "a3", 6],
* ["d1", "a4", 9],
* ["d1", "a5", 12]
* ];
* const res = UtlApp.reverseUnpivot(values);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* [
* [null, 'b1', 'c1', 'd1'],
* ['a2', 1, 2, 3],
* ['a3', 4, 5, 6],
* ['a4', 7, 8, 9],
* ['a5', 10, 11, 12]
* ]
* ```
*
* @param {Array} values 2 dimensional array.
* @return {Array} Reversed 2 dimensional array with unpivot.
*/
function reverseUnpivot(values) {
if (!Array.isArray(values) || !is2DimensionalArray(values)) {
throw new Error("Please give an array of values.");
}
const [a, b, c] = values[0].map((_, c) => values.map((r) => r[c]));
const ch = [...new Set(a)];
const rh = [...new Set(b)];
const size = rh.length;
const temp = [
[null, ...rh],
...[...Array(Math.ceil(c.length / size))]
.map((_) => c.splice(0, size))
.map((vv, i) => [ch[i], ...vv]),
];
return temp[0].map((_, c) => temp.map((r) => r[c]));
}
Calculate dot product from 2 arrays.
/**
* ### Description
* Calculate dot product from 2 arrays.
*
* ### Sample script
* ```
* const array1 = [1, 2, 3, 4, 5];
* const array2 = [3, 4, 5, 6, 7];
* const res = UtlApp.dotProduct(array1, array2);
* ```
*
* @param {Array} array1 1-dimensional array including numbers.
* @param {Array} array2 1-dimensional array including numbers.
* @return {Number} Calculated result of dot product.
*/
function dotProduct(array1, array2) {
if (!Array.isArray(array1) || !Array.isArray(array2)) {
throw new Error("Please give 2 arrays.");
}
return array1.reduce((t, e, i) => (t += e * array2[i]), 0);
}
Calculate cosine similarity from 2 arrays. This can be used with "Semantic Search using Gemini Pro API with Google Apps Script" of my report.
/**
* ### Description
* Calculate cosine similarity from 2 arrays.
*
* ### Sample script
* ```
* const array1 = [1, 2, 3, 4, 5];
* const array2 = [3, 4, 5, 6, 7];
* const res = UtlApp.cosineSimilarity(array1, array2);
* ```
*
* @param {Array} array1 1-dimensional array including numbers.
* @param {Array} array2 1-dimensional array including numbers.
* @return {Number} Calculated result of cosine similarity.
*/
function cosineSimilarity(array1, array2) {
if (!Array.isArray(array1) || !Array.isArray(array2)) {
throw new Error("Please give 2 arrays.");
}
const dotProduct = array1.reduce((t, e, i) => (t += e * array2[i]), 0);
const magnitudes = [array1, array2]
.map((e) => Math.sqrt(e.reduce((t, f) => (t += f * f), 0)))
.reduce((t, f) => (t *= f), 1);
return dotProduct / magnitudes;
}
Convert Int8Array to hex string array. Ref
/**
* ### Description
* Convert Int8Array to hex string array.
* Ref: https://tanaikech.github.io/2021/12/18/converting-from-string-to-hex-from-hex-to-bytes-from-bytes-to-string-using-google-apps-script/
*
* ### Sample script
* ```
* const ar = Utilities.newBlob("さんぷる").getBytes();
* const res = UtlApp.convInt8ArrayToHexAr(ar);
* console.log(res); // [ 'e3', '81', '95', 'e3', '82', '93', 'e3', '81', 'b7', 'e3', '82', '8b' ]
* ```
*
* @param {Array} array int8 array.
* @return {Array} Hex string array.
*/
function convInt8ArrayToHexAr(array) {
if (!Array.isArray(array)) {
throw new Error("Please give Int8Array.");
}
return array.map((byte) => ("0" + (byte & 0xff).toString(16)).slice(-2));
}
Convert string to hex. Ref
/**
* ### Description
* Convert string to hex.
* Ref: https://tanaikech.github.io/2021/12/18/converting-from-string-to-hex-from-hex-to-bytes-from-bytes-to-string-using-google-apps-script/
*
* ### Sample script
* ```
* const str = "sample string";
* const res = UtlApp.convStrToHex(str);
* console.log(res); // 73616d706c6520737472696e67
* ```
*
* @param {String} str Input string value.
* @return {String} Hex string value.
*/
function convStrToHex(str) {
if (!str) {
throw new Error("Please give a string value.");
}
return convInt8ArrayToHexAr(Utilities.newBlob(str).getBytes()).join("");
}
Convert string to hex. Ref
/**
* ### Description
* Convert string to hex.
* Ref: https://tanaikech.github.io/2021/12/18/converting-from-string-to-hex-from-hex-to-bytes-from-bytes-to-string-using-google-apps-script/
*
* ### Sample script
* ```
* const hex = "73616d706c6520737472696e67";
* const res = UtlApp.convHexToInt8Ar(hex);
* console.log(res); // [ 115, 97, 109, 112, 108, 101, 32, 115, 116, 114, 105, 110, 103 ]
*
* console.log(UtlApp.convInt8ArToStr(res)); // sample string
* ```
*
* @param {String} hex Input hex string value.
* @return {Array} Converted int8array.
*/
function convHexToInt8Ar(hex) {
if (!hex) {
throw new Error("Please give a hex string value.");
}
return hex
.match(/.{2}/g)
.map((e) =>
parseInt(e[0], 16).toString(2).length == 4
? parseInt(e, 16) - 256
: parseInt(e, 16)
);
}
Convert Int8Array to string value.
/**
* ### Description
* Convert Int8Array to string value.
*
* ### Sample script
* ```
* const hex = "73616d706c6520737472696e67";
* const res = UtlApp.convHexToInt8Ar(hex);
* console.log(res); // [ 115, 97, 109, 112, 108, 101, 32, 115, 116, 114, 105, 110, 103 ]
*
* console.log(UtlApp.convInt8ArToStr(res)); // sample string
* ```
*
* @param {Array} array Input int8array.
* @return {String} Converted string value.
*/
function convInt8ArToStr(array) {
if (!Array.isArray(array)) {
throw new Error("Please give Int8Array.");
}
return Utilities.newBlob(array).getDataAsString();
}
Convert Int8Array to Uint8Array.
/**
* ### Description
* Convert Int8Array to Uint8Array.
*
* ### Sample script
* ```
* const str = "さんぷる";
* const int8Ar = Utilities.newBlob(str).getBytes();
* console.log(int8Ar); // [ -29, -127, -107, -29, -126, -109, -29, -127, -73, -29, -126, -117 ]
*
* const res1 = UtlApp.convInt8ArToUint8Ar(int8Ar);
* console.log(res1); // [ 227, 129, 149, 227, 130, 147, 227, 129, 183, 227, 130, 139 ]
*
* const res2 = UtlApp.convUint8ArToInt8Ar(res1);
* console.log(res2); // [ -29, -127, -107, -29, -126, -109, -29, -127, -73, -29, -126, -117 ]
* ```
*
* @param {Array} array Input int8array.
* @return {Array} unit8array.
*/
function convInt8ArToUint8Ar(array) {
if (!Array.isArray(array)) {
throw new Error("Please give Int8Array.");
}
return [...Uint8Array.from(array)];
}
Convert Uint8Array to Int8Array.
/**
* ### Description
* Convert Uint8Array to Int8Array.
*
* ### Sample script
* ```
* const str = "さんぷる";
* const int8Ar = Utilities.newBlob(str).getBytes();
* console.log(int8Ar); // [ -29, -127, -107, -29, -126, -109, -29, -127, -73, -29, -126, -117 ]
*
* const res1 = UtlApp.convInt8ArToUint8Ar(int8Ar);
* console.log(res1); // [ 227, 129, 149, 227, 130, 147, 227, 129, 183, 227, 130, 139 ]
*
* const res2 = UtlApp.convUint8ArToInt8Ar(res1);
* console.log(res2); // [ -29, -127, -107, -29, -126, -109, -29, -127, -73, -29, -126, -117 ]
* ```
*
* @param {Array} array Input unit8array.
* @return {Array} int8array.
*/
function convUint8ArToInt8Ar(array) {
if (!Array.isArray(array)) {
throw new Error("Please give Uint8Array.");
}
return [...Int8Array.from(array)];
}
Search index from base data using a search data.
/**
* ### Description
* Search index from base data using a search data.
*
* ### Sample script
* ```
* const sampleString = "abc123def123ghi123jkl";
* const splitValue = "123";
* const baseData = Utilities.newBlob(sampleString).getBytes();
* const searchData = Utilities.newBlob(splitValue).getBytes();
* const res = UtlApp.searchIndexFromDataByData(baseData, searchData);
* console.log(res); // 3
* ```
*
* @param {Array} baseData Input byteArray of base data.
* @param {Array} searchData Input byteArray of search data using split.
* @return {Number} Index of search data. If the search data is not found, -1 is returned.
*/
function searchIndexFromDataByData(baseData, searchData) {
if (!Array.isArray(baseData) || !Array.isArray(searchData)) {
throw new Error("Please give Int8Array.");
}
const search = searchData.join("");
const bLen = searchData.length;
return baseData.findIndex(
(_, i, a) => [...Array(bLen)].map((_, j) => a[j + i]).join("") == search
);
}
Split byteArray by a search data. Ref
/**
* ### Description
* Split byteArray by a search data.
*
* ### Sample script
* ```
* const sampleString = "abc123def123ghi123jkl";
* const splitValue = "123";
* const baseData = Utilities.newBlob(sampleString).getBytes();
* const searchData = Utilities.newBlob(splitValue).getBytes();
* const res = UtlApp.splitByteArrayBySearchData(baseData, searchData);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* [
* [ 97, 98, 99 ],
* [ 100, 101, 102 ],
* [ 103, 104, 105 ],
* [ 106, 107, 108 ]
* ]
* ```
*
* @param {Array} baseData Input byteArray of base data.
* @param {Array} searchData Input byteArray of search data using split.
* @return {Array} An array including byteArray.
*/
function splitByteArrayBySearchData(baseData, searchData) {
if (!Array.isArray(baseData) || !Array.isArray(searchData)) {
throw new Error("Please give Int8Array.");
}
const search = searchData.join("");
const bLen = searchData.length;
const res = [];
let idx = 0;
do {
idx = baseData.findIndex(
(_, i, a) => [...Array(bLen)].map((_, j) => a[j + i]).join("") == search
);
if (idx != -1) {
res.push(baseData.splice(0, idx));
baseData.splice(0, bLen);
} else {
res.push(baseData.splice(0));
}
} while (idx != -1);
return res;
}
Converting text as unicode. Ref
/**
* @namespace ConvText
*
* ### Description
* Converting text as unicode.
* Ref: https://tanaikech.github.io/2020/11/13/converting-texts-to-bold-italic-and-bold-italic-types-of-unicode-using-google-apps-script/
*
* ### Sample script
* ```
* const text = "Sample value 123\nSample value 456\nSample value 789";
* console.log(text); // Original text
* console.log(UtlApp.ConvText.bold(text)); // Bold type
* console.log(UtlApp.ConvText.italic(text)); // Italic type
* console.log(UtlApp.ConvText.boldItalic(text)); // Bold-italic type
* console.log(UtlApp.ConvText.underLine(text)); // Underline
* console.log(UtlApp.ConvText.strikethrough(text)); // Strikethrough
* ```
*
*/
var ConvText = {
/**
* Method for processing
* @param {String} text
* @param {Object} obj
* @return {String} Converted text.
*/
c_: function (text, obj) {
return text.replace(
new RegExp(`[${obj.reduce((s, { r }) => (s += r), "")}]`, "g"),
(e) => {
const t = e.codePointAt(0);
if (
(t >= 48 && t <= 57) ||
(t >= 65 && t <= 90) ||
(t >= 97 && t <= 122)
) {
return obj.reduce((s, { r, d }) => {
if (new RegExp(`[${r}]`).test(e))
s = String.fromCodePoint(e.codePointAt(0) + d);
return s;
}, "");
}
return e;
}
);
},
/**
* Convert text to bold type.
* @param {String} text
* @return {String} Converted text.
*/
bold: function (text) {
return this.c_(text, [
{ r: "0-9", d: 120734 },
{ r: "A-Z", d: 120211 },
{ r: "a-z", d: 120205 },
]);
},
/**
* Convert text to italic type.
* Ref: https://tanaikech.github.io/2020/11/13/converting-texts-to-bold-italic-and-bold-italic-types-of-unicode-using-google-apps-script/
* @param {String} text
* @return {String} Converted text.
*/
italic: function (text) {
return this.c_(text, [
{ r: "A-Z", d: 120263 },
{ r: "a-z", d: 120257 },
]);
},
/**
* Convert text to bold and italic type.
* @param {String} text
* @return {String} Converted text.
*/
boldItalic: function (text) {
return this.c_(text, [
{ r: "A-Z", d: 120315 },
{ r: "a-z", d: 120309 },
]);
},
/**
* Convert text to underline type.
* @param {String} text
* @return {String} Converted text.
*/
underLine: function (text) {
return text.length > 0 ? [...text].join("\u0332") + "\u0332" : "";
},
/**
* Convert text to strikethrough type.
* @param {String} text
* @return {String} Converted text.
*/
strikethrough: function (text) {
return text.length > 0 ? [...text].join("\u0336") + "\u0336" : "";
},
};
As additional information, when you want to convert the above exported text to the normal text, you can use normalize(). The sample script can be seen at my answer on Stackoverflow.
Converting colum letter to column index. Start of column index is 0. Ref
/**
* ### Description
* Converting colum letter to column index. Start of column index is 0.
* Ref: https://tanaikech.github.io/2022/05/01/increasing-column-letter-by-one-using-google-apps-script/
*
* ### Sample script
* ```
* const res1 = UtlApp.columnLetterToIndex("Z");
* console.log(res1); // 25
* const res2 = UtlApp.columnLetterToIndex("AA");
* console.log(res2); // 26
* ```
*
* @param {String} letter Column letter.
* @return {Number} Column index.
*/
function columnLetterToIndex(letter = null) {
if (letter === null || typeof letter != "string") {
throw new Error("Please give the column letter as a string.");
}
letter = letter.toUpperCase();
return [...letter].reduce(
(c, e, i, a) =>
(c += (e.charCodeAt(0) - 64) * Math.pow(26, a.length - i - 1)),
-1
);
}
Converting colum index to column letter. Start of column index is 0. Ref
/**
* ### Description
* Converting colum index to column letter. Start of column index is 0.
* Ref: https://stackoverflow.com/a/53678158/7108653
*
* ### Sample script
* ```
* const res1 = UtlApp.columnIndexToLetter(res1);
* console.log(res1); // Z
* const res2 = UtlApp.columnIndexToLetter(res2);
* console.log(res2); // AA
* ```
*
* @param {Number} index Column index.
* @return {String} Column letter.
*/
function columnIndexToLetter(index = null) {
if (index === null || isNaN(index)) {
throw new Error(
"Please give the column indexr as a number. In this case, 1st number is 0."
);
}
return (a = Math.floor(index / 26)) >= 0
? columnIndexToLetter(a - 1) + String.fromCharCode(65 + (index % 26))
: "";
}
Converting a1Notation to gridrange. This will be useful for using Sheets API. Ref
/**
* ### Description
* Converting a1Notation to gridrange. This will be useful for using Sheets API.
* Ref: https://tanaikech.github.io/2017/07/31/converting-a1notation-to-gridrange-for-google-sheets-api/
*
* ### Sample script
* ```
* const a1Notation = "AB25:AD51";
* const sheetId = 0;
* const res = UtlApp.convA1NotationToGridRange(a1Notation, sheetId);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* {
* sheetId: 0,
* startRowIndex: 24,
* endRowIndex: 51,
* startColumnIndex: 27,
* endColumnIndex: 30
* }
* ```
*
* @param {String} a1Notation A1Notation of range.
* @param {Number} sheetId Sheet ID of the range.
* @return {Object} Gridrange.
*/
function convA1NotationToGridRange(a1Notation = null, sheetId = null) {
if (
a1Notation === null ||
sheetId === null ||
typeof a1Notation != "string" ||
isNaN(sheetId)
) {
throw new Error("Please give a1Notation (string) and sheet ID (integer).");
}
const { col, row } = a1Notation
.toUpperCase()
.split("!")
.map((f) => f.split(":"))
.pop()
.reduce(
(o, g) => {
var [r1, r2] = ["[A-Z]+", "[0-9]+"].map((h) => g.match(new RegExp(h)));
o.col.push(r1 && columnLetterToIndex(r1[0]));
o.row.push(r2 && Number(r2[0]));
return o;
},
{ col: [], row: [] }
);
col.sort((a, b) => (a > b ? 1 : -1));
row.sort((a, b) => (a > b ? 1 : -1));
const [start, end] = col.map((e, i) => ({ col: e, row: row[i] }));
const obj = {
sheetId,
startRowIndex: start?.row && start.row - 1,
endRowIndex: end?.row ? end.row : start.row,
startColumnIndex: start && start.col,
endColumnIndex: end ? end.col + 1 : start.col + 1,
};
if (obj.startRowIndex === null) {
obj.startRowIndex = 0;
delete obj.endRowIndex;
}
if (obj.startColumnIndex === null) {
obj.startColumnIndex = 0;
delete obj.endColumnIndex;
}
return obj;
}
Converting gridrange to a1Notation. This will be useful for using Sheets API. Ref
/**
* ### Description
* Converting gridrange to a1Notation. This will be useful for using Sheets API.
* Ref: https://tanaikech.github.io/2017/07/31/converting-a1notation-to-gridrange-for-google-sheets-api/
*
* ### Sample script 1
* ```
* const gridRange = {
* sheetId: 0,
* startRowIndex: 24,
* endRowIndex: 51,
* startColumnIndex: 27,
* endColumnIndex: 30
* };
* const sheetName = "Sheet1";
* const res = UtlApp.convGridRangeToA1Notation(gridRange, sheetName);
* console.log(res); // 'Sheet1'!AB25:AD51
* ```
*
* ### Sample script 2
* ```
* const gridRange = {
* sheetId: 0,
* startRowIndex: 24,
* endRowIndex: 51,
* startColumnIndex: 27,
* endColumnIndex: 30
* };
* const res = UtlApp.convGridRangeToA1Notation(gridRange);
* console.log(res); // AB25:AD51
* ```
*
* @param {Object} gridrange Gridrange of range.
* @param {String} sheetName Sheet name of the range.
* @return {String} A1Notation.
*/
function convGridRangeToA1Notation(gridrange, sheetName = "") {
if (gridrange === null) {
throw new Error("Please give gridRange (JSON object).");
}
const start = {};
const end = {};
if (gridrange.hasOwnProperty("startColumnIndex")) {
start.col = columnIndexToLetter(gridrange.startColumnIndex);
} else if (
!gridrange.hasOwnProperty("startColumnIndex") &&
gridrange.hasOwnProperty("endColumnIndex")
) {
start.col = "A";
}
if (gridrange.hasOwnProperty("startRowIndex")) {
start.row = gridrange.startRowIndex + 1;
} else if (
!gridrange.hasOwnProperty("startRowIndex") &&
gridrange.hasOwnProperty("endRowIndex")
) {
start.row = gridrange.endRowIndex;
}
if (gridrange.hasOwnProperty("endColumnIndex")) {
end.col = columnIndexToLetter(gridrange.endColumnIndex - 1);
} else if (!gridrange.hasOwnProperty("endColumnIndex")) {
end.col = "{Here, please set the max column letter.}";
}
if (gridrange.hasOwnProperty("endRowIndex")) {
end.row = gridrange.endRowIndex;
}
const k = ["col", "row"];
const st = k.map((e) => start[e]).join("");
const en = k.map((e) => end[e]).join("");
if (sheetName) {
return st == en ? `'${sheetName}'!${st}` : `'${sheetName}'!${st}:${en}`;
}
return st == en ? `${st}` : `${st}:${en}`;
}
This method is used for adding the query parameters to the URL. Ref
/**
* ### Description
* This method is used for adding the query parameters to the URL.
* Ref: https://tanaikech.github.io/2018/07/12/adding-query-parameters-to-url-using-google-apps-script/
*
* ### Sample script 1
* ```
* const url = "https://sampleUrl";
* const query = {
* query1: ["value1A", "value1B", "value1C"],
* query2: "value2A, value2B",
* query3: "value3A/value3B",
* };
* const endpoint = UtlApp.addQueryParameters(url, query);
* console.log(endpoint); // https://sampleUrl?query1=value1A&query1=value1B&query1=value1C&query2=value2A%2C%20value2B&query3=value3A%2Fvalue3B
* ```
*
* ### Sample script 2
* ```
* const url = "";
* const query = {
* query1: ["value1A", "value1B", "value1C"],
* query2: "value2A, value2B",
* query3: "value3A/value3B",
* };
* const endpoint = UtlApp.addQueryParameters(url, query);
* console.log(endpoint); // query1=value1A&query1=value1B&query1=value1C&query2=value2A%2C%20value2B&query3=value3A%2Fvalue3B
* ```
*
* @param {String} url The base URL for adding the query parameters.
* @param {Object} obj JSON object including query parameters.
* @return {String} URL including the query parameters.
*/
function addQueryParameters(url, obj) {
if (url === null || obj === null || typeof url != "string") {
throw new Error(
"Please give URL (String) and query parameter (JSON object)."
);
}
return (
(url == "" ? "" : `${url}?`) +
Object.entries(obj)
.flatMap(([k, v]) =>
Array.isArray(v)
? v.map((e) => `${k}=${encodeURIComponent(e)}`)
: `${k}=${encodeURIComponent(v)}`
)
.join("&")
);
}
- In this case, when you use
""
asurl
, only the query parameters are returned.
This method is used for parsing the URL including the query parameters. Ref
/**
* ### Description
* This method is used for parsing the URL including the query parameters.
* Ref: https://tanaikech.github.io/2018/07/12/adding-query-parameters-to-url-using-google-apps-script/
*
* ### Sample script
* ```
* const url = "https://sampleUrl.com/sample?key1=value1&key2=value2&key1=value3&key3=value4&key2=value5";
* const res = UtlApp.parseQueryParameters(url);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* {
* url: 'https://sampleUrl.com/sample',
* queryParameters:
* {
* key1: ['value1', 'value3'],
* key2: ['value2', 'value5'],
* key3: ['value4']
* }
* }
* ```
*
* @param {String} url The URL including the query parameters.
* @return {Object} JSON object including the base url and the query parameters.
*/
function parseQueryParameters(url) {
if (url === null || typeof url != "string") {
throw new Error("Please give URL (String) including the query parameters.");
}
const s = url.split("?");
if (s.length == 1) {
return { url: s[0], queryParameters: null };
}
const [baseUrl, query] = s;
if (query) {
const queryParameters = query.split("&").reduce(function (o, e) {
const temp = e.split("=");
const key = temp[0].trim();
let value = temp[1].trim();
value = isNaN(value) ? value : Number(value);
if (o[key]) {
o[key].push(value);
} else {
o[key] = [value];
}
return o;
}, {});
return { url: baseUrl, queryParameters };
}
return null;
}
This method is used for expanding A1Notations. Ref
/**
* ### Description
* This method is used for expanding A1Notations.
* Ref: https://tanaikech.github.io/2020/04/04/updated-expanding-a1notations-using-google-apps-script/
*
* ### Sample script
* ```
* const a1Notations = ["A1:E3", "B10:W13", "EZ5:FA8", "AAA1:AAB3"];
* const res = UtlApp.expandA1Notations(a1Notations);
* console.log(JSON.stringify(res));
* ```
*
* Result is as follows.
*
* ```
* [
* ["A1","B1","C1","D1","E1","A2","B2","C2","D2","E2","A3","B3","C3","D3","E3"],
* ["B10","C10","D10","E10","F10","G10","H10","I10","J10","K10","L10","M10","N10","O10","P10","Q10","R10","S10","T10","U10","V10","W10","B11","C11","D11","E11","F11","G11","H11","I11","J11","K11","L11","M11","N11","O11","P11","Q11","R11","S11","T11","U11","V11","W11","B12","C12","D12","E12","F12","G12","H12","I12","J12","K12","L12","M12","N12","O12","P12","Q12","R12","S12","T12","U12","V12","W12","B13","C13","D13","E13","F13","G13","H13","I13","J13","K13","L13","M13","N13","O13","P13","Q13","R13","S13","T13","U13","V13","W13"],
* ["EZ5","FA5","EZ6","FA6","EZ7","FA7","EZ8","FA8"],
* ["AAA1","AAB1","AAA2","AAB2","AAA3","AAB3"]
* ]
* ```
*
* @param {Array} a1Notations Array including A1Notations.
* @return {Array} Array including the expanded A1Notations.
*/
function expandA1Notations(a1Notations, maxRow = "1000", maxColumn = "Z") {
if (!Array.isArray(a1Notations) || a1Notations.length == 0) {
throw new Error("Please give a1Notations (Array).");
}
const reg1 = new RegExp("^([A-Z]+)([0-9]+)$");
const reg2 = new RegExp("^([A-Z]+)$");
const reg3 = new RegExp("^([0-9]+)$");
return a1Notations.map((e) => {
const a1 = e.split("!");
const r = a1.length > 1 ? a1[1] : a1[0];
const [r1, r2] = r.split(":");
if (!r2) return [r1];
let rr;
if (reg1.test(r1) && reg1.test(r2)) {
rr = [r1.toUpperCase().match(reg1), r2.toUpperCase().match(reg1)];
} else if (reg2.test(r1) && reg2.test(r2)) {
rr = [
[null, r1, 1],
[null, r2, maxRow],
];
} else if (reg1.test(r1) && reg2.test(r2)) {
rr = [r1.toUpperCase().match(reg1), [null, r2, maxRow]];
} else if (reg2.test(r1) && reg1.test(r2)) {
rr = [[null, r1, maxRow], r2.toUpperCase().match(reg1)];
} else if (reg3.test(r1) && reg3.test(r2)) {
rr =
Number(r1) > Number(r2)
? [
[null, "A", r2],
[null, maxColumn, r1],
]
: [
[null, "A", r1],
[null, maxColumn, r2],
];
} else if (reg1.test(r1) && reg3.test(r2)) {
rr = [r1.toUpperCase().match(reg1), [null, maxColumn, r2]];
} else if (reg3.test(r1) && reg1.test(r2)) {
let temp = r2.toUpperCase().match(reg1);
rr =
Number(temp[2]) > Number(r1)
? [
[null, temp[1], r1],
[null, maxColumn, temp[2]],
]
: [temp, [null, maxColumn, r1]];
} else {
throw new Error(`Wrong a1Notation: ${r}`);
}
const obj = {
startRowIndex: Number(rr[0][2]),
endRowIndex: rr.length == 1 ? Number(rr[0][2]) + 1 : Number(rr[1][2]) + 1,
startColumnIndex: columnLetterToIndex(rr[0][1]),
endColumnIndex:
rr.length == 1
? columnLetterToIndex(rr[0][1]) + 1
: columnLetterToIndex(rr[1][1]) + 1,
};
let temp = [];
for (let i = obj.startRowIndex; i < obj.endRowIndex; i++) {
for (let j = obj.startColumnIndex; j < obj.endColumnIndex; j++) {
temp.push(columnIndexToLetter(j) + i);
}
}
return temp;
});
}
This method is used for consolidating the scattered A1Notations.
You can see my report using this method at https://medium.com/@tanaike/consolidate-scattered-a1notations-into-continuous-ranges-on-google-spreadsheet-using-google-apps-c9ce870dcb99.
/**
* ### Description
* This method is used for consolidating the scattered A1Notations.
*
* ### Sample script
* ```
* const a1Notations = ["C1", "I1", "B2", "C2", "D2", "E2", "F2", "H2", "I2", "C3", "D3", "F3", "H3", "I3", "C4", "D4", "E4", "H4", "K4", "C5", "E5", "F5", "I5", "J5", "K5", "F6", "I6", "K6", "D7", "E7", "I7", "J7", "K7", "D8", "I8", "J8", "D9", "J9", "K9", "D10"];
* const res = UtlApp.consolidateA1Notations(a1Notations);
* console.log(JSON.stringify(res));
* ```
*
* Result is as follows.
*
* ```
* ["C1:C5","K4:K7","I5:I8","D7:D10","I1:I3","D2:F2","H2:H4","J7:J9","D3:D4","E4:E5","F5:F6","B2","F3","J5","E7","K9"]
* ```
*
* @param {Array} a1Notations Scattered A1Notations.
* @return {Array} Array including the consolidated A1Notations.
*/
function consolidateA1Notations(array) {
return new ConsolidateA1Notations(array).run();
}
/**
* Consolidate Scattered A1Notations into Continuous Ranges on Google Spreadsheet.
*/
class ConsolidateA1Notations {
/**
*
* @param {Array} a1Notations Scattered A1Notations.
*/
constructor(a1Notations) {
this.a1Notations = a1Notations;
}
/**
* ### Description
* Main method.
*
* @returns {Array} Array including consolidated A1Notations.
*/
run() {
return this.getConsolidateA1Notations_(this.a1Notations);
}
/**
* ### Description
* Processing to consolidate A1Notations.
*
* @param {Array} a1Notations Array including the inputted A1Notations.
* @returns {Array} Array including consolidated A1Notations.
*/
getConsolidateA1Notations_(a1Notations) {
const expandedA1Notations = expandA1Notations(a1Notations).flat();
const orgGridRanges = expandedA1Notations.map((e) =>
convA1NotationToGridRange(e, 0)
);
const gridRanges = orgGridRanges
.sort((a, b) => (a.startColumnIndex < b.startColumnIndex ? 1 : -1))
.sort((a, b) => (a.startRowIndex > b.startRowIndex ? 1 : -1));
const maxRow = Math.max(
...gridRanges.map(({ endRowIndex }) => endRowIndex)
);
const maxCol = Math.max(
...gridRanges.map(({ endColumnIndex }) => endColumnIndex)
);
const obj = gridRanges.reduce((o, e) => {
const { startRowIndex, endRowIndex, startColumnIndex, endColumnIndex } =
e;
const key = `sr@${startRowIndex}_er@${endRowIndex}_sc@${startColumnIndex}_ec@${endColumnIndex}`;
o[key] = e;
return o;
}, {});
let copiedGridranges = gridRanges.slice();
let copiedObj = { ...obj };
const res = [];
do {
const resObj = this.getMaxAreas_({
obj: copiedObj,
gridRanges: copiedGridranges,
maxRow,
maxCol,
});
res.push(resObj);
if (resObj.length > 0) {
const removeKeys = resObj.flatMap(({ removeKeys }) =>
removeKeys.flatMap(({ remove }) => remove)
);
copiedObj = Object.fromEntries(
Object.entries(copiedObj).filter(([k]) => !removeKeys.includes(k))
);
copiedGridranges = Object.entries(copiedObj).map(([, v]) => v);
}
} while (Object.keys(copiedObj).length > 0);
const result = res.reduce((ar, e) => {
if (e.length > 0) {
e.forEach((f) => {
f.removeKeys.forEach((g) => {
if (g.topLeft == g.bottomRight) {
ar.push(g.topLeft);
} else {
ar.push(`${g.topLeft}:${g.bottomRight}`);
}
});
});
}
return ar;
}, []);
return result;
}
/**
* ### Description
* Calculated the maximum rectangles from the inputted A1Notations.
*
* @param {Object} object GridRange converted from the inputted A1Notations, the numbers of maximum row and column.
* @returns {Object} Object including the calculated maximum rectangles.
*/
getMaxAreas_(object) {
let { obj, gridRanges, maxRow, maxCol } = object;
const areas = gridRanges.map((o) => {
const { startRowIndex, startColumnIndex } = o;
const cc = [];
const rr = [];
for (let r = startRowIndex; r < maxRow; r++) {
for (let c = startColumnIndex; c < maxCol; c++) {
const key = `sr@${r}_er@${r + 1}_sc@${c}_ec@${c + 1}`;
if (!obj[key]) {
cc.push(c - startColumnIndex);
break;
} else if (c == maxCol - 1) {
cc.push(c - startColumnIndex + 1);
break;
}
}
}
for (let c = startColumnIndex; c < maxCol; c++) {
for (let r = startRowIndex; r < maxRow; r++) {
const key = `sr@${r}_er@${r + 1}_sc@${c}_ec@${c + 1}`;
if (!obj[key]) {
rr.push(r - startRowIndex);
break;
} else if (r == maxRow - 1) {
rr.push(r - startRowIndex + 1);
break;
}
}
}
const r1 = rr.reduce(
(oo, e) => {
if (e < oo.temp) {
oo.temp = e;
oo.v.push(oo.temp);
} else {
oo.v.push(oo.temp);
}
return oo;
},
{ temp: rr[0], v: [] }
);
const c1 = cc.reduce(
(oo, e) => {
if (e < oo.temp) {
oo.temp = e;
oo.v.push(oo.temp);
} else {
oo.v.push(oo.temp);
}
return oo;
},
{ temp: cc[0], v: [] }
);
const r0 = r1.v.indexOf(0);
const c0 = c1.v.indexOf(0);
if (r0 > -1) {
r1.v.splice(r0);
}
if (c0 > -1) {
c1.v.splice(c0);
}
const areas = [];
let c1v = c1.v.slice();
for (let x = 1; x <= r1.v[0]; x++) {
const y = c1v.shift();
const key = `sr@${o.startRowIndex + x - 1}_er@${
o.startRowIndex + x
}_sc@${o.startColumnIndex + y - 1}_ec@${o.startColumnIndex + y}`;
obj = Object.fromEntries(Object.entries(obj).filter(([k]) => k != key));
areas.push({ r: x, c: y, area: x * y });
}
const maxArea = Math.max(...areas.map((e) => e.area));
const tempMaxAreaObj = areas.filter((e) => e.area == maxArea);
if (tempMaxAreaObj.length == 0) {
return [
{
areas: [],
maxAreaObj: [],
o,
removeKeys: [{ remove: [], topLeft: "", bottomRight: "" }],
},
];
}
const maxAreaObj = [tempMaxAreaObj[0]];
const removeKeys = maxAreaObj.map((e) => {
const temp = [];
for (let i = o.startRowIndex; i < o.startRowIndex + e.r; i++) {
for (let j = o.startColumnIndex; j < o.startColumnIndex + e.c; j++) {
const key = `sr@${i}_er@${i + 1}_sc@${j}_ec@${j + 1}`;
temp.push(key);
}
}
return {
remove: temp,
topLeft: `${columnIndexToLetter(o.startColumnIndex)}${
o.startRowIndex + 1
}`,
bottomRight: `${columnIndexToLetter(o.startColumnIndex + e.c - 1)}${
o.startRowIndex + e.r
}`,
};
});
return { areas, maxAreaObj, o, removeKeys };
});
const a = Math.max(
...areas.map(({ maxAreaObj }) =>
maxAreaObj && maxAreaObj.length > 0 && maxAreaObj[0]?.area
? maxAreaObj[0].area
: -1
)
);
const result = areas.filter(
({ maxAreaObj }) =>
maxAreaObj &&
maxAreaObj.length > 0 &&
maxAreaObj[0]?.area &&
maxAreaObj[0]?.area == a
);
return result;
}
}
This method is used for converting Blob to the data URL.
/**
* ### Description
* This method is used for converting Blob to the data URL.
*
* ### Sample script
* ```
* const blob = Utilities.newBlob("sample", MimeType.PLAIN_TEXT);
* const res = UtlApp.blobToDataUrl(blob);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* data:text/plain;base64,c2FtcGxl
* ```
*
* @param {Blob} blob Blob
* @return {String} Data URL converted from Blob.
*/
function blobToDataUrl(blob) {
if (typeof blob != "object" || blob.toString() != "Blob") {
throw new Error("Please give Blob as an argument.");
}
const mimeType = blob.getContentType();
if (!mimeType) {
throw new Error(
"Given Blob has no mimeType. Please set the mimeType to Blob."
);
}
return `data:${mimeType};base64,${Utilities.base64Encode(blob.getBytes())}`;
}
This method is used for converting a string of the snake case to the camel case.
/**
* ### Description
* This method is used for converting a string of the snake case to the camel case.
*
* ### Sample script
* ```
* const res = UtlApp.snake_caseToCamelCase("sample1_sample2_sample3", true);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* Sample1Sample2Sample3
* ```
*
* @param {String} value String value of the snake case.
* @param {Boolean} upperCaseForTopCharacter When this is true, the top character is converted to upper case. The default is false.
* @return {String} String value converted from the snake case to the camel case.
*/
function snake_caseToCamelCase(value, upperCaseForTopCharacter = false) {
if (!value || typeof value != "string") {
throw new Error("Please set string value of the snake case.");
}
if (upperCaseForTopCharacter) {
value = value.replace(/^./, ([a]) => a.toUpperCase());
}
return value.replace(/_./g, ([, a]) => a.toUpperCase());
}
This method is used for converting a string of the camel case to the snake case.
/**
* ### Description
* This method is used for converting a string of the camel case to the snake case.
*
* ### Sample script
* ```
* const res = UtlApp.camelCaseTosnake_case("Sample1Sample2Sample3");
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* sample1_sample2_sample3
* ```
*
* @param {String} value String value of the snake case.
* @return {String} String value converted from the snake case to the camel case.
*/
function camelCaseTosnake_case(value) {
if (!value || typeof value != "string") {
throw new Error("Please set string value of the camel case.");
}
return value.replace(/.[A-Z]/g, ([a, b]) => `${a}_${b}`).toLocaleLowerCase();
}
This method is used for creating the form data to HTTP request from an object.
/**
* ### Description
* This method is used for creating the form data to HTTP request from an object.
*
* ### Sample script
* ```
* const obj = {
* key0: "value0",
* key1: {
* key1a: "value1a",
* key1b: "value1b",
* },
* key2: {
* key2a: {
* key2aa: "value2aa",
* key2ab: "value2ab",
* },
* key1b: "value1b",
* },
* key3: ["ar1", "ar2", "ar3"],
* };
* const res = UtlApp.createFormDataObject(obj, false);
* console.log(res);
* ```
*
* Result is as follows.
*
* ```
* {
* "key0": "value0",
* "key1[key1a]": "value1a",
* "key1[key1b]": "value1b",
* "key2[key2a][key2aa]": "value2aa",
* "key2[key2a][key2ab]": "value2ab",
* "key2[key1b]": "value1b",
* "key3[0]": "ar1",
* "key3[1]": "ar2",
* "key3[2]": "ar3"
* }
* ```
*
* When the 2nd argument is true, the followine result is obtained.
*
* ```
* key0=value0&key1[key1a]=value1a&key1[key1b]=value1b&key2[key2a][key2aa]=value2aa&key2[key2a][key2ab]=value2ab&key2[key1b]=value1b&key3[0]=ar1&key3[1]=ar2&key3[2]=ar3
* ```
*
* @param {Object} object Object for converting to the form data.
* @param {Boolean} asQueryParameters When this is true, the result is returned as the query parameter. The default is false.
* @return {Object|String}
*/
function createFormDataObject(object, asQueryParameters = false) {
if (!object || typeof object != "object") {
throw new Error("Please set an object.");
}
// ref: https://stackoverflow.com/a/19101235
Object.flatten = function (data) {
var result = {};
function recurse(cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for (var i = 0, l = cur.length; i < l; i++)
recurse(cur[i], prop + "[" + i + "]");
if (l == 0) result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop + "___" + p : p);
}
if (isEmpty && prop) result[prop] = {};
}
}
recurse(data, "");
return result;
};
const obj = Object.flatten(object);
const res = Object.entries(obj).map(([k, v]) => {
const [t1, ...t2] = k.split("___");
return [`${t1}${t2.map((e) => `[${e}]`).join("")}`, v];
});
if (asQueryParameters) {
return res.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
}
return Object.fromEntries(res);
}
If you want to use only one method in the above methods without a library, you can do this simply.
In that case, please copy and paste one of the methods from the above scripts. At this time, there are several methods using the other methods. Please copy and paste all dependence methods.
If you want to run the method directly, please remove the identification of the library UtlApp
. By this, the script is run.
For example, when you want to directly use the following method is2DimensionalArray
without the library, please copy and paste the following script to the script editor.
/**
* ### Description
* When the inputted array is 2 dimensional array, true is returned.
*
* ### Sample script
* ```
* const array1 = ["", [1, 2, 3], [1, 2, 3], [1, 2, 3]];
* const res1 = UtlApp.is2DimensionalArray(array1); // false
*
* const array2 = [[1, 2, 3], [1, 2], [1]];
* const res2 = UtlApp.is2DimensionalArray(array2); // true
* ```
*
* @param {Array} array 2 dimensional array.
* @return {Boolean} When the inputted array is 2 dimensional array, true is returned.
*/
function is2DimensionalArray(array) {
return array.every((r) => Array.isArray(r));
}
When you run this method, please use it with the following script.
function sample() {
const array1 = ["", [1, 2, 3], [1, 2, 3], [1, 2, 3]];
const res1 = is2DimensionalArray(array1); // false
}
I believe that these methods will help to develop the applications created by Google Apps Script. So, I would like to grow this. If you want to add more methods, I welcome your contributions. At that time, please do "pull request" or email me ( tanaike@hotmail.com ). By testing your method, I would like to add it. Your name is also added as a contributor.
The policy of these methods is not to use the scopes. So, when you add your method, please confirm that your proposed method uses no scopes. And also, please check your JSDoc.
-
v1.0.0 (May 13, 2023)
- Initial release.
-
v1.0.1 (November 12, 2023)
- From this report by Max-Makhrov, a bug of
convA1NotationToGridRange
was removed.
- From this report by Max-Makhrov, a bug of
-
v1.0.2 (January 25, 2024)
- 2 methods of dotProduct and cosineSimilarity were added.
-
v1.0.3 (February 5, 2024)
- Methods of addQueryParameters was updated.
-
v1.0.4 (April 13, 2024)
- Updated the methods of convGridRangeToA1Notation. When the sheet name is not given, only the A1Notation is returned.
-
v1.0.5 (April 16, 2024)
- Methods of consolidateA1Notations was added. When this method is used, the scattered A1Notations can be consolidated.
-
v1.0.6 (May 4, 2024)
- A method of blobToDataUrl was added. When this method is used, the Blob data can be converted to the data URL.
-
v1.0.7 (September 4, 2024)
- Following 3 methods were added.
- snake_caseToCamelCase: This method is used for converting a string of the snake case to the camel case.
- camelCaseTosnake_case: This method is used for converting a string of the camel case to the snake case.
- createFormDataObject: This method is used for creating the form data to HTTP request from an object.