@qvac/harness 0.7.3 → 0.8.5

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.
Files changed (146) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/NOTICE.md +16 -0
  3. package/dist-lib/lib/capabilities/completion.d.ts +1 -0
  4. package/dist-lib/lib/capabilities/completion.js +28 -8
  5. package/dist-lib/lib/capabilities/tool-turns.d.ts +3 -0
  6. package/dist-lib/lib/capabilities/tool-turns.js +7 -0
  7. package/dist-lib/lib/context-window.js +2 -2
  8. package/dist-lib/lib/docx/index.d.ts +1 -1
  9. package/dist-lib/lib/docx/index.js +16 -24
  10. package/dist-lib/lib/helpers/run-outcome.d.ts +4 -1
  11. package/dist-lib/lib/helpers/run-outcome.js +14 -8
  12. package/dist-lib/lib/helpers/tool-call-markup.js +4 -4
  13. package/dist-lib/lib/hooks/built-in/arg-repair.js +7 -0
  14. package/dist-lib/lib/office/engine/codes.d.ts +62 -0
  15. package/dist-lib/lib/office/engine/codes.js +61 -0
  16. package/dist-lib/lib/office/engine/docx/document.d.ts +216 -0
  17. package/dist-lib/lib/office/engine/docx/document.js +1152 -0
  18. package/dist-lib/lib/office/engine/docx/index.d.ts +7 -0
  19. package/dist-lib/lib/office/engine/docx/index.js +8 -0
  20. package/dist-lib/lib/office/engine/docx/styles.d.ts +31 -0
  21. package/dist-lib/lib/office/engine/docx/styles.js +173 -0
  22. package/dist-lib/lib/office/engine/docx/template.d.ts +18 -0
  23. package/dist-lib/lib/office/engine/docx/template.js +133 -0
  24. package/dist-lib/lib/office/engine/docx/text.d.ts +55 -0
  25. package/dist-lib/lib/office/engine/docx/text.js +538 -0
  26. package/dist-lib/lib/office/engine/drawingml/chart.d.ts +63 -0
  27. package/dist-lib/lib/office/engine/drawingml/chart.js +539 -0
  28. package/dist-lib/lib/office/engine/drawingml/text.d.ts +35 -0
  29. package/dist-lib/lib/office/engine/drawingml/text.js +352 -0
  30. package/dist-lib/lib/office/engine/errors.d.ts +6 -0
  31. package/dist-lib/lib/office/engine/errors.js +12 -0
  32. package/dist-lib/lib/office/engine/image.d.ts +8 -0
  33. package/dist-lib/lib/office/engine/image.js +48 -0
  34. package/dist-lib/lib/office/engine/index.d.ts +12 -0
  35. package/dist-lib/lib/office/engine/index.js +7 -0
  36. package/dist-lib/lib/office/engine/opc/content-types.d.ts +17 -0
  37. package/dist-lib/lib/office/engine/opc/content-types.js +74 -0
  38. package/dist-lib/lib/office/engine/opc/media.d.ts +13 -0
  39. package/dist-lib/lib/office/engine/opc/media.js +11 -0
  40. package/dist-lib/lib/office/engine/opc/package.d.ts +51 -0
  41. package/dist-lib/lib/office/engine/opc/package.js +324 -0
  42. package/dist-lib/lib/office/engine/opc/paths.d.ts +7 -0
  43. package/dist-lib/lib/office/engine/opc/paths.js +46 -0
  44. package/dist-lib/lib/office/engine/opc/properties.d.ts +7 -0
  45. package/dist-lib/lib/office/engine/opc/properties.js +75 -0
  46. package/dist-lib/lib/office/engine/opc/relationships.d.ts +23 -0
  47. package/dist-lib/lib/office/engine/opc/relationships.js +65 -0
  48. package/dist-lib/lib/office/engine/opc/zip.d.ts +15 -0
  49. package/dist-lib/lib/office/engine/opc/zip.js +264 -0
  50. package/dist-lib/lib/office/engine/pptx/fit.d.ts +12 -0
  51. package/dist-lib/lib/office/engine/pptx/fit.js +37 -0
  52. package/dist-lib/lib/office/engine/pptx/index.d.ts +8 -0
  53. package/dist-lib/lib/office/engine/pptx/index.js +8 -0
  54. package/dist-lib/lib/office/engine/pptx/inherit.d.ts +29 -0
  55. package/dist-lib/lib/office/engine/pptx/inherit.js +155 -0
  56. package/dist-lib/lib/office/engine/pptx/notes.d.ts +5 -0
  57. package/dist-lib/lib/office/engine/pptx/notes.js +85 -0
  58. package/dist-lib/lib/office/engine/pptx/presentation.d.ts +54 -0
  59. package/dist-lib/lib/office/engine/pptx/presentation.js +749 -0
  60. package/dist-lib/lib/office/engine/pptx/read.d.ts +9 -0
  61. package/dist-lib/lib/office/engine/pptx/read.js +66 -0
  62. package/dist-lib/lib/office/engine/pptx/shapes.d.ts +52 -0
  63. package/dist-lib/lib/office/engine/pptx/shapes.js +276 -0
  64. package/dist-lib/lib/office/engine/pptx/slides.d.ts +27 -0
  65. package/dist-lib/lib/office/engine/pptx/slides.js +123 -0
  66. package/dist-lib/lib/office/engine/pptx/template.d.ts +20 -0
  67. package/dist-lib/lib/office/engine/pptx/template.js +391 -0
  68. package/dist-lib/lib/office/engine/pptx/types.d.ts +120 -0
  69. package/dist-lib/lib/office/engine/pptx/types.js +1 -0
  70. package/dist-lib/lib/office/engine/units.d.ts +3 -0
  71. package/dist-lib/lib/office/engine/units.js +12 -0
  72. package/dist-lib/lib/office/engine/utf8.d.ts +2 -0
  73. package/dist-lib/lib/office/engine/utf8.js +103 -0
  74. package/dist-lib/lib/office/engine/xlsx/formula.d.ts +49 -0
  75. package/dist-lib/lib/office/engine/xlsx/formula.js +381 -0
  76. package/dist-lib/lib/office/engine/xlsx/index.d.ts +8 -0
  77. package/dist-lib/lib/office/engine/xlsx/index.js +8 -0
  78. package/dist-lib/lib/office/engine/xlsx/refs.d.ts +22 -0
  79. package/dist-lib/lib/office/engine/xlsx/refs.js +82 -0
  80. package/dist-lib/lib/office/engine/xlsx/reshape.d.ts +21 -0
  81. package/dist-lib/lib/office/engine/xlsx/reshape.js +424 -0
  82. package/dist-lib/lib/office/engine/xlsx/sheet.d.ts +108 -0
  83. package/dist-lib/lib/office/engine/xlsx/sheet.js +662 -0
  84. package/dist-lib/lib/office/engine/xlsx/strings.d.ts +15 -0
  85. package/dist-lib/lib/office/engine/xlsx/strings.js +93 -0
  86. package/dist-lib/lib/office/engine/xlsx/styles.d.ts +38 -0
  87. package/dist-lib/lib/office/engine/xlsx/styles.js +369 -0
  88. package/dist-lib/lib/office/engine/xlsx/template.d.ts +7 -0
  89. package/dist-lib/lib/office/engine/xlsx/template.js +54 -0
  90. package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +161 -0
  91. package/dist-lib/lib/office/engine/xlsx/workbook.js +850 -0
  92. package/dist-lib/lib/office/engine/xlsx/write.d.ts +2 -0
  93. package/dist-lib/lib/office/engine/xlsx/write.js +42 -0
  94. package/dist-lib/lib/office/engine/xml.d.ts +45 -0
  95. package/dist-lib/lib/office/engine/xml.js +258 -0
  96. package/dist-lib/lib/office/extract.d.ts +9 -0
  97. package/dist-lib/lib/office/extract.js +19 -0
  98. package/dist-lib/lib/office/index.d.ts +1 -1
  99. package/dist-lib/lib/office/index.js +74 -19
  100. package/dist-lib/lib/tools/exec/adapters/python/module-distributions.js +0 -3
  101. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +7 -50
  102. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +38 -695
  103. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +1 -85
  104. package/dist-lib/lib/tools/exec/scope.d.ts +2 -0
  105. package/dist-lib/lib/tools/exec/scope.js +33 -2
  106. package/dist-lib/lib/tools/exec.js +9 -20
  107. package/dist-lib/lib/tools/office/ops.d.ts +13 -0
  108. package/dist-lib/lib/tools/office/ops.js +100 -0
  109. package/dist-lib/lib/tools/office.d.ts +12 -0
  110. package/dist-lib/lib/tools/office.js +486 -0
  111. package/dist-lib/lib/tools/pdf/build.d.ts +43 -0
  112. package/dist-lib/lib/tools/pdf/build.js +150 -0
  113. package/dist-lib/lib/tools/pdf/images.d.ts +7 -0
  114. package/dist-lib/lib/tools/pdf/images.js +82 -0
  115. package/dist-lib/lib/tools/pdf/request.d.ts +63 -0
  116. package/dist-lib/lib/tools/pdf/request.js +335 -0
  117. package/dist-lib/lib/tools/pdf.d.ts +6 -0
  118. package/dist-lib/lib/tools/pdf.js +302 -0
  119. package/dist-lib/lib/tools/skill.js +14 -0
  120. package/package.json +34 -6
  121. package/wheels/manifest.json +1 -160
  122. package/worker/harness-sidecar.mjs +5 -1
  123. package/dist-lib/lib/office/pptx.d.ts +0 -1
  124. package/dist-lib/lib/office/pptx.js +0 -26
  125. package/dist-lib/lib/office/python.d.ts +0 -8
  126. package/dist-lib/lib/office/python.js +0 -32
  127. package/dist-lib/lib/office/xlsx.d.ts +0 -1
  128. package/dist-lib/lib/office/xlsx.js +0 -26
  129. package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +0 -7
  130. package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +0 -318
  131. package/wheels/cffi-2.0.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  132. package/wheels/cryptography-47.0.0-cp314-abi3-pyemscripten_2026_0_wasm32.whl +0 -0
  133. package/wheels/defusedxml-0.7.1-py2.py3-none-any.whl +0 -0
  134. package/wheels/et_xmlfile-2.0.0-py3-none-any.whl +0 -0
  135. package/wheels/fonttools-4.62.1-py3-none-any.whl +0 -0
  136. package/wheels/fpdf2-2.8.8-py3-none-any.whl +0 -0
  137. package/wheels/lxml-6.0.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  138. package/wheels/openpyxl-3.1.5-py2.py3-none-any.whl +0 -0
  139. package/wheels/pillow-12.2.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  140. package/wheels/pycparser-3.0-py3-none-any.whl +0 -0
  141. package/wheels/pypdf-6.15.0-py3-none-any.whl +0 -0
  142. package/wheels/python_docx-1.2.0-py3-none-any.whl +0 -0
  143. package/wheels/python_pptx-1.0.2-py3-none-any.whl +0 -0
  144. package/wheels/six-1.17.0-py2.py3-none-any.whl +0 -0
  145. package/wheels/typing_extensions-4.15.0-py3-none-any.whl +0 -0
  146. package/wheels/xlsxwriter-3.2.9-py3-none-any.whl +0 -0
@@ -0,0 +1,662 @@
1
+ import * as xml from '../xml.js';
2
+ import * as refs from './refs.js';
3
+ import * as formulas from './formula.js';
4
+ import { DocsError } from '../errors.js';
5
+ import { isRecord } from '../../../guards.js';
6
+ import { runText } from './strings.js';
7
+ const X = xml.ns.x;
8
+ const DAY = 86400000;
9
+ const EPOCH_1900 = Date.UTC(1899, 11, 30);
10
+ const EPOCH_1900_EARLY = Date.UTC(1899, 11, 31);
11
+ const EPOCH_1904 = Date.UTC(1904, 0, 1);
12
+ export const SHEET_ORDER = [
13
+ 'sheetPr',
14
+ 'dimension',
15
+ 'sheetViews',
16
+ 'sheetFormatPr',
17
+ 'cols',
18
+ 'sheetData',
19
+ 'sheetCalcPr',
20
+ 'sheetProtection',
21
+ 'protectedRanges',
22
+ 'scenarios',
23
+ 'autoFilter',
24
+ 'sortState',
25
+ 'dataConsolidate',
26
+ 'customSheetViews',
27
+ 'mergeCells',
28
+ 'phoneticPr',
29
+ 'conditionalFormatting',
30
+ 'dataValidations',
31
+ 'hyperlinks',
32
+ 'printOptions',
33
+ 'pageMargins',
34
+ 'pageSetup',
35
+ 'headerFooter',
36
+ 'rowBreaks',
37
+ 'colBreaks',
38
+ 'customProperties',
39
+ 'cellWatches',
40
+ 'ignoredErrors',
41
+ 'smartTags',
42
+ 'drawing',
43
+ 'legacyDrawing',
44
+ 'legacyDrawingHF',
45
+ 'drawingHF',
46
+ 'picture',
47
+ 'oleObjects',
48
+ 'controls',
49
+ 'webPublishItems',
50
+ 'tableParts',
51
+ 'extLst'
52
+ ];
53
+ function timeOf(serial) {
54
+ return new Date(Math.round((serial % 1) * DAY)).toISOString().slice(11, 19);
55
+ }
56
+ export function serialToIso(serial, date1904) {
57
+ if (!(serial >= 0 && serial <= 2958465.99999))
58
+ return serial;
59
+ if (!date1904 && Math.floor(serial) === 60) {
60
+ return Number.isInteger(serial) ? '1900-02-29' : `1900-02-29T${timeOf(serial)}`;
61
+ }
62
+ let epoch = date1904 ? EPOCH_1904 : EPOCH_1900;
63
+ if (!date1904 && serial < 61) {
64
+ epoch = EPOCH_1900_EARLY;
65
+ }
66
+ const iso = new Date(Math.round(serial * DAY) + epoch).toISOString();
67
+ return Number.isInteger(serial) ? iso.slice(0, 10) : iso.slice(0, 19);
68
+ }
69
+ function serialFrom(ms, date1904) {
70
+ if (date1904) {
71
+ return (ms - EPOCH_1904) / DAY;
72
+ }
73
+ if (ms < Date.UTC(1900, 2, 1)) {
74
+ return (ms - EPOCH_1900_EARLY) / DAY;
75
+ }
76
+ return (ms - EPOCH_1900) / DAY;
77
+ }
78
+ export function isoToSerial(value, date1904) {
79
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?$/.exec(value);
80
+ if (!match) {
81
+ throw new DocsError('INVALID_VALUE', `date must be YYYY-MM-DD or YYYY-MM-DDTHH:MM, got ${value}`);
82
+ }
83
+ const [, y, mo, d, h = '0', mi = '0', s = '0'] = match;
84
+ const ms = Date.UTC(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s));
85
+ const check = new Date(ms);
86
+ if (check.getUTCMonth() !== Number(mo) - 1 || check.getUTCDate() !== Number(d)) {
87
+ throw new DocsError('INVALID_VALUE', `${value} is not a real date`);
88
+ }
89
+ const serial = serialFrom(ms, date1904);
90
+ if (serial < (date1904 ? 0 : 1)) {
91
+ throw new DocsError('INVALID_VALUE', `${value} is before the workbook's first date`);
92
+ }
93
+ return { serial, time: match[4] !== undefined };
94
+ }
95
+ function formulaError(input, why) {
96
+ return new DocsError('INVALID_FORMULA', `${JSON.stringify(input)} is not a formula (${why}); to store it as text use { value: ${JSON.stringify(input)} }`);
97
+ }
98
+ function parenthesisProblem(body) {
99
+ let depth = 0;
100
+ let i = 0;
101
+ while (i < body.length) {
102
+ const ch = body[i];
103
+ if (ch === '"' || ch === "'") {
104
+ const close = body.indexOf(ch, i + 1);
105
+ if (close === -1) {
106
+ return `an unclosed ${ch === '"' ? 'string' : 'sheet name'}`;
107
+ }
108
+ i = close + 1;
109
+ continue;
110
+ }
111
+ if (ch === '(') {
112
+ depth++;
113
+ }
114
+ else if (ch === ')' && --depth < 0) {
115
+ return 'a ) without a matching (';
116
+ }
117
+ i++;
118
+ }
119
+ return depth > 0 ? 'a ( without a matching )' : null;
120
+ }
121
+ function checkFormula(input) {
122
+ const body = input.replace(/^=/, '');
123
+ if (!body.trim()) {
124
+ throw formulaError(input, 'it is empty');
125
+ }
126
+ if (body.length > 8192) {
127
+ throw formulaError(input, 'it is longer than 8192 characters');
128
+ }
129
+ const problem = parenthesisProblem(body);
130
+ if (problem) {
131
+ throw formulaError(input, problem);
132
+ }
133
+ if (/[+\-*/^&=<>,(]\s*$/.test(body)) {
134
+ throw formulaError(input, 'it ends with an operator');
135
+ }
136
+ }
137
+ function isCellValue(value) {
138
+ return (value === null ||
139
+ typeof value === 'string' ||
140
+ typeof value === 'boolean' ||
141
+ (typeof value === 'number' && Number.isFinite(value)));
142
+ }
143
+ function normalizeText(input) {
144
+ if (input.length > 32767) {
145
+ throw new DocsError('INVALID_VALUE', 'a cell holds at most 32767 characters');
146
+ }
147
+ if (input.length > 1 && input.startsWith('=')) {
148
+ checkFormula(input);
149
+ return { content: true, formula: input, style: {} };
150
+ }
151
+ return { content: true, value: input, style: {} };
152
+ }
153
+ function normalizeObject(input) {
154
+ const { value, formula, date, ...style } = input;
155
+ const given = [value, formula, date].filter((v) => v !== undefined).length;
156
+ if (given > 1) {
157
+ throw new DocsError('INVALID_VALUE', 'give only one of value, formula or date');
158
+ }
159
+ if (formula !== undefined) {
160
+ if (typeof formula !== 'string') {
161
+ throw new DocsError('INVALID_VALUE', 'formula must be text like "=SUM(A1:A3)"');
162
+ }
163
+ checkFormula(formula);
164
+ return { content: true, formula, style };
165
+ }
166
+ if (date !== undefined) {
167
+ if (typeof date !== 'string') {
168
+ throw new DocsError('INVALID_VALUE', 'date must be text like "2026-03-31"');
169
+ }
170
+ return { content: true, date, style };
171
+ }
172
+ if (value === undefined) {
173
+ return { content: false, style };
174
+ }
175
+ if (!isCellValue(value)) {
176
+ throw new DocsError('INVALID_VALUE', 'value must be a string, a finite number, a boolean or null');
177
+ }
178
+ return { content: true, value, style };
179
+ }
180
+ function normalize(input) {
181
+ if (input === null || input === undefined) {
182
+ return { content: true, value: null, style: {} };
183
+ }
184
+ if (typeof input === 'number') {
185
+ if (!Number.isFinite(input)) {
186
+ throw new DocsError('INVALID_VALUE', 'numbers must be finite');
187
+ }
188
+ return { content: true, value: input, style: {} };
189
+ }
190
+ if (typeof input === 'boolean') {
191
+ return { content: true, value: input, style: {} };
192
+ }
193
+ if (typeof input === 'string') {
194
+ return normalizeText(input);
195
+ }
196
+ if (isRecord(input) && !Array.isArray(input)) {
197
+ return normalizeObject(input);
198
+ }
199
+ throw new DocsError('INVALID_VALUE', 'a cell takes a string, number, boolean, null or { value | formula | date, ...style }');
200
+ }
201
+ function shrunkEnd(max, shrink) {
202
+ if (max < shrink.at)
203
+ return max;
204
+ return max <= shrink.end ? shrink.at - 1 : max - shrink.count;
205
+ }
206
+ function rowNumber(row) {
207
+ return Number(row.getAttribute('r'));
208
+ }
209
+ function within(pos, area) {
210
+ return (pos.row >= area.top && pos.row <= area.bottom && pos.col >= area.left && pos.col <= area.right);
211
+ }
212
+ function parentCell(f) {
213
+ const parent = f.parentNode;
214
+ if (!xml.isElement(parent)) {
215
+ throw new DocsError('BROKEN_XLSX', 'a formula sits outside a cell');
216
+ }
217
+ return parent;
218
+ }
219
+ function columnSpan(el, n) {
220
+ const cols = el.parentNode;
221
+ const min = Number(el.getAttribute('min'));
222
+ const max = Number(el.getAttribute('max'));
223
+ if (!cols || n < min || n > max) {
224
+ return;
225
+ }
226
+ if (min < n) {
227
+ const before = xml.clone(el);
228
+ before.setAttribute('max', String(n - 1));
229
+ cols.insertBefore(before, el);
230
+ }
231
+ if (max > n) {
232
+ const after = xml.clone(el);
233
+ after.setAttribute('min', String(n + 1));
234
+ cols.insertBefore(after, el.nextSibling);
235
+ }
236
+ xml.remove(el);
237
+ }
238
+ export class Sheet {
239
+ host;
240
+ name;
241
+ part;
242
+ index;
243
+ masters;
244
+ constructor(host, entry) {
245
+ this.host = host;
246
+ this.name = entry.name;
247
+ this.part = entry.part;
248
+ this.index = entry.index;
249
+ this.masters = null;
250
+ }
251
+ get package() {
252
+ return this.host.package;
253
+ }
254
+ get doc() {
255
+ return this.package.xml(this.part);
256
+ }
257
+ get root() {
258
+ return xml.root(this.doc);
259
+ }
260
+ sheetData() {
261
+ const existing = xml.child(this.root, X, 'sheetData');
262
+ if (existing) {
263
+ return existing;
264
+ }
265
+ return xml.insertOrdered(this.root, xml.named(this.doc, 'sheetData', X), SHEET_ORDER);
266
+ }
267
+ rows() {
268
+ return xml.children(this.sheetData(), X, 'row');
269
+ }
270
+ touch() {
271
+ this.package.touch(this.part);
272
+ this.host.state.touched.add(this);
273
+ }
274
+ cells() {
275
+ return this.numberedRows().flatMap(({ el, r }) => this.numberedCells(el, r));
276
+ }
277
+ numberedRows() {
278
+ let next = 0;
279
+ return this.rows().map((el) => {
280
+ const r = el.hasAttribute('r') ? rowNumber(el) - 1 : next;
281
+ next = r + 1;
282
+ return { el, r };
283
+ });
284
+ }
285
+ numberedCells(row, r) {
286
+ let lastCol = -1;
287
+ return xml.children(row, X, 'c').map((c) => {
288
+ const pos = c.hasAttribute('r')
289
+ ? this.position(xml.attr(c, 'r'))
290
+ : { row: r, col: lastCol + 1 };
291
+ lastCol = pos.col;
292
+ return { el: c, row: pos.row, col: pos.col };
293
+ });
294
+ }
295
+ number() {
296
+ for (const { el, row, col } of this.cells()) {
297
+ if (!el.hasAttribute('r')) {
298
+ el.setAttribute('r', refs.cell(row, col));
299
+ }
300
+ }
301
+ for (const { el, r } of this.numberedRows()) {
302
+ if (!el.hasAttribute('r')) {
303
+ el.setAttribute('r', String(r + 1));
304
+ }
305
+ }
306
+ }
307
+ positionOf(c) {
308
+ if (c.hasAttribute('r')) {
309
+ return this.position(xml.attr(c, 'r'));
310
+ }
311
+ const found = this.cells().find((entry) => entry.el === c);
312
+ if (!found) {
313
+ throw new DocsError('BROKEN_XLSX', `sheet "${this.name}" has a cell outside any row`);
314
+ }
315
+ return { row: found.row, col: found.col };
316
+ }
317
+ usedRange() {
318
+ let area = null;
319
+ for (const { el, row, col } of this.cells()) {
320
+ if (!el.firstChild && !el.hasAttribute('t'))
321
+ continue;
322
+ area = area
323
+ ? {
324
+ top: Math.min(area.top, row),
325
+ left: Math.min(area.left, col),
326
+ bottom: Math.max(area.bottom, row),
327
+ right: Math.max(area.right, col)
328
+ }
329
+ : { top: row, left: col, bottom: row, right: col };
330
+ }
331
+ return area;
332
+ }
333
+ merged() {
334
+ const list = xml.child(this.root, X, 'mergeCells');
335
+ return list ? xml.children(list, X, 'mergeCell').map((m) => xml.attr(m, 'ref')) : [];
336
+ }
337
+ value(c) {
338
+ const t = c.getAttribute('t') || 'n';
339
+ const v = xml.child(c, X, 'v');
340
+ const raw = v ? xml.text(v) : null;
341
+ if (t === 's') {
342
+ return raw === null || raw === '' ? null : this.host.strings.get(Number(raw));
343
+ }
344
+ if (t === 'inlineStr') {
345
+ const is = xml.child(c, X, 'is');
346
+ return is ? runText(is) : '';
347
+ }
348
+ if (raw === null || raw === '') {
349
+ return null;
350
+ }
351
+ if (t === 'b') {
352
+ return raw === '1' || raw === 'true';
353
+ }
354
+ if (t === 'str' || t === 'e' || t === 'd') {
355
+ return raw;
356
+ }
357
+ const n = Number(raw);
358
+ if (this.host.styles.isDate(Number(c.getAttribute('s') || 0))) {
359
+ return serialToIso(n, this.host.date1904);
360
+ }
361
+ return n;
362
+ }
363
+ formula(c) {
364
+ const f = xml.child(c, X, 'f');
365
+ if (!f) {
366
+ return null;
367
+ }
368
+ const text = xml.text(f);
369
+ if (f.getAttribute('t') !== 'shared' || text) {
370
+ return `=${text}`;
371
+ }
372
+ const master = this.sharedMasters().get(xml.attr(f, 'si'));
373
+ if (!master) {
374
+ return null;
375
+ }
376
+ const at = this.positionOf(c);
377
+ return `=${formulas.shift(master.text, { rows: at.row - master.row, cols: at.col - master.col })}`;
378
+ }
379
+ resetMasters() {
380
+ this.masters = null;
381
+ }
382
+ read(opts = {}) {
383
+ const limit = opts.limit === undefined ? 10000 : opts.limit;
384
+ if (!(typeof limit === 'number' && Number.isInteger(limit) && limit > 0)) {
385
+ throw new DocsError('INVALID_RANGE', `limit is a positive number of cells, got ${String(opts.limit)}`);
386
+ }
387
+ const area = opts.range ? refs.parseRange(opts.range) : this.usedRange();
388
+ const out = {
389
+ name: this.name,
390
+ range: null,
391
+ rows: [],
392
+ formulas: {},
393
+ merged: this.merged()
394
+ };
395
+ if (!area) {
396
+ return out;
397
+ }
398
+ const width = area.right - area.left + 1;
399
+ let bottom = area.bottom;
400
+ if ((bottom - area.top + 1) * width > limit) {
401
+ bottom = area.top + Math.max(1, Math.floor(limit / width)) - 1;
402
+ out.truncated = true;
403
+ }
404
+ const window = { ...area, bottom };
405
+ out.range = refs.range(window);
406
+ out.rows = Array.from({ length: bottom - area.top + 1 }, () => new Array(width).fill(null));
407
+ this.fillRead(out, window);
408
+ return out;
409
+ }
410
+ get(ref) {
411
+ const { row, col } = refs.parse(ref);
412
+ const found = this.cells().find((c) => c.row === row && c.col === col);
413
+ if (!found) {
414
+ return { value: null, formula: null, format: 'General' };
415
+ }
416
+ return {
417
+ value: this.value(found.el),
418
+ formula: this.formula(found.el),
419
+ format: this.host.styles.formatCode(Number(found.el.getAttribute('s') || 0))
420
+ };
421
+ }
422
+ findRow(r) {
423
+ return this.numberedRows().find((entry) => entry.r === r)?.el ?? null;
424
+ }
425
+ ensureRow(r) {
426
+ this.number();
427
+ const rows = this.rows();
428
+ const existing = rows.find((row) => rowNumber(row) - 1 === r);
429
+ if (existing) {
430
+ return existing;
431
+ }
432
+ const el = xml.setAttributes(xml.named(this.doc, 'row', X), { r: r + 1 });
433
+ const next = rows.find((row) => rowNumber(row) - 1 > r);
434
+ this.sheetData().insertBefore(el, next ?? null);
435
+ return el;
436
+ }
437
+ findCell(pos) {
438
+ const row = this.findRow(pos.row);
439
+ if (!row) {
440
+ return null;
441
+ }
442
+ return this.numberedCells(row, pos.row).find((entry) => entry.col === pos.col)?.el ?? null;
443
+ }
444
+ ensureCell(pos) {
445
+ const row = this.ensureRow(pos.row);
446
+ const cells = xml.children(row, X, 'c');
447
+ const existing = cells.find((c) => refs.parse(c.getAttribute('r')).col === pos.col);
448
+ if (existing) {
449
+ return existing;
450
+ }
451
+ const el = xml.setAttributes(xml.named(this.doc, 'c', X), { r: refs.cell(pos.row, pos.col) });
452
+ const next = cells.find((c) => refs.parse(c.getAttribute('r')).col > pos.col);
453
+ row.insertBefore(el, next ?? null);
454
+ return el;
455
+ }
456
+ write(ref, input) {
457
+ const pos = refs.parse(ref);
458
+ this.guardWrite(pos, ref);
459
+ const spec = normalize(input);
460
+ const c = this.ensureCell(pos);
461
+ if (spec.content) {
462
+ this.writeContent(c, spec);
463
+ }
464
+ if (Object.keys(spec.style).length) {
465
+ c.setAttribute('s', String(this.host.styles.derive(Number(c.getAttribute('s') || 0), spec.style)));
466
+ }
467
+ this.host.state.cellsChanged = true;
468
+ this.touch();
469
+ }
470
+ lastRow() {
471
+ const used = this.usedRange();
472
+ return used ? used.bottom : -1;
473
+ }
474
+ shiftRows(from, delta) {
475
+ this.number();
476
+ const rows = this.rows();
477
+ const ordered = delta > 0 ? rows.slice().reverse() : rows;
478
+ for (const row of ordered) {
479
+ const r = rowNumber(row) - 1;
480
+ if (r < from) {
481
+ continue;
482
+ }
483
+ row.setAttribute('r', String(r + delta + 1));
484
+ for (const c of xml.children(row, X, 'c')) {
485
+ c.setAttribute('r', refs.cell(r + delta, refs.parse(c.getAttribute('r')).col));
486
+ }
487
+ }
488
+ this.host.state.cellsChanged = true;
489
+ this.touch();
490
+ }
491
+ removeRows(from, count) {
492
+ this.number();
493
+ for (const row of this.rows()) {
494
+ const r = rowNumber(row) - 1;
495
+ if (r >= from && r < from + count) {
496
+ xml.remove(row);
497
+ }
498
+ }
499
+ this.shiftRows(from + count, -count);
500
+ }
501
+ removeColumns(from, count) {
502
+ this.number();
503
+ for (const row of this.rows()) {
504
+ for (const c of xml.children(row, X, 'c')) {
505
+ const pos = refs.parse(c.getAttribute('r'));
506
+ if (pos.col >= from && pos.col < from + count) {
507
+ xml.remove(c);
508
+ }
509
+ else if (pos.col >= from + count) {
510
+ c.setAttribute('r', refs.cell(pos.row, pos.col - count));
511
+ }
512
+ }
513
+ }
514
+ const cols = xml.child(this.root, X, 'cols');
515
+ if (cols) {
516
+ this.shrinkColumns(cols, { at: from, count });
517
+ }
518
+ this.host.state.cellsChanged = true;
519
+ this.touch();
520
+ }
521
+ setColumnWidth(spec) {
522
+ const n = spec.col + 1;
523
+ const cols = xml.child(this.root, X, 'cols') ??
524
+ xml.insertOrdered(this.root, xml.named(this.doc, 'cols', X), SHEET_ORDER);
525
+ for (const el of xml.children(cols, X, 'col')) {
526
+ columnSpan(el, n);
527
+ }
528
+ const el = xml.setAttributes(xml.named(this.doc, 'col', X), {
529
+ min: n,
530
+ max: n,
531
+ width: spec.width,
532
+ customWidth: 1
533
+ });
534
+ const next = xml.children(cols, X, 'col').find((c) => Number(c.getAttribute('min')) > n);
535
+ cols.insertBefore(el, next ?? null);
536
+ this.touch();
537
+ }
538
+ updateDimension() {
539
+ const used = this.usedRange();
540
+ const dim = xml.child(this.root, X, 'dimension') ??
541
+ xml.insertOrdered(this.root, xml.named(this.doc, 'dimension', X), SHEET_ORDER);
542
+ dim.setAttribute('ref', used ? refs.range(used) : 'A1');
543
+ }
544
+ position(ref) {
545
+ try {
546
+ return refs.parse(ref);
547
+ }
548
+ catch {
549
+ throw new DocsError('BROKEN_XLSX', `sheet "${this.name}" has a cell with the invalid reference "${ref}"`);
550
+ }
551
+ }
552
+ sharedMasters() {
553
+ if (this.masters)
554
+ return this.masters;
555
+ const out = new Map();
556
+ for (const f of xml.descendants(this.doc, X, 'f')) {
557
+ if (f.getAttribute('t') !== 'shared' || !f.hasAttribute('ref') || !xml.text(f)) {
558
+ continue;
559
+ }
560
+ const at = this.positionOf(parentCell(f));
561
+ out.set(xml.attr(f, 'si'), { text: xml.text(f), row: at.row, col: at.col });
562
+ }
563
+ this.masters = out;
564
+ return out;
565
+ }
566
+ fillRead(out, area) {
567
+ for (const { el, row, col } of this.cells()) {
568
+ if (!within({ row, col }, area))
569
+ continue;
570
+ out.rows[row - area.top][col - area.left] = this.value(el);
571
+ const formula = this.formula(el);
572
+ if (formula) {
573
+ out.formulas[refs.cell(row, col)] = formula;
574
+ }
575
+ }
576
+ }
577
+ appendValue(c, value) {
578
+ xml
579
+ .append(c, xml.named(this.doc, 'v', X))
580
+ .appendChild(this.doc.createTextNode(xml.clean(value)));
581
+ }
582
+ writeContent(c, spec) {
583
+ for (const n of Array.from(c.childNodes)) {
584
+ c.removeChild(n);
585
+ }
586
+ c.removeAttribute('t');
587
+ if (spec.formula !== undefined) {
588
+ const f = xml.append(c, xml.named(this.doc, 'f', X));
589
+ f.appendChild(this.doc.createTextNode(xml.clean(spec.formula.replace(/^=/, ''))));
590
+ this.host.state.formulas = true;
591
+ return;
592
+ }
593
+ if (spec.date !== undefined) {
594
+ const { serial, time } = isoToSerial(spec.date, this.host.date1904);
595
+ this.appendValue(c, String(serial));
596
+ if (spec.style.format === undefined) {
597
+ spec.style.format = time ? 'yyyy-mm-dd hh:mm' : 'yyyy-mm-dd';
598
+ }
599
+ return;
600
+ }
601
+ this.writeValue(c, spec.value ?? null);
602
+ }
603
+ writeValue(c, value) {
604
+ if (typeof value === 'number') {
605
+ this.appendValue(c, String(value));
606
+ }
607
+ else if (typeof value === 'boolean') {
608
+ c.setAttribute('t', 'b');
609
+ this.appendValue(c, value ? '1' : '0');
610
+ }
611
+ else if (typeof value === 'string') {
612
+ c.setAttribute('t', 's');
613
+ this.appendValue(c, String(this.host.strings.add(value)));
614
+ }
615
+ }
616
+ shrinkColumns(cols, removed) {
617
+ const start = removed.at + 1;
618
+ const end = removed.at + removed.count;
619
+ for (const el of xml.children(cols, X, 'col')) {
620
+ const min = Number(el.getAttribute('min'));
621
+ const max = Number(el.getAttribute('max'));
622
+ const newMin = min < start ? min : Math.max(start, min - removed.count);
623
+ const newMax = shrunkEnd(max, { at: start, end, count: removed.count });
624
+ if (newMax < newMin) {
625
+ xml.remove(el);
626
+ }
627
+ else {
628
+ xml.setAttributes(el, { min: newMin, max: newMax });
629
+ }
630
+ }
631
+ if (!xml.children(cols, X, 'col').length) {
632
+ xml.remove(cols);
633
+ }
634
+ }
635
+ guardMerged(pos, ref) {
636
+ for (const range of this.merged()) {
637
+ const m = refs.parseRange(range);
638
+ if (within(pos, m) && (pos.row !== m.top || pos.col !== m.left)) {
639
+ throw new DocsError('CELL_MERGED', `${ref} is inside the merged range ${range}; write its top-left cell ${refs.cell(m.top, m.left)}`);
640
+ }
641
+ }
642
+ }
643
+ guardWrite(pos, ref) {
644
+ this.guardMerged(pos, ref);
645
+ for (const f of xml.descendants(this.doc, X, 'f')) {
646
+ const kind = f.getAttribute('t');
647
+ const span = f.getAttribute('ref');
648
+ if (!span || (kind !== 'shared' && kind !== 'array')) {
649
+ continue;
650
+ }
651
+ const area = refs.parseRange(span);
652
+ if (area.top === area.bottom && area.left === area.right) {
653
+ continue;
654
+ }
655
+ const owner = this.positionOf(parentCell(f));
656
+ const isOwner = owner.row === pos.row && owner.col === pos.col;
657
+ if ((kind === 'shared' && isOwner) || (kind === 'array' && within(pos, area))) {
658
+ throw new DocsError('SHARED_FORMULA', `${ref} is part of a ${kind} formula over ${span}; edit that range in Excel or rewrite every cell in it`);
659
+ }
660
+ }
661
+ }
662
+ }
@@ -0,0 +1,15 @@
1
+ import type { Element } from '../xml.ts';
2
+ import type { Package } from '../opc/package.ts';
3
+ export declare function runText(container: Element): string;
4
+ export declare class SharedStrings {
5
+ package: Package;
6
+ workbookPart: string;
7
+ part: string | null;
8
+ private items;
9
+ private index;
10
+ constructor(pkg: Package, workbookPart: string);
11
+ get(i: number): string | null;
12
+ add(value: string): number;
13
+ private ensurePart;
14
+ private load;
15
+ }