docxmlater 1.5.0 → 1.7.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.
@@ -753,6 +753,97 @@ class Document {
753
753
  }
754
754
  return count;
755
755
  }
756
+ applyStandardTableFormatting(shadingColorOrOptions) {
757
+ const options = typeof shadingColorOrOptions === 'string'
758
+ ? { headerRowShading: shadingColorOrOptions }
759
+ : shadingColorOrOptions;
760
+ const autofitToWindow = options?.autofitToWindow !== false;
761
+ const headerRowShading = (options?.headerRowShading || 'E9E9E9').toUpperCase();
762
+ const headerRowFormatting = {
763
+ bold: options?.headerRowFormatting?.bold !== false,
764
+ alignment: options?.headerRowFormatting?.alignment || 'center',
765
+ font: options?.headerRowFormatting?.font || 'Verdana',
766
+ size: options?.headerRowFormatting?.size || 12,
767
+ color: options?.headerRowFormatting?.color || '000000',
768
+ spacingBefore: options?.headerRowFormatting?.spacingBefore ?? 60,
769
+ spacingAfter: options?.headerRowFormatting?.spacingAfter ?? 60,
770
+ };
771
+ const cellMargins = {
772
+ top: options?.cellMargins?.top ?? 0,
773
+ bottom: options?.cellMargins?.bottom ?? 0,
774
+ left: options?.cellMargins?.left ?? 115,
775
+ right: options?.cellMargins?.right ?? 115,
776
+ };
777
+ const skipSingleCellTables = options?.skipSingleCellTables !== false;
778
+ const conditionalReplacementColor = options?.conditionalShading?.replacementColor || headerRowShading;
779
+ let tablesProcessed = 0;
780
+ let headerRowsFormatted = 0;
781
+ let cellsRecolored = 0;
782
+ const tables = this.getAllTables();
783
+ for (const table of tables) {
784
+ const rowCount = table.getRowCount();
785
+ const columnCount = table.getColumnCount();
786
+ if (skipSingleCellTables && rowCount === 1 && columnCount === 1) {
787
+ continue;
788
+ }
789
+ if (autofitToWindow) {
790
+ table.setLayout('auto');
791
+ }
792
+ const firstRow = table.getRow(0);
793
+ if (firstRow) {
794
+ for (const cell of firstRow.getCells()) {
795
+ cell.setShading({ fill: headerRowShading });
796
+ cell.setMargins(cellMargins);
797
+ for (const para of cell.getParagraphs()) {
798
+ para.setAlignment(headerRowFormatting.alignment);
799
+ para.setSpaceBefore(headerRowFormatting.spacingBefore);
800
+ para.setSpaceAfter(headerRowFormatting.spacingAfter);
801
+ for (const run of para.getRuns()) {
802
+ if (headerRowFormatting.bold)
803
+ run.setBold(true);
804
+ run.setFont(headerRowFormatting.font, headerRowFormatting.size);
805
+ run.setColor(headerRowFormatting.color);
806
+ }
807
+ }
808
+ }
809
+ headerRowsFormatted++;
810
+ }
811
+ for (let i = 1; i < rowCount; i++) {
812
+ const row = table.getRow(i);
813
+ if (!row)
814
+ continue;
815
+ for (const cell of row.getCells()) {
816
+ cell.setMargins(cellMargins);
817
+ const currentShading = cell.getFormatting().shading;
818
+ const currentColor = currentShading?.fill?.toUpperCase();
819
+ if (currentColor &&
820
+ currentColor !== 'FFFFFF' &&
821
+ currentColor !== headerRowShading) {
822
+ cell.setShading({ fill: conditionalReplacementColor });
823
+ cellsRecolored++;
824
+ const applyFormatting = options?.conditionalShading?.applyFormatting !== false;
825
+ if (applyFormatting) {
826
+ for (const para of cell.getParagraphs()) {
827
+ para.setAlignment('center');
828
+ para.setSpaceBefore(60);
829
+ para.setSpaceAfter(60);
830
+ for (const run of para.getRuns()) {
831
+ run.setBold(true);
832
+ run.setFont('Verdana', 12);
833
+ }
834
+ }
835
+ }
836
+ }
837
+ }
838
+ }
839
+ tablesProcessed++;
840
+ }
841
+ return {
842
+ tablesProcessed,
843
+ headerRowsFormatted,
844
+ cellsRecolored,
845
+ };
846
+ }
756
847
  centerLargeImages(minPixels = 100) {
757
848
  let count = 0;
758
849
  const minEmus = Math.round(minPixels * 9525);
@@ -962,6 +1053,32 @@ class Document {
962
1053
  }
963
1054
  return count;
964
1055
  }
1056
+ applyStandardNumberedListFormatting() {
1057
+ const instances = this.numberingManager.getAllInstances();
1058
+ let count = 0;
1059
+ for (const instance of instances) {
1060
+ const abstractNumId = instance.getAbstractNumId();
1061
+ const abstractNum = this.numberingManager.getAbstractNumbering(abstractNumId);
1062
+ if (!abstractNum)
1063
+ continue;
1064
+ const level0 = abstractNum.getLevel(0);
1065
+ if (!level0 || level0.getFormat() === 'bullet')
1066
+ continue;
1067
+ for (let levelIndex = 0; levelIndex < 9; levelIndex++) {
1068
+ const numLevel = abstractNum.getLevel(levelIndex);
1069
+ if (!numLevel)
1070
+ continue;
1071
+ numLevel.setFont('Verdana');
1072
+ numLevel.setFontSize(24);
1073
+ numLevel.setLeftIndent(720 * (levelIndex + 1));
1074
+ numLevel.setHangingIndent(360);
1075
+ numLevel.setAlignment('left');
1076
+ }
1077
+ this.applyFormattingToListParagraphs(instance.getNumId());
1078
+ count++;
1079
+ }
1080
+ return count;
1081
+ }
965
1082
  applyFormattingToListParagraphs(numId) {
966
1083
  const paragraphs = this.getAllParagraphs();
967
1084
  for (const para of paragraphs) {
@@ -1132,76 +1249,168 @@ class Document {
1132
1249
  }
1133
1250
  return counts;
1134
1251
  }
1135
- applyCustomFormattingToExistingStyles() {
1252
+ static DEFAULT_HEADING1_CONFIG = {
1253
+ run: {
1254
+ font: 'Verdana',
1255
+ size: 18,
1256
+ bold: true,
1257
+ color: '000000',
1258
+ },
1259
+ paragraph: {
1260
+ alignment: 'left',
1261
+ spacing: { before: 0, after: 240, line: 240, lineRule: 'auto' },
1262
+ },
1263
+ };
1264
+ static DEFAULT_HEADING2_CONFIG = {
1265
+ run: {
1266
+ font: 'Verdana',
1267
+ size: 14,
1268
+ bold: true,
1269
+ color: '000000',
1270
+ },
1271
+ paragraph: {
1272
+ alignment: 'left',
1273
+ spacing: { before: 120, after: 120, line: 240, lineRule: 'auto' },
1274
+ },
1275
+ tableOptions: {
1276
+ shading: 'BFBFBF',
1277
+ marginTop: 0,
1278
+ marginBottom: 0,
1279
+ marginLeft: 115,
1280
+ marginRight: 115,
1281
+ tableWidthPercent: 5000,
1282
+ },
1283
+ };
1284
+ static DEFAULT_HEADING3_CONFIG = {
1285
+ run: {
1286
+ font: 'Verdana',
1287
+ size: 12,
1288
+ bold: true,
1289
+ color: '000000',
1290
+ },
1291
+ paragraph: {
1292
+ alignment: 'left',
1293
+ spacing: { before: 60, after: 60, line: 240, lineRule: 'auto' },
1294
+ },
1295
+ };
1296
+ static DEFAULT_NORMAL_CONFIG = {
1297
+ run: {
1298
+ font: 'Verdana',
1299
+ size: 12,
1300
+ color: '000000',
1301
+ },
1302
+ paragraph: {
1303
+ alignment: 'left',
1304
+ spacing: { before: 60, after: 60, line: 240, lineRule: 'auto' },
1305
+ },
1306
+ };
1307
+ static DEFAULT_LIST_PARAGRAPH_CONFIG = {
1308
+ run: {
1309
+ font: 'Verdana',
1310
+ size: 12,
1311
+ color: '000000',
1312
+ },
1313
+ paragraph: {
1314
+ alignment: 'left',
1315
+ spacing: { before: 0, after: 60, line: 240, lineRule: 'auto' },
1316
+ indentation: { left: 360, hanging: 360 },
1317
+ contextualSpacing: true,
1318
+ },
1319
+ };
1320
+ applyCustomFormattingToExistingStyles(options) {
1136
1321
  const results = { heading1: false, heading2: false, heading3: false, normal: false, listParagraph: false };
1137
1322
  const heading1 = this.stylesManager.getStyle('Heading1');
1138
1323
  const heading2 = this.stylesManager.getStyle('Heading2');
1139
1324
  const heading3 = this.stylesManager.getStyle('Heading3');
1140
1325
  const normal = this.stylesManager.getStyle('Normal');
1141
1326
  const listParagraph = this.stylesManager.getStyle('ListParagraph');
1142
- if (heading1) {
1143
- heading1.setRunFormatting({
1144
- font: 'Verdana',
1145
- size: 18,
1146
- bold: true,
1147
- color: '000000'
1148
- });
1149
- heading1.setParagraphFormatting({
1150
- alignment: 'left',
1151
- spacing: { before: 0, after: 240, line: 240, lineRule: 'auto' }
1152
- });
1327
+ const h1Config = {
1328
+ run: {
1329
+ ...(heading1?.getRunFormatting() || Document.DEFAULT_HEADING1_CONFIG.run),
1330
+ ...options?.heading1?.run
1331
+ },
1332
+ paragraph: {
1333
+ ...(heading1?.getParagraphFormatting() || Document.DEFAULT_HEADING1_CONFIG.paragraph),
1334
+ ...options?.heading1?.paragraph
1335
+ },
1336
+ };
1337
+ const h2Config = {
1338
+ run: {
1339
+ ...(heading2?.getRunFormatting() || Document.DEFAULT_HEADING2_CONFIG.run),
1340
+ ...options?.heading2?.run
1341
+ },
1342
+ paragraph: {
1343
+ ...(heading2?.getParagraphFormatting() || Document.DEFAULT_HEADING2_CONFIG.paragraph),
1344
+ ...options?.heading2?.paragraph
1345
+ },
1346
+ tableOptions: {
1347
+ ...Document.DEFAULT_HEADING2_CONFIG.tableOptions,
1348
+ ...options?.heading2?.tableOptions
1349
+ },
1350
+ };
1351
+ const h3Config = {
1352
+ run: {
1353
+ ...(heading3?.getRunFormatting() || Document.DEFAULT_HEADING3_CONFIG.run),
1354
+ ...options?.heading3?.run
1355
+ },
1356
+ paragraph: {
1357
+ ...(heading3?.getParagraphFormatting() || Document.DEFAULT_HEADING3_CONFIG.paragraph),
1358
+ ...options?.heading3?.paragraph
1359
+ },
1360
+ };
1361
+ const normalConfig = {
1362
+ run: {
1363
+ ...(normal?.getRunFormatting() || Document.DEFAULT_NORMAL_CONFIG.run),
1364
+ ...options?.normal?.run
1365
+ },
1366
+ paragraph: {
1367
+ ...(normal?.getParagraphFormatting() || Document.DEFAULT_NORMAL_CONFIG.paragraph),
1368
+ ...options?.normal?.paragraph
1369
+ },
1370
+ };
1371
+ const listParaConfig = {
1372
+ run: {
1373
+ ...(listParagraph?.getRunFormatting() || Document.DEFAULT_LIST_PARAGRAPH_CONFIG.run),
1374
+ ...options?.listParagraph?.run
1375
+ },
1376
+ paragraph: {
1377
+ ...(listParagraph?.getParagraphFormatting() || Document.DEFAULT_LIST_PARAGRAPH_CONFIG.paragraph),
1378
+ ...options?.listParagraph?.paragraph
1379
+ },
1380
+ };
1381
+ if (heading1 && h1Config.run && h1Config.paragraph) {
1382
+ if (h1Config.run)
1383
+ heading1.setRunFormatting(h1Config.run);
1384
+ if (h1Config.paragraph)
1385
+ heading1.setParagraphFormatting(h1Config.paragraph);
1153
1386
  results.heading1 = true;
1154
1387
  }
1155
- if (heading2) {
1156
- heading2.setRunFormatting({
1157
- font: 'Verdana',
1158
- size: 14,
1159
- bold: true,
1160
- color: '000000'
1161
- });
1162
- heading2.setParagraphFormatting({
1163
- alignment: 'left',
1164
- spacing: { before: 120, after: 120, line: 240, lineRule: 'auto' }
1165
- });
1388
+ if (heading2 && h2Config.run && h2Config.paragraph) {
1389
+ if (h2Config.run)
1390
+ heading2.setRunFormatting(h2Config.run);
1391
+ if (h2Config.paragraph)
1392
+ heading2.setParagraphFormatting(h2Config.paragraph);
1166
1393
  results.heading2 = true;
1167
1394
  }
1168
- if (heading3) {
1169
- heading3.setRunFormatting({
1170
- font: 'Verdana',
1171
- size: 12,
1172
- bold: true,
1173
- color: '000000'
1174
- });
1175
- heading3.setParagraphFormatting({
1176
- alignment: 'left',
1177
- spacing: { before: 60, after: 60, line: 240, lineRule: 'auto' }
1178
- });
1395
+ if (heading3 && h3Config.run && h3Config.paragraph) {
1396
+ if (h3Config.run)
1397
+ heading3.setRunFormatting(h3Config.run);
1398
+ if (h3Config.paragraph)
1399
+ heading3.setParagraphFormatting(h3Config.paragraph);
1179
1400
  results.heading3 = true;
1180
1401
  }
1181
- if (normal) {
1182
- normal.setRunFormatting({
1183
- font: 'Verdana',
1184
- size: 12,
1185
- color: '000000'
1186
- });
1187
- normal.setParagraphFormatting({
1188
- alignment: 'left',
1189
- spacing: { before: 60, after: 60, line: 240, lineRule: 'auto' }
1190
- });
1402
+ if (normal && normalConfig.run && normalConfig.paragraph) {
1403
+ if (normalConfig.run)
1404
+ normal.setRunFormatting(normalConfig.run);
1405
+ if (normalConfig.paragraph)
1406
+ normal.setParagraphFormatting(normalConfig.paragraph);
1191
1407
  results.normal = true;
1192
1408
  }
1193
- if (listParagraph) {
1194
- listParagraph.setRunFormatting({
1195
- font: 'Verdana',
1196
- size: 12,
1197
- color: '000000'
1198
- });
1199
- listParagraph.setParagraphFormatting({
1200
- alignment: 'left',
1201
- spacing: { before: 0, after: 60, line: 240, lineRule: 'auto' },
1202
- indentation: { left: 360, hanging: 360 },
1203
- contextualSpacing: true
1204
- });
1409
+ if (listParagraph && listParaConfig.run && listParaConfig.paragraph) {
1410
+ if (listParaConfig.run)
1411
+ listParagraph.setRunFormatting(listParaConfig.run);
1412
+ if (listParaConfig.paragraph)
1413
+ listParagraph.setParagraphFormatting(listParaConfig.paragraph);
1205
1414
  results.listParagraph = true;
1206
1415
  }
1207
1416
  const processedParagraphs = new Set();
@@ -1240,30 +1449,42 @@ class Document {
1240
1449
  }
1241
1450
  const { inTable, cell } = this.isParagraphInTable(para);
1242
1451
  if (inTable && cell) {
1243
- cell.setShading({ fill: 'BFBFBF' });
1244
- cell.setMargins({ top: 0, bottom: 0, left: 115, right: 115 });
1245
- const table = this.getAllTables().find(t => {
1246
- for (const row of t.getRows()) {
1247
- for (const c of row.getCells()) {
1248
- if (c === cell)
1249
- return true;
1452
+ if (h2Config.tableOptions?.shading) {
1453
+ cell.setShading({ fill: h2Config.tableOptions.shading });
1454
+ }
1455
+ if (h2Config.tableOptions?.marginTop !== undefined || h2Config.tableOptions?.marginBottom !== undefined ||
1456
+ h2Config.tableOptions?.marginLeft !== undefined || h2Config.tableOptions?.marginRight !== undefined) {
1457
+ cell.setMargins({
1458
+ top: h2Config.tableOptions.marginTop ?? 0,
1459
+ bottom: h2Config.tableOptions.marginBottom ?? 0,
1460
+ left: h2Config.tableOptions.marginLeft ?? 115,
1461
+ right: h2Config.tableOptions.marginRight ?? 115,
1462
+ });
1463
+ }
1464
+ if (h2Config.tableOptions?.tableWidthPercent) {
1465
+ const table = this.getAllTables().find(t => {
1466
+ for (const row of t.getRows()) {
1467
+ for (const c of row.getCells()) {
1468
+ if (c === cell)
1469
+ return true;
1470
+ }
1250
1471
  }
1472
+ return false;
1473
+ });
1474
+ if (table) {
1475
+ table.setWidth(h2Config.tableOptions.tableWidthPercent);
1476
+ table.setWidthType('pct');
1251
1477
  }
1252
- return false;
1253
- });
1254
- if (table) {
1255
- table.setWidth(5000);
1256
- table.setWidthType('pct');
1257
1478
  }
1258
1479
  }
1259
1480
  else {
1260
1481
  this.wrapParagraphInTable(para, {
1261
- shading: 'BFBFBF',
1262
- marginTop: 0,
1263
- marginBottom: 0,
1264
- marginLeft: 115,
1265
- marginRight: 115,
1266
- tableWidthPercent: 5000
1482
+ shading: h2Config.tableOptions?.shading ?? 'BFBFBF',
1483
+ marginTop: h2Config.tableOptions?.marginTop ?? 0,
1484
+ marginBottom: h2Config.tableOptions?.marginBottom ?? 0,
1485
+ marginLeft: h2Config.tableOptions?.marginLeft ?? 115,
1486
+ marginRight: h2Config.tableOptions?.marginRight ?? 115,
1487
+ tableWidthPercent: h2Config.tableOptions?.tableWidthPercent ?? 5000,
1267
1488
  });
1268
1489
  }
1269
1490
  processedParagraphs.add(para);
@@ -1305,23 +1526,234 @@ class Document {
1305
1526
  processedParagraphs.add(para);
1306
1527
  }
1307
1528
  }
1308
- const hyperlinks = this.getHyperlinks();
1309
- for (const { hyperlink, paragraph } of hyperlinks) {
1310
- const text = hyperlink.getText().trim();
1311
- if (text === 'Top of the Document' || text === 'Top of Document') {
1312
- paragraph.setAlignment('right');
1313
- paragraph.setSpaceBefore(0);
1314
- paragraph.setSpaceAfter(0);
1529
+ return results;
1530
+ }
1531
+ applyStylesFromObjects(...styles) {
1532
+ const options = {};
1533
+ for (const style of styles) {
1534
+ const styleId = style.getStyleId();
1535
+ switch (styleId) {
1536
+ case 'Heading1':
1537
+ options.heading1 = {
1538
+ run: style.getRunFormatting(),
1539
+ paragraph: style.getParagraphFormatting(),
1540
+ };
1541
+ break;
1542
+ case 'Heading2':
1543
+ options.heading2 = {
1544
+ run: style.getRunFormatting(),
1545
+ paragraph: style.getParagraphFormatting(),
1546
+ tableOptions: style.getHeading2TableOptions(),
1547
+ };
1548
+ break;
1549
+ case 'Heading3':
1550
+ options.heading3 = {
1551
+ run: style.getRunFormatting(),
1552
+ paragraph: style.getParagraphFormatting(),
1553
+ };
1554
+ break;
1555
+ case 'Normal':
1556
+ options.normal = {
1557
+ run: style.getRunFormatting(),
1558
+ paragraph: style.getParagraphFormatting(),
1559
+ };
1560
+ break;
1561
+ case 'ListParagraph':
1562
+ options.listParagraph = {
1563
+ run: style.getRunFormatting(),
1564
+ paragraph: style.getParagraphFormatting(),
1565
+ };
1566
+ break;
1567
+ default:
1568
+ break;
1569
+ }
1570
+ }
1571
+ return this.applyCustomFormattingToExistingStyles(options);
1572
+ }
1573
+ parseTOCFieldInstruction(instrText) {
1574
+ const levels = new Set();
1575
+ const outlineMatch = instrText.match(/\\o\s+"(\d+)-(\d+)"/);
1576
+ if (outlineMatch && outlineMatch[1] && outlineMatch[2]) {
1577
+ const start = parseInt(outlineMatch[1], 10);
1578
+ const end = parseInt(outlineMatch[2], 10);
1579
+ for (let i = start; i <= end; i++) {
1580
+ if (i >= 1 && i <= 9) {
1581
+ levels.add(i);
1582
+ }
1583
+ }
1584
+ }
1585
+ const styleMatches = instrText.matchAll(/\\t\s+"([^"]+)",(\d+),"/g);
1586
+ for (const match of styleMatches) {
1587
+ const styleName = match[1];
1588
+ const levelStr = match[2];
1589
+ if (!styleName || !levelStr)
1590
+ continue;
1591
+ const level = parseInt(levelStr, 10);
1592
+ const headingMatch = styleName.match(/Heading\s*(\d+)/i);
1593
+ if (headingMatch && headingMatch[1]) {
1594
+ const headingLevel = parseInt(headingMatch[1], 10);
1595
+ if (headingLevel >= 1 && headingLevel <= 9) {
1596
+ levels.add(headingLevel);
1597
+ }
1598
+ }
1599
+ else if (level >= 1 && level <= 9) {
1600
+ levels.add(level);
1315
1601
  }
1316
1602
  }
1317
- this.updateAllHyperlinkColors('0000FF');
1318
- const tocElements = this.getTableOfContentsElements();
1319
- for (const tocElement of tocElements) {
1320
- const toc = tocElement.getTableOfContents();
1321
- toc.setShowPageNumbers(false);
1322
- toc.setHideInWebLayout(true);
1603
+ if (levels.size === 0) {
1604
+ return [1, 2, 3];
1323
1605
  }
1324
- return results;
1606
+ return Array.from(levels).sort((a, b) => a - b);
1607
+ }
1608
+ findHeadingsForTOC(levels) {
1609
+ const headings = [];
1610
+ const levelSet = new Set(levels);
1611
+ for (const element of this.bodyElements) {
1612
+ if (element instanceof Paragraph_1.Paragraph) {
1613
+ const para = element;
1614
+ const formatting = para.getFormatting();
1615
+ if (formatting.style) {
1616
+ const styleMatch = formatting.style.match(/Heading(\d+)/i);
1617
+ if (styleMatch && styleMatch[1]) {
1618
+ const headingLevel = parseInt(styleMatch[1], 10);
1619
+ if (levelSet.has(headingLevel)) {
1620
+ const text = para.getText().trim();
1621
+ if (text) {
1622
+ const bookmark = this.bookmarkManager.createHeadingBookmark(text);
1623
+ headings.push({
1624
+ level: headingLevel,
1625
+ text: text,
1626
+ bookmark: bookmark.getName()
1627
+ });
1628
+ }
1629
+ }
1630
+ }
1631
+ }
1632
+ }
1633
+ }
1634
+ return headings;
1635
+ }
1636
+ generateTOCXML(headings, originalInstrText) {
1637
+ const sdtId = Math.floor(Math.random() * 2000000000) - 1000000000;
1638
+ let tocXml = '<w:sdt>';
1639
+ tocXml += '<w:sdtPr>';
1640
+ tocXml += `<w:id w:val="${sdtId}"/>`;
1641
+ tocXml += '<w:docPartObj>';
1642
+ tocXml += '<w:docPartGallery w:val="Table of Contents"/>';
1643
+ tocXml += '<w:docPartUnique w:val="1"/>';
1644
+ tocXml += '</w:docPartObj>';
1645
+ tocXml += '</w:sdtPr>';
1646
+ tocXml += '<w:sdtContent>';
1647
+ tocXml += '<w:p>';
1648
+ tocXml += '<w:pPr>';
1649
+ tocXml += '<w:spacing w:after="0" w:before="0" w:line="240" w:lineRule="auto"/>';
1650
+ tocXml += '</w:pPr>';
1651
+ tocXml += '<w:r><w:fldChar w:fldCharType="begin"/></w:r>';
1652
+ tocXml += '<w:r>';
1653
+ tocXml += `<w:instrText xml:space="preserve">${this.escapeXml(originalInstrText)}</w:instrText>`;
1654
+ tocXml += '</w:r>';
1655
+ tocXml += '<w:r><w:fldChar w:fldCharType="separate"/></w:r>';
1656
+ if (headings.length > 0 && headings[0]) {
1657
+ tocXml += this.buildTOCEntryXML(headings[0]);
1658
+ }
1659
+ tocXml += '</w:p>';
1660
+ for (let i = 1; i < headings.length; i++) {
1661
+ const heading = headings[i];
1662
+ if (!heading)
1663
+ continue;
1664
+ tocXml += '<w:p>';
1665
+ tocXml += '<w:pPr>';
1666
+ tocXml += '<w:spacing w:after="0" w:before="0" w:line="240" w:lineRule="auto"/>';
1667
+ const indent = (heading.level - 1) * 720;
1668
+ if (indent > 0) {
1669
+ tocXml += `<w:ind w:left="${indent}"/>`;
1670
+ }
1671
+ tocXml += '</w:pPr>';
1672
+ tocXml += this.buildTOCEntryXML(heading);
1673
+ tocXml += '</w:p>';
1674
+ }
1675
+ tocXml += '<w:p>';
1676
+ tocXml += '<w:pPr>';
1677
+ tocXml += '<w:spacing w:after="0" w:before="0" w:line="240" w:lineRule="auto"/>';
1678
+ tocXml += '</w:pPr>';
1679
+ tocXml += '<w:r><w:fldChar w:fldCharType="end"/></w:r>';
1680
+ tocXml += '</w:p>';
1681
+ tocXml += '</w:sdtContent>';
1682
+ tocXml += '</w:sdt>';
1683
+ return tocXml;
1684
+ }
1685
+ buildTOCEntryXML(heading) {
1686
+ const escapedText = this.escapeXml(heading.text);
1687
+ let xml = '';
1688
+ xml += `<w:hyperlink w:anchor="${this.escapeXml(heading.bookmark)}">`;
1689
+ xml += '<w:r>';
1690
+ xml += '<w:rPr>';
1691
+ xml += '<w:rFonts w:ascii="Verdana" w:hAnsi="Verdana" w:cs="Verdana" w:eastAsia="Verdana"/>';
1692
+ xml += '<w:color w:val="0000FF"/>';
1693
+ xml += '<w:sz w:val="24"/>';
1694
+ xml += '<w:szCs w:val="24"/>';
1695
+ xml += '<w:u w:val="single"/>';
1696
+ xml += '</w:rPr>';
1697
+ xml += `<w:t xml:space="preserve">${escapedText}</w:t>`;
1698
+ xml += '</w:r>';
1699
+ xml += '</w:hyperlink>';
1700
+ return xml;
1701
+ }
1702
+ escapeXml(text) {
1703
+ return text
1704
+ .replace(/&/g, '&amp;')
1705
+ .replace(/</g, '&lt;')
1706
+ .replace(/>/g, '&gt;')
1707
+ .replace(/"/g, '&quot;')
1708
+ .replace(/'/g, '&apos;');
1709
+ }
1710
+ async replaceTableOfContents(filePath) {
1711
+ const handler = new ZipHandler_1.ZipHandler();
1712
+ await handler.load(filePath);
1713
+ const docXml = handler.getFileAsString('word/document.xml');
1714
+ if (!docXml) {
1715
+ throw new Error('word/document.xml not found in document');
1716
+ }
1717
+ const tocRegex = /<w:sdt>[\s\S]*?<w:docPartGallery w:val="Table of Contents"[\s\S]*?<\/w:sdt>/g;
1718
+ const tocMatches = Array.from(docXml.matchAll(tocRegex));
1719
+ if (tocMatches.length === 0) {
1720
+ return 0;
1721
+ }
1722
+ let replacedCount = 0;
1723
+ let modifiedXml = docXml;
1724
+ for (const match of tocMatches) {
1725
+ try {
1726
+ const tocXml = match[0];
1727
+ const instrMatch = tocXml.match(/<w:instrText[^>]*>([\s\S]*?)<\/w:instrText>/);
1728
+ if (!instrMatch || !instrMatch[1]) {
1729
+ continue;
1730
+ }
1731
+ let fieldInstruction = instrMatch[1];
1732
+ fieldInstruction = fieldInstruction
1733
+ .replace(/&lt;/g, '<')
1734
+ .replace(/&gt;/g, '>')
1735
+ .replace(/&quot;/g, '"')
1736
+ .replace(/&apos;/g, "'")
1737
+ .replace(/&amp;/g, '&');
1738
+ const levels = this.parseTOCFieldInstruction(fieldInstruction);
1739
+ const headings = this.findHeadingsForTOC(levels);
1740
+ if (headings.length === 0) {
1741
+ continue;
1742
+ }
1743
+ const newTocXml = this.generateTOCXML(headings, fieldInstruction);
1744
+ modifiedXml = modifiedXml.replace(tocXml, newTocXml);
1745
+ replacedCount++;
1746
+ }
1747
+ catch (error) {
1748
+ console.error(`Error replacing TOC: ${error}`);
1749
+ continue;
1750
+ }
1751
+ }
1752
+ if (replacedCount > 0) {
1753
+ handler.updateFile('word/document.xml', modifiedXml);
1754
+ await handler.save(filePath);
1755
+ }
1756
+ return replacedCount;
1325
1757
  }
1326
1758
  getSection() {
1327
1759
  return this.section;