diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..fc360d1 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,35 @@ +module.exports = { + 'rules': { + // TODO add a logger lib to the project + // 'no-console': 2 + 'brace-style': [2, '1tbs'], + 'camelcase': 1, + 'comma-dangle': [2, 'never'], + 'comma-spacing': [2, { 'before': false, 'after': true }], + 'comma-style': [2, 'last'], + 'eqeqeq': 2, + 'indent': [2, 4], + 'key-spacing': [2, { 'beforeColon': false, 'afterColon': true }], + 'linebreak-style': [2, 'unix'], + 'no-console': 0, + 'no-redeclare': 0, + 'no-underscore-dangle': 0, + 'no-unused-vars': 0, + 'object-curly-spacing': [2, 'always'], + 'quotes': [2, 'single'], + 'semi': [2, 'always'], + 'semi-spacing': [2, { 'before': false, 'after': true }], + 'space-after-keywords': 2, + 'space-before-blocks': [2, 'always'], + 'space-before-function-paren': [2, { 'anonymous': 'always', 'named': 'never' }], + 'space-before-keywords': 2, + 'space-in-parens': [2, 'never'], + 'space-infix-ops': 2, + 'strict': 0 + }, + 'env': { + 'node': true + }, + 'extends': 'eslint:recommended' +}; + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a90e009 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +.DS_Store +npm-debug.log +Excel.xlsx diff --git a/README.md b/README.md index 5b63bdd..cb767d4 100644 --- a/README.md +++ b/README.md @@ -1,206 +1,213 @@ # excel4node -An OOXML (xlsx) generator that supports formatting options +An OOXML (xlsx) generator that supports formatting options. -### Installation: -``` -npm install excel4node -``` - -### Sample: -A sample.js script is provided in the code. Running this will output a sample excel workbook named Excel.xlsx +## Installation -``` -node sample.js -``` + npm install excel4node -### Usage: -Instantiate a new workook -Takes optional params object to specify workbook options. -"jszip.compression" : change the zip compression method -"fileSharing" : equates to the "Password to modify" password option. This does not encrypt the workbook and users can still open the workbook as ReadOnly -"allowInterrupt" : uses an asynchronous forEach loop within code as to not block other operations if reports are being generated on the same thread as other processes that should take higher priority. +## Usage Example -``` +```javascript var xl = require('excel4node'); + var wb = new xl.WorkBook(); +var ws = wb.WorkSheet('My Worksheet'); + +var myCell = ws.Cell(1, 1); +myCell.String('Test Value'); -var wbOpts = { - jszip : { - compression : 'DEFLATE' - }, - fileSharing : { - password : 'Password', - userName : 'John Doe' - }, - allowInterrupt : false -} -var wb2 = new xl.WorkBook(wbOpts); +wb.write('MyExcel.xlsx'); ``` -Add a new WorkSheet to the workbook -Takes optional params object to specify page margins, zoom and print view centering and whether to fit to page on print. -The sheetProtection options are the same as the "Protect Sheet" functions in the Review tab of Excel to prevent certain user editing. Setting a value to true means that that particular function is protected and the user will not be able to do that thing. All options are false by default except for 'sheet' which defaults to true if the sheetProtection attribute is set in the worksheet options, but false if it is not. +## Sample + +A sample.js script is provided in the code. Running this will output a sample excel workbook named Excel.xlsx + + node sample.js + open Excel.xlsx + + +## Workbook +A Workbook represents an Excel document. + +```javascript +var xl = require('excel4node'); +var wb2 = new xl.WorkBook({ // optional params object + jszip: { + compression: 'DEFLATE' // change the zip compression method + }, + fileSharing: { // equates to "password to modify option" + password: 'Password', // This does not encrypt the workbook, + userName: 'John Doe' // and users can still open the workbook as read-only. + }, + allowInterrupt: false // do not asynchronously forEach loops +}); ``` -var ws = wb.WorkSheet('New Worksheet'); - -var wsOpts = { - margins:{ - left : .75, - right : .75, - top : 1.0, - bottom : 1.0, - footer : .5, - header : .5 - }, - printOptions:{ - centerHorizontal : true, - centerVertical : false - }, - view:{ - zoom : 100 - }, - outline:{ - summaryBelow : true - }, - fitToPage:{ - fitToHeight: 100, - orientation: 'landscape', - }, - sheetProtection : { - autoFilter : false, - deleteColumns : false, - deleteRows : false, - formatCells : false, - formatColumns : false, - formatRows : false, - insertColumns : false, - insertHyperlinks : false, - insertRows : false, - objects : false, - password : 'Password', - pivotTables : false, - scenarios : false, - sheet : true, - sort : false - } -} -var ws2 = wb.WorkSheet('New Worksheet', wsOpts); + +`allowInterrupt` uses an asynchronous forEach loop within code as to not block other operations if reports are being generated on the same thread as other processes that should take higher priority. + + +## Worksheet + +A Worksheet represents a tab within an excel document. + +```javascript +var ws = wb.WorkSheet('My Worksheet', { + margins: { // page margins + left: 0.75, + right: 0.75, + top: 1.0, + bottom: 1.0, + footer: 0.5, + header: 0.5 + }, + printOptions: { // page print options + centerHorizontal: true, + centerVertical: false + }, + view: { // page zoom + zoom: 100 + }, + outline: { + summaryBelow: true + }, + fitToPage: { + fitToHeight: 100, + orientation: 'landscape', + }, + sheetProtection: { // same as "Protect Sheet" in Review tab of Excel + autoFilter: false, + deleteColumns: false, + deleteRow : false, + formatCells: false, + formatColumns: false, + formatRows: false, + insertColumns: false, + insertHyperlinks: false, + insertRows: false, + objects: false, + password: 'Password', + pivotTables: false, + scenarios: false, + sheet: true, + sort: false + } +}); ``` -Optionally, you can set validations for the WorkSheet +The `sheetProtection` options are the same as the "Protect Sheet" functions in the Review tab of Excel to prevent certain user editing. Setting a value to true means that that particular function is protected and the user will not be able to do that thing. All options are false by default except for 'sheet' which defaults to true if the sheetProtection attribute is set in the worksheet options, but false if it is not. -``` + +### Worksheet Validations + +Optionally, you can set validations for a WorkSheet. + +```javascript ws.setValidation({ - type: "list", - allowBlank: 1, - showInputMessage: 1, - showErrorMessage: 1, - sqref: "X2:X10", - formulas: [ - 'value1,value2' - ] + type: 'list', + allowBlank: 1, + showInputMessage: 1, + showErrorMessage: 1, + sqref: 'X2:X10', + formulas: [ + 'value1,value2' + ] }); ws.setValidation({ - type: "list", - allowBlank: 1, - sqref: "B2:B10", - formulas: [ - '=sheet2!$A$1:$A$2' - ] + type: 'list', + allowBlank: 1, + sqref: 'B2:B10', + formulas: [ + '=sheet2!$A$1:$A$2' + ] }); ``` -Add a cell to a WorkSheet with some data. -Cell can take 6 data types: String, Number, Formula, Date, Link, Bool. -Cell takes two arguments: row, col + +## Rows & Columns + +Set dimensions of rows or columns: + +```javascript +ws.Row(1).Height(30); +ws.Column(1).Width(100); ``` -ws.Cell(1,1).String('My String'); -ws.Cell(2,1).Number(5); -ws.Cell(2,2).Number(10); -ws.Cell(2,3).Formula("A2+B2"); -ws.Cell(2,4).Formula("A2/C2"); -ws.Cell(2,5).Date(new Date()); -ws.Cell(2,6).Link('http://google.com'); or ws.Cell(2,6).Link('http://google.com','Link name'); -ws.Cell(2,7).Bool(true); + + +Freeze rows and columns: + +```javascript +ws.Column(3).Freeze(); // freeze the first two columns (everything prior to the specified column) +ws.Column(3).Freeze(8); // freeze the first two columns and scroll to the 8th column +ws.Row(3).Freeze(); // freeze the first two rows (everything prior to the specified row) +ws.Row(3).Freeze(8); // freeze the first two rows and scroll to the 8th row. ``` -Set Dimensions of Rows or Columns +See also "Series with frozen Row" tab in sample output workbook. + + +Set a row to be a filter row: +```javascript +ws.Row(1).Filter(); // no arguments passed will add filter to any populated columns +ws.Row(1).Filter(1,8); // optional start and end columns ``` -ws.Row(1).Height(30); -ws.Column(1).Width(100); + +See also "Departmental Spending Report" tab in sample output workbook. + + +Hide a specific oow or column: + +```javascript +ws.Row(2).Hide(); +ws.Column(2).Hide(); ``` -Create a Style and apply it to a cell - -* Font - * Bold - * Takes no arguments. Bolds text - * Italics - * Takes no arguments. Italicizes text - * Underline - * Takes no arguments. Underlines text - * Family - * Takes one argument: name of font family. - * Color - * Takes one argument: rbg color - * Size - * Takes one argument: size in Pts - * WrapText - * Takes no arguments. Set text wrapping to true. - * Alignment - * Vertical - * Takes one argument of options top, center, bottom - * Horizontal - * Takes one argument of left, center, right - * Rotation - * Takes one argument as degrees to rotate -* Number - * Format - * Takes one argument: Number style string -* Fill - * Color - * Takes one argument: Color in rgb - * Pattern - * Takes one argument: pattern style (solid, lightUp, etc) -* Border - * Takes one argument: object defining border - * each ordinal (top, right, etc) are only required if you want to define a border. If omitted, no border will be added to that side. - * style is required if oridinal is defined. if color is omitted, it will default to black. - - ``` - { - top:{ - style:'thin', - color:'CCCCCC' - }, - right:{ - style:'thin', - color:'CCCCCC' - }, - bottom:{ - style:'thin', - color:'CCCCCC' - }, - left:{ - style:'thin', - color:'CCCCCC' - }, - diagonal:{ - style:'thin', - color:'CCCCCC' - } - } - ``` +Set groupings on rows and optionally collapse them: ``` +ws.Row(rowNum).Group(level,isCollapsed) +ws.Row(1).Group(1,true) +``` + +See also "Groupings Summary Top" and "Groupings Summary Bottom" in sample output. + + +## Cells + +Represents a cell within a worksheet. + +Cell can take 6 data types: `String`, `Number`, `Formula`, `Date`, `Link`, and `Bool`. + +Cell takes two arguments: row, col. + +Add a cell to a WorkSheet with some data: + +```javascript +ws.Cell(1, 1).String('My String'); +ws.Cell(2, 1).Number(5); +ws.Cell(2, 2).Number(10); +ws.Cell(2, 3).Formula('A2+B2'); +ws.Cell(2, 4).Formula('A2/C2'); +ws.Cell(2, 5).Date(new Date()); +ws.Cell(2, 6).Link('http://google.com'); +ws.Cell(2, 6).Link('http://google.com', 'Link name'); +ws.Cell(2, 7).Bool(true); +``` + + +## Styles + +Style objects can be applied to cells: + +```javascript var myStyle = wb.Style(); myStyle.Font.Bold(); myStyle.Font.Italics(); @@ -215,120 +222,147 @@ myStyle.Font.WrapText(true); var myStyle2 = wb.Style(); myStyle2.Font.Size(14); -myStyle2.Number.Format("$#,##0.00;($#,##0.00);-"); +myStyle2.Number.Format('$#,##0.00;($#,##0.00);-'); var myStyle3 = wb.Style(); myStyle3.Font.Size(14); -myStyle3.Number.Format("##%"); +myStyle3.Number.Format('##%'); myStyle3.Fill.Pattern('solid'); mystyle3.Fill.Color('CCCCCC'); myStyle3.Border({ - top:{ - style:'thin', - color:'CCCCCC' - }, - bottom:{ - style:'thick' - }, - left:{ - style:'thin' - }, - right:{ - style:'thin' - } + top: { + style:'thin', + color:'CCCCCC' + }, + bottom: { + style:'thick' + }, + left: { + style:'thin' + }, + right: { + style:'thin' + } }); -ws.Cell(1,1).Style(myStyle); -ws.Cell(1,2).String('My 2nd String').Style(myStyle); -ws.Cell(2,1).Style(myStyle2); -ws.Cell(2,2).Style(myStyle2); -ws.Cell(2,3).Style(myStyle2); -ws.Cell(2,4).Style(myStyle3); +ws.Cell(1, 1).Style(myStyle); +ws.Cell(1, 2).String('My 2nd String').Style(myStyle); +ws.Cell(2, 1).Style(myStyle2); +ws.Cell(2, 2).Style(myStyle2); +ws.Cell(2, 3).Style(myStyle2); +ws.Cell(2, 4).Style(myStyle3); ``` -Apply Formatting to Cell -Syntax similar to creating styles -``` -ws.Cell(1,1).Format.Font.Color('FF0000'); -ws.Cell(1,1).Format.Fill.Pattern('solid'); -ws.Cell(1,1).Format.Fill.Color('AEAEAE'); -``` +Available styles: -Merge Cells and apply Styles or Formats to ranges -ws.Cell(row1,col1,row2,col2,merge) +- `Font.Bold()` bolds text +- `Font.Italics()` italicizes text +- `Font.Underline()` underlines text +- `Font.Family('Arial')` name of font family +- `Font.Color('DDEEFF')` hex rgb font color +- `Font.Size(12)` font size in Pts +- `Font.WrapText()` set text wrapping +- `Font.Alignment.Vertical('top')` options are `top`, `center`, `bottom` +- `Font.Alignment.Horizontal('left')` options are `left`, `center`, `right` +- `Font.Alignment.Rotation(15)` degrees to rotate +- `Number.Format('style')` number style string +- `Fill.Color('DDEEFF')` background color in rgb +- `Fill.Pattern('solid')` pattern style `solid`, `lightUp`, etc. +- `Border({...})` border styles (see below) -``` -ws.Cell(1,1,2,5,true).String('Merged Cells'); -ws.Cell(3,1,4,5).String('Each Cell in Range Contains this String'); -ws.Cell(3,1,4,5).Style(myStyle); -ws.Cell(1,1,2,5).Format.Font.Family('Arial'); +Border Styles: + +Takes one argument: object defining border. Each ordinal (top, right, etc) are only required if you want to define a border. If omitted, no border will be added to that side. Style is required if oridinal is defined. If color is omitted, it will default to black. + + ```javascript +myStyle3.Border({ + top: { + style: 'thin', + color: 'CCCCCC' + }, + right: { + style: 'thin', + color: 'CCCCCC' + }, + bottom: { + style: 'thin', + color: 'CCCCCC' + }, + left: { + style: 'thin', + color: 'CCCCCC' + }, + diagonal: { + style: 'thin', + color: 'CCCCCC' + } +}); ``` -Freeze Columns and Rows to prevent moving when scrolling horizontally -First example will freeze the first two columns (everything prior to the specified column); -Second example will freeze the first two columns and scroll to the 8th column. -Third example will freeze the first two rows (everything prior to the specified row); -Forth example will freeze the first two rows and scroll to the 8th row. -See "Series with frozen Row" tab in sample output workbook +Apply formatting directly to a cell (similar syntax to creating styles): +```javascript +ws.Cell(1, 1).Format.Font.Color('FF0000'); +ws.Cell(1, 1).Format.Fill.Pattern('solid'); +ws.Cell(1, 1).Format.Fill.Color('AEAEAE'); ``` -ws.Column(3).Freeze(); -ws.Column(3).Freeze(8); -ws.Row(3).Freeze(); -ws.Row(3).Freeze(8); -``` -Set a row to be a filter row -Optionally specify start and end columns -If no arguments passed, will add filter to any populated columns -See "Departmental Spending Report" tab in sample output workbook +Merge cells and apply styles or mormats to ranges: -``` -ws.Row(1).Filter(); -ws.Row(1).Filter(1,8); -``` -Hide a specific Row or Column +`ws.Cell(row1, col1, row2, col2, merge)` -``` -ws.Row(2).Hide(); -ws.Column(2).Hide(); +```javascript +ws.Cell(1, 1, 2, 5, true).String('Merged Cells'); +ws.Cell(3, 1, 4, 5).String('Each Cell in Range Contains this String'); +ws.Cell(3, 1, 4, 5).Style(myStyle); +ws.Cell(1, 1, 2, 5).Format.Font.Family('Arial'); ``` -Set Groupings on Rows and optionally collapse them. -See "Groupings Summary Top" and "Groupings Summary Bottom" in sample output. -``` -ws.Row(rowNum).Group(level,isCollapsed) -ws.Row(1).Group(1,true) -``` +## Images -Insert an image into a WorkSheet for -Image takes one argument which is relative path to image from node script -Image can be passed optional Position which takes 4 arguments -img.Position(row, col, [rowOffset], [colOffset]) -row = top left corner of image will be anchored to top of this row -col = top left corner of image will be anchored to left of this column -rowOffset = offset from top of row in EMUs -colOfset = offset from left of col in EMUs - -Currently images should be saved at a resolution of 96dpi. +Images can be inserted into a worksheet. -``` +`img.Position(row, col, [rowOffset], [colOffset])` + +```javascript +var imgPath = './my-image.jpg'; // relative path from node script var img1 = ws.Image(imgPath); img1.Position(1,1); -var img2 = ws.Image(imgPath2).Position(3,3,1000000,2000000); +Set image position directly: + +```javascript +var img2 = ws.Image(imgPath2).Position( + 3, // row to anchor top left corner of image + 3, // col to anchor top left corner of image + 1000000, // offset from top of row in EMUs + 2000000 // offset from left of col in EMUs +); ``` +Currently images should be saved at a resolution of 96dpi. + +-------------------------------------------------------------------------------- + +## Writing Output + Write the Workbook to local file synchronously or Write the Workbook to local file asynchrously or Send file via node response -``` -wb.write("My Excel File.xlsx"); -wb.write("My Excel File.xlsx",function(err){ ... }); -wb.write("My Excel File.xlsx",res); +```javascript +wb.write('My Excel File.xlsx'); // write synchronously + +wb.write('My Excel File.xlsx', function (err) { + // done writing +}); +wb.write('My Excel File.xlsx', res); // write to http response ``` + +## Notes + +- [MS-XSLX spec (pdf)](http://download.microsoft.com/download/D/3/3/D334A189-E51B-47FF-B0E8-C0479AFB0E3C/[MS-XLSX].pdf) diff --git a/lib/Cell.js b/lib/Cell.js index d82b84b..d048834 100644 --- a/lib/Cell.js +++ b/lib/Cell.js @@ -1,848 +1,846 @@ -var Style = require(__dirname+'/Style.js'), - _ = require('underscore'); - -exports.Cell = function(row1, col1, row2, col2, isMerged){ - - var thisWS = this; - var theseCells = { - cells:[], - excelRefs:[] - }; - - /****************************** - Cell Range Methods - ******************************/ - theseCells.String = string; - theseCells.Number = number; - theseCells.Bool = bool; - theseCells.Format = format(); - theseCells.Style = style; - theseCells.Date = date; - theseCells.Formula = formula; - theseCells.Merge = mergeCells; - theseCells.getCell = getCell; - theseCells.Properties = propHandler; - theseCells.Link = hyperlink; - - row2=row2?row2:row1; - col2=col2?col2:col1; - - /****************************** - Add all cells in range to Cell definition - ******************************/ - for(var r = row1; r <= row2; r++){ - var thisRow = thisWS.Row(r); - for(var c = col1; c<= col2; c++){ - var thisCol = thisWS.Column(parseInt(c)); - if(!thisRow.cells[c]){ - thisRow.cells[c] = new cell(thisWS); - } - var thisCell = thisRow.cells[c]; - thisCell.attributes['r'] = c.toExcelAlpha() + r; - thisRow.attributes['spans'] = '1:'+thisRow.cellCount(); - theseCells.cells.push(thisCell); - } - } - theseCells.excelRefs = getAllCellsInNumericRange(row1,col1,row2,col2); - theseCells.cells.sort(excelCellsSort); - - if(isMerged){ - theseCells.Merge(); - } - /****************************** - Cell Range Method Definitions - ******************************/ - function string(val){ - - var chars, chr; - chars = /[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]/; - chr = val.match(chars); - if (chr) { - console.log('Invalid Character for XML "' + chr + '" in string "' + val +'"'); - val=val.replace(chr,''); - } - - if(typeof(val) != 'string'){ - console.log('Value sent to String function of cells %s was not a string, it has type of %s',JSON.stringify(theseCells.excelRefs),typeof(val)); - val = ''; - } - - val=val.toString(); - // Remove Control characters, they aren't understood by xmlbuilder - val=val.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]/, ''); - - if(!isMerged){ - theseCells.cells.e4nForEach(function(c,i){ - c.String(thisWS.wb.getStringIndex(val)); - }); - }else{ - var c = theseCells.cells[0]; - c.String(thisWS.wb.getStringIndex(val)); - } - return theseCells; - } - function format(){ - var methods = { - 'Number':formatter().number, - 'Date':formatter().date, - 'Font':{ - Family:formatter().font.family, - Size:formatter().font.size, - Bold:formatter().font.bold, - Italics:formatter().font.italics, - Underline:formatter().font.underline, - Color:formatter().font.color, - WrapText:formatter().font.wraptext, - Alignment:{ - Vertical: formatter().font.alignment.vertical, - Horizontal: formatter().font.alignment.horizontal - } - }, - Fill:{ - Color:formatter().fill.color, - Pattern:formatter().fill.pattern - }, - Border:formatter().border - }; - return methods; - } - function style(sty){ - - // If style has a border, split excel cells into rows - if(sty.xf.applyBorder > 0){ - var cellRows = []; - var curRow = []; - var curCol = ""; - theseCells.excelRefs.e4nForEach(function(cr,i){ - var thisCol = cr.replace(/[0-9]/g,''); - if(thisCol!=curCol){ - if(curRow.length > 0){ - cellRows.push(curRow); - curRow=[]; - } - curCol=thisCol; - } - curRow.push(cr); - if(i == theseCells.excelRefs.length-1 && curRow.length > 0){ - cellRows.push(curRow); - } - }); - - var borderEdges = {} - borderEdges.left = cellRows[0][0].replace(/[0-9]/g,''); - borderEdges.right = cellRows[cellRows.length-1][0].replace(/[0-9]/g,''); - borderEdges.top = cellRows[0][0].replace(/[a-zA-Z]/g,''); - borderEdges.bottom = cellRows[0][cellRows[0].length-1].replace(/[a-zA-Z]/g,''); - } - - theseCells.cells.e4nForEach(function(c,i){ - if(theseCells.excelRefs.length == 1 || sty.xf.applyBorder == 0){ - c.Style(sty.xf.xfId); - }else{ - var curBorderId = sty.xf.borderId; - var masterBorder = JSON.parse(JSON.stringify(c.ws.wb.styleData.borders[curBorderId])); - - var thisBorder = {}; - var cellRef = c.getAttribute('r'); - var cellCol = cellRef.replace(/[0-9]/g,''); - var cellRow = cellRef.replace(/[a-zA-Z]/g,''); - - if(cellRow == borderEdges.top){ - thisBorder.top = masterBorder.top; - } - if(cellRow == borderEdges.bottom){ - thisBorder.bottom = masterBorder.bottom; - } - if(cellCol == borderEdges.left){ - thisBorder.left = masterBorder.left; - } - if(cellCol == borderEdges.right){ - thisBorder.right = masterBorder.right; - } - - if(c.getAttribute('s')!=undefined){ - var curStyle = Style.getStyleById(c.ws.wb,c.getAttribute('s')); - }else{ - var curStyle = Style.getStyleById(c.ws.wb,sty.xf.xfId); - } - - var newBorder = new Style.border(c.ws.wb,thisBorder); - var curXF = JSON.parse(JSON.stringify(curStyle)); - curXF.applyBorder=1; - curXF.borderId=newBorder.borderId; - var newXF = new Style.cellXfs(c.ws.wb,curXF); - c.setAttribute('s',newXF.xfId); - } - }); - return theseCells; - } - function date(val){ - if(!val || !val.toISOString){ - if(val.toISOString() != new Date(val).toISOString()){ - val = new Date(); - console.log('Value sent to Date function of cells %s was not a date, it has type of %s',JSON.stringify(theseCells.excelRefs),typeof(val)); - } - } - if(!isMerged){ - theseCells.cells.e4nForEach(function(c,i){ - var styleInfo = c.getStyleInfo(); - if(styleInfo.applyNumberFormat==1){ - if(styleInfo.numFmt.formatCode.substr(0,7) != '[$-409]'){ - console.log('Number format was already set for cell %s. It will be overridden with date format',thisCell.getAttribute('r')); - c.toCellRange().Format.Date(); - } - }else{ - c.toCellRange().Format.Date(); - } - c.Date(val); - }); - }else{ - var c = theseCells.cells[0]; - var styleInfo = c.getStyleInfo(); - if(styleInfo.applyNumberFormat==1){ - if(styleInfo.numFmt.formatCode.substr(0,7) != '[$-409]'){ - console.log('Number format was already set for cell %s. It will be overridden with date format',thisCell.getAttribute('r')); - c.toCellRange().Format.Date(); - } - }else{ - c.toCellRange().Format.Date(); - } - c.Date(val); - } - return theseCells; - } - function formula(val){ - if(typeof(val) != 'string'){ - console.log('Value sent to Formula function of cells %s was not a string, it has type of %s',JSON.stringify(theseCells.excelRefs),typeof(val)); - val = ''; - } - if(!isMerged){ - theseCells.cells.e4nForEach(function(c,i){ - c.Formula(val); - }); - }else{ - var c = theseCells.cells[0]; - c.Formula(val); - } - - return theseCells; - } - function number(val){ - if(val == undefined || parseFloat(val) != val){ - console.log('Value sent to Number function of cells %s was not a number, it has type of %s and value of %s', - JSON.stringify(theseCells.excelRefs), - typeof(val), - val - ); - val = ''; - } - val=parseFloat(val); - - if(!isMerged){ - theseCells.cells.e4nForEach(function(c,i){ - c.Number(val); - if(c.getAttribute('t')){ - c.deleteAttribute('t'); - } - }); - }else{ - var c = theseCells.cells[0]; - c.Number(val); - if(c.getAttribute('t')){ - c.deleteAttribute('t'); - } - } - return theseCells; - } - function bool(val){ - if(val == undefined || typeof (val.toString().toLowerCase() === 'true' || ((val.toString().toLowerCase() === 'false')? false: val)) !== 'boolean') { - console.log('Value sent to Bool function of cells %s was not a bool, it has type of %s and value of %s', - JSON.stringify(theseCells.excelRefs), - typeof(val), - val - ); - val = ''; - } - val = val.toString().toLowerCase() === 'true'; - - if(!isMerged){ - theseCells.cells.e4nForEach(function(c,i){ - c.Bool(val); - }); - }else{ - var c = theseCells.cells[0]; - c.Bool(val); - } - return theseCells; - } - function hyperlink(url,val){ - if(!val){ - val = url; - } - string(val); - if(!thisWS.hyperlinks){ - thisWS.hyperlinks=[]; - }; - var thisId=generateRId(); - - thisWS.hyperlinks.push({ref:theseCells.excelRefs[0],url:url,id:thisId}); - return theseCells; - } - function mergeCells(){ - if(!thisWS.mergeCells){ - thisWS.mergeCells=[]; - }; - var cellRange = this.excelRefs[0]+':'+this.excelRefs[this.excelRefs.length-1]; - var rangeCells = this.excelRefs; - var okToMerge = true; - thisWS.mergeCells.e4nForEach(function(cr){ - // Check to see if currently merged cells contain cells in new merge request - var curCells = getAllCellsInExcelRange(cr); - var intersection = arrayIntersectSafe(rangeCells,curCells); - if(intersection.length > 0){ - okToMerge = false; - console.log([ - 'Invalid Range for : '+col1.toExcelAlpha()+row1+":"+col2.toExcelAlpha()+row2, - 'Some cells in this range are already included in another merged cell range: '+c['mergeCell'][0]['@ref'], - 'The following are the intersection', - intersection - ]); - } - }); - if(okToMerge){ - thisWS.mergeCells.push(cellRange); - } - } - function formatter(){ - - var methods = { - number:function(fmt){ - theseCells.cells.e4nForEach(function(c){ - setNumberFormat(c, 'formatCode', fmt); - }); - return theseCells; - }, - date:function(fmt){ - theseCells.cells.e4nForEach(function(c){ - setDateFormat(c, 'formatCode', fmt); - }); - return theseCells; - }, - font:{ - family:function(val){ - theseCells.cells.e4nForEach(function(c){ - setCellFontAttribute(c,'name',val); - }); - return theseCells; - }, - size:function(val){ - theseCells.cells.e4nForEach(function(c){ - setCellFontAttribute(c,'sz',val); - }); - return theseCells; - }, - bold:function(){ - theseCells.cells.e4nForEach(function(c){ - setCellFontAttribute(c,'bold',true); - }); - return theseCells; - }, - italics:function(){ - theseCells.cells.e4nForEach(function(c){ - setCellFontAttribute(c,'italics',true); - }); - return theseCells; - }, - underline:function(){ - theseCells.cells.e4nForEach(function(c){ - setCellFontAttribute(c,'underline',true); - }); - return theseCells; - }, - color:function(val){ - theseCells.cells.e4nForEach(function(c){ - setCellFontAttribute(c,'color',Style.cleanColor(val)); - }); - return theseCells; - }, - wraptext:function(val){ - theseCells.cells.e4nForEach(function(c){ - setAlignmentAttribute(c,'wrapText',1); - }); - return theseCells; - }, - alignment:{ - vertical:function(val){ - theseCells.cells.e4nForEach(function(c){ - setAlignmentAttribute(c,'vertical',val); - }); - return theseCells; - }, - horizontal:function(val){ - theseCells.cells.e4nForEach(function(c){ - setAlignmentAttribute(c,'horizontal',val); - }); - return theseCells; - } - } - }, - fill:{ - color:function(val){ - theseCells.cells.e4nForEach(function(c){ - setCellFill(c,'fgColor',Style.cleanColor(val)); - }); - return theseCells; - }, - pattern:function(val){ - theseCells.cells.e4nForEach(function(c){ - setCellFill(c,'patternType',val); - }); - return theseCells; - } - }, - border:setCellsBorder - } - - function setAlignmentAttribute(c,attr,val){ - if(c.getAttribute('s')!=undefined){ - var curStyle = Style.getStyleById(c.ws.wb,c.getAttribute('s')); - }else{ - var curStyle = Style.getStyleById(c.ws.wb,0); - } - - var curXF = JSON.parse(JSON.stringify(curStyle)); - if(!curXF.alignment){ - curXF.alignment = {}; - } - curXF.applyAlignment=1; - curXF.alignment[attr]=val; - - var newXF = new Style.cellXfs(c.ws.wb,curXF); - c.setAttribute('s',newXF.xfId); - } - - function setNumberFormat(c, attr, val){ - if(c.getAttribute('s')!=undefined){ - var curStyle = Style.getStyleById(c.ws.wb,c.getAttribute('s')); - }else{ - var curStyle = Style.getStyleById(c.ws.wb,0); - } - - if(curStyle.numFmtId != 164 && curStyle.numFmtId != 14){ - var curNumFmt = JSON.parse(JSON.stringify(c.ws.wb.styleData.numFmts[curStyle.numFmtId - 165])); - }else{ - var curNumFmt = {}; - } - - curNumFmt[attr] = val; - var thisFmt = new Style.numFmt(c.ws.wb,curNumFmt); - var curXF = JSON.parse(JSON.stringify(curStyle)); - curXF.applyNumberFormat=1; - curXF.numFmtId=thisFmt.numFmtId; - var newXF = new Style.cellXfs(c.ws.wb,curXF); - c.setAttribute('s',newXF.xfId); - } - - function setDateFormat(c, attr, val){ - if(c.getAttribute('s')!=undefined){ - var curStyle = Style.getStyleById(c.ws.wb,c.getAttribute('s')); - }else{ - var curStyle = Style.getStyleById(c.ws.wb,0); - } - - if(curStyle.numFmtId != 164 && curStyle.numFmtId != 14 && c.ws.wb.styleData.numFmts[curStyle.numFmtId - 165].formatCode.substr(0,7) == '[$-409]'){ - var curNumFmt = JSON.parse(JSON.stringify(c.ws.wb.styleData.numFmts[curStyle.numFmtId - 165])); - }else{ - var curNumFmt = {}; - } - - var curXF = JSON.parse(JSON.stringify(curStyle)); - curXF.applyNumberFormat=1; - if(val){ - curNumFmt[attr] = val; - var thisFmt = new Style.numFmt(c.ws.wb,curNumFmt); - curXF.numFmtId=thisFmt.numFmtId; - }else{ - curXF.numFmtId=14; - } - var newXF = new Style.cellXfs(c.ws.wb,curXF); - c.setAttribute('s',newXF.xfId); - } - - function setCellsBorder(borderObj){ - var cellRows = []; - var curRow = []; - var curCol = ""; - theseCells.excelRefs.e4nForEach(function(cr,i){ - var thisCol = cr.replace(/[0-9]/g,''); - if(thisCol!=curCol){ - if(curRow.length > 0){ - cellRows.push(curRow); - curRow=[]; - } - curCol=thisCol; - } - curRow.push(cr); - if(i == theseCells.excelRefs.length-1 && curRow.length > 0){ - cellRows.push(curRow); - } - }); - - var borderEdges = {} - borderEdges.left = cellRows[0][0].replace(/[0-9]/g,''); - borderEdges.right = cellRows[cellRows.length-1][0].replace(/[0-9]/g,''); - borderEdges.top = cellRows[0][0].replace(/[a-zA-Z]/g,''); - borderEdges.bottom = cellRows[0][cellRows[0].length-1].replace(/[a-zA-Z]/g,''); - - theseCells.cells.e4nForEach(function(c,i){ - if(c.getAttribute('s')!=undefined){ - var curStyle = Style.getStyleById(c.ws.wb,c.getAttribute('s')); - }else{ - var curStyle = Style.getStyleById(c.ws.wb,0); - } - - var thisBorder = {}; - if(curStyle.applyBorder == 1){ - var curBorder = Style.getBorderById(c.ws.wb, curStyle.borderId); - thisBorder.left = curBorder.left; - thisBorder.right = curBorder.right; - thisBorder.top = curBorder.top; - thisBorder.bottom = curBorder.bottom; - thisBorder.diagonal = curBorder.diagonal; - } - var cellRef = c.getAttribute('r'); - var cellCol = cellRef.replace(/[0-9]/g,''); - var cellRow = cellRef.replace(/[a-zA-Z]/g,''); - - if(cellRow == borderEdges.top){ - thisBorder.top = borderObj.top; - } - if(cellRow == borderEdges.bottom){ - thisBorder.bottom = borderObj.bottom; - } - if(cellCol == borderEdges.left){ - thisBorder.left = borderObj.left; - } - if(cellCol == borderEdges.right){ - thisBorder.right = borderObj.right; - } - - - var newBorder = new Style.border(c.ws.wb,thisBorder); - var curXF = JSON.parse(JSON.stringify(curStyle)); - curXF.applyBorder=1; - curXF.borderId=newBorder.borderId; - var newXF = new Style.cellXfs(c.ws.wb,curXF); - c.setAttribute('s',newXF.xfId); - if(!c.getValue().type){ - c.String(''); - } - - }); - } - - function setCellFill(c, attr, val){ - if(c.getAttribute('s')!=undefined){ - var curStyle = Style.getStyleById(c.ws.wb,c.getAttribute('s')); - }else{ - var curStyle = Style.getStyleById(c.ws.wb,0); - } - - var curFill = JSON.parse(JSON.stringify(c.ws.wb.styleData.fills[curStyle.fillId])); - - curFill[attr] = val; - var thisFill = new Style.fill(c.ws.wb,curFill); - var curXF = JSON.parse(JSON.stringify(curStyle)); - curXF.applyFill=1; - curXF.fillId=thisFill.fillId; - var newXF = new Style.cellXfs(c.ws.wb,curXF); - c.setAttribute('s',newXF.xfId);; - } - - function setCellFontAttribute(c,attr,val){ - if(c.getAttribute('s')!=undefined){ - var curStyle = Style.getStyleById(c.ws.wb,c.getAttribute('s')); - }else{ - var curStyle = Style.getStyleById(c.ws.wb,0); - } - var curFont = JSON.parse(JSON.stringify(c.ws.wb.styleData.fonts[curStyle.fontId])); - curFont[attr] = val; - - var thisFont = new Style.font(c.ws.wb,curFont); - var curXF = JSON.parse(JSON.stringify(curStyle)); - curXF.applyFont=1; - curXF.fontId=thisFont.fontId; - var newXF = new Style.cellXfs(c.ws.wb,curXF); - c.setAttribute('s',newXF.xfId); - } - - return methods; - } - function getCell(ref){ - - return theseCells.cells[theseCells.excelRefs.indexOf(ref)]; - } - function propHandler(){ - var response = []; - - theseCells.cells.e4nForEach(function(c,i){ - response.push(c.getValue()); - }); - - return response; - } - return theseCells; +var _ = require('underscore'); +var Style = require('./style'); + +module.exports = cellAccessor; + +//------------------------------------------------------------------------------ + +function cellAccessor(row1, col1, row2, col2, isMerged) { + + var thisWS = this; + var theseCells = { + cells: [], + excelRefs: [] + }; + + /****************************** + Cell Range Methods + ******************************/ + theseCells.String = string; + theseCells.Number = number; + theseCells.Bool = bool; + theseCells.Format = format(); + theseCells.Style = style; + theseCells.Date = date; + theseCells.Formula = formula; + theseCells.Merge = mergeCells; + theseCells.getCell = getCell; + theseCells.Properties = propHandler; + theseCells.Link = hyperlink; + + row2 = row2 ? row2 : row1; + col2 = col2 ? col2 : col1; + + /****************************** + Add all cells in range to Cell definition + ******************************/ + for (var r = row1; r <= row2; r++) { + var thisRow = thisWS.Row(r); + for (var c = col1; c <= col2; c++) { + var thisCol = thisWS.Column(parseInt(c)); + if (!thisRow.cells[c]) { + thisRow.cells[c] = new Cell(thisWS); + } + var thisCell = thisRow.cells[c]; + thisCell.attributes['r'] = c.toExcelAlpha() + r; + thisRow.attributes['spans'] = '1:' + thisRow.cellCount(); + theseCells.cells.push(thisCell); + } + } + theseCells.excelRefs = getAllCellsInNumericRange(row1, col1, row2, col2); + theseCells.cells.sort(excelCellsSort); + + if (isMerged) { + theseCells.Merge(); + } + /****************************** + Cell Range Method Definitions + ******************************/ + function string(val) { + + var chars, chr; + chars = /[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]/; + chr = val.match(chars); + if (chr) { + console.log('Invalid Character for XML "' + chr + '" in string "' + val + '"'); + val = val.replace(chr, ''); + } + + if (typeof(val) !== 'string') { + console.log('Value sent to String function of cells %s was not a string, it has type of %s', JSON.stringify(theseCells.excelRefs), typeof(val)); + val = ''; + } + + val = val.toString(); + // Remove Control characters, they aren't understood by xmlbuilder + val = val.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]/, ''); + + if (!isMerged) { + theseCells.cells.e4nForEach(function (c, i) { + c.String(thisWS.wb.getStringIndex(val)); + }); + } else { + var c = theseCells.cells[0]; + c.String(thisWS.wb.getStringIndex(val)); + } + return theseCells; + } + function format() { + var methods = { + 'Number': formatter().number, + 'Date': formatter().date, + 'Font': { + Family: formatter().font.family, + Size: formatter().font.size, + Bold: formatter().font.bold, + Italics: formatter().font.italics, + Underline: formatter().font.underline, + Color: formatter().font.color, + WrapText: formatter().font.wraptext, + Alignment: { + Vertical: formatter().font.alignment.vertical, + Horizontal: formatter().font.alignment.horizontal + } + }, + Fill: { + Color: formatter().fill.color, + Pattern: formatter().fill.pattern + }, + Border: formatter().border + }; + return methods; + } + function style(sty) { + + // If style has a border, split excel cells into rows + if (sty.xf.applyBorder > 0) { + var cellRows = []; + var curRow = []; + var curCol = ''; + theseCells.excelRefs.e4nForEach(function (cr, i) { + var thisCol = cr.replace(/[0-9]/g, ''); + if (thisCol !== curCol) { + if (curRow.length > 0) { + cellRows.push(curRow); + curRow = []; + } + curCol = thisCol; + } + curRow.push(cr); + if (i === theseCells.excelRefs.length - 1 && curRow.length > 0) { + cellRows.push(curRow); + } + }); + + var borderEdges = {}; + borderEdges.left = cellRows[0][0].replace(/[0-9]/g, ''); + borderEdges.right = cellRows[cellRows.length - 1][0].replace(/[0-9]/g, ''); + borderEdges.top = cellRows[0][0].replace(/[a-zA-Z]/g, ''); + borderEdges.bottom = cellRows[0][cellRows[0].length - 1].replace(/[a-zA-Z]/g, ''); + } + + theseCells.cells.e4nForEach(function (c, i) { + if (theseCells.excelRefs.length === 1 || sty.xf.applyBorder === 0) { + c.Style(sty.xf.xfId); + } else { + var curBorderId = sty.xf.borderId; + var masterBorder = JSON.parse(JSON.stringify(c.ws.wb.styleData.borders[curBorderId])); + + var thisBorder = {}; + var cellRef = c.getAttribute('r'); + var cellCol = cellRef.replace(/[0-9]/g, ''); + var cellRow = cellRef.replace(/[a-zA-Z]/g, ''); + + if (cellRow === borderEdges.top) { + thisBorder.top = masterBorder.top; + } + if (cellRow === borderEdges.bottom) { + thisBorder.bottom = masterBorder.bottom; + } + if (cellCol === borderEdges.left) { + thisBorder.left = masterBorder.left; + } + if (cellCol === borderEdges.right) { + thisBorder.right = masterBorder.right; + } + + if (c.getAttribute('s') !== undefined) { + var curStyle = Style.getStyleById(c.ws.wb, c.getAttribute('s')); + } else { + var curStyle = Style.getStyleById(c.ws.wb, sty.xf.xfId); + } + + var newBorder = new Style.border(c.ws.wb, thisBorder); + var curXF = JSON.parse(JSON.stringify(curStyle)); + curXF.applyBorder = 1; + curXF.borderId = newBorder.borderId; + var newXF = new Style.cellXfs(c.ws.wb, curXF); + c.setAttribute('s', newXF.xfId); + } + }); + return theseCells; + } + function date(val) { + if (!val || !val.toISOString) { + if (val.toISOString() !== new Date(val).toISOString()) { + val = new Date(); + console.log('Value sent to Date function of cells %s was not a date, it has type of %s', JSON.stringify(theseCells.excelRefs), typeof(val)); + } + } + if (!isMerged) { + theseCells.cells.e4nForEach(function (c, i) { + var styleInfo = c.getStyleInfo(); + if (styleInfo.applyNumberFormat === 1) { + if (styleInfo.numFmt.formatCode.substr(0, 7) !== '[$-409]') { + console.log('Number format was already set for cell %s. It will be overridden with date format', thisCell.getAttribute('r')); + c.toCellRange().Format.Date(); + } + } else { + c.toCellRange().Format.Date(); + } + c.Date(val); + }); + } else { + var c = theseCells.cells[0]; + var styleInfo = c.getStyleInfo(); + if (styleInfo.applyNumberFormat === 1) { + if (styleInfo.numFmt.formatCode.substr(0, 7) !== '[$-409]') { + console.log('Number format was already set for cell %s. It will be overridden with date format', thisCell.getAttribute('r')); + c.toCellRange().Format.Date(); + } + } else { + c.toCellRange().Format.Date(); + } + c.Date(val); + } + return theseCells; + } + function formula(val) { + if (typeof(val) !== 'string') { + console.log('Value sent to Formula function of cells %s was not a string, it has type of %s', JSON.stringify(theseCells.excelRefs), typeof(val)); + val = ''; + } + if (!isMerged) { + theseCells.cells.e4nForEach(function (c, i) { + c.Formula(val); + }); + } else { + var c = theseCells.cells[0]; + c.Formula(val); + } + + return theseCells; + } + function number(val) { + if (val === undefined || parseFloat(val) !== val) { + console.log('Value sent to Number function of cells %s was not a number, it has type of %s and value of %s', + JSON.stringify(theseCells.excelRefs), + typeof(val), + val + ); + val = ''; + } + val = parseFloat(val); + + if (!isMerged) { + theseCells.cells.e4nForEach(function (c, i) { + c.Number(val); + if (c.getAttribute('t')) { + c.deleteAttribute('t'); + } + }); + } else { + var c = theseCells.cells[0]; + c.Number(val); + if (c.getAttribute('t')) { + c.deleteAttribute('t'); + } + } + return theseCells; + } + function bool(val) { + if (val === undefined || typeof (val.toString().toLowerCase() === 'true' || ((val.toString().toLowerCase() === 'false') ? false : val)) !== 'boolean') { + console.log('Value sent to Bool function of cells %s was not a bool, it has type of %s and value of %s', + JSON.stringify(theseCells.excelRefs), + typeof(val), + val + ); + val = ''; + } + val = val.toString().toLowerCase() === 'true'; + + if (!isMerged) { + theseCells.cells.e4nForEach(function (c, i) { + c.Bool(val); + }); + } else { + var c = theseCells.cells[0]; + c.Bool(val); + } + return theseCells; + } + function hyperlink(url, val) { + if (!val) { + val = url; + } + string(val); + if (!thisWS.hyperlinks) { + thisWS.hyperlinks = []; + } + var thisId = generateRId(); + + thisWS.hyperlinks.push({ ref: theseCells.excelRefs[0], url: url, id: thisId }); + return theseCells; + } + function mergeCells() { + if (!thisWS.mergeCells) { + thisWS.mergeCells = []; + } + var cellRange = this.excelRefs[0] + ':' + this.excelRefs[this.excelRefs.length - 1]; + var rangeCells = this.excelRefs; + var okToMerge = true; + thisWS.mergeCells.e4nForEach(function (cr) { + // Check to see if currently merged cells contain cells in new merge request + var curCells = getAllCellsInExcelRange(cr); + var intersection = arrayIntersectSafe(rangeCells, curCells); + if (intersection.length > 0) { + okToMerge = false; + console.log([ + 'Invalid Range for : ' + col1.toExcelAlpha() + row1 + ':' + col2.toExcelAlpha() + row2, + 'Some cells in this range are already included in another merged cell range: ' + c['mergeCell'][0]['@ref'], + 'The following are the intersection', + intersection + ]); + } + }); + if (okToMerge) { + thisWS.mergeCells.push(cellRange); + } + } + function formatter() { + + var methods = { + number: function (fmt) { + theseCells.cells.e4nForEach(function (c) { + setNumberFormat(c, 'formatCode', fmt); + }); + return theseCells; + }, + date: function (fmt) { + theseCells.cells.e4nForEach(function (c) { + setDateFormat(c, 'formatCode', fmt); + }); + return theseCells; + }, + font: { + family: function (val) { + theseCells.cells.e4nForEach(function (c) { + setCellFontAttribute(c, 'name', val); + }); + return theseCells; + }, + size: function (val) { + theseCells.cells.e4nForEach(function (c) { + setCellFontAttribute(c, 'sz', val); + }); + return theseCells; + }, + bold: function () { + theseCells.cells.e4nForEach(function (c) { + setCellFontAttribute(c, 'bold', true); + }); + return theseCells; + }, + italics: function () { + theseCells.cells.e4nForEach(function (c) { + setCellFontAttribute(c, 'italics', true); + }); + return theseCells; + }, + underline: function () { + theseCells.cells.e4nForEach(function (c) { + setCellFontAttribute(c, 'underline', true); + }); + return theseCells; + }, + color: function (val) { + theseCells.cells.e4nForEach(function (c) { + setCellFontAttribute(c, 'color', Style.cleanColor(val)); + }); + return theseCells; + }, + wraptext: function (val) { + theseCells.cells.e4nForEach(function (c) { + setAlignmentAttribute(c, 'wrapText', 1); + }); + return theseCells; + }, + alignment: { + vertical: function (val) { + theseCells.cells.e4nForEach(function (c) { + setAlignmentAttribute(c, 'vertical', val); + }); + return theseCells; + }, + horizontal: function (val) { + theseCells.cells.e4nForEach(function (c) { + setAlignmentAttribute(c, 'horizontal', val); + }); + return theseCells; + } + } + }, + fill: { + color: function (val) { + theseCells.cells.e4nForEach(function (c) { + setCellFill(c, 'fgColor', Style.cleanColor(val)); + }); + return theseCells; + }, + pattern: function (val) { + theseCells.cells.e4nForEach(function (c) { + setCellFill(c, 'patternType', val); + }); + return theseCells; + } + }, + border: setCellsBorder + }; + + function setAlignmentAttribute(c, attr, val) { + if (c.getAttribute('s') !== undefined) { + var curStyle = Style.getStyleById(c.ws.wb, c.getAttribute('s')); + } else { + var curStyle = Style.getStyleById(c.ws.wb, 0); + } + + var curXF = JSON.parse(JSON.stringify(curStyle)); + if (!curXF.alignment) { + curXF.alignment = {}; + } + curXF.applyAlignment = 1; + curXF.alignment[attr] = val; + + var newXF = new Style.cellXfs(c.ws.wb, curXF); + c.setAttribute('s', newXF.xfId); + } + + function setNumberFormat(c, attr, val) { + if (c.getAttribute('s') !== undefined) { + var curStyle = Style.getStyleById(c.ws.wb, c.getAttribute('s')); + } else { + var curStyle = Style.getStyleById(c.ws.wb, 0); + } + + if (curStyle.numFmtId !== 164 && curStyle.numFmtId !== 14) { + var curNumFmt = JSON.parse(JSON.stringify(c.ws.wb.styleData.numFmts[curStyle.numFmtId - 165])); + } else { + var curNumFmt = {}; + } + + curNumFmt[attr] = val; + var thisFmt = new Style.numFmt(c.ws.wb, curNumFmt); + var curXF = JSON.parse(JSON.stringify(curStyle)); + curXF.applyNumberFormat = 1; + curXF.numFmtId = thisFmt.numFmtId; + var newXF = new Style.cellXfs(c.ws.wb, curXF); + c.setAttribute('s', newXF.xfId); + } + + function setDateFormat(c, attr, val) { + if (c.getAttribute('s') !== undefined) { + var curStyle = Style.getStyleById(c.ws.wb, c.getAttribute('s')); + } else { + var curStyle = Style.getStyleById(c.ws.wb, 0); + } + + if (curStyle.numFmtId !== 164 && curStyle.numFmtId !== 14 && c.ws.wb.styleData.numFmts[curStyle.numFmtId - 165].formatCode.substr(0, 7) === '[$-409]') { + var curNumFmt = JSON.parse(JSON.stringify(c.ws.wb.styleData.numFmts[curStyle.numFmtId - 165])); + } else { + var curNumFmt = {}; + } + + var curXF = JSON.parse(JSON.stringify(curStyle)); + curXF.applyNumberFormat = 1; + if (val) { + curNumFmt[attr] = val; + var thisFmt = new Style.numFmt(c.ws.wb, curNumFmt); + curXF.numFmtId = thisFmt.numFmtId; + } else { + curXF.numFmtId = 14; + } + var newXF = new Style.cellXfs(c.ws.wb, curXF); + c.setAttribute('s', newXF.xfId); + } + + function setCellsBorder(borderObj) { + var cellRows = []; + var curRow = []; + var curCol = ''; + theseCells.excelRefs.e4nForEach(function (cr, i) { + var thisCol = cr.replace(/[0-9]/g, ''); + if (thisCol !== curCol) { + if (curRow.length > 0) { + cellRows.push(curRow); + curRow = []; + } + curCol = thisCol; + } + curRow.push(cr); + if (i === theseCells.excelRefs.length - 1 && curRow.length > 0) { + cellRows.push(curRow); + } + }); + + var borderEdges = {}; + borderEdges.left = cellRows[0][0].replace(/[0-9]/g, ''); + borderEdges.right = cellRows[cellRows.length - 1][0].replace(/[0-9]/g, ''); + borderEdges.top = cellRows[0][0].replace(/[a-zA-Z]/g, ''); + borderEdges.bottom = cellRows[0][cellRows[0].length - 1].replace(/[a-zA-Z]/g, ''); + + theseCells.cells.e4nForEach(function (c, i) { + if (c.getAttribute('s') !== undefined) { + var curStyle = Style.getStyleById(c.ws.wb, c.getAttribute('s')); + } else { + var curStyle = Style.getStyleById(c.ws.wb, 0); + } + + var thisBorder = {}; + if (curStyle.applyBorder === 1) { + var curBorder = Style.getBorderById(c.ws.wb, curStyle.borderId); + thisBorder.left = curBorder.left; + thisBorder.right = curBorder.right; + thisBorder.top = curBorder.top; + thisBorder.bottom = curBorder.bottom; + thisBorder.diagonal = curBorder.diagonal; + } + var cellRef = c.getAttribute('r'); + var cellCol = cellRef.replace(/[0-9]/g, ''); + var cellRow = cellRef.replace(/[a-zA-Z]/g, ''); + + if (cellRow === borderEdges.top) { + thisBorder.top = borderObj.top; + } + if (cellRow === borderEdges.bottom) { + thisBorder.bottom = borderObj.bottom; + } + if (cellCol === borderEdges.left) { + thisBorder.left = borderObj.left; + } + if (cellCol === borderEdges.right) { + thisBorder.right = borderObj.right; + } + + + var newBorder = new Style.border(c.ws.wb, thisBorder); + var curXF = JSON.parse(JSON.stringify(curStyle)); + curXF.applyBorder = 1; + curXF.borderId = newBorder.borderId; + var newXF = new Style.cellXfs(c.ws.wb, curXF); + c.setAttribute('s', newXF.xfId); + if (!c.getValue().type) { + c.String(''); + } + + }); + } + + function setCellFill(c, attr, val) { + if (c.getAttribute('s') !== undefined) { + var curStyle = Style.getStyleById(c.ws.wb, c.getAttribute('s')); + } else { + var curStyle = Style.getStyleById(c.ws.wb, 0); + } + + var curFill = JSON.parse(JSON.stringify(c.ws.wb.styleData.fills[curStyle.fillId])); + + curFill[attr] = val; + var thisFill = new Style.fill(c.ws.wb, curFill); + var curXF = JSON.parse(JSON.stringify(curStyle)); + curXF.applyFill = 1; + curXF.fillId = thisFill.fillId; + var newXF = new Style.cellXfs(c.ws.wb, curXF); + c.setAttribute('s', newXF.xfId); + } + + function setCellFontAttribute(c, attr, val) { + if (c.getAttribute('s') !== undefined) { + var curStyle = Style.getStyleById(c.ws.wb, c.getAttribute('s')); + } else { + var curStyle = Style.getStyleById(c.ws.wb, 0); + } + var curFont = JSON.parse(JSON.stringify(c.ws.wb.styleData.fonts[curStyle.fontId])); + curFont[attr] = val; + + var thisFont = new Style.font(c.ws.wb, curFont); + var curXF = JSON.parse(JSON.stringify(curStyle)); + curXF.applyFont = 1; + curXF.fontId = thisFont.fontId; + var newXF = new Style.cellXfs(c.ws.wb, curXF); + c.setAttribute('s', newXF.xfId); + } + + return methods; + } + function getCell(ref) { + + return theseCells.cells[theseCells.excelRefs.indexOf(ref)]; + } + function propHandler() { + var response = []; + + theseCells.cells.e4nForEach(function (c, i) { + response.push(c.getValue()); + }); + + return response; + } + return theseCells; } -var cell = function(ws){ - var thisCell = this; - thisCell.ws = ws; +// ----------------------------------------------------------------------------- - /****************************** - Cell Definition - ******************************/ - thisCell.attributes = {}; - thisCell.children = {}; - thisCell.valueType; +function Cell(ws) { + var thisCell = this; + thisCell.ws = ws; + thisCell.attributes = {}; + thisCell.children = {}; + thisCell.valueType; - return thisCell; + return thisCell; } /****************************** - Cell Methods + Cell Methods ******************************/ -cell.prototype.setAttribute = setAttribute; -cell.prototype.getAttribute = getAttribute; -cell.prototype.deleteAttribute = deleteAttribute; -cell.prototype.getStyleInfo = getStyleInfo; -cell.prototype.toCellRange = getCellRange; -cell.prototype.addChild = addChild; -cell.prototype.deleteChild = deleteChild; -cell.prototype.getChild = getChild; -cell.prototype.String = string; -cell.prototype.Number = number; -cell.prototype.Bool = bool; -cell.prototype.Date = date; -cell.prototype.Formula = formula; -cell.prototype.Style = styler; -cell.prototype.getValue = getValue; +Cell.prototype.setAttribute = setAttribute; +Cell.prototype.getAttribute = getAttribute; +Cell.prototype.deleteAttribute = deleteAttribute; +Cell.prototype.getStyleInfo = getStyleInfo; +Cell.prototype.toCellRange = getCellRange; +Cell.prototype.addChild = addChild; +Cell.prototype.deleteChild = deleteChild; +Cell.prototype.getChild = getChild; +Cell.prototype.String = string; +Cell.prototype.Number = number; +Cell.prototype.Bool = bool; +Cell.prototype.Date = date; +Cell.prototype.Formula = formula; +Cell.prototype.Style = styler; +Cell.prototype.getValue = getValue; /****************************** - Cell Method Definitions + Cell Method Definitions ******************************/ -function addChild(key,val){ +function addChild(key, val) { - return this.children[key]=val; + return this.children[key] = val; } -function getChild(key){ +function getChild(key) { - return this.children[key]; + return this.children[key]; } -function deleteChild(key){ +function deleteChild(key) { - return delete this.children[key]; + return delete this.children[key]; } -function setAttribute(attr,val){ +function setAttribute(attr, val) { - return this.attributes[attr] = val; + return this.attributes[attr] = val; } -function getAttribute(attr){ +function getAttribute(attr) { - return this.attributes[attr]; + return this.attributes[attr]; } -function deleteAttribute(attr){ +function deleteAttribute(attr) { - return delete this.attributes[attr]; + return delete this.attributes[attr]; } -function getStyleInfo(){ - - var styleData = {}; - if(this.getAttribute('s')){ - //UNDO var wbStyleData = this.ws.wb.styleData; - var wbStyleData = this.ws.wb.styleData; - var xf = wbStyleData.cellXfs[this.getAttribute('s')]; - styleData.xf = xf; - styleData.applyAlignment=xf.applyAlignment; - styleData.applyBorder=xf.applyBorder; - styleData.applyNumberFormat=xf.applyNumberFormat; - styleData.applyFill=xf.applyFill; - styleData.applyFont=xf.applyFont; - if(xf.applyAlignment!=0){ - styleData.alignment=xf.alignment; - } - if(xf.applyBorder != 0){ - styleData.border=wbStyleData.borders[xf.borderId]; - } - if(xf.applyNumberFormat != 0){ - styleData.numFmt=wbStyleData.numFmts[xf.numFmtId - 165]; - } - if(xf.applyFill != 0){ - styleData.fill=wbStyleData.fills[xf.fillId]; - } - if(xf.applyFont != 0){ - styleData.font=wbStyleData.fonts[xf.fontId]; - } - } - return styleData; +function getStyleInfo() { + + var styleData = {}; + if (this.getAttribute('s')) { + //UNDO var wbStyleData = this.ws.wb.styleData; + var wbStyleData = this.ws.wb.styleData; + var xf = wbStyleData.cellXfs[this.getAttribute('s')]; + styleData.xf = xf; + styleData.applyAlignment = xf.applyAlignment; + styleData.applyBorder = xf.applyBorder; + styleData.applyNumberFormat = xf.applyNumberFormat; + styleData.applyFill = xf.applyFill; + styleData.applyFont = xf.applyFont; + if (xf.applyAlignment !== 0) { + styleData.alignment = xf.alignment; + } + if (xf.applyBorder !== 0) { + styleData.border = wbStyleData.borders[xf.borderId]; + } + if (xf.applyNumberFormat !== 0) { + styleData.numFmt = wbStyleData.numFmts[xf.numFmtId - 165]; + } + if (xf.applyFill !== 0) { + styleData.fill = wbStyleData.fills[xf.fillId]; + } + if (xf.applyFont !== 0) { + styleData.font = wbStyleData.fonts[xf.fontId]; + } + } + return styleData; } -function getCellRange(){ +function getCellRange() { - //Since all formatting is done on cell ranges, convert cell to range of single cell - var rc = this.getAttribute('r').toExcelRowCol(); - //UNDO return this.ws.Cell(rc.row,rc.col); - return this.ws.Cell(rc.row,rc.col); + //Since all formatting is done on cell ranges, convert cell to range of single cell + var rc = this.getAttribute('r').toExcelRowCol(); + //UNDO return this.ws.Cell(rc.row,rc.col); + return this.ws.Cell(rc.row, rc.col); } -function string(strIndex){ +function string(strIndex) { - this.setAttribute('t','s'); - this.deleteChild('f'); - this.addChild('v',strIndex); - this.valueType = 'string'; + this.setAttribute('t', 's'); + this.deleteChild('f'); + this.addChild('v', strIndex); + this.valueType = 'string'; } -function number(val){ +function number(val) { - this.deleteChild('f'); - this.addChild('v',val); - this.valueType = 'number' + this.deleteChild('f'); + this.addChild('v', val); + this.valueType = 'number'; } -function bool(val){ +function bool(val) { - this.setAttribute('t','b'); - this.deleteChild('f'); - this.addChild('v',val); - this.valueType = 'bool'; + this.setAttribute('t', 'b'); + this.deleteChild('f'); + this.addChild('v', val); + this.valueType = 'bool'; } -function date(val){ +function date(val) { - this.deleteChild('f'); - val = new Date(val); - var ts = val.getExcelTS(); - this.addChild('v',ts); - this.valueType = 'date'; + this.deleteChild('f'); + val = new Date(val); + var ts = val.getExcelTS(); + this.addChild('v', ts); + this.valueType = 'date'; } -function formula(val){ +function formula(val) { - this.deleteChild('v'); - this.addChild('f',val); - this.valueType = 'formula'; + this.deleteChild('v'); + this.addChild('f', val); + this.valueType = 'formula'; } -function styler(style){ +function styler(style) { - this.setAttribute('s',style); + this.setAttribute('s', style); } -function getAllCellsInNumericRange(row1, col1, row2, col2){ - - var response = []; - row2=row2?row2:row1; - col2=col2?col2:col1; - for(var i=row1; i<=row2; i++){ - for(var j=col1; j<=col2; j++){ - response.push(j.toExcelAlpha() + i); - } - } - return response.sort(excelRefSort); +function getAllCellsInNumericRange(row1, col1, row2, col2) { + + var response = []; + row2 = row2 ? row2 : row1; + col2 = col2 ? col2 : col1; + for (var i = row1; i <= row2; i++) { + for (var j = col1; j <= col2; j++) { + response.push(j.toExcelAlpha() + i); + } + } + return response.sort(excelRefSort); } -function getAllCellsInExcelRange(range){ +function getAllCellsInExcelRange(range) { - var cells = range.split(':'); - var cell1props = cells[0].toExcelRowCol(); - var cell2props = cells[1].toExcelRowCol(); - return getAllCellsInNumericRange(cell1props.row,cell1props.col,cell2props.row,cell2props.col); + var cells = range.split(':'); + var cell1props = cells[0].toExcelRowCol(); + var cell2props = cells[1].toExcelRowCol(); + return getAllCellsInNumericRange(cell1props.row, cell1props.col, cell2props.row, cell2props.col); } -function arrayIntersectSafe(a, b){ - - var ai=0, bi=0; - var result = new Array(); - - while( ai < a.length && bi < b.length ) - { - if (a[ai] < b[bi] ){ ai++; } - else if (a[ai] > b[bi] ){ bi++; } - else /* they're equal */ - { - result.push(a[ai]); - ai++; - bi++; - } - } - return result; +function arrayIntersectSafe(a, b) { + + var ai = 0, bi = 0; + var result = new Array(); + + while (ai < a.length && bi < b.length) { + if (a[ai] < b[bi]) { + ai++; + } else if (a[ai] > b[bi]) { + bi++; + } else { + result.push(a[ai]); + ai++; + bi++; + } + } + return result; } -function excelRefSort(a, b){ - if(a.replace(/[0-9]/g,'') === b.replace(/[0-9]/g,'')){ - return a.replace(/[a-zA-Z]/g,'') - b.replace(/[a-zA-Z]/g,''); - } - - return compareCharCodes(a, b); +function excelRefSort(a, b) { + if (a.replace(/[0-9]/g, '') === b.replace(/[0-9]/g, '')) { + return a.replace(/[a-zA-Z]/g, '') - b.replace(/[a-zA-Z]/g, ''); + } + return compareCharCodes(a, b); } -function excelCellsSort(a, b){ - var ar = a.attributes.r; - var br = b.attributes.r; - if(ar.replace(/[0-9]/g,'') === br.replace(/[0-9]/g,'')){ - return ar.replace(/[a-zA-Z]/g,'') - br.replace(/[a-zA-Z]/g,''); - } - - return compareCharCodes(ar, br); +function excelCellsSort(a, b) { + var ar = a.attributes.r; + var br = b.attributes.r; + if (ar.replace(/[0-9]/g, '') === br.replace(/[0-9]/g, '')) { + return ar.replace(/[a-zA-Z]/g, '') - br.replace(/[a-zA-Z]/g, ''); + } + return compareCharCodes(ar, br); } -function compareCharCodes(a, b){ - var alphaOne = a.replace(/[0-9]/g,'').toUpperCase(); - var alphaTwo = b.replace(/[0-9]/g,'').toUpperCase(); - var numOne = ''; - var numTwo = ''; - - for( i = 0; i < alphaOne.length; i++){ - numOne += alphaOne.charCodeAt(i); - } - for( i = 0; i < alphaTwo.length; i++){ - numTwo += alphaTwo.charCodeAt(i); - } - - return Number(numOne) - Number(numTwo); +function compareCharCodes(a, b) { + var alphaOne = a.replace(/[0-9]/g, '').toUpperCase(); + var alphaTwo = b.replace(/[0-9]/g, '').toUpperCase(); + var numOne = ''; + var numTwo = ''; + + for (var i = 0; i < alphaOne.length; i++) { + numOne += alphaOne.charCodeAt(i); + } + for (var i = 0; i < alphaTwo.length; i++) { + numTwo += alphaTwo.charCodeAt(i); + } + return Number(numOne) - Number(numTwo); } -function getValue(){ - var obj = {}; - if(this.getChild('f')){ - obj.value = this.getChild('f'); - } - if(this.getChild('v')){ - if(this.getAttribute('t') == 's'){ - obj.value = this.ws.wb.getStringFromIndex(this.getChild('v')); - }else{ - obj.value = this.getChild('v'); - } - } - obj.ref = this.getAttribute('r'); - obj.type = this.valueType; - - return obj; +function getValue() { + var obj = {}; + if (this.getChild('f')) { + obj.value = this.getChild('f'); + } + if (this.getChild('v')) { + if (this.getAttribute('t') === 's') { + obj.value = this.ws.wb.getStringFromIndex(this.getChild('v')); + } else { + obj.value = this.getChild('v'); + } + } + obj.ref = this.getAttribute('r'); + obj.type = this.valueType; + + return obj; } -function generateRId(){ - var text = "R"; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for( var i=0; i < 16; i++ ) +function generateRId() { + var text = 'R'; + var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (var i = 0; i < 16; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); - + } return text; } diff --git a/lib/Column.js b/lib/Column.js index c46d06a..5a4e70e 100644 --- a/lib/Column.js +++ b/lib/Column.js @@ -1,100 +1,86 @@ -var Column = function(colNum){ - var thisWS = this; - if(!thisWS.cols){ - thisWS.cols={}; - } - if(!thisWS.cols[colNum]){ - var newCol = new column(); - newCol.setAttribute('max',colNum); - newCol.setAttribute('min',colNum); - newCol.setAttribute('customWidth',0); - newCol.setAttribute('width',thisWS.wb.defaults.colWidth); - thisWS.cols[colNum] = newCol; - } - - var thisCol = thisWS.cols[colNum]; - thisCol.ws = thisWS; - - return thisCol; -} - - -var column = function(){ - return this; +module.exports = columnAccessor; + +// ----------------------------------------------------------------------------- + +function columnAccessor(colNum) { + var thisWS = this; + if (!thisWS.cols) { + thisWS.cols = {}; + } + if (!thisWS.cols[colNum]) { + var newCol = new Column(); + newCol.setAttribute('max', colNum); + newCol.setAttribute('min', colNum); + newCol.setAttribute('customWidth', 0); + newCol.setAttribute('width', thisWS.wb.defaults.colWidth); + thisWS.cols[colNum] = newCol; + } + var thisCol = thisWS.cols[colNum]; + thisCol.ws = thisWS; + return thisCol; } -/****************************** - Column Methods - ******************************/ -column.prototype.setAttribute = setAttribute; -column.prototype.Hide = hideColumn; -column.prototype.Group = setGroup; -column.prototype.Width = setWidth; -column.prototype.Freeze = freezeColumn; - -/****************************** - Column Method Definitions - ******************************/ -function setWidth(w){ - this.setAttribute('width',w); - this.setAttribute('customWidth',1); +// ----------------------------------------------------------------------------- - return this; +function Column() { + return this; } -function hideColumn(){ - - this.setAttribute('hidden',1); - - return this; -} - -function setAttribute(attr,val){ - - this[attr] = val; - -} - -function setGroup(level,isHidden){ - this.ws.hasGroupings = true; - var hidden=isHidden?1:0; - this.setAttribute('outlineLevel',level); - this.setAttribute('hidden',hidden); - return this; -} - -function freezeColumn(scrollTo){ - var sTo = scrollTo?scrollTo:this.min; - var sv = this.ws.sheet.sheetViews[0].sheetView; - var pane; - var foundPane = false; - sv.e4nForEach(function(v,i){ - if(Object.keys(v).indexOf('pane') >= 0){ - pane = sv[i].pane; - foundPane = true; - } - }); - if(!foundPane){ - var l = sv.push({ - pane:{ - '@activePane':'topRight', - '@state':'frozen', - '@topLeftCell':sTo.toExcelAlpha() + '1', - '@xSplit':this.min-1 - } - }); - pane = sv[l-1].pane; - }else{ - var curTopLeft = pane['@topLeftCell']; - var points = curTopLeft.toExcelRowCol(); - pane['@activePane']='bottomRight'; - pane['@topLeftCell']=sTo.toExcelAlpha() + points.row; - pane['@xSplit']=this.min-1; - } - - return this; -} - - -exports.Column = Column; - +// Methods --------------------------------------------------------------------- + +Column.prototype.setAttribute = function (attr, val) { + this[attr] = val; +}; + +Column.prototype.Hide = function () { + this.setAttribute('hidden', 1); + return this; +}; + +Column.prototype.Group = function (level, isHidden) { + this.ws.hasGroupings = true; + var hidden = isHidden ? 1 : 0; + this.setAttribute('outlineLevel', level); + this.setAttribute('hidden', hidden); + return this; +}; + +Column.prototype.Width = function (w) { + this.setAttribute('width', w); + this.setAttribute('customWidth', 1); + return this; +}; + +Column.prototype.Freeze = function (scrollTo) { + var sTo = scrollTo ? scrollTo : this.min; + var sv = this.ws.sheet.sheetViews[0].sheetView; + var pane; + var foundPane = false; + + sv.e4nForEach(function (v, i) { + if (Object.keys(v).indexOf('pane') >= 0) { + pane = sv[i].pane; + foundPane = true; + } + }); + + if (!foundPane) { + var l = sv.push({ + pane: { + '@activePane': 'topRight', + '@state': 'frozen', + '@topLeftCell': sTo.toExcelAlpha() + '1', + '@xSplit': this.min - 1 + } + }); + pane = sv[l - 1].pane; + } else { + var curTopLeft = pane['@topLeftCell']; + var points = curTopLeft.toExcelRowCol(); + pane['@activePane'] = 'bottomRight'; + pane['@topLeftCell'] = sTo.toExcelAlpha() + points.row; + pane['@xSplit'] = this.min - 1; + } + + return this; +}; diff --git a/lib/Images.js b/lib/Images.js deleted file mode 100644 index 0843cda..0000000 --- a/lib/Images.js +++ /dev/null @@ -1,257 +0,0 @@ -var fs = require('fs'), -mime = require('mime'), -imgsz = require('image-size'); - -var drawing = function(imgURI){ - var d = { - props:{ - image:imgURI, - imageId:0, - mimeType:'', - extension:'', - width:0, - height:0, - dpi:96 - }, - xml:{ - 'xdr:oneCellAnchor':[ - { - 'xdr:from':{ - 'xdr:col':0, - 'xdr:colOff':0, - 'xdr:row':0, - 'xdr:rowOff':0 - } - }, - { - 'xdr:ext':{ - '@cx':0*9525, - '@cy':0*9525 - } - }, - { - 'xdr:pic':{ - 'xdr:nvPicPr':[ - { - 'xdr:cNvPr': { - '@descr':'image', - '@id':0, - '@name':'Picture' - } - }, - "xdr:cNvPicPr" - ], - 'xdr:blipFill':{ - 'a:blip':{ - '@cstate':'print', - '@r:embed':'rId0', - '@xmlns:r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships' - } - }, - 'xdr:spPr':[ - { - '@bwMode':'auto' - }, - { - 'a:xfrm':{ - 'a:off':{ - '@x':0, - '@y':0 - }, - 'a:ext':{ - '@cx':0*9525, - '@cy':0*9525 - } - } - }, - { - 'a:prstGeom':[ - { - '@prst':'rect' - }, - 'a:avLst' - ] - }, - "a:noFill", - { - 'a:ln':['a:noFill'] - } - ] - } - }, - "xdr:clientData" - ] - }, - Position:function(r, c, offY, offX){ - var offsetX = offX?offX:0; - var offsetY = offY?offY:0; - - d.xml['xdr:oneCellAnchor'].e4nForEach(function(v){ - if(v['xdr:from']){ - v['xdr:from']['xdr:col']=c-1; - v['xdr:from']['xdr:row']=r-1; - v['xdr:from']['xdr:colOff']=offsetX; - v['xdr:from']['xdr:rowOff']=offsetY; - } - else if(v['xdr:pic'] && offX && offY){ - var spPR = v['xdr:pic']['xdr:spPr']; - spPR.e4nForEach(function(o){ - if(o['a:xfrm']){ - o['a:xfrm']['a:off']['@x']=offsetX; - o['a:xfrm']['a:off']['@y']=offsetY; - } - }); - } - }); - }, - Properties:function(props){ - Object.keys(props).e4nForEach(function(k){ - d.props[k] = props[k]; - }); - }, - SetID:function(id){ - d.xml['xdr:oneCellAnchor'].e4nForEach(function(v){ - if(v['xdr:pic']){ - v['xdr:pic']['xdr:nvPicPr'][0]['xdr:cNvPr']['@id'] = id; - v['xdr:pic']['xdr:blipFill']['a:blip']['@r:embed'] = 'rId'+id; - } - }); - d.props.imageId=id; - }, - updateSize:function(){ - d.xml['xdr:oneCellAnchor'].e4nForEach(function(v){ - if(v['xdr:ext']){ - v['xdr:ext']['@cx']=d.props.width*9525*(96/d.props.dpi); - v['xdr:ext']['@cy']=d.props.height*9525*(96/d.props.dpi); - } - else if(v['xdr:pic']){ - var spPR = v['xdr:pic']['xdr:spPr']; - spPR.e4nForEach(function(o){ - if(o['a:xfrm']){ - o['a:xfrm']['a:ext']['@cx']=d.props.width*9525*(96/d.props.dpi); - o['a:xfrm']['a:ext']['@cy']=d.props.height*9525*(96/d.props.dpi); - } - }); - } - }); - } - - } - - return d; -} - -exports.Image = function(imgURI){ - - var wb=this.wb.workbook; - var wSs=this.wb.worksheets; - var ws=this; - - // add entry to [Content_Types].xml - var mimeType = mime.lookup(imgURI); - var extension = mimeType.split('/')[1]; - - var contentTypeAdded = false; - wb.Content_Types.Types.e4nForEach(function(t){ - if(t['Default']){ - if(t['Default']['@ContentType'] == mimeType){ - contentTypeAdded = true; - } - } - }) - if(!contentTypeAdded){ - wb.Content_Types.Types.push({ - "Default":{ - "@ContentType":mimeType, - "@Extension":extension - } - }); - } - - // create drawingn.xml file - // create drawingn.xml.rels file - if(!ws.drawings){ - ws.drawings = { - 'rels':{ - 'Relationships':[ - { - '@xmlns':'http://schemas.openxmlformats.org/package/2006/relationships' - } - ] - }, - 'xml':{ - 'xdr:wsDr':[ - { - '@xmlns:a':'http://schemas.openxmlformats.org/drawingml/2006/main', - '@xmlns:xdr':'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing' - } - ] - }, - drawings:[] - }; - } - if(!ws.rels){ - ws.rels = { - 'Relationships':[ - { - '@xmlns':'http://schemas.openxmlformats.org/package/2006/relationships' - } - ] - } - } - - var d = new drawing(imgURI); - - - d.Properties({ - 'mimeType':mimeType, - 'extension':extension - }); - - var dim = imgsz(imgURI); - d.Properties({ - 'width':dim.width, - 'height':dim.height - }); - d.updateSize(); - - ws.drawings.drawings.push(d); - var imgID = 0; - wSs.e4nForEach(function(s){ - if(s.drawings){ - imgID+=s.drawings.drawings.length; - } - }); - d.SetID(imgID); - - ws.drawings.rels.Relationships.push({ - 'Relationship':{ - '@Id':'rId'+imgID, - '@Target':'../media/image'+imgID+'.'+extension, - '@Type':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image' - } - }); - - var relExists = false; - ws.rels['Relationships'].e4nForEach(function(r){ - if(r['Relationship']){ - if(r['Relationship']['@Id'] == 'rId1'){ - relExists = true; - } - } - }); - if(!relExists){ - ws.rels['Relationships'].push({ - 'Relationship':{ - '@Id':'rId1', - '@Target':'../drawings/drawing'+ws.sheetId+'.xml', - '@Type':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing' - } - }); - } - ws.sheet.drawing = { - '@r:id':'rId1' - } - - return d; -} \ No newline at end of file diff --git a/lib/Row.js b/lib/Row.js index 0c38fdd..8b22627 100644 --- a/lib/Row.js +++ b/lib/Row.js @@ -1,176 +1,174 @@ -exports.Row = function(rowNum){ - var thisWS = this; - if(!thisWS.rows[rowNum]){ - thisWS.rows[rowNum] = new row(); - } - var thisRow = thisWS.rows[rowNum]; - thisRow.ws = thisWS; - thisRow.setAttribute('r',rowNum); - thisRow.setAttribute('spans','1:'+thisRow.cellCount()); - - //console.log(thisRow); - return thisRow; +module.exports = function rowAccessor(rowNum) { + var thisWS = this; + if (!thisWS.rows[rowNum]) { + thisWS.rows[rowNum] = new Row(); + } + var thisRow = thisWS.rows[rowNum]; + thisRow.ws = thisWS; + thisRow.setAttribute('r', rowNum); + thisRow.setAttribute('spans', '1:' + thisRow.cellCount()); + + return thisRow; +}; + +// ----------------------------------------------------------------------------- + +function Row() { + var thisRow = this; + thisRow.cells = {}; + thisRow.attributes = {}; + return thisRow; } -var row = function(){ - var thisRow = this; - thisRow.cells = {}; - thisRow.attributes = {}; - return thisRow; -} +// Row Methods ----------------------------------------------------------------- -/****************************** - Row Methods - ******************************/ -row.prototype.Height = height; -row.prototype.setAttribute = setAttribute; -row.prototype.cellCount = countCells; -row.prototype.Freeze = freeze; -row.prototype.Group = group; -row.prototype.Filter = filter; -row.prototype.Hide = hideRow; - -/****************************** - Row Method Definitions - ******************************/ -function countCells(){ - var cellCount = parseInt(Object.keys(this.cells).sort(alphaNumSort)[Object.keys(this.cells).length - 1]); - if(isNaN(cellCount)){ - cellCount = 1; - } - return cellCount; -} +Row.prototype.Height = height; +Row.prototype.setAttribute = setAttribute; +Row.prototype.cellCount = countCells; +Row.prototype.Freeze = freeze; +Row.prototype.Group = group; +Row.prototype.Filter = filter; +Row.prototype.Hide = hideRow; -function setAttribute(attr,val){ +// Row Method Definitions ------------------------------------------------------ - this.attributes[attr] = val; +function countCells() { + var cellCount = parseInt(Object.keys(this.cells).sort(alphaNumSort)[Object.keys(this.cells).length - 1]); + if (isNaN(cellCount)) { + cellCount = 1; + } + return cellCount; } -function freeze(scrollTo){ - var rID = this.attributes.r; - var sTo = scrollTo?scrollTo:rID; - var sv = this.ws.sheet.sheetViews[0].sheetView; - var pane; - var foundPane = false; - sv.e4nForEach(function(v,i){ - if(Object.keys(v).indexOf('pane') >= 0){ - pane = sv[i].pane; - foundPane = true; - } - }); - if(!foundPane){ - var l = sv.push({ - pane:{ - '@activePane':'bottomLeft', - '@state':'frozen', - '@topLeftCell':'A'+sTo, - '@ySplit':rID-1 - } - }); - pane = sv[l-1].pane; - }else{ - var curTopLeft = pane['@topLeftCell']; - var points = curTopLeft.toExcelRowCol(); - pane['@activePane']='bottomRight'; - pane['@topLeftCell']=points.col.toExcelAlpha() + sTo; - pane['@ySplit']=rID-1; - } +function setAttribute(attr, val) { + + this.attributes[attr] = val; } -function group(level,isHidden){ - this.ws.hasGroupings = true; - var hidden=isHidden?1:0; - this.setAttribute('outlineLevel',level); - this.setAttribute('hidden',hidden); - return this; +function freeze(scrollTo) { + var rID = this.attributes.r; + var sTo = scrollTo ? scrollTo : rID; + var sv = this.ws.sheet.sheetViews[0].sheetView; + var pane; + var foundPane = false; + sv.e4nForEach(function (v, i) { + if (Object.keys(v).indexOf('pane') >= 0) { + pane = sv[i].pane; + foundPane = true; + } + }); + if (!foundPane) { + var l = sv.push({ + pane: { + '@activePane': 'bottomLeft', + '@state': 'frozen', + '@topLeftCell': 'A' + sTo, + '@ySplit': rID - 1 + } + }); + pane = sv[l - 1].pane; + }else { + var curTopLeft = pane['@topLeftCell']; + var points = curTopLeft.toExcelRowCol(); + pane['@activePane'] = 'bottomRight'; + pane['@topLeftCell'] = points.col.toExcelAlpha() + sTo; + pane['@ySplit'] = rID - 1; + } } -function filter(startCol,endCol,filters){ - var rID = this.attributes.r; - filters = filters instanceof Array ? filters : []; - - if(typeof(startCol) != 'number' || typeof(endCol) != 'number'){ - startCol = 1; - endCol = this.cellCount(); - } - if(startCol instanceof Array){ //no start and end col specified - filters = startCol; - } - - startCol=startCol?startCol:1; - endCol=endCol?endCol:this.cellCount()+startCol; - - var thisWS = this.ws.sheet; - thisWS['autoFilter']=[{ - '@ref':startCol.toExcelAlpha()+rID+':'+endCol.toExcelAlpha()+rID - }]; - - /* Filter Object Definition - { - column:Int, - matchAll:Optional Boolean - rules:[ - { - val:String, - operator:Optional String, - } - ] - } - */ - filters.e4nForEach(function(f){ - if(typeof(startCol) == 'number' && f.rules instanceof Array){ - var thisFilter = { - 'filterColumn' : [ - { - '@colId':f.column - 1 - }, - { - 'customFilters':[] - } - ] - } - if(f.matchAll == true){ - thisFilter.filterColumn[1].customFilters.push({'@and':'1'}); - } - f.rules.e4nForEach(function(r){ - var thisRule = { - 'customFilter':{ - '@val':r.val - } - }; - if(r.operator){ - thisRule['customFilter']['@operator'] = r.operator; - } - - thisFilter.filterColumn[1].customFilters.push(thisRule); - }); - thisWS['autoFilter'].push(thisFilter); - } - }); +function group(level, isHidden) { + this.ws.hasGroupings = true; + var hidden = isHidden ? 1 : 0; + this.setAttribute('outlineLevel', level); + this.setAttribute('hidden', hidden); + return this; } -function height(height){ - this.setAttribute('customHeight',1); - this.setAttribute('ht',height); +function filter(startCol, endCol, filters) { + var rID = this.attributes.r; + filters = filters instanceof Array ? filters : []; + + if (typeof(startCol) !== 'number' || typeof(endCol) !== 'number') { + startCol = 1; + endCol = this.cellCount(); + } + if (startCol instanceof Array) { //no start and end col specified + filters = startCol; + } + + startCol = startCol ? startCol : 1; + endCol = endCol ? endCol : this.cellCount() + startCol; + + var thisWS = this.ws.sheet; + thisWS['autoFilter'] = [{ + '@ref': startCol.toExcelAlpha() + rID + ':' + endCol.toExcelAlpha() + rID + }]; + + /* Filter Object Definition + { + column:Int, + matchAll:Optional Boolean + rules:[ + { + val:String, + operator:Optional String, + } + ] + } + */ + filters.e4nForEach(function (f) { + if (typeof(startCol) === 'number' && f.rules instanceof Array) { + var thisFilter = { + 'filterColumn': [ + { + '@colId': f.column - 1 + }, + { + 'customFilters': [] + } + ] + }; + if (f.matchAll === true) { + thisFilter.filterColumn[1].customFilters.push({ '@and': '1' }); + } + f.rules.e4nForEach(function (r) { + var thisRule = { + 'customFilter': { + '@val': r.val + } + }; + if (r.operator) { + thisRule['customFilter']['@operator'] = r.operator; + } + + thisFilter.filterColumn[1].customFilters.push(thisRule); + }); + thisWS['autoFilter'].push(thisFilter); + } + }); } -function hideRow(){ +function height(height) { + this.setAttribute('customHeight', 1); + this.setAttribute('ht', height); +} - this.setAttribute('hidden',1); +function hideRow() { + this.setAttribute('hidden', 1); } -function alphaNumSort(a,b){ - if(parseInt(a) == a && parseInt(b) == b){ - var numA = parseInt(a); - var numB = parseInt(b); - return a - b; - }else{ - if(a < b){ - return -1; - }else if (b < a){ - return 1; - }else{ - return 0; - } - } -} \ No newline at end of file +function alphaNumSort(a, b) { + if (parseInt(a) === a && parseInt(b) === b) { + var numA = parseInt(a); + var numB = parseInt(b); + return a - b; + } else { + if (a < b) { + return -1; + } else if (b < a) { + return 1; + } else { + return 0; + } + } +} diff --git a/lib/Style.js b/lib/Style.js index b9957e2..d834c98 100644 --- a/lib/Style.js +++ b/lib/Style.js @@ -1,698 +1,700 @@ var _ = require('underscore'); -var style = function(wb,opts){ - var curStyle = {}; - - curStyle.wb = wb; - curStyle.xf = new exports.cellXfs(wb,opts); - - curStyle.Font = fontFunctions(this); - curStyle.Border = setBorder; - curStyle.Number = numberFunctions(this); - curStyle.Fill = fillFunctions(this); - curStyle.Clone = cloneStyle; - - function numberFunctions(){ - var methods = {}; - methods.Format = setFormat; - - function setFormat(fmt){ - if(!wb.styleData.numFmts[curStyle.xf.numFmtId - 164]){ - var curFmt = new exports.numFmt(curStyle.wb); - }else{ - var curFmt = JSON.parse(JSON.stringify(wb.styleData.numFmts[curStyle.xf.numFmtId - 164])); - } - curFmt.formatCode = fmt; - var thisFmt = new exports.numFmt(curStyle.wb, curFmt); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyNumberFormat = 1; - curXF.numFmtId = thisFmt.numFmtId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - - return methods; - } - - function fillFunctions(){ - var methods = {}; - methods.Color = setFillColor; - methods.Pattern = setFillPattern; - - var curFill = JSON.parse(JSON.stringify(wb.styleData.fills[curStyle.xf.fillId])); - - function setFillColor(color){ - curFill.fgColor = exports.cleanColor(color); - var thisFill = new exports.fill(curStyle.wb,curFill); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyFill=1; - curXF.fillId=thisFill.fillId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setFillPattern(pattern){ - curFill.patternType = pattern; - var thisFill = new exports.fill(curStyle.wb,curFill); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyFill=1; - curXF.fillId=thisFill.fillId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - - return methods; - } - - function fontFunctions(){ - var methods = {}; - methods.Options = setFontOptions; - methods.Family = setFontFamily; - methods.Bold = setFontBold; - methods.Italics = setFontItalics; - methods.Underline = setFontUnderline; - methods.Size = setFontSize; - methods.Color = setFontColor; - methods.WrapText = setTextWrap; - methods.Alignment = { - Vertical:setFontAlignmentVertical, - Horizontal:setFontAlignmentHorizontal, - Rotation:setTextRotation - } - - var curFont = JSON.parse(JSON.stringify(wb.styleData.fonts[curStyle.xf.fontId])); - - function setFontOptions(opts){ - Object.keys(opts).e4nForEach(function(o){ - curFont[o]=opts[o]; - }); - var thisFont = new exports.font(curStyle.wb,curFont); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyFont=1; - curXF.fontId=thisFont.fontId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setFontFamily(val){ - curFont.name = val; - var thisFont = new exports.font(curStyle.wb,curFont); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyFont=1; - curXF.fontId=thisFont.fontId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setFontBold(){ - curFont.bold = true; - var thisFont = new exports.font(curStyle.wb,curFont); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyFont=1; - curXF.fontId=thisFont.fontId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setFontItalics(){ - curFont.italics = true; - var thisFont = new exports.font(curStyle.wb,curFont); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyFont=1; - curXF.fontId=thisFont.fontId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setFontUnderline(){ - curFont.underline = true; - var thisFont = new exports.font(curStyle.wb,curFont); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyFont=1; - curXF.fontId=thisFont.fontId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setFontSize(val){ - curFont.sz = val; - var thisFont = new exports.font(curStyle.wb,curFont); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyFont=1; - curXF.fontId=thisFont.fontId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setFontColor(val){ - curFont.color = exports.cleanColor(val); - var thisFont = new exports.font(curStyle.wb,curFont); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyFont=1; - curXF.fontId=thisFont.fontId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setTextWrap(){ - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - if(!curXF.alignment){ - curXF.alignment = {}; - } - curXF.applyAlignment=1; - curXF.alignment.wrapText=1; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setFontAlignmentVertical(val){ - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - if(!curXF.alignment){ - curXF.alignment = {}; - } - curXF.applyAlignment=1; - curXF.alignment.vertical=val; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setFontAlignmentHorizontal(val){ - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - if(!curXF.alignment){ - curXF.alignment = {}; - } - curXF.applyAlignment=1; - curXF.alignment.horizontal=val; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - function setTextRotation(val){ - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - if(!curXF.alignment){ - curXF.alignment = {}; - } - curXF.applyAlignment=1; - curXF.alignment.textRotation=val; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - - return methods; - } - - function setBorder(opts){ - /* - opts should be object in form - style is required, color is optional and defaults to black if not specified. - not all ordinals are required. No board will be on side that is not specified. - { - left:{ - style:'style', - color:'rgb' - }, - right:{ - style:'style', - color:'rgb' - }, - top:{ - style:'style', - color:'rgb' - }, - bottom:{ - style:'style', - color:'rgb' - }, - diagonal:{ - style:'style', - color:'rgb' - } - } - */ - - var curBorder = new exports.border(curStyle.wb,opts); - var curXF = JSON.parse(JSON.stringify(curStyle.xf)); - curXF.applyBorder=1; - curXF.borderId=curBorder.borderId; - curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); - } - - function cloneStyle(){ - var oldStyle = this; - var newStyle = this.wb.Style(); - - var doNotCloneKeys = ['xfId','generateXMLObj']; - Object.keys(newStyle.xf).e4nForEach(function(k){ - if(doNotCloneKeys.indexOf(k)<0){ - newStyle.xf[k]=oldStyle.xf[k]; - } - }); - return newStyle; - } - - return curStyle; -} - -exports.border = function(wb,opts){ - - var curBorder = this; - - opts=opts?opts:{}; - curBorder.left=opts.left?opts.left:null; - curBorder.right=opts.right?opts.right:null; - curBorder.top=opts.top?opts.top:null; - curBorder.bottom=opts.bottom?opts.bottom:null; - curBorder.diagonal=opts.diagonal?opts.diagonal:null; - curBorder.generateXMLObj=genXMLObj; - var validStyles = [ - "hair", - "dotted", - "dashDotDot", - "dashDot", - "dashed", - "thin", - "mediumDashDotDot", - "slantDashDot", - "mediumDashDot", - "mediumDashed", - "medium", - "thick", - "double" - ]; - var ordinals = ['left','right','top','bottom','diagonal']; - - ordinals.e4nForEach(function(o){ - if(this && this[o]){ - if(validStyles.indexOf(this[o].style) < 0 ){ - console.log('Invalid or missing option %s specified for border style. replacing with "thin"',this[o].style); - console.log('Valid Options: %s'+validStyles.join(',')); - } - } - }) - - if(wb.styleData.borders.length == 0){ - curBorder.borderId=0; - wb.styleData.borders.push(this); - }else{ - var isMatched = false; - var curborderId = 0; - var border2 = JSON.parse(JSON.stringify(this)); - - while( isMatched == false && curborderId < wb.styleData.borders.length ){ - var border1 = JSON.parse(JSON.stringify(wb.styleData.borders[curborderId])); - - border1.borderId=null; - border2.borderId=null; - isMatched = _.isEqual(border1,border2); - if(isMatched){ - curBorder.borderId = curborderId; - }else{ - curborderId+=1; - } - } - if(!isMatched){ - curBorder.borderId = wb.styleData.borders.length; - wb.styleData.borders.push(this); - } - } - - function genXMLObj(){ - - var data = { - border:[] - }; - ordinals.e4nForEach(function(o){ - if(curBorder[o]){ - var tmpObj = {}; - tmpObj[o] = [ - { - '@style':curBorder[o].style?curBorder[o].style:'thin' - }, - { - 'color':{ - '@rgb':curBorder[o].color?exports.cleanColor(curBorder[o].color):'FF000000' - } - } - ]; - data.border.push(tmpObj); - }else{ - data.border.push(o); - } - }); - return data; - } -} - -exports.cellXfs = function(wb,opts){ - opts=opts?opts:{}; - this.applyAlignment=opts.applyAlignment?opts.applyAlignment:0; - this.applyBorder=opts.applyBorder?opts.applyBorder:0; - this.applyNumberFormat=opts.applyNumberFormat?opts.applyNumberFormat:0; - this.applyFill=opts.applyFill?opts.applyFill:0; - this.applyFont=opts.applyFont?opts.applyFont:0; - this.borderId=opts.borderId?opts.borderId:0; - this.fillId=opts.fillId?opts.fillId:0; - this.fontId=opts.fontId?opts.fontId:0; - this.numFmtId=opts.numFmtId?opts.numFmtId:164; - if(opts.alignment){ - this.alignment=opts.alignment; - } - this.generateXMLObj=genXMLObj; - - if(wb.styleData.cellXfs.length == 0){ - this.xfId = 0; - wb.styleData.cellXfs.push(this); - }else{ - var isMatched = false; - var curXfId = 0; - var xf2 = JSON.parse(JSON.stringify(this)); - - while( isMatched == false && curXfId < wb.styleData.cellXfs.length ){ - var xf1 = JSON.parse(JSON.stringify(wb.styleData.cellXfs[curXfId])); - - xf1.xfId=null; - xf2.xfId=null; - isMatched = _.isEqual(xf1,xf2); - if(isMatched){ - this.xfId = curXfId; - }else{ - curXfId+=1; - } - } - if(!isMatched){ - this.xfId = wb.styleData.cellXfs.length; - wb.styleData.cellXfs.push(this); - } - } - - function genXMLObj(){ - var data = {xf:{ - '@applyAlignment':this.applyAlignment, - '@applyNumberFormat':this.applyNumberFormat, - '@applyFill':this.applyFill, - '@applyFont':this.applyFont, - '@applyBorder':this.applyBorder, - '@borderId':this.borderId, - '@fillId':this.fillId, - '@fontId':this.fontId, - '@numFmtId':this.numFmtId - }}; - if(this.alignment){ - data.xf.alignment = []; - if(this.alignment.vertical){ - data.xf.alignment.push({'@vertical':this.alignment.vertical}); - } - if(this.alignment.horizontal){ - data.xf.alignment.push({'@horizontal':this.alignment.horizontal}); - } - if(this.alignment.wrapText){ - data.xf.alignment.push({'@wrapText':this.alignment.wrapText}); - } - if(this.alignment.textRotation){ - data.xf.alignment.push({'@textRotation':this.alignment.textRotation}); - } - } - return data; - } - - return this; -} - -exports.font = function(wb,opts){ - opts=opts?opts:{}; - this.bold=opts.bold?opts.bold:false; - this.italics=opts.italics?opts.italics:false; - this.underline=opts.underline?opts.underline:false; - this.sz=opts.sz?opts.sz:12; - this.color=opts.color?exports.cleanColor(opts.color):'FF000000'; - this.name=opts.name?opts.name:'Calibri'; - if(opts.alignment){ - this.alignment={}; - if(opts.alignment.vertical){ - this.alignment.vertical = opts.alignment.vertical; - this.applyAlignment=1; - } - if(opts.alignment.horizontal){ - this.alignment.horizontal = opts.alignment.horizontal; - this.applyAlignment=1; - } - if(opts.alignment.wrapText){ - this.alignment.wrapText = opts.alignment.wrapText; - this.applyAlignment=1; - } - if(opts.alignment.textRotation){ - this.alignment.textRotation = opts.alignment.textRotation; - this.applyAlignment=1; - } - } - this.generateXMLObj=genXMLObj; - - if(wb.styleData.fonts.length == 0){ - this.fontId = 0; - wb.styleData.fonts.push(this); - }else{ - var isMatched = false; - var curFontId = 0; - var font2 = JSON.parse(JSON.stringify(this)); - - while( isMatched == false && curFontId < wb.styleData.fonts.length ){ - var font1 = JSON.parse(JSON.stringify(wb.styleData.fonts[curFontId])); - - font1.fontId=null; - font2.fontId=null; - isMatched = _.isEqual(font1,font2); - if(isMatched){ - this.fontId = curFontId; - }else{ - curFontId+=1; - } - } - if(!isMatched){ - this.fontId = wb.styleData.fonts.length; - wb.styleData.fonts.push(this); - } - } - - function genXMLObj(){ - var data = { - font:[ - { - sz:{ - '@val':this.sz - } - }, - { - color:{ - '@rgb':this.color - } - }, - { - name:{ - '@val':this.name - } - } - ] - }; - if(this.underline){ - data.font.splice(0,0,'u'); - }; - if(this.italics){ - data.font.splice(0,0,'i'); - }; - if(this.bold){ - data.font.splice(0,0,'b'); - }; - - if(this.alignment){ - var alignment = {}; - if(this.alignment.vertical){ - alignment['@vertical']=this.alignment.vertical; - } - if(this.alignment.horizontal){ - alignment['@horizontal']=this.alignment.horizontal; - } - if(this.alignment.wrapText){ - alignment['@wrapText']=this.alignment.wrapText; - } - if(this.alignment.textRotation){ - alignment['@textRotation']=this.alignment.textRotation; - } - data.font.push({alignment:alignment}); - } - return data; - } - - return this; -} - -exports.numFmt = function(wb, opts){ - var opts = opts?opts:{}; - this.formatCode = opts.formatCode?opts.formatCode:'$ #,##0.00;$ #,##0.00;-'; - this.generateXMLObj=genXMLObj; - - if(wb.styleData.numFmts.length == 0){ - this.numFmtId=165; - wb.styleData.numFmts.push(this); - }else{ - var isMatched = false; - var curNumFmtId = 165; - var fmt2 = JSON.parse(JSON.stringify(this)); - - while( isMatched == false && curNumFmtId < wb.styleData.numFmts.length+165 ){ - var fmt1 = JSON.parse(JSON.stringify(wb.styleData.numFmts[curNumFmtId-165])); - - fmt1.numFmtId=null; - fmt2.numFmtId=null; - isMatched = _.isEqual(fmt1,fmt2); - if(isMatched){ - this.numFmtId = curNumFmtId; - }else{ - curNumFmtId+=1; - } - } - if(!isMatched){ - this.numFmtId = wb.styleData.numFmts.length + 165; - wb.styleData.numFmts.push(this); - } - } - - function genXMLObj(){ - var data = { - numFmt:[ - {'@formatCode':this.formatCode}, - {'@numFmtId':this.numFmtId} - ] - }; - return data; - } - // -} - -exports.fill = function(wb,opts){ - opts=opts?opts:{}; - this.patternType=opts.patternType?opts.patternType:'solid'; - if(opts.fgColor){ - this.fgColor=exports.cleanColor(opts.fgColor); - } - if(opts.bgColor){ - this.bgColor=exports.cleanColor(opts.bgColor); - } - this.generateXMLObj=genXMLObj; - - if(wb.styleData.fills.length == 0){ - this.fillId=0; - wb.styleData.fills.push(this); - }else{ - var isMatched = false; - var curFillId = 0; - var fill2 = JSON.parse(JSON.stringify(this)); - - while( isMatched == false && curFillId < wb.styleData.fills.length ){ - var fill1 = JSON.parse(JSON.stringify(wb.styleData.fills[curFillId])); - - fill1.fillId=null; - fill2.fillId=null; - isMatched = _.isEqual(fill1,fill2); - if(isMatched){ - this.fillId = curFillId; - }else{ - curFillId+=1; - } - } - if(!isMatched){ - this.fillId = wb.styleData.fills.length; - wb.styleData.fills.push(this); - } - } - - function genXMLObj(){ - var data = {fill:{patternFill:[]}}; - data.fill.patternFill.push({'@patternType':this.patternType}); - if(this.fgColor){ - data.fill.patternFill.push({fgColor:{'@rgb':this.fgColor}}); - }; - if(this.bgColor){ - data.fill.patternFill.push({bgColor:{'@rgb':this.bgColor}}); - }; - return data; - } -} - -exports.cleanColor = function(val){ - // check for RGB, RGBA or Excel Color Names and return RGBA - var excelColors = { - "black": "FF000000", - "brown": "FF993300", - "olive green": "FF333300", - "dark green": "FF003300", - "dark teal": "FF003366", - "dark blue": "FF000080", - "indigo": "FF333399", - "gray-80": "FF333333", - "dark red": "FF800000", - "orange": "FFFF6600", - "dark yellow": "FF808000", - "green": "FF008000", - "teal": "FF008080", - "blue": "FF0000FF", - "blue-gray": "FF666699", - "gray-50": "FF808080", - "red": "FFFF0000", - "light orange": "FFFF9900", - "lime": "FF99CC00", - "sea green": "FF339966", - "aqua": "FF33CCCC", - "light blue": "FF3366FF", - "violet": "FF800080", - "gray-40": "FF969696", - "pink": "FFFF00FF", - "gold": "FFFFCC00", - "yellow": "FFFFFF00", - "bright green": "FF00FF00", - "turquoise": "FF00FFFF", - "sky blue": "FF00CCFF", - "plum": "FF993366", - "gray-25": "FFC0C0C0", - "rose": "FFFF99CC", - "tan": "FFFFCC99", - "light yellow": "FFFFFF99", - "light green": "FFCCFFCC", - "light turquoise": "FFCCFFFF", - "pale blue": "FF99CCFF", - "lavender": "FFCC99FF", - "white": "FFFFFFFF" - } - - if(Object.keys(excelColors).indexOf(val.toLowerCase()) >= 0){ - // val was a named color that matches predefined list. return corresponding color - return excelColors[val.toLowerCase()]; - }else if(val.length == 8 && val.substr(0,2) == 'FF' && /^[a-fA-F0-9()]+$/.test(val)){ - // val is already a properly formatted color string, return upper case version of itself - return val.toUpperCase(); - }else if(val.length == 6 && /^[a-fA-F0-9()]+$/.test(val)){ - // val is color code without Alpha, add it and return - return 'FF'+val.toUpperCase(); - }else if(val.length == 7 && val.substr(0,1) == '#' && /^[a-fA-F0-9()]+$/.test(val.substr(1))){ - // val was sent as html style hex code, remove # and add alpha - return 'FF'+val.substr(1).toUpperCase(); - }else if(val.length == 9 && val.substr(0,1) == '#' && /^[a-fA-F0-9()]+$/.test(val.substr(1))){ - // val sent as html style hex code with alpha. revese alpha position and return - return val.substr(7).toUpperCase()+val.substr(1,6).toUpperCase(); - }else{ - // I don't know what this is, return valid color and console.log error - console.log('%s is an invalid color option. changing to white',val); - console.log('valid color options are html style hex codes or these colors by name: %s',Object.keys(excelColors).join(', ')); - return 'FFFFFFFF'; - } -} - -exports.Style = function(opts){ - var opts=opts?opts:{}; - if(this.styleData.fonts.length == 0){ - var defaultFont = new exports.font(this); - } - if(this.styleData.fills.length == 0){ - var defaultFill = new exports.fill(this,{patternType:'none'}); - var defaultFill2 = new exports.fill(this,{patternType:'gray125'}); - } - if(this.styleData.borders.length == 0){ - var defaultBorder = new exports.border(this); - } - if(this.styleData.cellXfs.length == 0){ - var defaultXF = new exports.cellXfs(this); - } - - var newStyle = new style(this,opts); - this.styleData.cellXfs[newStyle.xf.xfId] = newStyle.xf; - - return newStyle; -} - -exports.getStyleById = function(wb,id) { - var xf = undefined; - wb.styleData.cellXfs.e4nForEach(function(s){ - if(s.xfId==id){ - xf = s; - } - }); - return xf; -} - -exports.getBorderById = function(wb, id){ - return wb.styleData.borders[id]; -} \ No newline at end of file +var style = function (wb, opts) { + var curStyle = {}; + + curStyle.wb = wb; + curStyle.xf = new exports.cellXfs(wb, opts); + + curStyle.Font = fontFunctions(this); + curStyle.Border = setBorder; + curStyle.Number = numberFunctions(this); + curStyle.Fill = fillFunctions(this); + curStyle.Clone = cloneStyle; + + function numberFunctions() { + var methods = {}; + methods.Format = setFormat; + + function setFormat(fmt) { + if (!wb.styleData.numFmts[curStyle.xf.numFmtId - 164]) { + var curFmt = new exports.numFmt(curStyle.wb); + } else { + var curFmt = JSON.parse(JSON.stringify(wb.styleData.numFmts[curStyle.xf.numFmtId - 164])); + } + curFmt.formatCode = fmt; + var thisFmt = new exports.numFmt(curStyle.wb, curFmt); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyNumberFormat = 1; + curXF.numFmtId = thisFmt.numFmtId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + + return methods; + } + + function fillFunctions() { + var methods = {}; + methods.Color = setFillColor; + methods.Pattern = setFillPattern; + + var curFill = JSON.parse(JSON.stringify(wb.styleData.fills[curStyle.xf.fillId])); + + function setFillColor(color) { + curFill.fgColor = exports.cleanColor(color); + var thisFill = new exports.fill(curStyle.wb, curFill); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyFill = 1; + curXF.fillId = thisFill.fillId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setFillPattern(pattern) { + curFill.patternType = pattern; + var thisFill = new exports.fill(curStyle.wb, curFill); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyFill = 1; + curXF.fillId = thisFill.fillId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + + return methods; + } + + function fontFunctions() { + var methods = {}; + methods.Options = setFontOptions; + methods.Family = setFontFamily; + methods.Bold = setFontBold; + methods.Italics = setFontItalics; + methods.Underline = setFontUnderline; + methods.Size = setFontSize; + methods.Color = setFontColor; + methods.WrapText = setTextWrap; + methods.Alignment = { + Vertical: setFontAlignmentVertical, + Horizontal: setFontAlignmentHorizontal, + Rotation: setTextRotation + }; + + var curFont = JSON.parse(JSON.stringify(wb.styleData.fonts[curStyle.xf.fontId])); + + function setFontOptions(opts) { + Object.keys(opts).e4nForEach(function (o) { + curFont[o] = opts[o]; + }); + var thisFont = new exports.font(curStyle.wb, curFont); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyFont = 1; + curXF.fontId = thisFont.fontId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setFontFamily(val) { + curFont.name = val; + var thisFont = new exports.font(curStyle.wb, curFont); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyFont = 1; + curXF.fontId = thisFont.fontId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setFontBold() { + curFont.bold = true; + var thisFont = new exports.font(curStyle.wb, curFont); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyFont = 1; + curXF.fontId = thisFont.fontId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setFontItalics() { + curFont.italics = true; + var thisFont = new exports.font(curStyle.wb, curFont); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyFont = 1; + curXF.fontId = thisFont.fontId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setFontUnderline() { + curFont.underline = true; + var thisFont = new exports.font(curStyle.wb, curFont); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyFont = 1; + curXF.fontId = thisFont.fontId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setFontSize(val) { + curFont.sz = val; + var thisFont = new exports.font(curStyle.wb, curFont); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyFont = 1; + curXF.fontId = thisFont.fontId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setFontColor(val) { + curFont.color = exports.cleanColor(val); + var thisFont = new exports.font(curStyle.wb, curFont); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyFont = 1; + curXF.fontId = thisFont.fontId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setTextWrap() { + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + if (!curXF.alignment) { + curXF.alignment = {}; + } + curXF.applyAlignment = 1; + curXF.alignment.wrapText = 1; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setFontAlignmentVertical(val) { + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + if (!curXF.alignment) { + curXF.alignment = {}; + } + curXF.applyAlignment = 1; + curXF.alignment.vertical = val; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setFontAlignmentHorizontal(val) { + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + if (!curXF.alignment) { + curXF.alignment = {}; + } + curXF.applyAlignment = 1; + curXF.alignment.horizontal = val; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + function setTextRotation(val) { + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + if (!curXF.alignment) { + curXF.alignment = {}; + } + curXF.applyAlignment = 1; + curXF.alignment.textRotation = val; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + + return methods; + } + + function setBorder(opts) { + /* + opts should be object in form + style is required, color is optional and defaults to black if not specified. + not all ordinals are required. No board will be on side that is not specified. + { + left:{ + style:'style', + color:'rgb' + }, + right:{ + style:'style', + color:'rgb' + }, + top:{ + style:'style', + color:'rgb' + }, + bottom:{ + style:'style', + color:'rgb' + }, + diagonal:{ + style:'style', + color:'rgb' + } + } + */ + + var curBorder = new exports.border(curStyle.wb, opts); + var curXF = JSON.parse(JSON.stringify(curStyle.xf)); + curXF.applyBorder = 1; + curXF.borderId = curBorder.borderId; + curStyle.xf = new exports.cellXfs(curStyle.wb, curXF); + } + + function cloneStyle() { + var oldStyle = this; + var newStyle = this.wb.Style(); + + var doNotCloneKeys = ['xfId', 'generateXMLObj']; + Object.keys(newStyle.xf).e4nForEach(function (k) { + if (doNotCloneKeys.indexOf(k) < 0) { + newStyle.xf[k] = oldStyle.xf[k]; + } + }); + return newStyle; + } + + return curStyle; +}; + +exports.border = function (wb, opts) { + + var curBorder = this; + + opts = opts ? opts : {}; + curBorder.left = opts.left ? opts.left : null; + curBorder.right = opts.right ? opts.right : null; + curBorder.top = opts.top ? opts.top : null; + curBorder.bottom = opts.bottom ? opts.bottom : null; + curBorder.diagonal = opts.diagonal ? opts.diagonal : null; + curBorder.generateXMLObj = genXMLObj; + var validStyles = [ + 'hair', + 'dotted', + 'dashDotDot', + 'dashDot', + 'dashed', + 'thin', + 'mediumDashDotDot', + 'slantDashDot', + 'mediumDashDot', + 'mediumDashed', + 'medium', + 'thick', + 'double' + ]; + var ordinals = ['left', 'right', 'top', 'bottom', 'diagonal']; + + ordinals.e4nForEach(function (o) { + if (this && this[o]) { + if (validStyles.indexOf(this[o].style) < 0) { + console.log('Invalid or missing option %s specified for border style. replacing with "thin"', this[o].style); + console.log('Valid Options: %s' + validStyles.join(',')); + } + } + }); + + if (wb.styleData.borders.length === 0) { + curBorder.borderId = 0; + wb.styleData.borders.push(this); + } else { + var isMatched = false; + var curborderId = 0; + var border2 = JSON.parse(JSON.stringify(this)); + + while (isMatched === false && curborderId < wb.styleData.borders.length) { + var border1 = JSON.parse(JSON.stringify(wb.styleData.borders[curborderId])); + + border1.borderId = null; + border2.borderId = null; + isMatched = _.isEqual(border1, border2); + if (isMatched) { + curBorder.borderId = curborderId; + } else { + curborderId += 1; + } + } + if (!isMatched) { + curBorder.borderId = wb.styleData.borders.length; + wb.styleData.borders.push(this); + } + } + + function genXMLObj() { + + var data = { + border: [] + }; + ordinals.e4nForEach(function (o) { + if (curBorder[o]) { + var tmpObj = {}; + tmpObj[o] = [ + { + '@style': curBorder[o].style ? curBorder[o].style : 'thin' + }, + { + 'color': { + '@rgb': curBorder[o].color ? exports.cleanColor(curBorder[o].color) : 'FF000000' + } + } + ]; + data.border.push(tmpObj); + } else { + data.border.push(o); + } + }); + return data; + } +}; + +exports.cellXfs = function (wb, opts) { + opts = opts ? opts : {}; + this.applyAlignment = opts.applyAlignment ? opts.applyAlignment : 0; + this.applyBorder = opts.applyBorder ? opts.applyBorder : 0; + this.applyNumberFormat = opts.applyNumberFormat ? opts.applyNumberFormat : 0; + this.applyFill = opts.applyFill ? opts.applyFill : 0; + this.applyFont = opts.applyFont ? opts.applyFont : 0; + this.borderId = opts.borderId ? opts.borderId : 0; + this.fillId = opts.fillId ? opts.fillId : 0; + this.fontId = opts.fontId ? opts.fontId : 0; + this.numFmtId = opts.numFmtId ? opts.numFmtId : 164; + if (opts.alignment) { + this.alignment = opts.alignment; + } + this.generateXMLObj = genXMLObj; + + if (wb.styleData.cellXfs.length === 0) { + this.xfId = 0; + wb.styleData.cellXfs.push(this); + } else { + var isMatched = false; + var curXfId = 0; + var xf2 = JSON.parse(JSON.stringify(this)); + + while (isMatched === false && curXfId < wb.styleData.cellXfs.length) { + var xf1 = JSON.parse(JSON.stringify(wb.styleData.cellXfs[curXfId])); + + xf1.xfId = null; + xf2.xfId = null; + isMatched = _.isEqual(xf1, xf2); + if (isMatched) { + this.xfId = curXfId; + } else { + curXfId += 1; + } + } + if (!isMatched) { + this.xfId = wb.styleData.cellXfs.length; + wb.styleData.cellXfs.push(this); + } + } + + function genXMLObj() { + var data = { + xf: { + '@applyAlignment': this.applyAlignment, + '@applyNumberFormat': this.applyNumberFormat, + '@applyFill': this.applyFill, + '@applyFont': this.applyFont, + '@applyBorder': this.applyBorder, + '@borderId': this.borderId, + '@fillId': this.fillId, + '@fontId': this.fontId, + '@numFmtId': this.numFmtId + } + }; + if (this.alignment) { + data.xf.alignment = []; + if (this.alignment.vertical) { + data.xf.alignment.push({ '@vertical': this.alignment.vertical }); + } + if (this.alignment.horizontal) { + data.xf.alignment.push({ '@horizontal': this.alignment.horizontal }); + } + if (this.alignment.wrapText) { + data.xf.alignment.push({ '@wrapText': this.alignment.wrapText }); + } + if (this.alignment.textRotation) { + data.xf.alignment.push({ '@textRotation': this.alignment.textRotation }); + } + } + return data; + } + + return this; +}; + +exports.font = function (wb, opts) { + opts = opts ? opts : {}; + this.bold = opts.bold ? opts.bold : false; + this.italics = opts.italics ? opts.italics : false; + this.underline = opts.underline ? opts.underline : false; + this.sz = opts.sz ? opts.sz : 12; + this.color = opts.color ? exports.cleanColor(opts.color) : 'FF000000'; + this.name = opts.name ? opts.name : 'Calibri'; + if (opts.alignment) { + this.alignment = {}; + if (opts.alignment.vertical) { + this.alignment.vertical = opts.alignment.vertical; + this.applyAlignment = 1; + } + if (opts.alignment.horizontal) { + this.alignment.horizontal = opts.alignment.horizontal; + this.applyAlignment = 1; + } + if (opts.alignment.wrapText) { + this.alignment.wrapText = opts.alignment.wrapText; + this.applyAlignment = 1; + } + if (opts.alignment.textRotation) { + this.alignment.textRotation = opts.alignment.textRotation; + this.applyAlignment = 1; + } + } + this.generateXMLObj = genXMLObj; + + if (wb.styleData.fonts.length === 0) { + this.fontId = 0; + wb.styleData.fonts.push(this); + } else { + var isMatched = false; + var curFontId = 0; + var font2 = JSON.parse(JSON.stringify(this)); + + while (isMatched === false && curFontId < wb.styleData.fonts.length) { + var font1 = JSON.parse(JSON.stringify(wb.styleData.fonts[curFontId])); + + font1.fontId = null; + font2.fontId = null; + isMatched = _.isEqual(font1, font2); + if (isMatched) { + this.fontId = curFontId; + } else { + curFontId += 1; + } + } + if (!isMatched) { + this.fontId = wb.styleData.fonts.length; + wb.styleData.fonts.push(this); + } + } + + function genXMLObj() { + var data = { + font: [ + { + sz: { + '@val': this.sz + } + }, + { + color: { + '@rgb': this.color + } + }, + { + name: { + '@val': this.name + } + } + ] + }; + if (this.underline) { + data.font.splice(0, 0, 'u'); + } + if (this.italics) { + data.font.splice(0, 0, 'i'); + } + if (this.bold) { + data.font.splice(0, 0, 'b'); + } + + if (this.alignment) { + var alignment = {}; + if (this.alignment.vertical) { + alignment['@vertical'] = this.alignment.vertical; + } + if (this.alignment.horizontal) { + alignment['@horizontal'] = this.alignment.horizontal; + } + if (this.alignment.wrapText) { + alignment['@wrapText'] = this.alignment.wrapText; + } + if (this.alignment.textRotation) { + alignment['@textRotation'] = this.alignment.textRotation; + } + data.font.push({ alignment: alignment }); + } + return data; + } + + return this; +}; + +exports.numFmt = function (wb, opts) { + var opts = opts ? opts : {}; + this.formatCode = opts.formatCode ? opts.formatCode : '$ #,##0.00;$ #,##0.00;-'; + this.generateXMLObj = genXMLObj; + + if (wb.styleData.numFmts.length === 0) { + this.numFmtId = 165; + wb.styleData.numFmts.push(this); + } else { + var isMatched = false; + var curNumFmtId = 165; + var fmt2 = JSON.parse(JSON.stringify(this)); + + while (isMatched === false && curNumFmtId < wb.styleData.numFmts.length + 165) { + var fmt1 = JSON.parse(JSON.stringify(wb.styleData.numFmts[curNumFmtId - 165])); + + fmt1.numFmtId = null; + fmt2.numFmtId = null; + isMatched = _.isEqual(fmt1, fmt2); + if (isMatched) { + this.numFmtId = curNumFmtId; + } else { + curNumFmtId += 1; + } + } + if (!isMatched) { + this.numFmtId = wb.styleData.numFmts.length + 165; + wb.styleData.numFmts.push(this); + } + } + + function genXMLObj() { + var data = { + numFmt: [ + { '@formatCode': this.formatCode }, + { '@numFmtId': this.numFmtId } + ] + }; + return data; + } + // +}; + +exports.fill = function (wb, opts) { + opts = opts ? opts : {}; + this.patternType = opts.patternType ? opts.patternType : 'solid'; + if (opts.fgColor) { + this.fgColor = exports.cleanColor(opts.fgColor); + } + if (opts.bgColor) { + this.bgColor = exports.cleanColor(opts.bgColor); + } + this.generateXMLObj = genXMLObj; + + if (wb.styleData.fills.length === 0) { + this.fillId = 0; + wb.styleData.fills.push(this); + } else { + var isMatched = false; + var curFillId = 0; + var fill2 = JSON.parse(JSON.stringify(this)); + + while (isMatched === false && curFillId < wb.styleData.fills.length) { + var fill1 = JSON.parse(JSON.stringify(wb.styleData.fills[curFillId])); + + fill1.fillId = null; + fill2.fillId = null; + isMatched = _.isEqual(fill1, fill2); + if (isMatched) { + this.fillId = curFillId; + } else { + curFillId += 1; + } + } + if (!isMatched) { + this.fillId = wb.styleData.fills.length; + wb.styleData.fills.push(this); + } + } + + function genXMLObj() { + var data = { fill: { patternFill: [] } }; + data.fill.patternFill.push({ '@patternType': this.patternType }); + if (this.fgColor) { + data.fill.patternFill.push({ fgColor: { '@rgb': this.fgColor } }); + } + if (this.bgColor) { + data.fill.patternFill.push({ bgColor: { '@rgb': this.bgColor } }); + } + return data; + } +}; + +exports.cleanColor = function (val) { + // check for RGB, RGBA or Excel Color Names and return RGBA + var excelColors = { + 'black': 'FF000000', + 'brown': 'FF993300', + 'olive green': 'FF333300', + 'dark green': 'FF003300', + 'dark teal': 'FF003366', + 'dark blue': 'FF000080', + 'indigo': 'FF333399', + 'gray-80': 'FF333333', + 'dark red': 'FF800000', + 'orange': 'FFFF6600', + 'dark yellow': 'FF808000', + 'green': 'FF008000', + 'teal': 'FF008080', + 'blue': 'FF0000FF', + 'blue-gray': 'FF666699', + 'gray-50': 'FF808080', + 'red': 'FFFF0000', + 'light orange': 'FFFF9900', + 'lime': 'FF99CC00', + 'sea green': 'FF339966', + 'aqua': 'FF33CCCC', + 'light blue': 'FF3366FF', + 'violet': 'FF800080', + 'gray-40': 'FF969696', + 'pink': 'FFFF00FF', + 'gold': 'FFFFCC00', + 'yellow': 'FFFFFF00', + 'bright green': 'FF00FF00', + 'turquoise': 'FF00FFFF', + 'sky blue': 'FF00CCFF', + 'plum': 'FF993366', + 'gray-25': 'FFC0C0C0', + 'rose': 'FFFF99CC', + 'tan': 'FFFFCC99', + 'light yellow': 'FFFFFF99', + 'light green': 'FFCCFFCC', + 'light turquoise': 'FFCCFFFF', + 'pale blue': 'FF99CCFF', + 'lavender': 'FFCC99FF', + 'white': 'FFFFFFFF' + }; + + if (Object.keys(excelColors).indexOf(val.toLowerCase()) >= 0) { + // val was a named color that matches predefined list. return corresponding color + return excelColors[val.toLowerCase()]; + } else if (val.length === 8 && val.substr(0, 2) === 'FF' && /^[a-fA-F0-9()]+$/.test(val)) { + // val is already a properly formatted color string, return upper case version of itself + return val.toUpperCase(); + } else if (val.length === 6 && /^[a-fA-F0-9()]+$/.test(val)) { + // val is color code without Alpha, add it and return + return 'FF' + val.toUpperCase(); + } else if (val.length === 7 && val.substr(0, 1) === '#' && /^[a-fA-F0-9()]+$/.test(val.substr(1))) { + // val was sent as html style hex code, remove # and add alpha + return 'FF' + val.substr(1).toUpperCase(); + } else if (val.length === 9 && val.substr(0, 1) === '#' && /^[a-fA-F0-9()]+$/.test(val.substr(1))) { + // val sent as html style hex code with alpha. revese alpha position and return + return val.substr(7).toUpperCase() + val.substr(1, 6).toUpperCase(); + } else { + // I don't know what this is, return valid color and console.log error + console.log('%s is an invalid color option. changing to white', val); + console.log('valid color options are html style hex codes or these colors by name: %s', Object.keys(excelColors).join(', ')); + return 'FFFFFFFF'; + } +}; + +exports.Style = function (opts) { + var opts = opts ? opts : {}; + if (this.styleData.fonts.length === 0) { + var defaultFont = new exports.font(this); + } + if (this.styleData.fills.length === 0) { + var defaultFill = new exports.fill(this, { patternType: 'none' }); + var defaultFill2 = new exports.fill(this, { patternType: 'gray125' }); + } + if (this.styleData.borders.length === 0) { + var defaultBorder = new exports.border(this); + } + if (this.styleData.cellXfs.length === 0) { + var defaultXF = new exports.cellXfs(this); + } + + var newStyle = new style(this, opts); + this.styleData.cellXfs[newStyle.xf.xfId] = newStyle.xf; + + return newStyle; +}; + +exports.getStyleById = function (wb, id) { + var xf = undefined; + wb.styleData.cellXfs.e4nForEach(function (s) { + if (s.xfId === id) { + xf = s; + } + }); + return xf; +}; + +exports.getBorderById = function (wb, id) { + return wb.styleData.borders[id]; +}; diff --git a/lib/Utils.js b/lib/Utils.js index 2999bac..1eab72d 100644 --- a/lib/Utils.js +++ b/lib/Utils.js @@ -1,34 +1,33 @@ module.exports = { - generateRId : generateRId, - getHashOfPassword : getHashOfPassword, - aSyncForEach : aSyncForEach -} - -function generateRId(){ - var text = "R"; - var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - for( var i=0; i < 16; i++ ) + generateRId: generateRId, + getHashOfPassword: getHashOfPassword, + aSyncForEach: aSyncForEach +}; + +function generateRId() { + var text = 'R'; + var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (var i = 0; i < 16; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); - + } return text; } // http://www.openoffice.org/sc/excelfileformat.pdf section 4.18.4 -function getHashOfPassword(str){ - var curHash = '0000'; - for (var i = str.length - 1; i >= 0; i--){ - curHash = _getHashForChar(str[i], curHash); - } - var curHashBin = parseInt(curHash,16).toString(2); - var charCountBin = parseInt(str.length, 10).toString(2); - var saltBin = parseInt('CE4B',16).toString(2); - - var firstXOR = _bitXOR(curHashBin,charCountBin); - var finalHashBin = _bitXOR(firstXOR,saltBin); - var finalHash = String('0000' + parseInt(finalHashBin,2).toString(16).toUpperCase()).slice(-4); - - return finalHash; +function getHashOfPassword(str) { + var curHash = '0000'; + for (var i = str.length - 1; i >= 0; i--) { + curHash = _getHashForChar(str[i], curHash); + } + var curHashBin = parseInt(curHash, 16).toString(2); + var charCountBin = parseInt(str.length, 10).toString(2); + var saltBin = parseInt('CE4B', 16).toString(2); + + var firstXOR = _bitXOR(curHashBin, charCountBin); + var finalHashBin = _bitXOR(firstXOR, saltBin); + var finalHash = String('0000' + parseInt(finalHashBin, 2).toString(16).toUpperCase()).slice(-4); + + return finalHash; } @@ -36,44 +35,44 @@ function getHashOfPassword(str){ * Helper Functions */ -function _rotateBinary(bin){ - return bin.substr(1,bin.length - 1) + bin.substr(0,1); +function _rotateBinary(bin) { + return bin.substr(1, bin.length - 1) + bin.substr(0, 1); } -function _getHashForChar(char, hash){ - hash = hash ? hash : '0000'; - var charCode = char.charCodeAt(0); - var hashBin = parseInt(hash,16).toString(2); - var charBin = parseInt(charCode, 10).toString(2); - hashBin = String('000000000000000' + hashBin).substr(-15); - charBin = String('000000000000000' + charBin).substr(-15); - var nextHash = _bitXOR(hashBin, charBin); - nextHash = _rotateBinary(nextHash); - nextHash = parseInt(nextHash,2).toString(16); - - return nextHash; +function _getHashForChar(char, hash) { + hash = hash ? hash : '0000'; + var charCode = char.charCodeAt(0); + var hashBin = parseInt(hash, 16).toString(2); + var charBin = parseInt(charCode, 10).toString(2); + hashBin = String('000000000000000' + hashBin).substr(-15); + charBin = String('000000000000000' + charBin).substr(-15); + var nextHash = _bitXOR(hashBin, charBin); + nextHash = _rotateBinary(nextHash); + nextHash = parseInt(nextHash, 2).toString(16); + + return nextHash; } -function _bitXOR(a,b){ - var maxLength = a.length > b.length ? a.length : b.length; +function _bitXOR(a, b) { + var maxLength = a.length > b.length ? a.length : b.length; - var padString = ''; - for( var i = 0 ; i < maxLength; i++){ - padString += '0'; - } + var padString = ''; + for (var i = 0; i < maxLength; i++) { + padString += '0'; + } - a = String(padString + a).substr(-maxLength); - b = String(padString + b).substr(-maxLength); + a = String(padString + a).substr(-maxLength); + b = String(padString + b).substr(-maxLength); - var response = ''; - for(var i = 0; i < a.length; i++){ - response += a[i] == b[i] ? 0 : 1; - } - return response; + var response = ''; + for(var i = 0; i < a.length; i++) { + response += a[i] === b[i] ? 0 : 1; + } + return response; } -function aSyncForEach(fn){ - this.forEach(function(v,i){ - setTimeout(fn(v,i),0); - }); -} \ No newline at end of file +function aSyncForEach(fn) { + this.forEach(function (v, i) { + setTimeout(fn(v, i), 0); + }); +} diff --git a/lib/WorkBook.js b/lib/WorkBook.js index 39f5978..eeb7808 100644 --- a/lib/WorkBook.js +++ b/lib/WorkBook.js @@ -1,473 +1,487 @@ -var WorkSheet = require('./WorkSheet.js'), - style = require('./Style.js'), - utils = require('./Utils.js'), - xml = require('xmlbuilder'), - jszip = require('jszip'), - xml = require('xmlbuilder'), - fs = require('fs'); +var WorkSheet = require('./worksheet'); +var style = require('./style'); +var utils = require('./utils'); +var fs = require('fs'); +var jszip = require('jszip'); +var xml = require('xmlbuilder'); var xmlOutVars = {}; -var xmlDebugVars = { pretty: true, indent: ' ',newline: '\n' }; - -var WorkBook = function(opts){ - var thisWB = this; - var opts = opts?opts:{}; - - - if(opts.allowInterrupt){ - Array.prototype.e4nForEach = utils.aSyncForEach; - }else{ - Array.prototype.e4nForEach = Array.prototype.forEach; - } - - thisWB.opts = { - jszip : { - compression : 'DEFLATE' - } - }; - if(opts.jszip){ - Object.keys(opts.jszip).e4nForEach(function(k){ - thisWB.opts.jszip[k] = opts.jszip[k]; - }); - }; - - thisWB.defaults={ - colWidth:opts.colWidth?opts.colWidth:15 - }; - thisWB.styleData={ - numFmts:[], - fonts:[], - fills:[], - borders:[], - cellXfs:[] - }; - thisWB.worksheets=[]; - thisWB.workbook = { - WorkSheets:[], - workbook:{ - '@xmlns:r':'http://schemas.openxmlformats.org/officeDocument/2006/relationships', - '@xmlns':'http://schemas.openxmlformats.org/spreadsheetml/2006/main', - fileSharing : {}, - bookViews:[ - { - workbookView:{ - '@tabRatio':'600', - '@windowHeight':'14980', - '@windowWidth':'25600', - '@xWindow':'0', - '@yWindow':'1080' - } - } - ], - sheets:[] - }, - strings : { - sst:[ - { - '@count':0, - '@uniqueCount':0, - '@xmlns':'http://schemas.openxmlformats.org/spreadsheetml/2006/main' - } - ] - }, - workbook_xml_rels:{ - Relationships:[ - { - '@xmlns':'http://schemas.openxmlformats.org/package/2006/relationships' - }, - { - Relationship:{ - '@Id':utils.generateRId(), - '@Target':'sharedStrings.xml', - '@Type':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings' - } - }, - { - Relationship:{ - '@Id':utils.generateRId(), - '@Target':'styles.xml', - '@Type':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles' - } - } - ] - }, - global_rels:{ - Relationships:[ - { - '@xmlns':'http://schemas.openxmlformats.org/package/2006/relationships' - }, - { - Relationship:{ - '@Id':utils.generateRId(), - '@Target':'xl/workbook.xml', - '@Type':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument' - } - } - ] - }, - Content_Types:{ - Types:[ - { - '@xmlns':'http://schemas.openxmlformats.org/package/2006/content-types' - }, - { - Default:{ - '@ContentType':'application/xml', - '@Extension':'xml' - } - }, - { - Default:{ - '@ContentType':'application/vnd.openxmlformats-package.relationships+xml', - '@Extension':'rels' - } - }, - { - Override:{ - '@ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml', - '@PartName':'/xl/workbook.xml' - } - }, - { - Override:{ - '@ContentType':'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml', - '@PartName':'/xl/styles.xml' - } - }, - { - Override:{ - '@ContentType':'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml', - '@PartName':'/xl/sharedStrings.xml' - } - } - ] - }, - sharedStrings:[], - debug:false - } - - - if(opts.fileSharing){ - thisWB.workbook.workbook.fileSharing = { - '@reservationPassword' : utils.getHashOfPassword(opts.fileSharing.password), - '@userName' : (opts.fileSharing.userName ? opts.fileSharing.userName : 'excel4node') - } - }else{ - delete thisWB.workbook.workbook.fileSharing; - } - - // Generate default style - thisWB.Style(); - - return this +var xmlDebugVars = { pretty: true, indent: ' ', newline: '\n' }; + +module.exports = WorkBook; + +// ----------------------------------------------------------------------------- + +function WorkBook(opts) { + var thisWB = this; + var opts = opts ? opts : {}; + + if (opts.allowInterrupt) { + Array.prototype.e4nForEach = utils.aSyncForEach; + } else { + Array.prototype.e4nForEach = Array.prototype.forEach; + } + + thisWB.opts = { + jszip: { + compression: 'DEFLATE' + } + }; + + if (opts.jszip) { + Object.keys(opts.jszip).e4nForEach(function (k) { + thisWB.opts.jszip[k] = opts.jszip[k]; + }); + } + + thisWB.defaults = { + colWidth: opts.colWidth ? opts.colWidth : 15 + }; + + thisWB.styleData = { + numFmts: [], + fonts: [], + fills: [], + borders: [], + cellXfs: [] + }; + + thisWB.worksheets = []; + + thisWB.workbook = { + WorkSheets: [], + workbook: { + '@xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships', + '@xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + fileSharing: {}, + bookViews: [ + { + workbookView: { + '@tabRatio': '600', + '@windowHeight': '14980', + '@windowWidth': '25600', + '@xWindow': '0', + '@yWindow': '1080' + } + } + ], + sheets: [] + }, + strings: { + sst: [ + { + '@count': 0, + '@uniqueCount': 0, + '@xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main' + } + ] + }, + workbook_xml_rels: { + Relationships: [ + { + '@xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships' + }, + { + Relationship: { + '@Id': utils.generateRId(), + '@Target': 'sharedStrings.xml', + '@Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings' + } + }, + { + Relationship: { + '@Id': utils.generateRId(), + '@Target': 'styles.xml', + '@Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles' + } + } + ] + }, + global_rels: { + Relationships: [ + { + '@xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships' + }, + { + Relationship: { + '@Id': utils.generateRId(), + '@Target': 'xl/workbook.xml', + '@Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument' + } + } + ] + }, + Content_Types: { + Types: [ + { + '@xmlns': 'http://schemas.openxmlformats.org/package/2006/content-types' + }, + { + Default: { + '@ContentType': 'application/xml', + '@Extension': 'xml' + } + }, + { + Default: { + '@ContentType': 'application/vnd.openxmlformats-package.relationships+xml', + '@Extension': 'rels' + } + }, + { + Override: { + '@ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml', + '@PartName': '/xl/workbook.xml' + } + }, + { + Override: { + '@ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml', + '@PartName': '/xl/styles.xml' + } + }, + { + Override: { + '@ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml', + '@PartName': '/xl/sharedStrings.xml' + } + } + ] + }, + sharedStrings: [], + debug: false + }; + + if (opts.fileSharing) { + thisWB.workbook.workbook.fileSharing = { + '@reservationPassword': utils.getHashOfPassword(opts.fileSharing.password), + '@userName': (opts.fileSharing.userName ? opts.fileSharing.userName : 'excel4node') + }; + } else { + delete thisWB.workbook.workbook.fileSharing; + } + + // Generate default style + thisWB.Style(); + + return this; } -WorkBook.prototype.WorkSheet = function(name, opts){ - var thisWS = new WorkSheet(this); - thisWS.setName(name); - thisWS.setWSOpts(opts); - thisWS.sheetId = this.worksheets.length + 1; - this.worksheets.push(thisWS); - return thisWS; -} - -WorkBook.prototype.getStringIndex = function(val){ - - if(this.workbook.sharedStrings.indexOf(val) < 0){ - this.workbook.sharedStrings.push(val) - }; - return this.workbook.sharedStrings.indexOf(val); -} - -WorkBook.prototype.getStringFromIndex = function(val){ - - return this.workbook.sharedStrings[val]; -} - -WorkBook.prototype.write = function(fileName, response){ - - var buffer = this.writeToBuffer(); - - // If `response` is an object (a node response object) - if(typeof response === "object"){ - response.writeHead(200,{ - 'Content-Length':buffer.length, - 'Content-Type':'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'Content-Disposition':'attachment; filename="'+fileName+'"', - }); - response.end(buffer); - } - - // Else if `response` is a function, use it as a callback - else if(typeof response === "function"){ - fs.writeFile(fileName, buffer, function(err) { - response(err); - }); - } - - // Else response wasn't specified - else { - fs.writeFile(fileName, buffer, function(err) { - if (err) throw err; - }); - } -} - -WorkBook.prototype.createStyleSheetXML = function(){ - var thisWB = this; - var data={ - styleSheet:{ - '@xmlns':'http://schemas.openxmlformats.org/spreadsheetml/2006/main', - '@mc:Ignorable':'x14ac', - '@xmlns:mc':'http://schemas.openxmlformats.org/markup-compatibility/2006', - '@xmlns:x14ac':'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac', - numFmts:[], - fonts:[], - fills:[], - borders:[], - cellXfs:[] - } - } - - - var items = [ - 'numFmts', - 'fonts', - 'fills', - 'borders', - 'cellXfs' - ]; - - items.e4nForEach(function(i){ - data.styleSheet[i].push({'@count':thisWB.styleData[i].length}); - thisWB.styleData[i].e4nForEach(function(d){ - data.styleSheet[i].push(d.generateXMLObj()); - }); - }); - - var styleXML = xml.create(data); - return styleXML.end(xmlOutVars); -} - -WorkBook.prototype.writeToBuffer = function(){ - var xlsx = new jszip(); - var that = this; - var wbOut = JSON.parse(JSON.stringify(this.workbook)); - - this.worksheets.e4nForEach(function(sheet, i){ - var sheetCount = i+1; - var thisRId = utils.generateRId(); - var sheetExists = false; - - wbOut.workbook.sheets.e4nForEach(function(s){ - if(s.sheet['@sheetId'] == sheetCount){ - sheetExists = true; - } - }); - if(!sheetExists){ - wbOut.workbook_xml_rels.Relationships.push({ - Relationship:{ - '@Id':thisRId, - '@Target':'worksheets/sheet'+sheetCount+'.xml', - '@Type':'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet' - } - }) - wbOut.workbook.sheets.push({ - sheet:{ - '@name':sheet.name, - '@sheetId':sheetCount, - '@r:id':thisRId - } - }); - wbOut.Content_Types.Types.push({ - Override:{ - '@ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml', - '@PartName':'/xl/worksheets/sheet'+sheetCount+'.xml' - } - }); - if(that.debug){ - console.log("\n\r###### Sheet XML XML #####\n\r"); - //console.log(xmlStr.end(xmlDebugVars)) - }; - } - - if(sheet.drawings){ - if(that.debug){ - console.log("\n\r######## Drawings found ########\n\r") - } - var drawingRelsXML = xml.create(sheet.drawings.rels); - if(that.debug){ - console.log("\n\r###### Drawings Rels XML #####\n\r"); - console.log(drawingRelsXML.end(xmlDebugVars)) - }; - xlsx.folder("xl").folder("drawings").folder("_rels").file("drawing"+sheet.sheetId+".xml.rels",drawingRelsXML.end(xmlOutVars)); - - sheet.drawings.drawings.e4nForEach(function(d){ - sheet.drawings.xml['xdr:wsDr'].push(d.xml); - xlsx.folder("xl").folder("media").file('image'+d.props.imageId+'.'+d.props.extension,fs.readFileSync(d.props.image)); - if(that.debug){ - console.log("\n\r###### Drawing image data #####\n\r"); - console.log(fs.statSync(d.props.image)) - }; - }); - - var drawingXML = xml.create(sheet.drawings.xml); - xlsx.folder("xl").folder("drawings").file("drawing"+sheet.sheetId+".xml",drawingXML.end(xmlOutVars)); - if(that.debug){ - console.log("\n\r###### Drawings XML #####\n\r"); - console.log(drawingXML.end(xmlDebugVars)) - }; - - wbOut.Content_Types.Types.push({ - Override:{ - '@ContentType': 'application/vnd.openxmlformats-officedocument.drawing+xml', - '@PartName':'/xl/drawings/drawing'+sheet.sheetId+'.xml' - } - }); - } - - if(sheet.hyperlinks) { - wbOut.Content_Types.Types.push({ - Override:{ - '@ContentType': 'application/vnd.openxmlformats-package.relationships+xml', - '@PartName':'/xl/worksheets/_rels/sheet'+sheet.sheetId+'.xml.rels' - } - }); - if(!sheet.rels){ - sheet.rels = { - 'Relationships':[ - { - '@xmlns':'http://schemas.openxmlformats.org/package/2006/relationships' - } - ] - } - } - sheet.hyperlinks.e4nForEach(function(cr, i){ - var found = false; - sheet.rels.Relationships.e4nForEach(function(r){ - if(r.Relationship && r.Relationship['@Id'] == 'rId' + cr.id){ - found = true; - } - }); - - if(!found){ - sheet.rels.Relationships.push({ - 'Relationship':{ - '@Id': 'rId' + cr.id, - '@Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', - '@Target': cr.url, - '@TargetMode': 'External' - } - }); - } - }); - } - - if(sheet.rels){ - var sheetRelsXML = xml.create(sheet.rels); - if(that.debug){console.log(sheetRelsXML.end(xmlDebugVars))}; - xlsx.folder("xl").folder("worksheets").folder("_rels").file("sheet"+sheet.sheetId+".xml.rels",sheetRelsXML.end(xmlOutVars)); - } - - - /* - if autoFilter was generated, update ref to include all rows - */ - if(sheet.sheet.autoFilter){ - - var thisSheet = sheet.sheet; - var thisWS = sheet; - var curRef = thisSheet['autoFilter'][0]['@ref']; - var startCell = curRef.split(':')[0].toExcelRowCol(); - var endCell = curRef.split(':')[1].toExcelRowCol(); - var rowCount = Object.keys(thisWS.rows).length; - var startColAlpha = startCell.col.toExcelAlpha(); - var endColAlpha = endCell.col.toExcelAlpha(); - var newRef = startColAlpha + startCell.row + ':' + endColAlpha + rowCount; - - thisSheet['autoFilter'][0]['@ref'] = newRef; - - if(!wbOut.workbook['definedNames']){ - wbOut.workbook['definedNames'] = []; - } - var thisRef = sheet.sheet.autoFilter[0]['@ref']; - - wbOut.workbook['definedNames'].push({ - definedName : { - '@hidden' : 1, - '@localSheetId' : i, - '@name' : '_xlnm._FilterDatabase', - '#text': '\'' + sheet.name + '\'!$'+startColAlpha+'$'+startCell.row+':$'+endColAlpha+'$'+endCell.row - } - }); - } - - var xmlStr = sheet.toXML(); - xlsx.folder("xl").folder("worksheets").file('sheet'+sheetCount+'.xml',xmlStr); - - if(that.debug){ - console.log("\n\r###### SHEET "+sheetCount+" XML #####\n\r"); - console.log(xmlStr) - }; - }); - - wbOut.sharedStrings.e4nForEach(function(s){ - wbOut.strings.sst.push({'si':{'t':s}}); - }); - - wbOut.strings.sst[0]['@uniqueCount']=wbOut.sharedStrings.length; - - var wbXML = xml.create({workbook:JSON.parse(JSON.stringify(wbOut.workbook))}); - if(this.debug){ - console.log("\n\r###### WorkBook XML #####\n\r"); - console.log(wbXML.end(xmlDebugVars)); - }; - - //var styleXML = xml.create(JSON.parse(JSON.stringify(wbOut.styles))); - var styleXMLStr = this.createStyleSheetXML(); - //console.log(styleXMLStr); - - if(this.debug){ - console.log("\n\r###### Style XML #####\n\r"); - console.log(styleXMLStr); - }; - - var relsXML = xml.create(wbOut.workbook_xml_rels); - if(this.debug){ - console.log("\n\r###### WorkBook Rels XML #####\n\r"); - console.log(relsXML.end(xmlDebugVars)) - }; - - var Content_TypesXML = xml.create(wbOut.Content_Types); - if(this.debug){ - console.log("\n\r###### Content Types XML #####\n\r"); - console.log(Content_TypesXML.end(xmlDebugVars)) - }; - - var globalRelsXML = xml.create(wbOut.global_rels); - if(this.debug){ - console.log("\n\r###### Globals Rels XML #####\n\r"); - console.log(globalRelsXML.end(xmlDebugVars)) - }; - - var stringsXML = xml.create(wbOut.strings); - if(this.debug){ - console.log("\n\r###### Shared Strings XML #####\n\r"); - console.log(stringsXML.end(xmlDebugVars)) - }; - - xlsx.file("[Content_Types].xml",Content_TypesXML.end(xmlOutVars)); - xlsx.folder("_rels").file(".rels",globalRelsXML.end(xmlOutVars)); - xlsx.folder("xl").file("sharedStrings.xml",stringsXML.end(xmlOutVars)); - xlsx.folder("xl").file("styles.xml",styleXMLStr); - xlsx.folder("xl").file("workbook.xml",wbXML.end(xmlOutVars)); - xlsx.folder("xl").folder("_rels").file("workbook.xml.rels",relsXML.end(xmlOutVars)); - - delete wbOut; - return xlsx.generate({type:"nodebuffer",compression:this.opts.jszip.compression}); -} +// ----------------------------------------------------------------------------- + +WorkBook.prototype.WorkSheet = function (name, opts) { + var thisWS = new WorkSheet(this); + thisWS.setName(name); + thisWS.setWSOpts(opts); + thisWS.sheetId = this.worksheets.length + 1; + this.worksheets.push(thisWS); + return thisWS; +}; + +WorkBook.prototype.getStringIndex = function (val) { + if (this.workbook.sharedStrings.indexOf(val) < 0) { + this.workbook.sharedStrings.push(val); + } + return this.workbook.sharedStrings.indexOf(val); +}; + +WorkBook.prototype.getStringFromIndex = function (val) { + return this.workbook.sharedStrings[val]; +}; + +WorkBook.prototype.write = function (fileName, response) { + var buffer = this.writeToBuffer(); + + // If `response` is an object (a node response object) + if (typeof response === 'object') { + response.writeHead(200, { + 'Content-Length': buffer.length, + 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'Content-Disposition': 'attachment; filename="' + fileName + '"' + }); + response.end(buffer); + // Else if `response` is a function, use it as a callback + } else if (typeof response === 'function') { + fs.writeFile(fileName, buffer, function (err) { + response(err); + }); + // Else response wasn't specified + } else { + fs.writeFile(fileName, buffer, function (err) { + if (err) throw err; + }); + } +}; + +WorkBook.prototype.createStyleSheetXML = function () { + var thisWB = this; + var data = { + styleSheet: { + '@xmlns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main', + '@mc:Ignorable': 'x14ac', + '@xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006', + '@xmlns:x14ac': 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac', + numFmts: [], + fonts: [], + fills: [], + borders: [], + cellXfs: [] + } + }; + + var items = [ + 'numFmts', + 'fonts', + 'fills', + 'borders', + 'cellXfs' + ]; + + items.e4nForEach(function (i) { + data.styleSheet[i].push({ '@count': thisWB.styleData[i].length }); + thisWB.styleData[i].e4nForEach(function (d) { + data.styleSheet[i].push(d.generateXMLObj()); + }); + }); + + var styleXML = xml.create(data); + return styleXML.end(xmlOutVars); +}; + +WorkBook.prototype.writeToBuffer = function () { + var xlsx = new jszip(); + var that = this; + var wbOut = JSON.parse(JSON.stringify(this.workbook)); + + this.worksheets.e4nForEach(function (sheet, i) { + var sheetCount = i + 1; + var thisRId = utils.generateRId(); + var sheetExists = false; + + wbOut.workbook.sheets.e4nForEach(function (s) { + if (s.sheet['@sheetId'] === sheetCount) { + sheetExists = true; + } + }); + + if (!sheetExists) { + wbOut.workbook_xml_rels.Relationships.push({ + Relationship: { + '@Id': thisRId, + '@Target': 'worksheets/sheet' + sheetCount + '.xml', + '@Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet' + } + }); + wbOut.workbook.sheets.push({ + sheet: { + '@name': sheet.name, + '@sheetId': sheetCount, + '@r:id': thisRId + } + }); + wbOut.Content_Types.Types.push({ + Override: { + '@ContentType': 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml', + '@PartName': '/xl/worksheets/sheet' + sheetCount + '.xml' + } + }); + if (that.debug) { + console.log('\n\r###### Sheet XML XML #####\n\r'); + //console.log(xmlStr.end(xmlDebugVars)) + } + } + + if (sheet.drawings) { + if (that.debug) { + console.log('\n\r######## Drawings found ########\n\r'); + } + var drawingRelsXML = xml.create(sheet.drawings.rels); + if (that.debug) { + console.log('\n\r###### Drawings Rels XML #####\n\r'); + console.log(drawingRelsXML.end(xmlDebugVars)); + } + + xlsx.folder('xl').folder('drawings').folder('_rels') + .file('drawing' + sheet.sheetId + '.xml.rels', drawingRelsXML.end(xmlOutVars)); + + sheet.drawings.drawings.e4nForEach(function (d) { + sheet.drawings.xml['xdr:wsDr'].push(d.xml); + xlsx.folder('xl').folder('media') + .file('image' + d.props.imageId + '.' + d.props.extension, fs.readFileSync(d.props.image)); + if (that.debug) { + console.log('\n\r###### Drawing image data #####\n\r'); + console.log(fs.statSync(d.props.image)); + } + }); + + var drawingXML = xml.create(sheet.drawings.xml); + xlsx.folder('xl').folder('drawings') + .file('drawing' + sheet.sheetId + '.xml', drawingXML.end(xmlOutVars)); + + if (that.debug) { + console.log('\n\r###### Drawings XML #####\n\r'); + console.log(drawingXML.end(xmlDebugVars)); + } + + wbOut.Content_Types.Types.push({ + Override: { + '@ContentType': 'application/vnd.openxmlformats-officedocument.drawing+xml', + '@PartName': '/xl/drawings/drawing' + sheet.sheetId + '.xml' + } + }); + } + + if(sheet.hyperlinks) { + wbOut.Content_Types.Types.push({ + Override: { + '@ContentType': 'application/vnd.openxmlformats-package.relationships+xml', + '@PartName': '/xl/worksheets/_rels/sheet' + sheet.sheetId + '.xml.rels' + } + }); + if (!sheet.rels) { + sheet.rels = { + 'Relationships': [ + { + '@xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships' + } + ] + }; + } + sheet.hyperlinks.e4nForEach(function (cr, i) { + var found = false; + sheet.rels.Relationships.e4nForEach(function (r) { + if (r.Relationship && r.Relationship['@Id'] === 'rId' + cr.id) { + found = true; + } + }); + + if (!found) { + sheet.rels.Relationships.push({ + 'Relationship': { + '@Id': 'rId' + cr.id, + '@Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', + '@Target': cr.url, + '@TargetMode': 'External' + } + }); + } + }); + } + + if (sheet.rels) { + var sheetRelsXML = xml.create(sheet.rels); + if (that.debug) { + console.log(sheetRelsXML.end(xmlDebugVars)); + } + xlsx.folder('xl').folder('worksheets').folder('_rels') + .file('sheet' + sheet.sheetId + '.xml.rels', sheetRelsXML.end(xmlOutVars)); + } + + + /* + if autoFilter was generated, update ref to include all rows + */ + if (sheet.sheet.autoFilter) { + + var thisSheet = sheet.sheet; + var thisWS = sheet; + var curRef = thisSheet['autoFilter'][0]['@ref']; + var startCell = curRef.split(':')[0].toExcelRowCol(); + var endCell = curRef.split(':')[1].toExcelRowCol(); + var rowCount = Object.keys(thisWS.rows).length; + var startColAlpha = startCell.col.toExcelAlpha(); + var endColAlpha = endCell.col.toExcelAlpha(); + var newRef = startColAlpha + startCell.row + ':' + endColAlpha + rowCount; + + thisSheet['autoFilter'][0]['@ref'] = newRef; + + if (!wbOut.workbook['definedNames']) { + wbOut.workbook['definedNames'] = []; + } + var thisRef = sheet.sheet.autoFilter[0]['@ref']; + + wbOut.workbook['definedNames'].push({ + definedName: { + '@hidden': 1, + '@localSheetId': i, + '@name': '_xlnm._FilterDatabase', + '#text': '\'' + sheet.name + '\'!$' + startColAlpha + '$' + startCell.row + ':$' + endColAlpha + '$' + endCell.row + } + }); + } + + var xmlStr = sheet.toXML(); + xlsx.folder('xl').folder('worksheets').file('sheet' + sheetCount + '.xml', xmlStr); + + if (that.debug) { + console.log('\n\r###### SHEET ' + sheetCount + ' XML #####\n\r'); + console.log(xmlStr); + } + }); + + wbOut.sharedStrings.e4nForEach(function (s) { + wbOut.strings.sst.push({ 'si': { 't': s } }); + }); + + wbOut.strings.sst[0]['@uniqueCount'] = wbOut.sharedStrings.length; + + var wbXML = xml.create({ + workbook: JSON.parse(JSON.stringify(wbOut.workbook)) + }); + + if (this.debug) { + console.log('\n\r###### WorkBook XML #####\n\r'); + console.log(wbXML.end(xmlDebugVars)); + } + + var styleXMLStr = this.createStyleSheetXML(); + if (this.debug) { + console.log('\n\r###### Style XML #####\n\r'); + console.log(styleXMLStr); + } + + var relsXML = xml.create(wbOut.workbook_xml_rels); + if (this.debug) { + console.log('\n\r###### WorkBook Rels XML #####\n\r'); + console.log(relsXML.end(xmlDebugVars)); + } + + var Content_TypesXML = xml.create(wbOut.Content_Types); + if (this.debug) { + console.log('\n\r###### Content Types XML #####\n\r'); + console.log(Content_TypesXML.end(xmlDebugVars)); + } + + var globalRelsXML = xml.create(wbOut.global_rels); + if (this.debug) { + console.log('\n\r###### Globals Rels XML #####\n\r'); + console.log(globalRelsXML.end(xmlDebugVars)); + } + + var stringsXML = xml.create(wbOut.strings); + if (this.debug) { + console.log('\n\r###### Shared Strings XML #####\n\r'); + console.log(stringsXML.end(xmlDebugVars)); + } + + xlsx.file('[Content_Types].xml', Content_TypesXML.end(xmlOutVars)); + xlsx.folder('_rels').file('.rels', globalRelsXML.end(xmlOutVars)); + xlsx.folder('xl').file('sharedStrings.xml', stringsXML.end(xmlOutVars)); + xlsx.folder('xl').file('styles.xml', styleXMLStr); + xlsx.folder('xl').file('workbook.xml', wbXML.end(xmlOutVars)); + xlsx.folder('xl').folder('_rels').file('workbook.xml.rels', relsXML.end(xmlOutVars)); + + // TODO variables should not be deleted + delete wbOut; + + return xlsx.generate({ + type: 'nodebuffer', + compression: this.opts.jszip.compression + }); +}; + +// ----------------------------------------------------------------------------- WorkBook.prototype.Style = style.Style; -exports.WorkBook = WorkBook; diff --git a/lib/WorkSheet.js b/lib/WorkSheet.js index e9bf26e..e28affa 100644 --- a/lib/WorkSheet.js +++ b/lib/WorkSheet.js @@ -1,76 +1,83 @@ -var Column = require('./Column.js'), - Row = require('./Row.js'), - Cell = require('./Cell.js'), - Image = require('./Images.js'), - utils = require('./Utils.js'), - xml = require('xmlbuilder'); - -var WorkSheet = function(wb){ - this.wb = wb; - this.opts = {}; - this.name = ''; - this.hasGroupings = false; - this.margins = { - bottom:1.0, - footer:.5, - header:.5, - left:.75, - right:.75, - top:1.0 - }; - this.printOptions = { - centerHorizontal: false, - centerVertical: true - } - this.sheetView = { - workbookViewId:0, - rightToLeft:0, - zoomScale:100, - zoomScaleNormal:100, - zoomScalePageLayoutView:100 - } - this.sheet={}; - this.sheet['@mc:Ignorable']="x14ac"; - this.sheet['@xmlns']='http://schemas.openxmlformats.org/spreadsheetml/2006/main'; - this.sheet['@xmlns:mc']="http://schemas.openxmlformats.org/markup-compatibility/2006"; - this.sheet['@xmlns:r']='http://schemas.openxmlformats.org/officeDocument/2006/relationships'; - this.sheet['@xmlns:x14ac']="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"; - this.sheet.sheetPr = { - outlinePr:{ - '@summaryBelow':1 - } - } - this.sheet.sheetViews = [ - { - sheetView:[] - } - ]; - this.sheet.sheetFormatPr = { - '@baseColWidth':10, - '@defaultRowHeight':15, - '@x14ac:dyDescent':0 - }; - this.sheet.sheetData = []; - this.sheet.pageMargins = []; - this.sheet.pageSetup = []; - this.sheet.printOptions = []; - this.cols = {}; - this.rows = {}; - - this.Settings = { - Outline:{ - SummaryBelow : settings(this).outlineSummaryBelow - } - } -}; +var xml = require('xmlbuilder'); + +var Image = require('./image'); +var cellAccessor = require('./cell'); +var columnAccessor = require('./column'); +var rowAccessor = require('./row'); +var utils = require('./utils'); -WorkSheet.prototype.setName = function(name){ +module.exports = WorkSheet; - this.name = name; +// ------------------------------------------------------------------------------ + +function WorkSheet(wb) { + this.wb = wb; + this.opts = {}; + this.name = ''; + this.hasGroupings = false; + this.margins = { + bottom: 1.0, + footer: .5, + header: .5, + left: .75, + right: .75, + top: 1.0 + }; + this.printOptions = { + centerHorizontal: false, + centerVertical: true + }; + this.sheetView = { + workbookViewId: 0, + rightToLeft: 0, + zoomScale: 100, + zoomScaleNormal: 100, + zoomScalePageLayoutView: 100 + }; + this.sheet = {}; + this.sheet['@mc:Ignorable'] = 'x14ac'; + this.sheet['@xmlns'] = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'; + this.sheet['@xmlns:mc'] = 'http://schemas.openxmlformats.org/markup-compatibility/2006'; + this.sheet['@xmlns:r'] = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'; + this.sheet['@xmlns:x14ac'] = 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac'; + this.sheet.sheetPr = { + outlinePr: { + '@summaryBelow': 1 + } + }; + this.sheet.sheetViews = [ + { + sheetView: [] + } + ]; + this.sheet.sheetFormatPr = { + '@baseColWidth': 10, + '@defaultRowHeight': 15, + '@x14ac:dyDescent': 0 + }; + this.sheet.sheetData = []; + this.sheet.pageMargins = []; + this.sheet.pageSetup = []; + this.sheet.printOptions = []; + this.cols = {}; + this.rows = {}; + + this.Settings = { + Outline: { + SummaryBelow: settings(this).outlineSummaryBelow + } + }; } -WorkSheet.prototype.Cell = Cell.Cell; -WorkSheet.prototype.Row = Row.Row; -WorkSheet.prototype.Column = Column.Column; + +// ------------------------------------------------------------------------------ + +WorkSheet.prototype.setName = function (name) { + this.name = name; +}; + +WorkSheet.prototype.Cell = cellAccessor; +WorkSheet.prototype.Row = rowAccessor; +WorkSheet.prototype.Column = columnAccessor; WorkSheet.prototype.Image = Image.Image; WorkSheet.prototype.getCell = getCell; WorkSheet.prototype.setWSOpts = setWSOpts; @@ -78,25 +85,28 @@ WorkSheet.prototype.toXML = toXML; WorkSheet.prototype.setValidation = setValidation; var countValidation = 0; + function setValidation(data) { - countValidation++; + countValidation++; this.sheet.dataValidations = this.sheet.dataValidations || {}; this.sheet.dataValidations['@count'] = countValidation; - this.sheet.dataValidations['#list'] = this.sheet.dataValidations['#list'] || []; + var dataValidation = {}; - Object.keys(data).e4nForEach(function (key) { - if (key == 'formulas') { return; } + Object.keys(data).e4nForEach(function (key) { + if (key === 'formulas') { + return; + } dataValidation['@' + key] = data[key]; }); - data.formulas.e4nForEach(function (f, i) { - if( typeof f == 'number' || f.substr(0, 1) == '=' ) { - f = f; - } else { - f = '"' + f + '"'; - } + data.formulas.e4nForEach(function (f, i) { + if (typeof f === 'number' || f.substr(0, 1) === '=') { + f = f; + } else { + f = '"' + f + '"'; + } dataValidation['formula' + (i + 1)] = f; }); this.sheet.dataValidations['#list'].push({ @@ -104,400 +114,397 @@ function setValidation(data) { }); } -function getCell(a,b){ - var props = {}; - if(typeof(a) == 'string'){ - props = a.toExcelRowCol(); - }else if(typeof(a) == 'number' && typeof(b) == 'number'){ - props.row = a; - props.col = b; - }else{ - return undefined; - } - return thisWS.rows[props.row].cells[props.col]; +// TODO WorkSheet#getCell appears unused +function getCell(a, b) { + var props = {}; + if (typeof(a) === 'string') { + props = a.toExcelRowCol(); + } else if (typeof(a) === 'number' && typeof(b) === 'number') { + props.row = a; + props.col = b; + } else { + return undefined; + } + return thisWS.rows[props.row].cells[props.col]; } -function settings(thisWS){ - var theseSettings = {}; - theseSettings.outlineSummaryBelow = function(val){ - console.log("####################################################################################"); - console.log("# WorkSheet.Settings is deprecated and will be removed in version 1.0.0 #"); - console.log("# Create WorkBooks with opts paramater instead. #"); - console.log("####################################################################################"); - val = val?1:0; - thisWS.sheet.sheetPr.outlinePr['@summaryBelow']=val; - } - return theseSettings; +function settings(thisWS) { + var theseSettings = {}; + theseSettings.outlineSummaryBelow = function (val) { + console.log('####################################################################################'); + console.log('# WorkSheet.Settings is deprecated and will be removed in version 1.0.0 #'); + console.log('# Create WorkBooks with opts paramater instead. #'); + console.log('####################################################################################'); + val = val ? 1 : 0; + thisWS.sheet.sheetPr.outlinePr['@summaryBelow'] = val; + }; + return theseSettings; } -function setWSOpts(opts){ - var opts = opts?opts:{}; - var thisWS = this; - // Set Margins - if(opts.margins){ - this.margins.bottom = opts.margins.bottom?opts.margins.bottom:1.0; - this.margins.footer = opts.margins.footer?opts.margins.footer:.5; - this.margins.header = opts.margins.header?opts.margins.header:.5; - this.margins.left = opts.margins.left?opts.margins.left:.75; - this.margins.right = opts.margins.right?opts.margins.right:.75; - this.margins.top = opts.margins.top?opts.margins.top:1.0; - } - Object.keys(this.margins).e4nForEach(function(k){ - var margin = {}; - margin['@'+k] = thisWS.margins[k]; - thisWS.sheet.pageMargins.push(margin); - }); - - // Set Print Options - if(opts.printOptions){ - this.printOptions.centerHorizontal = opts.printOptions.centerHorizontal?opts.printOptions.centerHorizontal:false; - this.printOptions.centerVertical = opts.printOptions.centerVertical?opts.printOptions.centerVertical:false; - } - this.sheet.printOptions.push({'@horizontalCentered':this.printOptions.centerHorizontal?1:0}); - this.sheet.printOptions.push({'@verticalCentered':this.printOptions.centerVertical?1:0}); - - // Set Page View options - var thisView = this.sheet.sheetViews[0].sheetView; - if(opts.view){ - if(parseInt(opts.view.zoom) != opts.view.zoom){ - console.log("invalid value for zoom. value must be an integer. value was %s",opts.view.zoom); - opts.view.zoom = 100; - } - this.sheetView.zoomScale = opts.view.zoom?opts.view.zoom:100; - this.sheetView.zoomScaleNormal = opts.view.zoom?opts.view.zoom:100; - this.sheetView.zoomScalePageLayoutView = opts.view.zoom?opts.view.zoom:100; - - if (opts.view.rtl) { - this.sheetView.rightToLeft = 1; - } - } - - // Set Outline Options - if(opts.outline){ - thisWS.sheet.sheetPr.outlinePr = { - '@summaryBelow':opts.outline.summaryBelow==false?0:1 - }; - } - - // Set Page Setup - if (opts.fitToPage) { - this.sheet.sheetPr.pageSetUpPr = {'@fitToPage':1}; - this.sheet.pageSetup.push({'@fitToHeight':opts.fitToPage.fitToHeight?opts.fitToPage.fitToHeight:1}); - this.sheet.pageSetup.push({'@fitToWidth':opts.fitToPage.fitToWidth?opts.fitToPage.fitToWidth:1}); - this.sheet.pageSetup.push({'@orientation':opts.fitToPage.orientation ?opts.fitToPage.orientation :'portrait'}); - this.sheet.pageSetup.push({'@horizontalDpi':opts.fitToPage.horizontalDpi?opts.fitToPage.horizontalDpi:4294967292}); - this.sheet.pageSetup.push({'@verticalDpi':opts.fitToPage.verticalDpi?opts.fitToPage.verticalDpi:4294967292}); - } - - // Set WorkSheet protections - if (opts.sheetProtection) { - thisWS.sheet.sheetProtection = { - '@autoFilter' : (opts.sheetProtection.autoFilter ? opts.sheetProtection.autoFilter : false), - '@deleteColumns' : (opts.sheetProtection.deleteColumns ? opts.sheetProtection.deleteColumns : false), - '@deleteRows' : (opts.sheetProtection.deleteRows ? opts.sheetProtection.deleteRows : false), - '@formatCells' : (opts.sheetProtection.formatCells ? opts.sheetProtection.formatCells : false), - '@formatColumns' : (opts.sheetProtection.formatColumns ? opts.sheetProtection.formatColumns : false), - '@formatRows' : (opts.sheetProtection.formatRows ? opts.sheetProtection.formatRows : false), - '@insertColumns' : (opts.sheetProtection.insertColumns ? opts.sheetProtection.insertColumns : false), - '@insertHyperlinks' : (opts.sheetProtection.insertHyperlinks ? opts.sheetProtection.insertHyperlinks : false), - '@insertRows' : (opts.sheetProtection.insertRows ? opts.sheetProtection.insertRows : false), - '@objects' : (opts.sheetProtection.objects ? opts.sheetProtection.objects : false), - '@pivotTables' : (opts.sheetProtection.pivotTables ? opts.sheetProtection.pivotTables : false), - '@scenarios' : (opts.sheetProtection.scenarios ? opts.sheetProtection.scenarios : false), - '@selectLockedCells' : (opts.sheetProtection.selectLockedCells ? opts.sheetProtection.selectLockedCells : false), - '@selectUnlockedCells' : (opts.sheetProtection.selectUnlockedCells ? opts.sheetProtection.selectUnlockedCells : false), - '@sheet' : (opts.sheetProtection.sheet ? opts.sheetProtection.sheet : true), - '@sort' : (opts.sheetProtection.sort ? opts.sheetProtection.sort : false) - } - if(opts.sheetProtection.password){ - thisWS.sheet.sheetProtection['@password'] = utils.getHashOfPassword(opts.sheetProtection.password); - } - } - - thisView.push({'@workbookViewId':this.sheetView.workbookViewId?this.sheetView.workbookViewId:0}); - thisView.push({'@zoomScale':this.sheetView.zoomScale?this.sheetView.zoomScale:100}); - thisView.push({'@zoomScaleNormal':this.sheetView.zoomScaleNormal?this.sheetView.zoomScaleNormal:100}); - thisView.push({'@zoomScalePageLayoutView':this.sheetView.zoomScalePageLayoutView?this.sheetView.zoomScalePageLayoutView:100}); - thisView.push({'@rightToLeft':this.sheetView.rightToLeft?1:0}); -} +function setWSOpts(opts) { + var opts = opts ? opts : {}; + var thisWS = this; + // Set Margins + if (opts.margins) { + this.margins.bottom = opts.margins.bottom ? opts.margins.bottom : 1.0; + this.margins.footer = opts.margins.footer ? opts.margins.footer : .5; + this.margins.header = opts.margins.header ? opts.margins.header : .5; + this.margins.left = opts.margins.left ? opts.margins.left : .75; + this.margins.right = opts.margins.right ? opts.margins.right : .75; + this.margins.top = opts.margins.top ? opts.margins.top : 1.0; + } + Object.keys(this.margins).e4nForEach(function (k) { + var margin = {}; + margin['@' + k] = thisWS.margins[k]; + thisWS.sheet.pageMargins.push(margin); + }); -function toXML(){ - var thisWS = this; - var thisSheet = JSON.parse(JSON.stringify(thisWS.sheet)); - var sheetData = thisSheet.sheetData; - var xmlOutVars = {}; - var xmlDebugVars = { pretty: true, indent: ' ',newline: '\n' }; - - /* - Process groupings and add collapsed attributes to rows where applicable - */ - if(thisWS.hasGroupings){ - var lastRowNum = Object.keys(thisWS.rows).sort()[Object.keys(thisWS.rows).length - 1]; - var lastColNum = Object.keys(thisWS.cols).sort()[Object.keys(thisWS.cols).length - 1]; - - var rOutlineLevels = { - curHighestLevel:0, - 0:{ - startRow:1, - endRow:1, - isHidden:0 - } - }; - - - var cOutlineLevels = { - curHighestLevel:0, - 0:{ - startCol:1, - endCol:1, - isHidden:0 - } - }; - - var summaryBelow = parseInt(thisSheet.sheetPr.outlinePr['@summaryBelow']) == 0 ? false : true; - if(summaryBelow && thisWS.rows[lastRowNum].attributes.hidden){ - thisWS.Row(parseInt(lastRowNum)+1); - } - if(summaryBelow && thisWS.cols[lastColNum].hidden){ - thisWS.Column(parseInt(lastColNum)+1); - } - - Object.keys(thisWS.rows).e4nForEach(function(rNum,i){ - var rID = parseInt(rNum); - var curRow = thisWS.rows[rNum]; - var thisLevel = curRow.attributes.outlineLevel?curRow.attributes.outlineLevel:0; - var isHidden = curRow.attributes.hidden==1?curRow.attributes.hidden:0; - var rowNum = curRow.attributes.r; - - rOutlineLevels[0].endRow=rID; - rOutlineLevels[0].isHidden=isHidden; - - if(typeof(rOutlineLevels[thisLevel]) == 'undefined'){ - rOutlineLevels[thisLevel] = { - startRow:rID, - endRow:rID, - isHidden:isHidden - } - } - - if(thisLevel <= rOutlineLevels.curHighestLevel){ - rOutlineLevels[thisLevel].endRow = rID; - rOutlineLevels[thisLevel].isHidden = isHidden; - } - - if(thisLevel != rOutlineLevels.curHighestLevel || rID == lastRowNum){ - if(summaryBelow && thisLevel != rOutlineLevels.curHighestLevel){ - - if(rID == lastRowNum){ - thisLevel=1; - } - for(oLi = rOutlineLevels.curHighestLevel; oLi > thisLevel; oLi--){ - if(rOutlineLevels[oLi]){ - var rowToCollapse = rOutlineLevels[oLi].endRow + 1; - var lastRow = thisWS.Row(rowToCollapse); - lastRow.setAttribute('collapsed',rOutlineLevels[oLi].isHidden); - delete rOutlineLevels[oLi]; - } - } - }else if(!summaryBelow && thisLevel != rOutlineLevels.curHighestLevel){ - if(rOutlineLevels[thisLevel]){ - if(thisLevel>rOutlineLevels.curHighestLevel){ - var rowToCollapse = rOutlineLevels[rOutlineLevels.curHighestLevel].startRow; - }else{ - var rowToCollapse = rOutlineLevels[thisLevel].startRow; - } - var lastRow = thisWS.Row(rowToCollapse); - lastRow.setAttribute('collapsed',rOutlineLevels[thisLevel].isHidden); - rOutlineLevels[thisLevel].startRow = rowNum; - } - } - } - if(thisLevel!=rOutlineLevels.curHighestLevel){ - rOutlineLevels.curHighestLevel=thisLevel; - } - }); - - Object.keys(thisWS.cols).e4nForEach(function(cNum,i){ - var cID = parseInt(cNum); - var curCol = thisWS.cols[cNum]; - var thisLevel = curCol.outlineLevel?curCol.outlineLevel:0; - var isHidden = curCol.hidden==1?curCol.hidden:0; - var colNum = curCol.min; - - cOutlineLevels[0].endCol=cID; - cOutlineLevels[0].isHidden=isHidden; - - if(typeof(cOutlineLevels[thisLevel]) == 'undefined'){ - cOutlineLevels[thisLevel] = { - startCol:cID, - endCol:cID, - isHidden:isHidden - } - } - - if(thisLevel <= cOutlineLevels.curHighestLevel){ - cOutlineLevels[thisLevel].endCol = cID; - cOutlineLevels[thisLevel].isHidden = isHidden; - } - - if(thisLevel != cOutlineLevels.curHighestLevel || cID == lastColNum){ - if(summaryBelow && thisLevel != cOutlineLevels.curHighestLevel){ - - if(cID == lastColNum){ - thisLevel=1; - } - for(oLi = cOutlineLevels.curHighestLevel; oLi > thisLevel; oLi--){ - if(cOutlineLevels[oLi]){ - var colToCollapse = cOutlineLevels[oLi].endCol + 1; - var lastCol = thisWS.Column(colToCollapse); - lastCol.setAttribute('collapsed',cOutlineLevels[oLi].isHidden); - delete cOutlineLevels[oLi]; - } - } - }else if(!summaryBelow && thisLevel != cOutlineLevels.curHighestLevel){ - if(cOutlineLevels[thisLevel]){ - if(thisLevel>cOutlineLevels.curHighestLevel){ - var colToCollapse = cOutlineLevels[cOutlineLevels.curHighestLevel].startCol; - }else{ - var colToCollapse = cOutlineLevels[thisLevel].startCol; - } - var lastCol = thisWS.Column(colToCollapse); - lastCol.setAttribute('collapsed',cOutlineLevels[thisLevel].isHidden); - cOutlineLevels[thisLevel].startCol = colNum; - } - } - } - if(thisLevel!=cOutlineLevels.curHighestLevel){ - cOutlineLevels.curHighestLevel=thisLevel; - } - }); - } - - /* - Process Column Definitions - */ - if(Object.keys(thisWS.cols).length > 0){ - if(thisSheet.cols instanceof Array === false){ - thisSheet.cols=[]; - } - - Object.keys(thisWS.cols).e4nForEach(function(i){ - var c = thisWS.cols[i]; - var thisCol = {col:[]}; - Object.keys(c).e4nForEach(function(k){ - var tmpObj = {}; - if(typeof(c[k]) != 'object'){ - tmpObj['@'+k] = c[k]; - thisCol.col.push(tmpObj); - } - }); - thisSheet.cols.push(thisCol); - }); - } - - /* - Process Rows of data - */ - Object.keys(thisWS.rows).e4nForEach(function(r,i){ - var thisRow = {row:[]}; - Object.keys(thisWS.rows[r].attributes).e4nForEach(function(a,i){ - var attr = '@'+a; - var obj = {}; - obj[attr] = thisWS.rows[r].attributes[a]; - thisRow.row.push(obj); - }); - Object.keys(thisWS.rows[r].cells).e4nForEach(function(c,i){ - var thisCellIndex = thisRow.row.push({'c':{}}); - var thisCell = thisRow.row[thisCellIndex - 1]['c']; - Object.keys(thisWS.rows[r].cells[c].attributes).e4nForEach(function(a,i){ - thisCell['@'+a] = thisWS.rows[r].cells[c].attributes[a]; - }); - Object.keys(thisWS.rows[r].cells[c].children).e4nForEach(function(v,i){ - thisCell[v] = thisWS.rows[r].cells[c].children[v]; - }); - }) - sheetData.push(thisRow) - }); - - /* - Process and merged cells - */ - if(thisWS.mergeCells && thisWS.mergeCells.length>=0){ - thisSheet.mergeCells = []; - thisSheet.mergeCells.push({'@count':thisWS.mergeCells.length}); - thisWS.mergeCells.e4nForEach(function(cr){ - thisSheet.mergeCells.push({'mergeCell':{'@ref':cr}}); - }); - }; - - /* - Process hyperlinks - */ - if(thisWS.hyperlinks && thisWS.hyperlinks.length>=0){ - thisSheet.hyperlinks = []; - thisWS.hyperlinks.e4nForEach(function(cr){ - thisSheet.hyperlinks.push({'hyperlink':{'@ref':cr.ref,'@r:id':'rId' + cr.id}}); - }); - }; - - /* - Get dimensions of worksheet. - */ - - var rowCount = Object.keys(thisWS.rows).length; - var colCount = Object.keys(thisWS.cols).length; - if(rowCount && colCount){ - thisSheet['dimension'] = [{}]; - thisSheet['dimension'][0]['@ref'] = 'A1:' + colCount.toExcelAlpha()+rowCount; - } - - /* - Excel complains if specific attributes on not in the correct order in the XML doc. - */ - var excelOrder = [ - 'sheetPr', - 'dimension', - 'sheetViews', - 'sheetFormatPr', - 'cols', - 'sheetData', - 'sheetProtection', - 'autoFilter', - 'mergeCells', - 'hyperlinks', - 'dataValidations', - 'printOptions', - 'pageMargins', - 'pageSetup', - 'drawing' - ]; - var orderedDef = []; - Object.keys(thisSheet).e4nForEach(function(k){ - if(k.charAt(0)==='@'){ - var def = {}; - def[k] = thisSheet[k]; - orderedDef.push(def); - } - }); - excelOrder.e4nForEach(function(k){ - if(k=='autoFilter' && thisSheet[k]){ - thisSheet.sheetPr['@enableFormatConditionsCalculation']='1'; - thisSheet.sheetPr['@filterMode']='1'; - } - if(thisSheet[k]){ - var def = {}; - def[k] = thisSheet[k]; - orderedDef.push(def); - } - }); - - var wsXML = xml.create('worksheet',{version: '1.0', encoding: 'UTF-8', standalone: true}); - orderedDef.e4nForEach(function(obj){ - wsXML.ele(obj); - }); - var xmlStr = wsXML.end(xmlOutVars); - return xmlStr; + // Set Print Options + if (opts.printOptions) { + this.printOptions.centerHorizontal = opts.printOptions.centerHorizontal ? opts.printOptions.centerHorizontal : false; + this.printOptions.centerVertical = opts.printOptions.centerVertical ? opts.printOptions.centerVertical : false; + } + this.sheet.printOptions.push({ '@horizontalCentered': this.printOptions.centerHorizontal ? 1 : 0 }); + this.sheet.printOptions.push({ '@verticalCentered': this.printOptions.centerVertical ? 1 : 0 }); + + // Set Page View options + var thisView = this.sheet.sheetViews[0].sheetView; + if (opts.view) { + if (parseInt(opts.view.zoom) !== opts.view.zoom) { + console.log('invalid value for zoom. value must be an integer. value was %s', opts.view.zoom); + opts.view.zoom = 100; + } + this.sheetView.zoomScale = opts.view.zoom ? opts.view.zoom : 100; + this.sheetView.zoomScaleNormal = opts.view.zoom ? opts.view.zoom : 100; + this.sheetView.zoomScalePageLayoutView = opts.view.zoom ? opts.view.zoom : 100; + + if (opts.view.rtl) { + this.sheetView.rightToLeft = 1; + } + } + + // Set Outline Options + if (opts.outline) { + thisWS.sheet.sheetPr.outlinePr = { + '@summaryBelow': opts.outline.summaryBelow === false ? 0 : 1 + }; + } + + // Set Page Setup + if (opts.fitToPage) { + this.sheet.sheetPr.pageSetUpPr = { '@fitToPage': 1 }; + this.sheet.pageSetup.push({ '@fitToHeight': opts.fitToPage.fitToHeight ? opts.fitToPage.fitToHeight : 1 }); + this.sheet.pageSetup.push({ '@fitToWidth': opts.fitToPage.fitToWidth ? opts.fitToPage.fitToWidth : 1 }); + this.sheet.pageSetup.push({ '@orientation': opts.fitToPage.orientation ? opts.fitToPage.orientation : 'portrait' }); + this.sheet.pageSetup.push({ '@horizontalDpi': opts.fitToPage.horizontalDpi ? opts.fitToPage.horizontalDpi : 4294967292 }); + this.sheet.pageSetup.push({ '@verticalDpi': opts.fitToPage.verticalDpi ? opts.fitToPage.verticalDpi : 4294967292 }); + } + + // Set WorkSheet protections + if (opts.sheetProtection) { + thisWS.sheet.sheetProtection = { + '@autoFilter': (opts.sheetProtection.autoFilter ? opts.sheetProtection.autoFilter : false), + '@deleteColumns': (opts.sheetProtection.deleteColumns ? opts.sheetProtection.deleteColumns : false), + '@deleteRows': (opts.sheetProtection.deleteRows ? opts.sheetProtection.deleteRows : false), + '@formatCells': (opts.sheetProtection.formatCells ? opts.sheetProtection.formatCells : false), + '@formatColumns': (opts.sheetProtection.formatColumns ? opts.sheetProtection.formatColumns : false), + '@formatRows': (opts.sheetProtection.formatRows ? opts.sheetProtection.formatRows : false), + '@insertColumns': (opts.sheetProtection.insertColumns ? opts.sheetProtection.insertColumns : false), + '@insertHyperlinks': (opts.sheetProtection.insertHyperlinks ? opts.sheetProtection.insertHyperlinks : false), + '@insertRows': (opts.sheetProtection.insertRows ? opts.sheetProtection.insertRows : false), + '@objects': (opts.sheetProtection.objects ? opts.sheetProtection.objects : false), + '@pivotTables': (opts.sheetProtection.pivotTables ? opts.sheetProtection.pivotTables : false), + '@scenarios': (opts.sheetProtection.scenarios ? opts.sheetProtection.scenarios : false), + '@selectLockedCells': (opts.sheetProtection.selectLockedCells ? opts.sheetProtection.selectLockedCells : false), + '@selectUnlockedCells': (opts.sheetProtection.selectUnlockedCells ? opts.sheetProtection.selectUnlockedCells : false), + '@sheet': (opts.sheetProtection.sheet ? opts.sheetProtection.sheet : true), + '@sort': (opts.sheetProtection.sort ? opts.sheetProtection.sort : false) + }; + if (opts.sheetProtection.password) { + thisWS.sheet.sheetProtection['@password'] = utils.getHashOfPassword(opts.sheetProtection.password); + } + } + + thisView.push({ '@workbookViewId': this.sheetView.workbookViewId ? this.sheetView.workbookViewId : 0 }); + thisView.push({ '@zoomScale': this.sheetView.zoomScale ? this.sheetView.zoomScale : 100 }); + thisView.push({ '@zoomScaleNormal': this.sheetView.zoomScaleNormal ? this.sheetView.zoomScaleNormal : 100 }); + thisView.push({ '@zoomScalePageLayoutView': this.sheetView.zoomScalePageLayoutView ? this.sheetView.zoomScalePageLayoutView : 100 }); + thisView.push({ '@rightToLeft': this.sheetView.rightToLeft ? 1 : 0 }); } +function toXML() { + var thisWS = this; + var thisSheet = JSON.parse(JSON.stringify(thisWS.sheet)); + var sheetData = thisSheet.sheetData; + var xmlOutVars = {}; + var xmlDebugVars = { pretty: true, indent: ' ', newline: '\n' }; + + /* + Process groupings and add collapsed attributes to rows where applicable + */ + if (thisWS.hasGroupings) { + var lastRowNum = Object.keys(thisWS.rows).sort()[Object.keys(thisWS.rows).length - 1]; + var lastColNum = Object.keys(thisWS.cols).sort()[Object.keys(thisWS.cols).length - 1]; + + var rOutlineLevels = { + curHighestLevel: 0, + 0: { + startRow: 1, + endRow: 1, + isHidden: 0 + } + }; + + var cOutlineLevels = { + curHighestLevel: 0, + 0: { + startCol: 1, + endCol: 1, + isHidden: 0 + } + }; + + var summaryBelow = parseInt(thisSheet.sheetPr.outlinePr['@summaryBelow']) === 0 ? false : true; + if (summaryBelow && thisWS.rows[lastRowNum].attributes.hidden) { + thisWS.Row(parseInt(lastRowNum) + 1); + } + if (summaryBelow && thisWS.cols[lastColNum].hidden) { + thisWS.Column(parseInt(lastColNum) + 1); + } + + Object.keys(thisWS.rows).e4nForEach(function (rNum, i) { + var rID = parseInt(rNum); + var curRow = thisWS.rows[rNum]; + var thisLevel = curRow.attributes.outlineLevel ? curRow.attributes.outlineLevel : 0; + var isHidden = curRow.attributes.hidden === 1 ? curRow.attributes.hidden : 0; + var rowNum = curRow.attributes.r; + + rOutlineLevels[0].endRow = rID; + rOutlineLevels[0].isHidden = isHidden; + + if (typeof(rOutlineLevels[thisLevel]) === 'undefined') { + rOutlineLevels[thisLevel] = { + startRow: rID, + endRow: rID, + isHidden: isHidden + }; + } + + if (thisLevel <= rOutlineLevels.curHighestLevel) { + rOutlineLevels[thisLevel].endRow = rID; + rOutlineLevels[thisLevel].isHidden = isHidden; + } + + if (thisLevel !== rOutlineLevels.curHighestLevel || rID === lastRowNum) { + if (summaryBelow && thisLevel !== rOutlineLevels.curHighestLevel) { + if (rID === lastRowNum) { + thisLevel = 1; + } + var oLi; + for (oLi = rOutlineLevels.curHighestLevel; oLi > thisLevel; oLi--) { + if (rOutlineLevels[oLi]) { + var rowToCollapse = rOutlineLevels[oLi].endRow + 1; + var lastRow = thisWS.Row(rowToCollapse); + lastRow.setAttribute('collapsed', rOutlineLevels[oLi].isHidden); + delete rOutlineLevels[oLi]; + } + } + } else if (!summaryBelow && thisLevel !== rOutlineLevels.curHighestLevel) { + if (rOutlineLevels[thisLevel]) { + if (thisLevel > rOutlineLevels.curHighestLevel) { + var rowToCollapse = rOutlineLevels[rOutlineLevels.curHighestLevel].startRow; + } else { + var rowToCollapse = rOutlineLevels[thisLevel].startRow; + } + var lastRow = thisWS.Row(rowToCollapse); + lastRow.setAttribute('collapsed', rOutlineLevels[thisLevel].isHidden); + rOutlineLevels[thisLevel].startRow = rowNum; + } + } + } + if (thisLevel !== rOutlineLevels.curHighestLevel) { + rOutlineLevels.curHighestLevel = thisLevel; + } + }); + + Object.keys(thisWS.cols).e4nForEach(function (cNum, i) { + var cID = parseInt(cNum); + var curCol = thisWS.cols[cNum]; + var thisLevel = curCol.outlineLevel ? curCol.outlineLevel : 0; + var isHidden = curCol.hidden === 1 ? curCol.hidden : 0; + var colNum = curCol.min; + + cOutlineLevels[0].endCol = cID; + cOutlineLevels[0].isHidden = isHidden; + + if (typeof(cOutlineLevels[thisLevel]) === 'undefined') { + cOutlineLevels[thisLevel] = { + startCol: cID, + endCol: cID, + isHidden: isHidden + }; + } + + if (thisLevel <= cOutlineLevels.curHighestLevel) { + cOutlineLevels[thisLevel].endCol = cID; + cOutlineLevels[thisLevel].isHidden = isHidden; + } + + if (thisLevel !== cOutlineLevels.curHighestLevel || cID === lastColNum) { + if (summaryBelow && thisLevel !== cOutlineLevels.curHighestLevel) { + if (cID === lastColNum) { + thisLevel = 1; + } + var oLi; + for(oLi = cOutlineLevels.curHighestLevel; oLi > thisLevel; oLi--) { + if (cOutlineLevels[oLi]) { + var colToCollapse = cOutlineLevels[oLi].endCol + 1; + var lastCol = thisWS.Column(colToCollapse); + lastCol.setAttribute('collapsed', cOutlineLevels[oLi].isHidden); + delete cOutlineLevels[oLi]; + } + } + } else if (!summaryBelow && thisLevel !== cOutlineLevels.curHighestLevel) { + if (cOutlineLevels[thisLevel]) { + if (thisLevel > cOutlineLevels.curHighestLevel) { + var colToCollapse = cOutlineLevels[cOutlineLevels.curHighestLevel].startCol; + } else { + var colToCollapse = cOutlineLevels[thisLevel].startCol; + } + var lastCol = thisWS.Column(colToCollapse); + lastCol.setAttribute('collapsed', cOutlineLevels[thisLevel].isHidden); + cOutlineLevels[thisLevel].startCol = colNum; + } + } + } + if (thisLevel !== cOutlineLevels.curHighestLevel) { + cOutlineLevels.curHighestLevel = thisLevel; + } + }); + } + + /* + Process Column Definitions + */ + if (Object.keys(thisWS.cols).length > 0) { + if (thisSheet.cols instanceof Array === false) { + thisSheet.cols = []; + } + + Object.keys(thisWS.cols).e4nForEach(function (i) { + var c = thisWS.cols[i]; + var thisCol = { col: [] }; + Object.keys(c).e4nForEach(function (k) { + var tmpObj = {}; + if (typeof(c[k]) !== 'object') { + tmpObj['@' + k] = c[k]; + thisCol.col.push(tmpObj); + } + }); + thisSheet.cols.push(thisCol); + }); + } + + /* + Process Rows of data + */ + Object.keys(thisWS.rows).e4nForEach(function (r, i) { + var thisRow = { row: [] }; + Object.keys(thisWS.rows[r].attributes).e4nForEach(function (a, i) { + var attr = '@' + a; + var obj = {}; + obj[attr] = thisWS.rows[r].attributes[a]; + thisRow.row.push(obj); + }); + Object.keys(thisWS.rows[r].cells).e4nForEach(function (c, i) { + var thisCellIndex = thisRow.row.push({ 'c': {} }); + var thisCell = thisRow.row[thisCellIndex - 1]['c']; + Object.keys(thisWS.rows[r].cells[c].attributes).e4nForEach(function (a, i) { + thisCell['@' + a] = thisWS.rows[r].cells[c].attributes[a]; + }); + Object.keys(thisWS.rows[r].cells[c].children).e4nForEach(function (v, i) { + thisCell[v] = thisWS.rows[r].cells[c].children[v]; + }); + }); + sheetData.push(thisRow); + }); -module.exports = WorkSheet; + /* + Process and merged cells + */ + if (thisWS.mergeCells && thisWS.mergeCells.length >= 0) { + thisSheet.mergeCells = []; + thisSheet.mergeCells.push({ '@count': thisWS.mergeCells.length }); + thisWS.mergeCells.e4nForEach(function (cr) { + thisSheet.mergeCells.push({ 'mergeCell': { '@ref': cr } }); + }); + } + + /* + Process hyperlinks + */ + if (thisWS.hyperlinks && thisWS.hyperlinks.length >= 0) { + thisSheet.hyperlinks = []; + thisWS.hyperlinks.e4nForEach(function (cr) { + thisSheet.hyperlinks.push({ 'hyperlink': { '@ref': cr.ref, '@r:id': 'rId' + cr.id } }); + }); + } + + /* + Get dimensions of worksheet. + */ + + var rowCount = Object.keys(thisWS.rows).length; + var colCount = Object.keys(thisWS.cols).length; + if (rowCount && colCount) { + thisSheet['dimension'] = [{}]; + thisSheet['dimension'][0]['@ref'] = 'A1:' + colCount.toExcelAlpha() + rowCount; + } + + /* + Excel complains if specific attributes on not in the correct order in the XML doc. + */ + var excelOrder = [ + 'sheetPr', + 'dimension', + 'sheetViews', + 'sheetFormatPr', + 'cols', + 'sheetData', + 'sheetProtection', + 'autoFilter', + 'mergeCells', + 'hyperlinks', + 'dataValidations', + 'printOptions', + 'pageMargins', + 'pageSetup', + 'drawing' + ]; + var orderedDef = []; + Object.keys(thisSheet).e4nForEach(function (k) { + if(k.charAt(0) === '@') { + var def = {}; + def[k] = thisSheet[k]; + orderedDef.push(def); + } + }); + excelOrder.e4nForEach(function (k) { + if (k === 'autoFilter' && thisSheet[k]) { + thisSheet.sheetPr['@enableFormatConditionsCalculation'] = '1'; + thisSheet.sheetPr['@filterMode'] = '1'; + } + if(thisSheet[k]) { + var def = {}; + def[k] = thisSheet[k]; + orderedDef.push(def); + } + }); + + var wsXML = xml.create('worksheet', { version: '1.0', encoding: 'UTF-8', standalone: true }); + orderedDef.e4nForEach(function (obj) { + wsXML.ele(obj); + }); + var xmlStr = wsXML.end(xmlOutVars); + return xmlStr; +} diff --git a/lib/image.js b/lib/image.js new file mode 100644 index 0000000..bc2eb08 --- /dev/null +++ b/lib/image.js @@ -0,0 +1,256 @@ +var fs = require('fs'), + mime = require('mime'), + imgsz = require('image-size'); + +var drawing = function (imgURI) { + var d = { + props: { + image: imgURI, + imageId: 0, + mimeType: '', + extension: '', + width: 0, + height: 0, + dpi: 96 + }, + xml: { + 'xdr:oneCellAnchor': [ + { + 'xdr:from': { + 'xdr:col': 0, + 'xdr:colOff': 0, + 'xdr:row': 0, + 'xdr:rowOff': 0 + } + }, + { + 'xdr:ext': { + '@cx': 0 * 9525, + '@cy': 0 * 9525 + } + }, + { + 'xdr:pic': { + 'xdr:nvPicPr': [ + { + 'xdr:cNvPr': { + '@descr': 'image', + '@id': 0, + '@name': 'Picture' + } + }, + 'xdr:cNvPicPr' + ], + 'xdr:blipFill': { + 'a:blip': { + '@cstate': 'print', + '@r:embed': 'rId0', + '@xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' + } + }, + 'xdr:spPr': [ + { + '@bwMode': 'auto' + }, + { + 'a:xfrm': { + 'a:off': { + '@x': 0, + '@y': 0 + }, + 'a:ext': { + '@cx': 0 * 9525, + '@cy': 0 * 9525 + } + } + }, + { + 'a:prstGeom': [ + { + '@prst': 'rect' + }, + 'a:avLst' + ] + }, + 'a:noFill', + { + 'a:ln': ['a:noFill'] + } + ] + } + }, + 'xdr:clientData' + ] + }, + + Position: function (r, c, offY, offX) { + var offsetX = offX ? offX : 0; + var offsetY = offY ? offY : 0; + + d.xml['xdr:oneCellAnchor'].e4nForEach(function (v) { + if (v['xdr:from']) { + v['xdr:from']['xdr:col'] = c - 1; + v['xdr:from']['xdr:row'] = r - 1; + v['xdr:from']['xdr:colOff'] = offsetX; + v['xdr:from']['xdr:rowOff'] = offsetY; + } else if (v['xdr:pic'] && offX && offY) { + var spPR = v['xdr:pic']['xdr:spPr']; + spPR.e4nForEach(function (o) { + if (o['a:xfrm']) { + o['a:xfrm']['a:off']['@x'] = offsetX; + o['a:xfrm']['a:off']['@y'] = offsetY; + } + }); + } + }); + }, + Properties: function (props) { + Object.keys(props).e4nForEach(function (k) { + d.props[k] = props[k]; + }); + }, + SetID: function (id) { + d.xml['xdr:oneCellAnchor'].e4nForEach(function (v) { + if (v['xdr:pic']) { + v['xdr:pic']['xdr:nvPicPr'][0]['xdr:cNvPr']['@id'] = id; + v['xdr:pic']['xdr:blipFill']['a:blip']['@r:embed'] = 'rId' + id; + } + }); + d.props.imageId = id; + }, + updateSize: function () { + d.xml['xdr:oneCellAnchor'].e4nForEach(function (v) { + if (v['xdr:ext']) { + v['xdr:ext']['@cx'] = d.props.width * 9525 * (96 / d.props.dpi); + v['xdr:ext']['@cy'] = d.props.height * 9525 * (96 / d.props.dpi); + } else if (v['xdr:pic']) { + var spPR = v['xdr:pic']['xdr:spPr']; + spPR.e4nForEach(function (o) { + if (o['a:xfrm']) { + o['a:xfrm']['a:ext']['@cx'] = d.props.width * 9525 * (96 / d.props.dpi); + o['a:xfrm']['a:ext']['@cy'] = d.props.height * 9525 * (96 / d.props.dpi); + } + }); + } + }); + } + + }; + + return d; +}; + +module.exports.Image = function (imgURI) { + + var wb = this.wb.workbook; + var wSs = this.wb.worksheets; + var ws = this; + + // add entry to [Content_Types].xml + var mimeType = mime.lookup(imgURI); + var extension = mimeType.split('/')[1]; + + var contentTypeAdded = false; + wb.Content_Types.Types.e4nForEach(function (t) { + if (t['Default']) { + if (t['Default']['@ContentType'] === mimeType) { + contentTypeAdded = true; + } + } + }); + if (!contentTypeAdded) { + wb.Content_Types.Types.push({ + 'Default': { + '@ContentType': mimeType, + '@Extension': extension + } + }); + } + + // create drawingn.xml file + // create drawingn.xml.rels file + if (!ws.drawings) { + ws.drawings = { + 'rels': { + 'Relationships': [ + { + '@xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships' + } + ] + }, + 'xml': { + 'xdr:wsDr': [ + { + '@xmlns:a': 'http://schemas.openxmlformats.org/drawingml/2006/main', + '@xmlns:xdr': 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing' + } + ] + }, + drawings: [] + }; + } + if (!ws.rels) { + ws.rels = { + 'Relationships': [ + { + '@xmlns': 'http://schemas.openxmlformats.org/package/2006/relationships' + } + ] + }; + } + + var d = new drawing(imgURI); + + + d.Properties({ + 'mimeType': mimeType, + 'extension': extension + }); + + var dim = imgsz(imgURI); + d.Properties({ + 'width': dim.width, + 'height': dim.height + }); + d.updateSize(); + + ws.drawings.drawings.push(d); + var imgID = 0; + wSs.e4nForEach(function (s) { + if (s.drawings) { + imgID += s.drawings.drawings.length; + } + }); + d.SetID(imgID); + + ws.drawings.rels.Relationships.push({ + 'Relationship': { + '@Id': 'rId' + imgID, + '@Target': '../media/image' + imgID + '.' + extension, + '@Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image' + } + }); + + var relExists = false; + ws.rels['Relationships'].e4nForEach(function (r) { + if (r['Relationship']) { + if (r['Relationship']['@Id'] === 'rId1') { + relExists = true; + } + } + }); + if (!relExists) { + ws.rels['Relationships'].push({ + 'Relationship': { + '@Id': 'rId1', + '@Target': '../drawings/drawing' + ws.sheetId + '.xml', + '@Type': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing' + } + }); + } + ws.sheet.drawing = { + '@r:id': 'rId1' + }; + + return d; +}; diff --git a/lib/index.js b/lib/index.js index 84b1393..8151bc9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,13 +1,16 @@ -var wb = require('./WorkBook.js'), -style = require('./Style.js'); +var style = require('./style'); -exports.WorkBook = wb.WorkBook; -exports.Style = style.Style; +module.exports = { + WorkBook: require('./workbook'), + Style: style.Style +}; + +// TODO do not modify core prototypes -Number.prototype.toExcelAlpha = function(isCaps) { - isCaps=isCaps?isCaps:true; - var remaining = this; - var aCharCode = isCaps ? 65 : 97; +Number.prototype.toExcelAlpha = function (isCaps) { + isCaps = isCaps ? isCaps : true; + var remaining = this; + var aCharCode = isCaps ? 65 : 97; var columnName = ''; while (remaining > 0) { var mod = (remaining - 1) % 26; @@ -17,19 +20,23 @@ Number.prototype.toExcelAlpha = function(isCaps) { return columnName; }; -String.prototype.toExcelRowCol = function() { - var numeric = this.split(/\D/).filter(function(el) { return el != '' })[0]; - var alpha = this.split(/\d/).filter(function(el) { return el != '' })[0]; - var row = parseInt(numeric, 10); - var col = alpha.toUpperCase().split('').reduce(function(a, b, index, arr) { +String.prototype.toExcelRowCol = function () { + var numeric = this.split(/\D/).filter(function (el) { + return el !== ''; + })[0]; + var alpha = this.split(/\d/).filter(function (el) { + return el !== ''; + })[0]; + var row = parseInt(numeric, 10); + var col = alpha.toUpperCase().split('').reduce(function (a, b, index, arr) { return a + (b.charCodeAt(0) - 64) * Math.pow(26, arr.length - index - 1); }, 0); return { row: row, col: col }; }; -Date.prototype.getExcelTS = function(){ - var epoch = new Date(1899,11,31); - var dt = this.setDate(this.getDate()+1); - var ts = (dt-epoch)/(1000*60*60*24); - return ts; +Date.prototype.getExcelTS = function () { + var epoch = new Date(1899, 11, 31); + var dt = this.setDate(this.getDate() + 1); + var ts = (dt-epoch) / (1000 * 60 * 60 * 24); + return ts; }; diff --git a/package.json b/package.json index 9d7e432..997f8fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "excel4node", "version": "0.2.23", + "description": "Library to create Formatted Excel Files.", "keywords": [ "excel", "spreadsheet", @@ -11,7 +12,7 @@ "workbook", "ooxml" ], - "description": "Library to create Formatted Excel Files.", + "main": "./lib/index", "author": { "name": "Nater", "email": "nater@seas.harvard.edu" @@ -22,13 +23,6 @@ "url": "http://opensource.org/licenses/mit-license.php" } ], - "dependencies": { - "image-size": "0.3.5", - "jszip": "2.5.0", - "mime": "1.3.4", - "underscore": "1.8.3", - "xmlbuilder": "3.1.0" - }, "repository": { "type": "git", "url": "git://github.com/natergj/excel4node.git" @@ -36,6 +30,18 @@ "bugs": { "url": "https://github.com/natergj/excel4node/labels/bug" }, - "main": "./lib/index", - "readmeFilename": "README.md" + "readmeFilename": "README.md", + "scripts": { + "test": "NODE_ENV=test ./node_modules/tape/bin/tape ./tests/**/**/*.test.js" + }, + "dependencies": { + "image-size": "0.3.5", + "jszip": "2.5.0", + "mime": "1.3.4", + "underscore": "1.8.3", + "xmlbuilder": "3.1.0" + }, + "devDependencies": { + "tape": "^4.4.0" + } } diff --git a/tests/cell.test.js b/tests/cell.test.js new file mode 100644 index 0000000..1f068e4 --- /dev/null +++ b/tests/cell.test.js @@ -0,0 +1,15 @@ +var test = require('tape'); + +var xl = require('../lib/index'); + +function makeCell() { + var wb = new xl.WorkBook(); + var ws = wb.WorkSheet('test'); + return ws.Cell(1, 1); +} + +test('Cell coverage', function (t) { + t.plan(1); + var cell = makeCell(); + t.ok(cell); +}); diff --git a/tests/column.test.js b/tests/column.test.js new file mode 100644 index 0000000..2aea115 --- /dev/null +++ b/tests/column.test.js @@ -0,0 +1,19 @@ +var test = require('tape'); + +var xl = require('../lib/index'); +var Column = require('../lib/Column').Column; + +function makeColumn() { + var wb = new xl.WorkBook(); + var ws = wb.WorkSheet('test'); + return ws.Column(1); +} + +test('Column coverage', function (t) { + t.plan(4); + var col = makeColumn(); + t.ok(col.Width(100), 'Width()'); + t.ok(col.Freeze(), 'Freeze()'); + t.ok(col.Hide(), 'Hide()'); + t.ok(col.Group('foo', false), 'Group()'); +}); diff --git a/tests/workbook.test.js b/tests/workbook.test.js new file mode 100644 index 0000000..f4e315b --- /dev/null +++ b/tests/workbook.test.js @@ -0,0 +1,31 @@ +var test = require('tape'); + +// load prototype extensions +// TODO fix prototype extensions and remove this +require('../lib/index'); + +var WorkBook = require('../lib/WorkBook'); + +test('WorkBook init', function (t) { + t.plan(1); + var wb = new WorkBook(); + t.ok(wb); +}); + +// Initial test to cover lib at a high level +test('WorkBook coverage', function (t) { + t.plan(1); + + var wb = new WorkBook(); + + var ws = wb.WorkSheet('Test Worksheet'); + + var myCell = ws.Cell(1, 1); + myCell.String('Test Value'); + + t.ok( + Buffer.isBuffer(wb.writeToBuffer()), + 'WorkBook#writeToBuffer() returns a Buffer' + ); +}); + diff --git a/tests/worksheet.test.js b/tests/worksheet.test.js new file mode 100644 index 0000000..b9c5577 --- /dev/null +++ b/tests/worksheet.test.js @@ -0,0 +1,29 @@ +var test = require('tape'); + +var xl = require('../lib/index'); + +function makeWorkSheet() { + var wb = new xl.WorkBook(); + return wb.WorkSheet('test'); +} + +test('WorkSheet coverage', function (t) { + t.plan(3); + var ws = makeWorkSheet(); + t.ok(ws.Column(1)); + t.ok(ws.Row(1)); + t.ok(ws.Cell(1, 1)); +}); + +test('WorkSheet setValidation()', function (t) { + t.plan(1); + var ws = makeWorkSheet(); + ws.setValidation({ + type: 'list', + allowBlank: 1, + sqref: 'B2:B10', + formulas: ['=sheet2!$A$1:$A$2'] + }); + t.ok(ws); +}); +