Skip to content
This repository was archived by the owner on Nov 4, 2025. It is now read-only.

Commit 812f703

Browse files
committed
[DONE} - migration from v3 to v4.
1 parent b2a35a1 commit 812f703

4 files changed

Lines changed: 139 additions & 146 deletions

File tree

lib/sheets.js

Lines changed: 136 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ Sheets.prototype.get = async function get() {
7979
auth: authClient
8080
});
8181
return sheets;
82-
// this.sheets = sheets;
8382
} catch(e){
8483
throw new Error(e);
8584
}
@@ -98,7 +97,6 @@ Sheets.prototype.getSheets = async function getSheets(docId) {
9897
}
9998
const response = await sheets.spreadsheets.get(request);
10099

101-
console.log('v4 response-->', response.data.sheets )
102100
return(response.data.sheets.map((sheet) => {
103101
return {
104102
id: sheet.properties.sheetId,
@@ -121,7 +119,6 @@ Sheets.prototype.getSheets = async function getSheets(docId) {
121119
Sheets.prototype.getSheet = async function getSheet(docId, sheetId) {
122120
try {
123121
const sheetsInfo = await this.getSheets(docId);
124-
console.log('-----sheetsInfo-----', sheetsInfo)
125122
const { id, title, rowCount, colCount } = sheetsInfo
126123
.find((sheet) => sheet.id === +sheetId);
127124

@@ -143,7 +140,6 @@ Sheets.prototype.getSheet = async function getSheet(docId, sheetId) {
143140
* @return {Promise} A promise that resolves to list of data from a sheet
144141
*/
145142
Sheets.prototype.getList = async function getList(docId, sheetId) {
146-
// TODO: parse rows to sane data, now returns raw feed data
147143
try {
148144
const { title } = await this.getSheet(docId, sheetId);
149145
const sheets = await this.get();
@@ -153,7 +149,6 @@ Sheets.prototype.getList = async function getList(docId, sheetId) {
153149
range: title
154150
}
155151
const sheet = await sheets.spreadsheets.values.get(request);
156-
// console.log(JSON.stringify(sheet.data.values, null, 2));
157152
return sheet.data.values;
158153
} catch(error) {
159154
throw new Error(error);
@@ -177,165 +172,178 @@ Sheets.prototype.getCells = async function getCells(docId, sheetId) {
177172

178173

179174
/**
180-
* Convert rangeInfo to valid Google Sheets API v4 range
181-
*
182-
* Examples:
183-
*
184-
* - "Sheet1!A1:B2" refers to the first two cells in the top two rows of Sheet1.
185-
* - "Sheet1!A:A" refers to all the cells in the first column of Sheet1.
186-
* - "Sheet1!1:2" refers to all the cells in the first two rows of Sheet1.
187-
* - "Sheet1!A5:A" refers to all the cells of the first column of Sheet 1, from row 5 onward.
188-
* - "Sheet1" refers to all the cells in Sheet1.
189-
* - "'My Custom Sheet'!A:A" refers to all the cells in a sheet named "My Custom Sheet."
190-
* Single quotes are required for sheet names with spaces, special characters, or an alphanumeric combination.
191-
*
175+
* Sanitize range info before passing it to v4 request
192176
* @param {String} t Title, name of the sheet
193177
* @param {String} ri Range info, e.g "A1:B2", where "A" and "B" are columns and "1" and "2" are rows
194-
*/
195-
Sheets.prototype.toV4Range = (t, ri) => {
178+
*/
179+
Sheets.prototype.v4RangeSanitizer = (t, ri) => {
196180
// Titles with spaces need to be wrapped into single quotes
197181
let title = t.includes(" ") ? `'${t}'` : t;
198-
199-
// Return just the title "Sheet1" if there is no range info
200-
if ([undefined, null].includes(ri)) {
201-
return title;
202-
}
203-
204-
// "Sheet1!A3:" -> "Sheet1" -> start from A3 and go column per column on the response returning all cells that are on row 3 or above
205-
if (ri[ri.length - 1] === ":") {
206-
return title;
207-
}
208-
// "Sheet1!1:2" -> all the cells in the first two rows of Sheet1
209-
// const patternMatch = ri.match(/([a-zA-Z]*)(\d*):([a-zA-Z]*)(\d*)/);
210-
// if (patternMatch && patternMatch[1] === '' && patternMatch[3] === '') {
211-
// return title;
212-
// }
213-
214182
return `${title}!${ri}`;
215-
// return title;
216183
};
217184

185+
/**
186+
* Get total count of rows and columns in a data array
187+
* @param {Array} data Title, name of the sheet
188+
* @return {Array<number>} Total count of rows and columns
189+
*/
190+
Sheets.prototype.getRowAndColCount = (data) => {
191+
const dataArray = data || [];
192+
let col = 0;
193+
194+
for (let r = 0; r < dataArray.length; r += 1) {
195+
for (let c = 0; c < dataArray[r].length; c += 1) {
196+
if (c >= col) {
197+
col = col + c;
198+
}
199+
}
200+
}
201+
return [dataArray.length, col];
202+
}
203+
204+
/**
205+
* Get total count of rows and columns in a data array
206+
* @param {Array} data Title, name of the sheet
207+
* @return {Array<number>} Total count of rows and columns
208+
*/
209+
Sheets.prototype.paddedEmptyMatrix = (totalRow = 0, totalCol = 0, startRow = 1, startCol = 1) => {
210+
return [...Array(totalRow)]
211+
.map((_ ,r) =>
212+
[...Array(totalCol)]
213+
.map((__, c) => ({ row: r+startRow, column: excelColumnName.intToExcelCol(c+startCol), content: '' }))
214+
)
215+
}
218216

219-
Sheets.prototype.fullDataMatrix = function (data, range) {
220-
const parts = range//'D1:D5'//'C5:A1'//'A1:B2'//'A1:C5'//'B2:C2';
221-
const partsMatch = parts.match(/([a-zA-Z]*)(\d*):([a-zA-Z]*)(\d*)/);
222-
let noOfColumns = 0;
217+
/**
218+
* Creates a full, padded matrix
219+
* if the range is like 'A3:' or 'B1:C2' and there are missing
220+
* cells (no content) this function adds them there (unlike other functions),
221+
* thus you'll always have full matrix like B1:C2 -->
222+
* [
223+
[
224+
{ row: 1, column: "B", content: "B1" },
225+
{ row: 1, column: "C", content: "C1" },
226+
],
227+
[
228+
{ row: 2, column: "B", content: "" },
229+
{ row: 2, column: "C", content: "C2" },
230+
],
231+
]
232+
* or in either case it will return raw v4 response like, A:B --> [[A1, B1], ['', B2]]
233+
*
234+
* @param {Array} data Sheet document id
235+
* @param {String} rangeInfo Range info
236+
* @return {Array} Rows containing cells
237+
*/
238+
Sheets.prototype.paddedDataMatrix = function (data, rangePattern) {
239+
let noOfCols = 0;
223240
let startCol = 0;
224241
let noOfRows = 0;
225242
let startRow = 0;
226-
227-
// No column specified --> '1:2'
228-
if(partsMatch[1] === '' && partsMatch[3] === ''){
229-
let col = 0;
230-
231-
for (let r = 0; r < data.length; r += 1) {
232-
for (let c = 0; c < data[r].length; c += 1) {
233-
if (c >= col) {
234-
col = col + c;
235-
}
236-
}
237-
}
238-
noOfRows = Math.abs(+partsMatch[4] - +partsMatch[2]) + 1;
239-
startRow = Math.min(+partsMatch[2], +partsMatch[4]);
240-
noOfColumns = col;
241-
startCol = 1;
242-
}
243-
// No row specified --> 'A:B' OR 'A:A' OR 'B:B'
244-
else if (partsMatch[2] === '' && partsMatch[4] === '') {
245-
noOfRows = data.length;
246-
startRow = 1;
247-
noOfColumns = Math.abs(
248-
excelColumnName.excelColToInt(partsMatch[3]) - excelColumnName.excelColToInt(partsMatch[1])
249-
) + 1;
250-
startCol = Math.min(excelColumnName.excelColToInt(partsMatch[1]), excelColumnName.excelColToInt(partsMatch[3]))
251-
}
252-
// Only 1st part of a range specified --> 'A3:'
253-
else if (partsMatch[3] === '' && partsMatch[4] === '') {
254-
let col = 0;
255-
256-
for (let r = 0; r < data.length; r += 1) {
257-
for (let c = 0; c < data[r].length; c += 1) {
258-
if (c >= col) {
259-
col = col + c;
260-
}
261-
}
262-
}
263-
startRow = +partsMatch[2];
243+
let nMatrix = [];
244+
const [row, col] = this.getRowAndColCount(data);
245+
const [_, pattern1, pattern2, pattern3, pattern4] = rangePattern || [];
246+
const specialPatternType1 = [pattern3, pattern4].every(value => value === ''); // 'A3:'
247+
248+
if(!rangePattern){
249+
nMatrix = this.paddedEmptyMatrix(row, col);
250+
} else if (specialPatternType1) {
251+
startRow = +pattern2;
264252
noOfRows = Math.abs(data.length - startRow) + 1;
265-
startCol = excelColumnName.excelColToInt(partsMatch[1]);
266-
noOfColumns = Math.abs(col - startCol) + 1;
267-
268-
console.log('results-->', startRow, startCol, noOfRows, noOfColumns, partsMatch[1], excelColumnName.excelColToInt(partsMatch[1]))
269-
}
270-
// full range specified --> 'A1:C5'
271-
else {
272-
noOfRows = Math.abs(+partsMatch[4] - +partsMatch[2]) + 1;
273-
startRow = Math.min(+partsMatch[2], +partsMatch[4]);
274-
noOfColumns = Math.abs(
275-
excelColumnName.excelColToInt(partsMatch[3]) - excelColumnName.excelColToInt(partsMatch[1])
253+
startCol = excelColumnName.excelColToInt(pattern1);
254+
noOfCols = Math.abs(col - startCol) + 1;
255+
nMatrix = this.paddedEmptyMatrix(noOfRows, noOfCols, startRow, startCol);
256+
} else {
257+
noOfRows = Math.abs(+pattern4 - +pattern2) + 1;
258+
startRow = Math.min(+pattern2, +pattern4);
259+
noOfCols = Math.abs(
260+
excelColumnName.excelColToInt(pattern3) - excelColumnName.excelColToInt(pattern1)
276261
) + 1;
277-
startCol = Math.min(excelColumnName.excelColToInt(partsMatch[1]), excelColumnName.excelColToInt(partsMatch[3]))
262+
startCol = Math.min(excelColumnName.excelColToInt(pattern1), excelColumnName.excelColToInt(pattern3));
263+
nMatrix = this.paddedEmptyMatrix(noOfRows, noOfCols, startRow, startCol);
278264
}
279265

280-
const nMatrix = [...Array(noOfRows)]
281-
.map((_ ,r) =>
282-
[...Array(noOfColumns)]
283-
.map((__, c) => ({ row: r+startRow, col: c+startCol, content: '' }))
284-
)
285-
console.log('nMatrix', nMatrix)
286266
for (let r = 0; r < nMatrix.length; r += 1) {
287267
for (let c = 0; c < nMatrix[r].length; c += 1) {
288-
// console.log('data--->', r, c, data[r+startRow-1][c+startCol-1], nMatrix[r][c])
289-
// const startRowIndex = startRow - 1;
290-
// const startColIndex = startCol - 1;
291-
// console.log('results-->', startRow, startCol, data[r+startRowIndex][c+startColIndex])
292-
nMatrix[r][c].content = (data === undefined || data[r][c] === undefined) ? '' : data[r][c];
268+
if (specialPatternType1) {
269+
const startRowIndex = startRow - 1;
270+
const startColIndex = startCol - 1;
271+
nMatrix[r][c].content = (data === undefined || data[r+startRowIndex][c+startColIndex] === undefined)
272+
? ''
273+
: data[r+startRowIndex][c+startColIndex];
274+
} else {
275+
nMatrix[r][c].content = (data === undefined || data[r][c] === undefined)
276+
? ''
277+
: data[r][c];
278+
}
279+
293280
}
294281
}
295282
return nMatrix;
296283
}
297284

298285
/**
299-
* Retrieve cells based on given range
300-
* NOTE: If there are missing cells (no content) this function
301-
* adds them there (unlike other functions), thus you'll always
302-
* have full matrix
286+
* Retrieve cells data based on given range
287+
*
288+
* * All below ranges are v4 compatible but full matrix are [SUPPORTED] only for few of them:
289+
*
290+
* - [SUPPORTED] "Sheet1!A1:B2" refers to the first two cells in the top two rows of Sheet1.
291+
* - [SUPPORTED] "A3:" refres to all cells starts from 'A' column and 3rd row.
292+
* - "Sheet1!A:A" refers to all the cells in the first column of Sheet1.
293+
* - "Sheet1!1:2" refers to all the cells in the first two rows of Sheet1.
294+
* - "Sheet1!A5:A" refers to all the cells of the first column of Sheet 1, from row 5 onward.
295+
* - [SUPPORTED]"Sheet1" refers to all the cells in Sheet1.
296+
* - "'My Custom Sheet'!A:A" refers to all the cells in a sheet named "My Custom Sheet."
297+
* Single quotes are required for sheet names with spaces, special characters, or an alphanumeric combination.
298+
*
303299
*
304300
* @param {String} docId Sheet document id
305301
* @param {String} sheetId Sheet id
306-
* @param {Mixed} rangeInfo Range info. use sheets A1 notation for this https://developers.google.com/sheets/api/guides/concepts
307-
* @return {Promise<{ row: Number, column: String, content: String }[]>} Rows containing cells, like [[{A1}, {B1}], [{A2}, {B2}]]
302+
* @param {Mixed} rangeInfo Range info.
303+
* @return {Promise} Rows containing cells
308304
*/
309305
Sheets.prototype.getRange = async function(docId, sheetId, rangeInfo) {
310306
try {
311307
const [{ title }, sheets] = await Promise.all([
312308
this.getSheet(docId, sheetId),
313309
this.get()
314310
]);
315-
const range = this.toV4Range(title, rangeInfo);
311+
316312
const request = {
317313
spreadsheetId: docId,
318-
range
314+
range: this.v4RangeSanitizer(title, rangeInfo)
319315
}
320-
const sheet = await sheets.spreadsheets.values.get(request);
321-
console.log('Actual data-->', sheet.data.values)
322-
return this.fullDataMatrix(sheet.data.values, rangeInfo);
323-
// const patternMatch = ![undefined, null].includes(rangeInfo)
324-
// ? rangeInfo.match(/([a-zA-Z]*)(\d*):([a-zA-Z]*)(\d*)/)
325-
// : null
326-
// const sheet = await sheets.spreadsheets.values.get(request);
327-
// console.log('Actual data-->', sheet.data.values)
328-
// const fullMatrix = this.fullDataMatrix(sheet.data.values);
329-
330-
// if(patternMatch && patternMatch[1] === '' && patternMatch[3] === ''){ // range 'Sheet1!1:2'
331-
// return fullMatrix.slice(patternMatch[2] - 1, patternMatch[4]);
332-
// } else if (patternMatch && patternMatch[3] === '' && patternMatch[4] === '') { // range 'Sheet1!A3:'
333-
// return fullMatrix.slice(patternMatch[2] - 1, fullMatrix.length);
334-
// }
335-
// else { // range "Sheet1"
336-
// return fullMatrix;
337-
// }
338-
316+
317+
if (!rangeInfo) {
318+
const req = {
319+
...request,
320+
range: title
321+
}
322+
const sheet = await sheets.spreadsheets.values.get(req);
323+
return this.paddedDataMatrix(sheet.data.values);
324+
} else {
325+
const rangePattern = rangeInfo.match(/([a-zA-Z]*)(\d*):([a-zA-Z]*)(\d*)/);
326+
const [_, pattern1, pattern2, pattern3, pattern4] = rangePattern;
327+
const patternType1 = [pattern3, pattern4].every(value => value === ''); // 'A3:'
328+
const patternType2 = [pattern1, pattern2, pattern3, pattern4].every(value => value !== ''); // 'A1:C10'
329+
330+
if (patternType1) {
331+
const req = {
332+
...request,
333+
range: title
334+
}
335+
const sheet = await sheets.spreadsheets.values.get(req);
336+
return this.paddedDataMatrix(sheet.data.values, rangePattern);
337+
} else if(patternType2) {
338+
const sheet = await sheets.spreadsheets.values.get(request);
339+
return this.paddedDataMatrix(sheet.data.values, rangePattern);
340+
} else { // 'A:A' or '1:2' or 'A3:A' or 'Sheet1'
341+
const sheet = await sheets.spreadsheets.values.get(request);
342+
console.log('data->', sheet.data.values, request)
343+
return sheet.data.values || [];
344+
}
345+
}
346+
339347
} catch(error) {
340348
throw new Error(error);
341349
}

spec/cells.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

spec/sheets.spec.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,11 @@ describe("Sheets", function () {
4646
},
4747
values: {
4848
get: function ({range}) {
49-
console.log('<range>', range)
5049
// Partial range --> 'Sheet1!'
5150
if(range.includes('!')) {
5251
// 'Sheet1!A3:A'
53-
const match = range.match(/([a-zA-Z]+)(\d+):([a-zA-Z]+)(\d*)/);
54-
console.log('<match>', match)
55-
if (match[1] === match[3]) {
52+
const partsMatch = range.match(/([a-zA-Z]+)(\d+):([a-zA-Z]+)(\d*)/);
53+
if (partsMatch[1] === partsMatch[3]) {
5654
return Promise.resolve(rangeOfValues2)
5755
}
5856
return Promise.resolve(rangeOfValues1);
@@ -138,15 +136,12 @@ describe("Sheets", function () {
138136
// [ { row: 9, column: 'A', content: 'A9' }, { row: 9, column: 'B', content: '' }, { row: 9, column: 'C', content: '' } ],
139137
// [ { row: 10, column: 'A', content: 'A10' }, { row: 10, column: 'B', content: '' }, { row: 10, column: 'C', content: '' } ],
140138
// ]
141-
142-
// console.log('<ROWS>', rows)
143139
expect(rows.length).toBe(8);
144140
expect(rows[0].length).toBe(3);
145141
done();
146142
});
147143
});
148144

149-
// DONE
150145
it("returns rows with full range", function (done) {
151146
sheets.getRange(null, 807593019, "B1:C2").then(function (rows) {
152147
// rows = [
@@ -165,11 +160,8 @@ describe("Sheets", function () {
165160
});
166161
});
167162

168-
// DONE
169163
it("returns rows with no range", function (done) {
170164
sheets.getRange(null, 807593019, null).then(function (rows) {
171-
console.log('<Rows>', rows)
172-
// console.log('<<ROWS>>', rows)
173165
// rows = [
174166
// [
175167
// { row: 1, column: "A", content: "A1" },

0 commit comments

Comments
 (0)