(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 }; });