다른 명령
좋습니다. AutoIt이 로컬 HTTP 서버 + SQLite 백엔드 역할을 하고, 브라우저에서 jspreadsheet-ce(엑셀형 그리드)로 화면을 띄우는 구조로 만들어 드릴게요.
구조
project/
├── main.au3 ← HTTP 서버 + SQLite
├── sqlite3.dll ← SQLite UDF용 (AutoIt SQLite UDF에 포함)
└── web/
└── index.html ← 엑셀형 UI
동작: AutoIt이 127.0.0.1:8765 에서 listen → 자동으로 기본 브라우저 오픈 → 그리드에서 입력/수정 → /api/save, /api/load 로 SQLite와 통신.
main.au3
#include <SQLite.au3>
#include <SQLite.dll.au3>
Opt("TCPTimeout", 100)
OnAutoItExitRegister("OnExit")
Global $g_sDBPath = @ScriptDir & "\sheet.db"
Global $g_sWebRoot = @ScriptDir & "\web"
Global $g_iPort = 8765
Global $g_hDB
_SQLite_Startup()
If @error Then Exit MsgBox(16, "Err", "SQLite 시작 실패")
$g_hDB = _SQLite_Open($g_sDBPath)
_SQLite_Exec($g_hDB, _
"CREATE TABLE IF NOT EXISTS sheet_data(" & _
" row_idx INTEGER, col_idx INTEGER, value TEXT," & _
" PRIMARY KEY(row_idx, col_idx));")
TCPStartup()
Local $iListen = TCPListen("127.0.0.1", $g_iPort)
If $iListen = -1 Then Exit MsgBox(16, "Err", "포트 사용중: " & $g_iPort)
ShellExecute("http://127.0.0.1:" & $g_iPort & "/")
While 1
Local $iSock = TCPAccept($iListen)
If $iSock >= 0 Then _Handle($iSock)
Sleep(10)
WEnd
Func _Handle($iSock)
Local $sReq = "", $iWait = 0
While $iWait < 300
Local $c = TCPRecv($iSock, 8192)
If $c <> "" Then
$sReq &= $c
If StringInStr($sReq, @CRLF & @CRLF) Then ExitLoop
EndIf
Sleep(5)
$iWait += 1
WEnd
Local $iSplit = StringInStr($sReq, @CRLF & @CRLF)
Local $sHead = StringLeft($sReq, $iSplit - 1)
Local $sBody = StringMid($sReq, $iSplit + 3)
Local $aCL = StringRegExp($sHead, "(?i)Content-Length:\s*(\d+)", 1)
Local $iCL = (IsArray($aCL)) ? Int($aCL[0]) : 0
While StringLen(BinaryToString(StringToBinary($sBody, 4), 4)) < $iCL
Local $c = TCPRecv($iSock, 8192)
If $c <> "" Then $sBody &= $c
Sleep(5)
WEnd
Local $aFirst = StringRegExp($sHead, "^(\S+)\s+(\S+)", 1)
If Not IsArray($aFirst) Then
TCPCloseSocket($iSock)
Return
EndIf
Local $sMethod = $aFirst[0], $sPath = $aFirst[1]
Switch $sPath
Case "/", "/index.html"
_SendFile($iSock, $g_sWebRoot & "\index.html", "text/html; charset=utf-8")
Case "/api/load"
_ApiLoad($iSock)
Case "/api/save"
_ApiSave($iSock, $sBody)
Case "/api/clear"
_SQLite_Exec($g_hDB, "DELETE FROM sheet_data;")
_SendJson($iSock, '{"ok":true}')
Case Else
_Send404($iSock)
EndSwitch
TCPCloseSocket($iSock)
EndFunc
Func _ApiLoad($iSock)
Local $aRes, $iRows, $iCols
_SQLite_GetTable2d($g_hDB, _
"SELECT row_idx,col_idx,value FROM sheet_data ORDER BY row_idx,col_idx;", _
$aRes, $iRows, $iCols)
Local $s = '{"cells":['
For $i = 1 To $iRows
If $i > 1 Then $s &= ","
$s &= '{"r":' & $aRes[$i][0] & ',"c":' & $aRes[$i][1] & _
',"v":' & _JsonStr($aRes[$i][2]) & '}'
Next
$s &= ']}'
_SendJson($iSock, $s)
EndFunc
Func _ApiSave($iSock, $sBody)
_SQLite_Exec($g_hDB, "BEGIN;")
_SQLite_Exec($g_hDB, "DELETE FROM sheet_data;")
Local $aM = StringRegExp($sBody, _
'\{"r":(\d+),"c":(\d+),"v":"((?:\\.|[^"\\])*)"\}', 3)
If Not @error Then
Local $sStmt
For $i = 0 To UBound($aM) - 1 Step 3
Local $v = _JsonUnesc($aM[$i + 2])
$v = StringReplace($v, "'", "''")
_SQLite_Exec($g_hDB, _
"INSERT INTO sheet_data VALUES(" & _
$aM[$i] & "," & $aM[$i+1] & ",'" & $v & "');")
Next
EndIf
_SQLite_Exec($g_hDB, "COMMIT;")
_SendJson($iSock, '{"ok":true}')
EndFunc
Func _SendFile($iSock, $sFile, $sCT)
If Not FileExists($sFile) Then Return _Send404($iSock)
Local $h = FileOpen($sFile, 16)
Local $b = FileRead($h)
FileClose($h)
Local $sH = "HTTP/1.1 200 OK" & @CRLF & _
"Content-Type: " & $sCT & @CRLF & _
"Content-Length: " & BinaryLen($b) & @CRLF & _
"Connection: close" & @CRLF & @CRLF
TCPSend($iSock, $sH)
TCPSend($iSock, $b)
EndFunc
Func _SendJson($iSock, $sJson)
Local $b = StringToBinary($sJson, 4)
Local $sH = "HTTP/1.1 200 OK" & @CRLF & _
"Content-Type: application/json; charset=utf-8" & @CRLF & _
"Content-Length: " & BinaryLen($b) & @CRLF & _
"Connection: close" & @CRLF & @CRLF
TCPSend($iSock, $sH)
TCPSend($iSock, $b)
EndFunc
Func _Send404($iSock)
TCPSend($iSock, "HTTP/1.1 404 Not Found" & @CRLF & "Content-Length: 0" & @CRLF & @CRLF)
EndFunc
Func _JsonStr($s)
$s = StringReplace($s, "\", "\\")
$s = StringReplace($s, '"', '\"')
$s = StringReplace($s, @CR, "\r")
$s = StringReplace($s, @LF, "\n")
$s = StringReplace($s, @TAB, "\t")
Return '"' & $s & '"'
EndFunc
Func _JsonUnesc($s)
$s = StringReplace($s, "\n", @LF)
$s = StringReplace($s, "\r", @CR)
$s = StringReplace($s, "\t", @TAB)
$s = StringReplace($s, '\"', '"')
$s = StringReplace($s, "\\", "\")
Return $s
EndFunc
Func OnExit()
_SQLite_Close($g_hDB)
_SQLite_Shutdown()
TCPShutdown()
EndFunc
web/index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>웹 시트 (AutoIt + SQLite)</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspreadsheet-ce@4/dist/jspreadsheet.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsuites@4/dist/jsuites.css">
<style>
body { font-family:'맑은 고딕',sans-serif; padding:12px; background:#fafafa; }
.bar { margin-bottom:10px; display:flex; align-items:center; gap:8px; }
button { padding:7px 14px; border:1px solid #ccc; background:#fff;
border-radius:4px; cursor:pointer; }
button:hover { background:#f0f0f0; }
#status { margin-left:10px; color:#555; font-size:13px; }
h2 { margin:0 0 12px; font-size:18px; }
</style>
</head>
<body>
<h2>📊 웹 스프레드시트</h2>
<div class="bar">
<button onclick="saveAll()">💾 저장</button>
<button onclick="loadAll()">🔄 불러오기</button>
<button onclick="clearAll()">🗑 전체삭제</button>
<button onclick="exportCsv()">📤 CSV</button>
<span id="status">준비</span>
</div>
<div id="sheet"></div>
<script src="https://cdn.jsdelivr.net/npm/jsuites@4/dist/jsuites.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspreadsheet-ce@4/dist/jspreadsheet.js"></script>
<script>
const COLS = 10, ROWS = 50;
let sheet;
const setStatus = s => document.getElementById('status').textContent = s;
const emptyData = () => Array.from({length:ROWS}, () => Array(COLS).fill(''));
function initSheet(data){
const el = document.getElementById('sheet');
el.innerHTML = '';
sheet = jspreadsheet(el, {
data,
columns: Array.from({length:COLS}, (_,i) => ({
title: String.fromCharCode(65+i), width: 110
})),
allowInsertRow: true,
minDimensions: [COLS, ROWS],
onchange: () => setStatus('수정됨 (저장 필요)')
});
}
async function loadAll(){
setStatus('불러오는 중...');
const data = emptyData();
const r = await fetch('/api/load');
const j = await r.json();
for (const c of j.cells){
while (data.length <= c.r) data.push(Array(COLS).fill(''));
while (data[c.r].length <= c.c) data[c.r].push('');
data[c.r][c.c] = c.v;
}
initSheet(data);
setStatus(`불러옴 (${j.cells.length}셀)`);
}
async function saveAll(){
setStatus('저장 중...');
const data = sheet.getData();
const cells = [];
for (let r=0; r<data.length; r++)
for (let c=0; c<data[r].length; c++){
const v = data[r][c];
if (v !== '' && v != null) cells.push({r, c, v: String(v)});
}
const res = await fetch('/api/save', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({cells})
});
const j = await res.json();
setStatus(j.ok ? `저장 완료 (${cells.length}셀)` : '저장 실패');
}
async function clearAll(){
if (!confirm('전체 데이터를 삭제할까요?')) return;
await fetch('/api/clear', {method:'POST'});
initSheet(emptyData());
setStatus('삭제됨');
}
function exportCsv(){
const data = sheet.getData();
const csv = data.map(r => r.map(v =>
`"${String(v ?? '').replace(/"/g,'""')}"`).join(',')).join('\n');
const blob = new Blob(['\uFEFF'+csv], {type:'text/csv;charset=utf-8'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'sheet.csv';
a.click();
}
loadAll();
</script>
</body>
</html>
실행 방법
1. 위 구조대로 폴더 생성, 두 파일 저장 2. sqlite3.dll을 main.au3 옆에 두기 (AutoIt SQLite UDF 폴더에 포함) 3. main.au3 더블클릭 → 브라우저 자동 오픈 4. 셀에 입력 후 💾 저장 → SQLite에 즉시 저장됨
확장 포인트
• 시트 여러 개: sheet_data에 sheet_name 컬럼 추가 + 상단 탭 UI • 셀 서식: value 외에 style_json 컬럼 추가, jspreadsheet getStyle()/setStyle() 활용 • 수식: jspreadsheet-ce가 기본 SUM/AVG 등 지원 — 자동 동작함 • 오라클 연동: /api/oracle/query 엔드포인트 추가해서 AutoIt에서 sqlplus.exe 호출 결과를 그리드에 표시
CSV import나 다중 시트, 수식 자동계산 결과 저장 등 원하시는 방향 있으면 이어서 만들어 드릴게요.