@cj-tech-master/excelts 9.5.5 → 9.5.6

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 (60) hide show
  1. package/dist/browser/modules/excel/worksheet.d.ts +11 -0
  2. package/dist/browser/modules/excel/worksheet.js +13 -0
  3. package/dist/browser/modules/formula/integration/apply-writeback-plan.js +17 -3
  4. package/dist/browser/modules/formula/integration/workbook-adapter.js +20 -1
  5. package/dist/browser/modules/formula/integration/workbook-snapshot.d.ts +12 -0
  6. package/dist/browser/modules/formula/materialize/build-writeback-plan.js +47 -0
  7. package/dist/browser/modules/formula/materialize/types.d.ts +19 -3
  8. package/dist/browser/modules/formula/materialize/types.js +13 -3
  9. package/dist/browser/modules/pdf/builder/document-builder.js +2 -2
  10. package/dist/browser/modules/pdf/font/system-fonts.d.ts +24 -4
  11. package/dist/browser/modules/pdf/font/system-fonts.js +76 -32
  12. package/dist/browser/modules/pdf/render/pdf-exporter.js +6 -3
  13. package/dist/browser/modules/word/advanced/field-engine.js +151 -23
  14. package/dist/browser/modules/word/advanced/math-convert.js +2 -1
  15. package/dist/browser/modules/word/advanced/style-map.js +44 -6
  16. package/dist/browser/modules/word/convert/html/html-import.js +434 -71
  17. package/dist/browser/modules/word/convert/markdown/markdown-renderer.js +11 -3
  18. package/dist/browser/modules/word/layout/layout-full.js +4 -1
  19. package/dist/browser/modules/word/security/digital-signatures.js +160 -33
  20. package/dist/browser/modules/word/security/encryption.js +109 -9
  21. package/dist/cjs/modules/excel/worksheet.js +13 -0
  22. package/dist/cjs/modules/formula/integration/apply-writeback-plan.js +17 -3
  23. package/dist/cjs/modules/formula/integration/workbook-adapter.js +20 -1
  24. package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +47 -0
  25. package/dist/cjs/modules/formula/materialize/types.js +13 -3
  26. package/dist/cjs/modules/pdf/builder/document-builder.js +1 -1
  27. package/dist/cjs/modules/pdf/font/system-fonts.js +77 -32
  28. package/dist/cjs/modules/pdf/render/pdf-exporter.js +5 -2
  29. package/dist/cjs/modules/word/advanced/field-engine.js +151 -23
  30. package/dist/cjs/modules/word/advanced/math-convert.js +2 -1
  31. package/dist/cjs/modules/word/advanced/style-map.js +44 -6
  32. package/dist/cjs/modules/word/convert/html/html-import.js +434 -71
  33. package/dist/cjs/modules/word/convert/markdown/markdown-renderer.js +11 -3
  34. package/dist/cjs/modules/word/layout/layout-full.js +4 -1
  35. package/dist/cjs/modules/word/security/digital-signatures.js +160 -33
  36. package/dist/cjs/modules/word/security/encryption.js +109 -9
  37. package/dist/esm/modules/excel/worksheet.js +13 -0
  38. package/dist/esm/modules/formula/integration/apply-writeback-plan.js +17 -3
  39. package/dist/esm/modules/formula/integration/workbook-adapter.js +20 -1
  40. package/dist/esm/modules/formula/materialize/build-writeback-plan.js +47 -0
  41. package/dist/esm/modules/formula/materialize/types.js +13 -3
  42. package/dist/esm/modules/pdf/builder/document-builder.js +2 -2
  43. package/dist/esm/modules/pdf/font/system-fonts.js +76 -32
  44. package/dist/esm/modules/pdf/render/pdf-exporter.js +6 -3
  45. package/dist/esm/modules/word/advanced/field-engine.js +151 -23
  46. package/dist/esm/modules/word/advanced/math-convert.js +2 -1
  47. package/dist/esm/modules/word/advanced/style-map.js +44 -6
  48. package/dist/esm/modules/word/convert/html/html-import.js +434 -71
  49. package/dist/esm/modules/word/convert/markdown/markdown-renderer.js +11 -3
  50. package/dist/esm/modules/word/layout/layout-full.js +4 -1
  51. package/dist/esm/modules/word/security/digital-signatures.js +160 -33
  52. package/dist/esm/modules/word/security/encryption.js +109 -9
  53. package/dist/iife/excelts.iife.js +40 -26
  54. package/dist/iife/excelts.iife.js.map +1 -1
  55. package/dist/iife/excelts.iife.min.js +3 -3
  56. package/dist/types/modules/excel/worksheet.d.ts +11 -0
  57. package/dist/types/modules/formula/integration/workbook-snapshot.d.ts +12 -0
  58. package/dist/types/modules/formula/materialize/types.d.ts +19 -3
  59. package/dist/types/modules/pdf/font/system-fonts.d.ts +24 -4
  60. package/package.json +1 -1
@@ -180,32 +180,160 @@ function parseSeqSwitches(args) {
180
180
  }
181
181
  /** Parse IF field: IF expr1 op expr2 "trueText" "falseText" */
182
182
  function parseIfField(args) {
183
- // Pattern: expr1 = expr2 "trueText" "falseText"
184
- // Supports both quoted and unquoted operands.
185
- // Two-character operators must come first or `<=` would match `<` only.
186
- const match = /^"?([^"=<>!]*?)"?\s*(<=|>=|<>|=|<|>)\s*"?([^"]*?)"?\s+"([^"]*)"\s+"([^"]*)"/.exec(args);
187
- if (match) {
188
- return {
189
- left: match[1].trim(),
190
- operator: match[2],
191
- right: match[3].trim(),
192
- trueText: match[4],
193
- falseText: match[5]
194
- };
195
- }
196
- // Simpler pattern without quotes on operands
197
- const simpleMatch = /^(\S+)\s*(<=|>=|<>|=|<|>)\s*(\S+)\s+"([^"]*)"\s+"([^"]*)"/.exec(args);
198
- if (simpleMatch) {
199
- return {
200
- left: simpleMatch[1],
201
- operator: simpleMatch[2],
202
- right: simpleMatch[3],
203
- trueText: simpleMatch[4],
204
- falseText: simpleMatch[5]
205
- };
183
+ // Hand-rolled parser used in place of the previous chained regex
184
+ // (`/^"?([^"=<>!]*?)"?\s*(<=|>=|<>|=|<|>)\s*"?([^"]*?)"?\s+"([^"]*)"\s+"([^"]*)"/`).
185
+ // CodeQL flagged that regex as polynomial-redos. The grammar is also
186
+ // permissive in ways the regex captured implicitly: real Word IF
187
+ // fields contain operands such as `MERGEFIELD foo` (with internal
188
+ // whitespace) and operands wrapped in quotes. The scanner below
189
+ // mirrors the regex's accepted shape:
190
+ //
191
+ // args := SP* leftOperand SP* op SP* rightOperand SP+ "trueText" SP+ "falseText" …
192
+ //
193
+ // where `leftOperand` runs up to the first comparison operator that
194
+ // is not inside a quoted span, and `rightOperand` runs up to the
195
+ // first `"` that begins the trueText literal.
196
+ // 1. Find the comparison operator outside any quoted span.
197
+ const opPos = findIfOperator(args, 0);
198
+ if (!opPos) {
199
+ return null;
200
+ }
201
+ // 2. Left operand: everything before the operator, with surrounding
202
+ // whitespace and outer quotes stripped.
203
+ const left = stripOuterQuotes(args.slice(0, opPos.start).trim());
204
+ // 3. Right operand: text between the operator and the first quoted
205
+ // literal, with surrounding whitespace and outer quotes stripped.
206
+ let cursor = opPos.next;
207
+ // Scan to the next `"` that is not the immediate value-quoted operand.
208
+ // We have to be careful: the right operand itself may be quoted, e.g.
209
+ // `1 = "bar" "y" "n"`. To match the previous regex we adopt: skip
210
+ // whitespace, optionally consume one quoted span as the right operand,
211
+ // otherwise consume up to the next whitespace+`"` boundary.
212
+ cursor = skipSpaces(args, cursor);
213
+ let right;
214
+ if (args.charCodeAt(cursor) === 0x22 /* '"' */) {
215
+ const close = args.indexOf('"', cursor + 1);
216
+ if (close < 0) {
217
+ return null;
218
+ }
219
+ right = args.slice(cursor + 1, close);
220
+ cursor = close + 1;
221
+ }
222
+ else {
223
+ // Read until the next `"` (which begins the trueText literal).
224
+ const nextQuote = args.indexOf('"', cursor);
225
+ if (nextQuote < 0) {
226
+ return null;
227
+ }
228
+ right = args.slice(cursor, nextQuote).trim();
229
+ cursor = nextQuote;
230
+ }
231
+ // 4. trueText (required quoted string).
232
+ cursor = skipSpaces(args, cursor);
233
+ const trueRead = readQuotedString(args, cursor);
234
+ if (!trueRead) {
235
+ return null;
236
+ }
237
+ cursor = skipSpaces(args, trueRead.next);
238
+ // 5. falseText (required quoted string).
239
+ const falseRead = readQuotedString(args, cursor);
240
+ if (!falseRead) {
241
+ return null;
242
+ }
243
+ return {
244
+ left,
245
+ operator: opPos.value,
246
+ right,
247
+ trueText: trueRead.value,
248
+ falseText: falseRead.value
249
+ };
250
+ }
251
+ /**
252
+ * Find the first IF-field comparison operator (`<=`, `>=`, `<>`, `=`,
253
+ * `<`, `>`) starting at `from`, skipping over any quoted (`"…"`) spans
254
+ * so that operators inside operand strings are not mistaken for the
255
+ * top-level comparator.
256
+ *
257
+ * Bare `!` is reported as "no operator" rather than absorbed into the
258
+ * preceding operand: the previous regex excluded `!` from the left
259
+ * operand character class, so `1 != 1 …` was rejected outright. We
260
+ * preserve that rejection here to avoid silently parsing `!=` (not a
261
+ * Word IF-field operator) as `=` with `!` glued to the left operand.
262
+ */
263
+ function findIfOperator(s, from) {
264
+ const n = s.length;
265
+ let i = from;
266
+ while (i < n) {
267
+ const c = s.charCodeAt(i);
268
+ if (c === 0x22 /* '"' */) {
269
+ // Skip quoted span.
270
+ const close = s.indexOf('"', i + 1);
271
+ if (close < 0) {
272
+ return null;
273
+ }
274
+ i = close + 1;
275
+ continue;
276
+ }
277
+ if (c === 0x21 /* '!' */) {
278
+ // Reject `!` outside quotes — matches the previous regex behaviour.
279
+ return null;
280
+ }
281
+ if (c === 0x3c /* '<' */) {
282
+ const next = s.charCodeAt(i + 1);
283
+ if (next === 0x3d) {
284
+ return { start: i, next: i + 2, value: "<=" };
285
+ }
286
+ if (next === 0x3e) {
287
+ return { start: i, next: i + 2, value: "<>" };
288
+ }
289
+ return { start: i, next: i + 1, value: "<" };
290
+ }
291
+ if (c === 0x3e /* '>' */) {
292
+ if (s.charCodeAt(i + 1) === 0x3d) {
293
+ return { start: i, next: i + 2, value: ">=" };
294
+ }
295
+ return { start: i, next: i + 1, value: ">" };
296
+ }
297
+ if (c === 0x3d /* '=' */) {
298
+ return { start: i, next: i + 1, value: "=" };
299
+ }
300
+ i++;
206
301
  }
207
302
  return null;
208
303
  }
304
+ /**
305
+ * Strip a single matched pair of outer quotes (`"…"`) from a trimmed
306
+ * operand. Mirrors the implicit `"?…"?` shape of the original regex.
307
+ */
308
+ function stripOuterQuotes(s) {
309
+ if (s.length >= 2 && s.charCodeAt(0) === 0x22 && s.charCodeAt(s.length - 1) === 0x22) {
310
+ return s.slice(1, -1);
311
+ }
312
+ return s;
313
+ }
314
+ function skipSpaces(s, from) {
315
+ const n = s.length;
316
+ let i = from;
317
+ while (i < n) {
318
+ const c = s.charCodeAt(i);
319
+ if (c !== 0x20 && c !== 0x09 && c !== 0x0a && c !== 0x0d) {
320
+ break;
321
+ }
322
+ i++;
323
+ }
324
+ return i;
325
+ }
326
+ /** Read a quoted (`"…"`) string starting at `from`, or return null. */
327
+ function readQuotedString(s, from) {
328
+ if (s.charCodeAt(from) !== 0x22 /* '"' */) {
329
+ return null;
330
+ }
331
+ const close = s.indexOf('"', from + 1);
332
+ if (close < 0) {
333
+ return null;
334
+ }
335
+ return { value: s.slice(from + 1, close), next: close + 1 };
336
+ }
209
337
  /** Parse STYLEREF field to get the style name. */
210
338
  function parseStyleRef(args) {
211
339
  // STYLEREF "StyleName" or STYLEREF StyleName
@@ -405,7 +405,8 @@ function parseMMLTree(xml) {
405
405
  // forever (indexOf returning -1 used to set pos = 0, hanging the CPU).
406
406
  const end = xml.indexOf(">", pos);
407
407
  if (end === -1) {
408
- pos = xml.length;
408
+ // Malformed input — stop parsing. (`pos` is unused after the
409
+ // outer loop terminates, so no further assignment is needed.)
409
410
  break;
410
411
  }
411
412
  pos = end + 1;
@@ -293,14 +293,52 @@ function parseTarget(targetStr) {
293
293
  }
294
294
  const tagName = tagMatch[1];
295
295
  const className = tagMatch[2] ? tagMatch[2].substring(1).replace(/\./g, " ") : undefined;
296
- // Parse attributes [key=value]
296
+ // Parse attributes [key=value]. Implemented as a linear scan rather
297
+ // than a global regex so that adversarial mapping strings cannot
298
+ // trigger CodeQL's `js/polynomial-redos`. The regex form
299
+ // `/\[([^=]+)=([^\]]+)\]/g` exhibits backtracking on inputs like
300
+ // `[xxxxxxxxxxxxx`.
297
301
  let attributes;
298
- if (tagMatch[3]) {
302
+ const attrSection = tagMatch[3];
303
+ if (attrSection) {
299
304
  attributes = {};
300
- const attrRegex = /\[([^=]+)=([^\]]+)\]/g;
301
- let attrMatch;
302
- while ((attrMatch = attrRegex.exec(tagMatch[3])) !== null) {
303
- attributes[attrMatch[1]] = attrMatch[2].replace(/^['"]|['"]$/g, "");
305
+ const len = attrSection.length;
306
+ let i = 0;
307
+ while (i < len) {
308
+ if (attrSection.charCodeAt(i) !== 0x5b /* '[' */) {
309
+ i++;
310
+ continue;
311
+ }
312
+ const eq = attrSection.indexOf("=", i + 1);
313
+ const close = attrSection.indexOf("]", i + 1);
314
+ if (close < 0) {
315
+ break;
316
+ }
317
+ if (eq < 0 || eq > close) {
318
+ // No `=` inside this `[...]` — skip past it.
319
+ i = close + 1;
320
+ continue;
321
+ }
322
+ const key = attrSection.slice(i + 1, eq);
323
+ const rawValue = attrSection.slice(eq + 1, close);
324
+ // Strip a single leading and trailing quote (' or "). Two
325
+ // independent linear strips replace the previous
326
+ // `/^['"]|['"]$/g` regex.
327
+ let v = rawValue;
328
+ if (v.length >= 2) {
329
+ const first = v.charCodeAt(0);
330
+ if (first === 0x27 || first === 0x22) {
331
+ v = v.slice(1);
332
+ }
333
+ }
334
+ if (v.length >= 1) {
335
+ const last = v.charCodeAt(v.length - 1);
336
+ if (last === 0x27 || last === 0x22) {
337
+ v = v.slice(0, -1);
338
+ }
339
+ }
340
+ attributes[key] = v;
341
+ i = close + 1;
304
342
  }
305
343
  }
306
344
  return { tagName, className: className || undefined, attributes };