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);
Related
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)
}
I'm trying to filter the last column on a worksheet but I can't seem to get the Index of the column. To be clear, I need the index relative to the worksheet, no the range. I used VisibleView to find the Column, but there may be hidden rows, so my plan is to then load that column via getRangeByIndexes but I need the relative columnIndex to the worksheet.
I've tried a bunch of variations of the below, but I either get Object doesn't support 'getColumn' or columnIndex is undefined
Note: In the below example I've hardcoded 7 as that will be the last column relative to the VisibleView (Columns and rows are already hidden), but I'd like this to by dynamic for other functions and just returnthe "last visible column index".
var ws = context.workbook.worksheets.getActiveWorksheet()
var visible_rng = ws.getUsedRange(true).getVisibleView()
visible_rng.load(["columnCount", "columnIndex"])
await context.sync();
console.log('visible_rng.columnIndex')
console.log(visible_rng.getCell(0,7).columnIndex)
console.log(visible_rng.getColumn(7).columnIndex)
Well this method seems a bit hacky, please share if you know a better way! But, first thing I found was that getVisibleView only metions rows in the Description.
Represents the visible rows of the current range.
I decided to try getSpecialCells and was able to load the address property. I then had to use split and get the last column LETTER and convert this to the Index.
I also wanted the columnCount but this wasn't working w/ getSpecialCells so I polled that from getVisibleView and return an Object relating to Visible Views that I can build on the function later if I need more details.
Here it is:
async function Get_Visible_View_Details_Obj(context, ws) {
var visible_rng = ws.getUsedRange(true).getSpecialCells("Visible");
visible_rng.load("address")
var visible_view_rng = ws.getUsedRange(true).getVisibleView()
visible_view_rng.load("columnCount")
await context.sync();
var Filter_Col_Index = visible_rng.address
var Filter_Col_Index = Filter_Col_Index.split(",")
var Filter_Col_Index = Filter_Col_Index[Filter_Col_Index.length - 1]
var Filter_Col_Index = Filter_Col_Index.split("!")[1]
if (Filter_Col_Index.includes(":") == true) {
var Filter_Col_Index = Filter_Col_Index.split(":")[1]
}
var Filter_Col_Index = Get_Alpha_FromString(Filter_Col_Index)
var Filter_Col_Index = Get_Col_Index_From_Letters(Filter_Col_Index)
var Filter_Col_Index_Obj = {
"last_col_ws_index": Filter_Col_Index,
"columnCount": visible_view_rng.columnCount,
}
return Filter_Col_Index_Obj
}
Helper Funcs:
function Get_Alpha_FromString(str) {
return str.replace(/[^a-z]/gi, '');
}
function Get_Col_Index_From_Letters(str) {
str = str.toUpperCase();
let out = 0, len = str.length;
for (pos = 0; pos < len; pos++) {
out += (str.charCodeAt(pos) - 64) * Math.pow(26, len - pos - 1);
}
return out - 1;
}
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;
}
While reading data from excel file, we can use
no_of_rows = sheet.getPhysicalNumberOfRows();
to get the number of rows having data. Is there a way to get the count of rows having data in the first column of the sheet?
there not any method to get no of rows for particular column in apache poi. you manually need to traverse the row each one by one and then you can get it.
to get row count for particular column you can refer to this link
Have every column different row number?
int rows; // No of rows
rows = sheet.getPhysicalNumberOfRows();
int cols = 0; // No of columns
int tmp = 0;
for (int i = 0; i < rows; i++) {
row = sheet.getRow(i);
if (row != null) {
tmp = sheet.getRow(i).getPhysicalNumberOfCells();
if (tmp > cols) {
cols = tmp;
}
}
}
if (cols == 23) {
row = sheet.getRow(0);
if ("I.D.Number".equals(row.getCell(0).toString())) {
if ("Name".equals(row.getCell(1).toString())) {
if ("Surname".equals(row.getCell(2).toString())) {
applicaple = true;
}
}
}
}
Iterator rowsExcel = sheet.rowIterator();
XSSFRow row2 = null;
if (uygunluk == true) {
while (rowsExcel.hasNext()) {
row2 = (XSSFRow) rowsExcel.next();
if (row2.getRowNum() != 0) {
if (fmt.formatCellValue(row2.getCell(0)) == null || (fmt.formatCellValue(row2.getCell(0)).trim().equalsIgnoreCase(""))) {
System.out.print.ln("warn!!");
return;
} else if (fmt.formatCellValue(row2.getCell(0)) != null || !(fmt.formatCellValue(row2.getCell(0)).trim().equalsIgnoreCase(""))) {
idInfo = fmt.formatCellValue(row2.getCell(0);
}
I've written a script in google docs to use =importXML function and return the value on its own rather than leaving the function there loading on opening and every hour slowing the thing down.
Basically it uses the data in row D (hidden), sticks the formula in B2, then, overwrites B2 with the value of the formula. I then wanted to repeat this going down the list but just didn't know how - currently I've just repeated the function and changed the cell ID, which I'm aware is a travesty. Could someone guide a noob on how to do it efficiently?
function pullValues()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cellref1 = sheet.getRange("D2");
var ID = cellref1.getValue();
var apistring = "http://api.eve-central.com/api/marketstat?usesystem=30000142&typeid=" + ID;
var command = "importxml(\"" + apistring + "\", \"/evec_api/marketstat/type/sell/min\")";
var cellref2 = sheet.getRange("B2");
cellref2.setFormula(command);
var val = cellref2.getValue();
cellref2.setValue(val);
}
https://docs.google.com/spreadsheet/ccc?key=0AjZlH_sGnj6vdDU4QWdyZTVTd2E4RUFXZnVEZlZJS3c#gid=0
You have to iterate through all rows in the spreadsheet using a for loop. There are many ways to do it, the following code is one of them:
function pullValues()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var startRow = 2; // First row of data to process
var lastRow = sheet.getLastRow(); // Last row of data to process
for (var i = startRow; i <= lastRow; i++)
{
var cellref1 = sheet.getRange("D" + i);
var ID = cellref1.getValue();
var apistring = "http://api.eve-central.com/api/marketstat?usesystem=30000142&typeid=" + ID;
var command = "=ImportXML(\"" + apistring + "\", \"/evec_api/marketstat/type/sell/min\")";
var cellref2 = sheet.getRange("B" + i);
cellref2.setFormula(command);
var val = cellref2.getValue();
cellref2.setValue(val);
}
}