Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how. | 1 | /** |
| 2 | * Word documents and spreadsheets for `make_file` (files.ts), as Office | |
| 3 | * Open XML: a .docx from Markdown and an .xlsx from rows, each a zip of a | |
| 4 | * few XML parts. Pure and small: the zip stores its parts uncompressed, | |
| 5 | * which every reader takes. | |
| 6 | */ | |
| 7 | import { type Block, type Span, parseBlocks } from "./file-markdown.ts"; | |
| 8 | ||
| 9 | // ── Zip ─────────────────────────────────────────────────────────────────── | |
| 10 | ||
| 11 | const CRC_TABLE = (() => { | |
| 12 | const table = new Uint32Array(256); | |
| 13 | for (let i = 0; i < 256; i++) { | |
| 14 | let c = i; | |
| 15 | for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; | |
| 16 | table[i] = c >>> 0; | |
| 17 | } | |
| 18 | return table; | |
| 19 | })(); | |
| 20 | ||
| 21 | export function crc32(bytes: Uint8Array): number { | |
| 22 | let crc = 0xffffffff; | |
| 23 | for (const byte of bytes) crc = CRC_TABLE[(crc ^ byte) & 0xff]! ^ (crc >>> 8); | |
| 24 | return (crc ^ 0xffffffff) >>> 0; | |
| 25 | } | |
| 26 | ||
| 27 | /** A zip of `files` (path to bytes or text), stored without compression. */ | |
| 28 | export function zip(files: [string, Uint8Array | string][], at = new Date()): Uint8Array { | |
| 29 | const encoder = new TextEncoder(); | |
| 30 | const time = (at.getUTCHours() << 11) | (at.getUTCMinutes() << 5) | Math.floor(at.getUTCSeconds() / 2); | |
| 31 | const date = ((Math.max(1980, at.getUTCFullYear()) - 1980) << 9) | ((at.getUTCMonth() + 1) << 5) | at.getUTCDate(); | |
| 32 | const locals: Uint8Array[] = []; | |
| 33 | const centrals: Uint8Array[] = []; | |
| 34 | let offset = 0; | |
| 35 | for (const [path, content] of files) { | |
| 36 | const name = encoder.encode(path); | |
| 37 | const data = typeof content === "string" ? encoder.encode(content) : content; | |
| 38 | const crc = crc32(data); | |
| 39 | const local = new Uint8Array(30 + name.length + data.length); | |
| 40 | const lv = new DataView(local.buffer); | |
| 41 | lv.setUint32(0, 0x04034b50, true); | |
| 42 | lv.setUint16(4, 20, true); | |
| 43 | lv.setUint16(6, 0x0800, true); | |
| 44 | lv.setUint16(8, 0, true); | |
| 45 | lv.setUint16(10, time, true); | |
| 46 | lv.setUint16(12, date, true); | |
| 47 | lv.setUint32(14, crc, true); | |
| 48 | lv.setUint32(18, data.length, true); | |
| 49 | lv.setUint32(22, data.length, true); | |
| 50 | lv.setUint16(26, name.length, true); | |
| 51 | lv.setUint16(28, 0, true); | |
| 52 | local.set(name, 30); | |
| 53 | local.set(data, 30 + name.length); | |
| 54 | const central = new Uint8Array(46 + name.length); | |
| 55 | const cv = new DataView(central.buffer); | |
| 56 | cv.setUint32(0, 0x02014b50, true); | |
| 57 | cv.setUint16(4, 20, true); | |
| 58 | cv.setUint16(6, 20, true); | |
| 59 | cv.setUint16(8, 0x0800, true); | |
| 60 | cv.setUint16(10, 0, true); | |
| 61 | cv.setUint16(12, time, true); | |
| 62 | cv.setUint16(14, date, true); | |
| 63 | cv.setUint32(16, crc, true); | |
| 64 | cv.setUint32(20, data.length, true); | |
| 65 | cv.setUint32(24, data.length, true); | |
| 66 | cv.setUint16(28, name.length, true); | |
| 67 | cv.setUint32(42, offset, true); | |
| 68 | central.set(name, 46); | |
| 69 | locals.push(local); | |
| 70 | centrals.push(central); | |
| 71 | offset += local.length; | |
| 72 | } | |
| 73 | const centralSize = centrals.reduce((sum, c) => sum + c.length, 0); | |
| 74 | const end = new Uint8Array(22); | |
| 75 | const ev = new DataView(end.buffer); | |
| 76 | ev.setUint32(0, 0x06054b50, true); | |
| 77 | ev.setUint16(8, files.length, true); | |
| 78 | ev.setUint16(10, files.length, true); | |
| 79 | ev.setUint32(12, centralSize, true); | |
| 80 | ev.setUint32(16, offset, true); | |
| 81 | const out = new Uint8Array(offset + centralSize + end.length); | |
| 82 | let at2 = 0; | |
| 83 | for (const part of [...locals, ...centrals, end]) { | |
| 84 | out.set(part, at2); | |
| 85 | at2 += part.length; | |
| 86 | } | |
| 87 | return out; | |
| 88 | } | |
| 89 | ||
| 90 | /** Text for XML: escaped, without the control characters XML forbids. */ | |
| 91 | export function xml(text: string): string { | |
| 92 | return String(text) | |
| 93 | // eslint-disable-next-line no-control-regex | |
| 94 | .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, "") | |
| 95 | .replace(/&/g, "&") | |
| 96 | .replace(/</g, "<") | |
| 97 | .replace(/>/g, ">") | |
| 98 | .replace(/"/g, """); | |
| 99 | } | |
| 100 | ||
| 101 | const XML_HEAD = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'; | |
| 102 | ||
| 103 | function coreProps(title: string, at: Date): string { | |
| 104 | const when = at.toISOString().replace(/\.\d{3}Z$/, "Z"); | |
| 105 | return `${XML_HEAD}<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:title>${xml(title)}</dc:title><dc:creator>g1t</dc:creator><dcterms:created xsi:type="dcterms:W3CDTF">${when}</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">${when}</dcterms:modified></cp:coreProperties>`; | |
| 106 | } | |
| 107 | ||
| 108 | // ── Word ────────────────────────────────────────────────────────────────── | |
| 109 | ||
| 110 | const W = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"'; | |
| 111 | ||
| 112 | const DOCX_STYLES = `${XML_HEAD}<w:styles ${W}> | |
| 113 | <w:docDefaults><w:rPrDefault><w:rPr><w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="Calibri" w:cs="Calibri"/><w:sz w:val="22"/><w:szCs w:val="22"/><w:lang w:val="en-US"/></w:rPr></w:rPrDefault><w:pPrDefault><w:pPr><w:spacing w:after="140" w:line="276" w:lineRule="auto"/></w:pPr></w:pPrDefault></w:docDefaults> | |
| 114 | <w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/></w:style> | |
| 115 | <w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="360" w:after="160"/><w:outlineLvl w:val="0"/></w:pPr><w:rPr><w:b/><w:sz w:val="36"/><w:szCs w:val="36"/></w:rPr></w:style> | |
| 116 | <w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="280" w:after="120"/><w:outlineLvl w:val="1"/></w:pPr><w:rPr><w:b/><w:sz w:val="28"/><w:szCs w:val="28"/></w:rPr></w:style> | |
| 117 | <w:style w:type="paragraph" w:styleId="Heading3"><w:name w:val="heading 3"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="200" w:after="80"/><w:outlineLvl w:val="2"/></w:pPr><w:rPr><w:b/><w:sz w:val="24"/><w:szCs w:val="24"/></w:rPr></w:style> | |
| 118 | <w:style w:type="paragraph" w:styleId="Quote"><w:name w:val="Quote"/><w:basedOn w:val="Normal"/><w:qFormat/><w:pPr><w:pBdr><w:left w:val="single" w:sz="18" w:space="8" w:color="BFBFBF"/></w:pBdr><w:ind w:left="240"/></w:pPr><w:rPr><w:color w:val="595959"/></w:rPr></w:style> | |
| 119 | <w:style w:type="paragraph" w:styleId="Code"><w:name w:val="Code"/><w:basedOn w:val="Normal"/><w:pPr><w:shd w:val="clear" w:color="auto" w:fill="F2F2F2"/><w:spacing w:after="140" w:line="240" w:lineRule="auto"/></w:pPr><w:rPr><w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/><w:sz w:val="19"/><w:szCs w:val="19"/></w:rPr></w:style> | |
| 120 | <w:style w:type="paragraph" w:styleId="ListParagraph"><w:name w:val="List Paragraph"/><w:basedOn w:val="Normal"/><w:qFormat/><w:pPr><w:spacing w:after="60"/></w:pPr></w:style> | |
| 121 | <w:style w:type="character" w:styleId="Hyperlink"><w:name w:val="Hyperlink"/><w:rPr><w:color w:val="2F5FD0"/><w:u w:val="single"/></w:rPr></w:style> | |
| 122 | <w:style w:type="table" w:styleId="Table"><w:name w:val="Table"/><w:tblPr><w:tblBorders><w:top w:val="single" w:sz="4" w:color="BFBFBF"/><w:left w:val="single" w:sz="4" w:color="BFBFBF"/><w:bottom w:val="single" w:sz="4" w:color="BFBFBF"/><w:right w:val="single" w:sz="4" w:color="BFBFBF"/><w:insideH w:val="single" w:sz="4" w:color="BFBFBF"/><w:insideV w:val="single" w:sz="4" w:color="BFBFBF"/></w:tblBorders><w:tblCellMar><w:left w:w="100" w:type="dxa"/><w:right w:w="100" w:type="dxa"/></w:tblCellMar></w:tblPr></w:style> | |
| 123 | </w:styles>`; | |
| 124 | ||
| 125 | function runs(spans: Span[], links: string[], base: { bold?: boolean } = {}): string { | |
| 126 | return spans | |
| 127 | .map((span) => { | |
| 128 | const props = [ | |
| 129 | span.href ? '<w:rStyle w:val="Hyperlink"/>' : "", | |
| 130 | span.code ? '<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>' : "", | |
| 131 | span.bold || base.bold ? "<w:b/>" : "", | |
| 132 | span.italic ? "<w:i/>" : "", | |
| 133 | span.code ? '<w:shd w:val="clear" w:color="auto" w:fill="F2F2F2"/>' : "", | |
| 134 | ].join(""); | |
| 135 | const run = `<w:r>${props ? `<w:rPr>${props}</w:rPr>` : ""}<w:t xml:space="preserve">${xml(span.text)}</w:t></w:r>`; | |
| 136 | if (!span.href || !/^(https?:|mailto:)/i.test(span.href)) return run; | |
| 137 | links.push(span.href); | |
| 138 | return `<w:hyperlink r:id="rIdLink${links.length}">${run}</w:hyperlink>`; | |
| 139 | }) | |
| 140 | .join(""); | |
| 141 | } | |
| 142 | ||
| 143 | function paragraph(style: string | null, body: string, extra = ""): string { | |
| 144 | const props = `${style ? `<w:pStyle w:val="${style}"/>` : ""}${extra}`; | |
| 145 | return `<w:p>${props ? `<w:pPr>${props}</w:pPr>` : ""}${body}</w:p>`; | |
| 146 | } | |
| 147 | ||
| 148 | function wordBlock(block: Block, links: string[]): string { | |
| 149 | switch (block.kind) { | |
| 150 | case "heading": | |
| 151 | return paragraph(`Heading${block.level}`, runs(block.spans, links)); | |
| 152 | case "paragraph": | |
| 153 | return paragraph(null, runs(block.spans, links)); | |
| 154 | case "item": { | |
| 155 | const left = 360 + block.depth * 360; | |
| 156 | const marker = block.ordered ? `${block.number}.` : block.depth % 2 ? "–" : "•"; | |
| 157 | return paragraph("ListParagraph", `<w:r><w:t xml:space="preserve">${xml(marker)}</w:t></w:r><w:r><w:tab/></w:r>${runs(block.spans, links)}`, `<w:tabs><w:tab w:val="left" w:pos="${left}"/></w:tabs><w:ind w:left="${left}" w:hanging="300"/>`); | |
| 158 | } | |
| 159 | case "quote": | |
| 160 | return paragraph("Quote", runs(block.spans, links)); | |
| 161 | case "code": | |
| 162 | return paragraph( | |
| 163 | "Code", | |
| 164 | block.text | |
| 165 | .split("\n") | |
| 166 | .map((line, i) => `${i ? "<w:r><w:br/></w:r>" : ""}<w:r><w:t xml:space="preserve">${xml(line)}</w:t></w:r>`) | |
| 167 | .join(""), | |
| 168 | ); | |
| 169 | case "rule": | |
| 170 | return paragraph(null, "", '<w:pBdr><w:bottom w:val="single" w:sz="6" w:space="1" w:color="BFBFBF"/></w:pBdr>'); | |
| 171 | case "table": { | |
| 172 | const columns = Math.max(block.header.length, ...block.rows.map((row) => row.length), 1); | |
| 173 | const row = (cells: Span[][], header: boolean) => | |
| 174 | `<w:tr>${header ? "<w:trPr><w:tblHeader/></w:trPr>" : ""}${Array.from({ length: columns }, (_, c) => `<w:tc><w:tcPr>${header ? '<w:shd w:val="clear" w:color="auto" w:fill="EFEFEF"/>' : ""}</w:tcPr>${paragraph(null, runs(cells[c] ?? [], links, { bold: header }), '<w:spacing w:after="0"/>')}</w:tc>`).join("")}</w:tr>`; | |
| 175 | return `<w:tbl><w:tblPr><w:tblStyle w:val="Table"/><w:tblW w:w="5000" w:type="pct"/></w:tblPr><w:tblGrid>${Array.from({ length: columns }, () => `<w:gridCol w:w="${Math.floor(9720 / columns)}"/>`).join("")}</w:tblGrid>${block.header.length ? row(block.header, true) : ""}${block.rows.map((r) => row(r, false)).join("")}</w:tbl>${paragraph(null, "")}`; | |
| 176 | } | |
| 177 | } | |
| 178 | } | |
| 179 | ||
| 180 | /** A Word document of `markdown`, titled `title` (as its first heading when the Markdown starts without one). */ | |
| 181 | export function markdownDocx(title: string, markdown: string, at = new Date()): Uint8Array { | |
| 182 | let blocks = parseBlocks(markdown); | |
| 183 | if (title.trim() && !(blocks[0]?.kind === "heading" && blocks[0].level === 1)) blocks = [{ kind: "heading", level: 1, spans: [{ text: title.trim() }] }, ...blocks]; | |
| 184 | const links: string[] = []; | |
| 185 | const body = blocks.map((block) => wordBlock(block, links)).join("\n"); | |
| 186 | const document = `${XML_HEAD}<w:document ${W}><w:body>${body}<w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="1260" w:right="1260" w:bottom="1260" w:left="1260" w:header="708" w:footer="708" w:gutter="0"/></w:sectPr></w:body></w:document>`; | |
| 187 | const rels = `${XML_HEAD}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>${links | |
| 188 | .map((href, i) => `<Relationship Id="rIdLink${i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="${xml(href)}" TargetMode="External"/>`) | |
| 189 | .join("")}</Relationships>`; | |
| 190 | return zip( | |
| 191 | [ | |
| 192 | [ | |
| 193 | "[Content_Types].xml", | |
| 194 | `${XML_HEAD}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/></Types>`, | |
| 195 | ], | |
| 196 | [ | |
| 197 | "_rels/.rels", | |
| 198 | `${XML_HEAD}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/></Relationships>`, | |
| 199 | ], | |
| 200 | ["docProps/core.xml", coreProps(title, at)], | |
| 201 | ["word/document.xml", document], | |
| 202 | ["word/_rels/document.xml.rels", rels], | |
| 203 | ["word/styles.xml", DOCX_STYLES], | |
| 204 | ], | |
| 205 | at, | |
| 206 | ); | |
| 207 | } | |
| 208 | ||
| 209 | // ── Excel ───────────────────────────────────────────────────────────────── | |
| 210 | ||
| 211 | export type Cell = string | number | boolean | null; | |
| 212 | export type Sheet = { name: string; rows: Cell[][] }; | |
| 213 | ||
| 214 | /** A column's letters: 0 is A, 26 is AA. */ | |
| 215 | export function columnName(index: number): string { | |
| 216 | let name = ""; | |
| 217 | for (let i = index + 1; i > 0; i = Math.floor((i - 1) / 26)) name = String.fromCharCode(65 + ((i - 1) % 26)) + name; | |
| 218 | return name; | |
| 219 | } | |
| 220 | ||
| 221 | /** A cell's value as Excel keeps it: numbers (and numeric text) as numbers, the rest as text. */ | |
| 222 | export function cellValue(value: Cell): { kind: "number"; value: number } | { kind: "text"; value: string } | null { | |
| 223 | if (value === null || value === undefined || value === "") return null; | |
| 224 | if (typeof value === "number") return Number.isFinite(value) ? { kind: "number", value } : { kind: "text", value: String(value) }; | |
| 225 | if (typeof value === "boolean") return { kind: "text", value: value ? "TRUE" : "FALSE" }; | |
| 226 | const text = String(value); | |
| 227 | // Numeric text is a number, unless a leading zero says it's a code (a ZIP, an id). | |
| 228 | if (/^-?(0|[1-9]\d{0,14})(\.\d+)?$/.test(text.trim())) return { kind: "number", value: Number(text.trim()) }; | |
| 229 | return { kind: "text", value: text }; | |
| 230 | } | |
| 231 | ||
| 232 | /** A sheet name Excel takes: at most 31 characters, none of []:*?/\, unique in the workbook. */ | |
| 233 | export function sheetNames(names: string[]): string[] { | |
| 234 | const used = new Set<string>(); | |
| 235 | return names.map((raw, i) => { | |
| 236 | const base = (String(raw ?? "").replace(/[[\]:*?/\\]/g, " ").replace(/\s+/g, " ").trim() || `Sheet${i + 1}`).replace(/^'|'$/g, "").slice(0, 31) || `Sheet${i + 1}`; | |
| 237 | let name = base; | |
| 238 | for (let k = 2; used.has(name.toLowerCase()); k++) name = `${base.slice(0, 31 - String(k).length - 1)} ${k}`; | |
| 239 | used.add(name.toLowerCase()); | |
| 240 | return name; | |
| 241 | }); | |
| 242 | } | |
| 243 | ||
| 244 | function worksheet(sheet: Sheet): string { | |
| 245 | const columns = Math.max(1, ...sheet.rows.map((row) => row.length)); | |
| 246 | const widths = Array.from({ length: columns }, (_, c) => Math.min(60, Math.max(8, ...sheet.rows.slice(0, 200).map((row) => String(row[c] ?? "").length + 2)))); | |
| 247 | const rows = sheet.rows | |
| 248 | .map((row, r) => { | |
| 249 | const cells = row | |
| 250 | .map((raw, c) => { | |
| 251 | const value = cellValue(raw); | |
| 252 | if (!value) return ""; | |
| 253 | const ref = `${columnName(c)}${r + 1}`; | |
| 254 | const style = r === 0 ? ' s="1"' : ""; | |
| 255 | return value.kind === "number" ? `<c r="${ref}"${style}><v>${value.value}</v></c>` : `<c r="${ref}"${style} t="inlineStr"><is><t xml:space="preserve">${xml(value.value)}</t></is></c>`; | |
| 256 | }) | |
| 257 | .join(""); | |
| 258 | return `<row r="${r + 1}">${cells}</row>`; | |
| 259 | }) | |
| 260 | .join(""); | |
| 261 | const frozen = sheet.rows.length > 1 ? '<pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/>' : ""; | |
| 262 | return `${XML_HEAD}<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetViews><sheetView workbookViewId="0">${frozen}</sheetView></sheetViews><cols>${widths.map((w, c) => `<col min="${c + 1}" max="${c + 1}" width="${w}" customWidth="1"/>`).join("")}</cols><sheetData>${rows}</sheetData></worksheet>`; | |
| 263 | } | |
| 264 | ||
| 265 | const XLSX_STYLES = `${XML_HEAD}<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="2"><font><sz val="11"/><name val="Calibri"/></font><font><b/><sz val="11"/><name val="Calibri"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="2"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/><xf numFmtId="0" fontId="1" fillId="0" borderId="0" xfId="0" applyFont="1"/></cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>`; | |
| 266 | ||
| 267 | /** An Excel workbook of `sheets`, each one's first row its header (in bold, frozen). */ | |
| 268 | export function workbook(title: string, sheets: Sheet[], at = new Date()): Uint8Array { | |
| 269 | const names = sheetNames(sheets.map((sheet) => sheet.name)); | |
| 270 | return zip( | |
| 271 | [ | |
| 272 | [ | |
| 273 | "[Content_Types].xml", | |
| 274 | `${XML_HEAD}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>${sheets | |
| 275 | .map((_, i) => `<Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`) | |
| 276 | .join("")}<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/></Types>`, | |
| 277 | ], | |
| 278 | [ | |
| 279 | "_rels/.rels", | |
| 280 | `${XML_HEAD}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/></Relationships>`, | |
| 281 | ], | |
| 282 | ["docProps/core.xml", coreProps(title, at)], | |
| 283 | [ | |
| 284 | "xl/workbook.xml", | |
| 285 | `${XML_HEAD}<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>${names | |
| 286 | .map((name, i) => `<sheet name="${xml(name)}" sheetId="${i + 1}" r:id="rId${i + 1}"/>`) | |
| 287 | .join("")}</sheets></workbook>`, | |
| 288 | ], | |
| 289 | [ | |
| 290 | "xl/_rels/workbook.xml.rels", | |
| 291 | `${XML_HEAD}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${sheets | |
| 292 | .map((_, i) => `<Relationship Id="rId${i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${i + 1}.xml"/>`) | |
| 293 | .join("")}<Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>`, | |
| 294 | ], | |
| 295 | ["xl/styles.xml", XLSX_STYLES], | |
| 296 | ...sheets.map((sheet, i): [string, string] => [`xl/worksheets/sheet${i + 1}.xml`, worksheet(sheet)]), | |
| 297 | ], | |
| 298 | at, | |
| 299 | ); | |
| 300 | } |