11"use strict" ;
22const { google } = require ( 'googleapis' ) ;
33var Promise = require ( 'polyfill-promise' ) ;
4+ var excelColumnName = require ( 'excel-column-name' ) ;
45
56/**
67 * Authorizationn via GoogleApis
@@ -96,6 +97,8 @@ Sheets.prototype.getSheets = async function getSheets(docId) {
9697 spreadsheetId : docId
9798 }
9899 const response = await sheets . spreadsheets . get ( request ) ;
100+
101+ console . log ( 'v4 response-->' , response . data . sheets )
99102 return ( response . data . sheets . map ( ( sheet ) => {
100103 return {
101104 id : sheet . properties . sheetId ,
@@ -118,6 +121,7 @@ Sheets.prototype.getSheets = async function getSheets(docId) {
118121Sheets . prototype . getSheet = async function getSheet ( docId , sheetId ) {
119122 try {
120123 const sheetsInfo = await this . getSheets ( docId ) ;
124+ console . log ( '-----sheetsInfo-----' , sheetsInfo )
121125 const { id, title, rowCount, colCount } = sheetsInfo
122126 . find ( ( sheet ) => sheet . id === + sheetId ) ;
123127
@@ -149,6 +153,7 @@ Sheets.prototype.getList = async function getList(docId, sheetId) {
149153 range : title
150154 }
151155 const sheet = await sheets . spreadsheets . values . get ( request ) ;
156+ // console.log(JSON.stringify(sheet.data.values, null, 2));
152157 return sheet . data . values ;
153158 } catch ( error ) {
154159 throw new Error ( error ) ;
@@ -164,12 +169,132 @@ Sheets.prototype.getList = async function getList(docId, sheetId) {
164169Sheets . prototype . getCells = async function getCells ( docId , sheetId ) {
165170 try {
166171 const cells = await this . getRange ( docId , sheetId ) ;
167- return cells . flat ( ) ;
172+ return cells . flat ( ) . filter ( cell => cell . content != '' ) ;
168173 } catch ( e ) {
169174 throw new Error ( e ) ;
170175 }
171176} ;
172177
178+
179+ /**
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+ *
192+ * @param {String } t Title, name of the sheet
193+ * @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 ) => {
196+ // Titles with spaces need to be wrapped into single quotes
197+ 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+
214+ return `${ title } !${ ri } ` ;
215+ // return title;
216+ } ;
217+
218+
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 - z A - Z ] * ) ( \d * ) : ( [ a - z A - Z ] * ) ( \d * ) / ) ;
222+ let noOfColumns = 0 ;
223+ let startCol = 0 ;
224+ let noOfRows = 0 ;
225+ 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 ] ;
264+ 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 ] )
276+ ) + 1 ;
277+ startCol = Math . min ( excelColumnName . excelColToInt ( partsMatch [ 1 ] ) , excelColumnName . excelColToInt ( partsMatch [ 3 ] ) )
278+ }
279+
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 )
286+ for ( let r = 0 ; r < nMatrix . length ; r += 1 ) {
287+ 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 ] ;
293+ }
294+ }
295+ return nMatrix ;
296+ }
297+
173298/**
174299 * Retrieve cells based on given range
175300 * NOTE: If there are missing cells (no content) this function
@@ -179,33 +304,83 @@ Sheets.prototype.getCells = async function getCells(docId, sheetId) {
179304 * @param {String } docId Sheet document id
180305 * @param {String } sheetId Sheet id
181306 * @param {Mixed } rangeInfo Range info. use sheets A1 notation for this https://developers.google.com/sheets/api/guides/concepts
182- * @return {Promise } Rows containing cells, like [[{A1}, {B1}], [{A2}, {B2}]]
307+ * @return {Promise<{ row: Number, column: String, content: String }[]> } Rows containing cells, like [[{A1}, {B1}], [{A2}, {B2}]]
183308 */
184309Sheets . prototype . getRange = async function ( docId , sheetId , rangeInfo ) {
185310 try {
186311 const [ { title } , sheets ] = await Promise . all ( [
187312 this . getSheet ( docId , sheetId ) ,
188313 this . get ( )
189314 ] ) ;
190-
315+ const range = this . toV4Range ( title , rangeInfo ) ;
191316 const request = {
192317 spreadsheetId : docId ,
193- range : rangeInfo === undefined ? title : ` ${ title } ! ${ rangeInfo } ` ,
318+ range
194319 }
195320 const sheet = await sheets . spreadsheets . values . get ( request ) ;
196- return sheet . data . values . map ( ( row , index ) => {
197- const rowIndex = index + 1 ;
198- return row . map ( ( cell , cellIndex ) => {
199- return {
200- row : rowIndex ,
201- column : this . columnLetters . split ( ',' ) [ cellIndex ] ,
202- content : cell
203- } ;
204- } ) ;
205- } ) ;
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+
206339 } catch ( error ) {
207340 throw new Error ( error ) ;
208341 }
209342} ;
210343
344+ /**
345+ * Parse given range from given string, like 'A2:C8' or 'A2:'
346+ * @param {String } str Range info
347+ * @return {Object } Range info { from: { col: 'A', row: 2 }, to: ...}
348+ */
349+ Sheets . prototype . parseRangeInfo = function ( str ) {
350+ var rangeInfo = {
351+ from : {
352+ col : null ,
353+ row : null ,
354+ } ,
355+ to : {
356+ col : null ,
357+ row : null ,
358+ } ,
359+ } ;
360+
361+ // Match full range: A1:B20
362+ var match = str . match ( / ( [ a - z A - Z ] + ) ( \d + ) : ( [ a - z A - Z ] + ) ( \d + ) / ) ;
363+
364+ if ( match && match . length > 1 ) {
365+ rangeInfo . from . row = parseInt ( match [ 2 ] , 10 ) ;
366+ rangeInfo . from . col = match [ 1 ] ;
367+ rangeInfo . to . row = parseInt ( match [ 4 ] , 10 ) ;
368+ rangeInfo . to . col = match [ 3 ] ;
369+
370+ return rangeInfo ;
371+ }
372+
373+ // Match full range: A1:
374+ var match = str . match ( / ( \w ) ( \d + ) : / ) ;
375+ if ( match && match . length > 1 ) {
376+ rangeInfo . from . row = parseInt ( match [ 2 ] , 10 ) ;
377+ rangeInfo . from . col = match [ 1 ] ;
378+
379+ return rangeInfo ;
380+ }
381+
382+ return rangeInfo ;
383+ } ;
384+
385+
211386module . exports = Sheets ;
0 commit comments