diff --git a/e2e-tests/test-data/basic-documents/sdpr.docx b/e2e-tests/test-data/basic-documents/sdpr.docx index 5599465957..e31ad385a9 100644 Binary files a/e2e-tests/test-data/basic-documents/sdpr.docx and b/e2e-tests/test-data/basic-documents/sdpr.docx differ diff --git a/packages/super-editor/src/core/Editor.ts b/packages/super-editor/src/core/Editor.ts index 89e2e80e67..dd6197a551 100644 --- a/packages/super-editor/src/core/Editor.ts +++ b/packages/super-editor/src/core/Editor.ts @@ -2602,9 +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 & with & in styles.xml as DOCX viewers can't handle it - 'word/styles.xml': String(styles).replace(/&/gi, '&'), + 'word/styles.xml': String(styles), ...updatedHeadersFooters, ...(coreXml ? { 'docProps/core.xml': String(coreXml) } : {}), }; 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/data/sdpr.docx b/packages/super-editor/src/tests/data/sdpr.docx index b3b56b8fda..ebb7bb1013 100644 Binary files a/packages/super-editor/src/tests/data/sdpr.docx and b/packages/super-editor/src/tests/data/sdpr.docx differ 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());