(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
root.ISMIStools = Object.assign(root.ISMIStools || {}, factory());
}
})(typeof self !== 'undefined' ? self : this, function () {
'use strict';
// ---- Minimal ZIP writer (STORED, no compression) ----
// Produces a valid ZIP containing the XLSX parts. Stored entries are used
// because we have no deflate available (browser + zero-dependency Node).
function crc32(buf) {
let table = [];
for (let n = 0; n < 256; n++) {
let c = n;
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
table[n] = c >>> 0;
}
let crc = 0xffffffff;
for (let i = 0; i < buf.length; i++) crc = table[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
return (crc ^ 0xffffffff) >>> 0;
}
function zipStore(entries) {
// entries: [{ name, data: Uint8Array }]
const chunks = [];
const central = [];
let offset = 0;
const enc = new TextEncoder();
for (const e of entries) {
const nameBytes = enc.encode(e.name);
const crc = crc32(e.data);
const local = new Uint8Array(30 + nameBytes.length);
const dv = new DataView(local.buffer);
dv.setUint32(0, 0x04034b50, true); // local file header sig
dv.setUint16(4, 20, true); // version needed
dv.setUint16(6, 0, true); // flags
dv.setUint16(8, 0, true); // method: stored
dv.setUint16(10, 0, true); // mod time
dv.setUint16(12, 0x21, true); // mod date
dv.setUint32(14, crc, true);
dv.setUint32(18, e.data.length, true); // compressed size
dv.setUint32(22, e.data.length, true); // uncompressed size
dv.setUint16(26, nameBytes.length, true); // name len
dv.setUint16(28, 0, true); // extra len
local.set(nameBytes, 30);
chunks.push(local, e.data);
const cd = new Uint8Array(46 + nameBytes.length);
const cdv = new DataView(cd.buffer);
cdv.setUint32(0, 0x02014b50, true); // central dir sig
cdv.setUint16(4, 20, true); // version made by
cdv.setUint16(6, 20, true); // version needed
cdv.setUint16(8, 0, true); // flags
cdv.setUint16(10, 0, true); // method stored
cdv.setUint16(12, 0, true); // mod time
cdv.setUint16(14, 0x21, true); // mod date
cdv.setUint32(16, crc, true);
cdv.setUint32(20, e.data.length, true);
cdv.setUint32(24, e.data.length, true);
cdv.setUint16(28, nameBytes.length, true);
cdv.setUint16(30, 0, true); // extra len
cdv.setUint16(32, 0, true); // comment len
cdv.setUint16(34, 0, true); // disk start
cdv.setUint16(36, 0, true); // internal attrs
cdv.setUint32(38, 0, true); // external attrs
cdv.setUint32(42, offset, true); // local header offset
cd.set(nameBytes, 46);
central.push(cd);
offset += local.length + e.data.length;
}
const cdStart = offset;
let cdLen = 0;
for (const c of central) cdLen += c.length;
const eocd = new Uint8Array(22);
const eocdv = new DataView(eocd.buffer);
eocdv.setUint32(0, 0x06054b50, true);
eocdv.setUint16(8, entries.length, true); // total entries (this disk)
eocdv.setUint16(10, entries.length, true);
eocdv.setUint32(12, cdLen, true);
eocdv.setUint32(16, cdStart, true);
chunks.push(...central, eocd);
const total = chunks.reduce((s, c) => s + c.length, 0);
const out = new Uint8Array(total);
let pos = 0;
for (const c of chunks) { out.set(c, pos); pos += c.length; }
return out;
}
// ---- XML helpers ----
function esc(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&').replace(//g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
// ---- XLSX builder ----
// Builds a single-sheet workbook: a title/header block + a course table
// + a totals row, mirroring the CSV layout.
function buildStudyLoadXlsx(data) {
const header = data.header || {};
const enc = new TextEncoder();
// Gather all strings into a `sharedStrings` table; cells reference them.
const strings = [];
const sIndex = new Map();
function addS(str) {
const key = String(str == null ? '' : str);
if (!sIndex.has(key)) { sIndex.set(key, strings.length); strings.push(key); }
return sIndex.get(key);
}
// rows: array of arrays of { text } (all text cells for simplicity)
const rows = [];
rows.push([{ text: 'ISMIS HELPER - STUDY LOAD' }]);
rows.push([{ text: 'Program' }, { text: header.program || '' }]);
rows.push([{ text: 'Student Name' }, { text: header.fullName || header.studentName || '' }]);
rows.push([{ text: 'Student ID' }, { text: header.studentId || '' }]);
rows.push([{ text: 'Semester' }, { text: header.semester || '' }]);
rows.push([{ text: 'Year Level' }, { text: header.yearLevel || '' }]);
rows.push([{ text: 'Total Units' }, { text: data.totalUnits || '' }]);
rows.push([]); // blank
rows.push([{ text: 'Group' }, { text: 'Course Code' }, { text: 'Description' },
{ text: 'Schedule' }, { text: 'Faculty' }, { text: 'Units' }, { text: 'Status' }]);
for (const c of (data.courses || [])) {
rows.push([
{ text: c.group }, { text: c.courseCode }, { text: c.description },
{ text: c.schedule }, { text: c.faculty || c.teacher }, { text: c.units },
{ text: c.status }
]);
}
rows.push([{ text: 'Total' }, { text: '' }, { text: '' }, { text: '' },
{ text: '' }, { text: data.totalUnits || '' }, { text: '' }]);
// Pre-compute shared string refs so sheet1.xml can reference them.
const cellRefs = rows.map((row) => row.map((c) => c.text === '' ? -1 : addS(c.text)));
// ---- sheet1.xml ----
let sheetXml = '\n';
sheetXml += '';
sheetXml += '' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'';
sheetXml += '';
const colNames = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
cellRefs.forEach((refs, ri) => {
sheetXml += '';
refs.forEach((s, ci) => {
if (s < 0) return;
sheetXml += '' + s + '';
});
sheetXml += '
';
});
sheetXml += '';
// ---- sharedStrings.xml ----
let ssXml = '\n';
ssXml += '';
for (const s of strings) ssXml += '' + esc(s) + '';
ssXml += '';
// ---- other parts ----
const ct = '\n' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'';
const rels = '\n' +
'' +
'' +
'';
const wb = '\n' +
'' +
'';
const wbRels = '\n' +
'' +
'' +
'' +
'' +
'';
const xlRels = '\n' +
'' +
'' +
'';
const styles = '\n' +
'' +
'' +
'' +
'' +
'' +
'' +
'' +
'';
const parts = [
{ name: '[Content_Types].xml', data: enc.encode(ct) },
{ name: '_rels/.rels', data: enc.encode(rels) },
{ name: 'xl/workbook.xml', data: enc.encode(wb) },
{ name: 'xl/_rels/workbook.xml.rels', data: enc.encode(wbRels) },
{ name: 'xl/worksheets/sheet1.xml', data: enc.encode(sheetXml) },
{ name: 'xl/sharedStrings.xml', data: enc.encode(ssXml) },
{ name: 'xl/styles.xml', data: enc.encode(styles) }
];
return zipStore(parts);
}
return { buildStudyLoadXlsx };
});