From dd9bd69bb6421b7e5f6c23562849f90c08282fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=A9s=20Nieto?= Date: Tue, 3 Feb 2026 11:45:06 +0100 Subject: [PATCH 1/8] DEV-25396 Add document protection to settings --- lib/files/src/SettingsXml.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/files/src/SettingsXml.ts b/lib/files/src/SettingsXml.ts index 7e87bd2..a43f469 100644 --- a/lib/files/src/SettingsXml.ts +++ b/lib/files/src/SettingsXml.ts @@ -205,9 +205,9 @@ export class SettingsXml extends XmlFileWithContentTypes { if ($attachedTemplate) then element ${QNS.w}attachedTemplate { attribute ${QNS.r}id { $attachedTemplate } } else (), - if ($documentProtection) then element ${QNS.w}documentProtection { - attribute ${QNS.w}edit { map:get($documentProtection, 'edit') }, - attribute ${QNS.w}enforcement { map:get($documentProtection, 'enforcement') } + if (exists($documentProtection)) then element ${QNS.w}documentProtection { + attribute ${QNS.w}edit { $documentProtection('edit')}, + attribute ${QNS.w}enforcement { $documentProtection('enforcement') } } else (), if (exists($footnoteProperties)) then ( element ${QNS.w}footnotePr { @@ -228,8 +228,8 @@ export class SettingsXml extends XmlFileWithContentTypes { } } ) else (), - if (exists($defaultTabStop)) then element ${QNS.w}defaultTabStop { - attribute ${QNS.w}val { map:get($defaultTabStop, 'twip') } + if (exists($defaultTabStop)) then element ${QNS.w}defaultTabStop { + attribute ${QNS.w}val { $defaultTabStop('twip') } } else () } `, @@ -280,7 +280,12 @@ export class SettingsXml extends XmlFileWithContentTypes { `/${QNS.w}settings/map { "isTrackChangesEnabled": docxml:ct-on-off(./${QNS.w}trackChanges), "evenAndOddHeaders": docxml:ct-on-off(./${QNS.w}evenAndOddHeaders), - "documentProtection": docxml:ct-on-off(./${QNS.w}documentProtection) + "documentProtection": if (./${QNS.w}documentProtection) then ( + map { + "edit": string(./${QNS.w}documentProtection/@${QNS.w}edit), + "enforcement": docxml:st-on-off(./${QNS.w}documentProtection/@${QNS.w}enforcement) + } + ) else () }`, xml ); From 57d21f86e17f0ccb3feb12e39664e86bc9b8c450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=A9s=20Nieto?= Date: Fri, 27 Feb 2026 16:08:51 +0100 Subject: [PATCH 2/8] add tests --- lib/files/test/SettingsXml.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/files/test/SettingsXml.test.ts b/lib/files/test/SettingsXml.test.ts index 2f6a6ae..2fb72a1 100644 --- a/lib/files/test/SettingsXml.test.ts +++ b/lib/files/test/SettingsXml.test.ts @@ -24,11 +24,11 @@ describe('SettingsXml', () => { settings.set('attachedTemplate', 'foobar'); expect(settings.get('attachedTemplate')).toBe('foobar'); const meta = settings.relationships.meta.find( - (meta) => meta.type === RelationshipType.attachedTemplate + (meta) => meta.type === RelationshipType.attachedTemplate, ); expect(meta).toBeTruthy(); expect(settings.relationships.getTarget(meta?.id as string)).toBe( - 'foobar' + 'foobar', ); }); it('defaultTabStop', () => { From 5beb2842c560e5d3a772cda7425786db4cb04eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=A9s=20Nieto?= Date: Fri, 27 Feb 2026 16:15:41 +0100 Subject: [PATCH 3/8] fix format --- lib/files/test/SettingsXml.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/files/test/SettingsXml.test.ts b/lib/files/test/SettingsXml.test.ts index 2fb72a1..2f6a6ae 100644 --- a/lib/files/test/SettingsXml.test.ts +++ b/lib/files/test/SettingsXml.test.ts @@ -24,11 +24,11 @@ describe('SettingsXml', () => { settings.set('attachedTemplate', 'foobar'); expect(settings.get('attachedTemplate')).toBe('foobar'); const meta = settings.relationships.meta.find( - (meta) => meta.type === RelationshipType.attachedTemplate, + (meta) => meta.type === RelationshipType.attachedTemplate ); expect(meta).toBeTruthy(); expect(settings.relationships.getTarget(meta?.id as string)).toBe( - 'foobar', + 'foobar' ); }); it('defaultTabStop', () => { From c33fd96c9c56f739d1f51d9bb89016674f71611c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=A9s=20Nieto?= Date: Tue, 3 Mar 2026 15:04:12 +0100 Subject: [PATCH 4/8] Fix document protection and add a sample --- examples/protected-document.tsx | 24 ++++++++++++++++++++++++ lib/files/src/SettingsXml.ts | 6 +++--- 2 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 examples/protected-document.tsx diff --git a/examples/protected-document.tsx b/examples/protected-document.tsx new file mode 100644 index 0000000..9c6500e --- /dev/null +++ b/examples/protected-document.tsx @@ -0,0 +1,24 @@ +/** @jsx Docx.jsx */ +import { Text } from '../lib/components/document/src/Text.ts'; +import type { DocumentProtectionProps } from '../lib/files/src/SettingsXml.ts'; +import { cm } from '../lib/utilities/src/length.ts'; +import Docx, { Paragraph, Section } from '../mod.ts'; + +const docxFile = Docx.fromNothing(); + +const documentProtection: DocumentProtectionProps = { + edit: 'readOnly', + enforcement: true, +}; + +docxFile.document.settings.set('documentProtection', documentProtection); + +docxFile.document.set( +
+ + This is some protected text + +
+); + +await docxFile.toFile('protected-document.docx'); diff --git a/lib/files/src/SettingsXml.ts b/lib/files/src/SettingsXml.ts index a43f469..9a777a8 100644 --- a/lib/files/src/SettingsXml.ts +++ b/lib/files/src/SettingsXml.ts @@ -205,9 +205,9 @@ export class SettingsXml extends XmlFileWithContentTypes { if ($attachedTemplate) then element ${QNS.w}attachedTemplate { attribute ${QNS.r}id { $attachedTemplate } } else (), - if (exists($documentProtection)) then element ${QNS.w}documentProtection { - attribute ${QNS.w}edit { $documentProtection('edit')}, - attribute ${QNS.w}enforcement { $documentProtection('enforcement') } + if ($documentProtection) then element ${QNS.w}documentProtection { + attribute ${QNS.w}edit { map:get($documentProtection, 'edit') }, + attribute ${QNS.w}enforcement { map:get($documentProtection, 'enforcement') } } else (), if (exists($footnoteProperties)) then ( element ${QNS.w}footnotePr { From b68f51aefa34bde7bf57daf94babc925c1c5b98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81ngel=20G=C3=B3mez?= Date: Tue, 3 Mar 2026 15:38:24 +0100 Subject: [PATCH 5/8] fix --- lib/files/test/SettingsXml.test.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/files/test/SettingsXml.test.ts b/lib/files/test/SettingsXml.test.ts index 2f6a6ae..3981588 100644 --- a/lib/files/test/SettingsXml.test.ts +++ b/lib/files/test/SettingsXml.test.ts @@ -1,9 +1,15 @@ import { expect } from 'std/expect'; import { describe, it } from 'std/testing/bdd'; +import Docx from '@fontoxml/docxml'; + import { RelationshipType } from '../../enums.ts'; +import { serialize } from '../../utilities/src/dom.ts'; import { pt } from '../../utilities/src/length.ts'; -import { SettingsXml } from '../src/SettingsXml.ts'; +import { + type DocumentProtectionProps, + SettingsXml, +} from '../src/SettingsXml.ts'; describe('SettingsXml', () => { it('evenAndOddHeaders', () => { @@ -51,7 +57,7 @@ describe('SettingsXml', () => { position: 'beneathText', }); }); - it('DocumentProtectionProps', () => { + it('documentProtection', async () => { const settings = new SettingsXml('test'); expect(settings.get('documentProtection')).toBe(null); settings.set('documentProtection', { @@ -62,5 +68,20 @@ describe('SettingsXml', () => { edit: 'readOnly', enforcement: true, }); + + expect(serialize(await settings.$$$toNode())).toEqual( + '' + ); + + const docx = Docx.fromNothing(); + const customSettings = { + edit: 'trackedChanges', + enforcement: false, + } as DocumentProtectionProps; + docx.document.settings.set('documentProtection', customSettings); + const docxFromArchive = await Docx.fromArchive(await docx.toArchive()); + expect( + docxFromArchive.document.settings.get('documentProtection') + ).toEqual(customSettings); }); }); From b0ab1208bbba162c23cdb3691bf15e34f2e50733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=A9s=20Nieto?= Date: Tue, 3 Mar 2026 15:43:44 +0100 Subject: [PATCH 6/8] tiny changes --- lib/files/test/SettingsXml.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/files/test/SettingsXml.test.ts b/lib/files/test/SettingsXml.test.ts index 3981588..3d93e14 100644 --- a/lib/files/test/SettingsXml.test.ts +++ b/lib/files/test/SettingsXml.test.ts @@ -70,14 +70,14 @@ describe('SettingsXml', () => { }); expect(serialize(await settings.$$$toNode())).toEqual( - '' + `` ); const docx = Docx.fromNothing(); - const customSettings = { + const customSettings: DocumentProtectionProps = { edit: 'trackedChanges', enforcement: false, - } as DocumentProtectionProps; + }; docx.document.settings.set('documentProtection', customSettings); const docxFromArchive = await Docx.fromArchive(await docx.toArchive()); expect( From ebfec01710f4c929d709eba8dce83879a02c0bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=A9s=20Nieto?= Date: Tue, 3 Mar 2026 15:51:10 +0100 Subject: [PATCH 7/8] fix --- lib/files/src/SettingsXml.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/files/src/SettingsXml.ts b/lib/files/src/SettingsXml.ts index 9a777a8..a43f469 100644 --- a/lib/files/src/SettingsXml.ts +++ b/lib/files/src/SettingsXml.ts @@ -205,9 +205,9 @@ export class SettingsXml extends XmlFileWithContentTypes { if ($attachedTemplate) then element ${QNS.w}attachedTemplate { attribute ${QNS.r}id { $attachedTemplate } } else (), - if ($documentProtection) then element ${QNS.w}documentProtection { - attribute ${QNS.w}edit { map:get($documentProtection, 'edit') }, - attribute ${QNS.w}enforcement { map:get($documentProtection, 'enforcement') } + if (exists($documentProtection)) then element ${QNS.w}documentProtection { + attribute ${QNS.w}edit { $documentProtection('edit')}, + attribute ${QNS.w}enforcement { $documentProtection('enforcement') } } else (), if (exists($footnoteProperties)) then ( element ${QNS.w}footnotePr { From 8551040254f1d3841cd1b38e3e69b9eb94033a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=A9s=20Nieto?= Date: Tue, 3 Mar 2026 16:46:35 +0100 Subject: [PATCH 8/8] fix --- lib/files/src/SettingsXml.ts | 13 +++++++++---- lib/files/test/SettingsXml.test.ts | 26 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/files/src/SettingsXml.ts b/lib/files/src/SettingsXml.ts index a43f469..aa4b6e3 100644 --- a/lib/files/src/SettingsXml.ts +++ b/lib/files/src/SettingsXml.ts @@ -206,7 +206,7 @@ export class SettingsXml extends XmlFileWithContentTypes { attribute ${QNS.r}id { $attachedTemplate } } else (), if (exists($documentProtection)) then element ${QNS.w}documentProtection { - attribute ${QNS.w}edit { $documentProtection('edit')}, + if ($documentProtection('edit')) then attribute ${QNS.w}edit { $documentProtection('edit')} else (), attribute ${QNS.w}enforcement { $documentProtection('enforcement') } } else (), if (exists($footnoteProperties)) then ( @@ -281,9 +281,14 @@ export class SettingsXml extends XmlFileWithContentTypes { "isTrackChangesEnabled": docxml:ct-on-off(./${QNS.w}trackChanges), "evenAndOddHeaders": docxml:ct-on-off(./${QNS.w}evenAndOddHeaders), "documentProtection": if (./${QNS.w}documentProtection) then ( - map { - "edit": string(./${QNS.w}documentProtection/@${QNS.w}edit), - "enforcement": docxml:st-on-off(./${QNS.w}documentProtection/@${QNS.w}enforcement) + let $edit := string(./${QNS.w}documentProtection/@${QNS.w}edit) + let $enforcement := docxml:st-on-off(./${QNS.w}documentProtection/@${QNS.w}enforcement) + return if ($edit) + then map { + "edit": $edit, + "enforcement": $enforcement + } else map { + "enforcement": $enforcement } ) else () }`, diff --git a/lib/files/test/SettingsXml.test.ts b/lib/files/test/SettingsXml.test.ts index 3d93e14..28f51a2 100644 --- a/lib/files/test/SettingsXml.test.ts +++ b/lib/files/test/SettingsXml.test.ts @@ -57,7 +57,7 @@ describe('SettingsXml', () => { position: 'beneathText', }); }); - it('documentProtection', async () => { + it('documentProtection with edit property', async () => { const settings = new SettingsXml('test'); expect(settings.get('documentProtection')).toBe(null); settings.set('documentProtection', { @@ -84,4 +84,28 @@ describe('SettingsXml', () => { docxFromArchive.document.settings.get('documentProtection') ).toEqual(customSettings); }); + it('documentProtection without edit', async () => { + const settings = new SettingsXml('test'); + expect(settings.get('documentProtection')).toBe(null); + settings.set('documentProtection', { + enforcement: true, + }); + expect(settings.get('documentProtection')).toEqual({ + enforcement: true, + }); + + expect(serialize(await settings.$$$toNode())).toEqual( + `` + ); + + const docx = Docx.fromNothing(); + const customSettings: DocumentProtectionProps = { + enforcement: false, + }; + docx.document.settings.set('documentProtection', customSettings); + const docxFromArchive = await Docx.fromArchive(await docx.toArchive()); + expect( + docxFromArchive.document.settings.get('documentProtection') + ).toEqual(customSettings); + }); });