@lotics/cli 0.192.2 → 0.195.0

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/AGENTS.md CHANGED
@@ -23,17 +23,17 @@ conventions are, and where the traps are.
23
23
  - **Tools** (`lotics tools`, `lotics run <tool>`) — the *agent tool registry*: what an agent, workflow,
24
24
  or automation may call. Workspace data, templates, knowledge, admin.
25
25
  - **Commands** (`lotics --help` § COMMANDS) — the CLI's *own verbs*. Auth and org/workspace scoping,
26
- file upload/download/preview, local `.xlsx`/`.docx` authoring, and the whole custom-code app loop
27
- (scaffold, pull, deploy, codegen, dev, and running a bound workflow or agent end to end).
26
+ file upload/download/preview, and the whole custom-code app loop (scaffold, pull, deploy, codegen,
27
+ dev, and running a bound workflow or agent end to end).
28
28
 
29
29
  Several capabilities exist **only** as commands and appear nowhere in `lotics tools` — downloading a
30
30
  file and running a bound app agent are the two that most often get mistaken for missing. Concluding
31
31
  "the platform can't do X" from the tool list alone is a mistake; check both.
32
32
 
33
33
  ⚠️ **`lotics <subcommand> --help` prints the generic top-level help**, except `lotics report --help`
34
- and a bare `lotics auth` / `xlsx` / `docx`, which print their own. It does not describe the
35
- subcommand, so an unhelpful response there is *not* evidence the subcommand is absent. To find out
36
- whether something exists, read `lotics --help` § COMMANDS — the whole section, not a narrow grep.
34
+ and a bare `lotics auth`, which print their own. It does not describe the subcommand, so an unhelpful
35
+ response there is *not* evidence the subcommand is absent. To find out whether something exists, read
36
+ `lotics --help` § COMMANDS — the whole section, not a narrow grep.
37
37
 
38
38
  ## Conventions that hold across every command
39
39
 
@@ -108,5 +108,8 @@ whether something exists, read `lotics --help` § COMMANDS — the whole section
108
108
  editing `tool_names` or `knowledge_doc_ids` still retypes `useAgentRun` — green locally, unchanged
109
109
  in production, and reverted by the next `app codegen`. An agent's PROSE is not in the manifest at
110
110
  all: it lives in `src/agents/<alias>.md` and is pushed by `app agent set`.
111
+ - **Editing a local `.xlsx` / `.docx`.** A library call, not a verb: script `@lotics/xlsx` /
112
+ `@lotics/docx` (`lotics docs xlsx` / `lotics docs docx` prints the installed copy's API guide).
113
+ `lotics file preview` draws the result so it can be looked at.
111
114
  - **OAuth connections.** Attaching a connected account is web-only; the CLI can list them.
112
115
  - **Anything needing a browser.** `app dev` and `file preview` shell out to a local Chrome.
package/README.md CHANGED
@@ -110,8 +110,9 @@ irm https://lotics.ai/install.ps1 | iex # Windows PowerShell — no No
110
110
 
111
111
  ## Update
112
112
 
113
- Re-run whichever installer you used; `npm install -g @lotics/cli@latest` updates an npm install.
114
- The CLI names the right one for the copy you are running when it sees a newer version.
113
+ `lotics upgrade` updates this CLI in place, running whichever installer this copy came from. By
114
+ hand: re-run whichever installer you used; `npm install -g @lotics/cli@latest` updates an npm
115
+ install.
115
116
 
116
117
  The CLI checks for updates once per day and prints a note on stderr, naming the right installer, when a new version is available.
117
118
 
@@ -284,40 +285,20 @@ LOTICS_API_KEY=ltk_... lotics run query_tables '{}'
284
285
  LOTICS_ORG=acme LOTICS_WORKSPACE=wks_... lotics run query_tables '{}'
285
286
  ```
286
287
 
287
- ## Local .xlsx and .docx authoring
288
+ ## Local .xlsx and .docx files
288
289
 
289
- The CLI bundles Lotics's own xlsx and docx engines so you can read, write, and edit files on your local filesystem without installing `xlsx` / `exceljs` / `docx` from npm. Prefer these over third-party libraries — they round-trip faithfully with the Lotics editor, template engine, and formula engine.
290
+ Edit them by scripting Lotics's own engines, published as `@lotics/xlsx` and `@lotics/docx`. Prefer them over `xlsx` / `exceljs` / `docx` from npm: they round-trip faithfully with the Lotics editor, template engine, and formula engine. `npm i @lotics/xlsx @lotics/docx`, then read the API guide that ships inside the package: `lotics docs xlsx` / `lotics docs docx` prints the installed copy (`node_modules/@lotics/<pkg>/AGENTS.md` on a machine with no CLI). Both packages ship `.ts` sources as their entry, so run the script under a TypeScript-aware runner.
290
291
 
291
- ```bash
292
- # .xlsx
293
- lotics xlsx read ./report.xlsx # → JSON of sheets, cells, merges
294
- lotics xlsx read ./report.xlsx --with-format # …plus each cell's resolved style
295
- lotics xlsx write ./out.xlsx '{"sheets":[{"name":"S1","cells":{"A1":"Hi","B1":42}}]}'
296
- # A cell is a bare value, or an object with numFmt / style / formula:
297
- lotics xlsx write ./out.xlsx '{"sheets":[{"name":"S1",
298
- "colWidths":{"A":34,"B":14}, "rowHeights":{"1":28},
299
- "cells":{
300
- "A1":{"value":"Tổng","style":{"fontBold":true}},
301
- "B1":{"value":1234567,"numFmt":"#,##0"},
302
- "B2":{"formula":"B1*0.1","numFmt":"#,##0"}}}]}' # unknown keys are rejected, not ignored
303
- lotics xlsx set-cell ./report.xlsx 'Sheet1!A1' '=SUM(B:B)'
304
- lotics xlsx add-sheet ./report.xlsx 'Summary'
305
- lotics xlsx merge ./report.xlsx 'Sheet1!A1:C1'
306
- lotics xlsx insert-rows ./report.xlsx Sheet1 5 3
307
- lotics xlsx batch ./report.xlsx '[{"op":"set-cell","sheet":"Sheet1","ref":"A1","value":"Hi"}, {"op":"merge","sheet":"Sheet1","range":"A1:B1"}]'
308
-
309
- # .docx
310
- lotics docx read ./report.docx # → JSON of blocks with text + style
311
- lotics docx write ./out.docx '{"blocks":[{"kind":"paragraph","text":"Title","style":"Heading1"}]}'
312
- lotics docx append-paragraph ./report.docx 'New paragraph' --style=Heading2
313
- lotics docx insert-paragraph ./report.docx 'Front matter' --at=0
314
- lotics docx replace-text ./report.docx '{{name}}' 'Acme Inc.'
315
- lotics docx batch ./report.docx '[{"op":"append-paragraph","text":"A","style":"Heading1"},{"op":"replace-text","search":"x","replace":"y"}]'
316
- ```
292
+ ```ts
293
+ import { readFile, writeFile } from "node:fs/promises";
294
+ import { parseDocx, replaceText, serializeDocx } from "@lotics/docx";
317
295
 
318
- Edits mutate the file in place via an atomic temp-file + rename. Unknown OOXML constructs (tables, SmartArt, custom XML) round-trip verbatim — they show up in `read` as `opaque_block` entries.
296
+ const { doc, count } = replaceText(await parseDocx(await readFile("in.docx")), "{{name}}", "Acme Ltd.");
297
+ if (count === 0) throw new Error("{{name}} is not in this document — nothing written");
298
+ await writeFile("out.docx", await serializeDocx(doc));
299
+ ```
319
300
 
320
- Run `lotics xlsx` or `lotics docx` with no subcommand for the full list.
301
+ `lotics preview <file.xlsx|.docx>` draws one of those files to a PNG with the same engines, so a script's output can be looked at rather than guessed at.
321
302
 
322
303
  ## Knowledge docs
323
304
 
@@ -50253,6 +50253,14 @@ ${e.toString()}`);
50253
50253
  return colon === -1 ? tag : tag.slice(colon + 1);
50254
50254
  }
50255
50255
 
50256
+ // ../xlsx/src/invalid_model_error.ts
50257
+ var InvalidModelError = class extends Error {
50258
+ constructor(reason) {
50259
+ super(reason);
50260
+ this.name = "InvalidModelError";
50261
+ }
50262
+ };
50263
+
50256
50264
  // ../xlsx/node_modules/fast-xml-parser/src/util.js
50257
50265
  var nameStartChar2 = ":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
50258
50266
  var nameChar2 = nameStartChar2 + "\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
@@ -52902,7 +52910,7 @@ ${e.toString()}`);
52902
52910
  const colCount = Math.max(parsedCols.length > 0 ? parsedCols[parsedCols.length - 1].max : 0, maxCol);
52903
52911
  const columns = buildColumns(parsedCols, colHidden, maxContentWidth, colCount, defaultColWidth);
52904
52912
  const mergedCells = parseMergedCells(worksheet);
52905
- const conditionalFormats = parseConditionalFormats(worksheet, theme, indexedColors);
52913
+ const conditionalFormats = parseConditionalFormats(worksheet, theme, indexedColors, styles.dxfs);
52906
52914
  const images = parseImages(rels, zipEntries);
52907
52915
  const dataValidations = parseDataValidations(worksheet);
52908
52916
  const sparklineGroups = parseSparklineGroups(worksheet);
@@ -53233,7 +53241,7 @@ ${e.toString()}`);
53233
53241
  const cell = { column: cellRef.col, value: displayValue, content, style };
53234
53242
  cell.typedValue = typedValue;
53235
53243
  cell.rawValue = cellRawValue;
53236
- cell.originalXfIndex = styleIndex;
53244
+ if (styles.hasXf(styleIndex)) cell.originalXfIndex = styleIndex;
53237
53245
  if (numFmt !== "General") cell.numFmtCode = numFmt;
53238
53246
  if (type === "e" && rawValue) cell.error = rawValue;
53239
53247
  if (formula) cell.formula = formula;
@@ -53383,7 +53391,7 @@ ${e.toString()}`);
53383
53391
  const dataUrl = `data:${mimeType};base64,${base64}`;
53384
53392
  return { dataUrl, tl, br, ext, anchorKind, extent, pos };
53385
53393
  }
53386
- function parseConditionalFormats(worksheet, theme, indexedColors) {
53394
+ function parseConditionalFormats(worksheet, theme, indexedColors, dxfs) {
53387
53395
  const cfArr = worksheet["conditionalFormatting"];
53388
53396
  if (!cfArr) return [];
53389
53397
  const result = [];
@@ -53394,7 +53402,7 @@ ${e.toString()}`);
53394
53402
  if (!ruleArr) continue;
53395
53403
  const rules = [];
53396
53404
  for (const ruleEl of ruleArr) {
53397
- const parsed = parseCfRule(ruleEl, theme, indexedColors);
53405
+ const parsed = parseCfRule(ruleEl, theme, indexedColors, dxfs);
53398
53406
  if (parsed) rules.push(parsed);
53399
53407
  }
53400
53408
  if (rules.length > 0) {
@@ -53403,7 +53411,7 @@ ${e.toString()}`);
53403
53411
  }
53404
53412
  return result;
53405
53413
  }
53406
- function parseCfRule(el, theme, indexedColors) {
53414
+ function parseCfRule(el, theme, indexedColors, dxfs) {
53407
53415
  const type = el["@_type"];
53408
53416
  const priority = parseInt(el["@_priority"] ?? "1", 10);
53409
53417
  const stopIfTrue = el["@_stopIfTrue"] === "1";
@@ -53464,14 +53472,23 @@ ${e.toString()}`);
53464
53472
  case "aboveAverage":
53465
53473
  case "timePeriod":
53466
53474
  case "expression": {
53467
- return parseStyleCfRule(el, type, priority, stopIfTrue, theme, indexedColors);
53475
+ return parseStyleCfRule(el, type, priority, stopIfTrue, dxfs);
53468
53476
  }
53469
53477
  default:
53470
53478
  return void 0;
53471
53479
  }
53472
53480
  }
53473
- function parseStyleCfRule(el, type, priority, stopIfTrue, _theme, _indexedColors) {
53481
+ function parseStyleCfRule(el, type, priority, stopIfTrue, dxfs) {
53474
53482
  const rule = { ruleType: "style", priority, stopIfTrue, type };
53483
+ const dxfIdAttr = el["@_dxfId"];
53484
+ if (dxfIdAttr !== void 0) {
53485
+ const dxfId = parseInt(dxfIdAttr, 10);
53486
+ const dxf = dxfs[dxfId];
53487
+ if (dxf !== void 0) {
53488
+ rule.style = { ...dxf };
53489
+ rule.originalDxfId = dxfId;
53490
+ }
53491
+ }
53475
53492
  if (type === "cellIs") {
53476
53493
  rule.operator = el["@_operator"];
53477
53494
  const formula = el["formula"];
@@ -54158,6 +54175,11 @@ ${e.toString()}`);
54158
54175
  * the styles part gets regenerated to include the new code.
54159
54176
  */
54160
54177
  this.knownNumFmts = /* @__PURE__ */ new Set(["General", ""]);
54178
+ /**
54179
+ * What each `cellXfs` index of the loaded file resolves to here — the
54180
+ * baseline `originalXfExpresses` verifies an `originalXfIndex` against.
54181
+ */
54182
+ this.originalXfs = /* @__PURE__ */ new Map();
54161
54183
  this.keyMap.set(styleKey({}), 0);
54162
54184
  }
54163
54185
  /** Get style by index. Returns empty style for invalid indices. */
@@ -54191,6 +54213,44 @@ ${e.toString()}`);
54191
54213
  this.knownNumFmts.add(code2);
54192
54214
  this.dirty = true;
54193
54215
  }
54216
+ /**
54217
+ * Record what the loaded file's `cellXfs` table resolves to here. The whole
54218
+ * table, because an entry no cell carries still governs the export: a cell
54219
+ * written with no `s=` inherits entry 0. An entry whose style this registry
54220
+ * never interned stays unrecorded, so a write claiming it fails
54221
+ * `originalXfExpresses` and the claim is dropped.
54222
+ */
54223
+ recordOriginalXfs(cellXfs) {
54224
+ for (let i3 = 0; i3 < cellXfs.length; i3++) {
54225
+ const styleIndex = this.keyMap.get(styleKey(cellXfs[i3].style));
54226
+ if (styleIndex !== void 0) {
54227
+ this.originalXfs.set(i3, { styleIndex, numFmtCode: cellXfs[i3].numFmtCode });
54228
+ }
54229
+ }
54230
+ }
54231
+ /**
54232
+ * Whether the loaded file's xf `index` resolves to exactly this style and
54233
+ * number format here. The claim a caller makes by naming an `originalXfIndex`,
54234
+ * checked at the write so no later scan has to re-ask it.
54235
+ */
54236
+ originalXfExpresses(index, styleIndex, numFmtCode) {
54237
+ const original = this.originalXfs.get(index);
54238
+ return original !== void 0 && original.styleIndex === styleIndex && original.numFmtCode === (numFmtCode === "" ? "General" : numFmtCode);
54239
+ }
54240
+ /**
54241
+ * True when the xf index the writer emits for `cell` in styles-passthrough
54242
+ * mode still expresses the cell's style. A cell CARRYING an index always
54243
+ * does — every write path drops that index the moment the style or number
54244
+ * format moves off it. The question left is the one no cell states: a cell
54245
+ * the file never carried emits no `s=` and so inherits the file's xf 0,
54246
+ * which is right only when that entry expresses the style the model gives
54247
+ * it. Neither half reads cell protection or `quotePrefix`, which this model
54248
+ * does not carry, so an xf differing only there compares equal.
54249
+ */
54250
+ keepsOriginalXf(cell) {
54251
+ if (cell.originalXfIndex !== void 0) return true;
54252
+ return this.originalXfExpresses(0, cell.styleIndex, cell.numFmtCode);
54253
+ }
54194
54254
  /**
54195
54255
  * Reset the `dirty` flag and snapshot the current numFmt set as the
54196
54256
  * baseline. Called once after loading from a parsed workbook so that
@@ -54369,8 +54429,21 @@ ${e.toString()}`);
54369
54429
  *
54370
54430
  * Pass `originalXfIndex` to enable styles-passthrough mode for cells
54371
54431
  * copied from a parsed template.
54432
+ *
54433
+ * Both arguments are indexes into tables this sheet's registry owns and
54434
+ * neither is interned here, so both are checked against it — differently.
54435
+ * An unknown `styleIndex` throws: it would reach the writer and render as
54436
+ * the empty style, and there is no style to fall back to. An
54437
+ * `originalXfIndex` the file's xf table contradicts is instead dropped: the
54438
+ * cell still knows its style, so the export rebuilds the styles part rather
54439
+ * than shipping the cell with the one the file gave that index.
54372
54440
  */
54373
54441
  setWithStyleIndex(ref2, value2, styleIndex, numFmtCode = "General", formula, originalXfIndex) {
54442
+ if (this.styles && !(Number.isInteger(styleIndex) && styleIndex >= 0 && styleIndex < this.styles.size)) {
54443
+ throw new InvalidModelError(
54444
+ `setWithStyleIndex: styleIndex ${styleIndex} is not in this sheet's registry (0..${this.styles.size - 1})`
54445
+ );
54446
+ }
54374
54447
  if (this.styles) this.styles.registerNumFmt(numFmtCode);
54375
54448
  const cell = {
54376
54449
  value: value2,
@@ -54380,7 +54453,23 @@ ${e.toString()}`);
54380
54453
  numFmtCode,
54381
54454
  content: { type: "plain", text: "" }
54382
54455
  };
54383
- if (originalXfIndex !== void 0) cell.originalXfIndex = originalXfIndex;
54456
+ if (originalXfIndex !== void 0 && this.styles?.originalXfExpresses(originalXfIndex, styleIndex, numFmtCode)) {
54457
+ cell.originalXfIndex = originalXfIndex;
54458
+ }
54459
+ this.cells.set(ref2, cell);
54460
+ return cell;
54461
+ }
54462
+ /**
54463
+ * Re-style an existing cell. Replaces it rather than editing it in place, so
54464
+ * a style the cell's `originalXfIndex` no longer expresses drops that index
54465
+ * exactly as `set()` does — keeping it would ship the cell with the style the
54466
+ * FILE gave that index. Returns undefined for a ref holding no cell.
54467
+ */
54468
+ setStyleIndex(ref2, styleIndex) {
54469
+ const existing = this.cells.get(ref2);
54470
+ if (!existing) return void 0;
54471
+ const cell = { ...existing, styleIndex };
54472
+ if (styleIndex !== existing.styleIndex) delete cell.originalXfIndex;
54384
54473
  this.cells.set(ref2, cell);
54385
54474
  return cell;
54386
54475
  }
@@ -54449,6 +54538,30 @@ ${e.toString()}`);
54449
54538
  this.onChange?.(event);
54450
54539
  for (const sub of this.subscribers) sub(event);
54451
54540
  }
54541
+ /**
54542
+ * True while every reference the regenerated parts make into the styles part
54543
+ * resolves against the loaded file's own — the precondition for exporting
54544
+ * that part unrebuilt. Two kinds of reference exist: a cell's `s=` names a
54545
+ * `cellXfs` entry, and a style conditional-format rule's `dxfId` names a
54546
+ * `dxfs` entry. A rule read out of the file keeps the `dxfs` index it came
54547
+ * in on; a rule painting a style that index cannot name is one only the
54548
+ * rebuild can mint a dxf for, so a workbook holding one cannot skip it —
54549
+ * that rule would ship with no `dxfId` and paint nothing.
54550
+ */
54551
+ regeneratedPartsReferenceOnlyLoadedStyles() {
54552
+ for (const sheet of this.sheets) {
54553
+ for (const cell of sheet.cells.values()) {
54554
+ if (!this.styles.keepsOriginalXf(cell)) return false;
54555
+ }
54556
+ for (const cf of sheet.conditionalFormats) {
54557
+ for (const rule of cf.rules) {
54558
+ if (rule.ruleType !== "style") continue;
54559
+ if (rule.style !== void 0 && rule.originalDxfId === void 0) return false;
54560
+ }
54561
+ }
54562
+ }
54563
+ return true;
54564
+ }
54452
54565
  /** Produce an immutable ParsedSpreadsheet snapshot for read-only consumers. */
54453
54566
  toSnapshot() {
54454
54567
  return {
@@ -54550,7 +54663,7 @@ ${e.toString()}`);
54550
54663
  const styles = new StyleRegistry();
54551
54664
  const sheets = [];
54552
54665
  for (const ps of parsed.sheets) {
54553
- const sheet = loadSheetFromParsed(ps, styles);
54666
+ const sheet = loadSheetFromParsed(ps, styles, parsed.cellXfs !== void 0);
54554
54667
  sheets.push(sheet);
54555
54668
  }
54556
54669
  const wb = new WorkbookModel({
@@ -54577,10 +54690,11 @@ ${e.toString()}`);
54577
54690
  }
54578
54691
  }
54579
54692
  }
54693
+ if (parsed.cellXfs) styles.recordOriginalXfs(parsed.cellXfs);
54580
54694
  styles.markClean();
54581
54695
  return wb;
54582
54696
  }
54583
- function loadSheetFromParsed(ps, styles) {
54697
+ function loadSheetFromParsed(ps, styles, hasCellXfs) {
54584
54698
  const sheet = new SheetModel(ps.name, styles);
54585
54699
  sheet.sheetState = ps.sheetState;
54586
54700
  sheet.defaultRowHeight = ps.defaultRowHeight;
@@ -54627,20 +54741,28 @@ ${e.toString()}`);
54627
54741
  for (const cell of row.cells) {
54628
54742
  const ref2 = rowColToRef(row.index, cell.column);
54629
54743
  const styleIndex = styles.intern(cell.style);
54744
+ const numFmtCode = cell.numFmtCode ?? "General";
54630
54745
  styles.registerNumFmt(cell.numFmtCode);
54631
54746
  const rawValue = cell.typedValue !== void 0 ? cell.typedValue : inferRawValue(cell.value, cell.content);
54632
54747
  const model = {
54633
54748
  value: rawValue,
54634
54749
  displayValue: cell.value,
54635
54750
  styleIndex,
54636
- numFmtCode: cell.numFmtCode ?? "General",
54751
+ numFmtCode,
54637
54752
  content: cell.content
54638
54753
  };
54639
54754
  if (cell.error) model.error = cell.error;
54640
54755
  if (cell.formula) model.formula = cell.formula;
54641
54756
  if (cell.isArrayFormula) model.isArrayFormula = true;
54642
54757
  if (cell.arrayRange) model.arrayRange = cell.arrayRange;
54643
- if (cell.originalXfIndex !== void 0) model.originalXfIndex = cell.originalXfIndex;
54758
+ if (cell.originalXfIndex !== void 0) {
54759
+ if (!hasCellXfs) {
54760
+ throw new InvalidModelError(
54761
+ `${ps.name}!${ref2} carries originalXfIndex ${cell.originalXfIndex} but the snapshot has no cellXfs table`
54762
+ );
54763
+ }
54764
+ model.originalXfIndex = cell.originalXfIndex;
54765
+ }
54644
54766
  if (cell.content.type === "rich_text") {
54645
54767
  model.richText = cell.content.parts;
54646
54768
  }
@@ -64979,7 +65101,7 @@ ${e.toString()}`);
64979
65101
  // dropped before anything looks a tag up — see `stripNsPrefix`.
64980
65102
  transformTagName: stripNsPrefix,
64981
65103
  processEntities: false,
64982
- isArray: (tagName) => tagName === "numFmt" || tagName === "font" || tagName === "fill" || tagName === "border" || tagName === "xf" || tagName === "rgbColor" || tagName === "stop"
65104
+ isArray: (tagName) => tagName === "numFmt" || tagName === "font" || tagName === "fill" || tagName === "border" || tagName === "xf" || tagName === "dxf" || tagName === "rgbColor" || tagName === "stop"
64983
65105
  });
64984
65106
  function parseStyles(stylesXml, theme) {
64985
65107
  const doc3 = xmlParser3.parse(stylesXml);
@@ -65078,7 +65200,18 @@ ${e.toString()}`);
65078
65200
  if (!cellXfs[xfIndex].applyNumFmt && fmtId === 0) return "General";
65079
65201
  return customFmts.get(fmtId) ?? BUILTIN_NUM_FMTS[fmtId] ?? "General";
65080
65202
  }
65081
- return { resolveStyle, getNumFmt, indexedColors };
65203
+ let resolvedXfs;
65204
+ return {
65205
+ resolveStyle,
65206
+ getNumFmt,
65207
+ hasXf: (xfIndex) => xfIndex >= 0 && xfIndex < cellXfs.length,
65208
+ indexedColors,
65209
+ dxfs: parseDxfs(root, theme, indexedColors),
65210
+ get cellXfs() {
65211
+ resolvedXfs ?? (resolvedXfs = cellXfs.map((_, i3) => ({ style: resolveStyle(i3), numFmtCode: getNumFmt(i3) })));
65212
+ return resolvedXfs;
65213
+ }
65214
+ };
65082
65215
  }
65083
65216
  function parseIndexedColors(root) {
65084
65217
  const result = [...DEFAULT_INDEXED_COLORS];
@@ -65218,6 +65351,29 @@ ${e.toString()}`);
65218
65351
  color: colorEl ? parseColorElement(colorEl) : void 0
65219
65352
  };
65220
65353
  }
65354
+ function parseDxfs(root, theme, indexedColors) {
65355
+ const dxfsEl = root["dxfs"];
65356
+ const dxfArr = dxfsEl?.["dxf"];
65357
+ if (!dxfArr) return [];
65358
+ return dxfArr.map((el) => {
65359
+ const style = {};
65360
+ const fontEl = el["font"]?.[0];
65361
+ if (fontEl) {
65362
+ const font = parseFont(fontEl);
65363
+ if (font.bold) style.fontBold = true;
65364
+ if (font.italic) style.fontItalic = true;
65365
+ const fontColor = resolveColor(font.color, theme, indexedColors, "font");
65366
+ if (fontColor) style.fontColor = prefixHex(fontColor);
65367
+ }
65368
+ const fillEl = el["fill"]?.[0];
65369
+ if (fillEl) {
65370
+ const fill = parseFill(fillEl);
65371
+ const bgHex = resolveColor(fill.bgColor ?? fill.fgColor, theme, indexedColors, "fill");
65372
+ if (bgHex) style.backgroundColor = prefixHex(bgHex);
65373
+ }
65374
+ return style;
65375
+ });
65376
+ }
65221
65377
  function parseCellXfs(root) {
65222
65378
  const xfsEl = root["cellXfs"];
65223
65379
  if (!xfsEl) return [];
@@ -65303,7 +65459,10 @@ ${e.toString()}`);
65303
65459
  return {
65304
65460
  resolveStyle: () => ({}),
65305
65461
  getNumFmt: () => "General",
65306
- indexedColors: [...DEFAULT_INDEXED_COLORS]
65462
+ hasXf: () => false,
65463
+ indexedColors: [...DEFAULT_INDEXED_COLORS],
65464
+ dxfs: [],
65465
+ cellXfs: []
65307
65466
  };
65308
65467
  }
65309
65468
 
@@ -67074,7 +67233,13 @@ ${e.toString()}`);
67074
67233
  return { sheets: [emptySheet("Sheet1")], activeSheetIndex: 0 };
67075
67234
  }
67076
67235
  if (activeSheetIndex >= sheets.length) activeSheetIndex = 0;
67077
- const result = { sheets, activeSheetIndex };
67236
+ const result = {
67237
+ sheets,
67238
+ activeSheetIndex,
67239
+ get cellXfs() {
67240
+ return styles.cellXfs;
67241
+ }
67242
+ };
67078
67243
  if (workbookInfo.namedRanges.size > 0) {
67079
67244
  result.namedRanges = workbookInfo.namedRanges;
67080
67245
  }