@office-kit/xlsx 0.16.0 → 0.18.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.
Files changed (87) hide show
  1. package/README.md +89 -4
  2. package/dist/cell/cell.d.ts +12 -19
  3. package/dist/{cell-Df3cKRIl.mjs → cell-B5OUUdfI.mjs} +13 -7
  4. package/dist/cell-B5OUUdfI.mjs.map +1 -0
  5. package/dist/{cell-style-CNsET6WU.mjs → cell-style-CbB4nXTY.mjs} +11 -4
  6. package/dist/cell-style-CbB4nXTY.mjs.map +1 -0
  7. package/dist/cell.mjs +1 -1
  8. package/dist/{coordinate-Bt5Gni2I.mjs → coordinate-CxC88uBk.mjs} +31 -2
  9. package/dist/coordinate-CxC88uBk.mjs.map +1 -0
  10. package/dist/{defined-names-Ctu3F6ls.mjs → defined-names-Cmp8rbvI.mjs} +2 -2
  11. package/dist/{defined-names-Ctu3F6ls.mjs.map → defined-names-Cmp8rbvI.mjs.map} +1 -1
  12. package/dist/{differential-BPKa7wXi.mjs → differential-QLaKEiBk.mjs} +2 -2
  13. package/dist/{differential-BPKa7wXi.mjs.map → differential-QLaKEiBk.mjs.map} +1 -1
  14. package/dist/{drawing-ZJ3h4VHD.mjs → drawing-Cv6UtInY.mjs} +2 -2
  15. package/dist/{drawing-ZJ3h4VHD.mjs.map → drawing-Cv6UtInY.mjs.map} +1 -1
  16. package/dist/drawing.mjs +1 -1
  17. package/dist/{escape-rnWSPpWY.mjs → escape-rA5UpibT.mjs} +26 -3
  18. package/dist/escape-rA5UpibT.mjs.map +1 -0
  19. package/dist/io/load.d.ts +30 -0
  20. package/dist/io.mjs +2 -2
  21. package/dist/{iterparse-DZE0zy2M.mjs → iterparse-CZoRXvZW.mjs} +2 -2
  22. package/dist/{iterparse-DZE0zy2M.mjs.map → iterparse-CZoRXvZW.mjs.map} +1 -1
  23. package/dist/{load-B1sK-LjT.mjs → load-BpnCPv1S.mjs} +84 -45
  24. package/dist/load-BpnCPv1S.mjs.map +1 -0
  25. package/dist/{manifest-pLmx7KYh.mjs → manifest-9p0ccqmR.mjs} +10 -11
  26. package/dist/manifest-9p0ccqmR.mjs.map +1 -0
  27. package/dist/node.mjs +1 -1
  28. package/dist/packaging.mjs +1 -1
  29. package/dist/{parser-By6RWZVW.mjs → parser-B1tB9YaE.mjs} +15 -5
  30. package/dist/parser-B1tB9YaE.mjs.map +1 -0
  31. package/dist/{reader-DHMxLBQV.mjs → reader-pC7bxYAs.mjs} +30 -7
  32. package/dist/reader-pC7bxYAs.mjs.map +1 -0
  33. package/dist/{save-DmdWwxmw.mjs → save-1Z3344nz.mjs} +8 -8
  34. package/dist/{save-DmdWwxmw.mjs.map → save-1Z3344nz.mjs.map} +1 -1
  35. package/dist/schema.mjs +1 -1
  36. package/dist/{serialize-BC2Wu3bR.mjs → serialize-BtxgssFK.mjs} +24 -7
  37. package/dist/serialize-BtxgssFK.mjs.map +1 -0
  38. package/dist/{serializer-C6nm0mRJ.mjs → serializer-N6XIwsgp.mjs} +3 -3
  39. package/dist/{serializer-C6nm0mRJ.mjs.map → serializer-N6XIwsgp.mjs.map} +1 -1
  40. package/dist/streaming.mjs +129 -77
  41. package/dist/streaming.mjs.map +1 -1
  42. package/dist/styles/display-text.d.ts +95 -0
  43. package/dist/styles/format-code.d.ts +125 -0
  44. package/dist/styles/index.d.ts +1 -0
  45. package/dist/styles/numbers.d.ts +7 -0
  46. package/dist/styles.mjs +1161 -4
  47. package/dist/styles.mjs.map +1 -0
  48. package/dist/{stylesheet-writer-oXSuGnJy.mjs → stylesheet-writer-BEn-0P0x.mjs} +480 -173
  49. package/dist/stylesheet-writer-BEn-0P0x.mjs.map +1 -0
  50. package/dist/{table-BrP0RwUd.mjs → table-BxOLUZs3.mjs} +2 -2
  51. package/dist/{table-BrP0RwUd.mjs.map → table-BxOLUZs3.mjs.map} +1 -1
  52. package/dist/{tree-BX-sRRVp.mjs → tree-CspNjuYl.mjs} +12 -2
  53. package/dist/tree-CspNjuYl.mjs.map +1 -0
  54. package/dist/utils/cell-number.d.ts +10 -0
  55. package/dist/utils/coordinate.d.ts +11 -0
  56. package/dist/utils/escape.d.ts +17 -0
  57. package/dist/utils/xsd-boolean.d.ts +9 -0
  58. package/dist/utils.mjs +2 -2
  59. package/dist/workbook/index.d.ts +1 -1
  60. package/dist/workbook/workbook.d.ts +9 -46
  61. package/dist/{workbook-DOTFzCpz.mjs → workbook-tG9oYtzU.mjs} +50 -13
  62. package/dist/workbook-tG9oYtzU.mjs.map +1 -0
  63. package/dist/workbook.mjs +3 -3
  64. package/dist/worksheet/index.d.ts +1 -1
  65. package/dist/worksheet/worksheet.d.ts +55 -14
  66. package/dist/{worksheet-B2NOQ6dM.mjs → worksheet-DLMyDD7W.mjs} +87 -22
  67. package/dist/worksheet-DLMyDD7W.mjs.map +1 -0
  68. package/dist/worksheet.mjs +3 -3
  69. package/dist/xml/namespaces.d.ts +7 -0
  70. package/dist/xml/parser.d.ts +12 -0
  71. package/dist/xml/strict-package.d.ts +14 -0
  72. package/dist/xml.mjs +5 -5
  73. package/dist/zip.mjs +1 -1
  74. package/package.json +1 -1
  75. package/dist/cell-Df3cKRIl.mjs.map +0 -1
  76. package/dist/cell-style-CNsET6WU.mjs.map +0 -1
  77. package/dist/coordinate-Bt5Gni2I.mjs.map +0 -1
  78. package/dist/escape-rnWSPpWY.mjs.map +0 -1
  79. package/dist/load-B1sK-LjT.mjs.map +0 -1
  80. package/dist/manifest-pLmx7KYh.mjs.map +0 -1
  81. package/dist/parser-By6RWZVW.mjs.map +0 -1
  82. package/dist/reader-DHMxLBQV.mjs.map +0 -1
  83. package/dist/serialize-BC2Wu3bR.mjs.map +0 -1
  84. package/dist/stylesheet-writer-oXSuGnJy.mjs.map +0 -1
  85. package/dist/tree-BX-sRRVp.mjs.map +0 -1
  86. package/dist/workbook-DOTFzCpz.mjs.map +0 -1
  87. package/dist/worksheet-B2NOQ6dM.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"drawing-ZJ3h4VHD.mjs","names":[],"sources":["../src/drawing/image.ts","../src/drawing/anchor.ts","../src/drawing/drawing.ts"],"sourcesContent":["// Image embedding.\n//\n// `XlsxImage` is the workbook-level handle for any image referenced from a\n// worksheet drawing (or chart `<a:blipFill>`). Bytes are kept verbatim so\n// re-saving never re-encodes; format / width / height are detected from the\n// file header so callers don't have to specify them.\n\nimport { OpenXmlIoError } from '../utils/exceptions.js';\n\nexport type XlsxImageFormat = 'png' | 'jpeg' | 'gif' | 'bmp' | 'webp' | 'tiff' | 'svg' | 'emf' | 'wmf';\n\n/**\n * Map from image format to the Content-Types Default extension that Excel uses\n * in `[Content_Types].xml`. SVG / EMF / WMF use the same extension as the\n * format string; PNG/JPEG/GIF/BMP do too. WebP and TIFF use their canonical\n * short names.\n */\nexport const IMAGE_FORMAT_EXTENSION: Readonly<Record<XlsxImageFormat, string>> = {\n png: 'png',\n jpeg: 'jpeg',\n gif: 'gif',\n bmp: 'bmp',\n webp: 'webp',\n tiff: 'tiff',\n svg: 'svg',\n emf: 'emf',\n wmf: 'wmf',\n};\n\n/** Map from image format to its IANA `image/*` MIME type. */\nexport const IMAGE_FORMAT_MIME: Readonly<Record<XlsxImageFormat, string>> = {\n png: 'image/png',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n bmp: 'image/bmp',\n webp: 'image/webp',\n tiff: 'image/tiff',\n svg: 'image/svg+xml',\n emf: 'image/x-emf',\n wmf: 'image/x-wmf',\n};\n\nexport interface XlsxImage {\n bytes: Uint8Array;\n format: XlsxImageFormat;\n /** Pixel width. Zero when dimensions can't be determined for the format. */\n width: number;\n /** Pixel height. Zero when dimensions can't be determined for the format. */\n height: number;\n /** ZIP archive path, e.g. `xl/media/image3.png`. Set by the writer. */\n path?: string;\n /** rels-resolved id used by `<a:blip r:embed=\"...\">`. Set by the loader. */\n rId?: string;\n}\n\n// ---- Magic-byte format detection ------------------------------------------\n\nconst startsWith = (bytes: Uint8Array, sig: ReadonlyArray<number>, offset = 0): boolean => {\n if (bytes.length < offset + sig.length) return false;\n for (let i = 0; i < sig.length; i++) {\n if (bytes[offset + i] !== sig[i]) return false;\n }\n return true;\n};\n\nconst PNG_SIG = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];\nconst JPEG_SIG = [0xff, 0xd8, 0xff];\nconst GIF87 = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61];\nconst GIF89 = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];\nconst BMP_SIG = [0x42, 0x4d];\nconst RIFF_SIG = [0x52, 0x49, 0x46, 0x46];\nconst WEBP_TAG = [0x57, 0x45, 0x42, 0x50];\nconst TIFF_LE = [0x49, 0x49, 0x2a, 0x00];\nconst TIFF_BE = [0x4d, 0x4d, 0x00, 0x2a];\nconst EMF_SIG = [0x01, 0x00, 0x00, 0x00];\nconst EMF_TAG = [0x20, 0x45, 0x4d, 0x46]; // ' EMF' at offset 40\nconst WMF_SIG_PLACEABLE = [0xd7, 0xcd, 0xc6, 0x9a];\nconst WMF_SIG_BARE = [0x01, 0x00, 0x09, 0x00];\n\nconst isSvg = (bytes: Uint8Array): boolean => {\n // Inspect the first ~512 bytes for an `<svg` tag (with optional XML / DOCTYPE\n // preamble).\n const head = new TextDecoder('utf-8', { fatal: false }).decode(bytes.subarray(0, Math.min(bytes.length, 512)));\n return /<svg\\b/i.test(head);\n};\n\nexport function detectImageFormat(bytes: Uint8Array): XlsxImageFormat | undefined {\n if (startsWith(bytes, PNG_SIG)) return 'png';\n if (startsWith(bytes, JPEG_SIG)) return 'jpeg';\n if (startsWith(bytes, GIF87) || startsWith(bytes, GIF89)) return 'gif';\n if (startsWith(bytes, BMP_SIG)) return 'bmp';\n if (startsWith(bytes, RIFF_SIG) && startsWith(bytes, WEBP_TAG, 8)) return 'webp';\n if (startsWith(bytes, TIFF_LE) || startsWith(bytes, TIFF_BE)) return 'tiff';\n if (startsWith(bytes, EMF_SIG) && startsWith(bytes, EMF_TAG, 40)) return 'emf';\n if (startsWith(bytes, WMF_SIG_PLACEABLE) || startsWith(bytes, WMF_SIG_BARE)) return 'wmf';\n if (isSvg(bytes)) return 'svg';\n return undefined;\n}\n\n// ---- Per-format dimension extraction --------------------------------------\n\nconst readU32BE = (bytes: Uint8Array, offset: number): number =>\n ((bytes[offset] ?? 0) << 24) |\n ((bytes[offset + 1] ?? 0) << 16) |\n ((bytes[offset + 2] ?? 0) << 8) |\n (bytes[offset + 3] ?? 0);\n\nconst readU16LE = (bytes: Uint8Array, offset: number): number => (bytes[offset] ?? 0) | ((bytes[offset + 1] ?? 0) << 8);\n\nconst readU16BE = (bytes: Uint8Array, offset: number): number => ((bytes[offset] ?? 0) << 8) | (bytes[offset + 1] ?? 0);\n\ninterface Dimensions {\n width: number;\n height: number;\n}\n\nconst pngDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n // PNG: 8-byte signature, 4-byte chunk-length, 4-byte type (\"IHDR\"), then\n // 4-byte width, 4-byte height (both big-endian).\n if (bytes.length < 24) return undefined;\n if (bytes[12] !== 0x49 || bytes[13] !== 0x48 || bytes[14] !== 0x44 || bytes[15] !== 0x52) {\n return undefined;\n }\n return { width: readU32BE(bytes, 16), height: readU32BE(bytes, 20) };\n};\n\nconst gifDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n if (bytes.length < 10) return undefined;\n return { width: readU16LE(bytes, 6), height: readU16LE(bytes, 8) };\n};\n\nconst bmpDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n if (bytes.length < 26) return undefined;\n // BITMAPINFOHEADER: width (LE int32) at offset 18, height at offset 22.\n // Height can be negative for top-down bitmaps; we report absolute pixels.\n const w = (bytes[18] ?? 0) | ((bytes[19] ?? 0) << 8) | ((bytes[20] ?? 0) << 16) | ((bytes[21] ?? 0) << 24);\n const hRaw = (bytes[22] ?? 0) | ((bytes[23] ?? 0) << 8) | ((bytes[24] ?? 0) << 16) | ((bytes[25] ?? 0) << 24);\n // Treat width as signed; for height take absolute value (top-down bitmaps\n // store negative).\n const wSigned = (w | 0) >>> 0 > 0x7fffffff ? (w | 0) - 0x100000000 : w;\n const hSigned = (hRaw | 0) >>> 0 > 0x7fffffff ? (hRaw | 0) - 0x100000000 : hRaw;\n return { width: wSigned, height: Math.abs(hSigned) };\n};\n\nconst jpegDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n // Walk the marker stream until an SOFn (FFC0..FFCF, excluding FFC4/C8/CC).\n let i = 2; // skip SOI (FFD8)\n while (i + 9 < bytes.length) {\n if (bytes[i] !== 0xff) return undefined;\n let marker = bytes[i + 1] ?? 0;\n while (marker === 0xff) {\n // Padding bytes. Advance to the next non-FF.\n i += 1;\n marker = bytes[i + 1] ?? 0;\n }\n i += 2;\n const isSof = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;\n const len = readU16BE(bytes, i);\n if (isSof) {\n // SOF segment: 2 bytes length, 1 byte precision, 2 bytes height, 2 bytes\n // width.\n if (i + 7 > bytes.length) return undefined;\n const height = readU16BE(bytes, i + 3);\n const width = readU16BE(bytes, i + 5);\n return { width, height };\n }\n i += len;\n }\n return undefined;\n};\n\nconst webpDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n // RIFF header is 12 bytes; chunk type is at offset 12.\n if (bytes.length < 30) return undefined;\n // VP8 (lossy): \"VP8 \" chunk; width / height at offset 26 / 28 (LE 14-bit each\n // + 2 unused bits).\n if (\n bytes[12] === 0x56 && // V\n bytes[13] === 0x50 && // P\n bytes[14] === 0x38 && // 8\n bytes[15] === 0x20 // space\n ) {\n const w = readU16LE(bytes, 26) & 0x3fff;\n const h = readU16LE(bytes, 28) & 0x3fff;\n return { width: w, height: h };\n }\n // VP8L (lossless): 14-bit width-1 / height-1 packed little-endian starting at\n // offset 21.\n if (\n bytes[12] === 0x56 &&\n bytes[13] === 0x50 &&\n bytes[14] === 0x38 &&\n bytes[15] === 0x4c // L\n ) {\n if (bytes.length < 25) return undefined;\n const b0 = bytes[21] ?? 0;\n const b1 = bytes[22] ?? 0;\n const b2 = bytes[23] ?? 0;\n const b3 = bytes[24] ?? 0;\n const w = (b0 | (b1 << 8)) & 0x3fff;\n const h = ((b1 >> 6) | (b2 << 2) | ((b3 & 0x0f) << 10)) & 0x3fff;\n return { width: w + 1, height: h + 1 };\n }\n // VP8X (extended): width-1 (24 bits) / height-1 (24 bits) at offset 24 / 27.\n if (\n bytes[12] === 0x56 &&\n bytes[13] === 0x50 &&\n bytes[14] === 0x38 &&\n bytes[15] === 0x58 // X\n ) {\n if (bytes.length < 30) return undefined;\n const w = (bytes[24] ?? 0) | ((bytes[25] ?? 0) << 8) | ((bytes[26] ?? 0) << 16);\n const h = (bytes[27] ?? 0) | ((bytes[28] ?? 0) << 8) | ((bytes[29] ?? 0) << 16);\n return { width: w + 1, height: h + 1 };\n }\n return undefined;\n};\n\nexport function detectImageDimensions(bytes: Uint8Array, format: XlsxImageFormat): Dimensions {\n let dims: Dimensions | undefined;\n switch (format) {\n case 'png':\n dims = pngDimensions(bytes);\n break;\n case 'jpeg':\n dims = jpegDimensions(bytes);\n break;\n case 'gif':\n dims = gifDimensions(bytes);\n break;\n case 'bmp':\n dims = bmpDimensions(bytes);\n break;\n case 'webp':\n dims = webpDimensions(bytes);\n break;\n default:\n // tiff / svg / emf / wmf — readers can fall back to 0 and Excel will\n // assign defaults.\n dims = undefined;\n }\n return dims ?? { width: 0, height: 0 };\n}\n\n// ---- Public factory --------------------------------------------------------\n\n/**\n * Build an `XlsxImage` from raw bytes. Detects the format and dimensions\n * automatically. Caller may pass an explicit `format` to override detection\n * (e.g. when bytes were read from a `data:` URL with a known MIME type) or\n * `width`/`height` to skip the parser.\n */\nexport function loadImage(\n bytes: Uint8Array,\n opts: { format?: XlsxImageFormat; width?: number; height?: number } = {},\n): XlsxImage {\n const format = opts.format ?? detectImageFormat(bytes);\n if (!format) {\n throw new OpenXmlIoError('loadImage: could not determine image format from bytes');\n }\n const dims =\n opts.width !== undefined && opts.height !== undefined\n ? { width: opts.width, height: opts.height }\n : detectImageDimensions(bytes, format);\n return { bytes, format, width: dims.width, height: dims.height };\n}\n","// DrawingML anchors.\n//\n// An anchor positions a drawing (chart / image / shape) inside a worksheet.\n// ECMA-376 §20.5.2 defines three kinds:\n//\n// absolute — fixed (x, y) and (cx, cy) in EMU; ignores cell layout. oneCell —\n// pinned at `from` cell, fixed extent. Resizes with the\n// cell only on its top-left corner.\n// twoCell — anchored to `from` and `to` cells. Resizes / moves with\n// both corners depending on `editAs`.\n//\n// Coordinates are EMU (English Metric Units). 1 inch = 914400 EMU, 1 cm =\n// 360000 EMU, 1 px = 9525 EMU at 96 dpi.\n\nimport { columnIndexFromLetter } from '../utils/coordinate.js';\nimport { OpenXmlSchemaError } from '../utils/exceptions.js';\nimport { emuFromPx } from '../utils/units.js';\n\n/** EMU = English Metric Units. Drawing coordinates are stored in EMU on the wire. */\nexport interface Point2D {\n /** Horizontal offset in EMU. */\n x: number;\n /** Vertical offset in EMU. */\n y: number;\n}\n\nexport interface PositiveSize2D {\n cx: number;\n cy: number;\n}\n\n/**\n * `from` / `to` corners reference a cell by 0-based column + row index plus an\n * EMU offset within the cell. Excel's wire format is 0-based here even though\n * cell references in formulas / sheetData are 1-based.\n */\nexport interface AnchorMarker {\n col: number;\n colOff: number;\n row: number;\n rowOff: number;\n}\n\nexport type DrawingAnchor =\n | { kind: 'absolute'; pos: Point2D; ext: PositiveSize2D }\n | { kind: 'oneCell'; from: AnchorMarker; ext: PositiveSize2D }\n | { kind: 'twoCell'; from: AnchorMarker; to: AnchorMarker; editAs?: 'twoCell' | 'oneCell' | 'absolute' };\n\nconst A1_RE = /^([A-Za-z]{1,3})([1-9][0-9]*)$/;\n\n/**\n * Convert a cell ref (\"A1\", \"C5\") to a 0-based AnchorMarker with `colOff =\n * rowOff = 0`. Throws on malformed refs.\n */\nexport function anchorMarkerFromCellRef(ref: string): AnchorMarker {\n const m = A1_RE.exec(ref);\n if (!m || m[1] === undefined || m[2] === undefined) {\n throw new OpenXmlSchemaError(`anchorMarkerFromCellRef: invalid coordinate \"${ref}\"`);\n }\n const col = columnIndexFromLetter(m[1]) - 1; // 0-based\n const row = Number.parseInt(m[2], 10) - 1; // 0-based\n return { col, colOff: 0, row, rowOff: 0 };\n}\n\n/** Build an absolute anchor from an (x, y, cx, cy) EMU bundle. */\nexport function makeAbsoluteAnchor(opts: { x: number; y: number; cx: number; cy: number }): DrawingAnchor {\n return { kind: 'absolute', pos: { x: opts.x, y: opts.y }, ext: { cx: opts.cx, cy: opts.cy } };\n}\n\n/** Build a one-cell anchor pinned at `from` with an explicit pixel extent. */\nexport function makeOneCellAnchor(opts: {\n from: string | AnchorMarker;\n widthPx: number;\n heightPx: number;\n}): DrawingAnchor {\n const from = typeof opts.from === 'string' ? anchorMarkerFromCellRef(opts.from) : opts.from;\n return {\n kind: 'oneCell',\n from,\n ext: { cx: emuFromPx(opts.widthPx), cy: emuFromPx(opts.heightPx) },\n };\n}\n\n/**\n * Build a two-cell anchor from cell-ref pairs (or pre-built markers). Defaults\n * to `editAs='twoCell'` — drag both corners with the cells.\n */\nexport function makeTwoCellAnchor(opts: {\n from: string | AnchorMarker;\n to: string | AnchorMarker;\n editAs?: 'twoCell' | 'oneCell' | 'absolute';\n}): DrawingAnchor {\n const from = typeof opts.from === 'string' ? anchorMarkerFromCellRef(opts.from) : opts.from;\n const to = typeof opts.to === 'string' ? anchorMarkerFromCellRef(opts.to) : opts.to;\n return {\n kind: 'twoCell',\n from,\n to,\n ...(opts.editAs !== undefined ? { editAs: opts.editAs } : {}),\n };\n}\n","// Spreadsheet drawing data model.\n//\n// A `Drawing` is the per-worksheet `xl/drawings/drawingN.xml` part — a list of\n// anchor entries, each carrying a content variant (chart, picture, shape,\n// connector, group). Charts and pictures are modeled; everything else is kept\n// as the verbatim source XML so it survives a round-trip untouched.\n\nimport type { ChartSpace } from '../chart/chart.js';\nimport type { CxChartSpace } from '../chart/cx/chartex.js';\nimport type { DrawingAnchor } from './anchor.js';\nimport type { ShapeProperties } from './dml/shape-properties.js';\nimport type { XlsxImage } from './image.js';\n\n/** Reference to a chart part — the chart's drawing-rels rId resolves to xl/charts/chartN.xml. */\nexport interface ChartReference {\n /** Drawing-rels rId pointing at the chart part. Populated on read; the writer assigns its own. */\n rId?: string;\n /**\n * Legacy ECMA-376 chart payload (`c:chartSpace`). Stage-1 supports BarChart\n * end-to-end; other chart kinds populate this field as their parsers /\n * writers land.\n */\n space?: ChartSpace;\n /**\n * Excel-2016 chartex payload (`cx:chartSpace`). Mutually exclusive with\n * {@link space} for any given drawing item; the parser sniffs the root\n * element and populates whichever is appropriate.\n */\n cxSpace?: CxChartSpace;\n /**\n * `true` when the resolved chart part is a chartex (`cx:`) chart. Set by the\n * package writer so the drawing emitter knows to use the chartex\n * `<a:graphicData uri>` instead of the legacy chart URI — Excel rejects the\n * workbook when the URI doesn't match the chart's actual root namespace.\n */\n isCx?: boolean;\n}\n\n/** Reference to an embedded picture inside a worksheet drawing. */\nexport interface PictureReference {\n /** Drawing-rels rId pointing at the embedded image. Populated on read; the writer assigns its own. */\n rId?: string;\n /** Resolved image bytes + metadata. Populated on read; the writer reads it back. */\n image?: XlsxImage;\n /** Picture display name (`<xdr:cNvPr name=\"...\">`). */\n name?: string;\n /** Optional alt-text description. */\n descr?: string;\n /** Hidden flag (`<xdr:cNvPr hidden=\"1\"/>`). */\n hidden?: boolean;\n /** Per-picture shape properties (extra fill / line / rotation). */\n spPr?: ShapeProperties;\n}\n\nexport interface DrawingItem {\n anchor: DrawingAnchor;\n content:\n | { kind: 'chart'; chart: ChartReference }\n | { kind: 'picture'; picture: PictureReference }\n | { kind: 'unsupported'; rawTag: string };\n /**\n * The whole source anchor element, captured verbatim. Set by the reader for\n * `unsupported` content — a plain shape, a group, a connector — because the\n * model has no slot for its geometry, text or `<xdr:clientData>` attributes.\n * When present the writer emits it as-is instead of rebuilding the anchor\n * from {@link anchor} + {@link content}.\n */\n raw?: import('../xml/tree.js').XmlNode;\n}\n\nexport interface Drawing {\n items: DrawingItem[];\n /**\n * `<xdr:wsDr>` children that aren't anchors — in practice Excel's\n * `<mc:AlternateContent>` wrapper around an anchor that needs a newer\n * feature. Kept verbatim together with the `items` index they sat before, so\n * document order (which is z-order) survives a round-trip.\n */\n rawChildren?: Array<{ beforeItem: number; node: import('../xml/tree.js').XmlNode }>;\n /**\n * Drawing-rels entries the verbatim nodes above reference (`r:embed` on a\n * picture inside a group, `r:id` on a shape's hyperlink, …). Carried over\n * from the source part under their original ids so those references still\n * resolve after a re-save; the writer allocates its own ids around them.\n */\n rawRels?: import('../packaging/relationships.js').Relationships;\n}\n\nexport function makeDrawing(items: DrawingItem[] = []): Drawing {\n return { items };\n}\n\nexport function makeChartDrawingItem(anchor: DrawingAnchor, chart: ChartReference = {}): DrawingItem {\n return { anchor, content: { kind: 'chart', chart } };\n}\n\nexport function makePictureDrawingItem(anchor: DrawingAnchor, picture: PictureReference | XlsxImage): DrawingItem {\n // Distinguish raw image bytes from a full PictureReference by checking the\n // discriminator: XlsxImage carries `format`, PictureReference doesn't.\n const ref: PictureReference = 'format' in picture ? { image: picture as XlsxImage } : (picture as PictureReference);\n return { anchor, content: { kind: 'picture', picture: ref } };\n}\n\n// ---- Worksheet ergonomic helpers ----------------------------------------\n\nimport { loadImage } from './image.js';\nimport { makeOneCellAnchor } from './anchor.js';\nimport type { Worksheet } from '../worksheet/worksheet.js';\n\n/**\n * Drop an image onto a worksheet at a single-cell anchor. Lazy-allocates\n * `ws.drawing` (as `makeDrawing([])`) on first call and appends a picture\n * DrawingItem.\n *\n * `image` accepts either an `XlsxImage` (already loaded via `loadImage`) or raw\n * image bytes — in the bytes case, this helper sniffs the format with\n * `loadImage` itself.\n *\n * `at` is a cell ref like `\"C3\"`. Override `widthPx` / `heightPx` to scale;\n * otherwise the helper uses 96×96 defaults that look fine for typical icons.\n */\nexport const addImageAt = (\n ws: Worksheet,\n at: string,\n image: XlsxImage | Uint8Array,\n opts: { widthPx?: number; heightPx?: number } = {},\n): DrawingItem => {\n const xlsxImage: XlsxImage = image instanceof Uint8Array ? loadImage(image) : image;\n const anchor = makeOneCellAnchor({\n from: at,\n widthPx: opts.widthPx ?? 96,\n heightPx: opts.heightPx ?? 96,\n });\n const item = makePictureDrawingItem(anchor, xlsxImage);\n if (!ws.drawing) ws.drawing = makeDrawing([]);\n ws.drawing.items.push(item);\n return item;\n};\n\n/**\n * Anchor a chart to a worksheet at a single-cell ref. Lazy-allocates\n * `ws.drawing`. `chart` is the same `ChartReference` shape `makeChart\n * DrawingItem` accepts (`{ space }` for legacy chart, `{ cxSpace }` for\n * chartex).\n */\nexport const addChartAt = (\n ws: Worksheet,\n at: string,\n chart: ChartReference,\n opts: { widthPx?: number; heightPx?: number } = {},\n): DrawingItem => {\n const anchor = makeOneCellAnchor({\n from: at,\n widthPx: opts.widthPx ?? 480,\n heightPx: opts.heightPx ?? 320,\n });\n const item = makeChartDrawingItem(anchor, chart);\n if (!ws.drawing) ws.drawing = makeDrawing([]);\n ws.drawing.items.push(item);\n return item;\n};\n\n/**\n * Read-only snapshot of every picture DrawingItem on the sheet. Returns the\n * matching items (each with its anchor + picture reference). Empty array when\n * the sheet has no drawing or only non-picture items.\n */\nexport const listImagesOnSheet = (ws: Worksheet): ReadonlyArray<DrawingItem> => {\n if (!ws.drawing) return [];\n return ws.drawing.items.filter((it) => it.content.kind === 'picture');\n};\n\n/**\n * Read-only snapshot of every chart DrawingItem on the sheet. Each item has its\n * anchor + chart reference.\n */\nexport const listChartsOnSheet = (ws: Worksheet): ReadonlyArray<DrawingItem> => {\n if (!ws.drawing) return [];\n return ws.drawing.items.filter((it) => it.content.kind === 'chart');\n};\n\n/**\n * Drop every DrawingItem from the worksheet. Returns the count removed. The\n * `ws.drawing` field itself is left in place (empty) so subsequent `addImageAt`\n * / `addChartAt` calls don't have to re-allocate.\n */\nexport const removeAllDrawingItems = (ws: Worksheet): number => {\n if (!ws.drawing) return 0;\n const n = ws.drawing.items.length;\n ws.drawing.items = [];\n return n;\n};\n\n/**\n * Drop every picture DrawingItem from the worksheet, leaving charts and any\n * other content kinds untouched. Returns the count removed.\n */\nexport const removeAllImages = (ws: Worksheet): number => {\n if (!ws.drawing) return 0;\n const before = ws.drawing.items.length;\n ws.drawing.items = ws.drawing.items.filter((it) => it.content.kind !== 'picture');\n return before - ws.drawing.items.length;\n};\n\n/**\n * Drop every chart DrawingItem from the worksheet, leaving pictures and any\n * other content kinds untouched. Returns the count removed.\n */\nexport const removeAllCharts = (ws: Worksheet): number => {\n if (!ws.drawing) return 0;\n const before = ws.drawing.items.length;\n ws.drawing.items = ws.drawing.items.filter((it) => it.content.kind !== 'chart');\n return before - ws.drawing.items.length;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"drawing-Cv6UtInY.mjs","names":[],"sources":["../src/drawing/image.ts","../src/drawing/anchor.ts","../src/drawing/drawing.ts"],"sourcesContent":["// Image embedding.\n//\n// `XlsxImage` is the workbook-level handle for any image referenced from a\n// worksheet drawing (or chart `<a:blipFill>`). Bytes are kept verbatim so\n// re-saving never re-encodes; format / width / height are detected from the\n// file header so callers don't have to specify them.\n\nimport { OpenXmlIoError } from '../utils/exceptions.js';\n\nexport type XlsxImageFormat = 'png' | 'jpeg' | 'gif' | 'bmp' | 'webp' | 'tiff' | 'svg' | 'emf' | 'wmf';\n\n/**\n * Map from image format to the Content-Types Default extension that Excel uses\n * in `[Content_Types].xml`. SVG / EMF / WMF use the same extension as the\n * format string; PNG/JPEG/GIF/BMP do too. WebP and TIFF use their canonical\n * short names.\n */\nexport const IMAGE_FORMAT_EXTENSION: Readonly<Record<XlsxImageFormat, string>> = {\n png: 'png',\n jpeg: 'jpeg',\n gif: 'gif',\n bmp: 'bmp',\n webp: 'webp',\n tiff: 'tiff',\n svg: 'svg',\n emf: 'emf',\n wmf: 'wmf',\n};\n\n/** Map from image format to its IANA `image/*` MIME type. */\nexport const IMAGE_FORMAT_MIME: Readonly<Record<XlsxImageFormat, string>> = {\n png: 'image/png',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n bmp: 'image/bmp',\n webp: 'image/webp',\n tiff: 'image/tiff',\n svg: 'image/svg+xml',\n emf: 'image/x-emf',\n wmf: 'image/x-wmf',\n};\n\nexport interface XlsxImage {\n bytes: Uint8Array;\n format: XlsxImageFormat;\n /** Pixel width. Zero when dimensions can't be determined for the format. */\n width: number;\n /** Pixel height. Zero when dimensions can't be determined for the format. */\n height: number;\n /** ZIP archive path, e.g. `xl/media/image3.png`. Set by the writer. */\n path?: string;\n /** rels-resolved id used by `<a:blip r:embed=\"...\">`. Set by the loader. */\n rId?: string;\n}\n\n// ---- Magic-byte format detection ------------------------------------------\n\nconst startsWith = (bytes: Uint8Array, sig: ReadonlyArray<number>, offset = 0): boolean => {\n if (bytes.length < offset + sig.length) return false;\n for (let i = 0; i < sig.length; i++) {\n if (bytes[offset + i] !== sig[i]) return false;\n }\n return true;\n};\n\nconst PNG_SIG = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];\nconst JPEG_SIG = [0xff, 0xd8, 0xff];\nconst GIF87 = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61];\nconst GIF89 = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];\nconst BMP_SIG = [0x42, 0x4d];\nconst RIFF_SIG = [0x52, 0x49, 0x46, 0x46];\nconst WEBP_TAG = [0x57, 0x45, 0x42, 0x50];\nconst TIFF_LE = [0x49, 0x49, 0x2a, 0x00];\nconst TIFF_BE = [0x4d, 0x4d, 0x00, 0x2a];\nconst EMF_SIG = [0x01, 0x00, 0x00, 0x00];\nconst EMF_TAG = [0x20, 0x45, 0x4d, 0x46]; // ' EMF' at offset 40\nconst WMF_SIG_PLACEABLE = [0xd7, 0xcd, 0xc6, 0x9a];\nconst WMF_SIG_BARE = [0x01, 0x00, 0x09, 0x00];\n\nconst isSvg = (bytes: Uint8Array): boolean => {\n // Inspect the first ~512 bytes for an `<svg` tag (with optional XML / DOCTYPE\n // preamble).\n const head = new TextDecoder('utf-8', { fatal: false }).decode(bytes.subarray(0, Math.min(bytes.length, 512)));\n return /<svg\\b/i.test(head);\n};\n\nexport function detectImageFormat(bytes: Uint8Array): XlsxImageFormat | undefined {\n if (startsWith(bytes, PNG_SIG)) return 'png';\n if (startsWith(bytes, JPEG_SIG)) return 'jpeg';\n if (startsWith(bytes, GIF87) || startsWith(bytes, GIF89)) return 'gif';\n if (startsWith(bytes, BMP_SIG)) return 'bmp';\n if (startsWith(bytes, RIFF_SIG) && startsWith(bytes, WEBP_TAG, 8)) return 'webp';\n if (startsWith(bytes, TIFF_LE) || startsWith(bytes, TIFF_BE)) return 'tiff';\n if (startsWith(bytes, EMF_SIG) && startsWith(bytes, EMF_TAG, 40)) return 'emf';\n if (startsWith(bytes, WMF_SIG_PLACEABLE) || startsWith(bytes, WMF_SIG_BARE)) return 'wmf';\n if (isSvg(bytes)) return 'svg';\n return undefined;\n}\n\n// ---- Per-format dimension extraction --------------------------------------\n\nconst readU32BE = (bytes: Uint8Array, offset: number): number =>\n ((bytes[offset] ?? 0) << 24) |\n ((bytes[offset + 1] ?? 0) << 16) |\n ((bytes[offset + 2] ?? 0) << 8) |\n (bytes[offset + 3] ?? 0);\n\nconst readU16LE = (bytes: Uint8Array, offset: number): number => (bytes[offset] ?? 0) | ((bytes[offset + 1] ?? 0) << 8);\n\nconst readU16BE = (bytes: Uint8Array, offset: number): number => ((bytes[offset] ?? 0) << 8) | (bytes[offset + 1] ?? 0);\n\ninterface Dimensions {\n width: number;\n height: number;\n}\n\nconst pngDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n // PNG: 8-byte signature, 4-byte chunk-length, 4-byte type (\"IHDR\"), then\n // 4-byte width, 4-byte height (both big-endian).\n if (bytes.length < 24) return undefined;\n if (bytes[12] !== 0x49 || bytes[13] !== 0x48 || bytes[14] !== 0x44 || bytes[15] !== 0x52) {\n return undefined;\n }\n return { width: readU32BE(bytes, 16), height: readU32BE(bytes, 20) };\n};\n\nconst gifDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n if (bytes.length < 10) return undefined;\n return { width: readU16LE(bytes, 6), height: readU16LE(bytes, 8) };\n};\n\nconst bmpDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n if (bytes.length < 26) return undefined;\n // BITMAPINFOHEADER: width (LE int32) at offset 18, height at offset 22.\n // Height can be negative for top-down bitmaps; we report absolute pixels.\n const w = (bytes[18] ?? 0) | ((bytes[19] ?? 0) << 8) | ((bytes[20] ?? 0) << 16) | ((bytes[21] ?? 0) << 24);\n const hRaw = (bytes[22] ?? 0) | ((bytes[23] ?? 0) << 8) | ((bytes[24] ?? 0) << 16) | ((bytes[25] ?? 0) << 24);\n // Treat width as signed; for height take absolute value (top-down bitmaps\n // store negative).\n const wSigned = (w | 0) >>> 0 > 0x7fffffff ? (w | 0) - 0x100000000 : w;\n const hSigned = (hRaw | 0) >>> 0 > 0x7fffffff ? (hRaw | 0) - 0x100000000 : hRaw;\n return { width: wSigned, height: Math.abs(hSigned) };\n};\n\nconst jpegDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n // Walk the marker stream until an SOFn (FFC0..FFCF, excluding FFC4/C8/CC).\n let i = 2; // skip SOI (FFD8)\n while (i + 9 < bytes.length) {\n if (bytes[i] !== 0xff) return undefined;\n let marker = bytes[i + 1] ?? 0;\n while (marker === 0xff) {\n // Padding bytes. Advance to the next non-FF.\n i += 1;\n marker = bytes[i + 1] ?? 0;\n }\n i += 2;\n const isSof = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;\n const len = readU16BE(bytes, i);\n if (isSof) {\n // SOF segment: 2 bytes length, 1 byte precision, 2 bytes height, 2 bytes\n // width.\n if (i + 7 > bytes.length) return undefined;\n const height = readU16BE(bytes, i + 3);\n const width = readU16BE(bytes, i + 5);\n return { width, height };\n }\n i += len;\n }\n return undefined;\n};\n\nconst webpDimensions = (bytes: Uint8Array): Dimensions | undefined => {\n // RIFF header is 12 bytes; chunk type is at offset 12.\n if (bytes.length < 30) return undefined;\n // VP8 (lossy): \"VP8 \" chunk; width / height at offset 26 / 28 (LE 14-bit each\n // + 2 unused bits).\n if (\n bytes[12] === 0x56 && // V\n bytes[13] === 0x50 && // P\n bytes[14] === 0x38 && // 8\n bytes[15] === 0x20 // space\n ) {\n const w = readU16LE(bytes, 26) & 0x3fff;\n const h = readU16LE(bytes, 28) & 0x3fff;\n return { width: w, height: h };\n }\n // VP8L (lossless): 14-bit width-1 / height-1 packed little-endian starting at\n // offset 21.\n if (\n bytes[12] === 0x56 &&\n bytes[13] === 0x50 &&\n bytes[14] === 0x38 &&\n bytes[15] === 0x4c // L\n ) {\n if (bytes.length < 25) return undefined;\n const b0 = bytes[21] ?? 0;\n const b1 = bytes[22] ?? 0;\n const b2 = bytes[23] ?? 0;\n const b3 = bytes[24] ?? 0;\n const w = (b0 | (b1 << 8)) & 0x3fff;\n const h = ((b1 >> 6) | (b2 << 2) | ((b3 & 0x0f) << 10)) & 0x3fff;\n return { width: w + 1, height: h + 1 };\n }\n // VP8X (extended): width-1 (24 bits) / height-1 (24 bits) at offset 24 / 27.\n if (\n bytes[12] === 0x56 &&\n bytes[13] === 0x50 &&\n bytes[14] === 0x38 &&\n bytes[15] === 0x58 // X\n ) {\n if (bytes.length < 30) return undefined;\n const w = (bytes[24] ?? 0) | ((bytes[25] ?? 0) << 8) | ((bytes[26] ?? 0) << 16);\n const h = (bytes[27] ?? 0) | ((bytes[28] ?? 0) << 8) | ((bytes[29] ?? 0) << 16);\n return { width: w + 1, height: h + 1 };\n }\n return undefined;\n};\n\nexport function detectImageDimensions(bytes: Uint8Array, format: XlsxImageFormat): Dimensions {\n let dims: Dimensions | undefined;\n switch (format) {\n case 'png':\n dims = pngDimensions(bytes);\n break;\n case 'jpeg':\n dims = jpegDimensions(bytes);\n break;\n case 'gif':\n dims = gifDimensions(bytes);\n break;\n case 'bmp':\n dims = bmpDimensions(bytes);\n break;\n case 'webp':\n dims = webpDimensions(bytes);\n break;\n default:\n // tiff / svg / emf / wmf — readers can fall back to 0 and Excel will\n // assign defaults.\n dims = undefined;\n }\n return dims ?? { width: 0, height: 0 };\n}\n\n// ---- Public factory --------------------------------------------------------\n\n/**\n * Build an `XlsxImage` from raw bytes. Detects the format and dimensions\n * automatically. Caller may pass an explicit `format` to override detection\n * (e.g. when bytes were read from a `data:` URL with a known MIME type) or\n * `width`/`height` to skip the parser.\n */\nexport function loadImage(\n bytes: Uint8Array,\n opts: { format?: XlsxImageFormat; width?: number; height?: number } = {},\n): XlsxImage {\n const format = opts.format ?? detectImageFormat(bytes);\n if (!format) {\n throw new OpenXmlIoError('loadImage: could not determine image format from bytes');\n }\n const dims =\n opts.width !== undefined && opts.height !== undefined\n ? { width: opts.width, height: opts.height }\n : detectImageDimensions(bytes, format);\n return { bytes, format, width: dims.width, height: dims.height };\n}\n","// DrawingML anchors.\n//\n// An anchor positions a drawing (chart / image / shape) inside a worksheet.\n// ECMA-376 §20.5.2 defines three kinds:\n//\n// absolute — fixed (x, y) and (cx, cy) in EMU; ignores cell layout. oneCell —\n// pinned at `from` cell, fixed extent. Resizes with the\n// cell only on its top-left corner.\n// twoCell — anchored to `from` and `to` cells. Resizes / moves with\n// both corners depending on `editAs`.\n//\n// Coordinates are EMU (English Metric Units). 1 inch = 914400 EMU, 1 cm =\n// 360000 EMU, 1 px = 9525 EMU at 96 dpi.\n\nimport { columnIndexFromLetter } from '../utils/coordinate.js';\nimport { OpenXmlSchemaError } from '../utils/exceptions.js';\nimport { emuFromPx } from '../utils/units.js';\n\n/** EMU = English Metric Units. Drawing coordinates are stored in EMU on the wire. */\nexport interface Point2D {\n /** Horizontal offset in EMU. */\n x: number;\n /** Vertical offset in EMU. */\n y: number;\n}\n\nexport interface PositiveSize2D {\n cx: number;\n cy: number;\n}\n\n/**\n * `from` / `to` corners reference a cell by 0-based column + row index plus an\n * EMU offset within the cell. Excel's wire format is 0-based here even though\n * cell references in formulas / sheetData are 1-based.\n */\nexport interface AnchorMarker {\n col: number;\n colOff: number;\n row: number;\n rowOff: number;\n}\n\nexport type DrawingAnchor =\n | { kind: 'absolute'; pos: Point2D; ext: PositiveSize2D }\n | { kind: 'oneCell'; from: AnchorMarker; ext: PositiveSize2D }\n | { kind: 'twoCell'; from: AnchorMarker; to: AnchorMarker; editAs?: 'twoCell' | 'oneCell' | 'absolute' };\n\nconst A1_RE = /^([A-Za-z]{1,3})([1-9][0-9]*)$/;\n\n/**\n * Convert a cell ref (\"A1\", \"C5\") to a 0-based AnchorMarker with `colOff =\n * rowOff = 0`. Throws on malformed refs.\n */\nexport function anchorMarkerFromCellRef(ref: string): AnchorMarker {\n const m = A1_RE.exec(ref);\n if (!m || m[1] === undefined || m[2] === undefined) {\n throw new OpenXmlSchemaError(`anchorMarkerFromCellRef: invalid coordinate \"${ref}\"`);\n }\n const col = columnIndexFromLetter(m[1]) - 1; // 0-based\n const row = Number.parseInt(m[2], 10) - 1; // 0-based\n return { col, colOff: 0, row, rowOff: 0 };\n}\n\n/** Build an absolute anchor from an (x, y, cx, cy) EMU bundle. */\nexport function makeAbsoluteAnchor(opts: { x: number; y: number; cx: number; cy: number }): DrawingAnchor {\n return { kind: 'absolute', pos: { x: opts.x, y: opts.y }, ext: { cx: opts.cx, cy: opts.cy } };\n}\n\n/** Build a one-cell anchor pinned at `from` with an explicit pixel extent. */\nexport function makeOneCellAnchor(opts: {\n from: string | AnchorMarker;\n widthPx: number;\n heightPx: number;\n}): DrawingAnchor {\n const from = typeof opts.from === 'string' ? anchorMarkerFromCellRef(opts.from) : opts.from;\n return {\n kind: 'oneCell',\n from,\n ext: { cx: emuFromPx(opts.widthPx), cy: emuFromPx(opts.heightPx) },\n };\n}\n\n/**\n * Build a two-cell anchor from cell-ref pairs (or pre-built markers). Defaults\n * to `editAs='twoCell'` — drag both corners with the cells.\n */\nexport function makeTwoCellAnchor(opts: {\n from: string | AnchorMarker;\n to: string | AnchorMarker;\n editAs?: 'twoCell' | 'oneCell' | 'absolute';\n}): DrawingAnchor {\n const from = typeof opts.from === 'string' ? anchorMarkerFromCellRef(opts.from) : opts.from;\n const to = typeof opts.to === 'string' ? anchorMarkerFromCellRef(opts.to) : opts.to;\n return {\n kind: 'twoCell',\n from,\n to,\n ...(opts.editAs !== undefined ? { editAs: opts.editAs } : {}),\n };\n}\n","// Spreadsheet drawing data model.\n//\n// A `Drawing` is the per-worksheet `xl/drawings/drawingN.xml` part — a list of\n// anchor entries, each carrying a content variant (chart, picture, shape,\n// connector, group). Charts and pictures are modeled; everything else is kept\n// as the verbatim source XML so it survives a round-trip untouched.\n\nimport type { ChartSpace } from '../chart/chart.js';\nimport type { CxChartSpace } from '../chart/cx/chartex.js';\nimport type { DrawingAnchor } from './anchor.js';\nimport type { ShapeProperties } from './dml/shape-properties.js';\nimport type { XlsxImage } from './image.js';\n\n/** Reference to a chart part — the chart's drawing-rels rId resolves to xl/charts/chartN.xml. */\nexport interface ChartReference {\n /** Drawing-rels rId pointing at the chart part. Populated on read; the writer assigns its own. */\n rId?: string;\n /**\n * Legacy ECMA-376 chart payload (`c:chartSpace`). Stage-1 supports BarChart\n * end-to-end; other chart kinds populate this field as their parsers /\n * writers land.\n */\n space?: ChartSpace;\n /**\n * Excel-2016 chartex payload (`cx:chartSpace`). Mutually exclusive with\n * {@link space} for any given drawing item; the parser sniffs the root\n * element and populates whichever is appropriate.\n */\n cxSpace?: CxChartSpace;\n /**\n * `true` when the resolved chart part is a chartex (`cx:`) chart. Set by the\n * package writer so the drawing emitter knows to use the chartex\n * `<a:graphicData uri>` instead of the legacy chart URI — Excel rejects the\n * workbook when the URI doesn't match the chart's actual root namespace.\n */\n isCx?: boolean;\n}\n\n/** Reference to an embedded picture inside a worksheet drawing. */\nexport interface PictureReference {\n /** Drawing-rels rId pointing at the embedded image. Populated on read; the writer assigns its own. */\n rId?: string;\n /** Resolved image bytes + metadata. Populated on read; the writer reads it back. */\n image?: XlsxImage;\n /** Picture display name (`<xdr:cNvPr name=\"...\">`). */\n name?: string;\n /** Optional alt-text description. */\n descr?: string;\n /** Hidden flag (`<xdr:cNvPr hidden=\"1\"/>`). */\n hidden?: boolean;\n /** Per-picture shape properties (extra fill / line / rotation). */\n spPr?: ShapeProperties;\n}\n\nexport interface DrawingItem {\n anchor: DrawingAnchor;\n content:\n | { kind: 'chart'; chart: ChartReference }\n | { kind: 'picture'; picture: PictureReference }\n | { kind: 'unsupported'; rawTag: string };\n /**\n * The whole source anchor element, captured verbatim. Set by the reader for\n * `unsupported` content — a plain shape, a group, a connector — because the\n * model has no slot for its geometry, text or `<xdr:clientData>` attributes.\n * When present the writer emits it as-is instead of rebuilding the anchor\n * from {@link anchor} + {@link content}.\n */\n raw?: import('../xml/tree.js').XmlNode;\n}\n\nexport interface Drawing {\n items: DrawingItem[];\n /**\n * `<xdr:wsDr>` children that aren't anchors — in practice Excel's\n * `<mc:AlternateContent>` wrapper around an anchor that needs a newer\n * feature. Kept verbatim together with the `items` index they sat before, so\n * document order (which is z-order) survives a round-trip.\n */\n rawChildren?: Array<{ beforeItem: number; node: import('../xml/tree.js').XmlNode }>;\n /**\n * Drawing-rels entries the verbatim nodes above reference (`r:embed` on a\n * picture inside a group, `r:id` on a shape's hyperlink, …). Carried over\n * from the source part under their original ids so those references still\n * resolve after a re-save; the writer allocates its own ids around them.\n */\n rawRels?: import('../packaging/relationships.js').Relationships;\n}\n\nexport function makeDrawing(items: DrawingItem[] = []): Drawing {\n return { items };\n}\n\nexport function makeChartDrawingItem(anchor: DrawingAnchor, chart: ChartReference = {}): DrawingItem {\n return { anchor, content: { kind: 'chart', chart } };\n}\n\nexport function makePictureDrawingItem(anchor: DrawingAnchor, picture: PictureReference | XlsxImage): DrawingItem {\n // Distinguish raw image bytes from a full PictureReference by checking the\n // discriminator: XlsxImage carries `format`, PictureReference doesn't.\n const ref: PictureReference = 'format' in picture ? { image: picture as XlsxImage } : (picture as PictureReference);\n return { anchor, content: { kind: 'picture', picture: ref } };\n}\n\n// ---- Worksheet ergonomic helpers ----------------------------------------\n\nimport { loadImage } from './image.js';\nimport { makeOneCellAnchor } from './anchor.js';\nimport type { Worksheet } from '../worksheet/worksheet.js';\n\n/**\n * Drop an image onto a worksheet at a single-cell anchor. Lazy-allocates\n * `ws.drawing` (as `makeDrawing([])`) on first call and appends a picture\n * DrawingItem.\n *\n * `image` accepts either an `XlsxImage` (already loaded via `loadImage`) or raw\n * image bytes — in the bytes case, this helper sniffs the format with\n * `loadImage` itself.\n *\n * `at` is a cell ref like `\"C3\"`. Override `widthPx` / `heightPx` to scale;\n * otherwise the helper uses 96×96 defaults that look fine for typical icons.\n */\nexport const addImageAt = (\n ws: Worksheet,\n at: string,\n image: XlsxImage | Uint8Array,\n opts: { widthPx?: number; heightPx?: number } = {},\n): DrawingItem => {\n const xlsxImage: XlsxImage = image instanceof Uint8Array ? loadImage(image) : image;\n const anchor = makeOneCellAnchor({\n from: at,\n widthPx: opts.widthPx ?? 96,\n heightPx: opts.heightPx ?? 96,\n });\n const item = makePictureDrawingItem(anchor, xlsxImage);\n if (!ws.drawing) ws.drawing = makeDrawing([]);\n ws.drawing.items.push(item);\n return item;\n};\n\n/**\n * Anchor a chart to a worksheet at a single-cell ref. Lazy-allocates\n * `ws.drawing`. `chart` is the same `ChartReference` shape `makeChart\n * DrawingItem` accepts (`{ space }` for legacy chart, `{ cxSpace }` for\n * chartex).\n */\nexport const addChartAt = (\n ws: Worksheet,\n at: string,\n chart: ChartReference,\n opts: { widthPx?: number; heightPx?: number } = {},\n): DrawingItem => {\n const anchor = makeOneCellAnchor({\n from: at,\n widthPx: opts.widthPx ?? 480,\n heightPx: opts.heightPx ?? 320,\n });\n const item = makeChartDrawingItem(anchor, chart);\n if (!ws.drawing) ws.drawing = makeDrawing([]);\n ws.drawing.items.push(item);\n return item;\n};\n\n/**\n * Read-only snapshot of every picture DrawingItem on the sheet. Returns the\n * matching items (each with its anchor + picture reference). Empty array when\n * the sheet has no drawing or only non-picture items.\n */\nexport const listImagesOnSheet = (ws: Worksheet): ReadonlyArray<DrawingItem> => {\n if (!ws.drawing) return [];\n return ws.drawing.items.filter((it) => it.content.kind === 'picture');\n};\n\n/**\n * Read-only snapshot of every chart DrawingItem on the sheet. Each item has its\n * anchor + chart reference.\n */\nexport const listChartsOnSheet = (ws: Worksheet): ReadonlyArray<DrawingItem> => {\n if (!ws.drawing) return [];\n return ws.drawing.items.filter((it) => it.content.kind === 'chart');\n};\n\n/**\n * Drop every DrawingItem from the worksheet. Returns the count removed. The\n * `ws.drawing` field itself is left in place (empty) so subsequent `addImageAt`\n * / `addChartAt` calls don't have to re-allocate.\n */\nexport const removeAllDrawingItems = (ws: Worksheet): number => {\n if (!ws.drawing) return 0;\n const n = ws.drawing.items.length;\n ws.drawing.items = [];\n return n;\n};\n\n/**\n * Drop every picture DrawingItem from the worksheet, leaving charts and any\n * other content kinds untouched. Returns the count removed.\n */\nexport const removeAllImages = (ws: Worksheet): number => {\n if (!ws.drawing) return 0;\n const before = ws.drawing.items.length;\n ws.drawing.items = ws.drawing.items.filter((it) => it.content.kind !== 'picture');\n return before - ws.drawing.items.length;\n};\n\n/**\n * Drop every chart DrawingItem from the worksheet, leaving pictures and any\n * other content kinds untouched. Returns the count removed.\n */\nexport const removeAllCharts = (ws: Worksheet): number => {\n if (!ws.drawing) return 0;\n const before = ws.drawing.items.length;\n ws.drawing.items = ws.drawing.items.filter((it) => it.content.kind !== 'chart');\n return before - ws.drawing.items.length;\n};\n"],"mappings":"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"}
package/dist/drawing.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as makeChartDrawingItem, c as removeAllCharts, f as makeOneCellAnchor, h as loadImage, i as listImagesOnSheet, l as removeAllDrawingItems, n as addImageAt, o as makeDrawing, r as listChartsOnSheet, s as makePictureDrawingItem, t as addChartAt, u as removeAllImages } from "./drawing-ZJ3h4VHD.mjs";
1
+ import { a as makeChartDrawingItem, c as removeAllCharts, f as makeOneCellAnchor, h as loadImage, i as listImagesOnSheet, l as removeAllDrawingItems, n as addImageAt, o as makeDrawing, r as listChartsOnSheet, s as makePictureDrawingItem, t as addChartAt, u as removeAllImages } from "./drawing-Cv6UtInY.mjs";
2
2
  import { a as makeSchemeColor, i as makeColor, n as VALUED_COLOR_MOD_KINDS, o as makeSrgbColor, r as VALUELESS_COLOR_MOD_KINDS, t as SCHEME_COLOR_NAMES } from "./colors-ovWAwnZI.mjs";
3
3
  //#region src/drawing/dml/shape-properties.ts
4
4
  const makeShapeProperties = (opts = {}) => ({
@@ -1,5 +1,6 @@
1
+ import { o as OpenXmlSchemaError } from "./exceptions-D-CFwxgm.mjs";
1
2
  //#region src/utils/escape.ts
2
- const ILLEGAL_RE = /[\x00-\x1F\ud800-\udfff]/g;
3
+ const ILLEGAL_RE = /[\x00-\x1F\u{D800}-\u{DFFF}\u{FFFE}\u{FFFF}]/gu;
3
4
  const ESCAPED_PATTERN_RE = /(_)(x[0-9A-Fa-f]{4}_)/g;
4
5
  const toHex4 = (n) => n.toString(16).toUpperCase().padStart(4, "0");
5
6
  /**
@@ -43,7 +44,29 @@ function escapeXmlText(s) {
43
44
  function escapeXmlAttr(s) {
44
45
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
45
46
  }
47
+ const UNREPRESENTABLE_RE = /[\0-\x08\x0B\x0C\x0E-\x1F\p{Cs}\uFFFE\uFFFF]/u;
48
+ /**
49
+ * Escape text for an OOXML text node the reader hands back verbatim: `<f>`,
50
+ * `<formula>`, `<formula1>`, `<formula2>` and a `t="str"` cached formula
51
+ * result. The `_xHHHH_` convention of {@link escapeCellString} must not be
52
+ * applied to these: nothing decodes them on read, so an encoded sequence would
53
+ * reach Excel as the literal text `_x000D_`.
54
+ *
55
+ * Tab and LF stay literal; CR becomes a numeric character reference, which
56
+ * survives the CR-to-LF normalisation every conforming parser applies to raw
57
+ * text. A codepoint XML 1.0 cannot represent therefore has nowhere to go and is
58
+ * rejected rather than silently rewritten.
59
+ *
60
+ * `node` and `at` name the offending value in that error (`worksheet: formula`
61
+ * / `A1`). They stay separate so that a formula-heavy sheet does not build one
62
+ * message string per cell for an error it never raises.
63
+ */
64
+ function escapeXmlTextVerbatim(s, node, at) {
65
+ const unrepresentable = UNREPRESENTABLE_RE.exec(s);
66
+ if (unrepresentable) throw new OpenXmlSchemaError(`${node} at ${at} contains U+${toHex4(s.charCodeAt(unrepresentable.index))}, which XML 1.0 cannot represent`);
67
+ return escapeXmlText(s).replace(/\r/g, "&#13;");
68
+ }
46
69
  //#endregion
47
- export { unescapeCellString as i, escapeXmlAttr as n, escapeXmlText as r, escapeCellString as t };
70
+ export { unescapeCellString as a, escapeXmlTextVerbatim as i, escapeXmlAttr as n, escapeXmlText as r, escapeCellString as t };
48
71
 
49
- //# sourceMappingURL=escape-rnWSPpWY.mjs.map
72
+ //# sourceMappingURL=escape-rA5UpibT.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-rA5UpibT.mjs","names":[],"sources":["../src/utils/escape.ts"],"sourcesContent":["// XML 1.0 cell-string escaping. Mirrors openpyxl/openpyxl/utils/escape.py.\n//\n// Excel emits cell strings with control characters and other illegal\n// XML 1.0 codepoints encoded as `_xHHHH_` (uppercase hex). The\n// underscore itself is a literal in normal text but a sequence opener\n// in escape position; an existing `_xHHHH_` in the input is therefore\n// re-escaped to `_x005F_xHHHH_` so it round-trips losslessly.\n\nimport { OpenXmlSchemaError } from './exceptions.js';\n\n// Every codepoint XML 1.0 cannot carry: the C0 controls, an unpaired\n// surrogate (which has no UTF-8 encoding), and U+FFFE / U+FFFF. A character\n// reference is no way out either, since `&#0;` is as illegal as the raw byte.\n// This range deliberately covers `\\t` (U+0009), `\\n` (U+000A) and `\\r`\n// (U+000D) even though XML 1.0 considers them legal whitespace: XML\n// parsers normalise CRLF / lone CR to LF on read, so a cell string\n// containing `\\r` would silently lose its CR without the `_x000D_`\n// encoding. openpyxl escapes the same `\\x01-\\x19` range; we add NUL\n// to keep the writer well-formed when callers feed in binary data.\n//\n// The `u` flag makes the class match code points rather than UTF-16 code\n// units, so a well-formed surrogate pair is a single character outside\n// D800-DFFF and passes through intact.\nconst ILLEGAL_RE =\n // biome-ignore lint/suspicious/noControlCharactersInRegex: by design, these are the codepoints we replace\n /[\\x00-\\x1F\\u{D800}-\\u{DFFF}\\u{FFFE}\\u{FFFF}]/gu;\nconst ESCAPED_PATTERN_RE = /(_)(x[0-9A-Fa-f]{4}_)/g;\n\nconst toHex4 = (n: number): string => n.toString(16).toUpperCase().padStart(4, '0');\n\n/**\n * Escape a string for safe storage in an OOXML cell. Already-escaped\n * sequences (`_xHHHH_`) are protected by escaping their leading\n * underscore; illegal codepoints are replaced with their `_xHHHH_`\n * representation.\n */\nexport function escapeCellString(s: string): string {\n // Re-escape any existing `_xHHHH_` so it round-trips; the underscore\n // becomes `_x005F_` and the rest of the sequence is left as-is.\n const protectedString = s.replace(ESCAPED_PATTERN_RE, '_x005F_$2');\n return protectedString.replace(ILLEGAL_RE, (ch) => `_x${toHex4(ch.charCodeAt(0))}_`);\n}\n\nconst UNESCAPE_RE = /_x([0-9A-Fa-f]{4})_/g;\n\n/**\n * Inverse of {@link escapeCellString}. Looking from left to right\n * we replace any `_xHHHH_` sequence with the corresponding code unit;\n * the protected `_x005F_` becomes a literal underscore which the\n * subsequent replacements skip safely (replace's regex is non-overlapping).\n */\nexport function unescapeCellString(s: string): string {\n return s.replace(UNESCAPE_RE, (_full, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)));\n}\n\n// ---- XML escape helpers ----------------------------------------------------\n//\n// One canonical implementation for text-node and attribute escaping. The\n// previous codebase carried three near-identical copies (one each in save.ts,\n// xml/serializer.ts, xml/stream-writer.ts); they disagreed about `>`-in-attribute\n// handling and would have drifted further apart over time. Keeping them in a\n// single place also makes future fixes (e.g. surrogate-pair scrubbing) land\n// once rather than three times.\n\n/**\n * Escape a string for safe placement in an XML text node. Replaces the three\n * codepoints that would otherwise terminate the text region or open a markup\n * sequence (`&`, `<`, `>`); `\"` and `'` are not legal markup terminators\n * inside text and stay verbatim.\n */\nexport function escapeXmlText(s: string): string {\n return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n}\n\n/**\n * Escape a string for safe placement inside a `\"`-quoted XML attribute.\n * Handles `&`, `<`, `>` and the `\"` that would otherwise close the value.\n *\n * Note: this deliberately does NOT escape `\\r` / `\\n` / `\\t` to numeric\n * character references. XML 1.0 attribute-value normalisation would\n * collapses them to spaces, but the DOM read path (fast-xml-parser) does not\n * apply that normalisation, so the literal bytes round-trip through\n * `loadWorkbook`. Leaving them literal also matches what Excel itself emits.\n */\nexport function escapeXmlAttr(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\n// The codepoints XML 1.0 cannot carry in a text node under any encoding: the\n// C0 controls apart from tab / LF / CR, unpaired surrogates, and U+FFFE / U+FFFF. A character\n// reference is no help either, since `&#0;` is as illegal as the raw byte.\n// Matching on code points (`u` flag) is what keeps a well-formed surrogate\n// pair, and so every astral character, out of `\\p{Cs}`.\nconst UNREPRESENTABLE_RE =\n // biome-ignore lint/suspicious/noControlCharactersInRegex: by design, these are the codepoints being rejected\n /[\\0-\\x08\\x0B\\x0C\\x0E-\\x1F\\p{Cs}\\uFFFE\\uFFFF]/u;\n\n/**\n * Escape text for an OOXML text node the reader hands back verbatim: `<f>`,\n * `<formula>`, `<formula1>`, `<formula2>` and a `t=\"str\"` cached formula\n * result. The `_xHHHH_` convention of {@link escapeCellString} must not be\n * applied to these: nothing decodes them on read, so an encoded sequence would\n * reach Excel as the literal text `_x000D_`.\n *\n * Tab and LF stay literal; CR becomes a numeric character reference, which\n * survives the CR-to-LF normalisation every conforming parser applies to raw\n * text. A codepoint XML 1.0 cannot represent therefore has nowhere to go and is\n * rejected rather than silently rewritten.\n *\n * `node` and `at` name the offending value in that error (`worksheet: formula`\n * / `A1`). They stay separate so that a formula-heavy sheet does not build one\n * message string per cell for an error it never raises.\n */\nexport function escapeXmlTextVerbatim(s: string, node: string, at: string): string {\n const unrepresentable = UNREPRESENTABLE_RE.exec(s);\n if (unrepresentable) {\n const codePoint = toHex4(s.charCodeAt(unrepresentable.index));\n throw new OpenXmlSchemaError(`${node} at ${at} contains U+${codePoint}, which XML 1.0 cannot represent`);\n }\n return escapeXmlText(s).replace(/\\r/g, '&#13;');\n}\n"],"mappings":";;AAuBA,MAAM,aAEJ;AACF,MAAM,qBAAqB;AAE3B,MAAM,UAAU,MAAsB,EAAE,SAAS,EAAE,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,GAAG,GAAG;;;;;;;AAQlF,SAAgB,iBAAiB,GAAmB;CAIlD,OADwB,EAAE,QAAQ,oBAAoB,WACjC,CAAC,CAAC,QAAQ,aAAa,OAAO,KAAK,OAAO,GAAG,WAAW,CAAC,CAAC,EAAE,EAAE;AACrF;AAEA,MAAM,cAAc;;;;;;;AAQpB,SAAgB,mBAAmB,GAAmB;CACpD,OAAO,EAAE,QAAQ,cAAc,OAAO,QAAgB,OAAO,aAAa,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC;AACrG;;;;;;;AAiBA,SAAgB,cAAc,GAAmB;CAC/C,OAAO,EAAE,QAAQ,MAAM,OAAO,CAAC,CAAC,QAAQ,MAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAM;AAC5E;;;;;;;;;;;AAYA,SAAgB,cAAc,GAAmB;CAC/C,OAAO,EACJ,QAAQ,MAAM,OAAO,CAAC,CACtB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,MAAM,CAAC,CACrB,QAAQ,MAAM,QAAQ;AAC3B;AAOA,MAAM,qBAEJ;;;;;;;;;;;;;;;;;AAkBF,SAAgB,sBAAsB,GAAW,MAAc,IAAoB;CACjF,MAAM,kBAAkB,mBAAmB,KAAK,CAAC;CACjD,IAAI,iBAEF,MAAM,IAAI,mBAAmB,GAAG,KAAK,MAAM,GAAG,cAD5B,OAAO,EAAE,WAAW,gBAAgB,KAAK,CACS,EAAE,iCAAiC;CAEzG,OAAO,cAAc,CAAC,CAAC,CAAC,QAAQ,OAAO,OAAO;AAChD"}
package/dist/io/load.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import type { XlsxSource } from '../io/source.js';
2
+ import { type Relationships } from '../packaging/relationships.js';
2
3
  import { type SheetState, type Workbook } from '../workbook/workbook.js';
3
4
  import { type XmlNode } from '../xml/tree.js';
4
5
  import type { DecompressionLimits } from '../zip/decompression-guard.js';
6
+ import { type ZipArchive } from '../zip/reader.js';
5
7
  /**
6
8
  * Options for {@link loadWorkbook}. Earlier drafts exposed `readOnly` /
7
9
  * `keepLinks` / `keepVba` / `dataOnly` / `richText` placeholders that the
@@ -17,6 +19,10 @@ export interface LoadOptions {
17
19
  */
18
20
  decompressionLimits?: DecompressionLimits | false;
19
21
  }
22
+ /** Office Document relationship type: the package-root pointer to `xl/workbook.xml`. */
23
+ export declare const OFFICE_DOC_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
24
+ /** Root element of `xl/workbook.xml`. Shared with the streaming reader. */
25
+ export declare const WORKBOOK_TAG = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}workbook";
20
26
  /**
21
27
  * Resolve an OPC relationship target against its source part path.
22
28
  *
@@ -25,6 +31,30 @@ export interface LoadOptions {
25
31
  * - `..` segments collapse normally.
26
32
  */
27
33
  export declare function resolveRelTarget(sourcePartPath: string, target: string): string;
34
+ /** A workbook-level part a package carries at most one of. */
35
+ interface OptionalWorkbookPart {
36
+ /** How the part is named in error messages. */
37
+ name: string;
38
+ /** Relationship type that binds the part to the workbook. */
39
+ relType: string;
40
+ /** Where Excel writes the part. A convention, not a rule. */
41
+ conventionalPath: string;
42
+ }
43
+ export declare const SHARED_STRINGS_PART: OptionalWorkbookPart;
44
+ export declare const STYLES_PART: OptionalWorkbookPart;
45
+ /**
46
+ * Read an optional workbook-level part, following the relationship that binds
47
+ * it to the workbook.
48
+ *
49
+ * The relationship decides. `xl/sharedStrings.xml` and its siblings are only
50
+ * where Excel happens to write these parts, and a producer may point the rel
51
+ * at any part in the package, so the conventional path serves the packages
52
+ * that ship the part with no relationship at all.
53
+ *
54
+ * A relationship that resolves to nothing is a malformed package rather than
55
+ * an absent part, so it throws instead of reading back as an empty table.
56
+ */
57
+ export declare function readOptionalWorkbookPart(archive: ZipArchive, workbookPath: string, wbRels: Relationships, part: OptionalWorkbookPart): Uint8Array | undefined;
28
58
  interface SheetEntry {
29
59
  /** Display name (`sheet/@name`). */
30
60
  name: string;
package/dist/io.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { i as OpenXmlIoError } from "./exceptions-D-CFwxgm.mjs";
2
- import { t as loadWorkbook } from "./load-B1sK-LjT.mjs";
3
- import { n as workbookToBytes, t as saveWorkbook } from "./save-DmdWwxmw.mjs";
2
+ import { a as loadWorkbook } from "./load-BpnCPv1S.mjs";
3
+ import { n as workbookToBytes, t as saveWorkbook } from "./save-1Z3344nz.mjs";
4
4
  //#region src/io/browser.ts
5
5
  /**
6
6
  * Wrap a Blob (or File, since File extends Blob) as an XlsxSource. The
@@ -1,5 +1,5 @@
1
1
  import { o as OpenXmlSchemaError } from "./exceptions-D-CFwxgm.mjs";
2
- import { jt as qname } from "./tree-BX-sRRVp.mjs";
2
+ import { Nt as qname } from "./tree-CspNjuYl.mjs";
3
3
  import { SaxesParser } from "saxes";
4
4
  //#region src/xml/iterparse.ts
5
5
  /**
@@ -184,4 +184,4 @@ async function* iterParse(input) {
184
184
  //#endregion
185
185
  export { iterParse as t };
186
186
 
187
- //# sourceMappingURL=iterparse-DZE0zy2M.mjs.map
187
+ //# sourceMappingURL=iterparse-CZoRXvZW.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"iterparse-DZE0zy2M.mjs","names":[],"sources":["../src/xml/iterparse.ts"],"sourcesContent":["// SAX iterator over OOXML XML payloads. Wraps `saxes` (XMLNS-aware) and yields\n// a flat stream of {start | end | text} events with names already converted to\n// Clark notation (`{ns}local`) — same shape as the DOM parser produces for\n// static XmlNode trees, so consumers can switch between bulk and streaming\n// reads without retouching name comparison.\n//\n// Phase 1 §3 acceptance: 1 k–row sheetData walked end-to-end with cell counts\n// matching the source. The phase-4 read-only worksheet drives real-world use;\n// this layer just produces the events.\n//\n// DOCTYPE / external entity declarations are forbidden. saxes does not expand\n// external entities, and a prescan rejects a declaration before the parser\n// reaches it. A declaration is only legal in the prologue, so that is how far\n// the prescan reads.\n\nimport { SaxesParser } from 'saxes';\nimport { OpenXmlSchemaError } from '../utils/exceptions.js';\nimport { qname } from './namespaces.js';\n\nexport type SaxEvent =\n | { kind: 'start'; name: string; attrs: Record<string, string> }\n | { kind: 'end'; name: string }\n | { kind: 'text'; text: string };\n\n/**\n * Streamable input: `Uint8Array`, plain string, or a Web `ReadableStream` of\n * `Uint8Array` chunks (produced by xlsx zip entries via fflate, fetch, file\n * streams, etc.).\n */\nexport type SaxInput = Uint8Array | string | ReadableStream<Uint8Array>;\n\n/**\n * Recognize declarations only in the prologue, skipping comments and processing\n * instructions. State spans chunks, but retained text is at most one keyword or\n * terminator. Once a root tag begins, saxes handles all remaining syntax checks.\n */\nconst makeDtdScanner = (): ((chunk: string) => void) => {\n let state: 'prologue' | 'markup' | 'comment' | 'pi' | 'done' = 'prologue';\n let token = '';\n return (chunk) => {\n for (const char of chunk) {\n if (state === 'done') return;\n if (state === 'comment' || state === 'pi') {\n const terminator = state === 'comment' ? '-->' : '?>';\n token = (token + char).slice(-terminator.length);\n if (token === terminator) {\n state = 'prologue';\n token = '';\n }\n } else if (state === 'prologue') {\n if (char === '<') {\n state = 'markup';\n token = char;\n }\n } else {\n token += char;\n if (token === '<!--' || token === '<?') {\n state = token === '<!--' ? 'comment' : 'pi';\n token = '';\n } else if (/^<!DOCTYPE[ \\t\\r\\n]$/.test(token)) {\n throw new OpenXmlSchemaError('DTD declarations are not permitted in OOXML payloads');\n } else if (/^<!ENTITY[ \\t\\r\\n]$/.test(token)) {\n throw new OpenXmlSchemaError('Entity declarations are not permitted in OOXML payloads');\n } else if (!'<!--'.startsWith(token) && !'<!DOCTYPE'.startsWith(token) && !'<!ENTITY'.startsWith(token)) {\n // A root name starts here (or invalid markup, which saxes rejects).\n state = 'done';\n token = '';\n }\n }\n }\n };\n};\n\nconst isReadableStream = (v: unknown): v is ReadableStream<Uint8Array> => {\n return typeof v === 'object' && v !== null && typeof (v as ReadableStream).getReader === 'function';\n};\n\nconst decoder = (): TextDecoder => new TextDecoder('utf-8', { fatal: false });\n\n/**\n * Feed size, in code units. saxes runs its handlers synchronously inside\n * `write()`, so one `write()` queues every event its argument produces before\n * the consumer sees any of them. Capping the argument caps the queue.\n *\n * This bounds every input shape, including a `ReadableStream`, whose upstream\n * chunk size is not ours to choose: the zip reader pushes 64 KB of *compressed*\n * bytes per pull, and worksheet XML inflates roughly sevenfold, so its chunks\n * arrive around 450 KB.\n */\nconst FEED_CHUNK_SIZE = 64 * 1024;\n\n/**\n * Split text that exceeds the feed size. Boundaries fall on arbitrary offsets,\n * which is safe because saxes holds back a lone high surrogate at the end of a\n * `write()` and rejoins it with the next one, so a split codepoint still\n * surfaces as a single text event.\n */\nfunction* atFeedSize(text: string): IterableIterator<string> {\n if (text.length === 0) return;\n if (text.length <= FEED_CHUNK_SIZE) {\n yield text;\n return;\n }\n for (let i = 0; i < text.length; i += FEED_CHUNK_SIZE) {\n yield text.slice(i, i + FEED_CHUNK_SIZE);\n }\n}\n\n/**\n * Decode bytes in feed-sized slices. Slicing before the decode rather than\n * after it also bounds the decoder's output string, which matters when the\n * producer hands over a whole part at once. UTF-8 never expands, so a slice of\n * FEED_CHUNK_SIZE bytes decodes to at most that many code units and needs no\n * second split.\n *\n * `stream: true` holds back a codepoint split across a slice boundary instead\n * of emitting a replacement character for each half.\n */\nfunction* decodeAtFeedSize(bytes: Uint8Array, td: TextDecoder): IterableIterator<string> {\n for (let i = 0; i < bytes.byteLength; i += FEED_CHUNK_SIZE) {\n const text = td.decode(bytes.subarray(i, Math.min(i + FEED_CHUNK_SIZE, bytes.byteLength)), { stream: true });\n if (text.length > 0) yield text;\n }\n}\n\n/** Decode any supported input into a sequence of feed-sized text chunks. */\nasync function* decodedChunks(input: SaxInput): AsyncIterableIterator<string> {\n if (typeof input === 'string') {\n yield* atFeedSize(input);\n return;\n }\n const td = decoder();\n if (input instanceof Uint8Array) {\n yield* decodeAtFeedSize(input, td);\n } else if (isReadableStream(input)) {\n const reader = input.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n yield* decodeAtFeedSize(value, td);\n }\n } finally {\n // Consumers abandon the iteration routinely (`iterRows({ maxRow })`\n // returns as soon as the band ends). Without this the source stays\n // locked and its `cancel()` never runs, which is what releases the zip\n // reader's inflate state. A cancel that rejects is cleanup on a stream\n // nobody will read again, and must not mask why we left the loop.\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\n } else {\n throw new OpenXmlSchemaError('iterParse: unsupported input type');\n }\n const tail = td.decode();\n if (tail.length > 0) yield tail;\n}\n\ninterface SaxesOpenTag {\n name: string;\n uri: string;\n local: string;\n prefix: string;\n attributes: Record<string, { value: string; uri: string; local: string; prefix: string }>;\n isSelfClosing?: boolean;\n}\n\ninterface SaxesCloseTag {\n name: string;\n uri: string;\n local: string;\n prefix: string;\n}\n\nconst buildAttrsClark = (attrs: SaxesOpenTag['attributes']): Record<string, string> => {\n const out: Record<string, string> = {};\n for (const [, info] of Object.entries(attrs)) {\n // saxes already resolved the namespace when xmlns: true is set; raw xmlns /\n // xmlns:* declarations have prefix='xmlns' (or local==='xmlns' when\n // default) and we drop those — they're rebuilt by the serializer.\n if (info.prefix === 'xmlns' || (info.prefix === '' && info.local === 'xmlns')) continue;\n const key = qname(info.uri, info.local);\n out[key] = info.value;\n }\n return out;\n};\n\n/**\n * Parse the input as a stream of SAX events. Element / attribute names are\n * returned in Clark notation (`{ns}local`).\n */\nexport async function* iterParse(input: SaxInput): AsyncIterableIterator<SaxEvent> {\n // Set up the parser. xmlns: true gives us resolved {uri, local, prefix} on\n // every open / close tag and on every attribute.\n const parser = new SaxesParser({ xmlns: true, fragment: false });\n\n // Head-pointer ring instead of Array#shift: each saxes write() can produce\n // hundreds of events in a single synchronous batch (a `<row>` with dozens of\n // cells flushes one opentag + one text + one closetag per cell). `shift()`\n // is O(n) per element in V8, so a single-batch drain of N events would be\n // O(N²) before iteration. The head advances on yield; the queue is reset\n // (head + length) once it drains. Peak depth is one chunk's worth of events,\n // which is what keeps the queue bounded on a multi-GB sheet.\n let queue: SaxEvent[] = [];\n let head = 0;\n let pending: Error | undefined;\n\n parser.on('error', (err: Error) => {\n // saxes reports syntax errors as plain Error; consumers of this library\n // only ever see OpenXmlError subclasses.\n pending = new OpenXmlSchemaError(`Malformed XML: ${err.message}`, { cause: err });\n });\n parser.on('doctype', () => {\n pending = new OpenXmlSchemaError('DTD declarations are not permitted in OOXML payloads');\n });\n parser.on('opentag', (node: SaxesOpenTag) => {\n queue.push({ kind: 'start', name: qname(node.uri, node.local), attrs: buildAttrsClark(node.attributes) });\n });\n parser.on('closetag', (node: SaxesCloseTag) => {\n queue.push({ kind: 'end', name: qname(node.uri, node.local) });\n });\n parser.on('text', (text: string) => {\n if (text.length > 0) queue.push({ kind: 'text', text });\n });\n\n const drain = function* (): IterableIterator<SaxEvent> {\n for (;;) {\n const ev = head < queue.length ? queue[head] : undefined;\n if (ev === undefined) {\n // Reset rather than grow forever; the next batch starts at index 0.\n queue = [];\n head = 0;\n return;\n }\n head++;\n yield ev;\n }\n };\n\n const feed = (chunk: string): void => {\n parser.write(chunk);\n if (pending !== undefined) throw pending;\n };\n\n const scanForDtd = makeDtdScanner();\n for await (const chunk of decodedChunks(input)) {\n scanForDtd(chunk);\n feed(chunk);\n yield* drain();\n }\n\n parser.close();\n if (pending !== undefined) throw pending;\n yield* drain();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"iterparse-CZoRXvZW.mjs","names":[],"sources":["../src/xml/iterparse.ts"],"sourcesContent":["// SAX iterator over OOXML XML payloads. Wraps `saxes` (XMLNS-aware) and yields\n// a flat stream of {start | end | text} events with names already converted to\n// Clark notation (`{ns}local`) — same shape as the DOM parser produces for\n// static XmlNode trees, so consumers can switch between bulk and streaming\n// reads without retouching name comparison.\n//\n// Phase 1 §3 acceptance: 1 k–row sheetData walked end-to-end with cell counts\n// matching the source. The phase-4 read-only worksheet drives real-world use;\n// this layer just produces the events.\n//\n// DOCTYPE / external entity declarations are forbidden. saxes does not expand\n// external entities, and a prescan rejects a declaration before the parser\n// reaches it. A declaration is only legal in the prologue, so that is how far\n// the prescan reads.\n\nimport { SaxesParser } from 'saxes';\nimport { OpenXmlSchemaError } from '../utils/exceptions.js';\nimport { qname } from './namespaces.js';\n\nexport type SaxEvent =\n | { kind: 'start'; name: string; attrs: Record<string, string> }\n | { kind: 'end'; name: string }\n | { kind: 'text'; text: string };\n\n/**\n * Streamable input: `Uint8Array`, plain string, or a Web `ReadableStream` of\n * `Uint8Array` chunks (produced by xlsx zip entries via fflate, fetch, file\n * streams, etc.).\n */\nexport type SaxInput = Uint8Array | string | ReadableStream<Uint8Array>;\n\n/**\n * Recognize declarations only in the prologue, skipping comments and processing\n * instructions. State spans chunks, but retained text is at most one keyword or\n * terminator. Once a root tag begins, saxes handles all remaining syntax checks.\n */\nconst makeDtdScanner = (): ((chunk: string) => void) => {\n let state: 'prologue' | 'markup' | 'comment' | 'pi' | 'done' = 'prologue';\n let token = '';\n return (chunk) => {\n for (const char of chunk) {\n if (state === 'done') return;\n if (state === 'comment' || state === 'pi') {\n const terminator = state === 'comment' ? '-->' : '?>';\n token = (token + char).slice(-terminator.length);\n if (token === terminator) {\n state = 'prologue';\n token = '';\n }\n } else if (state === 'prologue') {\n if (char === '<') {\n state = 'markup';\n token = char;\n }\n } else {\n token += char;\n if (token === '<!--' || token === '<?') {\n state = token === '<!--' ? 'comment' : 'pi';\n token = '';\n } else if (/^<!DOCTYPE[ \\t\\r\\n]$/.test(token)) {\n throw new OpenXmlSchemaError('DTD declarations are not permitted in OOXML payloads');\n } else if (/^<!ENTITY[ \\t\\r\\n]$/.test(token)) {\n throw new OpenXmlSchemaError('Entity declarations are not permitted in OOXML payloads');\n } else if (!'<!--'.startsWith(token) && !'<!DOCTYPE'.startsWith(token) && !'<!ENTITY'.startsWith(token)) {\n // A root name starts here (or invalid markup, which saxes rejects).\n state = 'done';\n token = '';\n }\n }\n }\n };\n};\n\nconst isReadableStream = (v: unknown): v is ReadableStream<Uint8Array> => {\n return typeof v === 'object' && v !== null && typeof (v as ReadableStream).getReader === 'function';\n};\n\nconst decoder = (): TextDecoder => new TextDecoder('utf-8', { fatal: false });\n\n/**\n * Feed size, in code units. saxes runs its handlers synchronously inside\n * `write()`, so one `write()` queues every event its argument produces before\n * the consumer sees any of them. Capping the argument caps the queue.\n *\n * This bounds every input shape, including a `ReadableStream`, whose upstream\n * chunk size is not ours to choose: the zip reader pushes 64 KB of *compressed*\n * bytes per pull, and worksheet XML inflates roughly sevenfold, so its chunks\n * arrive around 450 KB.\n */\nconst FEED_CHUNK_SIZE = 64 * 1024;\n\n/**\n * Split text that exceeds the feed size. Boundaries fall on arbitrary offsets,\n * which is safe because saxes holds back a lone high surrogate at the end of a\n * `write()` and rejoins it with the next one, so a split codepoint still\n * surfaces as a single text event.\n */\nfunction* atFeedSize(text: string): IterableIterator<string> {\n if (text.length === 0) return;\n if (text.length <= FEED_CHUNK_SIZE) {\n yield text;\n return;\n }\n for (let i = 0; i < text.length; i += FEED_CHUNK_SIZE) {\n yield text.slice(i, i + FEED_CHUNK_SIZE);\n }\n}\n\n/**\n * Decode bytes in feed-sized slices. Slicing before the decode rather than\n * after it also bounds the decoder's output string, which matters when the\n * producer hands over a whole part at once. UTF-8 never expands, so a slice of\n * FEED_CHUNK_SIZE bytes decodes to at most that many code units and needs no\n * second split.\n *\n * `stream: true` holds back a codepoint split across a slice boundary instead\n * of emitting a replacement character for each half.\n */\nfunction* decodeAtFeedSize(bytes: Uint8Array, td: TextDecoder): IterableIterator<string> {\n for (let i = 0; i < bytes.byteLength; i += FEED_CHUNK_SIZE) {\n const text = td.decode(bytes.subarray(i, Math.min(i + FEED_CHUNK_SIZE, bytes.byteLength)), { stream: true });\n if (text.length > 0) yield text;\n }\n}\n\n/** Decode any supported input into a sequence of feed-sized text chunks. */\nasync function* decodedChunks(input: SaxInput): AsyncIterableIterator<string> {\n if (typeof input === 'string') {\n yield* atFeedSize(input);\n return;\n }\n const td = decoder();\n if (input instanceof Uint8Array) {\n yield* decodeAtFeedSize(input, td);\n } else if (isReadableStream(input)) {\n const reader = input.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n yield* decodeAtFeedSize(value, td);\n }\n } finally {\n // Consumers abandon the iteration routinely (`iterRows({ maxRow })`\n // returns as soon as the band ends). Without this the source stays\n // locked and its `cancel()` never runs, which is what releases the zip\n // reader's inflate state. A cancel that rejects is cleanup on a stream\n // nobody will read again, and must not mask why we left the loop.\n await reader.cancel().catch(() => {});\n reader.releaseLock();\n }\n } else {\n throw new OpenXmlSchemaError('iterParse: unsupported input type');\n }\n const tail = td.decode();\n if (tail.length > 0) yield tail;\n}\n\ninterface SaxesOpenTag {\n name: string;\n uri: string;\n local: string;\n prefix: string;\n attributes: Record<string, { value: string; uri: string; local: string; prefix: string }>;\n isSelfClosing?: boolean;\n}\n\ninterface SaxesCloseTag {\n name: string;\n uri: string;\n local: string;\n prefix: string;\n}\n\nconst buildAttrsClark = (attrs: SaxesOpenTag['attributes']): Record<string, string> => {\n const out: Record<string, string> = {};\n for (const [, info] of Object.entries(attrs)) {\n // saxes already resolved the namespace when xmlns: true is set; raw xmlns /\n // xmlns:* declarations have prefix='xmlns' (or local==='xmlns' when\n // default) and we drop those — they're rebuilt by the serializer.\n if (info.prefix === 'xmlns' || (info.prefix === '' && info.local === 'xmlns')) continue;\n const key = qname(info.uri, info.local);\n out[key] = info.value;\n }\n return out;\n};\n\n/**\n * Parse the input as a stream of SAX events. Element / attribute names are\n * returned in Clark notation (`{ns}local`).\n */\nexport async function* iterParse(input: SaxInput): AsyncIterableIterator<SaxEvent> {\n // Set up the parser. xmlns: true gives us resolved {uri, local, prefix} on\n // every open / close tag and on every attribute.\n const parser = new SaxesParser({ xmlns: true, fragment: false });\n\n // Head-pointer ring instead of Array#shift: each saxes write() can produce\n // hundreds of events in a single synchronous batch (a `<row>` with dozens of\n // cells flushes one opentag + one text + one closetag per cell). `shift()`\n // is O(n) per element in V8, so a single-batch drain of N events would be\n // O(N²) before iteration. The head advances on yield; the queue is reset\n // (head + length) once it drains. Peak depth is one chunk's worth of events,\n // which is what keeps the queue bounded on a multi-GB sheet.\n let queue: SaxEvent[] = [];\n let head = 0;\n let pending: Error | undefined;\n\n parser.on('error', (err: Error) => {\n // saxes reports syntax errors as plain Error; consumers of this library\n // only ever see OpenXmlError subclasses.\n pending = new OpenXmlSchemaError(`Malformed XML: ${err.message}`, { cause: err });\n });\n parser.on('doctype', () => {\n pending = new OpenXmlSchemaError('DTD declarations are not permitted in OOXML payloads');\n });\n parser.on('opentag', (node: SaxesOpenTag) => {\n queue.push({ kind: 'start', name: qname(node.uri, node.local), attrs: buildAttrsClark(node.attributes) });\n });\n parser.on('closetag', (node: SaxesCloseTag) => {\n queue.push({ kind: 'end', name: qname(node.uri, node.local) });\n });\n parser.on('text', (text: string) => {\n if (text.length > 0) queue.push({ kind: 'text', text });\n });\n\n const drain = function* (): IterableIterator<SaxEvent> {\n for (;;) {\n const ev = head < queue.length ? queue[head] : undefined;\n if (ev === undefined) {\n // Reset rather than grow forever; the next batch starts at index 0.\n queue = [];\n head = 0;\n return;\n }\n head++;\n yield ev;\n }\n };\n\n const feed = (chunk: string): void => {\n parser.write(chunk);\n if (pending !== undefined) throw pending;\n };\n\n const scanForDtd = makeDtdScanner();\n for await (const chunk of decodedChunks(input)) {\n scanForDtd(chunk);\n feed(chunk);\n yield* drain();\n }\n\n parser.close();\n if (pending !== undefined) throw pending;\n yield* drain();\n}\n"],"mappings":"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"}
@@ -1,14 +1,14 @@
1
1
  import { o as OpenXmlSchemaError } from "./exceptions-D-CFwxgm.mjs";
2
- import { h as loadImage } from "./drawing-ZJ3h4VHD.mjs";
3
- import { At as parseQName, a as findChildren, c as ARC_CONTENT_TYPES, f as ARC_ROOT_RELS, ft as SHEET_MAIN_NS, g as ARC_WORKBOOK, h as ARC_THEME, i as findChild, jt as qname, l as ARC_CORE, lt as REL_NS, m as ARC_STYLE, p as ARC_SHARED_STRINGS, q as MARKUP_COMPAT_NS, s as ARC_APP, u as ARC_CUSTOM } from "./tree-BX-sRRVp.mjs";
4
- import { D as findUserShapesRId, O as parseChartXml, T as parseChartExXml, _ as parseChartsheetXml, a as parseCommentsXml, b as parseWorksheetXml, c as NumberFormatSchema, d as BorderSchema, f as AlignmentSchema, h as parseDrawingXml, l as FontSchema, n as parseTableXml, p as collectRawRelIds, s as ProtectionSchema, u as fillFromTree, w as isChartExBytes, x as parseUserShapesXml } from "./stylesheet-writer-oXSuGnJy.mjs";
5
- import { n as parseXmlDocument, t as parseXml } from "./parser-By6RWZVW.mjs";
6
- import { t as fromTree } from "./serialize-BC2Wu3bR.mjs";
7
- import { B as corePropsFromBytes, Q as findById, _ as customPropsFromBytes, c as extendedPropsFromBytes, et as indexRelsById, nt as relsFromBytes, o as manifestFromBytes, tt as makeRelationships } from "./manifest-pLmx7KYh.mjs";
8
- import { en as stableStringify, ht as makeStylesheet } from "./cell-style-CNsET6WU.mjs";
9
- import { k as parseSharedStringsXml, r as createWorkbook } from "./workbook-DOTFzCpz.mjs";
10
- import { a as makeDefinedName } from "./defined-names-Ctu3F6ls.mjs";
11
- import { t as openZip } from "./reader-DHMxLBQV.mjs";
2
+ import { h as loadImage } from "./drawing-Cv6UtInY.mjs";
3
+ import { Nt as qname, a as findChildren, c as ARC_CONTENT_TYPES, f as ARC_ROOT_RELS, ft as SHEET_MAIN_NS, g as ARC_WORKBOOK, h as ARC_THEME, i as findChild, l as ARC_CORE, lt as REL_NS, m as ARC_STYLE, p as ARC_SHARED_STRINGS, q as MARKUP_COMPAT_NS, s as ARC_APP, u as ARC_CUSTOM } from "./tree-CspNjuYl.mjs";
4
+ import { E as parseChartExXml, O as findUserShapesRId, S as parseUserShapesXml, T as isChartExBytes, _ as parseChartsheetXml, a as parseCommentsXml, b as parseWorksheetXml, c as NumberFormatSchema, d as BorderSchema, f as AlignmentSchema, h as parseDrawingXml, k as parseChartXml, l as FontSchema, n as parseTableXml, p as collectRawRelIds, s as ProtectionSchema, u as fillFromTree } from "./stylesheet-writer-BEn-0P0x.mjs";
5
+ import { n as parseXml, r as parseXmlDocument } from "./parser-B1tB9YaE.mjs";
6
+ import { t as fromTree } from "./serialize-BtxgssFK.mjs";
7
+ import { $ as findByType, B as corePropsFromBytes, Q as findById, _ as customPropsFromBytes, c as extendedPropsFromBytes, et as indexRelsById, nt as relsFromBytes, o as manifestFromBytes, tt as makeRelationships } from "./manifest-9p0ccqmR.mjs";
8
+ import { A as parseSharedStringsXml, F as assertNotStrictRoot, P as assertNotStrictRelTypes, r as createWorkbook } from "./workbook-tG9oYtzU.mjs";
9
+ import { ht as makeStylesheet, tn as stableStringify } from "./cell-style-CbB4nXTY.mjs";
10
+ import { a as makeDefinedName } from "./defined-names-Cmp8rbvI.mjs";
11
+ import { t as openZip } from "./reader-pC7bxYAs.mjs";
12
12
  //#region src/styles/stylesheet-reader.ts
13
13
  const STYLESHEET_TAG = qname(SHEET_MAIN_NS, "styleSheet");
14
14
  const FONTS_TAG = qname(SHEET_MAIN_NS, "fonts");
@@ -46,7 +46,10 @@ const PROTECTION_TAG = qname(SHEET_MAIN_NS, "protection");
46
46
  */
47
47
  function parseStylesheetXml(bytes) {
48
48
  const root = parseXml(bytes);
49
- if (root.name !== STYLESHEET_TAG) throw new OpenXmlSchemaError(`parseStylesheetXml: root is "${root.name}", expected styleSheet`);
49
+ if (root.name !== STYLESHEET_TAG) {
50
+ assertNotStrictRoot(root.name);
51
+ throw new OpenXmlSchemaError(`parseStylesheetXml: root is "${root.name}", expected styleSheet`);
52
+ }
50
53
  const ss = makeStylesheet();
51
54
  ss.fonts.length = 0;
52
55
  ss.fills.length = 0;
@@ -196,8 +199,10 @@ const buildKeyIndex = (arr) => {
196
199
  };
197
200
  //#endregion
198
201
  //#region src/io/load.ts
199
- /** Office Document relationship type the package-root pointer to `xl/workbook.xml`. */
202
+ /** Office Document relationship type: the package-root pointer to `xl/workbook.xml`. */
200
203
  const OFFICE_DOC_REL_TYPE = `${REL_NS}/officeDocument`;
204
+ /** Root element of `xl/workbook.xml`. Shared with the streaming reader. */
205
+ const WORKBOOK_TAG = `{${SHEET_MAIN_NS}}workbook`;
201
206
  /**
202
207
  * Resolve an OPC relationship target against its source part path.
203
208
  *
@@ -210,6 +215,19 @@ function resolveRelTarget(sourcePartPath, target) {
210
215
  const lastSlash = sourcePartPath.lastIndexOf("/");
211
216
  return normalizePath((lastSlash >= 0 ? sourcePartPath.slice(0, lastSlash + 1) : "") + target);
212
217
  }
218
+ /**
219
+ * Some producers store literal spaces in ZIP entry names. Decode only as a
220
+ * fallback after looking for the exact package name, so encoded names retain
221
+ * their identity and existing relationship resolution stays compatible.
222
+ */
223
+ function decodeTarget(target) {
224
+ if (!target.includes("%")) return target;
225
+ try {
226
+ return decodeURIComponent(target);
227
+ } catch {
228
+ return target;
229
+ }
230
+ }
213
231
  function normalizePath(path) {
214
232
  const segments = path.split("/");
215
233
  const out = [];
@@ -223,6 +241,42 @@ function normalizePath(path) {
223
241
  }
224
242
  return out.join("/");
225
243
  }
244
+ const SHARED_STRINGS_PART = {
245
+ name: "sharedStrings",
246
+ relType: `${REL_NS}/sharedStrings`,
247
+ conventionalPath: ARC_SHARED_STRINGS
248
+ };
249
+ const STYLES_PART = {
250
+ name: "styles",
251
+ relType: `${REL_NS}/styles`,
252
+ conventionalPath: ARC_STYLE
253
+ };
254
+ const THEME_PART = {
255
+ name: "theme",
256
+ relType: `${REL_NS}/theme`,
257
+ conventionalPath: ARC_THEME
258
+ };
259
+ /**
260
+ * Read an optional workbook-level part, following the relationship that binds
261
+ * it to the workbook.
262
+ *
263
+ * The relationship decides. `xl/sharedStrings.xml` and its siblings are only
264
+ * where Excel happens to write these parts, and a producer may point the rel
265
+ * at any part in the package, so the conventional path serves the packages
266
+ * that ship the part with no relationship at all.
267
+ *
268
+ * A relationship that resolves to nothing is a malformed package rather than
269
+ * an absent part, so it throws instead of reading back as an empty table.
270
+ */
271
+ function readOptionalWorkbookPart(archive, workbookPath, wbRels, part) {
272
+ const rel = findByType(wbRels, part.relType);
273
+ if (!rel) return archive.has(part.conventionalPath) ? archive.read(part.conventionalPath) : void 0;
274
+ if (rel.targetMode === "External") throw new OpenXmlSchemaError(`workbook rels: the ${part.name} relationship is external ("${rel.target}"), and has to name a part inside the package`);
275
+ let path = resolveRelTarget(workbookPath, rel.target);
276
+ if (!archive.has(path)) path = resolveRelTarget(workbookPath, decodeTarget(rel.target));
277
+ if (!archive.has(path)) throw new OpenXmlSchemaError(`workbook rels: the ${part.name} relationship targets "${path}", which the package does not contain`);
278
+ return archive.read(path);
279
+ }
226
280
  /** Sibling rels-part path for a given part. `xl/workbook.xml` → `xl/_rels/workbook.xml.rels`. */
227
281
  function relsPathFor(partPath) {
228
282
  const i = partPath.lastIndexOf("/");
@@ -333,50 +387,35 @@ function loadWorkbookFromArchive(archive) {
333
387
  if (!archive.has("[Content_Types].xml")) throw new OpenXmlSchemaError(`loadWorkbook: missing "${ARC_CONTENT_TYPES}"`);
334
388
  const manifest = manifestFromBytes(archive.read(ARC_CONTENT_TYPES));
335
389
  if (!archive.has(ARC_ROOT_RELS)) throw new OpenXmlSchemaError(`loadWorkbook: missing "${ARC_ROOT_RELS}"`);
336
- const officeRel = relsFromBytes(archive.read(ARC_ROOT_RELS)).rels.find((r) => r.type === OFFICE_DOC_REL_TYPE);
337
- if (!officeRel) throw new OpenXmlSchemaError("loadWorkbook: root rels missing officeDocument relationship");
390
+ const rootRels = relsFromBytes(archive.read(ARC_ROOT_RELS));
391
+ const officeRel = rootRels.rels.find((r) => r.type === OFFICE_DOC_REL_TYPE);
392
+ if (!officeRel) {
393
+ assertNotStrictRelTypes(rootRels.rels.map((r) => r.type));
394
+ throw new OpenXmlSchemaError("loadWorkbook: root rels missing officeDocument relationship");
395
+ }
338
396
  const workbookPath = resolveRelTarget("", officeRel.target);
339
397
  if (workbookPath !== ARC_WORKBOOK) {
340
398
  if (!archive.has(workbookPath)) throw new OpenXmlSchemaError(`loadWorkbook: workbook part "${workbookPath}" not found in archive`);
341
399
  }
342
400
  const wbDoc = parseXmlDocument(archive.read(workbookPath));
343
401
  const wbRoot = wbDoc.root;
344
- if (parseQName(wbRoot.name).local !== "workbook") throw new OpenXmlSchemaError(`loadWorkbook: ${workbookPath} root is "${wbRoot.name}", expected workbook`);
402
+ if (wbRoot.name !== WORKBOOK_TAG) {
403
+ assertNotStrictRoot(wbRoot.name);
404
+ throw new OpenXmlSchemaError(`loadWorkbook: ${workbookPath} root is "${wbRoot.name}", expected workbook`);
405
+ }
345
406
  const sheetEntries = parseSheetEntries(wbRoot);
346
407
  const definedNamesFromXml = parseDefinedNames(wbRoot);
347
408
  const wbRelsPath = relsPathFor(workbookPath);
348
409
  if (sheetEntries.length > 0 && !archive.has(wbRelsPath)) throw new OpenXmlSchemaError(`loadWorkbook: workbook has sheets but rels part "${wbRelsPath}" is missing`);
349
- const wbRels = archive.has(wbRelsPath) ? relsFromBytes(archive.read(wbRelsPath)) : { rels: [] };
350
- let sharedStrings;
351
- if (archive.has(ARC_SHARED_STRINGS)) sharedStrings = parseSharedStringsXml(archive.read(ARC_SHARED_STRINGS));
352
- else {
353
- const sstRel = wbRels.rels.find((r) => r.type === `${REL_NS}/sharedStrings`);
354
- if (sstRel) {
355
- const sstPath = resolveRelTarget(workbookPath, sstRel.target);
356
- if (archive.has(sstPath)) sharedStrings = parseSharedStringsXml(archive.read(sstPath));
357
- }
358
- }
359
- const sst = sharedStrings?.entries ?? [];
360
- let styles;
361
- if (archive.has(ARC_STYLE)) styles = parseStylesheetXml(archive.read(ARC_STYLE));
362
- else {
363
- const stylesRel = wbRels.rels.find((r) => r.type === `${REL_NS}/styles`);
364
- if (stylesRel) {
365
- const stylesPath = resolveRelTarget(workbookPath, stylesRel.target);
366
- if (archive.has(stylesPath)) styles = parseStylesheetXml(archive.read(stylesPath));
367
- }
368
- }
410
+ const wbRels = archive.has(wbRelsPath) ? relsFromBytes(archive.read(wbRelsPath)) : makeRelationships();
411
+ const sstBytes = readOptionalWorkbookPart(archive, workbookPath, wbRels, SHARED_STRINGS_PART);
412
+ const sst = (sstBytes === void 0 ? void 0 : parseSharedStringsXml(sstBytes))?.entries ?? [];
413
+ const stylesBytes = readOptionalWorkbookPart(archive, workbookPath, wbRels, STYLES_PART);
414
+ const styles = stylesBytes === void 0 ? void 0 : parseStylesheetXml(stylesBytes);
369
415
  const properties = archive.has(ARC_CORE) ? corePropsFromBytes(archive.read(ARC_CORE)) : void 0;
370
416
  const appProperties = archive.has(ARC_APP) ? extendedPropsFromBytes(archive.read(ARC_APP)) : void 0;
371
417
  const customProperties = archive.has(ARC_CUSTOM) ? customPropsFromBytes(archive.read(ARC_CUSTOM)) : void 0;
372
- const themeXml = (() => {
373
- if (archive.has(ARC_THEME)) return archive.read(ARC_THEME);
374
- const themeRel = wbRels.rels.find((r) => r.type === `${REL_NS}/theme`);
375
- if (themeRel) {
376
- const themePath = resolveRelTarget(workbookPath, themeRel.target);
377
- if (archive.has(themePath)) return archive.read(themePath);
378
- }
379
- })();
418
+ const themeXml = readOptionalWorkbookPart(archive, workbookPath, wbRels, THEME_PART);
380
419
  const wb = createWorkbook({ date1904: parseDate1904(wbRoot) });
381
420
  if (styles) wb.styles = styles;
382
421
  if (properties) wb.properties = properties;
@@ -1167,6 +1206,6 @@ function capturePassthrough(archive, manifest, wb, roots, vml) {
1167
1206
  }
1168
1207
  }
1169
1208
  //#endregion
1170
- export { parseStylesheetXml as i, parseDate1904 as n, resolveRelTarget as r, loadWorkbook as t };
1209
+ export { loadWorkbook as a, readOptionalWorkbookPart as c, WORKBOOK_TAG as i, resolveRelTarget as l, SHARED_STRINGS_PART as n, parseDate1904 as o, STYLES_PART as r, parseSheetEntries as s, OFFICE_DOC_REL_TYPE as t, parseStylesheetXml as u };
1171
1210
 
1172
- //# sourceMappingURL=load-B1sK-LjT.mjs.map
1211
+ //# sourceMappingURL=load-BpnCPv1S.mjs.map