Skip to content

Commit 5fbe6a3

Browse files
committed
List타입 변수값 배열형식으로 적용
1 parent 04fe7f4 commit 5fbe6a3

File tree

3 files changed

+125
-29
lines changed

3 files changed

+125
-29
lines changed

queries/queries-sample.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
"vars": {
2121
"startDate": "2024-01-01",
2222
"endDate": "2024-06-30",
23-
"regionList": "'서울','부산'"
23+
"regionList": ["서울", "부산"],
24+
"statusList": ["ACTIVE", "PENDING", "COMPLETED"],
25+
"categoryIds": [1, 2, 3, 5],
26+
"maxRows": 1000
2427
},
2528
"sheets": [
2629
{
@@ -38,6 +41,22 @@
3841
"aggregateColumn": "Region",
3942
"db": "erpDB",
4043
"query": "SELECT * FROM Customers WHERE region IN (${regionList})"
44+
},
45+
{
46+
"name": "배열변수_주문상태별_조회",
47+
"use": false,
48+
"maxRows": 800,
49+
"aggregateColumn": "OrderStatus",
50+
"db": "sampleDB",
51+
"query": "SELECT OrderNumber, OrderDate, OrderStatus, PaymentStatus, TotalAmount FROM Orders WHERE OrderDate >= '${startDate}' AND OrderDate <= '${endDate}' AND OrderStatus IN (${statusList})"
52+
},
53+
{
54+
"name": "배열변수_카테고리별_상품",
55+
"use": false,
56+
"maxRows": 600,
57+
"aggregateColumn": "CategoryName",
58+
"db": "sampleDB",
59+
"query": "SELECT p.ProductID, p.ProductName, c.CategoryName, p.UnitPrice FROM Products p INNER JOIN Categories c ON p.CategoryID = c.CategoryID WHERE p.CategoryID IN (${categoryIds})"
4160
}
4261
]
4362
}

queries/queries-sample.xml

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
<var name="envType">운영</var>
2323
<var name="startDate">2024-01-01</var>
2424
<var name="endDate">2024-06-30</var>
25-
<var name="regionList">'서울','부산'</var>
25+
<var name="regionList">["서울", "부산"]</var>
26+
<var name="statusList">["ACTIVE", "PENDING", "COMPLETED"]</var>
27+
<var name="categoryIds">[1, 2, 3, 5]</var>
28+
<var name="maxRows">1000</var>
2629
</vars>
2730
<sheet name="${envType}_주문_목록" use="true" aggregateColumn="결제방법" maxRows="10" db="sampleDB">
2831
<![CDATA[
@@ -89,4 +92,35 @@
8992
ORDER BY o.OrderDate DESC
9093
]]>
9194
</sheet>
95+
<sheet name="배열변수_주문상태별_조회" use="false" aggregateColumn="주문상태">
96+
<![CDATA[
97+
SELECT
98+
OrderNumber as 주문번호,
99+
FORMAT(OrderDate, 'yyyy-MM-dd') as 주문일,
100+
OrderStatus as 주문상태,
101+
PaymentStatus as 결제상태,
102+
FORMAT(TotalAmount, 'N0') as 총금액,
103+
PaymentMethod as 결제방법
104+
FROM SampleDB.dbo.Orders
105+
WHERE OrderDate >= '${startDate}'
106+
AND OrderDate <= '${endDate}'
107+
AND OrderStatus IN (${statusList})
108+
ORDER BY OrderDate DESC
109+
]]>
110+
</sheet>
111+
<sheet name="배열변수_카테고리별_상품" use="false" aggregateColumn="카테고리명">
112+
<![CDATA[
113+
SELECT
114+
p.ProductID as 상품ID,
115+
p.ProductName as 상품명,
116+
c.CategoryName as 카테고리명,
117+
FORMAT(p.UnitPrice, 'N0') as 단가,
118+
p.UnitsInStock as 재고수량
119+
FROM SampleDB.dbo.Products p
120+
INNER JOIN SampleDB.dbo.Categories c ON p.CategoryID = c.CategoryID
121+
WHERE p.CategoryID IN (${categoryIds})
122+
AND p.Discontinued = 0
123+
ORDER BY c.CategoryName, p.ProductName
124+
]]>
125+
</sheet>
92126
</queries>

src/index.js

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,25 @@ const xml2js = require('xml2js');
88
const excelStyleHelper = require('./excel-style-helper');
99

1010
function substituteVars(str, vars) {
11-
return str.replace(/\$\{(\w+)\}/g, (_, v) => vars[v] ?? '');
11+
return str.replace(/\$\{(\w+)\}/g, (_, v) => {
12+
const value = vars[v];
13+
if (value === undefined || value === null) return '';
14+
15+
// 배열 타입인 경우 IN절 처리
16+
if (Array.isArray(value)) {
17+
// 문자열 배열인 경우 따옴표로 감싸기
18+
const inClause = value.map(val => {
19+
if (typeof val === 'string') {
20+
return `'${val.replace(/'/g, "''")}'`; // SQL 인젝션 방지를 위한 따옴표 이스케이핑
21+
}
22+
return val;
23+
}).join(', ');
24+
return inClause;
25+
} else {
26+
// 기존 방식: 단일 값 치환
27+
return value;
28+
}
29+
});
1230
}
1331

1432
async function loadQueriesFromXML(xmlPath) {
@@ -20,9 +38,34 @@ async function loadQueriesFromXML(xmlPath) {
2038
if (parsed.queries.vars && parsed.queries.vars[0] && parsed.queries.vars[0].var) {
2139
for (const v of parsed.queries.vars[0].var) {
2240
if (v.$ && v.$.name && v._) {
23-
globalVars[v.$.name] = v._.toString();
41+
let value = v._.toString();
42+
// 배열 형태 문자열을 실제 배열로 변환
43+
if (value.startsWith('[') && value.endsWith(']')) {
44+
try {
45+
value = JSON.parse(value);
46+
} catch (e) {
47+
// JSON 파싱 실패 시 문자열 그대로 사용
48+
}
49+
}
50+
// boolean 값 처리
51+
if (value === 'true') value = true;
52+
if (value === 'false') value = false;
53+
// 숫자 값 처리
54+
if (!isNaN(value) && !isNaN(parseFloat(value)) && typeof value === 'string') {
55+
value = parseFloat(value);
56+
}
57+
globalVars[v.$.name] = value;
2458
} else if (v.$ && v.$.name && typeof v === 'string') {
25-
globalVars[v.$.name] = v;
59+
let value = v;
60+
// 배열 형태 문자열을 실제 배열로 변환
61+
if (value.startsWith('[') && value.endsWith(']')) {
62+
try {
63+
value = JSON.parse(value);
64+
} catch (e) {
65+
// JSON 파싱 실패 시 문자열 그대로 사용
66+
}
67+
}
68+
globalVars[v.$.name] = value;
2669
}
2770
}
2871
}
@@ -274,11 +317,11 @@ async function main() {
274317
async function getDbPool(dbKey) {
275318
if (!dbPools[dbKey]) {
276319
if (!configObj.dbs[dbKey]) {
277-
throw new Error(`DB 접속 ID를 찾을 수 없습니다: ${dbKey}`);
278-
}
320+
throw new Error(`DB 접속 ID를 찾을 수 없습니다: ${dbKey}`);
321+
}
279322
console.log(`[DB] ${dbKey} 데이터베이스에 연결 중...`);
280323
const pool = new mssql.ConnectionPool(configObj.dbs[dbKey]);
281-
await pool.connect();
324+
await pool.connect();
282325
dbPools[dbKey] = pool;
283326
console.log(`[DB] ${dbKey} 데이터베이스 연결 완료`);
284327
}
@@ -434,26 +477,26 @@ async function main() {
434477
console.log(`[목차] 내용 채우기 완료 (총 ${createdSheetNames.length}개 시트)`);
435478

436479
if (createSeparateToc) {
437-
// 별도 목차 엑셀 파일 생성
438-
const tocWb = new ExcelJS.Workbook();
439-
const tocOnly = tocWb.addWorksheet('목차');
440-
tocOnly.addRow(['No', 'Sheet Name', 'Data Count']);
441-
createdSheetNames.forEach((obj, idx) => {
442-
const row = tocOnly.addRow([idx + 1, obj.displayName, createdSheetCounts[idx]]);
443-
row.getCell(2).font = { color: { argb: '0563C1' }, underline: true };
444-
row.getCell(3).font = { color: { argb: '0563C1' }, underline: true };
445-
});
446-
tocOnly.getRow(1).font = { bold: true };
447-
tocOnly.columns = [
448-
{ header: 'No', key: 'no', width: 6 },
449-
{ header: 'Sheet Name', key: 'name', width: 30 },
450-
{ header: 'Data Count', key: 'count', width: 12 }
451-
];
452-
const tocExt = path.extname(outFile);
453-
const tocBase = outFile.slice(0, -tocExt.length);
454-
const tocFile = `${tocBase}_목차_${getNowTimestampStr()}${tocExt}`;
455-
await tocWb.xlsx.writeFile(tocFile);
456-
console.log(`[목차] 별도 엑셀 파일 생성: ${tocFile}`);
480+
// 별도 목차 엑셀 파일 생성
481+
const tocWb = new ExcelJS.Workbook();
482+
const tocOnly = tocWb.addWorksheet('목차');
483+
tocOnly.addRow(['No', 'Sheet Name', 'Data Count']);
484+
createdSheetNames.forEach((obj, idx) => {
485+
const row = tocOnly.addRow([idx + 1, obj.displayName, createdSheetCounts[idx]]);
486+
row.getCell(2).font = { color: { argb: '0563C1' }, underline: true };
487+
row.getCell(3).font = { color: { argb: '0563C1' }, underline: true };
488+
});
489+
tocOnly.getRow(1).font = { bold: true };
490+
tocOnly.columns = [
491+
{ header: 'No', key: 'no', width: 6 },
492+
{ header: 'Sheet Name', key: 'name', width: 30 },
493+
{ header: 'Data Count', key: 'count', width: 12 }
494+
];
495+
const tocExt = path.extname(outFile);
496+
const tocBase = outFile.slice(0, -tocExt.length);
497+
const tocFile = `${tocBase}_목차_${getNowTimestampStr()}${tocExt}`;
498+
await tocWb.xlsx.writeFile(tocFile);
499+
console.log(`[목차] 별도 엑셀 파일 생성: ${tocFile}`);
457500
}
458501

459502
}
@@ -466,7 +509,7 @@ async function main() {
466509
// 모든 DB 연결 정리
467510
for (const [dbKey, pool] of Object.entries(dbPools)) {
468511
console.log(`[DB] ${dbKey} 데이터베이스 연결 종료`);
469-
await pool.close();
512+
await pool.close();
470513
}
471514
}
472515

0 commit comments

Comments
 (0)