documonster 0.16.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -401,26 +401,26 @@ function parsePatternFill(xml) {
401
401
  return { pattern: { preset, foreground, background } };
402
402
  }
403
403
  /**
404
- * Remove the FIRST matching `<tag …>…</tag>` (or self-closing
405
- * `<tag …/>`) block from the input XML, returning the remainder. Used
406
- * by {@link parseSpPr} to isolate shape-level children (fill / effects)
407
- * from decorative children that nest inside `<a:ln>` — the line's own
408
- * `<a:solidFill>` / `<a:noFill/>` / `<a:gradFill>` should not be
409
- * harvested as the shape's fill.
404
+ * Remove `<tag …>…</tag>` (or self-closing `<tag …/>`) blocks from the input
405
+ * XML, returning the remainder. Used by {@link parseSpPr} to keep the line's
406
+ * own `<a:solidFill>` / `<a:noFill/>` / `<a:gradFill>` inside `<a:ln>` from
407
+ * being harvested as the shape's fill.
410
408
  *
411
- * Strips ALL occurrences of `tag` (both self-closing and paired) in
412
- * one pass. A previous version returned after the earliest match,
413
- * which failed on inputs like `<a:ln/>…<a:ln>…</a:ln>` — the paired
414
- * block survived and its inner `<a:solidFill>` was then mistakenly
415
- * parsed as the shape's fill.
409
+ * Strips up to eight occurrences in one pass, so a `<a:ln/>` ahead of
410
+ * `<a:ln>…</a:ln>` does not leave the paired block behind.
416
411
  *
417
- * Not a general-purpose XML tool — does not handle same-named nested
418
- * occurrences. Sufficient for DrawingML spPr, where `<a:ln>` never
419
- * nests another `<a:ln>`.
412
+ * The trailing slash is **captured** rather than excluded with a `(?<!/)`
413
+ * lookbehind, which Safari only supports from 16.4 and which threw
414
+ * `SyntaxError` here at first use, not at import.
415
+ *
416
+ * Not a general-purpose XML tool: no same-named nesting, and no `>` inside an
417
+ * attribute value. Sufficient for DrawingML spPr.
420
418
  */
421
419
  function stripOuterElement(xml, tag) {
422
- const selfCloseRe = new RegExp(`<${tag}\\b[^>]*/>`);
423
- const openRe = new RegExp(`<${tag}\\b[^>]*(?<!/)>`);
420
+ // `[^>]*?` is lazy so `(/?)` gets the chance to claim the slash of a
421
+ // self-closing tag; a greedy `[^>]*` would swallow it and report every
422
+ // `<tag …/>` as a paired open tag.
423
+ const tagRe = new RegExp(`<${tag}\\b[^>]*?(/?)>`);
424
424
  const closeRe = new RegExp(`</${tag}>`);
425
425
  let current = xml;
426
426
  // Strip up to 8 occurrences. DrawingML spPr never has more than one
@@ -428,36 +428,27 @@ function stripOuterElement(xml, tag) {
428
428
  // the ceiling guards against pathological input causing infinite
429
429
  // loops without changing the happy path.
430
430
  for (let i = 0; i < 8; i++) {
431
- const selfCloseMatch = selfCloseRe.exec(current);
432
- const openMatch = openRe.exec(current);
433
- const selfCloseIndex = selfCloseMatch ? selfCloseMatch.index : Infinity;
434
- const openIndex = openMatch ? openMatch.index : Infinity;
435
- if (selfCloseIndex === Infinity && openIndex === Infinity) {
431
+ const match = tagRe.exec(current);
432
+ if (!match) {
436
433
  break;
437
434
  }
438
- if (selfCloseIndex < openIndex && selfCloseMatch) {
439
- current =
440
- current.slice(0, selfCloseMatch.index) +
441
- current.slice(selfCloseMatch.index + selfCloseMatch[0].length);
435
+ const matchEnd = match.index + match[0].length;
436
+ if (match[1] === "/") {
437
+ current = current.slice(0, match.index) + current.slice(matchEnd);
442
438
  continue;
443
439
  }
444
- if (openMatch) {
445
- // Find the close tag that pairs with this open — start searching
446
- // after the open's end to avoid capturing `</tag>` that belongs
447
- // to a prior unrelated (e.g. self-closing lookalike) open.
448
- const openEnd = openMatch.index + openMatch[0].length;
449
- const closeMatch = closeRe.exec(current.slice(openEnd));
450
- if (!closeMatch) {
451
- // Malformed — stop stripping to avoid further mutation of
452
- // input we don't understand.
453
- break;
454
- }
455
- const closeStart = openEnd + closeMatch.index;
456
- const closeEnd = closeStart + closeMatch[0].length;
457
- current = current.slice(0, openMatch.index) + current.slice(closeEnd);
458
- continue;
440
+ // Find the close tag that pairs with this open — start searching
441
+ // after the open's end to avoid capturing `</tag>` that belongs
442
+ // to a prior unrelated (e.g. self-closing lookalike) open.
443
+ const closeMatch = closeRe.exec(current.slice(matchEnd));
444
+ if (!closeMatch) {
445
+ // Malformed — stop stripping to avoid further mutation of
446
+ // input we don't understand.
447
+ break;
459
448
  }
460
- break;
449
+ current =
450
+ current.slice(0, match.index) +
451
+ current.slice(matchEnd + closeMatch.index + closeMatch[0].length);
461
452
  }
462
453
  return current;
463
454
  }
@@ -876,11 +876,17 @@ export function setWorkbookModel(wb, value) {
876
876
  // workbook replaces any accumulated state from previous writes.
877
877
  wb._writerExternalLinkCache = new Map();
878
878
  }
879
+ /**
880
+ * Deep-clone workbook-model data.
881
+ *
882
+ * `structuredClone` unguarded: it exists in every supported environment, and
883
+ * the `JSON.parse(JSON.stringify(...))` fallback this used to carry was a
884
+ * lossier second implementation, not a fallback — it strips a typed-array
885
+ * prototype, which the `Uint8Array` values on `ChartExModel.externalParts`
886
+ * need. `chart/chart-handle.ts` had already dropped the same fallback.
887
+ */
879
888
  function deepClone(value) {
880
- if (typeof structuredClone === "function") {
881
- return structuredClone(value);
882
- }
883
- return JSON.parse(JSON.stringify(value));
889
+ return structuredClone(value);
884
890
  }
885
891
  /**
886
892
  * Deep-copy a {@link ChartEntry}, preserving every field that affects
@@ -1,35 +1,13 @@
1
1
  /**
2
2
  * GUID helpers for OOXML.
3
3
  *
4
- * Office parts that need stable cross-reference ids (threaded comments,
5
- * chart uniqueIds, slicer caches, …) use the canonical Microsoft
6
- * `{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}` format — 32 uppercase hex
7
- * characters split into 8-4-4-4-12 groups, surrounded by braces.
8
- *
9
- * `synthGuid()` returns an RFC-4122 version-4-compatible value suitable
10
- * for those callers. We don't use `crypto.randomUUID()` directly because
11
- * it isn't available in every supported runtime (older Node without
12
- * `node:crypto` globals); `Math.random` is good enough for uniqueness
13
- * within a single workbook, which is the only thing Office cares about.
4
+ * Office parts needing stable cross-reference ids (threaded comments, chart
5
+ * uniqueIds, slicer caches) use uppercase hex in 8-4-4-4-12 groups; callers add
6
+ * the braces. `synthGuid()` delegates to {@link uuidV4} and upper-cases it
7
+ * rather than rolling its own digits out of `Math.random()`, which skipped Web
8
+ * Crypto even where it exists and duplicated the version/variant masking.
14
9
  */
10
+ import { uuidV4 } from "../../../utils/uuid.js";
15
11
  export function synthGuid() {
16
- const hex = "0123456789ABCDEF";
17
- let out = "";
18
- for (let i = 0; i < 32; i++) {
19
- // Version 4: bit 48 (13th hex digit) is always '4'.
20
- // Variant 10xx: bit 64 (17th hex digit) is one of 8, 9, A, B.
21
- if (i === 12) {
22
- out += "4";
23
- }
24
- else if (i === 16) {
25
- out += hex[8 + Math.floor(Math.random() * 4)];
26
- }
27
- else {
28
- out += hex[Math.floor(Math.random() * 16)];
29
- }
30
- if (i === 7 || i === 11 || i === 15 || i === 19) {
31
- out += "-";
32
- }
33
- }
34
- return out;
12
+ return uuidV4().toUpperCase();
35
13
  }
@@ -42,7 +42,7 @@ export function promisify(fn) {
42
42
  // =============================================================================
43
43
  // High Water Mark
44
44
  // =============================================================================
45
- let _defaultHighWaterMark = 65536; // 64KB default (matches Node.js 25)
45
+ let _defaultHighWaterMark = 65536; // 64KB default (matches Node's own on every supported line)
46
46
  let _defaultHighWaterMarkObjectMode = 16; // 16 objects default
47
47
  /**
48
48
  * Get the default high water mark for streams
@@ -24,8 +24,10 @@ import { Writable } from "./writable.js";
24
24
  /**
25
25
  * Ensure the options include a highWaterMark so that native Node.js streams
26
26
  * use the same default as the browser implementation (from common/utils.ts).
27
- * This matters on Node.js 20/21 where the native default is 16 KB instead of
28
- * the 64 KB we standardise on across both platforms.
27
+ *
28
+ * Every supported Node line already defaults to the same 64 KB, so this is not
29
+ * a version workaround: it exists so `setDefaultHighWaterMark` is honoured on
30
+ * native streams too, which otherwise read Node's default rather than ours.
29
31
  */
30
32
  function withDefaultHWM(options) {
31
33
  if (options?.highWaterMark != null) {
@@ -5,6 +5,7 @@
5
5
  * used throughout the word module. Consolidates previously duplicated
6
6
  * implementations of base64, UUID, UTF-16LE encoding, and XML helpers.
7
7
  */
8
+ import { uuidV4 } from "../../../utils/uuid.js";
8
9
  import { DocxError } from "../errors.js";
9
10
  // =============================================================================
10
11
  // Shared TextEncoder / TextDecoder singletons
@@ -62,25 +63,18 @@ export function base64ToBytes(s) {
62
63
  // =============================================================================
63
64
  // UUID v4 Generation
64
65
  // =============================================================================
65
- /**
66
- * Generate a random UUID v4 string in the form "XXXXXXXX-XXXX-4XXX-YXXX-XXXXXXXXXXXX".
67
- * Uses `crypto.getRandomValues` for cryptographic randomness.
68
- */
69
- function generateUuid() {
70
- const bytes = new Uint8Array(16);
71
- crypto.getRandomValues(bytes);
72
- // RFC 4122 v4 UUID: set version (4) and variant (10xx) bits
73
- bytes[6] = (bytes[6] & 0x0f) | 0x40;
74
- bytes[8] = (bytes[8] & 0x3f) | 0x80;
75
- const hex = Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("");
76
- return `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(12, 16)}-${hex.substring(16, 20)}-${hex.substring(20, 32)}`;
77
- }
78
66
  /**
79
67
  * Generate a GUID in braced format: "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}".
80
68
  * Used by font obfuscation (w:fontKey) and building blocks.
69
+ *
70
+ * Delegates to {@link uuidV4} rather than assembling a third copy of the v4 bit
71
+ * masking here. These ids identify rather than protect — a `w:fontKey` is
72
+ * stored in the document in plain text — so a non-cryptographic tier is
73
+ * acceptable. Keys, salts and verifiers use {@link randomBytes}, which throws
74
+ * when Web Crypto is missing.
81
75
  */
82
76
  export function generateGuid() {
83
- return `{${generateUuid()}}`;
77
+ return `{${uuidV4()}}`;
84
78
  }
85
79
  // =============================================================================
86
80
  // UTF-16LE Encoding