const fs = require('fs'); const path = require('path'); const csvFolderPath = './csv'; // 引用符内の改行を保持したままCSVをレコード単位で分割 function splitCsvRecords(content) { const records = []; let current = ''; let inQuotes = false; for (let i = 0; i < content.length; i += 1) { const char = content[i]; const next = content[i + 1]; if (char === '"') { if (inQuotes && next === '"') { current += '""'; i += 1; } else { inQuotes = !inQuotes; current += char; } continue; } if (!inQuotes && (char === '\n' || char === '\r')) { records.push(current); current = ''; // CRLFは1つの改行として扱う if (char === '\r' && next === '\n') { i += 1; } continue; } current += char; } if (current.length > 0) { records.push(current); } // 末尾空行由来の空レコードを除外 while (records.length > 0 && records[records.length - 1].trim() === '') { records.pop(); } return records; } // ダブルクォーテーションを含むCSV1行を解析 function parseCsvLine(line) { const fields = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i += 1) { const char = line[i]; if (char === '"') { // "" はエスケープされたダブルクォーテーション if (inQuotes && line[i + 1] === '"') { current += '"'; i += 1; } else { inQuotes = !inQuotes; } continue; } if (char === ',' && !inQuotes) { fields.push(current); current = ''; continue; } current += char; } fields.push(current); return fields; } // フィールドをCSV形式へ再構築 function toCsvLine(fields) { return fields .map(field => `"${String(field).replace(/"/g, '""')}"`) .join(','); } // CSVファイルを読み込む関数 function processCSVFiles() { const files = fs.readdirSync(csvFolderPath).filter(file => file.endsWith('.csv')); files.forEach(file => { const filePath = path.join(csvFolderPath, file); const content = fs.readFileSync(filePath, 'utf-8'); const eol = content.includes('\r\n') ? '\r\n' : '\n'; const records = splitCsvRecords(content); // ヘッダ行のみの場合 if (records.length === 1) { if (file.startsWith('✕')) { return; } const newFileName = `✕${file}`; const newFilePath = path.join(csvFolderPath, newFileName); fs.renameSync(filePath, newFilePath); console.log(`Renamed: ${file} → ${newFileName}`); return; } // データが1行以上ある場合 let modified = false; const updatedRecords = records.map((record, index) => { if (index === 0) return record; // ヘッダ行はスキップ const fields = parseCsvLine(record); const firstField = fields[0]; // "(P)"と"(P)"のすべてのパターンを削除 const patterns = ['(P)', '(P)', '(P)', '(P)']; let updatedField = firstField; patterns.forEach(pattern => { if (updatedField.endsWith(pattern)) { updatedField = updatedField.slice(0, -pattern.length); modified = true; } }); fields[0] = updatedField; return toCsvLine(fields); }); // 修正があった場合、ファイルを上書き if (modified) { fs.writeFileSync(filePath, updatedRecords.join(eol), 'utf-8'); console.log(`Modified: ${file}`); } }); } processCSVFiles();