1
0

feat: Background color fetching
All checks were successful
Remote Deploy / deploy (push) Successful in 6s

This commit is contained in:
2026-02-10 21:36:53 +01:00
parent cfdd97c935
commit de7ac3a48d
4 changed files with 133 additions and 14 deletions

View File

@@ -16,10 +16,94 @@ import fs from "fs";
import parseAbsence from "../utils/parseAbsence.js"
import parseTeachers from "../utils/parseTeachers.js"
import ExcelJS from "exceljs"
import JSZip from "jszip";
import { parseStringPromise } from "xml2js";
/**
* Read theme colors from the workbook
*/
async function getThemeColors(filePath) {
const data = fs.readFileSync(filePath);
const zip = await JSZip.loadAsync(data);
// list all files for debug
const themeFile = zip.file("xl/theme/theme1.xml");
if (!themeFile) {
return null;
}
const themeXml = await themeFile.async("text");
const theme = await parseStringPromise(themeXml);
const scheme = theme["a:theme"]?.["a:themeElements"]?.[0]?.["a:clrScheme"]?.[0];
if (!scheme) return null;
function getColor(node) {
if (node["a:srgbClr"]) return node["a:srgbClr"][0].$.val;
if (node["a:sysClr"]) return node["a:sysClr"][0].$.lastClr;
return null;
}
const colors = {
0: getColor(scheme["a:dk1"]?.[0]),
1: getColor(scheme["a:lt1"]?.[0]),
2: getColor(scheme["a:dk2"]?.[0]),
3: getColor(scheme["a:lt2"]?.[0]),
4: getColor(scheme["a:accent1"]?.[0]),
5: getColor(scheme["a:accent2"]?.[0]),
6: getColor(scheme["a:accent3"]?.[0]),
7: getColor(scheme["a:accent4"]?.[0]),
8: getColor(scheme["a:accent5"]?.[0]),
9: getColor(scheme["a:accent6"]?.[0]),
};
return colors;
}
/**
* Apply Excel tint to a base hex color
*/
function applyTintToHex(hex, tint = 0) {
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
const tintChannel = (c) =>
tint > 0 ? Math.round(c + (255 - c) * tint) : Math.round(c * (1 + tint));
const nr = tintChannel(r);
const ng = tintChannel(g);
const nb = tintChannel(b);
return [nr, ng, nb]
.map((v) => v.toString(16).padStart(2, "0"))
.join("")
.toUpperCase();
}
/**
* Resolve final hex for a cell fill
*/
function resolveCellColor(cell, themeColors) {
if (!cell.fill?.fgColor) return null;
const fg = cell.fill.fgColor;
if (fg.argb) return `#${fg.argb}`;
if (fg.theme !== undefined && themeColors) {
const base = themeColors[fg.theme];
if (!base) return null;
return `#${applyTintToHex(base, fg.tint ?? 0)}`;
}
return null;
}
export default async function parseV3(downloadedFilePath) {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(downloadedFilePath);
const themeColors = await getThemeColors(downloadedFilePath);
const teacherMap = await parseTeachers();
@@ -29,7 +113,7 @@ export default async function parseV3(downloadedFilePath) {
const schedule = {};
for (const { dateKey, sheet } of resolvedDays) {
const { changes, absence, inWork, takesPlace, reservedRooms } = extractDaySchedule(sheet, teacherMap);
const { changes, absence, inWork, takesPlace, reservedRooms } = extractDaySchedule(sheet, teacherMap, themeColors);
schedule[dateKey] = {
info: { inWork },
@@ -104,9 +188,9 @@ function groupSheetsByDate(items) {
// ────────────────────────────────────────────────────────────
//
function extractDaySchedule(sheet, teacherMap) {
function extractDaySchedule(sheet, teacherMap, themeColors) {
return {
changes: extractClassChanges(sheet),
changes: extractClassChanges(sheet, themeColors),
absence: extractAbsence(sheet, teacherMap),
inWork: isPripravaSheet(sheet.name.toLowerCase()),
takesPlace: extractTakesPlace(sheet),
@@ -128,7 +212,7 @@ function isPripravaSheet(name) {
// ────────────────────────────────────────────────────────────
//
function extractClassChanges(sheet) {
function extractClassChanges(sheet, themeColors) {
const classRegex = /[AEC][0-4][a-c]?\s*\/.*/s;
const prefixRegex = /[AEC][0-4][a-c]?/;
@@ -150,20 +234,20 @@ function extractClassChanges(sheet) {
classCells.forEach((address, index) => {
const row = sheet.getRow(sheet.getCell(address).row);
changes[classes[index]] = buildLessonArray(row, address);
changes[classes[index]] = buildLessonArray(row, address, themeColors);
});
return changes;
}
function buildLessonArray(row, ignoreAddress) {
function buildLessonArray(row, ignoreAddress, themeColors) {
const lessons = [];
row.eachCell((cell) => {
if (cell.address === ignoreAddress) return;
const colIndex = letterToNumber(cell.address.replace(/[0-9]/g, ""));
lessons[colIndex] = parseLessonCell(cell);
lessons[colIndex] = parseLessonCell(cell, themeColors);
});
const normalized = Array.from(lessons, (x) => (x === undefined ? null : x));
@@ -172,15 +256,15 @@ function buildLessonArray(row, ignoreAddress) {
return normalized.slice(1, 11);
}
function parseLessonCell(cell) {
function parseLessonCell(cell, themeColors) {
try {
const text = (cell.text || "").trim();
const cleanupRegex = /^úklid\s+(?:\d+\s+)?[A-Za-z]{2}$/;
if (!text || cleanupRegex.test(text) || !cell.fill?.fgColor) return null;
const backgroundColor = cell.fill.fgColor.argb === undefined ? undefined : `#${cell.fill.fgColor.argb}`;
const foregroundColor = backgroundColor === undefined ? undefined : (
const backgroundColor = resolveCellColor(cell, themeColors);
const foregroundColor = !backgroundColor ? undefined : (
cell.font?.color?.argb === undefined ? undefined : `#${cell.font.color.argb}`
);
@@ -318,4 +402,4 @@ function formatNowTime() {
);
}
//parseV3("db/current.xlsx")
parseV3("db/current.xlsx")