仕組み : dataを改行 / カンマ / タブ /文字列 /数字 に分ける
1行ずつ処理。
「日付 :」で新しい日付ブロック開始。
日付に 品目・金額の行 紐付け。
金額 : 行末と中央の数字を抽出。
品目 : 数字を除いた残りの部分。
→ 2次元配列 or オブジェクト配列に加工
合計金額 parseIntで取り出し、total +=で足す。
NaNチェックつける。
サーバー保存 axios.post(URL, data)でJSON送信。
品目 数字を除いた残りを item に
既存データをクリアしてから全件保存
→ HTMLにテーブル表示
入れたいdata
3 : トマト / 卵❌2. 317
4 : 疲れてヨーグル. 54
胸 124 洗濯機汚とり. 110
コーヒー + ジュース108
ジュース×.2 ノート. 201
梅酒. 114
JSのみのcode
<textarea id="inputBox" rows="15" cols="60" placeholder="ここにメモ帳の内容を貼る"></textarea>
<button onclick="parseMemo()">変換して表示</button>
<table id="output" border="1"></table>
<script>
function parseMemo() {
const raw = document.getElementById("inputBox").value;
const lines = raw.trim().split("\n");
let currentDate = "";
const result = [];
lines.forEach(line => {
line = line.trim();
if (line === "") return;
// 日付の行(例: "2 :")
const dateMatch = line.match(/^(\d{1,2})\s*:/);
if (dateMatch) {
currentDate = dateMatch[1];
line = line.replace(/^(\d{1,2})\s*:\s*/, "");
}
// 数字(=金額)を抽出
const amountMatch = line.match(/(\d{2,5})(?!\d)/); // 例: 317, 1207など
const amount = amountMatch ? amountMatch[1] : "";
// 品目(数字を除いたテキスト)
const item = line.replace(/(\d{2,5})(?!\d)/g, "").trim();
if (item || amount) {
result.push({ date: currentDate, item, amount });
}
});
// 表に表示
const table = document.getElementById("output");
table.innerHTML = "<tr><th>日付</th><th>品目</th><th>金額</th></tr>";
result.forEach(row => {
const tr = document.createElement("tr");
["date", "item", "amount"].forEach(key => {
const td = document.createElement("td");
td.textContent = row[key];
tr.appendChild(td);
});
table.appendChild(tr);
});
}
</script>
完成系 (idb保存)
<textarea id="inputBox" rows="15" cols="60" placeholder="ここに家計簿メモを貼る"></textarea>
<br>
<button onclick="parseMemo()">変換して表示 & 保存</button>
<p id="total"></p>
<table id="output" border="1"></table>
<script>
const DB_NAME = 'kakeiboDB';
const STORE_NAME = 'entries';
// IndexedDBを開く・作る
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 1);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// IndexedDBに保存
async function saveToIDB(dataArray) {
const db = await openDB();
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite');
const store = tx.objectStore(STORE_NAME);
// まず全部クリアしてから新規保存する(上書き)
const clearReq = store.clear();
clearReq.onsuccess = () => {
// 配列の各アイテムをput
dataArray.forEach(item => store.put(item));
};
clearReq.onerror = e => reject(e.target.error);
tx.oncomplete = () => resolve();
tx.onerror = e => reject(e.target.error);
});
}
async function parseMemo() {
const raw = document.getElementById("inputBox").value;
const lines = raw.trim().split("\n");
let currentDate = "";
const result = [];
let total = 0;
lines.forEach(line => {
line = line.trim();
if (line === "") return;
const dateMatch = line.match(/^(\d{1,2})\s*:/);
if (dateMatch) {
currentDate = dateMatch[1];
line = line.replace(/^(\d{1,2})\s*:\s*/, "");
}
const amountMatch = line.match(/(\d{2,5})(?!\d)/);
const amount = amountMatch ? parseInt(amountMatch[1]) : null;
const item = line.replace(/(\d{2,5})(?!\d)/g, "").trim();
if (item || amount) {
result.push({ date: currentDate, item, amount });
if (!isNaN(amount)) total += amount;
}
});
const table = document.getElementById("output");
table.innerHTML = "<tr><th>日付</th><th>品目</th><th>金額</th></tr>";
result.forEach(row => {
const tr = document.createElement("tr");
["date", "item", "amount"].forEach(key => {
const td = document.createElement("td");
td.textContent = row[key] || "";
tr.appendChild(td);
});
table.appendChild(tr);
});
document.getElementById("total").textContent = `合計金額: ${total} 円`;
// IndexedDBに保存
try {
await saveToIDB(result);
console.log("IndexedDBに保存成功");
} catch (e) {
console.error("IndexedDB保存エラー:", e);
}
}
</script>