Skip to content

Commit

Permalink
Merge pull request #648 from jmaister/fix-remove-columns
Browse files Browse the repository at this point in the history
Fix remove columns
  • Loading branch information
jmaister authored Oct 30, 2021
2 parents c6cc3de + 0c9e364 commit 1d17cd3
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 156 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"build": "webpack --config webpack.config.js --progress --mode development --watch",
"prod": "webpack --config webpack.config.js --progress --mode production",
"test": "jest --coverage && coveralls < coverage/lcov.info",
"jest": "jest --coverage"
"jest": "jest --coverage",
"j": "jest --watch --testMatch remove",
"watch": "jest --watch"
},
"main": "dist/excellentexport.js",
"devDependencies": {
Expand Down
173 changes: 18 additions & 155 deletions src/excellentexport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import * as XLSX from 'xlsx';

import * as utils from './utils';

export interface ConvertOptions {
anchor?: (string|HTMLAnchorElement),
openAsDownload?: boolean,
Expand All @@ -31,147 +33,8 @@ export interface SheetOptions {

const ExcellentExport = function() {

const b64toBlob = function (b64Data:string, contentType:string, sliceSize?:number): Blob {
// function taken from http://stackoverflow.com/a/16245768/2591950
// author Jeremy Banks http://stackoverflow.com/users/1114/jeremy-banks
contentType = contentType || '';
sliceSize = sliceSize || 512;

const byteCharacters = atob(b64Data);
const byteArrays = [];

for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);

const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i = i + 1) {
byteNumbers[i] = slice.charCodeAt(i);
}

const byteArray = new Uint8Array(byteNumbers);

byteArrays.push(byteArray);
}

return new Blob(byteArrays, {
type: contentType
});
};

const version = "3.7.2";
const template = {excel: '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>{table}</table></body></html>'};
let csvDelimiter = ",";
let csvNewLine = "\r\n";

/**
* Convert a string to Base64.
*/
const base64 = function(s:string) : string {
return btoa(unescape(encodeURIComponent(s)));
};

const format = function(s:string, context:any) : string {
return s.replace(new RegExp("{(\\w+)}", "g"), function(m, p) {
return context[p];
});
};

/**
* Get element by ID.
* @param {*} element
*/
const getTable = function(element :(HTMLTableElement|string)) : HTMLTableElement {
if (typeof element === 'string') {
return document.getElementById(element) as HTMLTableElement;
}
return element;
};

/**
* Get element by ID.
* @param {*} element
*/
const getAnchor = function(element :(HTMLAnchorElement|string)) : HTMLAnchorElement {
if (typeof element === 'string') {
return document.getElementById(element) as HTMLAnchorElement;
}
return element;
};

/**
* Encode a value for CSV.
* @param {*} value
*/
const fixCSVField = function(value) {
let fixedValue = value;
const addQuotes = (value.indexOf(csvDelimiter) !== -1) || (value.indexOf('\r') !== -1) || (value.indexOf('\n') !== -1);
const replaceDoubleQuotes = (value.indexOf('"') !== -1);

if (replaceDoubleQuotes) {
fixedValue = fixedValue.replace(/"/g, '""');
}
if (addQuotes || replaceDoubleQuotes) {
fixedValue = '"' + fixedValue + '"';
}

return fixedValue;
};

const tableToArray = function(table:HTMLTableElement) : any[][] {
let tableInfo = Array.prototype.map.call(table.querySelectorAll('tr'), function(tr) {
return Array.prototype.map.call(tr.querySelectorAll('th,td'), function(td) {
return td.innerHTML;
});
});
return tableInfo;
};

const tableToCSV = function(table:HTMLTableElement) : string {
let data = "";
for (let i = 0; i < table.rows.length; i=i+1) {
const row = table.rows[i];
for (let j = 0; j < row.cells.length; j=j+1) {
const col = row.cells[j];
data = data + (j ? csvDelimiter : '') + fixCSVField(col.textContent.trim());
}
data = data + csvNewLine;
}
return data;
};

const createDownloadLink = function(anchor:HTMLAnchorElement, base64data:string, exporttype:string, filename:string) : boolean {
if (window.navigator.msSaveBlob) {
const blob = b64toBlob(base64data, exporttype);
window.navigator.msSaveBlob(blob, filename);
return false;
} else if (window.URL.createObjectURL) {
const blob = b64toBlob(base64data, exporttype);
anchor.href = window.URL.createObjectURL(blob);
} else {
anchor.download = filename;
anchor.href = "data:" + exporttype + ";base64," + base64data;
}

// Return true to allow the link to work
return true;
};

// String to ArrayBuffer
const s2ab = function (s:string): ArrayBuffer {
let buf = new ArrayBuffer(s.length);
let view = new Uint8Array(buf);
for (let i=0; i !== s.length; ++i) {
view[i] = s.charCodeAt(i) & 0xFF;
}
return buf;
};

const removeColumn = function(arr2d:any[][], colIndex:number) {
for (let i = 0; i < arr2d.length; i++) {
const row = arr2d[i];
row.splice(colIndex, 1);
}
};

/*
ExcellentExport.convert(options, sheets);
Expand Down Expand Up @@ -225,7 +88,7 @@ const ExcellentExport = function() {
// Select data source
let dataArray;
if (sheetConf.from && sheetConf.from.table) {
dataArray = tableToArray(getTable(sheetConf.from.table));
dataArray = utils.tableToArray(utils.getTable(sheetConf.from.table));
} else if(sheetConf.from && sheetConf.from.array) {
dataArray = sheetConf.from.array
} else {
Expand All @@ -242,10 +105,7 @@ const ExcellentExport = function() {
}
// Filter columns
if (sheetConf.removeColumns) {
const toRemove = sheetConf.removeColumns.sort().reverse();
toRemove.forEach(function(columnIndex) {
removeColumn(dataArray, columnIndex);
});
utils.removeColumns(dataArray, sheetConf.removeColumns);
}

// Convert data, by value
Expand All @@ -272,15 +132,15 @@ const ExcellentExport = function() {

const wbOut:string = XLSX.write(workbook, {bookType: options.format, bookSST:true, type: 'binary'});
try {
const blob = new Blob([s2ab(wbOut)], { type: "application/octet-stream" });
const blob = new Blob([utils.string2ArrayBuffer(wbOut)], { type: "application/octet-stream" });
const filename = (options.filename || 'download') + '.' + options.format;
// Support for IE.
if (window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blob, filename);
return false;
}
if (options.anchor) {
const anchor = getAnchor(options.anchor);
const anchor = utils.getAnchor(options.anchor);
anchor.href = window.URL.createObjectURL(blob);
anchor.download = filename;
} else if (options.openAsDownload) {
Expand All @@ -306,25 +166,28 @@ const ExcellentExport = function() {
return version;
},
excel: function(anchor:(HTMLAnchorElement|string), table:HTMLTableElement, name:string) {
table = getTable(table);
anchor = getAnchor(anchor);
table = utils.getTable(table);
anchor = utils.getAnchor(anchor);
const ctx = {worksheet: name || 'Worksheet', table: table.innerHTML};
const b64 = base64(format(template.excel, ctx));
return createDownloadLink(anchor, b64, 'application/vnd.ms-excel','export.xls');
const b64 = utils.base64(utils.format(utils.templates.excel, ctx));
return utils.createDownloadLink(anchor, b64, 'application/vnd.ms-excel','export.xls');
},
csv: function(anchor:(HTMLAnchorElement|string), table:HTMLTableElement, delimiter?:string, newLine?:string) {
let csvDelimiter = ",";
let csvNewLine = "\r\n";

if (delimiter !== undefined && delimiter) {
csvDelimiter = delimiter;
}
if (newLine !== undefined && newLine) {
csvNewLine = newLine;
}

table = getTable(table);
anchor = getAnchor(anchor);
const csvData = "\uFEFF" + tableToCSV(table);
const b64 = base64(csvData);
return createDownloadLink(anchor, b64, 'application/csv', 'export.csv');
table = utils.getTable(table);
anchor = utils.getAnchor(anchor);
const csvData = "\uFEFF" + utils.tableToCSV(table, csvDelimiter, csvNewLine);
const b64 = utils.base64(csvData);
return utils.createDownloadLink(anchor, b64, 'application/csv', 'export.csv');
},
convert: function(options:ConvertOptions, sheets:SheetOptions[]) {
return convert(options, sheets);
Expand Down
141 changes: 141 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

export const b64toBlob = function (b64Data:string, contentType:string, sliceSize?:number): Blob {
// function taken from http://stackoverflow.com/a/16245768/2591950
// author Jeremy Banks http://stackoverflow.com/users/1114/jeremy-banks
contentType = contentType || '';
sliceSize = sliceSize || 512;

const byteCharacters = atob(b64Data);
const byteArrays = [];

for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);

const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i = i + 1) {
byteNumbers[i] = slice.charCodeAt(i);
}

const byteArray = new Uint8Array(byteNumbers);

byteArrays.push(byteArray);
}

return new Blob(byteArrays, {
type: contentType
});
};

export const templates = {excel: '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>{table}</table></body></html>'};

/**
* Convert a string to Base64.
*/
export const base64 = function(s:string) : string {
return btoa(unescape(encodeURIComponent(s)));
};

export const format = function(s:string, context:any) : string {
return s.replace(new RegExp("{(\\w+)}", "g"), function(m, p) {
return context[p];
});
};

/**
* Get element by ID.
* @param {*} element
*/
export const getTable = function(element :(HTMLTableElement|string)) : HTMLTableElement {
if (typeof element === 'string') {
return document.getElementById(element) as HTMLTableElement;
}
return element;
};

/**
* Get element by ID.
* @param {*} element
*/
export const getAnchor = function(element :(HTMLAnchorElement|string)) : HTMLAnchorElement {
if (typeof element === 'string') {
return document.getElementById(element) as HTMLAnchorElement;
}
return element;
};

/**
* Encode a value for CSV.
* @param {*} value
*/
const fixCSVField = function(value:string, csvDelimiter:string) {
let fixedValue = value;
const addQuotes = (value.indexOf(csvDelimiter) !== -1) || (value.indexOf('\r') !== -1) || (value.indexOf('\n') !== -1);
const replaceDoubleQuotes = (value.indexOf('"') !== -1);

if (replaceDoubleQuotes) {
fixedValue = fixedValue.replace(/"/g, '""');
}
if (addQuotes || replaceDoubleQuotes) {
fixedValue = '"' + fixedValue + '"';
}

return fixedValue;
};

export const tableToArray = function(table:HTMLTableElement) : any[][] {
let tableInfo = Array.prototype.map.call(table.querySelectorAll('tr'), function(tr) {
return Array.prototype.map.call(tr.querySelectorAll('th,td'), function(td) {
return td.innerHTML;
});
});
return tableInfo;
};

export const tableToCSV = function(table:HTMLTableElement, csvDelimiter:string = ',', csvNewLine:string = '\n') : string {
let data = "";
for (let i = 0; i < table.rows.length; i=i+1) {
const row = table.rows[i];
for (let j = 0; j < row.cells.length; j=j+1) {
const col = row.cells[j];
data = data + (j ? csvDelimiter : '') + fixCSVField(col.textContent.trim(), csvDelimiter);
}
data = data + csvNewLine;
}
return data;
};

export const createDownloadLink = function(anchor:HTMLAnchorElement, base64data:string, exporttype:string, filename:string) : boolean {
if (window.navigator.msSaveBlob) {
const blob = b64toBlob(base64data, exporttype);
window.navigator.msSaveBlob(blob, filename);
return false;
} else if (window.URL.createObjectURL) {
const blob = b64toBlob(base64data, exporttype);
anchor.href = window.URL.createObjectURL(blob);
} else {
anchor.download = filename;
anchor.href = "data:" + exporttype + ";base64," + base64data;
}

// Return true to allow the link to work
return true;
};

// String to ArrayBuffer
export const string2ArrayBuffer = function (s:string): ArrayBuffer {
let buf = new ArrayBuffer(s.length);
let view = new Uint8Array(buf);
for (let i=0; i !== s.length; ++i) {
view[i] = s.charCodeAt(i) & 0xFF;
}
return buf;
};

export const removeColumns = function(dataArray:any[][], columnIndexes:number[]) {
const uniqueIndexes = [...new Set(columnIndexes)].sort().reverse();
uniqueIndexes.forEach(function(columnIndex) {
dataArray.forEach(function(row) {
row.splice(columnIndex, 1);
});
});
};
Loading

0 comments on commit 1d17cd3

Please sign in to comment.