147 lines
4.0 KiB
JavaScript
147 lines
4.0 KiB
JavaScript
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(); |