@alosha/xlsx 0.3.1 → 0.4.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/README.md CHANGED
@@ -118,7 +118,7 @@ functions accept a `ReadOptions` (reserved for future use).
118
118
  | Multiple worksheets, sheet state (visible / hidden / veryHidden) | ✅ | ✅ | |
119
119
  | Workbook & sheet views (freeze/split, zoom, active tab) | ✅ | ✅ | round-trip as data |
120
120
  | Workbook metadata (creator, dates, title, …) | ✅ | ✅ | |
121
- | Hyperlink cells | ⚠️ | ⚠️ | round-trip as display **text** see limitations |
121
+ | Hyperlink cells (external URL/mailto) | | | real OOXML relationship, round-trips as `{ text, hyperlink, tooltip? }`; internal `location=` links deferred |
122
122
  | Streaming read/write | ❌ | ❌ | deferred |
123
123
  | Drawings / images / charts | ❌ | ❌ | deferred |
124
124
  | Data validation, conditional formatting, comments | ❌ | ❌ | deferred |
@@ -138,7 +138,6 @@ behave as you'd expect. Common differences to watch for:
138
138
  - **Clearer row inheritance.** Instead of ExcelJS's `'i' | 'o' | 'i+' | 'o+'` DSL, `addRow` /
139
139
  `insertRow` take `{ inheritFrom: "above" | "below", includeEmpty?: boolean }`.
140
140
  - **ESM-first.** The package is ESM with a CJS fallback; prefer `import` over `require`.
141
- - **Hyperlinks round-trip as text** (see below).
142
141
 
143
142
  Not everything ExcelJS does is implemented yet — check the feature matrix and limitations before
144
143
  porting a workbook that leans on drawings, streaming, or data validation.
@@ -151,9 +150,10 @@ behavioural notes to check before porting.
151
150
 
152
151
  ## Known limitations
153
152
 
154
- - **Hyperlink cells round-trip as their display text.** Setting a `{ text, hyperlink }` value
155
- writes the visible text; the clickable link relationship is not yet persisted, and reading a
156
- workbook with hyperlinks yields the text only. Real hyperlink relationships are planned.
153
+ - **Internal (in-workbook) hyperlinks are deferred.** External `http(s)`/`mailto` cell hyperlinks
154
+ round-trip as real OOXML relationships (`{ text, hyperlink, tooltip? }`). Links to a location
155
+ within the same workbook (ExcelJS's `location="Sheet2!A1"` form, no external relationship) aren't
156
+ written or read yet.
157
157
  - **No streaming.** Reading and writing are buffer-based (the whole workbook lives in memory). A
158
158
  streaming API for very large sheets is deferred.
159
159
  - **No drawings, images, or charts.** These parts are ignored on read and not emitted on write.
@@ -1692,7 +1692,8 @@ var REL_TYPE = {
1692
1692
  worksheet: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
1693
1693
  styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
1694
1694
  sharedStrings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
1695
- theme: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"
1695
+ theme: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme",
1696
+ hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
1696
1697
  };
1697
1698
  var PART_PATH = {
1698
1699
  contentTypes: "[Content_Types].xml",
@@ -1993,11 +1994,23 @@ function openPackage(bytes) {
1993
1994
  relsMap.set(rel.id, { type: rel.type, target: resolveRelTarget(workbookDir, rel.target) });
1994
1995
  }
1995
1996
  }
1997
+ const partRels = (partPath) => {
1998
+ const dir = dirname(partPath);
1999
+ const relsXml = text(relsPathFor(partPath));
2000
+ const map = /* @__PURE__ */ new Map();
2001
+ if (relsXml === void 0) return map;
2002
+ for (const rel of parseRels(relsXml)) {
2003
+ const target = rel.mode === "External" ? rel.target : resolveRelTarget(dir, rel.target);
2004
+ map.set(rel.id, { type: rel.type, target, mode: rel.mode });
2005
+ }
2006
+ return map;
2007
+ };
1996
2008
  return {
1997
2009
  entry,
1998
2010
  text,
1999
2011
  workbookRels: () => relsMap,
2000
- workbookPath: () => workbookPath
2012
+ workbookPath: () => workbookPath,
2013
+ partRels
2001
2014
  };
2002
2015
  }
2003
2016
  function readZip(bytes) {
@@ -2019,7 +2032,8 @@ function parseRels(xml) {
2019
2032
  rels.push({
2020
2033
  id: child.attrs.Id ?? "",
2021
2034
  type: child.attrs.Type ?? "",
2022
- target: child.attrs.Target ?? ""
2035
+ target: child.attrs.Target ?? "",
2036
+ mode: child.attrs.TargetMode === "External" ? "External" : "Internal"
2023
2037
  });
2024
2038
  });
2025
2039
  }
@@ -2923,6 +2937,7 @@ function parseWorksheet(ws, xml, ctx) {
2923
2937
  while (ev && !(ev.kind === "open" && localName(ev.name) === "worksheet")) ev = c.next();
2924
2938
  if (ev?.kind !== "open" || ev.selfClosing) return;
2925
2939
  const merges = [];
2940
+ const hyperlinks = [];
2926
2941
  c.eachChild(ev.name, (child) => {
2927
2942
  switch (localName(child.name)) {
2928
2943
  case "cols":
@@ -2934,11 +2949,17 @@ function parseWorksheet(ws, xml, ctx) {
2934
2949
  case "mergeCells":
2935
2950
  if (!child.selfClosing) collectMerges(c, child.name, merges);
2936
2951
  break;
2952
+ case "hyperlinks":
2953
+ if (!child.selfClosing) collectHyperlinks(c, child.name, hyperlinks);
2954
+ break;
2937
2955
  default:
2938
2956
  if (!child.selfClosing) c.textUntilClose(child.name);
2939
2957
  }
2940
2958
  });
2941
2959
  for (const ref of merges) ws.mergeCells(ref);
2960
+ if (ctx.rels) {
2961
+ for (const link of hyperlinks) applyHyperlink(ws, link, ctx.rels);
2962
+ }
2942
2963
  }
2943
2964
  function parseCols(c, container, ws, ctx) {
2944
2965
  c.eachChild(container, (col) => {
@@ -3079,6 +3100,35 @@ function collectMerges(c, container, merges) {
3079
3100
  }
3080
3101
  });
3081
3102
  }
3103
+ function collectHyperlinks(c, container, hyperlinks) {
3104
+ c.eachChild(container, (child) => {
3105
+ if (localName(child.name) !== "hyperlink") {
3106
+ if (!child.selfClosing) c.textUntilClose(child.name);
3107
+ return;
3108
+ }
3109
+ const ref = child.attrs.ref;
3110
+ const rId = relId2(child);
3111
+ if (ref === void 0 || rId === "") return;
3112
+ const tooltip = child.attrs.tooltip;
3113
+ hyperlinks.push(tooltip !== void 0 ? { ref, rId, tooltip } : { ref, rId });
3114
+ });
3115
+ }
3116
+ function applyHyperlink(ws, link, rels) {
3117
+ const rel = rels.get(link.rId);
3118
+ if (!rel) return;
3119
+ const topLeft = link.ref.split(":")[0] ?? link.ref;
3120
+ const cell = ws.getCell(topLeft);
3121
+ const text = typeof cell.value === "string" ? cell.value : "";
3122
+ cell.value = link.tooltip !== void 0 ? { text, hyperlink: rel.target, tooltip: link.tooltip } : { text, hyperlink: rel.target };
3123
+ }
3124
+ function relId2(el) {
3125
+ const direct = el.attrs["r:id"];
3126
+ if (direct !== void 0) return direct;
3127
+ for (const key of Object.keys(el.attrs)) {
3128
+ if (localName(key) === "id") return el.attrs[key] ?? "";
3129
+ }
3130
+ return "";
3131
+ }
3082
3132
  function decodeBool(raw) {
3083
3133
  if (raw === "1" || raw === "true" || raw === "TRUE") return true;
3084
3134
  if (raw === "0" || raw === "false" || raw === "FALSE") return false;
@@ -3116,7 +3166,9 @@ function readWorkbookBuffer(bytes, _options) {
3116
3166
  const target = rels.get(sheet.rId)?.target;
3117
3167
  if (target === void 0) continue;
3118
3168
  const sheetXml = pkg.text(target);
3119
- if (sheetXml !== void 0) parseWorksheet(ws, sheetXml, ctx);
3169
+ if (sheetXml !== void 0) {
3170
+ parseWorksheet(ws, sheetXml, { ...ctx, rels: pkg.partRels(target) });
3171
+ }
3120
3172
  }
3121
3173
  const coreXml = pkg.text(PART_PATH.coreProps);
3122
3174
  if (coreXml !== void 0) applyCoreProps(wb, coreXml);
@@ -3382,6 +3434,22 @@ function renderWorkbookRels(rels) {
3382
3434
  return w.toString();
3383
3435
  }
3384
3436
 
3437
+ // src/xlsx/parts/worksheet-rels.ts
3438
+ function renderWorksheetRels(rels) {
3439
+ const w = new XmlWriter();
3440
+ w.open("Relationships", { xmlns: NS.packageRels });
3441
+ for (const rel of rels) {
3442
+ w.leaf("Relationship", {
3443
+ Id: rel.rId,
3444
+ Type: REL_TYPE.hyperlink,
3445
+ Target: rel.target,
3446
+ TargetMode: rel.mode
3447
+ });
3448
+ }
3449
+ w.close("Relationships");
3450
+ return w.toString();
3451
+ }
3452
+
3385
3453
  // src/xlsx/parts/worksheet.ts
3386
3454
  var DEFAULT_DATE_NUMFMT = "mm-dd-yy";
3387
3455
  function renderWorksheet(ws, ctx) {
@@ -3399,11 +3467,13 @@ function renderWorksheet(ws, ctx) {
3399
3467
  defaultRowHeight: model.properties.defaultRowHeight ?? 15,
3400
3468
  defaultColWidth: model.properties.defaultColWidth
3401
3469
  });
3470
+ const hyperlinks = [];
3402
3471
  renderCols(w, model.columns, ctx);
3403
- renderSheetData(w, model.rows, ctx);
3472
+ renderSheetData(w, model.rows, ctx, hyperlinks);
3404
3473
  renderMergeCells(w, model.merges);
3474
+ const rels = renderHyperlinks(w, hyperlinks);
3405
3475
  w.close("worksheet");
3406
- return w.toString();
3476
+ return { xml: w.toString(), rels };
3407
3477
  }
3408
3478
  function renderCols(w, columns, ctx) {
3409
3479
  if (!columns || columns.length === 0) return;
@@ -3429,13 +3499,13 @@ function renderCols(w, columns, ctx) {
3429
3499
  for (const f of fragments) w.raw(f);
3430
3500
  w.close("cols");
3431
3501
  }
3432
- function renderSheetData(w, rows, ctx) {
3502
+ function renderSheetData(w, rows, ctx, hyperlinks) {
3433
3503
  w.open("sheetData");
3434
3504
  for (const row of rows) {
3435
3505
  const styleId = row.style ? ctx.styles.addStyle(row.style) : 0;
3436
3506
  let cellsXml = "";
3437
3507
  for (const cell of row.cells) {
3438
- cellsXml += renderCell(cell, ctx);
3508
+ cellsXml += renderCell(cell, ctx, hyperlinks);
3439
3509
  }
3440
3510
  const hasAttrs = row.height !== void 0 || row.hidden || (row.outlineLevel ?? 0) !== 0 || styleId !== 0;
3441
3511
  if (cellsXml === "" && !hasAttrs) continue;
@@ -3453,7 +3523,7 @@ function renderSheetData(w, rows, ctx) {
3453
3523
  }
3454
3524
  w.close("sheetData");
3455
3525
  }
3456
- function renderCell(cell, ctx) {
3526
+ function renderCell(cell, ctx, hyperlinks) {
3457
3527
  const styleId = cell.style ? ctx.styles.addStyle(cell.style) : 0;
3458
3528
  const r = cell.address;
3459
3529
  switch (cell.type) {
@@ -3472,8 +3542,11 @@ function renderCell(cell, ctx) {
3472
3542
  return valueCell(r, styleId, "e", cell.value.error);
3473
3543
  case 2 /* String */:
3474
3544
  return stringCell(r, styleId, cell.value, ctx);
3475
- case 7 /* Hyperlink */:
3476
- return stringCell(r, styleId, cell.value.text, ctx);
3545
+ case 7 /* Hyperlink */: {
3546
+ const link = cell.value;
3547
+ hyperlinks.push({ ref: r, target: link.hyperlink, tooltip: link.tooltip });
3548
+ return stringCell(r, styleId, link.text, ctx);
3549
+ }
3477
3550
  case 6 /* RichText */:
3478
3551
  return richTextCell(r, styleId, cell.value, ctx);
3479
3552
  case 8 /* Formula */:
@@ -3547,6 +3620,18 @@ function renderMergeCells(w, merges) {
3547
3620
  for (const ref of merges) w.leaf("mergeCell", { ref });
3548
3621
  w.close("mergeCells");
3549
3622
  }
3623
+ function renderHyperlinks(w, hyperlinks) {
3624
+ if (hyperlinks.length === 0) return void 0;
3625
+ const rels = [];
3626
+ w.open("hyperlinks");
3627
+ hyperlinks.forEach((h, i) => {
3628
+ const rId = `rId${i + 1}`;
3629
+ w.leaf("hyperlink", { ref: h.ref, "r:id": rId, tooltip: h.tooltip });
3630
+ rels.push({ rId, target: h.target, mode: "External" });
3631
+ });
3632
+ w.close("hyperlinks");
3633
+ return renderWorksheetRels(rels);
3634
+ }
3550
3635
 
3551
3636
  // src/xlsx/write-workbook.ts
3552
3637
  function writeWorkbookBuffer(wb, options) {
@@ -3610,7 +3695,10 @@ function writeWorkbookBuffer(wb, options) {
3610
3695
  put(PART_PATH.styles, styles.toXml());
3611
3696
  if (sharedStringsXml !== void 0) put(PART_PATH.sharedStrings, sharedStringsXml);
3612
3697
  put(PART_PATH.theme, renderTheme());
3613
- for (const [file, xml] of worksheetXml) put(`xl/worksheets/${file}`, xml);
3698
+ for (const [file, { xml, rels }] of worksheetXml) {
3699
+ put(`xl/worksheets/${file}`, xml);
3700
+ if (rels !== void 0) put(`xl/worksheets/_rels/${file}.rels`, rels);
3701
+ }
3614
3702
  return writeZipArchive(entries);
3615
3703
  }
3616
3704
  async function writeWorkbookFile(wb, path, options) {
@@ -3659,5 +3747,5 @@ Object.defineProperty(Workbook.prototype, "xlsx", {
3659
3747
  var version = "0.0.0";
3660
3748
 
3661
3749
  export { AloshaXlsxError, Cell, CellType, Column, DEFAULT_COLUMN_WIDTH, DuplicateWorksheetNameError, FormulaType, InvalidAddressError, InvalidCellValueError, InvalidPackageError, InvalidRangeError, InvalidRowNumberError, InvalidWorksheetNameError, MergeConflictError, Range, ReservedWorksheetNameError, Row, UnsupportedFeatureError, Workbook, Worksheet, WorksheetNameTooLongError, colLetterToNumber, colNumberToLetter, createSharedStrings, createStyles, dateToSerial, decodeAddress, decodeEx, detectType, effectiveType, encodeAddress, fromStored, isAloshaXlsxError, readWorkbookBuffer, readWorkbookFile, resolveCellStyle, serialToDate, toCsv, toStored, toText, validateAddress, version, writeWorkbookBuffer, writeWorkbookFile };
3662
- //# sourceMappingURL=chunk-6XXKAKLS.js.map
3663
- //# sourceMappingURL=chunk-6XXKAKLS.js.map
3750
+ //# sourceMappingURL=chunk-C67HH2MR.js.map
3751
+ //# sourceMappingURL=chunk-C67HH2MR.js.map