Files
gia-gve-certificates/src/parser.js
2026-07-01 09:18:06 +05:00

171 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const XLSX = require('xlsx');
const SUBJECT_NAMES = [
'Русский язык', 'Математика', 'Обществознание', 'Физика',
'Химия', 'Информатика', 'Биология', 'Английский язык',
'География', 'Литература', 'История', 'Немецкий язык',
'Французский язык', 'Испанский язык'
];
const HEADER_MAP = {
'фамилия': 'last_name',
'имя': 'first_name',
'отчество': 'patronymic',
'класс': 'class',
'код оо': 'school_code',
'серия': 'passport_series',
'номер': 'passport_number',
'первичный балл': 'primary_score',
'оценка': 'grade'
};
function normalize(s) {
return String(s || '').trim().toLowerCase();
}
function parseXls(filePath) {
const workbook = XLSX.readFile(filePath);
const sheetName = workbook.SheetNames[0];
const sheet = workbook.Sheets[sheetName];
const rows = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: '' });
const parsedSubjects = [];
const studentsMap = new Map();
const results = [];
let currentSubject = null;
let colIdx = null;
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
if (!row || row.every(c => c === '' || c === null || c === undefined)) continue;
const firstCell = String(row[0] || '').trim();
if (isSummaryRowText(firstCell)) {
currentSubject = null;
colIdx = null;
continue;
}
const subjectInfo = detectSubject(firstCell);
if (subjectInfo) {
currentSubject = subjectInfo;
parsedSubjects.push(subjectInfo);
colIdx = null;
continue;
}
if (currentSubject && !colIdx) {
colIdx = findColumnHeaders(row);
if (colIdx) continue;
}
if (currentSubject && colIdx) {
const sd = extractStudentData(row, colIdx);
if (sd && sd.last_name) {
const key = `${normalize(sd.last_name)}|${normalize(sd.first_name)}|${normalize(sd.patronymic)}|${normalize(sd.class)}`;
if (!studentsMap.has(key)) {
studentsMap.set(key, {
last_name: sd.last_name,
first_name: sd.first_name,
patronymic: sd.patronymic,
class: sd.class,
school_code: sd.school_code || '',
passport_series: sd.passport_series || '',
passport_number: sd.passport_number || ''
});
}
results.push({
student_key: key,
subject_code: currentSubject.code,
subject_name: currentSubject.name,
exam_date: currentSubject.date || '',
primary_score: sd.primary_score,
grade: sd.grade
});
}
}
}
return {
subjects: uniqSubjects(parsedSubjects),
students: [...studentsMap.values()],
results
};
}
function uniqSubjects(subjects) {
const seen = new Set();
return subjects.filter(s => {
if (seen.has(s.code)) return false;
seen.add(s.code);
return true;
});
}
function detectSubject(firstCell) {
if (!firstCell) return null;
const match = firstCell.match(/^(\d{2})\s*-\s*(.+?)\s+(\d{4}\.\d{2}\.\d{2})$/);
if (!match) return null;
const code = match[1];
const name = match[2];
const date = match[3];
if (SUBJECT_NAMES.some(sn => sn.toLowerCase() === name.toLowerCase())) {
return { code, name, date };
}
return null;
}
function isSummaryRowText(firstCell) {
return /^(средни|всего|итого)/i.test(firstCell);
}
function findColumnHeaders(row) {
const map = {};
for (let i = 0; i < row.length; i++) {
const s = normalize(String(row[i]));
for (const [pattern, field] of Object.entries(HEADER_MAP)) {
if (s === pattern || s.includes(pattern)) {
map[field] = i;
break;
}
}
}
if (map.last_name && (map.primary_score || map.grade)) return map;
return null;
}
function extractStudentData(row, colIdx) {
const get = (field) => {
const idx = colIdx[field];
if (idx === undefined || idx >= row.length) return '';
return String(row[idx]).trim();
};
const firstCol = String(row[0] || '').trim();
if (!firstCol || !/^\d+$/.test(firstCol)) return null;
const last_name = get('last_name');
if (!last_name || last_name.length < 2) return null;
const toInt = (v) => {
const n = parseInt(v, 10);
return isNaN(n) ? null : n;
};
return {
last_name,
first_name: get('first_name'),
patronymic: get('patronymic'),
class: get('class'),
school_code: get('school_code'),
passport_series: get('passport_series'),
passport_number: get('passport_number'),
primary_score: toInt(get('primary_score')),
grade: toInt(get('grade'))
};
}
module.exports = { parseXls };