ExcelJS write an array to excel - javascript

I'm using an API where i fetch information and it stores in an array called res. I want this array to be written to an excel file. The first information in the array should be in A2 and the second in B2 etc etc.
This is my code:
const api2 = require("fordonsuppgifter-api-wrapper");
(async () => {
console.log("fetching vehicle info");
var res = await api2.GetVehicleInformation("XMP433");
console.log(res);
})();
const Excel = require('exceljs');
const fileName = 'simple.xlsx';
const wb = new Excel.Workbook();
const ws = wb.addWorksheet('My Sheet');
const r3 = ws.getRow(3);
r3.values = [1, 2, 3, 4, 5, 6];
wb.xlsx
.writeFile(fileName)
.then(() => {
console.log('file created');
})
.catch(err => {
console.log(err.message);
});
The API is working, but I can't get it to write to an excel file.

You can create excel file with both combination call.
The exceljs library need to moving cell write function
ws.getCell(<cell_address>).value = <assigned_value>
cell_address needs to increase number for moving down(increase row number)
Example: A1 -> A2 for next row.
The columnName() can convert from integer to Excel Column Address.
Example
columnName(1) -> A
columnName(2) -> B
You needs to convert Object() from JSON to Object type
Then can get the JSON key's list
Object.keys(carData)
Final Code
const Excel = require('exceljs');
const api2 = require("fordonsuppgifter-api-wrapper");
const columnName = (index) => {
var cname = String.fromCharCode(65 + ((index - 1) % 26));
if (index > 26)
cname = String.fromCharCode(64 + (index - 1) / 26) + cname;
return cname;
}
const getCarInformation = async (keyword) => {
try {
const res = await api2.GetVehicleInformation(keyword);
return Promise.resolve(res);
} catch (error) {
return Promise.reject(error);
}
}
// Search car by keyword
getCarInformation("XMP433")
.then((carData) => {
const fileName = 'simple.xlsx';
const workbook = new Excel.Workbook();
// make workbook with 'car' name
const ws = workbook.addWorksheet("car")
// Start Cell A1 for title column
let headerColumn = 1
let section_number = 1
for (let key in carData) {
// title column, example : A1 = Sammanfattning
ws.getCell(columnName(headerColumn) + String(section_number)).value = key
subItems = carData[key]
row_number = section_number
for (let subKey in subItems) {
// Sub title Cell Bx, example : B1 = Registreringsnummer
ws.getCell(columnName(headerColumn + 1) + String(row_number)).value = subKey
// value Cell Cx, example : C1 = '(2*) XMP433'
ws.getCell(columnName(headerColumn + 2) + String(row_number)).value = subItems[subKey]
row_number++;
}
// Jump to next title
section_number = section_number + Object.keys(carData[key]).length;
}
workbook.xlsx
.writeFile(fileName)
.then(() => {
console.log('file created');
})
.catch(err => {
console.log(err.message);
});
})
.catch(err => {
console.log(err.message);
});
Result in simple.xlsx
You need to click on header for expanding the width of a column to fit its contents after saved file.

Related

variable is getting overwritten when passed into child function

Can someone explain how the rowsx variable is getting overwritten when passed into the generateBulkInsertQueries function? I expect the length value of rowsx to be 1 throughout the entire piece of code
//HELPER FUNCTIONS-------------------------------------------
var generateBulkInsertQueries = (
data,
dbname,
tablename,
colnames,
batchSize=10
) => {
if ([dbname, tablename].some((element) => element.indexOf('..') > -1))
throw `generateInsertQueries() expected distinct db (${dbname}) and tables (${tablename})`;
//if the colnames param is passed use it, otherwise generate colnames form the data
if (!dbname || dbname.length < 1) throw `generateInsertQueries(): invalid dbname (${dbname})`;
let cols = colnames ? colnames : Object.keys(data[0]); //transform keys are column names
//transform values are inserted strings
let values = [];
for(let rows of splitArrayIntoChunks(data,batchSize))
{
let ss=rows.map(r=>`(${sqlConcatOneLine(r,cols)})`)
values.push({outdata:`insert into [${dbname}].[dbo].[${tablename}](${cols.map((c) => `[${c}]`).join(',')}) values ${ss.join(',')}`,indata:rows});
};
return values
};
//demo: let [list,chunkSize] = [[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], 6];
//https://stackoverflow.com/questions/8495687/split-array-into-chunks
var splitArrayIntoChunks=(list,chunkSize)=>{
list = [...Array(Math.ceil(list.length / chunkSize))].map(_ => list.splice(0,chunkSize))
//console.log(list);
return list
}
var sqlConcatOneLine=(row,cols)=>{
return cols
.map((k) => {
let ret = "''";
try {
ret = "'" + row[k].toString().replace(/'/g, '').replace(/\n/g, '').replace(/\r/g, '').toString() + "'";
} catch (e) {
// console.log(
// `WARN: err inserting value (likely null) generateSQLBulkInsertQuery: ${e}`
// );
}
return ret;
})
.join(',')
}
//EXAMPLE OF ERROR STARTS HERE--------------------------------
(async ()=>{
//init the var
var rowsx = [{ col1: "1", col2: "2" }];
console.log({rowsxlen:rowsx.length,here:1})
var sqliq = generateBulkInsertQueries(rowsx, 'xx', 'yy', ['col1','col2'],1);
console.log({rowsxlen:rowsx.length,here:2})
if(rowsx.length!=1) console.log(`failed. rowsx.length!=1`)
else console.log(`success!!!!`)
})()
You've got a splice() (doc) applied to the first param of splitArrayIntoChunks, which is the data param of the containing function, which is the rowsx array you're hoping not to mutate.
So the approach to chunking used here mutates its input. Either do it with a non-destructive reduce (see here, from the same article you used), or make a copy the input over which to apply the splice. (Demo'd that below)
//HELPER FUNCTIONS-------------------------------------------
var generateBulkInsertQueries = (
data,
dbname,
tablename,
colnames,
batchSize=10
) => {
if ([dbname, tablename].some((element) => element.indexOf('..') > -1))
throw `generateInsertQueries() expected distinct db (${dbname}) and tables (${tablename})`;
//if the colnames param is passed use it, otherwise generate colnames form the data
if (!dbname || dbname.length < 1) throw `generateInsertQueries(): invalid dbname (${dbname})`;
let cols = colnames ? colnames : Object.keys(data[0]); //transform keys are column names
//transform values are inserted strings
let values = [];
for(let rows of splitArrayIntoChunks(data,batchSize))
{
let ss=rows.map(r=>`(${sqlConcatOneLine(r,cols)})`)
values.push({outdata:`insert into [${dbname}].[dbo].[${tablename}](${cols.map((c) => `[${c}]`).join(',')}) values ${ss.join(',')}`,indata:rows});
};
return values
};
//demo: let [list,chunkSize] = [[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], 6];
//https://stackoverflow.com/questions/8495687/split-array-into-chunks
var splitArrayIntoChunks=(list,chunkSize)=>{
let spliceMe = [...list];
return [...Array(Math.ceil(list.length / chunkSize))].map(_ => spliceMe.splice(0,chunkSize))
//console.log(list);
}
var sqlConcatOneLine=(row,cols)=>{
return cols
.map((k) => {
let ret = "''";
try {
ret = "'" + row[k].toString().replace(/'/g, '').replace(/\n/g, '').replace(/\r/g, '').toString() + "'";
} catch (e) {
// console.log(
// `WARN: err inserting value (likely null) generateSQLBulkInsertQuery: ${e}`
// );
}
return ret;
})
.join(',')
}
//EXAMPLE OF ERROR STARTS HERE--------------------------------
(async ()=>{
//init the var
var rowsx = [{ col1: "1", col2: "2" }];
console.log({rowsxlen:rowsx.length,here:1})
var sqliq = generateBulkInsertQueries(rowsx, 'xx', 'yy', ['col1','col2'],1);
console.log({rowsxlen:rowsx.length,here:2})
if(rowsx.length!=1) console.log(`failed. rowsx.length!=1`)
else console.log(`success!!!!`)
})()

How do I unpack a data from a binary string in Node.js?

In PHP you can do this as follows:
$raw_data = base64_decode("1Xr5WEtvrApEByOh4GtDb1nDtls");
$unpacked = unpack('Vx/Vy', $raw_data);
/* output:
array(2) {
["x"]=>
int(1492744917)
["y"]=>
int(179072843)
}
*/
How do I achieve the same result in Node.js?
So far, I have tried this:
const base64URLDecode = (data) => {
let buffer
data = data.replace(/-/g, '+').replace(/_/g, '/')
while (data.length % 4) {
data += '='
}
if (data instanceof Buffer) {
buffer = data
} else {
buffer = Buffer.from(data.toString(), 'binary')
}
return buffer.toString('base64')
}
const byteToUINT8 = (bytes) => {
const byteLength = bytes.length
const uint8Arr = new Uint8Array(byteLength)
return uint8Arr.map((char, key) => bytes.charCodeAt(key))
}
const unpack = (binString) => {
const byteChars = base64URLDecode(binString)
const uint8Arr = byteToUINT8(byteChars)
const uint32Arr = new Uint32Array(uint8Arr.buffer)
return uint32Arr
}
unpack('1Xr5WEtvrApEByOh4GtDb1nDtls') // output: [1180980814, 2036880973, ...]
However, this is giving me a totally mismatched result in comparison with PHP's unpack. What am I doing wrong?
Checkout this snippet:
const buf = Buffer.from("1Xr5WEtvrApEByOh4GtDb1nDtls", 'base64')
const uint32array = new Uint32Array(buf.buffer, buf.byteOffset, buf.length / Uint32Array.BYTES_PER_ELEMENT)
// Variable uint32array presents:
// Uint32Array(5) [
// 1492744917,
// ... ]
The documentation about Nodejs TypedArray is here: Nodejs Buffer

Function: Error stackTraceLimit: 10 - JavaScript, Google AppScript

Forgive me for my silly questions, I am really a new bee for this coding world.
I have a google app script running on a page where it extracts data from a different sheet. Code so far works fine except it skips few set of code lines and give me an { [Function: Error] stackTraceLimit: 10 } . This skipping part is very important and appreciate id you guys can help.
Thanks. I will post the entire code below.
const bookAId = 'xxxxxxxxxxxxx'; // ssId of book A
const bookBId = 'yyyyyyyyyyyyy'; // ssId of book B
const sheetA = 'RS_Tharalsdson' // name of sheet in book A containing sheet names
const deadlineRange = 'G3'; // the cell in book B sheet i that you want to copy - deadline
const pmsRange = 'A3' // the cell in book B sheet i that you want to copy - pms
const pmsOnq = 'onq';
const pmsFosse = 'fosse'
const pmsGalaxy = 'galaxylightspeed'
const pmsOpera = 'opera'
const fileFormatCol = 4 // column D
const fileFormatRow = 6 // first row containing file formats
const operaCol = 6 // column F
const operaRow = 6 // first row of opera file formats
const subCol = 5 // submission method column of fosse,onq and galaxy
const subRow = 6 // submission starting row of fosse,onq and galaxy
const subColOpr = 4 // opera submission col
const subRowOpr = 6// opera submission starting row
function getFileFormat(){
const ssA = SpreadsheetApp.openById(bookAId);
const sA = ssA.getSheetByName(sheetA);
const sheetNames = sA.getRange('G2:G').getValues().reduce((names, row) => row[0] !== '' ? names.concat(row[0]) : names ,[]);
const ssB = SpreadsheetApp.openById(bookBId);
const fileFormatsFromBookB = []; // collect the values you find in each sheet
const submissionMethods = []; //collect all the submission methods in each sheet
for (const sheetName of sheetNames) {
const sheet = ssB.getSheetByName(sheetName);
if (!sheet){
fileFormatsFromBookB.push(['Sheet Not Found'])
continue;
}
const pmsCell = sheet.getRange(pmsRange).getValue();
var array1 = [{}];
var string2 = pmsCell;
array1 = string2.split(/[:\n]/);
var pms = array1[1];
pms = pms.replace(/\s+/g, '').toLowerCase();
console.log(sheetName)
console.log(pms)
if(pms==pmsOpera){
const fileFormatRange = sheet.getRange(operaRow, operaCol, sheet.getLastRow(), 1);
const fileFormats = fileFormatRange.getValues();
var col0 = fileFormats.map(function(value,index) { return value[0]; });
const distinct = (value, index, self) =>{ return self.indexOf(value)===index;}
var unq = col0.filter(distinct).toString();
fileFormatsFromBookB.push([unq]);
//ERROR SKIPS BELOW CODE//
const subMethodsRange = sheet.getRange(subRowOpr, subColOpr, sheet.getLastRow(), 1);
const subMethods = subMethodsRange.getValues();
var col1 = subMethods.map(function(value,index) { return value[0]; });
const distinct1 = (value, index, self) =>{ return self.indexOf(value)===index;}
var unqSub = col1.filter(distinct1).toString();
submissionMethods.push([unqSub]);
console.log(Error)
}
if (pms==pmsOnq || pms==pmsFosse || pms==pmsGalaxy) {
const fileFormatRange = sheet.getRange(fileFormatRow, fileFormatCol, sheet.getLastRow(), 1);
const fileFormats = fileFormatRange.getValues();
var col0 = fileFormats.map(function(value,index) { return value[0]; });
const distinct = (value, index, self) =>{ return self.indexOf(value)===index;}
var unq = col0.filter(distinct).toString();
fileFormatsFromBookB.push([unq]);
const subMethodsRange = sheet.getRange(subRow, subCol, sheet.getLastRow(), 1);
const subMethods = subMethodsRange.getValues();
var col1 = subMethods.map(function(value,index) { return value[0]; });
const distinct1 = (value, index, self) =>{ return self.indexOf(value)===index;}
var unqSub = col1.filter(distinct1).toString();
submissionMethods.push([unqSub]);
}
//else {
// submissionMethods.push(["__"]);
//}
sA.getRange(2, 10, fileFormatsFromBookB.length, 1).setValues(fileFormatsFromBookB); // paste all of the values you collected into the paste range you specified in book
sA.getRange(2, 11, submissionMethods.length, 1).setValues(submissionMethods); // paste submission methods:unique values
}
}
I managed to fix the error. It is actually a typo. I have not been pushing a value to the search array if a the search returns nothing. Code is below.
if (!sheet){
fileFormatsFromBookB.push(['Sheet Not Found'])
submissionMethods.push(['Sheet Not Found'])//THIS FIXED THE ISSUE
continue;
}
Thank you for all the help guys.

SetValues() to two different cell ranges together in Google Sheet

I have two spreadsheet books.
BookA
BookB
I have sheet names of BookB stored in BookA.
I want to search through all the sheets in BookB that matches the sheet name stored in BookA. If a sheet is found get the values in Cell 'A3' and paste it in BookA in front of the respective sheet name.
(I have managed to achieve this task successfully. Issue comes now. Brace yourselves)
I want to get the 'File Format' details without duplicates from the sheets of BookB and paste that in the sheet of BookA in front of the page name. May be my way is not correct. If someone can help I am grateful.
Note that File Format details are mentioned in two different ranges in the given two sheets. ALBW - D6:D21 and BFLCB - F6:F21
const pmsRange = 'A3' // the cell in book B sheet i that you want to copy
function getFileFormat(){
const ssA = SpreadsheetApp.openById(bookAId);
const sA = ssA.getSheetByName(sheetA);
const sheetNames = sA.getRange('G2:G').getValues().reduce((names, row) => row[0] !== '' ? names.concat(row[0]) : names ,[]);
const ssB = SpreadsheetApp.openById(bookBId);
const valuesFromSheetB = []; // collect the values you find in each sheet of book B
for (const sheetName of sheetNames) {
const sheet = ssB.getSheetByName(sheetName);
if (!sheet) {
valuesFromSheetB.push(['Sheet Not Found']);
continue;
}
const value = sheet.getRange(pmsRange).getValue(); // get the value from the range you specified
var array1 = [{}];
var string1 = value;
array1 = string1.split(/[:\n]/);
var pms = array1[1];
pms = pms.replace(/\s+/g, '');
if(pms.toLowerCase()=="onq"){
console.log(sheetName+":"+pms);
var col0 = exts.map(function(value,index) { return value[0]; });
const distinct = (value, index, self) =>{ return self.indexOf(value)===index;}
var unq = col0.filter(distinct).toString();
console.log(unq)
extsFromSheetB.push([unq])
}
sA.getRange(2, 8, valuesFromSheetB.length, 1).setValues(valuesFromSheetB); // paste all of the values you collected into the paste range you specified in book A
}
}
These edits should get you what you need. The script will find sheets in book B whose names are listed in book A. Once a sheet is found, it will check to see if the value in the pmsRange of that sheet contains the pmsSearchValue. If it does, then it will store all of the file formats separated by ' / '. If it doesn't then it will store ''. Finally, after iterating over every sheet name collected from book A, it will paste the file formats into the paste range that you specified in your example.
const pmsRange = 'A3' // the cell in book B sheet i that you want to copy
const pmsSearchValue = 'OnQ';
const fileFormatCol = 4 // column D
const fileFormatRow = 6 // first row containing file formats
function getFileFormat(){
const ssA = SpreadsheetApp.openById(bookAId);
const sA = ssA.getSheetByName(sheetA);
const sheetNames = sA.getRange('G2:G').getValues().reduce((names, row) => row[0] !== '' ? names.concat(row[0]) : names ,[]);
const ssB = SpreadsheetApp.openById(bookBId);
const fileFormatsFromBookB = []; // collect the values you find in each sheet of book B
for (const sheetName of sheetNames) {
const sheet = ssB.getSheetByName(sheetName);
if (!sheet) continue;
const pmsCell = sheet.getRange(pmsRange).getValue();
if (pmsCell && pmsCell.indexOf(pmsSearchValue)) {
const fileFormatRange = sheet.getRange(fileFormatRow, fileFormatCol, sheet.getLastRow(), 1);
const fileFormats = fileFormatRange.getValues().filter(f => f !== '').join(' / ');
fileFormatsFromBookB.push([fileFormats]);
} else {
fileFormatsFromBookB.push(['']);
}
sA.getRange(2, 10, fileFormatsFromBookB.length, 1).setValues(fileFormatsFromBookB); // paste all of the values you collected into the paste range you specified in book A
}
}
References: None. This is mostly vanilla javascript taking advantage of the Apps Script Spreadsheet Class that you are already using in the sample in your question.
I managed to get the code edited by #RayGun to to fit to my requirement. Thank you. Posting the code here if someone else face the same issue as me.
const pmsRange = 'A3' // the cell in book B sheet i that you want to copy - pms
const pmsOnq = 'onq';
const pmsFosse = 'fosse'
const pmsGalaxy = 'galaxylightspeed'
const pmsOpera = 'opera'
const fileFormatCol = 4 // column D
const fileFormatRow = 6 // first row containing file formats
const operaCol = 6 // column F
const operaRow = 6 // first row of opera file formats
function getFileFormat(){
const ssA = SpreadsheetApp.openById(bookAId);
const sA = ssA.getSheetByName(sheetA);
const sheetNames = sA.getRange('G2:G').getValues().reduce((names, row) => row[0] !== '' ? names.concat(row[0]) : names ,[]);
const ssB = SpreadsheetApp.openById(bookBId);
const fileFormatsFromBookB = []; // collect the values you find in each sheet of book B
for (const sheetName of sheetNames) {
const sheet = ssB.getSheetByName(sheetName);
if (!sheet){
fileFormatsFromBookB.push(['Sheet Not Found'])
continue;
}
const pmsCell = sheet.getRange(pmsRange).getValue();
var array1 = [{}];
var string2 = pmsCell;
array1 = string2.split(/[:\n]/);
var pms = array1[1];
pms = pms.replace(/\s+/g, '').toLowerCase();
console.log(sheetName)
console.log(pms)
if (pms==pmsOnq || pms==pmsFosse || pms==pmsGalaxy) {
const fileFormatRange = sheet.getRange(fileFormatRow, fileFormatCol, sheet.getLastRow(), 1);
const fileFormats = fileFormatRange.getValues();
var col0 = fileFormats.map(function(value,index) { return value[0]; });
const distinct = (value, index, self) =>{ return self.indexOf(value)===index;}
var unq = col0.filter(distinct).toString();
fileFormatsFromBookB.push([unq]);
}
if(pms==pmsOpera){
const fileFormatRange = sheet.getRange(operaRow, operaCol, sheet.getLastRow(), 1);
const fileFormats = fileFormatRange.getValues();
var col0 = fileFormats.map(function(value,index) { return value[0]; });
const distinct = (value, index, self) =>{ return self.indexOf(value)===index;}
var unq = col0.filter(distinct).toString();
fileFormatsFromBookB.push([unq]);
}
/*else {
fileFormatsFromBookB.push(['']);
}*/
sA.getRange(2, 10, fileFormatsFromBookB.length, 1).setValues(fileFormatsFromBookB); // paste all of the values you collected into the paste range you specified in book
}
}
Note that you have to remove that else part of if statement. Otherwise it skips a cell and gives a result in a wrong range.

Why is my for loop not working how I expect to? Run function twice - JavaScript

So guys, I've got scraping function, where I create object of scraped data. Code of scraper is:
const axios = require('axios');
const cheerio = require('cheerio');
const db = require('../config/db.config');
const Article = db.article;
const prices = new Array();
const ids = new Array();
const descs = new Array();
const links = new Array();
for (p = 1; p < 3; p++) {
function again() {
const url = `https://www.olx.ba/pretraga?vrsta=samoprodaja&kategorija=23&sort_order=desc&kanton=9&sacijenom=sacijenom&stranica=${p}`;
axios
.get(url)
.then((response) => {
let $ = cheerio.load(response.data);
$('div[class="naslov"] > a').each((i, el) => {
const id = $(el).attr('href'); // ID, description and link are in the same div class
const desc = id;
const link = id;
descs.push(desc.substring(36)); //Retriving description with substring and push into array
ids.push(id.substring(27, 35)); //Retriving id with substring and push into array
links.push(link); //Retriving link and push into array
for (var i = 0; i < descs.length; i++) {
descs[i] = descs[i].replace('/', '').replace('-', ' ');
}
});
$('div[class="datum"] > span').each((i, el) => {
$('span[class="prekrizenacijena"]').remove();
const price = $(el).text();
prices.push(price); //Retriving price and push into array
});
for (var i = prices.length - 1; i >= 0; i--) {
if (prices[i] === 'PO DOGOVORU') {
prices.splice(i, 1);
}
}
async function asy() {
const sqm = new Array();
for (k = 0; k < links.length; k++) {
const res = await axios
.get(`${links[k]}`)
.then((result) => {
let $ = cheerio.load(result.data);
const pr = $('div[class="df2 "]').first().text();
sqm.push(pr);
for (var i = 0; i < sqm.length; i++) {
sqm[i] = sqm[i].replace('m2', '');
}
})
.catch((err) => {
//handle error
console.log(err);
});
}
const object = ids.map((element, index) => {
const ppm2 =
parseFloat(
prices[index].replace(/\.| ?€$/g, '').replace(',', '.')
) / parseFloat(sqm[index]);
const ppm2final = Math.round(ppm2);
return {
id: element,
price: prices[index],
descr: descs[index],
link: links[index],
sqm: sqm[index],
ppm2: ppm2final + ' KM',
};
});
console.log(object);
console.log(Object.keys(object).length);
/*const ins = await Article.bulkCreate(object)
.then(console.log('Data added to DB'))
.catch((err) => console.log(err));*/
}
asy();
})
.catch((e) => {
console.log(e);
});
}
again();
}
Now when I delete first for lop and function again() and instead of ${p} in url insert eg. 1,2,3 etc. it's working perfect - sqm is fetched for correct link.
Now the problem:
I want to run this url multiple times because ${p} is number of page on that url. Now first problem I got:
sqm isn't correct - sqm data is thrown all over the object and isn't correct for that link.(it's correct when I don't use ${p}
First time i get sqm data(but not correct for that link), when function needs to get ran second time (for second page - to ${p}=2) - sqm isn't fetched at all (it throws NaN).
Also I've got console.log(Object.keys(object).length); where I expect first time to be 30, then after is runned second time to I get 60.(each page contains 30 articles), but I get 60, then again 60.
I've tried with many things: async functions, putting axios to await etc. but nothing really work - sometimes I get only 30 articles, sometimes 60 but with incorrect values.

Categories

Resources