@@ -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) {
121119Sheets . 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 */
145142Sheets . 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 - z A - Z ] * ) ( \d * ) : ( [ a - z A - 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 */
309305Sheets . 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 - z A - Z ] * ) ( \d * ) : ( [ a - z A - 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 }
0 commit comments