Skip to content

Commit 63ad126

Browse files
committed
Added the ability to create files other than Excel files (xlsx, xls) (txt, csv, sql, log, ...)
1 parent ae86dc0 commit 63ad126

File tree

4 files changed

+107
-9
lines changed

4 files changed

+107
-9
lines changed

queries/queries-with-dynamic-variables.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<queries>
2-
<excel db="sampleDB" output="d:/temp/dynamic_variables_test_2024.xlsx" maxRows="20" style="modern" aggregateInfoTemplate="Related data {count} records {details}">
2+
<excel db="sampleDB" output="d:/temp/dynamic_variables_test_2024.log" maxRows="20" style="modern" aggregateInfoTemplate="Related data {count} records {details}">
33
<!-- Style template ID (default: default) -->
44
<!-- Available templates: default, modern, dark, colorful, minimal, business, premium -->
55
<!-- Can be overridden with individual style attributes if needed -->

queries/test-sheet-name-validation.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<queries>
3-
<excel db="sampleDB" output="d:/temp/sheet_name_validation_test.xlsx">
3+
<excel db="sampleDB" output="d:/temp/sheet_name_validation_test.xls">
44
</excel>
55

66
<!-- Valid sheet name -->

src/excel-generator.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const fs = require('fs');
2+
const path = require('path');
13
const ExcelJS = require('exceljs');
24
const excelStyleHelper = require('./excel-style-helper');
35
const FileUtils = require('./file-utils');
@@ -61,6 +63,92 @@ class ExcelGenerator {
6163
this.fileUtils = FileUtils;
6264
}
6365

66+
async exportPerSheetFiles(options) {
67+
const { sheets, outputPath, format } = options;
68+
const ext = FileUtils.getExtension(outputPath).toLowerCase();
69+
const dir = FileUtils.getDirname(outputPath);
70+
const base = FileUtils.getBasename(outputPath);
71+
const extNoDot = ext.startsWith('.') ? ext.slice(1) : ext;
72+
const targetDir = path.join(dir, `${base}_${extNoDot}`);
73+
if (!fs.existsSync(targetDir)) {
74+
fs.mkdirSync(targetDir, { recursive: true });
75+
}
76+
const delimiter = format === 'txt' ? '\t' : ',';
77+
const withBOM = true;
78+
const crlf = '\r\n';
79+
80+
function sanitizeFilename(name) {
81+
const replaced = String(name).replace(/[\\/:*?"<>|]/g, '_').trim();
82+
return replaced.substring(0, 100) || 'sheet';
83+
}
84+
function sanitizeIdentifier(name) {
85+
let id = String(name).replace(/[^A-Za-z0-9_]/g, '_');
86+
if (/^[0-9]/.test(id)) id = 'T_' + id;
87+
return id || 'T_SHEET';
88+
}
89+
function escapeCsv(val) {
90+
if (val === null || val === undefined) return '';
91+
const s = String(val);
92+
if (s.includes('"') || s.includes('\n') || s.includes('\r') || s.includes(delimiter)) {
93+
return '"' + s.replace(/"/g, '""') + '"';
94+
}
95+
return s;
96+
}
97+
function toSqlLiteral(val) {
98+
if (val === null || val === undefined) return 'NULL';
99+
if (typeof val === 'number') return String(val);
100+
if (typeof val === 'boolean') return val ? '1' : '0';
101+
if (val instanceof Date) return `'${val.toISOString().slice(0, 19).replace('T', ' ')}'`;
102+
const s = String(val).replace(/'/g, "''");
103+
return `'${s}'`;
104+
}
105+
106+
for (const sheetDef of sheets) {
107+
if (!this.isSheetEnabled(sheetDef)) continue;
108+
const baseName = sheetDef.originalName || sheetDef.name;
109+
const safeName = sanitizeFilename(baseName);
110+
const filePath = path.join(targetDir, `${safeName}${ext}`);
111+
FileUtils.ensureDirExists(filePath);
112+
const rows = Array.isArray(sheetDef.data) ? sheetDef.data : [];
113+
114+
if (format === 'sql') {
115+
const colsSet = new Set();
116+
rows.forEach(r => Object.keys(r || {}).forEach(k => colsSet.add(k)));
117+
const columns = Array.from(colsSet);
118+
const table = sanitizeIdentifier(baseName);
119+
const lines = [];
120+
if (columns.length === 0) {
121+
const single = `-- No data`;
122+
lines.push(single);
123+
} else {
124+
for (const r of rows) {
125+
const values = columns.map(c => toSqlLiteral(r && r[c] !== undefined ? r[c] : null)).join(', ');
126+
lines.push(`INSERT INTO ${table} (${columns.map(c => `[${c}]`).join(', ')}) VALUES (${values});`);
127+
}
128+
}
129+
const content = lines.join(crlf) + crlf;
130+
const data = withBOM ? Buffer.from('\ufeff' + content, 'utf8') : Buffer.from(content, 'utf8');
131+
fs.writeFileSync(filePath, data);
132+
console.log(`[WRITE] ${filePath}`);
133+
continue;
134+
}
135+
136+
const colsSet = new Set();
137+
rows.forEach(r => Object.keys(r || {}).forEach(k => colsSet.add(k)));
138+
const columns = Array.from(colsSet);
139+
const lines = [];
140+
if (columns.length > 0) lines.push(columns.join(delimiter));
141+
for (const r of rows) {
142+
const vals = columns.map(c => escapeCsv(r && r[c] !== undefined ? r[c] : ''));
143+
lines.push(vals.join(delimiter));
144+
}
145+
const content = lines.join(crlf) + crlf;
146+
const data = withBOM ? Buffer.from('\ufeff' + content, 'utf8') : Buffer.from(content, 'utf8');
147+
fs.writeFileSync(filePath, data);
148+
console.log(`[WRITE] ${filePath}`);
149+
}
150+
}
151+
64152
/**
65153
* 엑셀 파일 생성
66154
* @param {Object} options - 생성 옵션

src/index.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -382,14 +382,24 @@ async function main() {
382382
sheetIndex++;
383383
}
384384

385-
// 엑셀 파일 생성
385+
// 파일 생성 (엑셀 또는 per-sheet 파일들)
386386
if (processedSheets.length > 0) {
387-
await excelGenerator.generateExcel({
388-
sheets: processedSheets,
389-
outputPath: outFile,
390-
createdSheetNames: createdSheetNames,
391-
createdSheetCounts: createdSheetCounts
392-
});
387+
const ext = FileUtils.getExtension(outFile).toLowerCase();
388+
if (ext === '.xlsx' || ext === '.xls') {
389+
await excelGenerator.generateExcel({
390+
sheets: processedSheets,
391+
outputPath: outFile,
392+
createdSheetNames: createdSheetNames,
393+
createdSheetCounts: createdSheetCounts
394+
});
395+
} else {
396+
const format = (ext === '.csv') ? 'csv' : 'txt';
397+
await excelGenerator.exportPerSheetFiles({
398+
sheets: processedSheets,
399+
outputPath: outFile,
400+
format
401+
});
402+
}
393403
}
394404

395405
// 모든 DB 연결 정리

0 commit comments

Comments
 (0)