excel-csv-read-write
Version:
Excel and CSV read write utility.
462 lines • 21.9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createHeaderFromInstances = exports.getValuesArray = exports.getHeaders2 = exports.getHeaders = exports.toBoolean = exports.date2Sn = exports.dateFromSn = exports.json2excelBlob = exports.json2workbook = exports.toFileAsync = exports.createWorkbook = exports.json2excel = exports.csvStream2json = exports.csv2json = exports.excelBuffer2json = exports.excelFromArrayBuffer = exports.data2json = exports.excelData2json = exports.excelStream2json = exports.excel2json = exports.csv2json2 = exports.excel2json2 = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const csvtojson_1 = __importDefault(require("csvtojson"));
const iconv = __importStar(require("iconv-lite"));
const JSZip = __importStar(require("jszip"));
const xlsx_populate_1 = __importDefault(require("xlsx-populate"));
const logger_1 = require("./logger");
// const XlsxPopulate = require('xlsx-populate')
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const logger = (0, logger_1.getLogger)('main');
const excel2json2 = async ({ filePath, sheetName = 'Sheet1', formatFunc, option, }) => {
return await (0, exports.excel2json)(filePath, sheetName, formatFunc, option);
};
exports.excel2json2 = excel2json2;
const csv2json2 = async ({ filePath, encoding = 'Shift_JIS' }) => {
return await (0, exports.csv2json)(filePath, encoding);
};
exports.csv2json2 = csv2json2;
/**
* Excelファイルを読み込み、各行をデータとして配列で返すメソッド。
* @param path Excelファイルパス
* @param sheet シート名
* @param sheetName
* @param formatFunc フォーマット関数。instanceは各行データが入ってくるので、任意に整形して返せばよい
*/
const excel2json = async (inputFullPath, sheetName = 'Sheet1', formatFunc, option) => {
// const promise = new JSZip.external.Promise<Buffer>((resolve, reject) => {
// fs.readFile(inputFullPath, (err, data) => (err ? reject(err) : resolve(data)))
// }).then(async (data: Buffer) => await XlsxPopulate.fromDataAsync(data))
// return excelData2json(await promise, sheetName, formatFunc)
const stream = fs.createReadStream(inputFullPath);
return await (0, exports.excelStream2json)(stream, sheetName, formatFunc, option);
};
exports.excel2json = excel2json;
/**
* Excelファイルを読み込み、各行をデータとして配列で返すメソッド。
* @param stream
* @param sheetName
* @param format_func フォーマット関数。instanceは各行データが入ってくるので、任意に整形して返せばよい
*/
const excelStream2json = async (stream, sheetName = 'Sheet1', formatFunc, option) => {
// cf:https://qiita.com/masakura/items/5683e8e3e655bfda6756
const promise = new JSZip.external.Promise((resolve, _) => {
const buffers = [];
stream.on('data', (data) => buffers.push(data)).on('end', () => resolve(Buffer.concat(buffers)));
}).then(async (buf) => await xlsx_populate_1.default.fromDataAsync(buf));
return (0, exports.excelData2json)(await promise, sheetName, formatFunc, option);
};
exports.excelStream2json = excelStream2json;
/**
* Excelファイルを読み込み、各行をデータとして配列で返すメソッド。
* @param stream
* @param sheetName
* @param formatFunc フォーマット関数。instanceは各行データが入ってくるので、任意に整形して返せばよい
*/
const excelData2json = (workbook, sheetName = 'Sheet1', formatFunc, option) => {
// console.log(headings.length)
const valuesArray = (0, exports.getValuesArray)(workbook, sheetName);
return (0, exports.data2json)(valuesArray, formatFunc, option);
};
exports.excelData2json = excelData2json;
const data2json = (allValuesArray, formatFunc, option) => {
const tmpAllValuesArray = [...[...allValuesArray]];
const startIndex = option?.startIndex ?? 0;
const valuesArray = tmpAllValuesArray.splice(startIndex, Number.MAX_VALUE);
// option なければヘッダ使うのでtrue
// ある場合は、
// useHeader =true か、useHeaderがない場合はtrue
// もしくは
// key !==='columnIndex' か、がないばあいはtrue
const useHeader = option?.useHeader ?? true; // optionがなければtrue/ あればuseHeader値があるかみて返す、useHeader値がなかったらtrue
// console.log(`useHeader: ${String(useHeader)}`)
// header処理
const headings = useHeader ? (0, exports.getHeaders2)(valuesArray) : [];
if (useHeader) {
valuesArray.shift();
}
// header処理
// useHeaderの時しか、使わないけど。
const columnStartIndex = option?.columnStartIndex ?? 0;
const columnEndIndex = option?.columnEndIndex ?? Number.MAX_VALUE;
const headerLogic = (box, column, index) => {
// 列単位で処理してきて、ヘッダの名前で代入する。
box[headings[index]] = column;
return box;
};
const indexLogic = (box, column, index) => {
// 列単位で処理してきて、ヘッダの名前で代入する。
if (index >= columnStartIndex && index <= columnEndIndex) {
box[index] = column;
}
return box;
};
const reduceLogic = useHeader ? headerLogic : indexLogic;
const instances = valuesArray.map((values) => {
return values.reduce(reduceLogic, {});
});
if (formatFunc) {
return instances.map((instance) => formatFunc(instance));
}
return instances;
};
exports.data2json = data2json;
/**
*
* @param buffer
* @returns
*/
const excelFromArrayBuffer = async (buffer) => {
const uint8 = new Uint8Array(buffer);
return await xlsx_populate_1.default.fromDataAsync(uint8);
};
exports.excelFromArrayBuffer = excelFromArrayBuffer;
/**
* ExcelデータをArrayBufferとして受け取って、各行をデータとして配列で返すメソッド。
* @param buffer
* @param sheetName
* @param formatFunc
* @param formatFunc フォーマット関数。instanceは各行データが入ってくるので、任意に整形して返せばよい
*/
const excelBuffer2json = async (buffer, sheetName = 'Sheet1', formatFunc, option) => {
const workbook = await (0, exports.excelFromArrayBuffer)(buffer);
return (0, exports.excelData2json)(workbook, sheetName, formatFunc, option);
};
exports.excelBuffer2json = excelBuffer2json;
/**
* 指定したパスのcsvファイルをロードして、JSONオブジェクトとしてparseする。
* 全行読み込んだら完了する Promise を返す。
* @param filePath
*/
const csv2json = async (filePath, encoding = 'Shift_JIS') => {
return await (0, exports.csvStream2json)(fs.createReadStream(filePath), encoding);
};
exports.csv2json = csv2json;
/**
* 指定したパスのcsvファイルをロードして、JSONオブジェクトとしてparseする。
* 全行読み込んだら完了する Promise を返す。
* @param fs
*/
const csvStream2json = async (stream, encoding = 'Shift_JIS') => {
return await new Promise((resolve, reject) => {
const datas = [];
void stream
.pipe(iconv.decodeStream(encoding))
.pipe(iconv.encodeStream('utf-8'))
.pipe((0, csvtojson_1.default)()
.on('data', (data) => datas.push(Buffer.isBuffer(data) ? JSON.parse(data.toString()) : JSON.parse(data)))
// .on('done', (error) => (error ? reject(error) : resolve(datas)))
.on('error', (error) => reject(error)))
.on('end', () => resolve(datas));
});
};
exports.csvStream2json = csvStream2json;
/**
* 引数のJSON配列を、指定したテンプレートを用いて、指定したファイルに出力します。
* @param instances JSON配列
* @param outputFullPath 出力Excelのパス
* @param templateFullPath 元にするテンプレートExcelのパス
* @param sheetName テンプレートExcelのシート名(シート名で出力する)
* @param applyStyles 出力時のExcelを書式フォーマットしたい場合に使用する。
*/
const json2excel = async (instances, outputFullPath, templateFullPath = '', sheetName = 'Sheet1', converters, applyStyles) => {
logger.debug(`template path: ${templateFullPath}`);
// console.log(instances[0])
// console.table(instances)
let headings = []; // ヘッダ名の配列
let workbook;
const fileIsNew = templateFullPath === ''; // templateが指定されない場合新規(fileIsNew = true)、そうでない場合テンプレファイルに出力
if (!fileIsNew) {
// 指定された場合は、一行目の文字列群を使ってプロパティを作成する
workbook = await xlsx_populate_1.default.fromFileAsync(templateFullPath);
headings = (0, exports.getHeaders)(workbook, sheetName);
}
else {
// templateが指定されない場合は、空ファイルをつくり、オブジェクトのプロパティでダンプする。
workbook = await xlsx_populate_1.default.fromBlankAsync();
if (instances.length > 0) {
headings = (0, exports.createHeaderFromInstances)(instances);
}
}
if (instances.length > 0) {
const csvArrays = createCsvArrays(headings, instances, converters);
// console.table(csvArrays)
const rowCount = instances.length;
const columnCount = headings.length;
const sheet = workbook.sheet(sheetName) ?? workbook.addSheet(sheetName);
if (!fileIsNew && sheet.usedRange()) {
sheet.usedRange()?.clear(); // Excel上のデータを削除して。
}
sheet.cell('A1').value(csvArrays);
// データがあるところには罫線を引く(細いヤツ)
const startCell = sheet.cell('A1');
const endCell = startCell.relativeCell(rowCount, columnCount - 1);
sheet.range(startCell, endCell).style('border', {
top: { style: 'hair' },
left: { style: 'hair' },
bottom: { style: 'hair' },
right: { style: 'hair' },
});
// よくある整形パタン。
// sheet.range(`C2:C${rowCount + 1}`).style('numberFormat', '@') // 書式: 文字(コレをやらないと、見かけ上文字だが、F2で抜けると数字になっちゃう)
// sheet.range(`E2:F${rowCount + 1}`).style('numberFormat', 'yyyy/mm/dd') // 書式: 日付
// sheet.range(`H2:H${rowCount + 1}`).style('numberFormat', 'yyyy/mm/dd hh:mm') // 書式: 日付+時刻
if (applyStyles) {
applyStyles(instances, workbook, sheetName);
}
}
logger.debug(outputFullPath);
await workbook.toFileAsync(outputFullPath);
return toFullPath(outputFullPath);
};
exports.json2excel = json2excel;
const createWorkbook = async (path) => {
return (path != null) ? await xlsx_populate_1.default.fromFileAsync(path) : await xlsx_populate_1.default.fromBlankAsync();
};
exports.createWorkbook = createWorkbook;
const toFileAsync = async (workbook, path) => {
logger.debug(path);
return await workbook.toFileAsync(path);
// return toFullPath(path)
};
exports.toFileAsync = toFileAsync;
/**
* 引数のJSON配列を、指定したテンプレートを用いて、指定したファイルに出力します。
* @param instances JSON配列
* @param templateFullPath 元にするテンプレートExcelのパス
* @param sheetName テンプレートExcelのシート名(シート名で出力する)
* @param applyStyles 出力時のExcelを書式フォーマットしたい場合に使用する。
*/
const json2workbook = ({ instances, workbook, sheetName = 'Sheet1', converters, headerConverter, applyStyles, columnSortOrder, }) => {
// logger.debug(`template path: ${templateFullPath}`)
// console.log(instances[0])
// console.table(instances)
let headings = []; // ヘッダ名の配列
// let workbook: XlsxPopulate.Workbook
// const fileIsNew: boolean = templateFullPath === '' // templateが指定されない場合新規(fileIsNew = true)、そうでない場合テンプレファイルに出力
// if (fs.existsSync(outputFullPath)) {
// workbook = await XlsxPopulate.fromFileAsync(outputFullPath)
// if (instances.length > 0) {
// headings = createHeaderFromInstances(instances)
// }
// } else
// if (!fileIsNew) {
// 指定された場合は、一行目の文字列群を使ってプロパティを作成する
// workbook = await XlsxPopulate.fromFileAsync(templateFullPath)
// headings = getHeaders(workbook, sheetName)
// } else {
// templateが指定されない場合は、空ファイルをつくり、オブジェクトのプロパティでダンプする。
// if (!workbook) {
// workbook = await XlsxPopulate.fromBlankAsync()
// }
// }
if (instances.length > 0) {
headings = (0, exports.createHeaderFromInstances)(instances);
const csvArrays = createCsvArrays(headings, instances, converters, headerConverter, columnSortOrder);
// console.table(csvArrays)
const rowCount = instances.length;
const columnCount = headings.length;
const sheet = workbook.sheet(sheetName) ?? workbook.addSheet(sheetName);
// if (!fileIsNew && sheet.usedRange()) {
// sheet.usedRange()?.clear() // Excel上のデータを削除して。
// }
sheet.cell('A1').value(csvArrays);
// データがあるところには罫線を引く(細いヤツ)
const startCell = sheet.cell('A1');
const endCell = startCell.relativeCell(rowCount, columnCount - 1);
sheet.range(startCell, endCell).style('border', {
top: { style: 'hair' },
left: { style: 'hair' },
bottom: { style: 'hair' },
right: { style: 'hair' },
});
// よくある整形パタン。
// sheet.range(`C2:C${rowCount + 1}`).style('numberFormat', '@') // 書式: 文字(コレをやらないと、見かけ上文字だが、F2で抜けると数字になっちゃう)
// sheet.range(`E2:F${rowCount + 1}`).style('numberFormat', 'yyyy/mm/dd') // 書式: 日付
// sheet.range(`H2:H${rowCount + 1}`).style('numberFormat', 'yyyy/mm/dd hh:mm') // 書式: 日付+時刻
if (applyStyles) {
applyStyles(instances, workbook, sheetName);
}
}
// logger.debug(outputFullPath)
// await workbook.toFileAsync(outputFullPath)
//
// return toFullPath(outputFullPath)
return workbook;
};
exports.json2workbook = json2workbook;
/**
* 引数のJSON配列を、指定したテンプレートを用いて、指定したファイルに出力します。
* @param instances JSON配列
* @param sheetName テンプレートExcelのシート名(シート名で出力する)
* @param applyStyles 出力時のExcelを書式フォーマットしたい場合に使用する。
*/
const json2excelBlob = async (instances, sheetName = 'Sheet1', converters, applyStyles) => {
let headings = []; // ヘッダ名の配列
const workbook = await xlsx_populate_1.default.fromBlankAsync();
if (instances.length > 0) {
headings = (0, exports.createHeaderFromInstances)(instances);
}
if (instances.length > 0) {
const csvArrays = createCsvArrays(headings, instances, converters);
// console.table(csvArrays)
const rowCount = instances.length;
const columnCount = headings.length;
const sheet = workbook.sheet(sheetName);
sheet.cell('A1').value(csvArrays);
// データがあるところには罫線を引く(細いヤツ)
const startCell = sheet.cell('A1');
const endCell = startCell.relativeCell(rowCount, columnCount - 1);
sheet.range(startCell, endCell).style('border', {
top: { style: 'hair' },
left: { style: 'hair' },
bottom: { style: 'hair' },
right: { style: 'hair' },
});
// よくある整形パタン。
// sheet.range(`C2:C${rowCount + 1}`).style('numberFormat', '@') // 書式: 文字(コレをやらないと、見かけ上文字だが、F2で抜けると数字になっちゃう)
// sheet.range(`E2:F${rowCount + 1}`).style('numberFormat', 'yyyy/mm/dd') // 書式: 日付
// sheet.range(`H2:H${rowCount + 1}`).style('numberFormat', 'yyyy/mm/dd hh:mm') // 書式: 日付+時刻
if (applyStyles) {
applyStyles(instances, workbook, sheetName);
}
}
const blob = await workbook.outputAsync();
return blob;
};
exports.json2excelBlob = json2excelBlob;
const toFullPath = (str) => (path.isAbsolute(str) ? str : path.join(path.resolve(''), str));
// 自前実装
const createCsvArrays = (headings, instances, converters, headerConverter, columnSortOrder) => {
const csvArrays = instances.map((tmpInstance) => {
const instance = tmpInstance;
// console.log(instance)
if (columnSortOrder) {
headings.sort(columnSortOrder);
}
const csvArray = headings.reduce((box, header) => {
// console.log(`${instance[header]}: ${instance[header] instanceof Object}`)
// console.log(converters)
// if (converters && converters[header]) {
if (converters?.[header]) {
// header名に合致するConverterがある場合はそれ優先で適用
const converter = converters[header];
box.push(converter(instance[header]));
}
else if (instance[header] instanceof Object) {
// Converterがない場合は、文字列に変換
box.push(JSON.stringify(instance[header]));
}
else {
box.push(instance[header]); // あとはそのまま
}
return box;
}, []);
return csvArray;
});
headerConverter ? csvArrays.unshift(headerConverter(headings)) : csvArrays.unshift(headings);
return csvArrays;
};
/**
* Excelのシリアル値を、Dateへ変換します。
* @param serialNumber シリアル値
*/
const dateFromSn = (serialNumber) => {
return xlsx_populate_1.default.numberToDate(serialNumber);
};
exports.dateFromSn = dateFromSn;
const date2Sn = (date) => {
return xlsx_populate_1.default.dateToNumber(date);
};
exports.date2Sn = date2Sn;
const toBoolean = function (boolStr) {
if (typeof boolStr === 'boolean') {
return boolStr;
}
return boolStr.toLowerCase() === 'true';
};
exports.toBoolean = toBoolean;
// XlsxPopulate
const getHeaders = (workbook, sheetName, option = { startIndex: 0 }) => {
const sheet = workbook.sheet(sheetName);
const tmp = option.startIndex ?? 0;
if (sheet.usedRange()) {
const headers = sheet.usedRange()?.value()[tmp];
return headers;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
// return sheet.usedRange()!.value().shift() as string[]
}
return [];
};
exports.getHeaders = getHeaders;
/**
* 二次元配列の先頭行をとって、それをヘッダ列として返す
* @param instanceArray
* @returns
*/
const getHeaders2 = (instanceArray) => instanceArray[0].map(cell => typeof cell === 'string' ? cell.trim() : cell);
exports.getHeaders2 = getHeaders2;
// XlsxPopulate
const getValuesArray = (workbook, sheetName) => {
const sheet = workbook.sheet(sheetName);
if (sheet.usedRange()) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return sheet.usedRange().value();
}
return new Array([]);
};
exports.getValuesArray = getValuesArray;
const createHeaderFromInstances = (instances) => {
// const headings = Object.keys(instances[0] as CSVData)
const headings = instances.reduce((box, current) => {
const currentKeys = Object.keys(current);
for (const currentKey of currentKeys) {
if (!box.includes(currentKey)) {
box.push(currentKey);
}
}
return box;
}, []);
return headings;
};
exports.createHeaderFromInstances = createHeaderFromInstances;
//# sourceMappingURL=commonUtils.js.map