UNPKG

excel-csv-read-write

Version:
462 lines 21.9 kB
"use strict"; 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