From 99528b193554d96f6d62e23a2e3b9b209f094fa1 Mon Sep 17 00:00:00 2001 From: Luccas Correa Date: Tue, 10 Feb 2026 14:35:03 -0300 Subject: [PATCH 1/5] fix: correctly escape HTML entities in styles.xml during export --- packages/super-editor/src/core/Editor.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/super-editor/src/core/Editor.ts b/packages/super-editor/src/core/Editor.ts index 89e2e80e67..003eff93b2 100644 --- a/packages/super-editor/src/core/Editor.ts +++ b/packages/super-editor/src/core/Editor.ts @@ -2603,8 +2603,9 @@ export class Editor extends EventEmitter { 'word/_rels/document.xml.rels': String(rels), 'word/numbering.xml': String(numbering), - // Replace & with & in styles.xml as DOCX viewers can't handle it - 'word/styles.xml': String(styles).replace(/&/gi, '&'), + // Replace bare & with & in styles.xml as DOCX viewers can't handle it + // but avoid double-escaping existing entities like &. + 'word/styles.xml': String(styles).replace(/&(?!#\d+;|#x[0-9a-fA-F]+;|[a-zA-Z][a-zA-Z0-9]+;)/g, '&'), ...updatedHeadersFooters, ...(coreXml ? { 'docProps/core.xml': String(coreXml) } : {}), }; From f9deb7955a06428e700fcb57ee4ee1bfc05a9690 Mon Sep 17 00:00:00 2001 From: Luccas Correa Date: Tue, 10 Feb 2026 14:43:29 -0300 Subject: [PATCH 2/5] fix: test document styles --- e2e-tests/test-data/basic-documents/sdpr.docx | Bin 88410 -> 80605 bytes .../super-editor/src/tests/data/sdpr.docx | Bin 21002 -> 21073 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/e2e-tests/test-data/basic-documents/sdpr.docx b/e2e-tests/test-data/basic-documents/sdpr.docx index 55994659570ed28b7fbf58479cc309cfdde5ef65..e31ad385a9ab60a6837f407734381eae1ddb0b15 100644 GIT binary patch delta 2269 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 RatREgW1|EB_zivq{}+|$00961 delta 4045 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 From 5c9921bff9763a1fca54d79ab6e8fd7f143d96de Mon Sep 17 00:00:00 2001 From: Luccas Correa Date: Tue, 10 Feb 2026 16:51:31 -0300 Subject: [PATCH 3/5] fix: only allow valid XML entities in styles.xml --- packages/super-editor/src/core/Editor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/super-editor/src/core/Editor.ts b/packages/super-editor/src/core/Editor.ts index 003eff93b2..dad6c3238c 100644 --- a/packages/super-editor/src/core/Editor.ts +++ b/packages/super-editor/src/core/Editor.ts @@ -2603,9 +2603,9 @@ export class Editor extends EventEmitter { 'word/_rels/document.xml.rels': String(rels), 'word/numbering.xml': String(numbering), - // Replace bare & with & in styles.xml as DOCX viewers can't handle it - // but avoid double-escaping existing entities like &. - 'word/styles.xml': String(styles).replace(/&(?!#\d+;|#x[0-9a-fA-F]+;|[a-zA-Z][a-zA-Z0-9]+;)/g, '&'), + // Replace bare & with & in styles.xml as DOCX viewers can't handle it, + // but avoid double-escaping valid XML entities and numeric references. + 'word/styles.xml': String(styles).replace(/&(?!#\d+;|#x[0-9a-fA-F]+;|(?:amp|lt|gt|quot|apos);)/g, '&'), ...updatedHeadersFooters, ...(coreXml ? { 'docProps/core.xml': String(coreXml) } : {}), }; From 2fa5830af753196c4aea519cc727f83d267192c9 Mon Sep 17 00:00:00 2001 From: Luccas Correa Date: Wed, 11 Feb 2026 10:14:46 -0300 Subject: [PATCH 4/5] fix: remove double encoding of HTML entities --- packages/super-editor/src/core/Editor.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/super-editor/src/core/Editor.ts b/packages/super-editor/src/core/Editor.ts index dad6c3238c..dd6197a551 100644 --- a/packages/super-editor/src/core/Editor.ts +++ b/packages/super-editor/src/core/Editor.ts @@ -2602,10 +2602,7 @@ export class Editor extends EventEmitter { 'docProps/custom.xml': String(customXml), 'word/_rels/document.xml.rels': String(rels), 'word/numbering.xml': String(numbering), - - // Replace bare & with & in styles.xml as DOCX viewers can't handle it, - // but avoid double-escaping valid XML entities and numeric references. - 'word/styles.xml': String(styles).replace(/&(?!#\d+;|#x[0-9a-fA-F]+;|(?:amp|lt|gt|quot|apos);)/g, '&'), + 'word/styles.xml': String(styles), ...updatedHeadersFooters, ...(coreXml ? { 'docProps/core.xml': String(coreXml) } : {}), }; From e70be3bec24152c391ed90930075110234ae095b Mon Sep 17 00:00:00 2001 From: Luccas Correa Date: Wed, 11 Feb 2026 10:15:34 -0300 Subject: [PATCH 5/5] fix: add guard in HTML entity replacement regex --- .../src/core/super-converter/exporter.js | 2 +- .../src/tests/export/docxExporter.test.js | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/super-editor/src/core/super-converter/exporter.js b/packages/super-editor/src/core/super-converter/exporter.js index 39ab356902..685f44d313 100644 --- a/packages/super-editor/src/core/super-converter/exporter.js +++ b/packages/super-editor/src/core/super-converter/exporter.js @@ -565,7 +565,7 @@ export class DocxExporter { #replaceSpecialCharacters(text) { if (text === undefined || text === null) return text; return String(text) - .replace(/&/g, '&') + .replace(/&(?!#\d+;|#x[0-9a-fA-F]+;|(?:amp|lt|gt|quot|apos);)/g, '&') .replace(//g, '>') .replace(/"/g, '"') diff --git a/packages/super-editor/src/tests/export/docxExporter.test.js b/packages/super-editor/src/tests/export/docxExporter.test.js index a4c0dcfae3..7581d6e2e1 100644 --- a/packages/super-editor/src/tests/export/docxExporter.test.js +++ b/packages/super-editor/src/tests/export/docxExporter.test.js @@ -40,6 +40,31 @@ describe('DocxExporter', () => { expect(xml).toContain('Format=<<NUM>>_<<VER>>'); }); + it('does not double-escape ampersands in text nodes', () => { + const exporter = new DocxExporter(createConverterStub()); + + const data = { + name: 'w:document', + attributes: {}, + elements: [ + { + name: 'w:t', + elements: [ + { + type: 'text', + text: 'Rock & Roll & Jazz', + }, + ], + }, + ], + }; + + const xml = exporter.schemaToXml(data); + + expect(xml).toContain('Rock & Roll & Jazz'); + expect(xml).not.toContain('&amp;'); + }); + describe('error handling for text elements', () => { it('handles missing elements array gracefully', () => { const exporter = new DocxExporter(createConverterStub());