docxmlater 10.2.0 → 10.2.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.
@@ -1238,8 +1238,14 @@ export class DocumentParser {
1238
1238
  if (nextClose === -1) break;
1239
1239
  if (nextOpen !== -1 && nextOpen < nextClose) {
1240
1240
  const charAfter = paraContent[nextOpen + openTag.length];
1241
- if (charAfter === '>' || charAfter === ' ' || charAfter === '/' ||
1242
- charAfter === '\t' || charAfter === '\n' || charAfter === '\r') {
1241
+ if (
1242
+ charAfter === '>' ||
1243
+ charAfter === ' ' ||
1244
+ charAfter === '/' ||
1245
+ charAfter === '\t' ||
1246
+ charAfter === '\n' ||
1247
+ charAfter === '\r'
1248
+ ) {
1243
1249
  depth++;
1244
1250
  }
1245
1251
  searchFrom = nextOpen + openTag.length;
@@ -1323,19 +1329,32 @@ export class DocumentParser {
1323
1329
  ? [hyperlinks]
1324
1330
  : [];
1325
1331
  if (child.index < hyperlinkArray.length) {
1326
- const result = this.parseHyperlinkFromObject(
1327
- hyperlinkArray[child.index],
1328
- relationshipManager
1329
- );
1330
- if (result.hyperlink) {
1331
- paragraph.addHyperlink(result.hyperlink);
1332
- }
1333
- // Add any bookmarks found inside the hyperlink
1334
- for (const bookmark of result.bookmarkStarts) {
1335
- paragraph.addBookmarkStart(bookmark);
1336
- }
1337
- for (const bookmark of result.bookmarkEnds) {
1338
- paragraph.addBookmarkEnd(bookmark);
1332
+ const hyperlinkObj = hyperlinkArray[child.index];
1333
+
1334
+ // Hyperlinks containing tracked changes (w:del/w:ins inside w:hyperlink)
1335
+ // cannot survive parseHyperlinkFromObject round-trip — preserve as raw XML
1336
+ const hasRevisionChildren =
1337
+ hyperlinkObj['w:del'] ||
1338
+ hyperlinkObj['w:ins'] ||
1339
+ hyperlinkObj['w:moveFrom'] ||
1340
+ hyperlinkObj['w:moveTo'];
1341
+ if (hasRevisionChildren) {
1342
+ const rawXml = extractElementXmlAtPosition(child.pos, 'w:hyperlink');
1343
+ if (rawXml) {
1344
+ paragraph.addContent(new PreservedElement(rawXml, 'w:hyperlink', 'inline'));
1345
+ }
1346
+ } else {
1347
+ const result = this.parseHyperlinkFromObject(hyperlinkObj, relationshipManager);
1348
+ if (result.hyperlink) {
1349
+ paragraph.addHyperlink(result.hyperlink);
1350
+ }
1351
+ // Add any bookmarks found inside the hyperlink
1352
+ for (const bookmark of result.bookmarkStarts) {
1353
+ paragraph.addBookmarkStart(bookmark);
1354
+ }
1355
+ for (const bookmark of result.bookmarkEnds) {
1356
+ paragraph.addBookmarkEnd(bookmark);
1357
+ }
1339
1358
  }
1340
1359
  }
1341
1360
  } else if (child.type === 'w:fldSimple') {
@@ -1347,8 +1366,12 @@ export class DocumentParser {
1347
1366
  paragraph.addField(field);
1348
1367
  }
1349
1368
  }
1350
- } else if (child.type === 'w:ins' || child.type === 'w:del' ||
1351
- child.type === 'w:moveFrom' || child.type === 'w:moveTo') {
1369
+ } else if (
1370
+ child.type === 'w:ins' ||
1371
+ child.type === 'w:del' ||
1372
+ child.type === 'w:moveFrom' ||
1373
+ child.type === 'w:moveTo'
1374
+ ) {
1352
1375
  const revisionXml = extractElementXmlAtPosition(child.pos, child.type);
1353
1376
  if (revisionXml) {
1354
1377
  // Detect nested revision elements (e.g., w:del inside w:ins)
@@ -2811,6 +2834,35 @@ export class DocumentParser {
2811
2834
  if (isHyperlinkInstruction(instruction)) {
2812
2835
  const parsed = parseHyperlinkInstruction(instruction);
2813
2836
  if (parsed && resultText) {
2837
+ // HYPERLINK fields with tracked changes in the result region cannot be
2838
+ // converted to Hyperlink objects — the revisions would become orphaned
2839
+ // standalone paragraph content. Keep as ComplexField instead.
2840
+ if (fieldRevisions.length > 0) {
2841
+ const complexField = this.createComplexFieldFromRuns(fieldRuns);
2842
+ if (complexField) {
2843
+ for (const rev of fieldRevisions) {
2844
+ rev.setFieldContext({
2845
+ field: complexField,
2846
+ instruction: complexField.getInstruction(),
2847
+ position: 'result',
2848
+ });
2849
+ }
2850
+ complexField.setResultRevisions(fieldRevisions);
2851
+ groupedContent.push(complexField);
2852
+ } else {
2853
+ fieldRuns.forEach((run) => groupedContent.push(run));
2854
+ for (const revision of fieldRevisions) {
2855
+ groupedContent.push(revision);
2856
+ }
2857
+ }
2858
+ fieldRuns = [];
2859
+ instructionRevisions = [];
2860
+ fieldRevisions = [];
2861
+ fieldState = null;
2862
+ break;
2863
+ }
2864
+
2865
+ // Normal path (no revisions) — convert to Hyperlink object
2814
2866
  let hyperlink: Hyperlink;
2815
2867
  if (parsed.url && parsed.url.trim() !== '') {
2816
2868
  // External hyperlink (or combined external + anchor)
@@ -685,6 +685,8 @@ export class ComplexField {
685
685
  */
686
686
  setResult(result: string): this {
687
687
  this.result = result;
688
+ this.resultRevisions = []; // Clear orphaned revisions from old text
689
+ this.resultContent = []; // Clear raw XML content since we have new text
688
690
  return this;
689
691
  }
690
692
 
@@ -22,6 +22,7 @@ import type { Run } from '../elements/Run';
22
22
  import type { Hyperlink } from '../elements/Hyperlink';
23
23
  import { isRunContent, isHyperlinkContent, isImageRunContent } from '../elements/RevisionContent';
24
24
  import type { ImageRun } from '../elements/ImageRun';
25
+ import { ComplexField } from '../elements/Field';
25
26
  import { Table } from '../elements/Table';
26
27
  import { Section } from '../elements/Section';
27
28
  import { getGlobalLogger, createScopedLogger, ILogger } from './logger';
@@ -544,6 +545,18 @@ function acceptRevisionsInParagraph(
544
545
  // If we reach here, this revision type is not being accepted
545
546
  // Keep it in the content
546
547
  newContent.push(item);
548
+ } else if (item instanceof ComplexField && item.hasResultRevisions()) {
549
+ // Accept revisions nested inside ComplexField result sections
550
+ const fieldRevisions = item.getResultRevisions();
551
+ for (const rev of fieldRevisions) {
552
+ const revType = rev.getType();
553
+ if (revType === 'insert' && options.acceptInsertions) result.insertionsAccepted++;
554
+ else if (revType === 'delete' && options.acceptDeletions) result.deletionsAccepted++;
555
+ else if ((revType === 'moveFrom' || revType === 'moveTo') && options.acceptMoves)
556
+ result.movesAccepted++;
557
+ }
558
+ item.setResultRevisions([]); // Clear after acceptance
559
+ newContent.push(item);
547
560
  } else {
548
561
  // Non-revision content - keep as-is
549
562
  newContent.push(item);