Office Scripts (Sum Cells By Color) - excel

I'm trying convert one code that it make a sum of cells by color in VBA, but i need to use the same code or action from code in Office Scripts, i dont know how is the structure in this plataform, maybe, can you help me to do it?
the code in VBA is this:
code
Function SumByColor(Cellcolor As Range, RangeSum As Range) As Double
Dim cell As Range
For Each cell In RangeSum
If celda.Interior.ColorIndex = Celdacolor.Cells(1, 1).Interior.ColorIndex Then SumByColor = SumByColor+ cell
Next cell
Set cell = Nothing
End Function
So i need to use this code in office scripts

Here is one way to write your function in OfficeScript and how to call it -
function main(workbook: ExcelScript.Workbook) {
let sheet = workbook.getActiveWorksheet();
console.log (sumByColor(sheet.getRange("E41"), workbook.getSelectedRange()))
}
function sumByColor(cellColor:ExcelScript.Range, rangeSum:ExcelScript.Range):number {
let value = 0;
let rowCount = rangeSum.getRowCount();
let columnCount = rangeSum.getColumnCount();
let colorToCheck = cellColor.getFormat().getFill().getColor();
// loop through each cell in rangeSum
for (let row=0; row<rowCount; row++)
for (let column = 0; column < columnCount; column++)
{
if (rangeSum.getCell(row,column).getFormat().getFill().getColor() == colorToCheck)
value += rangeSum.getCell(row, column).getValue() as number
}
return value;
}

Thanks for your help. My final code is it :
function main(workbook: ExcelScript.Workbook) {
let sheet = workbook.getActiveWorksheet();
var cont = 2;
const celdas = ['B10', 'C10', 'D10', 'E10', 'F10', 'G10', 'H10', 'I10', 'J10', 'K10', 'L10', 'M10'];
celdas.forEach(celda => {
let valCel = celda;
let startingCell = sheet.getRange(valCel);
var ranguito = "O" + cont.toString();
let rangeDataValue = sheet.getRange(ranguito).getValue() as string;
console.log(sumByColor(sheet.getRange("Q3"), sheet.getRange(rangeDataValue), startingCell))
cont = cont + 1;
});
}
function sumByColor(cellColor: ExcelScript.Range, rangeSum: ExcelScript.Range, writeCell: ExcelScript.Range) {
let value = 0;
let rowCount = rangeSum.getRowCount();
let columnCount = rangeSum.getColumnCount();
let colorToCheck = cellColor.getFormat().getFill().getColor();
// loop through each cell in rangeSum
for (let row = 0; row < rowCount; row++)
for (let column = 0; column < columnCount; column++) {
if (rangeSum.getCell(row, column).getFormat().getFill().getColor() == colorToCheck) {
var total = rangeSum.getCell(row, column).getValue() as string;
value = value + parseFloat(total);
}
}
writeCell.setValue(value);
console.log(value)
}

Related

Split column into rows using excel office script

I would like to split a column into rows using excel office script but I cant figure out how.
I have a schedule in below format in excel which I would like to split into columns.
Original table
Final table need to be like this.
Final Table
Is this achievable, if yes, could someone please share the code
Based on your description I made an attempt at a solution with Office Scripts. It takes a table like this:
and outputs a new table on a new worksheet, like this:
For better or worse I attempted to keep the logic in the workbook via formulas derived from the first table and output into the second. This formula logic would need to be rewritten if there is more than one activity per day.
I'm not a developer but I can already see areas in this Office Script that need improvement:
function main(workbook: ExcelScript.Workbook) {
// delete new worksheet if it already exists so the rest of the script can run effectively
// should you need to retain data simply rename worksheet before running
if (workbook.getWorksheet("My New Sheet") != undefined) {
workbook.getWorksheet("My New Sheet").delete()
}
// assumes your original data is in a table
let myTable = workbook.getTable("Table1");
let tableData = myTable.getRangeBetweenHeaderAndTotal().getValues();
// extract the dates as excel serial numbers
let allDates:number[] = [];
for (let i = 0; i < tableData.length; i++) {
allDates.push(tableData[i][2], tableData[i][3]);
}
let oldestDate = Math.min(...allDates);
let newestDate = Math.max(...allDates);
let calendarSpread = newestDate-oldestDate+2;
// construct formula from the tableData
// first add a new 'column' to tableData to represent the days of the week (as numbers) on which the activity is planned. this will be an array added to each 'row'.
for (let r = 0; r < tableData.length; r++) {
tableData[r].push(findDay(tableData[r][1]));
}
// start a near blank formula string
let formulaText:string = '=';
// use the following cell reference
let cellRef = 'C2';
// construct the formual for each row in the data and with each day of the week for the row
let rowCount:number;
for (let r = 0; r < tableData.length; r++) {
if (tableData[r][4].length > 1) {
formulaText += 'IF(AND(OR(';
} else {
formulaText += 'IF(AND(';
}
for (let a=0; a < tableData[r][4].length; a++) {
formulaText += 'WEEKDAY(' + cellRef + ')=' + tableData[r][4][a].toString();
if (a == tableData[r][4].length - 1 && tableData[r][4].length > 1) {
formulaText += '),';
} else {
formulaText += ', ';
}
}
formulaText += cellRef + '>=' + tableData[r][2] + ', ' + cellRef + '<=' + tableData[r][3] + '), "' + tableData[r][0] + '", ';
rowCount = r+1;
}
formulaText += '"-"';
for (let p=0; p<rowCount; p++) {
formulaText += ')';
}
// create a new sheet
let newSheet = workbook.addWorksheet("My New Sheet");
// add the header row
let header = newSheet.getRange("A1:C1").setValues([["Activity", "Day", "Date"]])
// insert the oldest date into the first row, then add formula to adjacent cells in row
let firstDate = newSheet.getRange("C2")
firstDate.setValue(oldestDate);
firstDate.setNumberFormatLocal("m/d/yyyy");
firstDate.getOffsetRange(0, -1).setFormula("=C2");
firstDate.getOffsetRange(0, -1).setNumberFormatLocal("ddd");
firstDate.getOffsetRange(0, -2).setFormula(formulaText);
// use autofill to copy results down until the last day in the sequence
let autoFillRange = "A2:C" + (calendarSpread).toString();
firstDate.getResizedRange(0, -2).autoFill(autoFillRange, ExcelScript.AutoFillType.fillDefault);
// convert the the range to a table and format the columns
let outputTable = newSheet.addTable(newSheet.getUsedRange(), true);
outputTable.getRange().getFormat().autofitColumns();
//navigate to the new sheet
newSheet.activate();
}
// function to return days (as a number) for each day of week found in a string
function findDay(foo: string) {
// start with a list of days to search for
let daysOfWeek:string[] = ["Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"];
//create empty arrays
let searchResults:number[] = [];
let daysFound:number[] = [];
// search for each day of the week, this will create an array for each day of the week where the value is -1 if the day is not found or write the position where the day is found
for (let d of daysOfWeek) {
searchResults.push(foo.search(d));
}
// now take the search results array and if the number contained is greater than -1 add it's position+1 to a days found array. this should end up being a list of numbered days of the week found in a string/cell
for (let i = 0; i < searchResults.length; i++) {
if (searchResults[i] > -1) {
daysFound.push(i + 1);
}
}
return daysFound
}
I managed to get this working using below code for anyone who might be interested.
workbook.getWorksheet('UpdatedSheet')?.delete()
let usedRange = workbook.getActiveWorksheet().getTables()[0].getRangeBetweenHeaderAndTotal();
let newString: string[][] = [];
usedRange.getValues().forEach(row => {
let daysRows: string[][] = [];
let days = row[1].toString().split(',');
days.forEach(cellValue => {
if (cellValue != ' ') {
let eachDayData = row.toString().replace(row[1].toString(), cellValue).split(',');
daysRows.push(eachDayData);
}
});
daysRows.forEach(actualDay => {
const effDate = new Date(Math.round((actualDay[2] as unknown as number - 25569) * 86400 * 1000))
const disDate = new Date(Math.round((actualDay[3] as unknown as number - 25569) * 86400 * 1000))
getDatesInRange(effDate, disDate).forEach(element => {
let options = { weekday: 'short' }
if (element.toLocaleDateString('en-GB', options) == actualDay[1]) {
let datas = actualDay.toString().replace(actualDay[2], element.toDateString()).split(',')
datas.pop()
newString.push(datas)
}
});
});
});
workbook.getWorksheet('UpdatedSheet')?.delete()
let workSheet = workbook.addWorksheet('UpdatedSheet');
workSheet.activate();
let headers = workSheet.getRange('A1:C1').setValue([['Activity', 'Day', 'Date']])
let range = workSheet.getRange('A2');
let resizedRange = range.getAbsoluteResizedRange(newString.length, newString[0].length);
resizedRange.setValues(newString);
let tableRange = workSheet.getRange("A2").getSurroundingRegion().getAddress();
let newTable = workbook.addTable(workSheet.getRange(tableRange), true);
newTable.setName('updatedTable');
workSheet.getRange().getFormat().autofitColumns()
}
function getDatesInRange(startDate: { getTime: () => string | number | Date; }, endDate: string | number | Date) {
const date = new Date(startDate.getTime());
const dates: Date[] = [];
while (date <= endDate) {
dates.push(new Date(date));
date.setDate(date.getDate() + 1);
}
return dates;
}

Logging to console whenever a cell value exceeds character limit

I have this pseudocode in mind to log an error whenever the length of a cell value within the ItemNumber column exceeds 10 characters. I would also like to display the offending row number as well.
Haven't got a chance to test this out fully, but is it possible?
Any help would be much appreciated!
let itemDesc = newTable.getColumnByName("ItemNumber")
for (let i = itemDesc.length - 1; i >= 0; i--) {
let event = itemDesc[i];
let rowNumber = ???
if (event.length > 10) {
console.log(`Character count exceeds 10 characters at Rows: ${rowNumber}`);
}
}
This code should work. It assumes that the data is in an Excel table / ListObject:
function main(workbook: ExcelScript.Workbook) {
let ws = workbook.getActiveWorksheet();
let newTable = ws.getTable("Table1");
let itemDesc = newTable.getColumnByName("ItemNumber");
let rang = itemDesc.getRange();
let vals = rang.getValues();
let rowNumbers = []
vals.forEach((item, rowNumber) => {
if (rowNumber > 0 && item[0].toString().length > 10) {
rowNumbers.push(rowNumber)
}
})
rowNumbers.forEach(row => console.log(`Character count exceeds 10 characters at Rows: ${row}`)) //prints each offending row on a separate line
console.log(`Character count exceeds 10 characters at Rows: ${rowNumbers.toString()}`); //prints all offending rows on one line
}

Getting all values from all table

i couldn't ask my question very well. I put here a table that i want to create in google sheet, excel or other. Anyone can help me please?
street name
number
Impasse Malabane
9
Impasse Malabane
Impasse du Puech
Impasse Bimet
Rue Levers
Impasse du Puech
1 bis
9
1 bis
6
26
Rue Levers
26
5
2 bis
2
28
Impasse Bimet
6
8
1
Impasse Bimet
2
8 bis
Impasse Malabane
5
Impasse du Puech
2 bis
Impasse Malabane
8
Impasse Malabane
8 bis
Impasse du Puech
1
Rue Levers
28
In Google Sheets, assuming that your original headers "Street Name" and "Number" are in A1 and B1 (with the data in A2:B), place this in D2:
=ArrayFormula(TRANSPOSE(TRIM({UNIQUE(FILTER(A2:A,A2:A<>"")),TO_TEXT(SPLIT(TRANSPOSE(TRIM(SPLIT(TEXTJOIN("| ",1,{IFERROR(SUBSTITUTE(VLOOKUP(UNIQUE(FILTER(A2:A,A2:A<>""))&TRANSPOSE(UNIQUE(FILTER(B2:B,B2:B<>""))),A2:A&B2:B,1,FALSE),UNIQUE(FILTER(A2:A,A2:A<>"")),"")),SEQUENCE(COUNTA(UNIQUE(FILTER(A2:A,A2:A<>""))),1,1,0)})&"|","| 1|",0,1))),"|"))})))
This one formula will generate all results. As you add new data in A2:B, it will be added without any need to edit the formula.
This should work in Google Apps Script:
function myFunction() {
// initialize
var ss = SpreadsheetApp.getActiveSheet();
var lr = ss.getLastRow();
var values = ss.getRange(2,1,lr-1,2).getValues();
console.log(values.sort());
var result = [];
console.log(result);
var prevCol = '';
// populate array
for (i = 0; i < values.length; i++) {
if (values[i][0] != prevCol) {
result.push([values[i][0], values[i][1]]);
}
else {
result[result.length-1].push(values[i][1]);
}
prevCol = values[i][0];
}
console.log(result);
// transpose array
var newArray = [];
for(var i = 0; i < result.length; i++){
newArray.push([]);
};
for(var i = 0; i < result.length; i++){
for(var j = 0; j < result.length; j++){
newArray[j].push(result[i][j]);
};
};
// write to range
var dest = ss.getRange(2,4,newArray[0].length,newArray.length);
dest.setValues(newArray);
}
It's a little difficult to understand the question, but I think you are trying to convert a 2-column table into a multi-column table with data.
You can do this with an array formula. First, copy all your distinct street names into a single row. In the picture below, this was done in cells F1:I1.
Cell F2 formula:
{=IFERROR(INDEX($B$2:$B$9999, SMALL(IF($F$1=$A$2:$A$9999, ROW($A$2:$A$9999)-ROW($A$2)+1), ROW(1:1))),"")}
You need to press CTRL+SHIFT+ENTER to get those curly brackets to make this an array formula. Drag it down as far as you need, well beyond as many rows as you would expect. It's wrapped in an IFERROR handler to show a blank if no more results are found.
The formulas for the other columns are similar. You just need to change the reference from $F$1 to $G$1 and so on, and also repeat the array trick.
Also, if you have more than 9999 rows, just adjust the end row number in the formula.
This VBA script does it. Assuming your data starts in A1 in Sheet1.
Sub transform()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim first_col As String: first_col = "A"
Dim ws2 As Worksheet
Dim c_last As Range
Dim c As Range
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim key As Variant
Dim arr() As String
Set c_last = ws.Cells(ws.Rows.Count, first_col).End(xlUp)
For Each c In ws.Range(first_col & 2, c_last)
If dict.Exists(Trim(c)) Then
dict.Item(Trim(c)) = dict.Item(Trim(c)) & "$" & CStr(c.Offset(, 1))
Else
dict.Item(Trim(c)) = CStr(c.Offset(, 1))
End If
Next
Set ws2 = ws.Parent.Sheets.Add(After:=ws.Parent.Worksheets(ws.Parent.Worksheets.Count))
Set c = ws2.Range("A1")
For Each key In dict.Keys()
c = key
arr = Split(dict.Item(key), "$")
c.Offset(1).Resize(UBound(arr) + 1) = Application.Transpose(arr)
Set c = c.Offset(, 1)
Next
End Sub
Before:
After:
A correction of Carlos' proposal (Google Sheets) :
function myFunction() {
// initialize
var ss = SpreadsheetApp.getActiveSheet();
var lr = ss.getLastRow();
var values = ss.getRange(2,1,lr-1,2).getValues();
values.sort();
var result = [];
var prevCol = '';
// populate array
for (i = 0; i < values.length; i++) {
if (values[i][0] != prevCol) {
result.push([values[i][0], values[i][1]]);
}
else {
result[result.length-1].push(values[i][1]);
}
prevCol = values[i][0];
}
// determine maximum length
var max = 0 ;
for(var i = 0; i < result.length; i++){
max=Math.max(max,result[i].length);
};
// complete the array by replacing null values with an empty string
for (var i = 0; i < result.length; i ++) {
for (var j = 0; j < max; j ++) {
if (result[i][j] == null) {result[i][j] = "";}
}
}
// transpose array
var result = transpose(result);
// write to range
ss.getRange(2,4,result.length,result[0].length).setValues(result);
}
function transpose(a){
return Object.keys(a[0]).map(function (c) { return a.map(function (r) { return r[c]; }); });
}

Read column wise values using XLSX

How to read Column wise values from excel using nodejs(node-xlsx) ? Please advise.
I want to read Col A data, then Col B and so on, if any data exists in other columns, then put data in array.
I am able to read A1, B1, A2, B2... this way but not A1, A2, A3... then B1, B2, B3... etc.
Sample column wise data
I did custom way but not able to go to col C dynamically.
const xlsxfile = require("xlsx");
var arr = [];
const spreadsheet = xlsxfile.readFile('./Code.xlsx');
const sheets = spreadsheet.SheetNames;
console.log('Sheet Names -- ' + sheets);
const firstSheetName = sheets[0];
const firstSheet = spreadsheet.Sheets[firstSheetName];
console.log(firstSheet);
for (z in firstSheet) {
if (z[0] === '!') continue;
//parse out the column, row, and value
var tt = 0;
for (var i = 0; i < z.length; i++) {
if (!isNaN(z[i])) {
tt = i;
break;
}
};
var col = z.substring(0, tt);
var row = parseInt(z.substring(tt));
for (; ; row++) { // looping over all rows in a column
const firstCol = firstSheet[col+''+row];
if (!firstCol) {
break;
}
let value = firstCol.w;
//console.log(value);
if (value)
if (!arr.includes(value))
arr.push(value);
else
continue;
else
break;
}
}
console.log('final array = '+arr);
I somehow achieved my goal in very bad way. In top most loop it's always trying to get A1, B1, C1 like that. But in between I hijacked and looping all rows.
So in that way, I am keeping one track of what's my last column and comparing in next is same column or new column. if same column, I am existing.
But is there any better way to get all values may be in array format column wise?
const xlsxfile = require("xlsx");
var arr = [];
const spreadsheet = xlsxfile.readFile('./Code.xlsx');
const sheets = spreadsheet.SheetNames;
console.log('Sheet Names -- ' + sheets);
const firstSheetName = sheets[0];
const firstSheet = spreadsheet.Sheets[firstSheetName];
console.log(firstSheet);
let earliercol = []; // keeping a track
for (z in firstSheet) {
if (z[0] === '!') continue;
//parse out the column, row, and value
let tt = 0;
for (let i = 0; i < z.length; i++) {
if (!isNaN(z[i])) {
tt = i;
break;
}
};
let col = z.substring(0, tt);
let row = parseInt(z.substring(tt));
if (earliercol.includes(col)) // checking current col already traversed or not
break;
earliercol.push(col); // assigning current col name
//var value = worksheet[z].v;
for (; ; row++) {
//const firstCol = firstSheet['A' + i];
const firstCol = firstSheet[col+''+row];
if (!firstCol) {
break;
}
let value = firstCol.w;
//console.log(value);
if (value)
if (!arr.includes(value))
arr.push(value);
else
continue;
else
break;
}
}
console.log('final array = '+arr);

how to read excel's data to json string?

I have a excel like this:
A B C
a1 b1 c1
c2
a2 b2 c3
c4
Now i want to read the data to json string using c# language like this:
"{'a1':{'b1':['c1','c2']},'a2':{'b2':['c3','c4']}}"
I want to do it because i have to to parse this data to json in javascript,
the data i want to have is:
var obj = {
a1:{
b1:[c1,c2]
},
a2:{
b2:[c3,c4]
}}
I have thought,but i have no thinking.
part of my c# code is this:
public string SheetToData(Worksheet worksheet)
{
int rowsCount = worksheet.NotEmptyRowMax;
int columnsCount = worksheet.NotEmptyColumnMax;
if (rowsCount < 1 || columnsCount < 1)
{
return;
}
for (int r = 0; r < rowsCount + 1; r++)
{
var row = worksheet.Rows[r];
for (int c = 0; c < columnsCount + 1; c++)
{
var cell = row[c];
var value = (cell.ValueAsString ?? "").Trim();
}
}
}
Thanks for your help who can help me solve this problem
The row variable is not needed. You can access a cell value through Cells collection:
for (int r = 0; r < rowsCount + 1; r++)
{
for (int c = 0; c < columnsCount + 1; c++)
{
var value = (worksheet.Cells[r,c].Value ?? "").ToString().Trim();
}
}

Resources