docxmlater 11.0.8 → 11.0.10

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.
@@ -1,4 +1,5 @@
1
1
  import { Run } from './Run.js';
2
+ import { XMLBuilder } from '../xml/XMLBuilder.js';
2
3
  export class ImageRun extends Run {
3
4
  imageElement;
4
5
  _rawRunXml;
@@ -17,7 +18,14 @@ export class ImageRun extends Run {
17
18
  }
18
19
  toXML() {
19
20
  if (this._rawRunXml) {
20
- return { name: '__rawXml', rawXml: this._rawRunXml };
21
+ if (!this.imageElement.isMutated()) {
22
+ return { name: '__rawXml', rawXml: this._rawRunXml };
23
+ }
24
+ const drawingXml = XMLBuilder.elementToString(this.imageElement.toXML());
25
+ const spliced = this._rawRunXml.replace(/<w:drawing\b[\s\S]*<\/w:drawing>/, drawingXml);
26
+ if (spliced !== this._rawRunXml) {
27
+ return { name: '__rawXml', rawXml: spliced };
28
+ }
21
29
  }
22
30
  const drawing = this.imageElement.toXML();
23
31
  const children = [];
@@ -1 +1 @@
1
- {"version":3,"file":"ImageRun.js","sourceRoot":"","sources":["../../../src/elements/ImageRun.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAgB/B,MAAM,OAAO,QAAS,SAAQ,GAAG;IACvB,YAAY,CAAQ;IACpB,UAAU,CAAU;IAM5B,YAAY,KAAY;QAGtB,KAAK,CAAC,EAAE,CAAC,CAAC;QACV,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;IAC5B,CAAC;IAED,YAAY,CAAC,GAAW;QACtB,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;IACxB,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAMD,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAOD,KAAK;QACH,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;QACvD,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAiB,EAAE,CAAC;QAMlC,MAAM,GAAG,GAAG,GAAG,CAAC,wBAAwB,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QAC/D,IAAI,GAAG;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,OAAO;YACL,IAAI,EAAE,KAAK;YACX,QAAQ;SACT,CAAC;IACJ,CAAC;CACF","sourcesContent":["/**\n * ImageRun - A run that contains an image (drawing)\n * Extends Run class for type-safe paragraph content\n *\n * This is a specialized Run that contains a drawing instead of text.\n * It generates proper w:r (run) XML with w:drawing child element.\n */\n\nimport { Run } from './Run.js';\nimport { Image } from './Image.js';\nimport { XMLElement } from '../xml/XMLBuilder.js';\n\n/**\n * ImageRun - A run containing an embedded image\n *\n * In WordprocessingML, images are embedded in runs as drawing elements:\n * <w:r>\n * <w:drawing>\n * <wp:inline>\n * ... image data ...\n * </wp:inline>\n * </w:drawing>\n * </w:r>\n */\nexport class ImageRun extends Run {\n private imageElement: Image;\n private _rawRunXml?: string;\n\n /**\n * Creates a new image run\n * @param image The image to embed in this run\n */\n constructor(image: Image) {\n // Call parent constructor with empty text\n // The text is irrelevant for image runs\n super('');\n this.imageElement = image;\n }\n\n setRawRunXml(xml: string): void {\n this._rawRunXml = xml;\n }\n\n getRawRunXml(): string | undefined {\n return this._rawRunXml;\n }\n\n /**\n * Gets the image element\n * @returns Image instance\n */\n getImageElement(): Image {\n return this.imageElement;\n }\n\n /**\n * Override toXML to generate image-specific XML\n * Generates a w:r element containing w:drawing instead of w:t\n * @returns XMLElement with w:r containing w:drawing\n */\n toXML(): XMLElement {\n if (this._rawRunXml) {\n return { name: '__rawXml', rawXml: this._rawRunXml };\n }\n const drawing = this.imageElement.toXML();\n const children: XMLElement[] = [];\n // Per ECMA-376 §17.3.2.28, w:rPr (if present) must precede the run's\n // content. For runs containing an inline drawing, the rPr's w:rFonts\n // affects line metrics in Word; dropping it shifts the baseline and\n // can let images (with shadow effectExtent) overflow their containing\n // cell. Emit rPr whenever the run carries formatting.\n const rPr = Run.generateRunPropertiesXML(this.getFormatting());\n if (rPr) children.push(rPr);\n children.push(drawing);\n return {\n name: 'w:r',\n children,\n };\n }\n}\n"]}
1
+ {"version":3,"file":"ImageRun.js","sourceRoot":"","sources":["../../../src/elements/ImageRun.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,OAAO,EAAc,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAc9D,MAAM,OAAO,QAAS,SAAQ,GAAG;IACvB,YAAY,CAAQ;IACpB,UAAU,CAAU;IAM5B,YAAY,KAAY;QAGtB,KAAK,CAAC,EAAE,CAAC,CAAC;QACV,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;IAC5B,CAAC;IAED,YAAY,CAAC,GAAW;QACtB,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;IACxB,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAMD,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAOD,KAAK;QACH,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YAEpB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,CAAC;gBACnC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;YACvD,CAAC;YAKD,MAAM,UAAU,GAAG,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC;YACzE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,kCAAkC,EAAE,UAAU,CAAC,CAAC;YACxF,IAAI,OAAO,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC/C,CAAC;QAEH,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAiB,EAAE,CAAC;QAMlC,MAAM,GAAG,GAAG,GAAG,CAAC,wBAAwB,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QAC/D,IAAI,GAAG;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,OAAO;YACL,IAAI,EAAE,KAAK;YACX,QAAQ;SACT,CAAC;IACJ,CAAC;CACF","sourcesContent":["/**\n * ImageRun - A run that contains an image (drawing)\n * Extends Run class for type-safe paragraph content\n *\n * This is a specialized Run that contains a drawing instead of text.\n * It generates proper w:r (run) XML with w:drawing child element.\n */\n\nimport { Run } from './Run.js';\nimport { Image } from './Image.js';\nimport { XMLElement, XMLBuilder } from '../xml/XMLBuilder.js';\n\n/**\n * ImageRun - A run containing an embedded image\n *\n * In WordprocessingML, images are embedded in runs as drawing elements:\n * <w:r>\n * <w:drawing>\n * <wp:inline>\n * ... image data ...\n * </wp:inline>\n * </w:drawing>\n * </w:r>\n */\nexport class ImageRun extends Run {\n private imageElement: Image;\n private _rawRunXml?: string;\n\n /**\n * Creates a new image run\n * @param image The image to embed in this run\n */\n constructor(image: Image) {\n // Call parent constructor with empty text\n // The text is irrelevant for image runs\n super('');\n this.imageElement = image;\n }\n\n setRawRunXml(xml: string): void {\n this._rawRunXml = xml;\n }\n\n getRawRunXml(): string | undefined {\n return this._rawRunXml;\n }\n\n /**\n * Gets the image element\n * @returns Image instance\n */\n getImageElement(): Image {\n return this.imageElement;\n }\n\n /**\n * Override toXML to generate image-specific XML\n * Generates a w:r element containing w:drawing instead of w:t\n * @returns XMLElement with w:r containing w:drawing\n */\n toXML(): XMLElement {\n if (this._rawRunXml) {\n // No live mutation since parse — emit the captured run XML verbatim (round-trip).\n if (!this.imageElement.isMutated()) {\n return { name: '__rawXml', rawXml: this._rawRunXml };\n }\n // The live image was mutated (e.g. setBorder/setSize). Splice the regenerated\n // <w:drawing> into the captured run XML so the change applies while preserving the\n // run's rPr and other captured details — a full regenerate would drop the parsed\n // rPr (not modeled on revision-nested image runs) and can clip the image in Word.\n const drawingXml = XMLBuilder.elementToString(this.imageElement.toXML());\n const spliced = this._rawRunXml.replace(/<w:drawing\\b[\\s\\S]*<\\/w:drawing>/, drawingXml);\n if (spliced !== this._rawRunXml) {\n return { name: '__rawXml', rawXml: spliced };\n }\n // No <w:drawing> found to splice (unexpected) — fall through to full regeneration.\n }\n const drawing = this.imageElement.toXML();\n const children: XMLElement[] = [];\n // Per ECMA-376 §17.3.2.28, w:rPr (if present) must precede the run's\n // content. For runs containing an inline drawing, the rPr's w:rFonts\n // affects line metrics in Word; dropping it shifts the baseline and\n // can let images (with shadow effectExtent) overflow their containing\n // cell. Emit rPr whenever the run carries formatting.\n const rPr = Run.generateRunPropertiesXML(this.getFormatting());\n if (rPr) children.push(rPr);\n children.push(drawing);\n return {\n name: 'w:r',\n children,\n };\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docxmlater",
3
- "version": "11.0.8",
3
+ "version": "11.0.10",
4
4
  "description": "TypeScript library for editing existing Word documents with full tracked-changes, comment, and bookmark fidelity. Round-trip safe DOCX modification.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -3787,13 +3787,18 @@ export class DocumentParser {
3787
3787
  hyperlink.setTooltip(parsed.tooltip);
3788
3788
  }
3789
3789
 
3790
- // Group field runs by paragraph index
3791
- const runsByParagraph = new Map<number, Set<number>>();
3790
+ // Group field runs by paragraph, keyed by Run OBJECT IDENTITY (not index).
3791
+ // Adjacent HYPERLINK fields chain across paragraph boundaries (one paragraph holds
3792
+ // the previous field's `end` followed by the next field's `begin`). Processing the
3793
+ // earlier field mutates that shared paragraph via setContent, shifting run indices;
3794
+ // a captured runIndex for a later field would then point at the wrong run and drop
3795
+ // its content. Matching by object identity is stable across those mutations.
3796
+ const runsByParagraph = new Map<number, Set<Run>>();
3792
3797
  for (const fr of fieldTracker.fieldRuns) {
3793
3798
  if (!runsByParagraph.has(fr.paragraphIndex)) {
3794
3799
  runsByParagraph.set(fr.paragraphIndex, new Set());
3795
3800
  }
3796
- runsByParagraph.get(fr.paragraphIndex)!.add(fr.runIndex);
3801
+ runsByParagraph.get(fr.paragraphIndex)!.add(fr.run);
3797
3802
  }
3798
3803
 
3799
3804
  // Process each affected paragraph
@@ -3801,7 +3806,7 @@ export class DocumentParser {
3801
3806
 
3802
3807
  for (const pIdx of affectedParagraphIndices) {
3803
3808
  const paragraph = allParagraphs[pIdx]!;
3804
- const runIndicesToRemove = runsByParagraph.get(pIdx)!;
3809
+ const runsToRemove = runsByParagraph.get(pIdx)!;
3805
3810
  const content = paragraph.getContent();
3806
3811
 
3807
3812
  if (pIdx === targetParagraphIndex) {
@@ -3810,7 +3815,7 @@ export class DocumentParser {
3810
3815
  let hyperlinkInserted = false;
3811
3816
 
3812
3817
  for (let rIdx = 0; rIdx < content.length; rIdx++) {
3813
- if (runIndicesToRemove.has(rIdx)) {
3818
+ if (runsToRemove.has(content[rIdx] as Run)) {
3814
3819
  // Insert Hyperlink at position of first field run in this paragraph
3815
3820
  if (!hyperlinkInserted) {
3816
3821
  newContent.push(hyperlink);
@@ -3826,7 +3831,7 @@ export class DocumentParser {
3826
3831
  } else {
3827
3832
  // Other paragraphs: remove field runs entirely
3828
3833
  const newContent = content.filter(
3829
- (_: ParagraphContent, rIdx: number) => !runIndicesToRemove.has(rIdx)
3834
+ (item: ParagraphContent) => !runsToRemove.has(item as Run)
3830
3835
  );
3831
3836
  paragraph.setContent(newContent);
3832
3837
  }
@@ -335,6 +335,10 @@ export class Image {
335
335
  private flipV = false;
336
336
  private border?: ImageBorder;
337
337
 
338
+ // Set true when a serialization-affecting setter (setBorder/setSize) mutates this image
339
+ // after parse, so an owning ImageRun knows to refresh its captured raw run XML.
340
+ private _mutated = false;
341
+
338
342
  // Group A: Simple attribute preservation (ECMA-376 compliance)
339
343
  private presetGeometry: PresetGeometry = 'rect';
340
344
  private compressionState: BlipCompressionState = 'none';
@@ -978,9 +982,18 @@ export class Image {
978
982
  setSize(width: number, height: number): this {
979
983
  this.width = width;
980
984
  this.height = height;
985
+ this._mutated = true;
981
986
  return this;
982
987
  }
983
988
 
989
+ /**
990
+ * Returns true if a serialization-affecting setter mutated this image after parse.
991
+ * An owning ImageRun uses this to refresh its captured raw run XML.
992
+ */
993
+ isMutated(): boolean {
994
+ return this._mutated;
995
+ }
996
+
984
997
  async updateImageData(newSource: string | Buffer): Promise<void> {
985
998
  this.source = newSource;
986
999
  this.imageData = undefined;
@@ -1510,6 +1523,7 @@ export class Image {
1510
1523
  this.effectExtent.right = Math.max(this.effectExtent.right, halfBorderEmu);
1511
1524
  this.effectExtent.bottom = Math.max(this.effectExtent.bottom, halfBorderEmu);
1512
1525
 
1526
+ this._mutated = true;
1513
1527
  return this;
1514
1528
  }
1515
1529
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { Run } from './Run.js';
10
10
  import { Image } from './Image.js';
11
- import { XMLElement } from '../xml/XMLBuilder.js';
11
+ import { XMLElement, XMLBuilder } from '../xml/XMLBuilder.js';
12
12
 
13
13
  /**
14
14
  * ImageRun - A run containing an embedded image
@@ -60,7 +60,20 @@ export class ImageRun extends Run {
60
60
  */
61
61
  toXML(): XMLElement {
62
62
  if (this._rawRunXml) {
63
- return { name: '__rawXml', rawXml: this._rawRunXml };
63
+ // No live mutation since parse — emit the captured run XML verbatim (round-trip).
64
+ if (!this.imageElement.isMutated()) {
65
+ return { name: '__rawXml', rawXml: this._rawRunXml };
66
+ }
67
+ // The live image was mutated (e.g. setBorder/setSize). Splice the regenerated
68
+ // <w:drawing> into the captured run XML so the change applies while preserving the
69
+ // run's rPr and other captured details — a full regenerate would drop the parsed
70
+ // rPr (not modeled on revision-nested image runs) and can clip the image in Word.
71
+ const drawingXml = XMLBuilder.elementToString(this.imageElement.toXML());
72
+ const spliced = this._rawRunXml.replace(/<w:drawing\b[\s\S]*<\/w:drawing>/, drawingXml);
73
+ if (spliced !== this._rawRunXml) {
74
+ return { name: '__rawXml', rawXml: spliced };
75
+ }
76
+ // No <w:drawing> found to splice (unexpected) — fall through to full regeneration.
64
77
  }
65
78
  const drawing = this.imageElement.toXML();
66
79
  const children: XMLElement[] = [];