docxmlater 10.1.0 → 10.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docxmlater",
3
- "version": "10.1.0",
3
+ "version": "10.1.2",
4
4
  "description": "A comprehensive DOCX editing framework for creating, reading, and manipulating Microsoft Word documents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -430,6 +430,8 @@ export class Document {
430
430
  private _decimalSymbol?: string;
431
431
  /** List separator for locale (w:listSeparator) per ECMA-376 Part 1 §17.15.1.55 */
432
432
  private _listSeparator?: string;
433
+ /** Document view type (w:view) per ECMA-376 Part 1 §17.15.1.92 — element #1 in CT_Settings */
434
+ private _documentView?: string;
433
435
 
434
436
  /** When true, _postProcessDocumentXml() strips INCLUDEPICTURE field markup from document.xml */
435
437
  private _flattenIncludePictureFields = false;
@@ -847,6 +849,12 @@ export class Document {
847
849
  }
848
850
  }
849
851
 
852
+ // Parse w:view per ECMA-376 Part 1 §17.15.1.92 (element #1 in CT_Settings)
853
+ const viewMatch = /<w:view\s+w:val\s*=\s*"([^"]*)"[^>]*\/?>/.exec(settingsXml);
854
+ if (viewMatch?.[1]) {
855
+ this._documentView = viewMatch[1];
856
+ }
857
+
850
858
  // Parse w:evenAndOddHeaders per ECMA-376 Part 1 §17.15.1.28
851
859
  if (/<w:evenAndOddHeaders\b[^>]*\/?>/.test(settingsXml)) {
852
860
  this._evenAndOddHeaders = true;
@@ -3303,6 +3311,7 @@ export class Document {
3303
3311
  });
3304
3312
  // Apply settings merges to the generated XML for new documents
3305
3313
  if (this._settingsModified) {
3314
+ xml = this.mergeViewIntoSettings(xml);
3306
3315
  xml = this.mergeTrackChangesIntoSettings(xml);
3307
3316
  xml = this.mergeBooleanSettingsIntoSettings(xml);
3308
3317
  }
@@ -3316,6 +3325,7 @@ export class Document {
3316
3325
 
3317
3326
  // Start from original XML and selectively merge changed settings
3318
3327
  let xml = this._originalSettingsXml;
3328
+ xml = this.mergeViewIntoSettings(xml);
3319
3329
  xml = this.mergeTrackChangesIntoSettings(xml);
3320
3330
  xml = this.mergeProtectionIntoSettings(xml);
3321
3331
  xml = this.mergeRsidsIntoSettings(xml);
@@ -3324,6 +3334,29 @@ export class Document {
3324
3334
  return xml;
3325
3335
  }
3326
3336
 
3337
+ /**
3338
+ * Merges the document view (w:view) into settings.xml.
3339
+ *
3340
+ * Per CT_Settings schema: w:view is element #1 — must be the first child
3341
+ * of w:settings. Only modifies if setDocumentView() was called.
3342
+ *
3343
+ * @private
3344
+ */
3345
+ private mergeViewIntoSettings(xml: string): string {
3346
+ if (this._documentView === undefined) {
3347
+ return xml;
3348
+ }
3349
+
3350
+ // Remove any existing w:view element
3351
+ xml = xml.replace(/<w:view\b[^>]*\/?>\s*/g, '');
3352
+
3353
+ // Insert as first child of w:settings (element #1 per CT_Settings)
3354
+ const viewXml = `<w:view w:val="${XMLBuilder.escapeXmlAttribute(this._documentView)}"/>`;
3355
+ xml = xml.replace(/(<w:settings\b[^>]*>)/, `$1\n ${viewXml}`);
3356
+
3357
+ return xml;
3358
+ }
3359
+
3327
3360
  /**
3328
3361
  * Merges track changes settings (w:trackRevisions, w:doNotTrackFormatting,
3329
3362
  * w:trackFormatting, w:revisionView) into settings.xml.
@@ -10949,6 +10982,26 @@ export class Document {
10949
10982
  /**
10950
10983
  * Gets whether even and odd headers/footers are enabled (w:evenAndOddHeaders)
10951
10984
  */
10985
+ /**
10986
+ * Gets the document view type (w:view) per ECMA-376 Part 1 §17.15.1.92.
10987
+ * Common values: 'print', 'web', 'outline', 'masterPages', 'normal', 'none'.
10988
+ * @returns The view type string, or undefined if not set
10989
+ */
10990
+ getDocumentView(): string | undefined {
10991
+ return this._documentView;
10992
+ }
10993
+
10994
+ /**
10995
+ * Sets the document view type (w:view) per ECMA-376 Part 1 §17.15.1.92.
10996
+ * Controls which view mode Word uses when opening the document.
10997
+ * Common values: 'print' (Print Layout), 'web' (Web Layout), 'outline', 'normal' (Draft).
10998
+ * @param view - The view type to set
10999
+ */
11000
+ setDocumentView(view: string): void {
11001
+ this._documentView = view;
11002
+ this._settingsModified = true;
11003
+ }
11004
+
10952
11005
  getEvenAndOddHeaders(): boolean {
10953
11006
  return this._evenAndOddHeaders ?? false;
10954
11007
  }
@@ -6099,14 +6099,14 @@ export class DocumentParser {
6099
6099
  table.setBidiVisual(true);
6100
6100
  }
6101
6101
 
6102
- // Parse table width
6102
+ // Parse table width — always set when w:tblW is present, including w:w="0" w:type="auto"
6103
+ // (auto-sized tables). Skipping w:w="0" would leave the constructor default (9360/dxa),
6104
+ // causing tblPrChange snapshots to capture wrong "previous" width values.
6103
6105
  if (tblPrObj["w:tblW"]) {
6104
- const width = parseInt(tblPrObj["w:tblW"]["@_w:w"] || "0", 10);
6106
+ const width = safeParseInt(tblPrObj["w:tblW"]["@_w:w"], 0);
6105
6107
  const widthType = tblPrObj["w:tblW"]["@_w:type"] || "dxa";
6106
- if (width > 0) {
6107
- table.setWidth(width);
6108
- table.setWidthType(widthType);
6109
- }
6108
+ table.setWidth(width);
6109
+ table.setWidthType(widthType);
6110
6110
  }
6111
6111
 
6112
6112
  // Parse table caption
@@ -6142,6 +6142,12 @@ export class DocumentParser {
6142
6142
  }
6143
6143
  }
6144
6144
 
6145
+ // Parse table indentation (w:tblInd) per ECMA-376 Part 1 §17.4.43
6146
+ if (tblPrObj["w:tblInd"]) {
6147
+ const indentVal = safeParseInt(tblPrObj["w:tblInd"]["@_w:w"], 0);
6148
+ table.setIndent(indentVal);
6149
+ }
6150
+
6145
6151
  // Parse table cell margins (w:tblCellMar) per ECMA-376 Part 1 §17.4.42
6146
6152
  if (tblPrObj["w:tblCellMar"]) {
6147
6153
  const cellMar = tblPrObj["w:tblCellMar"];
@@ -2073,24 +2073,29 @@ export class Run {
2073
2073
  const prevRPr = this.propertyChangeRevision.previousProperties
2074
2074
  ? Run.generateRunPropertiesXML(this.propertyChangeRevision.previousProperties as RunFormatting)
2075
2075
  : null;
2076
- const rPrChangeChildren: XMLElement[] = prevRPr ? [prevRPr] : [];
2077
-
2078
- // Create w:rPrChange element with attributes
2079
- const rPrChange: XMLElement = {
2080
- name: 'w:rPrChange',
2081
- attributes: {
2082
- 'w:id': this.propertyChangeRevision.id.toString(),
2083
- 'w:author': this.propertyChangeRevision.author,
2084
- 'w:date': formatDateForXml(this.propertyChangeRevision.date),
2085
- },
2086
- children: rPrChangeChildren,
2087
- };
2088
-
2089
- // Add rPrChange to rPr children (must come last per ECMA-376)
2090
- if (rPrElement.children) {
2091
- rPrElement.children.push(rPrChange);
2092
- } else {
2093
- rPrElement.children = [rPrChange];
2076
+
2077
+ // Only emit rPrChange if there are actual previous properties to serialize.
2078
+ // Per ECMA-376 §17.13.5.32, rPrChange requires a child w:rPr (minOccurs=1).
2079
+ // When previous properties are all undefined (inherited from styles, not explicit),
2080
+ // emitting an empty or self-closing rPrChange causes Word to strip all formatting
2081
+ // in "Original" markup view, which is visually destructive.
2082
+ if (prevRPr) {
2083
+ const rPrChange: XMLElement = {
2084
+ name: 'w:rPrChange',
2085
+ attributes: {
2086
+ 'w:id': this.propertyChangeRevision.id.toString(),
2087
+ 'w:author': this.propertyChangeRevision.author,
2088
+ 'w:date': formatDateForXml(this.propertyChangeRevision.date),
2089
+ },
2090
+ children: [prevRPr],
2091
+ };
2092
+
2093
+ // Add rPrChange to rPr children (must come last per ECMA-376)
2094
+ if (rPrElement.children) {
2095
+ rPrElement.children.push(rPrChange);
2096
+ } else {
2097
+ rPrElement.children = [rPrChange];
2098
+ }
2094
2099
  }
2095
2100
  }
2096
2101