191 lines
7.7 KiB
JavaScript
191 lines
7.7 KiB
JavaScript
const http = require("http");
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
const XLSX = require("xlsx");
|
||
const formidable = require("formidable");
|
||
|
||
const PORT = process.env.PORT || 3000;
|
||
|
||
function readExcelFile(filePath) {
|
||
const wb = XLSX.readFile(filePath);
|
||
const sheets = {};
|
||
wb.SheetNames.forEach(function (name) {
|
||
const data = XLSX.utils.sheet_to_json(wb.Sheets[name], { header: 1, defval: "" });
|
||
sheets[name] = data;
|
||
});
|
||
return { name: path.basename(filePath), sheets: sheets, sheetNames: wb.SheetNames };
|
||
}
|
||
|
||
function compareFiles(fileA, fileB) {
|
||
const a = readExcelFile(fileA);
|
||
const b = readExcelFile(fileB);
|
||
var report = [];
|
||
report.push("Сравнение: " + a.name + " vs " + b.name);
|
||
report.push("=".repeat(60));
|
||
report.push("");
|
||
|
||
var allSheets = Array.from(new Set(a.sheetNames.concat(b.sheetNames)));
|
||
|
||
allSheets.forEach(function (sheetName) {
|
||
var inA = a.sheetNames.indexOf(sheetName) >= 0;
|
||
var inB = b.sheetNames.indexOf(sheetName) >= 0;
|
||
|
||
if (!inA) {
|
||
report.push("Лист «" + sheetName + "»: есть ТОЛЬКО во втором файле (" + b.name + ")");
|
||
report.push(" Строк: " + (b.sheets[sheetName] ? b.sheets[sheetName].length : 0));
|
||
report.push("");
|
||
return;
|
||
}
|
||
if (!inB) {
|
||
report.push("Лист «" + sheetName + "»: есть ТОЛЬКО в первом файле (" + a.name + ")");
|
||
report.push(" Строк: " + (a.sheets[sheetName] ? a.sheets[sheetName].length : 0));
|
||
report.push("");
|
||
return;
|
||
}
|
||
|
||
var rowsA = a.sheets[sheetName];
|
||
var rowsB = b.sheets[sheetName];
|
||
var maxRows = Math.max(rowsA.length, rowsB.length);
|
||
var maxCols = 0;
|
||
rowsA.forEach(function (r) { if (r.length > maxCols) maxCols = r.length; });
|
||
rowsB.forEach(function (r) { if (r.length > maxCols) maxCols = r.length; });
|
||
|
||
var changes = [];
|
||
var added = 0, removed = 0, modified = 0;
|
||
|
||
for (var i = 0; i < maxRows; i++) {
|
||
var rowA = rowsA[i] || [];
|
||
var rowB = rowsB[i] || [];
|
||
var maxC = Math.max(rowA.length, rowB.length);
|
||
for (var j = 0; j < maxC; j++) {
|
||
var valA = String(rowA[j] != null ? rowA[j] : "").trim();
|
||
var valB = String(rowB[j] != null ? rowB[j] : "").trim();
|
||
if (valA === valB) continue;
|
||
var colLetter = String.fromCharCode(65 + (j % 26));
|
||
if (j >= 26) colLetter = String.fromCharCode(64 + Math.floor(j / 26)) + colLetter;
|
||
var cellRef = colLetter + (i + 1);
|
||
if (valA === "" && valB !== "") {
|
||
changes.push(" + " + cellRef + ": «" + valB + "»");
|
||
added++;
|
||
} else if (valA !== "" && valB === "") {
|
||
changes.push(" - " + cellRef + ": было «" + valA + "»");
|
||
removed++;
|
||
} else {
|
||
changes.push(" ~ " + cellRef + ": «" + valA + "» → «" + valB + "»");
|
||
modified++;
|
||
}
|
||
}
|
||
}
|
||
|
||
report.push("Лист «" + sheetName + "» — " + rowsA.length + "×" + rowsA[0].length + " vs " + rowsB.length + "×" + (rowsB[0] ? rowsB[0].length : 0));
|
||
if (changes.length === 0) {
|
||
report.push(" Отличий нет.");
|
||
} else {
|
||
report.push(" Изменений: " + (added + removed + modified) + " (+" + added + " / -" + removed + " / ~" + modified + ")");
|
||
report.push("");
|
||
report.push.apply(report, changes);
|
||
}
|
||
report.push("");
|
||
});
|
||
|
||
return {
|
||
fileNameA: a.name,
|
||
fileNameB: b.name,
|
||
sheetsA: a.sheetNames.length,
|
||
sheetsB: b.sheetNames.length,
|
||
report: report.join("\n")
|
||
};
|
||
}
|
||
|
||
http.createServer(function (req, res) {
|
||
if (req.method === "POST" && req.url === "/api/compare") {
|
||
var form = formidable({ uploadDir: __dirname, keepExtensions: true, maxFileSize: 100 * 1024 * 1024 });
|
||
form.parse(req, function (err, fields, files) {
|
||
if (err) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify({ ok: false, error: "Ошибка загрузки: " + err.message }));
|
||
}
|
||
var f1 = files.file1 && files.file1[0];
|
||
var f2 = files.file2 && files.file2[0];
|
||
if (!f1 || !f2) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify({ ok: false, error: "Загрузите оба файла" }));
|
||
}
|
||
try {
|
||
var result = compareFiles(f1.filepath, f2.filepath);
|
||
try { fs.unlinkSync(f1.filepath); } catch (e) {}
|
||
try { fs.unlinkSync(f2.filepath); } catch (e) {}
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true, data: result }));
|
||
} catch (e) {
|
||
res.writeHead(500, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: false, error: "Ошибка чтения файла: " + e.message }));
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (req.method === "POST" && req.url === "/api/analyze") {
|
||
var form2 = formidable({ uploadDir: __dirname, keepExtensions: true, maxFileSize: 100 * 1024 * 1024 });
|
||
form2.parse(req, function (err, fields, files) {
|
||
if (err) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify({ ok: false, error: "Ошибка загрузки: " + err.message }));
|
||
}
|
||
var f = files.file && files.file[0];
|
||
if (!f) {
|
||
res.writeHead(400, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify({ ok: false, error: "Загрузите файл" }));
|
||
}
|
||
try {
|
||
var wb = XLSX.readFile(f.filepath);
|
||
var analysis = [];
|
||
analysis.push("Анализ файла: " + path.basename(f.filepath));
|
||
analysis.push("=".repeat(60));
|
||
analysis.push("Листов: " + wb.SheetNames.length);
|
||
analysis.push("Имена листов: " + wb.SheetNames.join(", "));
|
||
analysis.push("");
|
||
|
||
wb.SheetNames.forEach(function (name) {
|
||
var data = XLSX.utils.sheet_to_json(wb.Sheets[name], { header: 1, defval: "" });
|
||
var rows = data.length;
|
||
var cols = 0;
|
||
data.forEach(function (r) { if (r.length > cols) cols = r.length; });
|
||
var nonEmpty = 0;
|
||
data.forEach(function (r) {
|
||
r.forEach(function (c) { if (String(c).trim() !== "") nonEmpty++; });
|
||
});
|
||
analysis.push("Лист «" + name + "»:");
|
||
analysis.push(" Размер: " + rows + " строк × " + cols + " столбцов");
|
||
analysis.push(" Заполненных ячеек: " + nonEmpty);
|
||
if (rows > 0 && cols > 0) {
|
||
analysis.push(" Заголовок (строка 1): " + data[0].map(function (c) { return String(c).trim(); }).filter(Boolean).join(" | "));
|
||
}
|
||
analysis.push("");
|
||
});
|
||
|
||
try { fs.unlinkSync(f.filepath); } catch (e) {}
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true, data: { report: analysis.join("\n") } }));
|
||
} catch (e) {
|
||
res.writeHead(500, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: false, error: "Ошибка чтения файла: " + e.message }));
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
|
||
var file = req.url === "/" ? "/index.html" : req.url.split("?")[0];
|
||
var full = path.join(__dirname, file);
|
||
if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) {
|
||
var ext = path.extname(full);
|
||
var type = ext === ".css" ? "text/css" : ext === ".js" ? "application/javascript" : "text/html";
|
||
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
|
||
return res.end(fs.readFileSync(full));
|
||
}
|
||
res.writeHead(404);
|
||
res.end("Not found");
|
||
}).listen(PORT, function () {
|
||
console.log("Сервер на порту " + PORT);
|
||
});
|