@shbernal/ts-xlsx 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/core/workbook.d.ts +13 -1
- package/dist/core/worksheet.d.ts +9 -0
- package/dist/io/opc/zip-mtime.d.ts +5 -0
- package/dist/io/opc/zip-mtime.js +1 -0
- package/dist/io/xlsx/edit-vba.js +2 -1
- package/dist/io/xlsx/read-worksheet.js +6 -0
- package/dist/io/xlsx/read.js +33 -2
- package/dist/io/xlsx/sheet-properties.js +3 -2
- package/dist/io/xlsx/workbook-xml.d.ts +1 -1
- package/dist/io/xlsx/workbook-xml.js +6 -2
- package/dist/io/xlsx/write-stream.js +2 -0
- package/dist/io/xlsx/write.d.ts +5 -1
- package/dist/io/xlsx/write.js +4 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -236,6 +236,11 @@ npx skills add ./node_modules/@shbernal/ts-xlsx # offline, matches your inst
|
|
|
236
236
|
npx skills add shbernal/ts-xlsx # or straight from the repo
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
+
That writes the skill under `.agents/skills/`, links it into whichever agent directories it
|
|
240
|
+
finds, and records what it did in `skills-lock.json`. Track the lock file and ignore the
|
|
241
|
+
copies: they are derived, `npx skills experimental_install` rebuilds them from the lock, and
|
|
242
|
+
`npx skills update` overwrites them in place.
|
|
243
|
+
|
|
239
244
|
It covers triage (is this bug ours or your file's?), reducing a failure to a script that
|
|
240
245
|
builds its own input, and — because spreadsheets carry real data — never uploading a
|
|
241
246
|
workbook to a public tracker. Once the reproduction stands on its own, it files without
|
package/dist/core/workbook.d.ts
CHANGED
|
@@ -89,12 +89,24 @@ export declare const DEFAULT_WORKBOOK_VIEW: {
|
|
|
89
89
|
readonly height: 12220;
|
|
90
90
|
readonly activeTab: 0;
|
|
91
91
|
};
|
|
92
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Document-level metadata — what Excel's File ▸ Info panel shows. Mostly the package's core
|
|
94
|
+
* properties (`docProps/core.xml`); `company` is the exception and lives in the extended part,
|
|
95
|
+
* because that is where OOXML puts it. One interface either way: which part a field lands in is
|
|
96
|
+
* the format's business, not the caller's.
|
|
97
|
+
*/
|
|
93
98
|
export interface WorkbookProperties {
|
|
99
|
+
/** `dc:title` — the document's title, as Excel's File ▸ Info shows it. */
|
|
100
|
+
title?: string;
|
|
94
101
|
creator?: string;
|
|
95
102
|
lastModifiedBy?: string;
|
|
96
103
|
created?: Date;
|
|
97
104
|
modified?: Date;
|
|
105
|
+
/**
|
|
106
|
+
* `Company` in the extended properties (`docProps/app.xml`), not the core ones — the only
|
|
107
|
+
* field here that does not live beside the others, because OOXML puts it in the other part.
|
|
108
|
+
*/
|
|
109
|
+
company?: string;
|
|
98
110
|
}
|
|
99
111
|
/**
|
|
100
112
|
* A named reference in the workbook — the entries Excel surfaces in its Name Manager. A name maps
|
package/dist/core/worksheet.d.ts
CHANGED
|
@@ -51,6 +51,15 @@ export interface SheetView {
|
|
|
51
51
|
ySplit?: number;
|
|
52
52
|
/** The cell anchoring the bottom-right scrolling pane; defaults to the first unfrozen cell. */
|
|
53
53
|
topLeftCell?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the grid's cell lines are drawn on screen. Absent means Excel's default, which is on,
|
|
56
|
+
* so only `false` is worth setting: a sheet that paints its own fills and borders reads as a
|
|
57
|
+
* document rather than a spreadsheet once the grid behind it is off.
|
|
58
|
+
*
|
|
59
|
+
* On screen only, and unrelated to {@link PrintOptions.gridLines}, which decides whether the grid
|
|
60
|
+
* is *printed*. Excel exposes them as two separate checkboxes because the answers differ.
|
|
61
|
+
*/
|
|
62
|
+
showGridLines?: boolean;
|
|
54
63
|
}
|
|
55
64
|
/**
|
|
56
65
|
* Per-column formatting. A column may exist purely to carry these, with no cells. The style
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const FIXED_ENTRY_MTIME = new Date(2001, 0, 1, 12, 0, 0, 0).getTime();
|
package/dist/io/xlsx/edit-vba.js
CHANGED
|
@@ -3,6 +3,7 @@ import { VbaAuthorError } from '../../vba/errors.js';
|
|
|
3
3
|
import { addVbaReference, removeVbaModule, } from '../../vba/project-editor.js';
|
|
4
4
|
import { relsPathFor } from '../opc/part-paths.js';
|
|
5
5
|
import { parseRelationshipRecords, relationshipTargetByType, resolveRelativePart, resolveWorkbookPart, } from '../opc/read-opc.js';
|
|
6
|
+
import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
|
|
6
7
|
const OFFICE_DOCUMENT_REL = 'officeDocument';
|
|
7
8
|
const VBA_PROJECT_REL = 'vbaProject';
|
|
8
9
|
const VBA_SIGNATURE_REL_INFIX = 'vbaProjectSignature';
|
|
@@ -21,7 +22,7 @@ function applyToVbaProjectPart(xlsx, apply) {
|
|
|
21
22
|
}
|
|
22
23
|
files[binPath] = apply(bin);
|
|
23
24
|
dropStaleSignature(files, binPath);
|
|
24
|
-
return zipSync(files);
|
|
25
|
+
return zipSync(files, { mtime: FIXED_ENTRY_MTIME });
|
|
25
26
|
}
|
|
26
27
|
function locateVbaProjectPart(files) {
|
|
27
28
|
const rootRels = textPart(files, '_rels/.rels');
|
|
@@ -200,6 +200,7 @@ export function parseWorksheet(xml, sheet, sharedStrings, xfStyles) {
|
|
|
200
200
|
break;
|
|
201
201
|
case 'tabColor':
|
|
202
202
|
case 'outlinePr':
|
|
203
|
+
case 'sheetView':
|
|
203
204
|
case 'pane':
|
|
204
205
|
case 'pageSetUpPr':
|
|
205
206
|
case 'printOptions':
|
|
@@ -312,6 +313,11 @@ function applySheetProperties(local, attrs, sheet) {
|
|
|
312
313
|
if (attrs.summaryRight !== undefined)
|
|
313
314
|
sheet.outline.summaryRight = boolPresent(attrs.summaryRight);
|
|
314
315
|
break;
|
|
316
|
+
case 'sheetView':
|
|
317
|
+
if (attrs.showGridLines !== undefined && !boolPresent(attrs.showGridLines)) {
|
|
318
|
+
sheet.view.showGridLines = false;
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
315
321
|
case 'pane':
|
|
316
322
|
if (attrs.state === 'frozen' || attrs.state === 'frozenSplit') {
|
|
317
323
|
sheet.view.state = 'frozen';
|
package/dist/io/xlsx/read.js
CHANGED
|
@@ -53,6 +53,9 @@ export function readXlsx(data, options = {}) {
|
|
|
53
53
|
const core = partText('docProps/core.xml');
|
|
54
54
|
if (core !== undefined)
|
|
55
55
|
applyCoreProperties(workbook, core);
|
|
56
|
+
const app = partText('docProps/app.xml');
|
|
57
|
+
if (app !== undefined)
|
|
58
|
+
applyAppProperties(workbook, app);
|
|
56
59
|
workbook.protection = parseWorkbookProtection(workbookXml);
|
|
57
60
|
applyWorkbookView(workbook.view, workbookXml);
|
|
58
61
|
readWorkbookPersons(workbookRelsXml, pkg, workbook);
|
|
@@ -436,7 +439,13 @@ function parseWorkbookDefinedNames(xml, sheetOrder) {
|
|
|
436
439
|
});
|
|
437
440
|
return names;
|
|
438
441
|
}
|
|
439
|
-
const CORE_PROPERTY_LOCAL_NAMES = new Set([
|
|
442
|
+
const CORE_PROPERTY_LOCAL_NAMES = new Set([
|
|
443
|
+
'title',
|
|
444
|
+
'creator',
|
|
445
|
+
'lastModifiedBy',
|
|
446
|
+
'created',
|
|
447
|
+
'modified',
|
|
448
|
+
]);
|
|
440
449
|
function applyCoreProperties(workbook, xml) {
|
|
441
450
|
let capture = '';
|
|
442
451
|
let text = '';
|
|
@@ -453,7 +462,9 @@ function applyCoreProperties(workbook, xml) {
|
|
|
453
462
|
onClose(name) {
|
|
454
463
|
if (capture === '' || localName(name) !== capture)
|
|
455
464
|
return;
|
|
456
|
-
if (capture === '
|
|
465
|
+
if (capture === 'title')
|
|
466
|
+
workbook.properties.title = text;
|
|
467
|
+
else if (capture === 'creator')
|
|
457
468
|
workbook.properties.creator = text;
|
|
458
469
|
else if (capture === 'lastModifiedBy')
|
|
459
470
|
workbook.properties.lastModifiedBy = text;
|
|
@@ -470,3 +481,23 @@ function applyCoreProperties(workbook, xml) {
|
|
|
470
481
|
},
|
|
471
482
|
});
|
|
472
483
|
}
|
|
484
|
+
function applyAppProperties(workbook, xml) {
|
|
485
|
+
let capture = false;
|
|
486
|
+
let text = '';
|
|
487
|
+
parseXml(xml, {
|
|
488
|
+
onOpen(name) {
|
|
489
|
+
capture = localName(name) === 'Company';
|
|
490
|
+
text = '';
|
|
491
|
+
},
|
|
492
|
+
onText(chunk) {
|
|
493
|
+
if (capture)
|
|
494
|
+
text += chunk;
|
|
495
|
+
},
|
|
496
|
+
onClose(name) {
|
|
497
|
+
if (!capture || localName(name) !== 'Company')
|
|
498
|
+
return;
|
|
499
|
+
workbook.properties.company = text;
|
|
500
|
+
capture = false;
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
}
|
|
@@ -4,10 +4,11 @@ import { attr, boolAttr, escapeAttr, escapeText, numberText } from '../../xml/xm
|
|
|
4
4
|
import { colorAttrs } from './color-xml.js';
|
|
5
5
|
export function sheetViewsXml(view, active) {
|
|
6
6
|
const selected = active ? ' tabSelected="1"' : '';
|
|
7
|
+
const gridLines = view.showGridLines === false ? ' showGridLines="0"' : '';
|
|
7
8
|
const xSplit = view.xSplit ?? 0;
|
|
8
9
|
const ySplit = view.ySplit ?? 0;
|
|
9
10
|
if (view.state !== 'frozen' || (xSplit === 0 && ySplit === 0)) {
|
|
10
|
-
return `<sheetViews><sheetView${selected} workbookViewId="0"/></sheetViews>`;
|
|
11
|
+
return `<sheetViews><sheetView${gridLines}${selected} workbookViewId="0"/></sheetViews>`;
|
|
11
12
|
}
|
|
12
13
|
const topLeftCell = view.topLeftCell ?? encodeAddress(xSplit + 1, ySplit + 1);
|
|
13
14
|
const activePane = xSplit > 0 && ySplit > 0 ? 'bottomRight' : xSplit > 0 ? 'topRight' : 'bottomLeft';
|
|
@@ -16,7 +17,7 @@ export function sheetViewsXml(view, active) {
|
|
|
16
17
|
(ySplit > 0 ? ` ySplit="${ySplit}"` : '') +
|
|
17
18
|
` topLeftCell="${escapeAttr(topLeftCell)}" activePane="${activePane}" state="frozen"/>`;
|
|
18
19
|
const selection = `<selection pane="${activePane}" activeCell="${escapeAttr(topLeftCell)}" sqref="${escapeAttr(topLeftCell)}"/>`;
|
|
19
|
-
return `<sheetViews><sheetView${selected} workbookViewId="0">${pane}${selection}</sheetView></sheetViews>`;
|
|
20
|
+
return `<sheetViews><sheetView${gridLines}${selected} workbookViewId="0">${pane}${selection}</sheetView></sheetViews>`;
|
|
20
21
|
}
|
|
21
22
|
export function sheetPrXml(sheet) {
|
|
22
23
|
const children = (sheet.tabColor !== undefined ? `<tabColor ${colorAttrs(sheet.tabColor)}/>` : '') +
|
|
@@ -9,4 +9,4 @@ export declare function workbookXml(workbook: Workbook, preservedRels: readonly
|
|
|
9
9
|
export declare const FIXED_WORKBOOK_REL_COUNT = 2;
|
|
10
10
|
export declare function workbookRelsXml(sheetCount: number, hasSharedStrings: boolean, personsRelId: string | null, preservedRels: readonly PreservedWorkbookRel[], pivots: readonly PivotPlan[]): string;
|
|
11
11
|
export declare function corePropsXml(properties: WorkbookProperties): string;
|
|
12
|
-
export declare function appPropsXml(): string;
|
|
12
|
+
export declare function appPropsXml(properties: WorkbookProperties): string;
|
|
@@ -232,6 +232,9 @@ export function workbookRelsXml(sheetCount, hasSharedStrings, personsRelId, pres
|
|
|
232
232
|
}
|
|
233
233
|
export function corePropsXml(properties) {
|
|
234
234
|
const parts = [];
|
|
235
|
+
if (properties.title !== undefined) {
|
|
236
|
+
parts.push(`<dc:title>${escapeText(properties.title)}</dc:title>`);
|
|
237
|
+
}
|
|
235
238
|
if (properties.creator !== undefined) {
|
|
236
239
|
parts.push(`<dc:creator>${escapeText(properties.creator)}</dc:creator>`);
|
|
237
240
|
}
|
|
@@ -253,7 +256,8 @@ export function corePropsXml(properties) {
|
|
|
253
256
|
function w3cdtf(element, date) {
|
|
254
257
|
return `<dcterms:${element} xsi:type="dcterms:W3CDTF">${date.toISOString()}</dcterms:${element}>`;
|
|
255
258
|
}
|
|
256
|
-
export function appPropsXml() {
|
|
259
|
+
export function appPropsXml(properties) {
|
|
260
|
+
const company = properties.company === undefined ? '' : `<Company>${escapeText(properties.company)}</Company>`;
|
|
257
261
|
return (XML_DECLARATION +
|
|
258
|
-
`<Properties xmlns="${NS.extProps}"><Application>ts-xlsx</Application
|
|
262
|
+
`<Properties xmlns="${NS.extProps}"><Application>ts-xlsx</Application>${company}</Properties>`);
|
|
259
263
|
}
|
|
@@ -6,6 +6,7 @@ import { INTERNAL } from '../../core/internal.js';
|
|
|
6
6
|
import { isSharedFormulaValue } from '../../core/value.js';
|
|
7
7
|
import { Workbook } from '../../core/workbook.js';
|
|
8
8
|
import { AuthoringError } from '../../errors.js';
|
|
9
|
+
import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
|
|
9
10
|
import { buildColumnDefaults, buildPackageParts, createStyleRegistry, Extent, renderRow, } from './write.js';
|
|
10
11
|
export class StreamedRow {
|
|
11
12
|
#cells;
|
|
@@ -244,6 +245,7 @@ function streamZipPackage(parts, onChunk) {
|
|
|
244
245
|
});
|
|
245
246
|
for (const [name, data] of Object.entries(parts)) {
|
|
246
247
|
const entry = new ZipDeflate(name, { level: 6 });
|
|
248
|
+
entry.mtime = FIXED_ENTRY_MTIME;
|
|
247
249
|
zip.add(entry);
|
|
248
250
|
entry.push(data, true);
|
|
249
251
|
}
|
package/dist/io/xlsx/write.d.ts
CHANGED
|
@@ -37,6 +37,10 @@ export interface InternalWriteOptions extends WriteOptions {
|
|
|
37
37
|
/**
|
|
38
38
|
* Serialise a workbook into an `.xlsx` package.
|
|
39
39
|
*
|
|
40
|
+
* The bytes are a pure function of the workbook: an unchanged model written twice produces two
|
|
41
|
+
* identical archives, because entry timestamps are pinned to a fixed date rather than taken from the
|
|
42
|
+
* clock. A committed `.xlsx` therefore only changes when something about it changed.
|
|
43
|
+
*
|
|
40
44
|
* @throws {AuthoringError} if the workbook has no worksheets (a zero-sheet package is corrupt),
|
|
41
45
|
* or holds a value the writer cannot yet represent.
|
|
42
46
|
*/
|
|
@@ -44,7 +48,7 @@ export declare function writeXlsx(workbook: Workbook, options?: WriteOptions): U
|
|
|
44
48
|
/**
|
|
45
49
|
* Serialise a workbook into an `.xlsx` package, deflating off the calling thread.
|
|
46
50
|
*
|
|
47
|
-
* Produces the same package {@link writeXlsx} does —
|
|
51
|
+
* Produces the same package {@link writeXlsx} does — byte for byte, entry timestamps included — and
|
|
48
52
|
* exists for one reason: DEFLATE dominates the cost of writing a large workbook, and {@link writeXlsx}
|
|
49
53
|
* spends all of it on the caller's thread. Here `fflate` deflates each part in a worker, so the event
|
|
50
54
|
* loop keeps turning (stalls drop from the whole write to tens of milliseconds) and parts compress in
|
package/dist/io/xlsx/write.js
CHANGED
|
@@ -3,6 +3,7 @@ import { DEFAULT_THEME_XML } from '../../core/theme.js';
|
|
|
3
3
|
import { AuthoringError } from '../../errors.js';
|
|
4
4
|
import { THEME_PART_PATH } from '../opc/part-paths.js';
|
|
5
5
|
import { relsPartXml } from '../opc/rels.js';
|
|
6
|
+
import { FIXED_ENTRY_MTIME } from '../opc/zip-mtime.js';
|
|
6
7
|
import { collectComments, commentsXml, vmlDrawingXml } from './comments.js';
|
|
7
8
|
import { collectHyperlinks, planHyperlinks } from './hyperlinks.js';
|
|
8
9
|
import { drawingRelsXml, drawingXml } from './images.js';
|
|
@@ -17,12 +18,12 @@ import { appPropsXml, contentTypesXml, corePropsXml, FIXED_WORKBOOK_REL_COUNT, r
|
|
|
17
18
|
import { worksheetRelsXml, worksheetXml, } from './worksheet-xml.js';
|
|
18
19
|
export { buildColumnDefaults, Extent, renderRow, } from './worksheet-xml.js';
|
|
19
20
|
export function writeXlsx(workbook, options = {}) {
|
|
20
|
-
return zipSync(buildPackageParts(workbook, options), { level: 6 });
|
|
21
|
+
return zipSync(buildPackageParts(workbook, options), { level: 6, mtime: FIXED_ENTRY_MTIME });
|
|
21
22
|
}
|
|
22
23
|
export async function writeXlsxAsync(workbook, options = {}) {
|
|
23
24
|
const parts = buildPackageParts(workbook, options);
|
|
24
25
|
return await new Promise((resolve, reject) => {
|
|
25
|
-
zip(parts, { level: 6 }, (error, data) => {
|
|
26
|
+
zip(parts, { level: 6, mtime: FIXED_ENTRY_MTIME }, (error, data) => {
|
|
26
27
|
if (error)
|
|
27
28
|
reject(error);
|
|
28
29
|
else
|
|
@@ -177,7 +178,7 @@ export function buildPackageParts(workbook, options = {}) {
|
|
|
177
178
|
'[Content_Types].xml': strToU8(contentTypesXml(sheets.length, allTables, commentNumbers, drawingNumbers, printerSettingsNumbers, media.extensions, hasSharedStrings, preserved.parts, allPivots, preservedWorkbookRels, threadedCommentNumbers, persons.length > 0)),
|
|
178
179
|
'_rels/.rels': strToU8(rootRelsXml(preserved.root)),
|
|
179
180
|
'docProps/core.xml': strToU8(corePropsXml(workbook.properties)),
|
|
180
|
-
'docProps/app.xml': strToU8(appPropsXml()),
|
|
181
|
+
'docProps/app.xml': strToU8(appPropsXml(workbook.properties)),
|
|
181
182
|
'xl/workbook.xml': strToU8(workbookXml(workbook, preservedWorkbookRels, allPivots)),
|
|
182
183
|
'xl/_rels/workbook.xml.rels': strToU8(workbookRelsXml(sheets.length, hasSharedStrings, personsRelId, preservedWorkbookRels, allPivots)),
|
|
183
184
|
'xl/styles.xml': strToU8(styles.toXml()),
|